#601

Global Rank · of 601 Skills

wallet-brc100-go AI Agent Skill

View Source: b-open-io/bsv-skills

Medium

Installation

npx skills add b-open-io/bsv-skills --skill wallet-brc100-go

20

Installs

BRC-100 Wallet Implementation Guide (Go)

Comprehensive guide for implementing BRC-100 conforming wallets using the go-wallet-toolbox package.

🎯 Quick Reference

Core Dependencies

import (
    "github.com/bsv-blockchain/go-wallet-toolbox/pkg/wallet"
    "github.com/bsv-blockchain/go-wallet-toolbox/pkg/storage"
    "github.com/bsv-blockchain/go-wallet-toolbox/pkg/services"
    sdk "github.com/bsv-blockchain/go-sdk/wallet"
    "github.com/bsv-blockchain/go-sdk/primitives/ec"
    "github.com/bsv-blockchain/go-sdk/transaction"
)

Installation

go get github.com/bsv-blockchain/go-wallet-toolbox
go get github.com/bsv-blockchain/go-sdk

πŸ“š Table of Contents

  1. Wallet Initialization
  2. Transaction Operations
  3. Key Management
  4. Storage Configuration
  5. Certificate Operations
  6. Error Handling
  7. Production Patterns

1. Wallet Initialization

Pattern A: Simple Wallet Setup

package main

import (
    "context"
    "log"

    "github.com/bsv-blockchain/go-wallet-toolbox/pkg/wallet"
    "github.com/bsv-blockchain/go-wallet-toolbox/pkg/storage"
    "github.com/bsv-blockchain/go-wallet-toolbox/pkg/services"
    "github.com/bsv-blockchain/go-wallet-toolbox/pkg/defs"
    sdk "github.com/bsv-blockchain/go-sdk/wallet"
    "github.com/bsv-blockchain/go-sdk/primitives/ec"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

func createWallet() (*wallet.Wallet, error) {
    ctx := context.Background()

    // 1. Generate root key (or derive from mnemonic)
    rootKey, err := ec.NewPrivateKey()
    if err != nil {
        return nil, err
    }

    // 2. Create key deriver
    keyDeriver := sdk.NewKeyDeriver(rootKey)

    // 3. Setup SQLite storage
    db, err := gorm.Open(sqlite.Open("wallet.db"), &gorm.Config{})
    if err != nil {
        return nil, err
    }

    storageOpts := &storage.Options{
        Db:                 db,
        StorageIdentityKey: rootKey.PubKey().Compressed(),
        StorageName:        "main-wallet",
    }

    walletStorage, err := storage.NewStorage(storageOpts)
    if err != nil {
        return nil, err
    }

    // 4. Setup services (mainnet)
    servicesOpts := &services.Options{
        Network: defs.Mainnet,
        ArcURL:  "https://arc.taal.com",
        WocURL:  "https://api.whatsonchain.com/v1/bsv/main",
    }

    walletServices, err := services.NewServices(ctx, servicesOpts)
    if err != nil {
        return nil, err
    }

    // 5. Create wallet
    w, err := wallet.NewWallet(
        ctx,
        keyDeriver,
        walletStorage,
        wallet.WithServices(walletServices),
        wallet.WithNetwork(defs.Mainnet),
    )
    if err != nil {
        return nil, err
    }

    return w, nil
}

Pattern B: Wallet with MySQL Storage

import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

func createMySQLWallet(ctx context.Context, rootKey *ec.PrivateKey) (*wallet.Wallet, error) {
    // MySQL DSN
    dsn := "user:password@tcp(127.0.0.1:3306)/wallet_db?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        return nil, err
    }

    storageOpts := &storage.Options{
        Db:                 db,
        StorageIdentityKey: rootKey.PubKey().Compressed(),
        StorageName:        "mysql-wallet",
    }

    walletStorage, err := storage.NewStorage(storageOpts)
    if err != nil {
        return nil, err
    }

    keyDeriver := sdk.NewKeyDeriver(rootKey)
    walletServices, err := services.NewServices(ctx, &services.Options{
        Network: defs.Mainnet,
        ArcURL:  "https://arc.taal.com",
    })
    if err != nil {
        return nil, err
    }

    return wallet.NewWallet(
        ctx,
        keyDeriver,
        walletStorage,
        wallet.WithServices(walletServices),
        wallet.WithNetwork(defs.Mainnet),
    )
}

Pattern C: Testnet Wallet

