Expert guidance for implementing BRC-100 conforming wallets using Go wallet-toolbox. Covers wallet initialization, transaction creation/signing, key management, storage, and certificate operations following the BRC-100 standard in Go.
Provides expert guidance for implementing BRC-100 wallets in Go using go-wallet-toolbox. Claude will use this when you need to initialize wallets, create/sign transactions, manage keys, configure storage, or handle certificate operations following BRC-100 standards.
/plugin marketplace add b-open-io/bsv-skills/plugin install bsv-skills@b-open-ioThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Comprehensive guide for implementing BRC-100 conforming wallets using the go-wallet-toolbox package.
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"
)
go get github.com/bsv-blockchain/go-wallet-toolbox
go get github.com/bsv-blockchain/go-sdk
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
}
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),
)
}
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),
)
}
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")
}
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
}
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
}
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
}
// 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
}
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
}
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
}
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)
}
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)
}
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)
}
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)
}
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)
}
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
}
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)
}
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)
}
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
}
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
}
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)
}
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
}
| 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!
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.