Skip to content

Go SDK ​

The official HashNut Go SDK provides a convenient wrapper around the HashNut Payment API.

Installation ​

bash
go get github.com/nuttybounty/hashnut-sdk-go/v4

Initialize the Client ​

go
import hashnut "github.com/nuttybounty/hashnut-sdk-go/v4"

// Always pass false as the second argument — production (https://defi.hashnut.io/api/v4.0.0)
client := hashnut.NewClient("your-secret-key", false)
go
import hashnut "github.com/nuttybounty/hashnut-sdk-go/v4"

// WithBaseURL must include /api/v4.0.0 — the SDK appends relative paths.
client := hashnut.NewClient(
	"your-secret-key",
	false,
	hashnut.WithBaseURL("https://custom.endpoint.com/api/v4.0.0"),
)

NOTE

The SDK takes only the secretKey. accessKeyId is a per-request parameter (CreateOrderRequest.AccessKeyID), not a client constructor argument.

Methods ​

CreateOrder ​

Create a new payment order.

go
import "github.com/nuttybounty/hashnut-sdk-go/v4/model"

order, err := client.CreateOrder(&model.CreateOrderRequest{
	AccessKeyID:     "your-access-key-id",
	MerchantOrderID: "ORDER-001",
	BlockChain:      "ETH",
	TokenSymbol:     "usdt",
	Amount:          "25.00",
	SplitterAddress: "0xYourSplitterAddress",
	// Optional fields
	Subject:        "Monthly Subscription",
	ExpireDuration: 1800, // seconds
	CallbackURL:    "https://yoursite.com/payment-result",
})
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Pay Order ID:    %s\n", order.PayOrderID)
fmt.Printf("Receipt Address: %s\n", order.ReceiptAddress)
fmt.Printf("Pay URL:         %s\n", order.PayURL)

IMPORTANT

Persist order.AccessSign alongside payOrderId in your own order table. Every later QueryOrder / ConfirmPaid call needs it, and you cannot recompute it — the server derives it by HMAC-ing payOrderId + merchantOrderId with your secretKey.

QueryOrder ​

Query the current state of an order. All three fields are required; the server errors out if any is missing.

go
result, err := client.QueryOrder(&model.QueryOrderRequest{
	PayOrderID:      order.PayOrderID,
	MerchantOrderID: "ORDER-001",
	AccessSign:      order.AccessSign, // from the create-order response
})
if err != nil {
	log.Fatal(err)
}

// Numeric fields like State and Amount are json.Number, not int
fmt.Printf("State:   %s\n", result.State.String())
fmt.Printf("Amount:  %s\n", result.Amount.String())
fmt.Printf("Tx Hash: %s\n", result.PayTxID)

ConfirmPaid ​

Confirm a payment manually with its transaction hash. Returns only an error, not the order — call QueryOrder afterwards to read the new state.

go
err := client.ConfirmPaid(&model.ConfirmPaidRequest{
	PayOrderID:      order.PayOrderID,
	MerchantOrderID: "ORDER-001",
	AccessSign:      order.AccessSign,
	PayTxID:         "0x9876543210fedcba...",
})
if err != nil {
	log.Fatal(err)
}

CancelOrder ​

Cancel an unpaid order. Takes only PayOrderID and likewise returns only an error.

go
err := client.CancelOrder(&model.CancelOrderRequest{
	PayOrderID: order.PayOrderID,
})
if err != nil {
	log.Fatal(err)
}
// The order moves to state -3 (CANCELED); confirm with QueryOrder

QueryChains ​

Query all supported blockchain networks. Takes no arguments.

go
chains, err := client.QueryChains()
if err != nil {
	log.Fatal(err)
}

for _, chain := range chains {
	fmt.Printf("%s chainId=%d confirms=%d eip1559=%v\n",
		chain.BlockChain, chain.ChainID, chain.TxConfirmCount, chain.EIP1559Support)
}

QueryCoins ​

Query all supported tokens. Takes no arguments — fetch the full list and filter by BlockChain yourself.

go
coins, err := client.QueryCoins()
if err != nil {
	log.Fatal(err)
}

for _, coin := range coins {
	if coin.BlockChain != "ETH" {
		continue
	}
	fmt.Printf("%s/%s decimals=%d contract=%s\n",
		coin.BlockChain, coin.TokenSymbol, coin.Decimals, coin.ContractAddress)
}

Error Handling ​

All methods return an error as the second return value. The SDK does not export a structured error type; API business errors are wrapped into an error whose message looks like api error (code=1): xxx:

go
_, err := client.CreateOrder(&model.CreateOrderRequest{
	// ...
})
if err != nil {
	// business error: api error (code=1): invalid request sign
	// HTTP error:     http 502: <body>
	// network/parse:  http request: ... / unmarshal response: ...
	fmt.Printf("create order failed: %v\n", err)
	return
}

WARNING

Do not use errors.As to assert a concrete error type — the SDK returns plain errors built with fmt.Errorf. To branch on error codes, match the message, or take over the request yourself with WithHTTPClient.

WARNING

Numeric fields in API responses (such as amount and paidAmount) are returned as json.Number to handle both string and number representations from the backend. Use .String() to read the value or .Int64() / .Float64() for numeric conversion.

Complete Example ​

go
package main

import (
	"fmt"
	"log"
	"time"

	hashnut "github.com/nuttybounty/hashnut-sdk-go/v4"
	"github.com/nuttybounty/hashnut-sdk-go/v4/model"
)

func main() {
	client := hashnut.NewClient("your-secret-key", false) // second argument is always false

	order, err := client.CreateOrder(&model.CreateOrderRequest{
		AccessKeyID:     "your-access-key-id",
		MerchantOrderID: fmt.Sprintf("ORDER-%d", time.Now().Unix()),
		BlockChain:      "ETH",
		TokenSymbol:     "usdt",
		Amount:          "1.00",
		SplitterAddress: "0xYourSplitterAddress",
		Subject:         "Test Order",
	})
	if err != nil {
		log.Fatalf("create order: %v", err)
	}

	// In a real integration, persist PayOrderID + AccessSign — later calls need them
	fmt.Printf("Order created: %s\n", order.PayOrderID)
	fmt.Printf("Send funds to: %s usdt -> %s\n", order.Amount.String(), order.ReceiptAddress)
	fmt.Printf("Pay URL: %s\n", order.PayURL)

	// Poll for payment (in production, use webhooks instead)
	for i := 0; i < 60; i++ {
		time.Sleep(10 * time.Second)

		result, err := client.QueryOrder(&model.QueryOrderRequest{
			PayOrderID:      order.PayOrderID,
			MerchantOrderID: order.MerchantOrderID,
			AccessSign:      order.AccessSign,
		})
		if err != nil {
			log.Printf("Query error: %v", err)
			continue
		}

		// State is a json.Number: convert to int64 before comparing
		state, err := result.State.Int64()
		if err != nil {
			log.Printf("bad state %q: %v", result.State.String(), err)
			continue
		}

		fmt.Printf("State: %d\n", state)
		if state == 3 { // 3 = SUCCESS, 4 = FINISH
			fmt.Println("Payment successful!")
			return
		}
		if state < 0 { // -1 FAILED / -2 EXPIRED / -3 CANCELED
			log.Fatalf("order ended in state %d", state)
		}
	}

	log.Println("Polling timed out; order still not paid")
}

TIP

In production, prefer webhook notifications over polling. Polling is shown here for simplicity.