func createTestnetWallet(ctx context.Context) (*wallet.Wallet, error) {
    rootKey, _ := ec.NewPrivateKey()
    keyDeriver := sdk.NewKeyDeriver(rootKey)

    db, _ := gorm.Open(sqlite.Open("testnet.db"), &gorm.Config{})
    storageOpts := &storage.Options{
        Db:                 db,
        StorageIdentityKey: rootKey.PubKey().Compressed(),
        StorageName:        "testnet-wallet",
    }
    walletStorage, _ := storage.NewStorage(storageOpts)

    // Testnet services
    walletServices, err := services.NewServices(ctx, &services.Options{
        Network: defs.Testnet,
        ArcURL:  "https://arc-test.taal.com",
        WocURL:  "https://api.whatsonchain.com/v1/bsv/test",
    })
    if err != nil {
        return nil, err
    }

    return wallet.NewWallet(
        ctx,
        keyDeriver,
        walletStorage,
        wallet.WithServices(walletServices),
        wallet.WithNetwork(defs.Testnet),
    )
}

2. Transaction Operations

Create a Transaction

import (
    "github.com/bsv-blockchain/go-sdk/script"
    sdk "github.com/bsv-blockchain/go-sdk/wallet"
)

func sendBSV(
    ctx context.Context,
    w *wallet.Wallet,
    recipientAddress string,
    satoshis uint64,
) (string, error) {
    // Build locking script from address
    lockingScript, err := script.NewP2PKHFromAddress(recipientAddress)
    if err != nil {
        return "", err
    }

    // Create action args
    args := &sdk.CreateActionArgs{
        Description: "Send BSV payment",
        Outputs: []sdk.CreateActionOutput{
            {
                LockingScript:    lockingScript.String(),
                Satoshis:         satoshis,
                OutputDescription: fmt.Sprintf("Payment to %s", recipientAddress),
                Basket:            "default",
                Tags:              []string{"payment"},
            },
        },
        Options: &sdk.CreateActionOptions{
            AcceptDelayedBroadcast: false, // Broadcast immediately
            RandomizeOutputs:       true,  // Privacy
        },
    }

    // Create action
    result, err := w.CreateAction(ctx, args)
    if err != nil {
        return "", err
    }

    if result.TxID != nil {
        return *result.TxID, nil
    }

    // Handle signing if needed
    if result.SignableTransaction != nil {
        return signAndFinalize(ctx, w, result.SignableTransaction)
    }

    return "", fmt.Errorf("unexpected result format")
}

Sign a Transaction

func signTransaction(
    ctx context.Context,
    w *wallet.Wallet,
    reference string,
    unlockingScripts map[uint32]sdk.UnlockingScriptSend,
) (*sdk.SignActionResult, error) {
    args := &sdk.SignActionArgs{
        Reference: reference,
        Spends:    unlockingScripts,
    }

    result, err := w.SignAction(ctx, args)
    if err != nil {
        return nil, err
    }

    return result, nil
}

Check Wallet Balance

func getBalance(ctx context.Context, w *wallet.Wallet) (uint64, error) {
    // Use special operation for quick balance
    args := &sdk.ListOutputsArgs{
        Basket: "00000000000000000000000000000000", // Special basket for balance
    }

    result, err := w.ListOutputs(ctx, args)
    if err != nil {
        return 0, err
    }

    return uint64(result.TotalOutputs), nil
}

// Get detailed balance with UTXOs
func getDetailedBalance(ctx context.Context, w *wallet.Wallet) (*sdk.ListOutputsResult, error) {
    args := &sdk.ListOutputsArgs{
        Basket:    "default",
        Spendable: to.Ptr(true),
        Limit:     to.Ptr(uint32(100)),
        Offset:    to.Ptr(uint32(0)),
    }

    result, err := w.ListOutputs(ctx, args)
    if err != nil {
        return nil, err
    }

    log.Printf("Found %d spendable outputs", len(result.Outputs))
    for _, output := range result.Outputs {
        log.Printf("  %s: %d satoshis", output.Outpoint, output.Satoshis)
    }

    return result, nil
}

List Transaction History

func listTransactions(ctx context.Context, w *wallet.Wallet) (*sdk.ListActionsResult, error) {
    args := &sdk.ListActionsArgs{
        Labels:         []string{},
        LabelQueryMode: to.Ptr(sdk.LabelQueryModeAny),
        Limit:          to.Ptr(uint32(50)),
        Offset:         to.Ptr(uint32(0)),
    }

    result, err := w.ListActions(ctx, args)
    if err != nil {
        return nil, err
    }

    log.Printf("Found %d actions", result.TotalActions)
    for _, action := range result.Actions {
        log.Printf("  %s: %s - %s", *action.TxID, action.Status, action.Description)
    }

    return result, nil
}

