Skip to content

Add Bitcoin and Ethereum banks: wallets that work like cards - #27

Merged
IAmJSD merged 2 commits into
bank/3-slashfrom
bank/4-crypto
Oct 21, 2026
Merged

IAmJSD merged 2 commits into
bank/3-slashfrom
bank/4-crypto

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Part 4 of 7 in the bank stack, on #26.

What this adds

Each crypto "card" is a fresh wallet funded from the server's treasury with exactly the card's limit, so the limit holds on chain. Killing the card sweeps what's left back to the treasury. A wallet's key is derived from the treasury key and the card's id, so none is stored, and a restarted container can be given its key again.

  • Bitcoin (@nibblet/bank-bitcoin):
    • Native segwit, signed with @scure/btc-signer; reads and broadcasts through an Esplora API (mempool.space by default).
    • BITCOIN_TREASURY_KEY and an explicit BITCOIN_NETWORK, with no default, so a test key never lands on mainnet. Optional BITCOIN_ESPLORA_URL and BITCOIN_FEE_RATE.
    • Minimum card is 5,000 sats.
  • Ethereum (@nibblet/bank-ethereum):
    • Plain transfers through viem to ETHEREUM_RPC_URL. Optional ETHEREUM_CHAIN_ID, checked before anything is sent, and ETHEREUM_ASSET for another EVM chain's coin.
    • Gas comes out of the card's balance.
  • Settling: a wallet whose funding isn't visible yet reports settled: false instead of sweeping nothing and writing the money off. The server keeps it closing and tries again.
  • Shared logic added to the plugin base: a one-at-a-time queue for treasury sends (no two cards picking the same coins or nonce), and how a wallet's spending is counted.

#31 (part 6) moves these settings into a form in Settings → Bank; the environment stays as a fallback.

Limits worth knowing

  • A nibblet holds its wallet's key, so a crypto card's whole limit should be treated as given away. If the nibblet copies the key, it can race the sweep.
  • Chains with an L1 data fee (Base, Optimism) aren't supported yet. The sweep pays exactly 21,000 gas, so it would fail there.

Screenshots

From #28's UI, in the mock server (?mock=1): the Bitcoin bank with its treasury balance, and two wallet cards. The second has been killed and is waiting for its funding to show before it sweeps.

Bitcoin (signet), 0.0418 BTC available

Two Bitcoin wallet cards, one closing and waiting on bitcoin

Review fixes

  • Only a 4xx from Esplora counts as a refused broadcast. A 5xx can come after the transaction was relayed, so it's now an unclear failure: the card stays pending and is looked for, instead of being forgotten with a funded wallet.

Checks

pnpm format:check, pnpm -r typecheck and pnpm test pass on this branch on its own. Bitcoin tests sign and apply real transactions against an in-memory Esplora; an Ethereum test checks a real viem-signed transfer.


Generated by Claude Code

@IAmJSD
IAmJSD added this pull request to stack #29 October 19, 2026 15:34
@IAmJSD
IAmJSD force-pushed the bank/4-crypto branch 2 times, most recently from 45cec95 to c1e17b4 Compare October 19, 2026 21:28
claude and others added 2 commits October 21, 2026 11:15
Each crypto card is a fresh wallet funded from the server's treasury with
exactly the card's limit, so the limit holds on chain, and swept back to
the treasury when it's killed. Wallet keys are derived from the treasury
key and the card's id, so none is stored and a restarted container can be
given its key again.

`@nibblet/bank-bitcoin` uses native segwit and an Esplora API, signing with
`@scure/btc-signer`; it needs `BITCOIN_TREASURY_KEY` and an explicit
`BITCOIN_NETWORK`. `@nibblet/bank-ethereum` sends plain transfers through
viem to `ETHEREUM_RPC_URL`, optionally pinned to `ETHEREUM_CHAIN_ID`. The
plugin base gains what they share: a queue for treasury sends and how a
wallet's spending is counted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGXXQcj3hZN2jiB6As6kk5
Only a 4xx from Esplora means a broadcast went nowhere. A 5xx can come
after the transaction was relayed, so it's now an unclear failure: the
server keeps the card pending and looks for it, instead of forgetting a
funded wallet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD
IAmJSD merged commit 214f3a2 into main Oct 21, 2026
4 of 6 checks passed
@IAmJSD
IAmJSD deleted the bank/4-crypto branch October 21, 2026 10:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants