WebSocket client + CLI harness + pytest suite that exercises each axis of a CKBunker + Coldcard Mk4 policy and asserts the expected outcomes, including the critical negative test that a large PSBT without TOTP is rejected with a specific 'rule #1: need user(s) confirmation' reason. Configuration via .env / YAML / CLI flags, two pre-crafted test PSBTs as fixtures (generation guide in fixtures/README.md), dashboard counter scraper as sanity check, design rationale in docs/. |
||
|---|---|---|
| .. | ||
| README.md | ||
Test PSBTs — how to generate them
The harness needs two pre-crafted PSBTs:
| Fixture | Amount | Policy path expected |
|---|---|---|
small.psbt |
≤ auto-approve cap (e.g. 9,000 sats if your Rule #2 cap is 10,000) | Signs without TOTP |
large.psbt |
> auto-approve cap, ≤ user-auth cap (e.g. 100,000 sats) | Rejected without TOTP; signs with TOTP |
Both PSBTs must:
- be spendable by the Coldcard bound to your CKBunker (same seed / xpub)
- spend to an address you control (or a burn address — they are test inputs, you never broadcast them)
- use a real UTXO the Coldcard can see (watch-only wallet)
Method 1 — Sparrow Wallet (recommended for first-time setup)
- In Sparrow, open or create a watch-only wallet loaded with your
Coldcard's xpub. (The Coldcard's HSM-Mode QR or a
coldcard.txtexport works.) - Send yourself a small amount on testnet or signet so you have a UTXO to spend without losing real sats. (For mainnet demos, 10k sats is ~AUD $1.)
- Build two transactions:
Small demo— pay 9,000 sats (or 90% of your Rule #2 per-txn cap) to any receive address in the same wallet. Sparrow → Send → Save PSBT → write tofixtures/small.psbt.Large demo— pay 100,000 sats (or mid-range of your Rule #1 cap) the same way. Save asfixtures/large.psbt.
- Both PSBTs should show Coldcard as a required signer in Sparrow.
Do NOT broadcast these. The harness signs them, but you verify the signatures in Sparrow and then discard — there's no reason to spend real sats on a validation run.
Method 2 — bitcoind (CI / automation)
If you're wiring the harness into CI against a regtest or signet deployment, scripting PSBT generation is a one-off:
#!/usr/bin/env bash
# Requires bitcoin-cli on PATH, pointed at a node that sees your wallet.
set -euo pipefail
WALLET="ckbunker-watch"
FEE_RATE=10 # sat/vB
recipient=$(bitcoin-cli -rpcwallet=$WALLET getnewaddress)
small_raw=$(bitcoin-cli -rpcwallet=$WALLET walletcreatefundedpsbt \
'[]' "[{\"$recipient\":0.00009000}]" 0 \
"{\"fee_rate\":$FEE_RATE}" | jq -r '.psbt')
echo "$small_raw" | base64 -d > fixtures/small.psbt
large_raw=$(bitcoin-cli -rpcwallet=$WALLET walletcreatefundedpsbt \
'[]' "[{\"$recipient\":0.00100000}]" 0 \
"{\"fee_rate\":$FEE_RATE}" | jq -r '.psbt')
echo "$large_raw" | base64 -d > fixtures/large.psbt
Method 3 — use the same PSBT file over and over
Nothing in the harness requires the PSBT to be spendable right now for the
reject-path test (test_04). The Coldcard rejects on amount, not on
whether the UTXO is still unspent. So:
small.psbtcan be reused until the UTXO is spent elsewhere.large.psbtcan be reused indefinitely — every validation run that tests Rule #1 rejection produces a rejection regardless of UTXO state.
If you run the full suite frequently, consider crafting large.psbt
deliberately against an already-spent UTXO so the success path
(test_05) fails at signature verification (not policy evaluation) —
this is arguably safer than running with signable funds live.
File format
Either binary (psbt\xff... magic bytes) or base64-encoded text is
accepted by the harness — it auto-detects via magic bytes. Sparrow exports
binary by default; bitcoin-cli returns base64.
What NOT to do
- Do not commit real PSBTs to git —
.gitignorealready blocks*.psbtin this directory. - Do not use a PSBT that spends a UTXO you can't afford to move. The harness does not broadcast, but a leaked signed PSBT can be broadcast by anyone.
- Do not reuse production keys for generating fixtures — prefer testnet or signet.