BLAKE_SWAP_PORT.md raw

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 doeshowwhere
build a funding psbtconverttopsbt + utxoupdatepsbtpkg/chain/chain.go:1215, :1235
signwalletprocesspsbtpkg/chain/chain.go:1287
finalisefinalizepsbtpkg/chain/chain.go:1260
find coinslistunspent, listtransactions, block rescanpkg/chain/chain.go:660, pkg/fund/payment.go:29, pkg/observe/scan.go:33
compile the HTLC descriptorgetdescriptorinfo + deriveaddressespkg/contract/contract.go:90
derive an addressgetnewaddresspkg/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

one place a preimage is inserted. It is pure and already input-0-only by choice.

source that is not estimatesmartfee.

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 packagereplacescontents
pkg/txcreaterawtransactionunsigned tx build, serialisation, weight/vsize, BIP143 sighash for P2WPKH and P2WSH, SIGHASH_ALL
pkg/psbt (write side)converttopsbt, utxoupdatepsbt, finalizepsbtglobal and per-input maps, witness UTXO and witness script, final scriptwitness assembly, extract
pkg/signwalletprocesspsbtECDSA secp256k1 over the BIP143 digest, low-S normalisation, DER encoding, key selection by script
pkg/scriptgetdescriptorinfo, deriveaddressesthe wsh HTLC script compiled from pkg/htlc's descriptor, P2WSH/P2WPKH scriptPubKeys, and a local bech32 address writer (tinywallet has one)
pkg/utxolistunspentthe wallet's own coin table: outpoint, value, script, height, spent-by, with persistence through pkg/store
pkg/feerateestimatesmartfeea rate from recent blocks or a stated floor, with the swap's cap policy applied
pkg/broadcastsendrawtransactionsubmit 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.

  1. 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.

  1. 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.

  1. pkg/script for the swap's wsh HTLC, cross-checked against getdescriptorinfo once in the

rig, then asserted byte-for-byte in tests.

  1. pkg/utxo on top of pkg/domain.Sync's matched heights, once block fetch exists.
  2. Re-point pkg/fund, pkg/settle, pkg/observe at the new implementation, keeping the

Node interfaces as the seam.

  1. 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.