# Plugging blake-swap into tinywallet What it will take to move the swap's wallet-side behaviour onto `tinywallet`, with no full node and no wallet RPC. Written after reading the swap's Go tree, so the first section is the part that changes the plan. ## What the swap's Go code actually contains There is no on-chain signer in it. Every transaction is built, signed and finalised by bitcoind over JSON-RPC: | what the swap does | how | where | | --- | --- | --- | | build a funding psbt | `converttopsbt` + `utxoupdatepsbt` | `pkg/chain/chain.go:1215`, `:1235` | | sign | `walletprocesspsbt` | `pkg/chain/chain.go:1287` | | finalise | `finalizepsbt` | `pkg/chain/chain.go:1260` | | find coins | `listunspent`, `listtransactions`, block rescan | `pkg/chain/chain.go:660`, `pkg/fund/payment.go:29`, `pkg/observe/scan.go:33` | | compile the HTLC descriptor | `getdescriptorinfo` + `deriveaddresses` | `pkg/contract/contract.go:90` | | derive an address | `getnewaddress` | `pkg/chain/chain.go:733` | The only signing in Go is BIP-340 Schnorr over Nostr event ids (`pkg/keys/keys.go:106`), which is not a transaction signature. There is no BIP32/39/84, no tx serialiser, no BIP143 sighash, no PSBT writer, no miniscript compiler, and no address encoder. So "plug the swap's signing and PSBT code into the wallet" is not a move: it is the work. What the swap gives us is the *shape* — interfaces, policy, the script, and the state machines that call them. ## What is reusable as it stands - `pkg/psbt` — the minimal BIP174 reader and `AddSHA256Preimage` (`psbt.go:116`), which is the one place a preimage is inserted. It is pure and already input-0-only by choice. - `pkg/htlc` — the descriptor strings (`:92`, `:103`) and `ClassifySpend` (`:161`). Pure. - `pkg/amount`, `pkg/decimal` — exact arithmetic. Pure. - `pkg/fees` — `ChooseRate` and `EnforceCap` policy, fed by `FeeData`. Pure, but needs a fee source that is not `estimatesmartfee`. - The interfaces the swap already calls through, which a local implementation can satisfy with no upstream edit: `chain.Wallet` (`chain.go:882`), `contract.Node` (`contract.go:27`), `settle.Node` (`settle.go:33`), `fund.Node` (`fund.go:40`), `observe.Node` (`observe.go:29`), `settle.Finalizer` (`settle.go:91`), and `swap.Node` as their union (`engine.go:39`). ## What has to be built here | new package | replaces | contents | | --- | --- | --- | | `pkg/tx` | `createrawtransaction` | unsigned tx build, serialisation, weight/vsize, BIP143 sighash for P2WPKH and P2WSH, `SIGHASH_ALL` | | `pkg/psbt` (write side) | `converttopsbt`, `utxoupdatepsbt`, `finalizepsbt` | global and per-input maps, witness UTXO and witness script, final scriptwitness assembly, extract | | `pkg/sign` | `walletprocesspsbt` | ECDSA secp256k1 over the BIP143 digest, low-S normalisation, DER encoding, key selection by script | | `pkg/script` | `getdescriptorinfo`, `deriveaddresses` | the wsh HTLC script compiled from `pkg/htlc`'s descriptor, P2WSH/P2WPKH scriptPubKeys, and a local bech32 address writer (tinywallet has one) | | `pkg/utxo` | `listunspent` | the wallet's own coin table: outpoint, value, script, height, spent-by, with persistence through `pkg/store` | | `pkg/feerate` | `estimatesmartfee` | a rate from recent blocks or a stated floor, with the swap's cap policy applied | | `pkg/broadcast` | `sendrawtransaction` | submit a final tx through whatever path the deployment has (relay tunnel or gateway command) | `pkg/domain` then grows the operations the swap calls: `Fund`, `Bump`, `Claim`, `Refund`, `Sweep`, each returning an outpoint or a txid and recording what it did, so a reload can see the intent that is in flight. `pkg/fund`, `pkg/settle`, `pkg/observe`'s state machines stay where they are in the swap and are re-pointed at these. ## The scanning blocker, which needs a decision A compact filter tells you *that* a block mentions one of your scripts. It does not tell you the txid, the value, or which output it was — that is in the block. The bridge's default command whitelist is headers and filters only (`pkg/bridge/bridge.go:102`), and `getdata` is not on it, so today the wallet can learn a height and nothing else. Three ways out: 1. **Allow block download through the bridge** — add `getdata`/`block`/`tx` to the whitelist behind a size and rate cap, and teach the client to fetch a block by hash and match its scripts locally. This keeps the light-client trust model: filters point, blocks prove, and the wallet verifies the merkle root itself. 2. **A gateway-side index** — the gateway answers "give me the txs in this block touching these scripts". Less data, more trust in the gateway, and a new surface to secure. 3. **Full blocks from a local node** — a return to the wallet service this whole line of work removes. The recommendation is (1): it is the only one that leaves every byte the wallet believes verified by the wallet. It is a bridge change (whitelist plus caps) and a client change (block fetch, merkle verification, script matching), neither of which is large, and both are testable against the regtest rig. ## Order 1. `pkg/tx` + `pkg/sign` + `pkg/psbt` write side, with the BIP143 test vectors and a signed P2WPKH spend verified against a node's own `decoderawtransaction`. 2. `pkg/script` for the swap's wsh HTLC, cross-checked against `getdescriptorinfo` once in the rig, then asserted byte-for-byte in tests. 3. `pkg/utxo` on top of `pkg/domain.Sync`'s matched heights, once block fetch exists. 4. Re-point `pkg/fund`, `pkg/settle`, `pkg/observe` at the new implementation, keeping the `Node` interfaces as the seam. 5. Make the node-wallet paths refuse by name — `createwallet`, `importdescriptors`, `walletprocesspsbt` — with a message that says the wallet is local now, rather than failing with a connection error.