Bitcoin Wallet & Transaction Engineering
A wallet is a policy engine, a key boundary, and a transaction-construction system. An address list is not a wallet design.
Goal — construct, inspect, sign, finalize, broadcast, replace, and recover descriptor-driven PSBTs without putting a seed phrase in an application server.
1. Descriptor-first wallet model
Use descriptors to represent spending policy and derivation, then derive addresses from that policy. Learn BIP-32 HD derivation, BIP-39 seed handling, BIP-84 native SegWit, BIP-86 Taproot, and why xpub exposure leaks wallet structure even though it cannot sign.
# Inspect descriptors and wallet state on regtest.
btc -rpcwallet=lab listdescriptors
btc -rpcwallet=lab getnewaddress "receive" bech32
btc -rpcwallet=lab listunspent 1 9999999 '[]' true
btc -rpcwallet=lab getaddressinfo "$ADDR"
Know P2WPKH, P2WSH, P2TR, Bech32/Bech32m, witness data, virtual size, and fee units. Build Script understanding around actual spending conditions; do not hand-roll serialization/signatures in a production system.
2. PSBT workflow
# Core can construct a funded PSBT; inspect before signing.
PSBT=$(btc -rpcwallet=lab walletcreatefundedpsbt \
'[]' '[{"bcrt1...":0.001}]' 0 '{"replaceable":true,"fee_rate":2}')
echo "$PSBT" | jq -r .psbt | base64 -d > tx.psbt
btc decodepsbt "$(echo "$PSBT" | jq -r .psbt)"
# A separate signer owns keys. Finalize only after policy checks.
SIGNED=$(btc -rpcwallet=signer walletprocesspsbt "$(echo "$PSBT" | jq -r .psbt)")
FINAL=$(btc finalizepsbt "$(echo "$SIGNED" | jq -r .psbt)")
btc testmempoolaccept "[\"$(echo "$FINAL" | jq -r .hex)\"]"
| Review before signing | Why |
|---|---|
| Inputs and UTXO ownership | prevents unexpected wallet/policy use |
| Recipient scripts and amount in sats | prevents address/amount substitution |
| Change output classification | prevents accidental overpayment or change theft |
| Fee, vbytes, sat/vB, RBF sequence | controls cost and future replacement ability |
| Locktime, sighash, derivation/policy metadata | ensures signing commits to the intended transaction |
3. Fee and mempool operations
- Estimate feerates using a policy that can fail safely; quote the user fee and confirmation target, not an invented certainty.
- Use RBF to replace an unconfirmed transaction and CPFP to accelerate a spendable output. Test both on regtest and document your custody approval rules.
- Handle dust, UTXO fragmentation, coin selection, privacy tradeoffs, package limits, and mempool rejection. A broadcast response is not confirmation.
3b. Package relay & air-gapped signing
- Package relay — related parent/child txs accepted together; test on regtest when policy allows. Document when your product relies on package acceptance vs independent broadcasts.
- Air-gapped drill — unsigned PSBT exported offline, reviewed, signed, returned. Follow
labs/phase-25-bitcoin/airgap/DRILL.md. - Double-spend monitoring — track unconfirmed spends; alert if conflicting tx appears in mempool.
4. Practical lab — 2-of-3 treasury with recovery
Deliverable
- Create a 2-of-3 descriptor with an additional timelocked recovery path on regtest. Keep signers in separate wallet processes or hardware emulators.
- Fund it, create a PSBT with recipient/change/RBF policy, independently decode it, sign with two participants, test acceptance, broadcast, and mine it.
- Build an approval CLI/API that refuses unknown descriptors, excessive fee rates, non-whitelisted policy changes, and missing human-readable transaction review.
- Run a recovery drill: restore only from documented descriptors/backups, derive the same UTXOs, then spend through the recovery branch after advancing regtest time/blocks.
- Test an RBF replacement, CPFP child, dust rejection, malformed PSBT, wrong-network address, and unavailable signer.
Lab repo: labs/phase-25-bitcoin/ — scripts/regtest-setup.sh + air-gap drill.