Go Examples¶
Complete, self-contained Go programs demonstrating common tasks with the Dilithia SDK. Each scenario is a standalone package main program ready to compile and run.
Prerequisites¶
Install the SDK module:
Set the environment variable pointing to the native Dilithium shared library:
Scenario 1: Balance Monitor Bot¶
A bot that recovers its wallet from a saved mnemonic, checks its balance, and if the balance exceeds a threshold, sends tokens to a destination address via a contract call. Covers: client setup, wallet recovery, balance query, contract call construction, signing, submission, and receipt polling.
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
"time"
"github.com/dilithia/languages-sdk/go/sdk"
)
const (
rpcURL = "https://rpc.dilithia.network/rpc"
tokenContract = "dil1_token_main"
destination = "dil1_recipient_address"
threshold = 500_000
sendAmount = 100_000
)
func main() {
ctx := context.Background()
// 1. Initialize client with functional options and crypto adapter
client := sdk.NewClient(rpcURL, sdk.WithTimeout(15*time.Second))
crypto, err := sdk.LoadNativeCryptoAdapter()
if err != nil {
log.Fatal("crypto adapter unavailable: ", err)
}
// 2. Recover wallet from saved mnemonic
mnemonic := os.Getenv("BOT_MNEMONIC")
if mnemonic == "" {
log.Fatal("Set BOT_MNEMONIC env var")
}
account, err := crypto.RecoverHDWallet(ctx, mnemonic)
if err != nil {
log.Fatal("wallet recovery failed: ", err)
}
fmt.Println("Bot address:", account.Address)
// 3. Check current balance — returns *sdk.Balance with typed fields
balance, err := client.GetBalance(ctx, sdk.Address(account.Address))
if err != nil {
var httpErr *sdk.HttpError
var rpcErr *sdk.RpcError
if errors.As(err, &httpErr) {
log.Fatalf("HTTP error %d: %s", httpErr.StatusCode, httpErr.Message)
} else if errors.As(err, &rpcErr) {
log.Fatalf("RPC error %d: %s", rpcErr.Code, rpcErr.Message)
}
log.Fatal("balance query failed: ", err)
}
fmt.Printf("Current balance: %s (raw: %s)\n", balance.Value, balance.RawValue)
if balance.Value.Less(sdk.TokenAmount(threshold)) {
fmt.Printf("Balance below threshold %d. Nothing to do.\n", threshold)
return
}
// 4. Build a contract call to transfer tokens
call := client.BuildContractCall(tokenContract, "transfer", map[string]any{
"to": string(sdk.Address(destination)),
"amount": sendAmount,
}, "")
// 5. Simulate first to verify it would succeed
simResult, err := client.Simulate(ctx, call)
if err != nil {
log.Fatal("simulation failed: ", err)
}
fmt.Println("Simulation result:", simResult)
// 6. Sign the message and attach the signature to the call
sig, err := crypto.SignMessage(ctx, account.SecretKey, fmt.Sprintf("%v", call))
if err != nil {
log.Fatal("signing failed: ", err)
}
call["algorithm"] = sig.Algorithm
call["signature"] = sig.Signature
// 7. Submit — returns *sdk.SubmitResult with .Accepted and .TxHash
submitted, err := client.SendCall(ctx, call)
if err != nil {
var dilErr *sdk.DilithiaError
if errors.As(err, &dilErr) {
log.Fatalf("Dilithia error: %s", dilErr.Message)
}
log.Fatal("submit failed: ", err)
}
fmt.Printf("Transaction submitted: %s (accepted: %v)\n", submitted.TxHash, submitted.Accepted)
// 8. Poll for receipt — returns *sdk.Receipt with .TxHash, .BlockHeight, .Status
receipt, err := client.WaitForReceipt(ctx, submitted.TxHash, 20, 2*time.Second)
if err != nil {
log.Fatal("receipt polling failed: ", err)
}
fmt.Printf("Confirmed at block %d, status: %s, tx: %s\n",
receipt.BlockHeight, receipt.Status, receipt.TxHash)
}
Scenario 2: Multi-Account Treasury Manager¶
A service that derives accounts 0 through 4 from a single mnemonic, checks each balance, and consolidates all funds into account 0. Covers: HD derivation loop, multiple balance queries, and batch transaction construction.
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
"time"
"github.com/dilithia/languages-sdk/go/sdk"
)
const (
rpcURL = "https://rpc.dilithia.network/rpc"
tokenContract = "dil1_token_main"
numAccounts = 5
)
func main() {
ctx := context.Background()
// Initialize client with functional options
client := sdk.NewClient(rpcURL, sdk.WithTimeout(15*time.Second))
crypto, err := sdk.LoadNativeCryptoAdapter()
if err != nil {
log.Fatal("crypto adapter unavailable: ", err)
}
mnemonic := os.Getenv("TREASURY_MNEMONIC")
if mnemonic == "" {
log.Fatal("Set TREASURY_MNEMONIC env var")
}
// 1. Derive all accounts from the same mnemonic
accounts := make([]sdk.Account, numAccounts)
for i := 0; i < numAccounts; i++ {
acct, err := crypto.RecoverHDWalletAccount(ctx, mnemonic, i)
if err != nil {
log.Fatalf("failed to derive account %d: %v", i, err)
}
accounts[i] = acct
}
treasuryAddr := sdk.Address(accounts[0].Address)
fmt.Println("Treasury address (account 0):", treasuryAddr)
// 2. Check balances for every account — GetBalance returns *sdk.Balance
balances := make([]*sdk.Balance, numAccounts)
for i, acct := range accounts {
bal, err := client.GetBalance(ctx, sdk.Address(acct.Address))
if err != nil {
log.Fatalf("balance query failed for account %d: %v", i, err)
}
balances[i] = bal
fmt.Printf(" Account %d: %s -> %s\n", acct.AccountIndex, bal.Address, bal.Value)
}
// 3. Consolidate: transfer from accounts 1-4 to account 0
for i := 1; i < numAccounts; i++ {
if balances[i].Value.IsZero() {
fmt.Printf(" Account %d: zero balance, skipping.\n", i)
continue
}
call := client.BuildContractCall(tokenContract, "transfer", map[string]any{
"to": string(treasuryAddr),
"amount": balances[i].RawValue,
}, "")
fmt.Printf(" Consolidating %s from account %d...\n", balances[i].Value, i)
// Sign with the source account's secret key
sig, err := crypto.SignMessage(ctx, accounts[i].SecretKey, fmt.Sprintf("%v", call))
if err != nil {
log.Fatalf("signing failed for account %d: %v", i, err)
}
call["algorithm"] = sig.Algorithm
call["signature"] = sig.Signature
// SendCall returns *sdk.SubmitResult
submitted, err := client.SendCall(ctx, call)
if err != nil {
var rpcErr *sdk.RpcError
if errors.As(err, &rpcErr) {
log.Fatalf("RPC error for account %d: code=%d msg=%s", i, rpcErr.Code, rpcErr.Message)
}
log.Fatalf("submit failed for account %d: %v", i, err)
}
// WaitForReceipt returns *sdk.Receipt
receipt, err := client.WaitForReceipt(ctx, submitted.TxHash, 12, time.Second)
if err != nil {
log.Fatalf("receipt polling failed for account %d: %v", i, err)
}
fmt.Printf(" Done. Block %d, status: %s\n", receipt.BlockHeight, receipt.Status)
}
// 4. Final balance check on treasury
finalBalance, err := client.GetBalance(ctx, treasuryAddr)
if err != nil {
log.Fatal("final balance query failed: ", err)
}
fmt.Println("\nTreasury final balance:", finalBalance.Value)
}
Scenario 3: Signature Verification Service¶
An API endpoint that receives a signed message and verifies the signature against the claimed public key and address. Covers: address validation, public key validation, signature verification, and structured error handling.
package main
import (
"context"
"errors"
"fmt"
"log"
"github.com/dilithia/languages-sdk/go/sdk"
)
type verifyRequest struct {
PublicKey string
Address sdk.Address
Message string
Signature string
}
type verifyResult struct {
Valid bool
Error string
}
func verifySignedMessage(crypto sdk.CryptoAdapter, req verifyRequest) verifyResult {
ctx := context.Background()
// 1. Validate the public key format
if err := crypto.ValidatePublicKey(ctx, req.PublicKey); err != nil {
return verifyResult{Valid: false, Error: "Invalid public key format"}
}
// 2. Validate the signature format
if err := crypto.ValidateSignature(ctx, req.Signature); err != nil {
return verifyResult{Valid: false, Error: "Invalid signature format"}
}
// 3. Validate the claimed address format using sdk.Address
if _, err := crypto.ValidateAddress(ctx, string(req.Address)); err != nil {
return verifyResult{Valid: false, Error: "Invalid address format"}
}
// 4. Verify that the public key maps to the claimed address
derivedAddress, err := crypto.AddressFromPublicKey(ctx, req.PublicKey)
if err != nil {
return verifyResult{Valid: false, Error: "Failed to derive address from public key"}
}
if sdk.Address(derivedAddress) != req.Address {
return verifyResult{Valid: false, Error: "Address does not match public key"}
}
// 5. Verify the cryptographic signature
valid, err := crypto.VerifyMessage(ctx, req.PublicKey, req.Message, req.Signature)
if err != nil {
var dilErr *sdk.DilithiaError
if errors.As(err, &dilErr) {
return verifyResult{Valid: false, Error: "Dilithia error: " + dilErr.Message}
}
return verifyResult{Valid: false, Error: "Signature verification error: " + err.Error()}
}
if !valid {
return verifyResult{Valid: false, Error: "Signature verification failed"}
}
return verifyResult{Valid: true}
}
func main() {
crypto, err := sdk.LoadNativeCryptoAdapter()
if err != nil {
log.Fatal("crypto adapter unavailable: ", err)
}
result := verifySignedMessage(crypto, verifyRequest{
PublicKey: "abcd1234...",
Address: sdk.Address("dil1_abc123"),
Message: "Login nonce: 98765",
Signature: "deadbeef...",
})
if result.Valid {
fmt.Println("Signature is valid. User authenticated.")
} else {
fmt.Println("Verification failed:", result.Error)
}
}
Scenario 4: Wallet Backup and Recovery¶
Create a new wallet, save the encrypted wallet file to disk, then recover it later from the saved file. Covers the full wallet lifecycle: generate mnemonic, create encrypted wallet file, serialize, write to disk, read from disk, deserialize, and recover.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"github.com/dilithia/languages-sdk/go/sdk"
)
const (
walletPath = "./my-wallet.json"
password = "my-secure-passphrase"
)
func main() {
ctx := context.Background()
crypto, err := sdk.LoadNativeCryptoAdapter()
if err != nil {
log.Fatal("crypto adapter unavailable: ", err)
}
if _, err := os.Stat(walletPath); os.IsNotExist(err) {
// ---- CREATE NEW WALLET ----
fmt.Println("No wallet found. Creating a new one...")
// 1. Generate a fresh mnemonic
mnemonic, err := crypto.GenerateMnemonic(ctx)
if err != nil {
log.Fatal("mnemonic generation failed: ", err)
}
fmt.Println("SAVE THIS MNEMONIC SECURELY:")
fmt.Println(mnemonic)
fmt.Println()
// 2. Create an encrypted wallet file from the mnemonic
account, err := crypto.CreateHDWalletFileFromMnemonic(ctx, mnemonic, password)
if err != nil {
log.Fatal("wallet creation failed: ", err)
}
fmt.Println("Address:", sdk.Address(account.Address))
fmt.Println("Public key:", account.PublicKey)
// 3. Serialize the wallet file to JSON and save to disk
if account.WalletFile == nil {
log.Fatal("wallet file not generated")
}
data, err := json.MarshalIndent(account.WalletFile, "", " ")
if err != nil {
log.Fatal("JSON marshal failed: ", err)
}
if err := os.WriteFile(walletPath, data, 0600); err != nil {
log.Fatal("file write failed: ", err)
}
fmt.Println("Wallet saved to", walletPath)
} else {
// ---- RECOVER EXISTING WALLET ----
fmt.Println("Wallet file found. Recovering...")
// 4. Read the wallet file from disk
data, err := os.ReadFile(walletPath)
if err != nil {
log.Fatal("file read failed: ", err)
}
var walletFile sdk.WalletFile
if err := json.Unmarshal(data, &walletFile); err != nil {
log.Fatal("JSON unmarshal failed: ", err)
}
// 5. Recover using mnemonic + password
mnemonic := os.Getenv("WALLET_MNEMONIC")
if mnemonic == "" {
log.Fatal("Set WALLET_MNEMONIC env var to recover")
}
account, err := crypto.RecoverWalletFile(ctx, walletFile, mnemonic, password)
if err != nil {
log.Fatal("wallet recovery failed: ", err)
}
fmt.Println("Recovered address:", sdk.Address(account.Address))
fmt.Println("Public key:", account.PublicKey)
fmt.Println("Wallet recovered successfully. Ready to sign transactions.")
}
}
Scenario 5: Gas-Sponsored Meta-Transaction¶
Submit a transaction where the gas fee is paid by a sponsor contract instead of the sender. Useful for onboarding new users who have no tokens to pay for gas. Covers: gas sponsor connector setup, checking remaining quota, building a paymaster-attached call, signing, and submission.
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
"time"
"github.com/dilithia/languages-sdk/go/sdk"
)
const (
rpcURL = "https://rpc.dilithia.network/rpc"
sponsorContract = "dil1_gas_sponsor_v1"
paymasterAddr = "dil1_paymaster_addr"
targetContract = "dil1_nft_mint"
)
func main() {
ctx := context.Background()
// Initialize client with functional options
client := sdk.NewClient(rpcURL, sdk.WithTimeout(15*time.Second))
crypto, err := sdk.LoadNativeCryptoAdapter()
if err != nil {
log.Fatal("crypto adapter unavailable: ", err)
}
// 1. Recover the user's wallet (new user with zero balance)
mnemonic := os.Getenv("USER_MNEMONIC")
if mnemonic == "" {
log.Fatal("Set USER_MNEMONIC env var")
}
account, err := crypto.RecoverHDWallet(ctx, mnemonic)
if err != nil {
log.Fatal("wallet recovery failed: ", err)
}
fmt.Println("User address:", sdk.Address(account.Address))
// 2. Set up the gas sponsor connector
sponsor := sdk.NewGasSponsorConnector(client, sponsorContract, paymasterAddr)
// 3. Check if the sponsor will accept this call — QueryContract returns *sdk.QueryResult
acceptResult, err := client.QueryContract(ctx, sponsorContract, "accept", map[string]any{
"user": string(sdk.Address(account.Address)),
"contract": targetContract,
"method": "mint",
})
if err != nil {
log.Fatal("accept query failed: ", err)
}
fmt.Println("Sponsor accepts:", acceptResult)
// 4. Check remaining gas quota for this user
quotaResult, err := client.QueryContract(ctx, sponsorContract, "remaining_quota", map[string]any{
"user": string(sdk.Address(account.Address)),
})
if err != nil {
log.Fatal("quota query failed: ", err)
}
fmt.Println("Remaining quota:", quotaResult)
// 5. Get gas estimate — returns *sdk.GasEstimate
gasEstimate, err := client.GetGasEstimate(ctx)
if err != nil {
log.Fatal("gas estimate failed: ", err)
}
fmt.Println("Current gas estimate:", gasEstimate)
// 6. Build the actual contract call
call := client.BuildContractCall(targetContract, "mint", map[string]any{
"token_id": "nft_001",
"metadata": "ipfs://QmSomeHash",
}, "")
// 7. Apply the paymaster (sponsor pays the gas)
sponsoredCall := sponsor.ApplyPaymaster(call)
fmt.Println("Sponsored call:", sponsoredCall)
// 8. Sign and submit the sponsored call
sig, err := crypto.SignMessage(ctx, account.SecretKey, fmt.Sprintf("%v", sponsoredCall))
if err != nil {
log.Fatal("signing failed: ", err)
}
sponsoredCall["algorithm"] = sig.Algorithm
sponsoredCall["signature"] = sig.Signature
// SendCall returns *sdk.SubmitResult
submitted, err := client.SendCall(ctx, sponsoredCall)
if err != nil {
var httpErr *sdk.HttpError
var rpcErr *sdk.RpcError
if errors.As(err, &httpErr) {
log.Fatalf("HTTP error %d: %s", httpErr.StatusCode, httpErr.Message)
} else if errors.As(err, &rpcErr) {
log.Fatalf("RPC error %d: %s", rpcErr.Code, rpcErr.Message)
}
log.Fatal("submit failed: ", err)
}
fmt.Printf("Sponsored tx submitted: %s (accepted: %v)\n", submitted.TxHash, submitted.Accepted)
// 9. Wait for confirmation — returns *sdk.Receipt
receipt, err := client.WaitForReceipt(ctx, submitted.TxHash, 12, time.Second)
if err != nil {
log.Fatal("receipt polling failed: ", err)
}
fmt.Printf("Confirmed at block %d, status: %s\n", receipt.BlockHeight, receipt.Status)
}
Scenario 6: Cross-Chain Message Sender¶
Send a message to another Dilithia chain via the messaging connector. Useful for bridging data or triggering actions on a remote chain. Covers: messaging connector setup, building outbound messages, signing, and submission.
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
"time"
"github.com/dilithia/languages-sdk/go/sdk"
)
const (
rpcURL = "https://rpc.dilithia.network/rpc"
messagingContract = "dil1_bridge_v1"
paymaster = "dil1_bridge_paymaster"
destChain = "dilithia-testnet-2"
)
func main() {
ctx := context.Background()
// Initialize client with functional options
client := sdk.NewClient(rpcURL, sdk.WithTimeout(15*time.Second))
crypto, err := sdk.LoadNativeCryptoAdapter()
if err != nil {
log.Fatal("crypto adapter unavailable: ", err)
}
mnemonic := os.Getenv("BRIDGE_MNEMONIC")
if mnemonic == "" {
log.Fatal("Set BRIDGE_MNEMONIC env var")
}
account, err := crypto.RecoverHDWallet(ctx, mnemonic)
if err != nil {
log.Fatal("wallet recovery failed: ", err)
}
fmt.Println("Sender address:", sdk.Address(account.Address))
// 1. Set up the messaging connector
messaging := sdk.NewMessagingConnector(client, messagingContract, paymaster)
// 2. Resolve a name before sending — ResolveName returns *sdk.NameRecord
nameRecord, err := client.ResolveName(ctx, "alice.dili")
if err != nil {
var rpcErr *sdk.RpcError
if errors.As(err, &rpcErr) {
fmt.Printf("Name resolution RPC error: code=%d msg=%s\n", rpcErr.Code, rpcErr.Message)
} else {
fmt.Println("Name resolution failed (using fallback address):", err)
}
} else {
fmt.Printf("Resolved alice.dili -> %s\n", nameRecord)
}
// 3. Get network info — returns *sdk.NetworkInfo
netInfo, err := client.GetNetworkInfo(ctx)
if err != nil {
log.Fatal("network info failed: ", err)
}
fmt.Println("Network info:", netInfo)
// 4. Build the cross-chain message payload
payload := map[string]any{
"action": "lock_tokens",
"sender": string(sdk.Address(account.Address)),
"amount": 50_000,
"recipient": string(sdk.Address("dil1_remote_recipient")),
"timestamp": time.Now().UnixMilli(),
}
// 5. Build the send-message call (paymaster attached automatically)
messageCall := messaging.BuildSendMessageCall(destChain, payload)
fmt.Println("Message call:", messageCall)
// 6. Simulate the message send
simResult, err := client.Simulate(ctx, messageCall)
if err != nil {
log.Fatal("simulation failed: ", err)
}
fmt.Println("Simulation:", simResult)
// 7. Sign and send the message
sig, err := crypto.SignMessage(ctx, account.SecretKey, fmt.Sprintf("%v", messageCall))
if err != nil {
log.Fatal("signing failed: ", err)
}
messageCall["algorithm"] = sig.Algorithm
messageCall["signature"] = sig.Signature
// SendCall returns *sdk.SubmitResult
submitted, err := client.SendCall(ctx, messageCall)
if err != nil {
var dilErr *sdk.DilithiaError
if errors.As(err, &dilErr) {
log.Fatalf("Dilithia error: %s", dilErr.Message)
}
log.Fatal("submit failed: ", err)
}
fmt.Printf("Message tx submitted: %s (accepted: %v)\n", submitted.TxHash, submitted.Accepted)
// 8. Wait for confirmation — returns *sdk.Receipt
receipt, err := client.WaitForReceipt(ctx, submitted.TxHash, 12, time.Second)
if err != nil {
log.Fatal("receipt polling failed: ", err)
}
fmt.Printf("Message confirmed at block %d, status: %s\n", receipt.BlockHeight, receipt.Status)
// 9. Optionally build a receive-message call for the remote side
receiveCall := messaging.BuildReceiveMessageCall(
"dilithia-mainnet", // source chain
messagingContract, // source contract
payload,
)
fmt.Println("Receive call (for remote chain):", receiveCall)
}
Scenario 7: Contract Deployment¶
Deploy a WASM smart contract to the Dilithia chain. Reads the WASM binary, hashes the bytecode, builds and signs a canonical deploy payload, assembles the full DeployPayload, sends the deploy request, and waits for confirmation.
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"os"
"time"
"github.com/dilithia/languages-sdk/go/sdk"
)
const (
rpcURL = "https://rpc.dilithia.network/rpc"
contractName = "my_contract"
wasmPath = "./my_contract.wasm"
chainID = "dilithia-mainnet"
)
func main() {
ctx := context.Background()
// 1. Initialize client with functional options and crypto adapter
client := sdk.NewClient(rpcURL, sdk.WithTimeout(30*time.Second))
crypto, err := sdk.LoadNativeCryptoAdapter()
if err != nil {
log.Fatal("crypto adapter unavailable: ", err)
}
// 2. Recover wallet from mnemonic
mnemonic := os.Getenv("DEPLOYER_MNEMONIC")
if mnemonic == "" {
log.Fatal("Set DEPLOYER_MNEMONIC env var")
}
account, err := crypto.RecoverHDWallet(ctx, mnemonic)
if err != nil {
log.Fatal("wallet recovery failed: ", err)
}
fmt.Println("Deployer address:", sdk.Address(account.Address))
// 3. Read the WASM file as hex
bytecodeHex, err := sdk.ReadWasmFileHex(wasmPath)
if err != nil {
log.Fatal("failed to read wasm file: ", err)
}
fmt.Printf("Bytecode size: %d bytes\n", len(bytecodeHex)/2)
// 4. Get the current nonce — returns *sdk.Nonce with .NextNonce
nonceResult, err := client.GetNonce(ctx, sdk.Address(account.Address))
if err != nil {
var httpErr *sdk.HttpError
if errors.As(err, &httpErr) {
log.Fatalf("HTTP error %d: %s", httpErr.StatusCode, httpErr.Message)
}
log.Fatal("nonce query failed: ", err)
}
fmt.Printf("Current nonce: %d\n", nonceResult.NextNonce)
// 5. Hash the bytecode hex for the canonical payload
bytecodeHash, err := crypto.HashHex(ctx, bytecodeHex)
if err != nil {
log.Fatal("hashing failed: ", err)
}
fmt.Println("Bytecode hash:", bytecodeHash)
// 6. Build the canonical deploy payload (keys sorted for deterministic signing)
canonical := client.BuildDeployCanonicalPayload(
string(sdk.Address(account.Address)), contractName, bytecodeHash, nonceResult.NextNonce, chainID,
)
fmt.Println("Canonical payload:", canonical)
// 7. Sign the canonical payload
canonicalJSON, err := json.Marshal(canonical)
if err != nil {
log.Fatal("JSON marshal failed: ", err)
}
sig, err := crypto.SignMessage(ctx, account.SecretKey, string(canonicalJSON))
if err != nil {
log.Fatal("signing failed: ", err)
}
fmt.Println("Signed with algorithm:", sig.Algorithm)
// 8. Assemble the full DeployPayload
deployPayload := sdk.DeployPayload{
Name: contractName,
Bytecode: bytecodeHex,
From: string(sdk.Address(account.Address)),
Alg: sig.Algorithm,
PK: account.PublicKey,
Sig: sig.Signature,
Nonce: nonceResult.NextNonce,
ChainID: chainID,
Version: 1,
}
// 9. Deploy the contract — returns *sdk.SubmitResult
result, err := client.DeployContract(ctx, deployPayload)
if err != nil {
var rpcErr *sdk.RpcError
var dilErr *sdk.DilithiaError
if errors.As(err, &rpcErr) {
log.Fatalf("RPC error %d: %s", rpcErr.Code, rpcErr.Message)
} else if errors.As(err, &dilErr) {
log.Fatalf("Dilithia error: %s", dilErr.Message)
}
log.Fatal("deploy request failed: ", err)
}
fmt.Printf("Deploy tx submitted: %s (accepted: %v)\n", result.TxHash, result.Accepted)
// 10. Wait for the receipt — returns *sdk.Receipt
receipt, err := client.WaitForReceipt(ctx, result.TxHash, 30, 3*time.Second)
if err != nil {
log.Fatal("receipt polling failed: ", err)
}
fmt.Printf("Contract deployed at block %d, status: %s, tx: %s\n",
receipt.BlockHeight, receipt.Status, receipt.TxHash)
}
Scenario 8: Shielded Pool Deposit & Withdraw¶
Deposit tokens into a shielded pool using a commitment hash and zero-knowledge proof, then withdraw them to a recipient address by revealing a nullifier. Covers: ZK adapter setup, commitment computation, proof generation, shielded deposit, commitment root retrieval, nullifier check, shielded withdrawal, and receipt polling.
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/dilithia/languages-sdk/go/sdk"
)
const (
rpcURL = "https://rpc.dilithia.network/rpc"
secretHex = "aabbccdd11223344aabbccdd11223344aabbccdd11223344aabbccdd11223344"
nonceHex = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
recipient = "dil1_recipient_address"
)
func main() {
ctx := context.Background()
// 1. Initialize client, crypto adapter, and ZK adapter
client := sdk.NewClient(rpcURL, sdk.WithTimeout(10*time.Second))
crypto, err := sdk.LoadNativeCryptoAdapter()
if err != nil {
log.Fatal("crypto adapter unavailable: ", err)
}
zk, err := sdk.LoadNativeZkAdapter()
if err != nil {
log.Fatal("zk adapter unavailable: ", err)
}
// 2. Compute the commitment for the deposit
var depositValue int64 = 250_000
commitment, err := zk.ComputeCommitment(ctx, depositValue, secretHex, nonceHex)
if err != nil {
log.Fatal("commitment computation failed: ", err)
}
fmt.Println("Commitment:", commitment)
// 3. Hash the commitment for on-chain storage
commitmentHash, err := crypto.HashHex(ctx, commitment)
if err != nil {
log.Fatal("hashing commitment failed: ", err)
}
fmt.Println("Commitment hash:", commitmentHash)
// 4. Generate a preimage proof for the deposit
proofResult, err := zk.GeneratePreimageProof(ctx, []int64{depositValue})
if err != nil {
log.Fatal("proof generation failed: ", err)
}
fmt.Println("Deposit proof generated")
// 5. Deposit into the shielded pool
depositTx, err := client.ShieldedDeposit(ctx, commitmentHash, depositValue, proofResult)
if err != nil {
log.Fatal("shielded deposit failed: ", err)
}
fmt.Printf("Deposit tx submitted: %s\n", depositTx)
// 6. Wait for the deposit receipt
depositReceipt, err := client.WaitForReceipt(ctx, depositTx, 20, 2*time.Second)
if err != nil {
log.Fatal("deposit receipt polling failed: ", err)
}
fmt.Printf("Deposit confirmed at block %d, status: %s\n",
depositReceipt.BlockHeight, depositReceipt.Status)
// 7. Retrieve the current commitment root
commitmentRoot, err := client.GetCommitmentRoot(ctx)
if err != nil {
log.Fatal("commitment root retrieval failed: ", err)
}
fmt.Println("Commitment root:", commitmentRoot)
// 8. Compute the nullifier for withdrawal
nullifierHash, err := zk.ComputeNullifier(ctx, secretHex, nonceHex)
if err != nil {
log.Fatal("nullifier computation failed: ", err)
}
fmt.Println("Nullifier hash:", nullifierHash)
// 9. Check that the nullifier has not already been spent
spent, err := client.IsNullifierSpent(ctx, nullifierHash)
if err != nil {
log.Fatal("nullifier check failed: ", err)
}
if spent {
log.Fatal("nullifier already spent — cannot withdraw")
}
fmt.Println("Nullifier is unspent, proceeding with withdrawal")
// 10. Generate a withdrawal proof
withdrawProof, err := zk.GeneratePreimageProof(ctx, []int64{depositValue})
if err != nil {
log.Fatal("withdrawal proof generation failed: ", err)
}
// 11. Withdraw from the shielded pool
var withdrawAmount int64 = 250_000
withdrawTx, err := client.ShieldedWithdraw(
ctx, nullifierHash, withdrawAmount, recipient, withdrawProof, commitmentRoot,
)
if err != nil {
log.Fatal("shielded withdraw failed: ", err)
}
fmt.Printf("Withdraw tx submitted: %s\n", withdrawTx)
// 12. Wait for the withdrawal receipt
withdrawReceipt, err := client.WaitForReceipt(ctx, withdrawTx, 20, 2*time.Second)
if err != nil {
log.Fatal("withdraw receipt polling failed: ", err)
}
fmt.Printf("Withdraw confirmed at block %d, status: %s\n",
withdrawReceipt.BlockHeight, withdrawReceipt.Status)
fmt.Println("Shielded deposit and withdrawal complete.")
}
Scenario 9: ZK Proof Generation & Verification¶
Generate and verify zero-knowledge proofs without interacting with the chain. Demonstrates standalone Poseidon hashing, preimage proof round-trips, and range proof round-trips. Covers: ZK adapter setup, Poseidon hash, preimage proof generation and verification, range proof generation and verification.
package main
import (
"context"
"fmt"
"log"
"github.com/dilithia/languages-sdk/go/sdk"
)
func main() {
ctx := context.Background()
// 1. Load the ZK adapter (no client or crypto adapter needed)
zk, err := sdk.LoadNativeZkAdapter()
if err != nil {
log.Fatal("zk adapter unavailable: ", err)
}
// ---- Poseidon Hashing ----
// 2. Compute a Poseidon hash over a set of field elements
hash, err := zk.PoseidonHash(ctx, []int64{42, 100, 7})
if err != nil {
log.Fatal("poseidon hash failed: ", err)
}
fmt.Println("Poseidon hash:", hash)
// 3. Hash a single value to show deterministic output
hashSingle, err := zk.PoseidonHash(ctx, []int64{12345})
if err != nil {
log.Fatal("poseidon hash (single) failed: ", err)
}
fmt.Println("Poseidon hash (single):", hashSingle)
// ---- Preimage Proof Generation & Verification ----
// 4. Generate a preimage proof for a known set of inputs
preimageInputs := []int64{10, 20, 30}
preimageProof, err := zk.GeneratePreimageProof(ctx, preimageInputs)
if err != nil {
log.Fatal("preimage proof generation failed: ", err)
}
fmt.Println("Preimage proof generated successfully")
// 5. Verify the preimage proof
preimageValid, err := zk.VerifyPreimageProof(ctx, preimageProof.Proof, preimageProof.VK, preimageProof.Inputs)
if err != nil {
log.Fatal("preimage proof verification failed: ", err)
}
fmt.Printf("Preimage proof valid: %v\n", preimageValid)
// ---- Range Proof Generation & Verification ----
// 6. Generate a range proof asserting that a value lies within [min, max]
var value int64 = 500
var min int64 = 0
var max int64 = 1000
rangeProof, err := zk.GenerateRangeProof(ctx, value, min, max)
if err != nil {
log.Fatal("range proof generation failed: ", err)
}
fmt.Printf("Range proof generated for value=%d in [%d, %d]\n", value, min, max)
// 7. Verify the range proof
rangeValid, err := zk.VerifyRangeProof(ctx, rangeProof.Proof, rangeProof.VK, rangeProof.Inputs)
if err != nil {
log.Fatal("range proof verification failed: ", err)
}
fmt.Printf("Range proof valid: %v\n", rangeValid)
// ---- Edge Case: Out-of-Range Value ----
// 8. Attempt to generate a range proof for a value outside the range
var outOfRange int64 = 2000
_, err = zk.GenerateRangeProof(ctx, outOfRange, min, max)
if err != nil {
fmt.Printf("Expected error for out-of-range value %d: %v\n", outOfRange, err)
} else {
fmt.Println("Warning: proof generated for out-of-range value (unexpected)")
}
fmt.Println("All ZK operations completed.")
}
Scenario 10: Name Service & Identity Profile¶
A utility that registers a .dili name, configures profile records, resolves names, and demonstrates the full name service lifecycle. Covers: getRegistrationCost, isNameAvailable, registerName, setNameTarget, setNameRecord, getNameRecords, lookupName, resolveName, reverseResolveName, getNamesByOwner, renewName, transferName, releaseName.
package main
import (
"context"
"fmt"
"log"
"os"
"time"
"github.com/dilithia/languages-sdk/go/sdk"
)
const (
rpcURL = "https://rpc.dilithia.network/rpc"
name = "alice"
transferTo = "dil1_bob_address"
)
func main() {
ctx := context.Background()
// 1. Initialize client and crypto adapter
client := sdk.NewClient(rpcURL, sdk.WithTimeout(10*time.Second))
crypto, err := sdk.LoadNativeCryptoAdapter()
if err != nil {
log.Fatal("failed to load crypto adapter: ", err)
}
// 2. Recover wallet from mnemonic
mnemonic := os.Getenv("WALLET_MNEMONIC")
if mnemonic == "" {
log.Fatal("Set WALLET_MNEMONIC env var")
}
account, err := crypto.RecoverHdWallet(ctx, mnemonic)
if err != nil {
log.Fatal("wallet recovery failed: ", err)
}
fmt.Printf("Address: %s\n", account.Address)
// 3. Query registration cost for the name
cost, err := client.GetRegistrationCost(ctx, name)
if err != nil {
log.Fatal("get registration cost failed: ", err)
}
fmt.Printf("Registration cost for %q: %s\n", name, cost.Formatted())
// 4. Check if name is available
available, err := client.IsNameAvailable(ctx, name)
if err != nil {
log.Fatal("name availability check failed: ", err)
}
fmt.Printf("Name %q available: %v\n", name, available)
if !available {
log.Fatalf("Name %q is already taken", name)
}
// 5. Register name
regHash, err := client.RegisterName(ctx, name, account.Address, account.SecretKey)
if err != nil {
log.Fatal("register name failed: ", err)
}
regReceipt, err := client.WaitForReceipt(ctx, regHash, 20, 2*time.Second)
if err != nil {
log.Fatal("register receipt failed: ", err)
}
fmt.Printf("Name registered in block %d\n", regReceipt.BlockHeight)
// 6. Set target address
targetHash, err := client.SetNameTarget(ctx, name, account.Address, account.SecretKey)
if err != nil {
log.Fatal("set name target failed: ", err)
}
_, err = client.WaitForReceipt(ctx, targetHash, 20, 2*time.Second)
if err != nil {
log.Fatal("set target receipt failed: ", err)
}
fmt.Printf("Target address set to %s\n", account.Address)
// 7. Set profile records
records := []struct{ Key, Value string }{
{"display_name", "Alice"},
{"avatar", "https://example.com/alice.png"},
{"bio", "Builder on Dilithia"},
{"email", "alice@example.com"},
{"website", "https://alice.dev"},
}
for _, r := range records {
hash, err := client.SetNameRecord(ctx, name, r.Key, r.Value, account.SecretKey)
if err != nil {
log.Fatalf("set record %q failed: %v", r.Key, err)
}
_, err = client.WaitForReceipt(ctx, hash, 20, 2*time.Second)
if err != nil {
log.Fatalf("set record %q receipt failed: %v", r.Key, err)
}
fmt.Printf(" Set record %q = %q\n", r.Key, r.Value)
}
// 8. Get all records
allRecords, err := client.GetNameRecords(ctx, name)
if err != nil {
log.Fatal("get name records failed: ", err)
}
fmt.Printf("All records: %+v\n", allRecords)
// 9. Resolve name to address
resolved, err := client.ResolveName(ctx, name)
if err != nil {
log.Fatal("resolve name failed: ", err)
}
fmt.Printf("ResolveName(%q) -> %s\n", name, resolved)
// 10. Reverse resolve address to name
reverseName, err := client.ReverseResolveName(ctx, account.Address)
if err != nil {
log.Fatal("reverse resolve failed: ", err)
}
fmt.Printf("ReverseResolveName(%q) -> %s\n", account.Address, reverseName)
// 11. List all names by owner
owned, err := client.GetNamesByOwner(ctx, account.Address)
if err != nil {
log.Fatal("get names by owner failed: ", err)
}
fmt.Printf("Names owned by %s: %v\n", account.Address, owned)
// 12. Renew name
renewHash, err := client.RenewName(ctx, name, account.SecretKey)
if err != nil {
log.Fatal("renew name failed: ", err)
}
renewReceipt, err := client.WaitForReceipt(ctx, renewHash, 20, 2*time.Second)
if err != nil {
log.Fatal("renew receipt failed: ", err)
}
fmt.Printf("Name renewed in block %d\n", renewReceipt.BlockHeight)
// 13. Transfer name to another address
transferHash, err := client.TransferName(ctx, name, transferTo, account.SecretKey)
if err != nil {
log.Fatal("transfer name failed: ", err)
}
transferReceipt, err := client.WaitForReceipt(ctx, transferHash, 20, 2*time.Second)
if err != nil {
log.Fatal("transfer receipt failed: ", err)
}
fmt.Printf("Name transferred to %s in block %d\n", transferTo, transferReceipt.BlockHeight)
}
Scenario 11: Credential Issuance & Verification¶
An issuer creates a KYC credential schema, issues a credential to a holder, and a verifier checks it with selective disclosure. Covers: registerSchema, issueCredential, getSchema, getCredential, listCredentialsByHolder, listCredentialsByIssuer, verifyCredential, revokeCredential.
package main
import (
"context"
"fmt"
"log"
"os"
"time"
"github.com/dilithia/languages-sdk/go/sdk"
)
const (
rpcURL = "https://rpc.dilithia.network/rpc"
holderAddress = "dil1_holder_address"
)
func main() {
ctx := context.Background()
// 1. Initialize client and crypto adapter
client := sdk.NewClient(rpcURL, sdk.WithTimeout(10*time.Second))
crypto, err := sdk.LoadNativeCryptoAdapter()
if err != nil {
log.Fatal("failed to load crypto adapter: ", err)
}
// 2. Recover issuer wallet
mnemonic := os.Getenv("ISSUER_MNEMONIC")
if mnemonic == "" {
log.Fatal("Set ISSUER_MNEMONIC env var")
}
issuer, err := crypto.RecoverHdWallet(ctx, mnemonic)
if err != nil {
log.Fatal("wallet recovery failed: ", err)
}
fmt.Printf("Issuer address: %s\n", issuer.Address)
// 3. Register a KYC schema
schema := sdk.Schema{
Name: "KYC_Basic_v1",
Attributes: []sdk.SchemaAttribute{
{Name: "full_name", Type: "string"},
{Name: "country", Type: "string"},
{Name: "age", Type: "u64"},
{Name: "verified", Type: "bool"},
},
}
schemaHash, err := client.RegisterSchema(ctx, schema, issuer.SecretKey)
if err != nil {
log.Fatal("register schema failed: ", err)
}
schemaReceipt, err := client.WaitForReceipt(ctx, schemaHash, 20, 2*time.Second)
if err != nil {
log.Fatal("schema receipt failed: ", err)
}
registeredHash := schemaReceipt.Logs[0]["schema_hash"].(string)
fmt.Printf("Schema registered: %s\n", registeredHash)
// 4. Issue credential to holder with commitment hash
commitmentInput := fmt.Sprintf("%s:KYC_Basic_v1:%d", holderAddress, time.Now().Unix())
commitmentHash, err := crypto.HashHex(ctx, []byte(commitmentInput))
if err != nil {
log.Fatal("hash failed: ", err)
}
issueHash, err := client.IssueCredential(ctx, sdk.CredentialParams{
SchemaHash: registeredHash,
Holder: holderAddress,
CommitmentHash: commitmentHash,
Attributes: map[string]interface{}{
"full_name": "Alice Smith",
"country": "CH",
"age": 30,
"verified": true,
},
}, issuer.SecretKey)
if err != nil {
log.Fatal("issue credential failed: ", err)
}
issueReceipt, err := client.WaitForReceipt(ctx, issueHash, 20, 2*time.Second)
if err != nil {
log.Fatal("issue receipt failed: ", err)
}
fmt.Printf("Credential issued in block %d\n", issueReceipt.BlockHeight)
// 5. Get schema by hash
fetchedSchema, err := client.GetSchema(ctx, registeredHash)
if err != nil {
log.Fatal("get schema failed: ", err)
}
fmt.Printf("Schema: %+v\n", fetchedSchema)
// 6. Get credential by commitment
credential, err := client.GetCredential(ctx, commitmentHash)
if err != nil {
log.Fatal("get credential failed: ", err)
}
fmt.Printf("Credential: %+v\n", credential)
// 7. List credentials by holder
holderCreds, err := client.ListCredentialsByHolder(ctx, holderAddress)
if err != nil {
log.Fatal("list by holder failed: ", err)
}
fmt.Printf("Holder has %d credential(s)\n", len(holderCreds))
// 8. List credentials by issuer
issuerCreds, err := client.ListCredentialsByIssuer(ctx, issuer.Address)
if err != nil {
log.Fatal("list by issuer failed: ", err)
}
fmt.Printf("Issuer has %d credential(s)\n", len(issuerCreds))
// 9. Verify selective disclosure — prove age > 18 without revealing exact age
proof, err := crypto.GenerateSelectiveDisclosureProof(ctx, commitmentHash, []string{"age"}, map[string]interface{}{
"age": map[string]interface{}{"operator": "gt", "threshold": 18},
})
if err != nil {
log.Fatal("selective disclosure proof failed: ", err)
}
verified, err := client.VerifyCredential(ctx, commitmentHash, proof)
if err != nil {
log.Fatal("verify credential failed: ", err)
}
fmt.Printf("Selective disclosure (age > 18) verified: %v\n", verified)
// 10. Revoke the credential
revokeHash, err := client.RevokeCredential(ctx, commitmentHash, issuer.SecretKey)
if err != nil {
log.Fatal("revoke credential failed: ", err)
}
revokeReceipt, err := client.WaitForReceipt(ctx, revokeHash, 20, 2*time.Second)
if err != nil {
log.Fatal("revoke receipt failed: ", err)
}
fmt.Printf("Credential revoked in block %d\n", revokeReceipt.BlockHeight)
// 11. Verify revocation by fetching credential again
revokedCred, err := client.GetCredential(ctx, commitmentHash)
if err != nil {
log.Fatal("get revoked credential failed: ", err)
}
fmt.Printf("Credential status after revocation: %s\n", revokedCred.Status)
}