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.
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.
pkg/psbt — the minimal BIP174 reader and AddSHA256Preimage (psbt.go:116), which is theone 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.
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).
| 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.
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:
getdata/block/tx to the whitelistbehind 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.
these scripts". Less data, more trust in the gateway, and a new surface to secure.
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.
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.
pkg/script for the swap's wsh HTLC, cross-checked against getdescriptorinfo once in therig, then asserted byte-for-byte in tests.
pkg/utxo on top of pkg/domain.Sync's matched heights, once block fetch exists.pkg/fund, pkg/settle, pkg/observe at the new implementation, keeping the Node interfaces as the seam.
createwallet, importdescriptors, walletprocesspsbt — with a message that says the wallet is local now, rather than failing
with a connection error.