Architecture

sats is a native Bitcoin wallet with a portable core. The CLI and MCP server share wallet state and transaction preparation, while agent sends add a bounded authorization step before signing.

System shape

flowchart TD
    CLI["Human CLI"] --> Native["Native workflows"]
    MCP["MCP server"] --> Native
    Native --> Core["sats-core"]
    Native --> State["Store + watch-only wallet"]
    Native --> Providers["Chain providers + guards"]

crates/sats-core owns deterministic wallet and authorization behavior. crates/sats owns environment effects: command parsing, terminal rendering, files, SQLite, network clients, provider selection, passwords, and MCP stdio.

Crates

sats-core

The portable engine has no filesystem, network, clock, terminal, or async runtime dependencies. Callers provide time and operate on a BDK wallet they own.

ModuleResponsibility
authzGrant model, spend request, deterministic allow/deny decision, reservation and refund
engineIn-memory PSBT preparation and conservative UTXO exclusion
planPrepared spends, legacy PSBT sessions, finalized transaction records, and legacy-plan conversion
seedBIP-39 generation and parsing; BIP-86 public and private descriptors
sealVersioned Argon2id/XChaCha20-Poly1305 secret envelopes
signerEnvironment-neutral signer trait and local mnemonic signer
error, fmt, amountTyped errors, satoshi formatting, and shorthand amount parsing

sats

The native crate composes the portable core with operating-system and network adapters.

ModuleResponsibility
main, cliParse global flags and commands, resolve the selected network, dispatch workflows
commandsHuman CLI workflows and their text/JSON presentation
configTOML configuration and canonical network names
storeXDG paths, atomic files, PSBT sessions, finalized transactions, legacy plans, grants, and sensitive-file permissions
walletdSQLite-backed watch-only BDK wallet creation, loading, and persistence
providerTyped capabilities, driver resolution, chain access, and UTXO guards
keys, passwordUnlock the master seed or a grant-wrapped seed
mcpMCP stdio server, tool schemas, and agent send orchestration
uiTerminal presentation

main.rs is the composition root. Leaf feature logic belongs in the owning module, not in dispatch.

sats-web

The website playground: sats-core compiled to WebAssembly behind a small JSON API for the interactive terminal at the project website. Only the chain is simulated (an in-memory faucet and instant confirmation); planning, UTXO exclusion, signing, sealing, and grant authorization run the same core code as the native surfaces. It holds no compatibility surface — the CLI and MCP schemas remain the stable contracts — and it must never gain filesystem, network, or native-store dependencies.

Persistent state

Without SATS_DIR, configuration and data use platform XDG locations. SATS_DIR places both under one directory, which is useful for tests and isolated runs.

StatePath relative to the data/config rootContents
Configurationconfig.tomlDefault network and typed providers
Master seedseed.sealedPassword-sealed mnemonic
Wallet<network>/wallet.sqlitePublic descriptors and BDK changes only
PSBT sessions<network>/psbts/<id>.jsonRead-only legacy state from older releases' staged workflow; new exports are PSBT file artifacts
Finalized transactions<network>/transactions/<txid>.jsonPrivate raw transaction hex, pending/broadcast status, and payment metadata
Legacy plans<network>/plans/<id>.jsonPre-refactor state; read, permission-hardened, and converted on sign/broadcast
Grants<network>/grants/<agent>.jsonLimits, accounting, and grant-wrapped seed

Wallet state, sessions, transactions, legacy plans, and grants are namespaced by Bitcoin network. The sealed master seed is shared so each network derives from the same mnemonic. Sensitive files are written atomically with restrictive permissions.

Human send

sequenceDiagram
    participant H as Human
    participant C as CLI
    participant P as Providers
    participant E as Core
    participant S as Store
    H->>C: sats send address amount
    C->>P: sync, guards, fee estimate
    C->>E: prepare PSBT
    C-->>H: amount, fee, confirmation
    C->>E: sign and finalize locally
    C->>S: save pending raw transaction
    C->>P: broadcast transaction
    C->>S: mark transaction broadcast

The shared preparation pipeline validates the address before network I/O, syncs the wallet, builds the union of dust and configured-guard exclusions, estimates the fee when none was supplied, and asks sats-core to build the PSBT. Preparation fails rather than using stale state after a sync failure.

The prepared PSBT stays in memory during a normal send. After finalization, sats writes private raw transaction hex before any broadcast attempt. A broadcast failure therefore leaves a pending transaction that can be retried without retaining the signed PSBT.

Agent send

sequenceDiagram
    participant A as Agent
    participant M as MCP
    participant P as Preparation
    participant Z as Authorization
    participant S as Store
    A->>M: send address, amount
    M->>S: reload active grant
    M->>Z: cheap amount precheck
    M->>P: shared safe preparation
    M->>Z: authorize amount plus fee
    M->>S: reserve and persist budget
    M->>M: sign, save finalized tx, broadcast
    M-->>A: sent, denied, or error

The amount-only precheck rejects an obviously impossible request before network access. Final authorization uses the prepared fee. Budget is persisted before signing; it is refunded only if signing fails before a signature exists. Broadcast failure leaves both the finalized transaction record and budget reservation intact.

Provider model

A provider is bound to one network and advertises audited capabilities:

Provider resolution is configuration work and performs no network I/O. Operations validate the selected network when they execute. See Providers and guards for precedence and configuration.

Change map

ChangePrimary ownerRequired checks
Transaction selection, preparation, or finalized-record metadatasats-core::engine, sats-core::planCore unit tests plus CLI/MCP integration paths
Grant rule or accountingsats-core::authzDecision edge cases, persistence ordering, MCP denial tests
Human command or flagcli, commands, main dispatchCLI integration test and docs/cli.md
MCP tool or result schemamcp::serverMCP integration test and docs/mcp.md
Provider driver or capabilityprovider, configMocked driver, network mismatch, ambiguity and failure tests
Persisted statestore, walletd, relevant core modelBackward-reading and atomic-write tests
Key or signing behaviorseed, seal, signer, keysSecurity-focused unit and end-to-end signing tests

Read AGENTS.md before implementing any of these changes.