This guide provides a walkthrough for setting up a Go project to interact with the Sonr network using the official Go Client SDK. You will learn how to configure the client, manage keys, query the blockchain, send transactions, and use advanced features like WebAuthn gasless transactions.

Prerequisites

1. Project Setup

1

Initialize a Go Module

Create a new directory for your project and initialize a Go module:
mkdir sonr-go-quickstart
cd sonr-go-quickstart
go mod init github.com/your-username/sonr-go-quickstart
2

Add the Sonr Client SDK Dependency

Add the Sonr Client SDK to your project’s dependencies:
go get github.com/sonr-io/sonr/client

2. Client Configuration and Setup

Let’s set up the Sonr client with proper configuration and key management.
1

Create the Main File

Create a new file named main.go.
2

Initialize the Client

Add the following code to main.go to initialize the Sonr client:
package main

import (
    "context"
    "fmt"
    "log"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"

    "github.com/sonr-io/sonr/client/config"
    "github.com/sonr-io/sonr/client/keys"
    "github.com/sonr-io/sonr/client/sonr"
    "github.com/sonr-io/sonr/client/tx"
)

func main() {
    // Use local network configuration
    cfg := config.LocalNetwork()

    // Establish gRPC connection
    conn, err := grpc.Dial(
        cfg.GRPC,
        grpc.WithTransportCredentials(insecure.NewCredentials()),
    )
    if err != nil {
        log.Fatal("Failed to connect:", err)
    }
    defer conn.Close()

    // Create the Sonr client
    client, err := sonr.NewClient(&cfg, conn)
    if err != nil {
        log.Fatal("Failed to create client:", err)
    }

    fmt.Println("Sonr client initialized successfully!")
    fmt.Printf("Connected to: %s\n", cfg.ChainID)
}
3

Create a Keyring Manager

Add key management to handle wallet operations:
// Initialize keyring manager (using test backend for development)
keyringManager, err := keys.NewKeyringManager(
    "test",           // backend: test, file, os
    ".sonr-keys",     // directory for keys
    cfg.ChainID,      // chain ID
)
if err != nil {
    log.Fatal("Failed to create keyring:", err)
}

// Create a new wallet
walletIdentity, mnemonic, err := keyringManager.CreateWallet(
    context.Background(),
    "my-wallet",  // wallet name
    "",          // passphrase (empty for test backend)
)
if err != nil {
    log.Fatal("Failed to create wallet:", err)
}

fmt.Printf("Wallet created!\n")
fmt.Printf("Address: %s\n", walletIdentity.Address)
fmt.Printf("DID: %s\n", walletIdentity.DID)
fmt.Printf("Mnemonic: %s\n", mnemonic)
Important: In production, use secure keyring backends like “os” or “file” with proper passphrase protection. Never expose mnemonics in your code.

3. Querying the Blockchain

Now, let’s query the blockchain using the unified query client.
1

Create a Query Client

Add the query client to your application:
import (
    "github.com/sonr-io/sonr/client/query"
)

// Create query client
queryClient, err := query.NewQueryClient(conn, &cfg)
if err != nil {
    log.Fatal("Failed to create query client:", err)
}
2

Query Account Balance

Query an account’s balance:
// Query account balance
address := walletIdentity.Address // or any other address
balance, err := queryClient.Balance(
    context.Background(),
    address,
    cfg.StakingDenom, // "usnr"
)
if err != nil {
    log.Printf("Failed to query balance: %v", err)
} else {
    fmt.Printf("Balance for %s: %s %s\n",
        address,
        balance.Balance.Amount.String(),
        balance.Balance.Denom,
    )
}

// Query all balances for an account
allBalances, err := queryClient.AllBalances(
    context.Background(),
    address,
    nil, // pagination
)
if err != nil {
    log.Printf("Failed to query all balances: %v", err)
} else {
    fmt.Printf("All balances: %v\n", allBalances.Balances)
}
3

Query Module-Specific Data

