# GO SDK Microservices

This guide provides a complete walkthrough for setting up and running a Go-based microservice that interacts with the Züs decentralized storage network.

### &#x20;1. Prerequisites Setup

Before you begin:

#### Install Go

install [Go (version 1.20+ recommended)](https://golang.org/dl/). Verify installation:

```bash
go version
```

#### Get a Züs Wallet

You’ll need a `.json` wallet file. &#x20;

It is typically located at:

```bash
~/.zcn/wallet.json
```

Alternatively, you can store the wallet JSON file in any location of your choice. To retrieve your wallet information, you can use the **recover wallet** command from [`zwalletcli`](https://github.com/0chain/zwalletcli?tab=readme-ov-file#recovering-wallet---recoverwallet).

Or you can use any location to create the json file. You can retrieve your wallet info by using [recover wallet](https://github.com/0chain/zwalletcli?tab=readme-ov-file#recovering-wallet---recoverwallet) from the zwalletcli.

Below is sample wallet structure:

```
{
  "client_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "client_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "keys": [
    {
      "public_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "private_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    }
  ],
  "mnemonics": "xxxx xxxx xxxx xxxxx",
  "version": "1.0",
  "date_created": "2023-05-03T12:44:46+05:30",
  "nonce": 0
}
```

#### Create `config.json`

Create a file named `config.json` in your project root with the following structure:

```json
{
  "wallet_json": "/Users/yourusername/.zcn/wallet.json",
  "block_worker": "https://mainnet.zus.network/dns",
  "chain_id": "0af33bc5402b5f13e349347db8b88b6543157baadb7c9265ef0a8f87429e4a44",
  "signature_scheme": "bls0chain",
  "min_submit": 50,
  "min_confirmation": 50,
  "confirmation_chain_length": 3,
  "sharder_consensous": 3
}
```

### 2. Project File Structure

Your project should be structured as follows:

```
/project-root
├── config.json
├── main.go
└── handlers/
    ├── download_handler.go
    ├── memfile_handler.go
    ├── sdk_handler.go
    └── upload_handler.go
```

### 3. Code Your `main.go`

```go
package main

import (
	"log"
	"net/http"

	"go_zus_blog/handlers" // Replace with your module name if different
	"github.com/gorilla/mux"
)

func main() {
	router := mux.NewRouter()

	router.HandleFunc("/init-sdk", handlers.InitSDKHandler).Methods("POST")
	router.HandleFunc("/upload-file", handlers.UploadFileHandler).Methods("POST")
	router.HandleFunc("/download-file", handlers.DownloadFileHandler).Methods("POST")

	log.Println("Go microservice running on port 8080")
	log.Fatal(http.ListenAndServe(":8080", router))
}
```

Install Gorilla Mux:

```
go get -u github.com/gorilla/mux
```

In case you see an error while importing go\_zus\_handlers, run this command:

```bash
go mod init go_zus_blog
```

### 4. Build Your Handlers

#### `sdk_handler.go`

```go
package handlers

import (
	"encoding/json"
	"log"
	"net/http"
	"os"

	"github.com/0chain/gosdk/core/client"
)

type Config struct {
	WalletJSON              string `json:"wallet_json"`
	BlockWorker             string `json:"block_worker"`
	ChainID                 string `json:"chain_id"`
	SignatureScheme         string `json:"signature_scheme"`
	MinSubmit               int    `json:"min_submit"`
	MinConfirmation         int    `json:"min_confirmation"`
	ConfirmationChainLength int    `json:"confirmation_chain_length"`
	SharderConsensous       int    `json:"sharder_consensous"`
}

func InitSDKHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	file, err := os.ReadFile("./config.json")
	if err != nil {
		http.Error(w, "Failed to read config.json: "+err.Error(), http.StatusInternalServerError)
		return
	}

	var config Config
	err = json.Unmarshal(file, &config)
	if err != nil {
		http.Error(w, "Invalid config.json: "+err.Error(), http.StatusInternalServerError)
		return
	}

	if config.WalletJSON == "" {
		http.Error(w, "Missing wallet JSON path", http.StatusBadRequest)
		return
	}

	if _, err := os.Stat(config.WalletJSON); os.IsNotExist(err) {
		http.Error(w, "Wallet JSON file not found: "+config.WalletJSON, http.StatusBadRequest)
		return
	}

	walletContent, err := os.ReadFile(config.WalletJSON)
	if err != nil {
		http.Error(w, "Failed to read wallet JSON: "+err.Error(), http.StatusInternalServerError)
		return
	}

	err = client.InitSDK(
		string(walletContent),
		config.BlockWorker,
		config.ChainID,
		config.SignatureScheme,
		0,
		true,
		0,
		config.MinSubmit,
		config.MinConfirmation,
		config.ConfirmationChainLength,
		config.SharderConsensous,
	)
	if err != nil {
		http.Error(w, "SDK initialization failed: "+err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{"message": "SDK initialized successfully"}`))
}
```

#### `memfile_handler.go`

```go
package handlers