3. Key Management

Get Public Key

// Get identity key
func getIdentityKey(ctx context.Context, w *wallet.Wallet) (string, error) {
    args := &sdk.GetPublicKeyArgs{
        IdentityKey: to.Ptr(true),
    }

    result, err := w.GetPublicKey(ctx, args)
    if err != nil {
        return "", err
    }

    return result.PublicKey, nil
}

// Get derived key for protocol
func getDerivedKey(
    ctx context.Context,
    w *wallet.Wallet,
    protocolID sdk.ProtocolID,
    keyID string,
    counterparty string,
) (string, error) {
    args := &sdk.GetPublicKeyArgs{
        ProtocolID:   protocolID,
        KeyID:        keyID,
        Counterparty: to.Ptr(counterparty),
    }

    result, err := w.GetPublicKey(ctx, args)
    if err != nil {
        return "", err
    }

    return result.PublicKey, nil
}

Encrypt/Decrypt Data

import "encoding/base64"

func encryptMessage(
    ctx context.Context,
    w *wallet.Wallet,
    plaintext string,
    recipientPubKey string,
) (string, error) {
    args := &sdk.WalletEncryptArgs{
        Plaintext:    []byte(plaintext),
        ProtocolID:   sdk.ProtocolID{2, "secure-messaging"},
        KeyID:        "msg-key",
        Counterparty: to.Ptr(recipientPubKey),
    }

    result, err := w.Encrypt(ctx, args)
    if err != nil {
        return "", err
    }

    return base64.StdEncoding.EncodeToString(result.Ciphertext), nil
}

func decryptMessage(
    ctx context.Context,
    w *wallet.Wallet,
    ciphertext string,
    senderPubKey string,
) (string, error) {
    ciphertextBytes, err := base64.StdEncoding.DecodeString(ciphertext)
    if err != nil {
        return "", err
    }

    args := &sdk.WalletDecryptArgs{
        Ciphertext:   ciphertextBytes,
        ProtocolID:   sdk.ProtocolID{2, "secure-messaging"},
        KeyID:        "msg-key",
        Counterparty: to.Ptr(senderPubKey),
    }

    result, err := w.Decrypt(ctx, args)
    if err != nil {
        return "", err
    }

    return string(result.Plaintext), nil
}

Create Signature

func signData(ctx context.Context, w *wallet.Wallet, data string) (string, error) {
    args := &sdk.CreateSignatureArgs{
        Data:         []byte(data),
        ProtocolID:   sdk.ProtocolID{2, "document-signing"},
        KeyID:        "sig-key",
        Counterparty: to.Ptr("self"),
    }

    result, err := w.CreateSignature(ctx, args)
    if err != nil {
        return "", err
    }

    return base64.StdEncoding.EncodeToString(result.Signature), nil
}

4. Storage Configuration

SQLite Storage

import (
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

func setupSQLiteStorage(dbPath string, identityKey []byte) (*storage.Storage, error) {
    db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
    if err != nil {
        return nil, err
    }

    opts := &storage.Options{
        Db:                 db,
        StorageIdentityKey: identityKey,
        StorageName:        "sqlite-wallet",
    }

    return storage.NewStorage(opts)
}

MySQL Storage

import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

func setupMySQLStorage(dsn string, identityKey []byte) (*storage.Storage, error) {
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        return nil, err
    }

    opts := &storage.Options{
        Db:                 db,
        StorageIdentityKey: identityKey,
        StorageName:        "mysql-wallet",
    }

    return storage.NewStorage(opts)
}

PostgreSQL Storage

import (
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

func setupPostgresStorage(dsn string, identityKey []byte) (*storage.Storage, error) {
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        return nil, err
    }

    opts := &storage.Options{
        Db:                 db,
        StorageIdentityKey: identityKey,
        StorageName:        "postgres-wallet",
    }

    return storage.NewStorage(opts)
}

5. Certificate Operations

Acquire Certificate (Direct Protocol)

func acquireDirectCertificate(
    ctx context.Context,
    w *wallet.Wallet,
    certType string,
    certifier string,
) (*sdk.AcquireCertificateResult, error) {
    identityKey, _ := getIdentityKey(ctx, w)

    args := &sdk.AcquireCertificateArgs{
        AcquisitionProtocol: sdk.AcquisitionProtocolDirect,
        Type:                certType,
        Certifier:           certifier,
        SerialNumber:        []byte("cert-serial-123"),
        Subject:             identityKey,
        RevocationOutpoint:  "txid.vout",
        Fields: map[string][]byte{
            "name":  []byte("Alice Smith"),
            "email": []byte("alice@example.com"),
        },
        KeyringForSubject: map[string]string{
            // Master keyring data
        },
        Signature: []byte("signature-bytes"),
    }

    return w.AcquireCertificate(ctx, args)
}

