skills/wallet-brc100-go/SKILL.md
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.
npx skillsauth add b-open-io/bsv-skills wallet-brc100-goInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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("[email protected]"),
},
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!
development
This skill should be used when the user asks to integrate "Yours Wallet", "connect a BSV wallet", use "BRC-100", "pay with BSV wallet", add "wallet checkout in a web app", use "yours-wallet-provider", build a "pay with Yours Wallet" button, access "window.CWI", or connect a React app to a BRC-100 wallet.
development
This skill should be used when the user asks to "send BSV", "transfer satoshis", "create payment transaction", "send from WIF", "P2PKH transaction", or needs to build, sign, and broadcast P2PKH transactions from a WIF private key using @bsv/sdk.
development
This skill should be used when the user asks to "encrypt message with BSV key", "decrypt with private key", "ECDH encryption", "AES-256-GCM BSV", "EncryptedMessage", "BRC-2 encryption", or needs to encrypt/decrypt data using BSV keys and @bsv/sdk.
tools
This skill should be used when the user asks to "implement BRC-100 wallet", "use wallet-toolbox", "TypeScript BSV wallet", "BRC-100 implementation", "desktop wallet", "Electron wallet", "browser wallet", "IndexedDB wallet storage", "wallet actions", "wallet baskets", "UTXO management", "createAction", "listOutputs", "wallet certificates", "WalletClient", "noSend", "BEEF payment", "pay-beef", "send BEEF", "Transaction.fromBEEF", "toHexBEEF", or needs guidance on building conforming wallets using @bsv/wallet-toolbox or connecting to a user's BRC-100 wallet via WalletClient.