import (
	"bytes"
	"errors"
	"io"
	"os"
	"sync"
	"time"
)

type MemFile struct {
	buffer  *bytes.Buffer
	mu      sync.Mutex
	name    string
	modTime time.Time
	mode    os.FileMode
}

func NewMemFile(name string) *MemFile {
	return &MemFile{
		buffer:  new(bytes.Buffer),
		name:    name,
		modTime: time.Now(),
		mode:    0644,
	}
}

func (m *MemFile) Read(p []byte) (int, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.buffer.Read(p)
}

func (m *MemFile) Write(p []byte) (int, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.buffer.Write(p)
}

func (m *MemFile) Seek(offset int64, whence int) (int64, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	switch whence {
	case io.SeekStart:
		m.buffer = bytes.NewBuffer(m.buffer.Bytes()[offset:])
		return offset, nil
	case io.SeekEnd:
		m.buffer = bytes.NewBuffer(m.buffer.Bytes()[len(m.buffer.Bytes())+int(offset):])
		return int64(len(m.buffer.Bytes())), nil
	case io.SeekCurrent:
		return 0, errors.New("unsupported seek method")
	}
	return 0, errors.New("invalid seek whence")
}

func (m *MemFile) Close() error {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.buffer.Reset()
	return nil
}

func (m *MemFile) Stat() (os.FileInfo, error) {
	return &memFileInfo{
		name:    m.name,
		size:    int64(m.buffer.Len()),
		modTime: m.modTime,
		mode:    m.mode,
	}, nil
}

func (m *MemFile) Sync() error {
	return nil
}

func (m *MemFile) ToBytes() []byte {
	return m.buffer.Bytes()
}

type memFileInfo struct {
	name    string
	size    int64
	modTime time.Time
	mode    os.FileMode
}

func (fi *memFileInfo) Name() string       { return fi.name }
func (fi *memFileInfo) Size() int64        { return fi.size }
func (fi *memFileInfo) Mode() os.FileMode  { return fi.mode }
func (fi *memFileInfo) ModTime() time.Time { return fi.modTime }
func (fi *memFileInfo) IsDir() bool        { return false }
func (fi *memFileInfo) Sys() interface{}   { return nil }
```

#### `upload_handler.go`

```go
package handlers

import (
	"encoding/json"
	"log"
	"net/http"
	"os"
	"path/filepath"

	"github.com/0chain/gosdk/constants"
	"github.com/0chain/gosdk/zboxcore/fileref"
	"github.com/0chain/gosdk/zboxcore/sdk"
)

type ShortURLResponse struct {
	AuthTicket string `json:"auth_ticket"`
}

type UploadRequest struct {
	AllocationID     string `json:"allocation_id"`
	LocalPath        string `json:"local_path"`
	Encrypt          bool   `json:"encrypt"`
	WebStreaming     bool   `json:"web_streaming"`
	IsMemoryDownload bool   `json:"download_file"`
}