Acquire Certificate (Issuance Protocol)

func requestCertificateIssuance(
    ctx context.Context,
    w *wallet.Wallet,
) (*sdk.AcquireCertificateResult, error) {
    args := &sdk.AcquireCertificateArgs{
        AcquisitionProtocol: sdk.AcquisitionProtocolIssuance,
        Type:                "https://example.com/kyc-certificate",
        Certifier:           "certifier-identity-key",
        CertifierURL:        to.Ptr("https://certifier.example.com"),
        Fields: map[string]interface{}{
            "name":      "Alice Smith",
            "birthdate": "1990-01-01",
            "country":   "US",
        },
    }

    return w.AcquireCertificate(ctx, args)
}

List Certificates

func listCertificates(ctx context.Context, w *wallet.Wallet) (*sdk.ListCertificatesResult, error) {
    args := &sdk.ListCertificatesArgs{
        Certifiers: []string{"certifier-identity-key"},
        Types:      []string{"https://example.com/user-certificate"},
        Limit:      to.Ptr(uint32(50)),
        Offset:     to.Ptr(uint32(0)),
    }

    result, err := w.ListCertificates(ctx, args)
    if err != nil {
        return nil, err
    }

    log.Printf("Found %d certificates", result.TotalCertificates)
    for _, cert := range result.Certificates {
        log.Printf("  Type: %s, Certifier: %s", cert.Type, cert.Certifier)
    }

    return result, nil
}

Prove Certificate

func proveCertificate(
    ctx context.Context,
    w *wallet.Wallet,
    certificateID string,
) (*sdk.ProveCertificateResult, error) {
    args := &sdk.ProveCertificateArgs{
        CertificateID:   certificateID,
        FieldsToReveal:  []string{"name", "email"},
        Verifier:        "verifier-identity-key",
        Privileged:      to.Ptr(false),
    }

    return w.ProveCertificate(ctx, args)
}

6. Error Handling

Standard Error Handling

import (
    "errors"
    "github.com/bsv-blockchain/go-wallet-toolbox/pkg/defs"
)

func handleWalletError(err error) {
    if err == nil {
        return
    }

    // Check for specific error types
    var invalidParamErr *defs.ErrInvalidParameter
    if errors.As(err, &invalidParamErr) {
        log.Printf("Invalid parameter: %s - %s", invalidParamErr.Parameter, invalidParamErr.Message)
        return
    }

    var internalErr *defs.ErrInternal
    if errors.As(err, &internalErr) {
        log.Printf("Internal error: %s", internalErr.Message)
        return
    }

    // Generic error
    log.Printf("Wallet error: %v", err)
}

Transaction Error Handling

func sendWithErrorHandling(
    ctx context.Context,
    w *wallet.Wallet,
    recipient string,
    satoshis uint64,
) (string, error) {
    txid, err := sendBSV(ctx, w, recipient, satoshis)
    if err != nil {
        handleWalletError(err)

        // Check if it's a review actions error
        if strings.Contains(err.Error(), "review") {
            log.Printf("Transaction requires review")
            // Handle review flow
        }

        return "", err
    }

    log.Printf("Transaction sent: %s", txid)
    return txid, nil
}

7. Production Patterns

Wallet Manager Pattern

type WalletManager struct {
    wallet  *wallet.Wallet
    storage *storage.Storage
    mu      sync.RWMutex
}

func NewWalletManager(ctx context.Context, rootKey *ec.PrivateKey) (*WalletManager, error) {
    db, err := gorm.Open(sqlite.Open("wallet.db"), &gorm.Config{})
    if err != nil {
        return nil, err
    }

    storageOpts := &storage.Options{
        Db:                 db,
        StorageIdentityKey: rootKey.PubKey().Compressed(),
        StorageName:        "managed-wallet",
    }

    walletStorage, err := storage.NewStorage(storageOpts)
    if err != nil {
        return nil, err
    }

    keyDeriver := sdk.NewKeyDeriver(rootKey)
    walletServices, err := services.NewServices(ctx, &services.Options{
        Network: defs.Mainnet,
        ArcURL:  "https://arc.taal.com",
    })
    if err != nil {
        return nil, err
    }

    w, err := wallet.NewWallet(
        ctx,
        keyDeriver,
        walletStorage,
        wallet.WithServices(walletServices),
        wallet.WithNetwork(defs.Mainnet),
    )
    if err != nil {
        return nil, err
    }

    return &WalletManager{
        wallet:  w,
        storage: walletStorage,
    }, nil
}