Query DID documents and other module data:
import (
    "github.com/sonr-io/sonr/client/modules/did"
)

// Create DID module client
didClient := did.NewDIDClient()

// Query DID document (if exists)
didID := "did:sonr:example123"
didDoc, err := queryClient.GetDID(context.Background(), didID)
if err != nil {
    log.Printf("DID not found: %v", err)
} else {
    fmt.Printf("DID Document: %+v\n", didDoc)
}

// List all DIDs with pagination
didList, err := queryClient.ListDIDs(
    context.Background(),
    &query.ListOptions{
        Limit: 10,
        Offset: 0,
    },
)
if err != nil {
    log.Printf("Failed to list DIDs: %v", err)
} else {
    fmt.Printf("Found %d DIDs\n", len(didList.DIDs))
}

4. Building and Broadcasting Transactions

Let’s build and broadcast transactions using the transaction builder.
1

Create a Transaction Builder

Initialize the transaction builder with gas estimation:
// Create transaction builder
txBuilder, err := tx.NewTxBuilder(&cfg, conn)
if err != nil {
    log.Fatal("Failed to create tx builder:", err)
}

// Create broadcaster
broadcaster, err := tx.NewBroadcaster(&cfg, conn)
if err != nil {
    log.Fatal("Failed to create broadcaster:", err)
}

// Create gas estimator
gasEstimator := tx.NewGasEstimator(conn, &cfg)
2

Send Tokens Between Accounts

Build and broadcast a bank send transaction:
import (
    sdk "github.com/cosmos/cosmos-sdk/types"
    banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)

// Create a second wallet to receive funds
receiver, _, err := keyringManager.CreateWallet(
    context.Background(),
    "receiver-wallet",
    "",
)
if err != nil {
    log.Fatal("Failed to create receiver wallet:", err)
}

// Create send message
amount := sdk.NewCoins(sdk.NewInt64Coin("usnr", 1000000)) // 1 SNR
sendMsg := &banktypes.MsgSend{
    FromAddress: walletIdentity.Address,
    ToAddress:   receiver.Address,
    Amount:      amount,
}

// Build transaction
txBuilder = txBuilder.
    AddMessage(sendMsg).
    WithMemo("Test transaction").
    WithGasLimit(200000)

// Estimate gas
gasEstimate, err := gasEstimator.EstimateGas(
    context.Background(),
    []sdk.Msg{sendMsg},
)
if err != nil {
    log.Printf("Gas estimation failed: %v", err)
} else {
    fmt.Printf("Estimated gas: %d\n", gasEstimate.GasLimit)
    txBuilder = txBuilder.WithGasLimit(gasEstimate.GasLimit)
}

// Calculate and set fee
fee := gasEstimator.CalculateFee(
    gasEstimate.GasLimit,
    cfg.GasPrice,
    cfg.StakingDenom,
)
txBuilder = txBuilder.WithFee(fee)

// Sign the transaction
signedTx, err := txBuilder.Sign(context.Background(), keyringManager)
if err != nil {
    log.Fatal("Failed to sign transaction:", err)
}

// Broadcast the transaction
result, err := broadcaster.BroadcastTx(context.Background(), signedTx)
if err != nil {
    log.Fatal("Failed to broadcast transaction:", err)
}

fmt.Printf("Transaction successful!\n")
fmt.Printf("TxHash: %s\n", result.TxHash)
fmt.Printf("Height: %d\n", result.Height)
fmt.Printf("Gas Used: %d\n", result.GasUsed)

5. WebAuthn Gasless Transactions (Advanced)

Sonr supports gasless WebAuthn registration, allowing users to onboard without holding tokens.
1

Initialize WebAuthn Client

Set up the WebAuthn client for gasless operations:
import (
    "github.com/sonr-io/sonr/client/auth"
)

// Create WebAuthn client
webauthnClient := auth.NewWebAuthnClient(
    keyringManager,
    "localhost",     // Relying Party ID
    "Sonr Local",    // Relying Party Name
)