func UploadFileHandler(w http.ResponseWriter, r *http.Request) {
	var req UploadRequest

	// Decode the incoming JSON payload
	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		log.Printf("Invalid request payload: %v", err)
		http.Error(w, "Invalid request payload", http.StatusBadRequest)
		return
	}

	log.Printf("Received upload request: %+v", req)

	// Validate required fields
	if req.AllocationID == "" || req.LocalPath == "" {
		log.Printf("Missing required fields: %+v", req)
		http.Error(w, "AllocationID and LocalPath are required", http.StatusBadRequest)
		return
	}

	// Check if the file exists locally
	if _, err := os.Stat(req.LocalPath); os.IsNotExist(err) {
		log.Printf("File not found at: %s", req.LocalPath)
		http.Error(w, "File not found at "+req.LocalPath, http.StatusBadRequest)
		return
	}

	// Generate the remote path using the file name
	fileName := filepath.Base(req.LocalPath)
	remotePath := "/" + fileName
	log.Printf("Generated remote path: %s", remotePath)

	// Get the allocation object using the provided AllocationID
	allocation, err := sdk.GetAllocation(req.AllocationID)
	if err != nil {
		log.Printf("Failed to get allocation: %v", err)
		http.Error(w, "Failed to get allocation: "+err.Error(), http.StatusInternalServerError)
		return
	}

	// Open the local file
	fileReader, err := os.Open(req.LocalPath)
	if err != nil {
		log.Printf("Failed to open file: %v", err)
		http.Error(w, "Failed to open file: "+err.Error(), http.StatusInternalServerError)
		return
	}
	defer fileReader.Close()

	// Get file size
	fileInfo, err := fileReader.Stat()
	if err != nil {
		log.Printf("Failed to get file info: %v", err)
		http.Error(w, "Failed to get file info: "+err.Error(), http.StatusInternalServerError)
		return
	}

	// Set up the operation for the upload
	operations := []sdk.OperationRequest{
		{
			OperationType: constants.FileOperationInsert,
			FileMeta: sdk.FileMeta{
				Path:       req.LocalPath,
				RemotePath: remotePath,
				RemoteName: fileName,
				ActualSize: fileInfo.Size(),
			},
			FileReader:     fileReader,
			IsWebstreaming: req.WebStreaming,
			IsRepair:       false,
			Opts: func() []sdk.ChunkedUploadOption {
				// Include encryption option only if Encrypt is true
				if req.Encrypt {
					return []sdk.ChunkedUploadOption{
						sdk.WithEncrypt(req.Encrypt),
					}
				}
				return nil
			}(),
		},
	}

	// Perform the multi-operation
	log.Println("Starting DoMultiOperation...")
	err = allocation.DoMultiOperation(operations)
	if err != nil {
		log.Printf("Multi-operation failed: %v", err)
		http.Error(w, "Upload failed: "+err.Error(), http.StatusInternalServerError)
		return
	}

	log.Println("File uploaded successfully to Züs network")

	// Generate Auth Ticket
	//authTicket, err := allocation.GetAuthTicketForShare(remotePath, req.FileName, fileref.FILE, "")
	authTicket, err := allocation.GetAuthTicketForShare(remotePath, fileName, fileref.FILE, "")
	if err != nil {
		log.Printf("Failed to generate auth ticket: %v", err)
		http.Error(w, "Failed to generate auth ticket", http.StatusInternalServerError)
		return
	}
	log.Printf("Auth Ticket: %+v", authTicket)

	// Return short URL
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(ShortURLResponse{
		AuthTicket: authTicket,
	})
}

```

#### `download_handler.go`

```go
package handlers

import (
"encoding/json"
"log"
"net/http"

//"path/filepath"
//"strings"

"github.com/0chain/gosdk/zboxcore/fileref"
"github.com/0chain/gosdk/zboxcore/sdk"
)

type DownloadRequest struct {
AllocationID string `json:"allocation_id"`
FileName     string `json:"filename"`
AuthTicket   string `json:"auth_ticket"`
LocalPath    string `json:"local_path"`
}

func DownloadFileHandler(w http.ResponseWriter, r *http.Request) {
var req DownloadRequest

// Decode the incoming JSON payload
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
log.Printf("Invalid request payload: %v", err)
http.Error(w, "Invalid request payload", http.StatusBadRequest)
return
}
log.Printf("Received download request: %+v", req)

allocation, err := sdk.GetAllocation(req.AllocationID)
if err != nil {
http.Error(w, "Failed to get allocation", http.StatusInternalServerError)
return
}

// Generate the remote path using the file name
remotePath := "/" + req.FileName
log.Printf("Generated remote path: %s", remotePath)

remoteLookupHash := fileref.GetReferenceLookup(req.AllocationID, remotePath)
log.Printf("Generated remoteLookupHash: %s", remoteLookupHash)

// Create in-memory file
//memFile := NewMemFile(remotePath)

// Download file
err = allocation.DownloadFromAuthTicket(
req.LocalPath, req.AuthTicket, remoteLookupHash, remotePath, false, nil, true,
)
if err != nil {
http.Error(w, "File download failed", http.StatusInternalServerError)
return
}
```

### 5. Run Your Server

```
go run main.go
```

Expected output:

```
Go microservice running on port 8080
```

### 6. Test With `curl`

#### Initialize SDK

```bash
curl -X POST http://localhost:8080/init-sdk
```

#### Upload a File

```bash
curl -X POST http://localhost:8080/upload-file \
  -H "Content-Type: application/json" \
  -d '{
    "allocation_id": "your_allocation_id",
    "local_path": "/Users/you/Downloads/file.txt",
    "encrypt": false,
    "web_streaming": false,
    "download_file": false
  }'
```

#### Download a File

```bash
curl -X POST http://localhost:8080/download-file \
  -H "Content-Type: application/json" \
  -d '{
    "allocation_id": "allocation-id",
    "filename": "filename of file you would like to download from allocation",
    "auth_ticket": "auth-ticket-string",
  "local_path": "/path/to/local/download"
  }' 
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.zus.network/zus-docs/sdks/go-sdk/go-sdk-microservices.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