func (wm *WalletManager) GetWallet() *wallet.Wallet {
    wm.mu.RLock()
    defer wm.mu.RUnlock()
    return wm.wallet
}

func (wm *WalletManager) Close() error {
    wm.mu.Lock()
    defer wm.mu.Unlock()

    // Cleanup resources
    return nil
}

Retry Pattern

func sendWithRetry(
    ctx context.Context,
    w *wallet.Wallet,
    recipient string,
    satoshis uint64,
    maxRetries int,
) (string, error) {
    var lastErr error

    for attempt := 1; attempt <= maxRetries; attempt++ {
        txid, err := sendBSV(ctx, w, recipient, satoshis)
        if err == nil {
            return txid, nil
        }

        lastErr = err
        log.Printf("Attempt %d failed: %v", attempt, err)

        if attempt < maxRetries {
            time.Sleep(time.Second * time.Duration(attempt))
        }
    }

    return "", fmt.Errorf("all %d retries failed: %w", maxRetries, lastErr)
}

Background Monitor Pattern

import "github.com/bsv-blockchain/go-wallet-toolbox/pkg/monitor"

func setupMonitor(
    ctx context.Context,
    walletStorage *storage.Storage,
    walletServices *services.WalletServices,
) (*monitor.Monitor, error) {
    opts := &monitor.Options{
        Storage:  walletStorage,
        Services: walletServices,
        Network:  defs.Mainnet,
    }

    m, err := monitor.NewMonitor(ctx, opts)
    if err != nil {
        return nil, err
    }

    // Start monitoring in background
    go func() {
        if err := m.Start(ctx); err != nil {
            log.Printf("Monitor error: %v", err)
        }
    }()

    return m, nil
}

πŸ”— Additional Resources


πŸ“ Common Patterns Summary

Task Function Key Args
Send BSV CreateAction() Outputs, Options
Check balance ListOutputs() Special basket
List UTXOs ListOutputs() Basket, Spendable
Get history ListActions() Labels, Limit
Get pubkey GetPublicKey() ProtocolID, KeyID
Encrypt data Encrypt() Plaintext, Counterparty
Get certificate AcquireCertificate() Type, Certifier

Remember: Always use context for cancellation, handle errors explicitly, and follow Go best practices for concurrent wallet access!

Installs

Installs 20
Global Rank #601 of 601

Security Audit

ath Safe
socket Safe
Alerts: 0 Score: 90
snyk Medium
EU EU-Hosted Inference API

Power your AI Agents with the best open-source models.

Drop-in OpenAI-compatible API. No data leaves Europe.

Explore Inference API

GLM

GLM 5

$1.00 / $3.20

per M tokens

Kimi

Kimi K2.5

$0.60 / $2.80

per M tokens

MiniMax

MiniMax M2.5

$0.30 / $1.20

per M tokens

Qwen

Qwen3.5 122B

$0.40 / $3.00

per M tokens

How to use this skill

1

Install wallet-brc100-go by running npx skills add b-open-io/bsv-skills --skill wallet-brc100-go in your project directory. Run the install command above in your project directory. The skill file will be downloaded from GitHub and placed in your project.

2

No configuration needed. Your AI agent (Claude Code, Cursor, Windsurf, etc.) automatically detects installed skills and uses them as context when generating code.

3

The skill enhances your agent's understanding of wallet-brc100-go, helping it follow established patterns, avoid common mistakes, and produce production-ready output.

What you get

Skills are plain-text instruction files β€” not executable code. They encode expert knowledge about frameworks, languages, or tools that your AI agent reads to improve its output. This means zero runtime overhead, no dependency conflicts, and full transparency: you can read and review every instruction before installing.

Compatibility

This skill works with any AI coding agent that supports the skills.sh format, including Claude Code (Anthropic), Cursor, Windsurf, Cline, Aider, and other tools that read project-level context files. Skills are framework-agnostic at the transport level β€” the content inside determines which language or framework it applies to.

Data sourced from the skills.sh registry and GitHub. Install counts and security audits are updated regularly.

EU Made in Europe

Chat with 100+ AI Models in one App.

Use Claude, ChatGPT, Gemini alongside with EU-Hosted Models like Deepseek, GLM-5, Kimi K2.5 and many more.

Get the App:

Customer Support