bsv-wallet-cli 0.1.2

Self-hosted BSV wallet CLI and BRC-100 server, wire-compatible with MetaNet Client
Documentation
# bsv-wallet-cli

> Single Rust binary providing both a CLI wallet and an HTTP server that implements all 28 BRC WalletInterface endpoints, wire-compatible with the MetaNet Client.

## Architecture

The binary has two modes: **CLI commands** for interactive wallet use, and an **HTTP server** (port 3322) that is wire-compatible with MetaNet Client.

```
src/
  main.rs          — CLI entrypoint, routes clap subcommands
  context.rs       — WalletContext: loads ROOT_KEY, opens SQLite, configures services
  cli.rs           — Clap argument definitions
  commands/        — One file per CLI command (init, send, daemon, serve, split, etc.)
  server/
    mod.rs         — Axum router setup, CORS, auth middleware, SpendingLock
    handlers.rs    — All 28 endpoint handlers, each translating MetaNet JSON ↔ SDK types
    types.rs       — MetaNet Client JSON request/response structs (the translation layer)
```

**Dependency chain**: `bsv-wallet-cli` → `bsv-wallet-toolbox-rs` (wallet engine, SQLite storage, signing, broadcasting) → `bsv-rs` (WalletInterface trait, crypto primitives). Both are published on crates.io.

## Conventions

- **MetaNet Client wire compatibility** is the top constraint. The HTTP server must produce identical JSON to the MetaNet Client so existing tools (x402 skill, BRC-31 auth) work without modification.
- **Translation layer** (`server/types.rs`): MetaNet Client uses different JSON shapes than the Rust SDK. Key differences:
  - `protocolID`: mixed array `[2, "name"]` vs SDK's `Protocol { security_level, protocol_name }`
  - `counterparty`: hex string vs `Counterparty::Other(PublicKey)`
  - Binary data (`tx`, `signature`): JSON number arrays `[0,1,2,...]` vs hex strings
  - Field names: MetaNet Client uses `protocolID` / `keyID` (capital D). Use `#[serde(alias = "protocolID")]` since `rename_all = "camelCase"` produces lowercase `d`.
- **SpendingLock**: A FIFO mutex serializes `createAction` calls. With limited UTXOs, concurrent spending races on SQLite's write lock. Non-spending endpoints (crypto, queries) run fully concurrent.
- **Daemon vs Serve**: `daemon` = Monitor (background sync tasks) + HTTP server. `serve` = HTTP server only (dev mode, no background sync).

## Development

```bash
# Build
cargo build --release

# Run daemon (default port 3322)
bsv-wallet daemon

# Run daemon on a custom port
bsv-wallet daemon --port 3322

# Run HTTP server only (no background sync)
bsv-wallet serve --port 3322

# Run tests (41 synthetic tests, no server needed)
cargo test --test integration

# E2E tests (requires a running daemon with funded wallet)
bsv-wallet daemon &
WALLET_URL=http://localhost:3322 cargo test --test integration e2e_ -- --test-threads=1
```

Environment variables: `ROOT_KEY` (required, set by `bsv-wallet init`), `CHAINTRACKS_URL` (optional, for chain tracking), `AUTH_TOKEN` (optional, bearer auth), `RUST_LOG` for tracing. Note: port is set via `--port` CLI flag, not an environment variable.

## Decisions

- **Axum over JSON-RPC**: The MetaNet Client uses REST-style POST endpoints, not JSON-RPC. Matching its URL structure (`/createAction`, `/getPublicKey`, etc.) ensures wire compatibility.
- **SQLite via sqlx**: Single-file database for portability. No external database process needed.
- **Port 3322**: Default HTTP server port. Override with `--port`.
- **crates.io deps**: `bsv-wallet-toolbox-rs` and `bsv-rs` are published on crates.io. No sibling directories needed — `cargo build` works from a fresh clone.
- **BEEF → AtomicBEEF conversion**: WoC returns standard BEEF (`0x0100beef`), but `internalizeAction` requires AtomicBEEF (`0x01010101`). Conversion prepends the AtomicBEEF header + reversed txid before the standard BEEF payload.
- **createAction signing modes**: `sign_and_process: true` + `noSend: false` = signed and broadcast (default). `noSend: true` = signed but not broadcast. `sign_and_process: false` = unsigned template for deferred signing via `signAction`. These are distinct flows — `noSend` is not a dry run.