bsv-wallet-cli
Your agent's favorite wallet.
A self-hosted BRC-100 wallet server and CLI. Single Rust binary. Runs as a command-line wallet, an HTTP server with all 28 WalletInterface endpoints, or both. Wire-compatible with MetaNet Client.
Built for AI agents, autonomous software, and developers who want self-hosted BSV infrastructure.
Install
curl -sSf https://raw.githubusercontent.com/Calhooon/bsv-wallet-cli/main/install.sh | sh
Or build from source:
cargo install --git https://github.com/Calhooon/bsv-wallet-cli.git
Quick Start
# Initialize wallet (generates identity key, creates SQLite database)
bsv-wallet init
# Show your funding address
bsv-wallet address
# Check balance
bsv-wallet balance
# Run the HTTP server (default port 3322)
bsv-wallet daemon
Use Cases
Local development wallet
Run bsv-wallet daemon on your machine. Build apps against the 28 BRC-100 endpoints at localhost:3322. Wire-compatible with MetaNet Client -- any tool built for it works without modification.
Deployable wallet server
Host on your own infrastructure. Any app that speaks BRC-100 connects by changing one URL:
# On your server
bsv-wallet init
bsv-wallet daemon
# From any client (bsv-worm, your app, etc.)
WORM_WALLET_URL=https://wallet.myserver.com:3322
Shared wallet for multi-agent fleets
Multiple AI agents sharing one wallet, one funding source, one UTXO pool:
# One wallet server
bsv-wallet daemon
# N agents pointing at it
WORM_WALLET_URL=http://wallet-internal:3322 bsv-worm serve --port 8080
WORM_WALLET_URL=http://wallet-internal:3322 bsv-worm serve --port 8081
See bsv-worm for an autonomous agent that uses this wallet.
Custody separation
Keep keys on a hardened server, run agents on throwaway VMs. Compromise the agent, keys are safe. The wallet never exposes private key material -- clients send protocol_id, key_id, counterparty and the wallet derives keys internally via BRC-42.
Fork and customize
Swap storage backends, add HSM key management, put it behind your corporate proxy for audit logging, add OAuth/mTLS -- the BRC-100 interface stays identical. Anything built for MetaNet Client works unmodified against your fork.
Why bsv-wallet-cli?
- No GUI required -- Runs headless on servers, VMs, CI pipelines. The only BRC-100 wallet that doesn't need a desktop or browser.
- Non-custodial -- Your keys stay on your machine. No cloud service, no account, no third party.
- Wire-compatible -- Any tool built for MetaNet Client works without modification. Same endpoints, same JSON format.
- Built for agents -- AI agents, autonomous software, multi-wallet fleets. One wallet server, N agents pointing at it.
CLI Commands
| Command | Description |
|---|---|
init |
Generate identity key and create wallet database |
identity |
Show public key and identity info |
balance |
Show spendable balance |
address |
Show BRC-29 funding address |
send <address> <sats> |
Send BSV to a P2PKH address |
fund <beef_hex> |
Internalize a BEEF transaction (receive funds) |
outputs |
List unspent outputs |
actions |
List transaction history |
split --count N |
Split UTXOs for concurrent spending |
gift-send <pubkey> <sats> --unlock <date> |
Send a time-locked BSV gift |
gift-inspect <txid> |
See a gift, prove it's yours + when it unlocks |
gift-claim <txid> |
Claim a time-locked gift after its unlock time |
daemon |
Run monitor + HTTP server (production) |
serve |
Run HTTP server only (dev mode) |
services |
Show blockchain service status |
Time-locked gifts
Pay someone such that they own the key immediately but can't spend until a date
you set (--unlock +3mo, 2026-12-25, or a unix timestamp). See
GIFT.md for the full flow. Note: unlock dates are supported through
Jan 2038; later dates use a code path not yet mainnet-proven — gift-send
warns and you should pick an earlier date for real value.
HTTP Server
All 28 BRC WalletInterface endpoints on http://127.0.0.1:3322, wire-compatible with MetaNet Client.
Endpoints
Status (GET)
/isAuthenticated-- health check/getHeight-- current chain height/getNetwork--mainnetortestnet/getVersion-- wallet version/waitForAuthentication-- auth status
Crypto (POST, requires Origin header)
/getPublicKey-- identity or derived public key/createSignature//verifySignature-- ECDSA signing/encrypt//decrypt-- symmetric encryption via BRC-42/createHmac//verifyHmac-- HMAC operations/getHeaderForHeight-- block header lookup
Transactions (POST, requires Origin header)
/createAction-- build, sign, and broadcast transactions/signAction-- sign a deferred (unsigned) transaction/abortAction-- cancel a deferred transaction and release UTXOs/internalizeAction-- accept incoming payments/listActions-- transaction history/listOutputs-- UTXO listing/relinquishOutput-- release an output
Certificates (POST, requires Origin header)
/acquireCertificate-- store a certificate/listCertificates-- query certificates/proveCertificate-- prove certificate ownership/relinquishCertificate-- delete a certificate
Discovery (POST, requires Origin header)
/discoverByIdentityKey//discoverByAttributes-- certificate discovery/revealCounterpartyKeyLinkage//revealSpecificKeyLinkage-- key linkage revelation
Architecture
bsv-wallet-cli (this repo -- CLI + HTTP server)
|-- rust-wallet-toolbox (wallet engine -- storage, signing, broadcasting)
+-- bsv-sdk (WalletInterface trait, types, crypto primitives)
- Storage: SQLite via sqlx (single file, portable)
- Concurrency: Spending operations queue via FIFO lock. All other endpoints (crypto, queries, status) are fully concurrent.
- Blockchain: Chaintracks (primary) with WoC/BHS/Bitails failover
Configuration
All configuration is via environment variables:
| Variable | Required | Description |
|---|---|---|
ROOT_KEY |
Yes | Wallet root private key (hex). Set by bsv-wallet init. |
CHAINTRACKS_URL |
No | Chaintracks server URL for chain tracking |
AUTH_TOKEN |
No | Bearer token for HTTP auth (localhost-only, optional) |
TLS_CERT_PATH |
No | TLS certificate path (requires --features tls) |
TLS_KEY_PATH |
No | TLS private key path (requires --features tls) |
MIN_UTXOS |
No | Low UTXO warning threshold in daemon mode (default: 3) |
ARC_URL |
No | Override the broadcaster URL (classic ARC, or the Arcade endpoint in Arcade mode) |
ARC_MODE=arcade / ARCADE=1 |
No | Arcade V2 mode: EF-only submit, SSE push statuses, push proofs |
CALLBACK_TOKEN |
No | Override the per-wallet callback token (auto-generated + persisted as <db>.callback-token otherwise) |
PUBLIC_CALLBACK_URL |
No | Public HTTPS URL for Arcade status webhooks (X-CallbackUrl), pointing at this daemon's /arc-callback |
BIND_ADDR |
No | HTTP server bind address (default 127.0.0.1; set 0.0.0.0 for the tunnel/webhook case) |
RELAY_URL / RELAY_POLL_SECS |
No | Drain a bsv-wallet-relay queue (see proof-delivery ladder) |
TAAL_API_KEY / MAIN_TAAL_API_KEY |
No | TAAL ARC auth (Bearer / raw Authorization respectively) |
Port is set via --port CLI flag (default: 3322), not an environment variable.
Broadcasting: Arcade V2 mode
By default the wallet broadcasts through classic ARC (TAAL → GorillaPool →
Bitails → WoC failover) and the Monitor discovers merkle proofs by polling.
Setting ARC_MODE=arcade (or ARCADE=1) switches the primary broadcaster to
Arcade V2, the Teranode-native broadcaster
(https://arcade-v2-us-1.bsvblockchain.tech unless ARC_URL overrides):
- EF-only submit — Arcade rejects BEEF; every unmined ancestor in a
transaction's BEEF is converted to Extended Format (BRC-30) and batch-posted
as binary concat to
POST /txs(Arcade dedupes re-submitted ancestors). - Always-async statuses — submit returns
202; the Monitor'sarcade_eventstask subscribes outbound to the per-wallet SSE stream (GET /events?callbackToken=…) and gates spendability onSEEN_ON_NETWORK(~3s, reliable). Fatal verdicts (REJECTED,DOUBLE_SPEND_ATTEMPTED) mark the transaction so it is never re-broadcast. - Failover preserved — classic ARC providers stay behind Arcade in the postBeef chain, so a transient Arcade outage never blocks a broadcast.
Each wallet db gets its own callback token (auto-generated, persisted as
<db>.callback-token, never logged), so any number of concurrent wallets
(e.g. :3322, :3382) get independent status streams.
Proof delivery: the acquisition ladder
Merkle proofs (BUMPs) complete a transaction (unproven → completed). The
wallet acquires them by the best available rung — god-tier when possible,
never blocked:
(a) Direct webhook — god-tier, zero polling. When the daemon can present
public HTTPS, Arcade pushes the MINED webhook — which carries the
merklePath — straight into wallet storage via the daemon's own
POST /arc-callback route. Two ways to be publicly reachable:
# TLS directly (public IP + cert):
TLS_CERT_PATH=/path/cert.pem TLS_KEY_PATH=/path/key.pem \
PUBLIC_CALLBACK_URL=https://wallet.example.com:3322/arc-callback \
ARC_MODE=arcade bsv-wallet daemon # built with --features tls
# Or a tunnel (cloudflared shown; tailscale funnel works the same way):
cloudflared tunnel --url http://localhost:3322 # gives https://<name>.trycloudflare.com
PUBLIC_CALLBACK_URL=https://<name>.trycloudflare.com/arc-callback \
ARC_MODE=arcade bsv-wallet daemon
/arc-callback is authenticated by the per-wallet callback token
(Authorization: Bearer <token> or X-CallbackToken) and is exempt from
the wallet AUTH_TOKEN bearer — the broadcaster doesn't know your wallet
token. Invalid proofs are validated against the ChainTracker and rejected,
never stored. Note Arcade's webhook target is SSRF-guarded: it must be public
HTTPS — plain localhost can never receive it, which is why the SSE rung below
is the default.
(b) SSE-triggered fetch — the always-works default. On a MINED SSE event
the Monitor immediately fetches the BUMP through its existing services stack
(WhatsOnChain/Bitails/ARC) and ingests it through the same validated path.
Event-driven, no schedule polling, fully local — works from a laptop behind
NAT with zero setup. (The 2-hourly polling sync remains as the final safety
net, exactly as before.)
(c) bsv-wallet-relay — one public receiver for fleets. A small
store-and-forward companion binary (ships in this repo): Arcade posts
callbacks to the relay; wallet daemons poll their queue outbound by
callback token and ingest through the same path — connect/disconnect at will.
# On the public box:
RELAY_PORT=3390 bsv-wallet-relay # sqlite queue in relay.db
# Each wallet daemon (can be localhost-only):
ARC_MODE=arcade \
PUBLIC_CALLBACK_URL=https://relay.example.com/arc-callback \
RELAY_URL=https://relay.example.com \
bsv-wallet daemon
Relay design: a token is auto-registered on first /pull (wallets poll from
boot, before any submit); callbacks with unknown tokens are rejected, and
tokens are high-entropy so a guessed token reads nothing. Set
RELAY_ADMIN_TOKEN + RELAY_REQUIRE_REGISTER=1 to require explicit
POST /register instead.
MCP Server
A separate binary exposes all 28 endpoints as MCP tools for AI agents (Claude Code, Codex, etc.):
# Start the wallet daemon first
bsv-wallet daemon
# In another terminal -- start MCP server (stdio transport, 29 tools)
bsv-wallet-mcp
Add to your Claude Code MCP config:
{
"mcpServers": {
"bsv-wallet": {
"command": "bsv-wallet-mcp",
"env": { "WALLET_URL": "http://localhost:3322" }
}
}
}
Building
# Standard build
cargo build --release
# With TLS support
cargo build --release --features tls
Testing
# Run all synthetic tests (no server needed, 41 tests)
cargo test --test integration
# Run e2e tests against a live wallet
bsv-wallet daemon &
WALLET_URL=http://localhost:3322 cargo test --test integration e2e_ -- --test-threads=1
No comments yet
Be the first to share your take.