# What is live, what is not The state of the two-chain wallet, written so nobody has to reconstruct it from commit titles. ## Live - **Seed**: BIP39, 12 to 24 words, English wordlist pinned by hash, official trezor vectors. The phrase is sealed at rest with AES-256-GCM under PBKDF2-SHA256 (200k), so a stolen browser profile is not a stolen wallet. A wrong passphrase and a damaged record are the same refusal, because the cipher cannot tell them apart. - **Keys**: BIP32 private derivation, BIP84 accounts, one seed for both chains — `btc` account 0, `blake` account 1, coin type from the network profile. P2WPKH addresses, and the swap's HTLC witness script pinned byte-for-byte to what Core compiles from `wsh(or_i(and_v(v:pk(C),sha256(H)),and_v(v:after(T),pk(R))))`. - **Store**: memory is the truth, IndexedDB is written behind it in one transaction per flush (2 s), never per write. A lost flush is a rescan from the seed, not a lost wallet. - **Chain**: a binary frame channel to nostrino (`pkg/chaindata`), a `MessagePort` adapter (`pkg/msgport`), a light-client worker (`nostrino-channel`), and a wallet domain that resumes from the stored filter tip, matches both branches with a 100-address lookahead, and records matched heights. - **Block transport** — proven against the regtest node (`internal/rigscan`, `NOSTRINO_RIG=1`): a filter match names a height, `neutrino.Block` fetches that block, the merkle root is proved, and the coin is recorded with txid, vout, value and height. The same run confirms the node's `gettxout` agrees on the value and that `getaddressinfo` reports the wallet's address as not the node's. `neutrino.Block` refuses a frame over 8 MiB before it is allocated and checks the first 80 bytes hash to the header the wallet already verified; `chaindata.MethodBlock` carries the block to the wallet, and `domain.Sync` fetches each unscanned matched height, proves the merkle root, adds the coins, and only then records the marker — one flush per sync. Getting there fixed two protocol bugs the node's own log named: the frame magic was written little endian, which reverses every network magic on the wire (`Wrong MessageStart dab5bffa received`), and `getcfheaders` asked for the whole chain in one request where BIP157 allows 2000 blocks (`peer requested too many cfilters/cfheaders`). The client now chunks. - **Page**: `web/` — connect, create (words shown once, typed back to confirm), restore, unlock, lock, next address, sync. Compiles and serves; **not yet driven in a browser**. - **Refusal**: in the browser build blake-swap answers every node wallet RPC with "the full node's wallet service is not part of this build", naming the method. The CLI and the rig suites are unchanged, so the evidence that those paths work still exists. ## Not live, in the order it blocks 1. **The swap's wallet calls.** `pkg/fund`, `pkg/settle` and `pkg/observe` still call their `Node` interfaces; the browser refuses those calls rather than rewriting them. Re-pointing them at this wallet is what turns the refusal into a working funding and settlement path, and it is wiring: the wallet can build, sign, describe and now fund from its own coins. 2. **The browser run.** The page compiles and serves, but no browser has completed connect, create, words, confirm, address, fund, mine, sync and coins against a relay and a gateway. ## The next step Re-point `pkg/fund`, `pkg/settle` and `pkg/observe` at the local wallet, implement only the `Node` methods those flows call, and keep `walletdep.Refuse` for what genuinely has no local equivalent. Then the browser run.