wallet-brc100-go

This skill should be used when the user asks to "implement BRC-100 wallet in Go", "use go-wallet-toolbox", "Go BSV wallet", "BRC-100 Go implementation", or needs guidance on building conforming wallets using Go wallet-toolbox.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "wallet-brc100-go" with this command: npx skills add b-open-io/bsv-skills/b-open-io-bsv-skills-wallet-brc100-go

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

TaskFunctionKey Args
Send BSVCreateAction()Outputs, Options
Check balanceListOutputs()Special basket
List UTXOsListOutputs()Basket, Spendable
Get historyListActions()Labels, Limit
Get pubkeyGetPublicKey()ProtocolID, KeyID
Encrypt dataEncrypt()Plaintext, Counterparty
Get certificateAcquireCertificate()Type, Certifier

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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Web3

wallet-brc100

No summary provided by upstream source.

Repository SourceNeeds Review
Web3

wallet-encrypt-decrypt

No summary provided by upstream source.

Repository SourceNeeds Review
Web3

wallet-send-bsv

No summary provided by upstream source.

Repository SourceNeeds Review
General

create-script-template

No summary provided by upstream source.

Repository SourceNeeds Review