// Create gasless transaction manager
gaslessManager := auth.NewGaslessTransactionManager(
    txBuilder,
    broadcaster,
    &cfg,
)

// Create WebAuthn gasless client
gaslessClient := auth.NewWebAuthnGaslessClient(
    webauthnClient,
    gaslessManager,
    &cfg,
)
2

Initiate Gasless Registration

Start a gasless WebAuthn registration:
// Begin gasless registration
registrationResult, err := gaslessClient.RegisterGasless(
    context.Background(),
    "alice",        // username
    "Alice Smith",  // display name
)
if err != nil {
    log.Fatal("Failed to initiate registration:", err)
}

fmt.Printf("Registration initiated!\n")
fmt.Printf("Gasless eligible: %v\n", registrationResult.GaslessEligible)
fmt.Printf("Estimated gas: %d\n", registrationResult.EstimatedGas)

// The challenge would be sent to a browser for completion
// In a real application, you'd handle the browser response
3

Check Gasless Eligibility

Verify if a transaction is eligible for gasless processing:
// Check if messages are eligible for gasless
msgs := []sdk.Msg{
    &didtypes.MsgRegisterWebAuthnCredential{
        Controller: "sonr1...",
        Username:   "alice",
    },
}

isEligible := gaslessManager.IsEligibleForGasless(msgs)
fmt.Printf("Transaction eligible for gasless: %v\n", isEligible)

// Estimate gas for gasless transaction
gasNeeded := gaslessManager.EstimateGaslessGas(
    "/did.v1.MsgRegisterWebAuthnCredential",
)
fmt.Printf("Gas needed for gasless WebAuthn: %d\n", gasNeeded)

6. Working with DID Module

Create and manage decentralized identities:
1

Create a DID Document

import (
    didtypes "github.com/sonr-io/sonr/x/did/types"
)

// Create DID document
didDoc := &didtypes.DidDocument{
    Id:         "did:sonr:" + walletIdentity.Address,
    Controller: walletIdentity.Address,
    VerificationMethod: []*didtypes.VerificationMethod{
        {
            Id:                     "did:sonr:" + walletIdentity.Address + "#key-1",
            VerificationMethodKind: didtypes.VerificationMethodKind_Ed25519VerificationKey2020,
            Controller:             "did:sonr:" + walletIdentity.Address,
            PublicKeyMultibase:     "z6MkhaXgBZD...", // Your public key
        },
    },
    Service: []*didtypes.Service{
        {
            Id:             "did:sonr:" + walletIdentity.Address + "#dwn",
            ServiceKind:    didtypes.ServiceKind_DecentralizedWebNode,
            SingleEndpoint: "https://dwn.example.com",
        },
    },
}

// Create the message
createDIDMsg, err := didClient.CreateDID(
    walletIdentity.Address,
    didDoc,
)
if err != nil {
    log.Fatal("Failed to create DID message:", err)
}

// Add to transaction and broadcast
txBuilder = txBuilder.
    ClearMessages().
    AddMessage(createDIDMsg).
    WithMemo("Create DID")

// Sign and broadcast as shown earlier

Next Steps

This quickstart has covered the fundamentals of the Sonr Go Client SDK:
  • Client Configuration: Setting up connections and network configurations
  • Key Management: Creating and managing wallets with the keyring
  • Querying: Reading blockchain state and module data
  • Transactions: Building, signing, and broadcasting transactions
  • Gas Estimation: Calculating optimal gas limits and fees
  • WebAuthn: Gasless onboarding with WebAuthn credentials
  • DID Module: Creating decentralized identities

Advanced Topics to Explore:

  • DWN Module: Manage decentralized web nodes and data records
  • Service Module: Register and verify services with domain verification
  • UCAN Integration: Implement capability-based authorization
  • Multi-signature: Create and manage multi-sig accounts
  • IBC Transfers: Cross-chain token transfers
  • Custom Modules: Interact with your own custom modules

Useful Resources: