Closed. v1.0.0 was built in 16 chunks of about one session each, and chunk 17, the transaction view, made 1.1.0. What comes next is in post-release-roadmap.md; a chunk picked up from there still lands its handoff note in this file.
Compressed on 2026-10-02. Every chunk’s full scope and all 30 handoff notes — about 90 KB — are kept verbatim in archive/roadmap-v1.md. Nothing was deleted. Each entry below is one line, so this file can be read at a glance; the archive has the reasoning, the surprises, and the transaction hashes and fees from the live runs. A line reference to this file written before 2026-10-02 resolves against the archive.
Status: ✅ done · 🚧 in progress · ⬜ not started
Done. Every chunk is done. Scopes here are one line; the archive has each in full, and the plans have the detail.
| # | Chunk | What it was | |
|---|---|---|---|
| 1 | WASM foundation | ✅ | The seedelf-wasm crate: register create and re-randomize, the ownership check, the Schnorr proof, built with wasm-bindgen. |
| 2 | Seedelf key derivation | ✅ | The v1 HKDF spec in seedelf-crypto, frozen, with nine test vectors. BIP39 moved into Rust. |
| 3 | Cardano keys | ✅ | Phrase → CIP-1852 account 0', checked against Lace’s @cardano-sdk/key-management vectors. |
| 4 | Extension scaffold | ✅ | Vite, React 19, TypeScript, an MV3 manifest, WASM in the worker, and CI (web-wallet.yml). |
| 5 | Vault and lock | ✅ | Lace’s SecretBox sealing the BIP39 entropy, create and restore onboarding, auto-lock, unlock back-off. Plan. |
| 6 | Balance | ✅ | The TS Koios client, the contract scan, account discovery on both chains with gap limit 20, and the receive QR code. |
| 7 | Builder extraction + move in | ✅ | Building split from network calls in seedelf-core so it compiles to WASM, then move-in end to end. Plan. |
| 8 | Create a Seedelf | ✅ | The stealth mint (util mint) with giveme.my collateral. Plan. |
| 8b | Mint first | ✅ | The first Seedelf is paid by the Cardano account, using its own collateral, before any move-in — because a mint links the Seedelf to whatever paid for it. |
| 9 | Transfer | ✅ | Seedelf → Seedelf. Found Register::is_valid’s identity-point hole here. Plan. |
| 10 | Withdraw | ✅ | sweep and remove on ScriptSpend; every CLI script spend now ends in commands/spend.rs. Plan. |
| 11a | Style and flow pass | ✅ | The UI much more like Lace’s dark mode — its look, not its brand. Dark only, Inter, Lucide. Plan. |
| 11b | Size and live runs | ✅ | A size-tuned WASM profile (opt-level = "z", no wasm-opt), and live preprod runs of every flow. Same plan. |
| 11c | Testers | ✅ | Everything for an unlisted preprod listing, never submitted: npm run package, the reproducible zip, store/, the images. Same plan. |
| 12 | Style and flow | ✅ | The owner’s 29 findings, each decided with them: Tokens, Settings, Activity, Contacts, UTxOs with locks, and more. Plan. |
| 13 | Staking and voting | ✅ | A full Cardano wallet with Seedelf inside: staking with one pool, a pool browser, and voting delegation. Plan. |
| 14 | Style and flow, second pass | ✅ | The owner’s second round: Private and Public naming, several recipients, hide balances, notes, CSV, a currency. Plan. |
| 15 | dApps: the connector, and private swaps | ✅ | CIP-30 off by default, private sessions through one-time accounts, a dApp browser, and private CIP-30. Plan. |
| 16 | Lovejoin | ✅ | The mixer in the wallet: 10 ₳ boxes, a fan-out chained locally, each box withdrawn after a random delay. Plan. |
This section is closed, and so is this file. Chunk 17 was the last thing chosen from it. Everything after v1.1.0 is in post-release-roadmap.md, which also carries what chunk 17 left open.
| # | Chunk | What it was | |
|---|---|---|---|
| 17 | The transaction view | ✅ | Transaction details on every review and in the connector’s sign window: the transaction decoded from the CBOR about to be signed, plus the raw bytes. Plan. |
Also done from this section: the mainnet build flag (2026-09-26, one build carries both networks), and merging seedelf-web-wallet into main (PR #266). The contract round trip (A2) is still open and is tracked in post-release-roadmap.md.
Newest first. Up to 2026-10-01 each is one line, with the full note in archive/roadmap-v1.md, keeping the one thing worth remembering — usually a surprise that would cost a later session real time. From chunk 18 on, the note here is the full note, since the archive was frozen on 2026-10-02.
web-wallet/small-fixes-1.6.0, from main after #308; PR #309). archive/plans/chunk-35-small-fixes.md is the plan and the record.
return_cost_test, which matched inputs by their hash’s first byte and failed about one run in 128.web-wallet/release-1.5.0, from main after PR #305). A minor release: chunks 27–33 (the restore scan and Find leftovers, Public activity’s blind spots, three correctness fixes, Rewards history, the collateral payment in Activity, 5 ₳ for a found session’s cancel, the store listing in Spanish and Japanese) and #298, a light and a line under Read Cardano through Koios only saying which server the wallet is using (no plan: it asks the worker’s own record of the holds every 10 s while Settings is open, so it asks no server and needs no policy entry; 7 strings, machine drafts, none critical).
dappUi.ties.privacy.staked). With it: a certificate, a withdrawal or a stake pool’s reward account that names another of the wallet’s stake keys is named, with its own warning (dappUi.ties.privacy.certified), where the prompt said “isn’t yours” and “isn’t in it” (X05, L1: WebAssembly’s inspection gives each certificate’s stake key, credential); an account’s own key past the first 20 is looked for up to the 500th (X06); and two sessions’ keys in one address both tie (X07).1c33984); the runbook’s rollback note says what rolling back past it does.claim.warn.found, dappUi.ties.privacy.staked, dappUi.ties.privacy.certified, dappUi.cert.warn.walletStakeKey, dappUi.withdrawal.warn.walletRewards, sess.skip.warn.collateralComing, dapps.found.privacy.together, staking.review.warn.stopUnpaid) was back-translated blind by a second model and compared; two Japanese corrections came out of it, each passed blind again (the reviewer notes in verified-critical-{es,ja}.json).seedelf-data 85 offline with clippy clean, the WebAssembly’s node tests 45; Vitest 2,138 (2 skipped) on both builds; e2e 96 on the dev build and 96 on the store build, the data layer’s 3.deploy, then status) and its /health; then the package on the merge commit (npm run package), e2e on it, the upload with the Spanish and Japanese listing (chunk 33), and the record PR.63f87b8, and the API deployed from it through the workflow. The live API then took the rewards history oldest first (200) and refused it newest first (400), with /health all ok and the node connected, 34 s behind its tip. The package, seedelf-wallet-1.5.0-mainnet.zip, is built from 63f87b8 with Node 24.19.0 (its own zlib, 1.3.2.1-motley) and Ubuntu clang 18.1.3: SHA-256 d72022bb24bf36ed4761447e7b658685dbe8e94e42798968ccfcc7eba668e409 (1,877,066 bytes, 24 files), and a second build of the same tree, made apart, gave the same bytes. The whole e2e suite, 96 tests, passed on it.web-wallet/store-listing-i18n, from main after PR #304). archive/plans/chunk-33-store-listing-languages.md is the plan and the record.
__MSG_appDesc__, and the build writes Chrome’s _locales/{en,es,ja} from SUMMARY in manifest.ts, so the store’s summary comes in each language with the package. npm run store:images shoots each screen in all three languages by switching the wallet’s language on it, English’s images unchanged, Spanish’s and Japanese’s in images/es/ and images/ja/. Each language’s description and the upload’s steps are in store/README.md. No server change, and no privacy policy entry. Vitest 2,042 (2 skipped) on both builds, e2e 83.web-wallet/found-session-top-up, from main after PR #303). archive/plans/chunk-32-found-session-collateral.md is the plan and the record.
cancelCollateralBuild, then topUpSubmit), then the cancel once a read finds it. The view says cancelCollateral, missing or coming. No server change, and no privacy policy entry: nothing new is sent or kept.Session with the worker’s answers stubbed. (1.5.0’s release review: the e2e fakes now plant a found session, holding money and no order. One test presses Put in 5 ₳ for Lovejoin’s Send, which giveme.my’s recorded refusal turns away, and another sends the return directly: 1.5.0’s cross-area review, X21, X11. Put in 5 ₳ to cancel, which needs an order, is still unrun end to end: 1.5.0’s last review, L13.)web-wallet/collateral-activity, from main after PR #302). archive/plans/chunk-31-collateral-activity.md is the plan and the record.
collateralPaid, the newest 20, through a reclaim too), and the current collateral’s payment counts for a wallet from before the record. No server change. The privacy policy has a dated entry (12 October): something new is kept on the device, encrypted, though nothing new is sent.getCollateral with null as this wallet does, and names the payment Send; CIP-30 marks the call deprecated. The plan file has what Lace and Eternl do.web-wallet/rewards-history, from main after PR #301). archive/plans/chunk-30-rewards-history.md is the plan and the record. The data layer needs deploying before a wallet that asks for it ships: an API without the route answers 404, and any failure holds the wallet’s whole public part on Koios for 5 minutes. The usual order (merge, deploy the API, check it, then package) does it.
POST /api/v1/account_reward_history on the data layer, checked against mainnet Koios row for row on every page for four keys (5,766, 2,513, 457 and no rewards); the wallet’s Rewards history page, reached from the rewards, or from Not staking when the key was ever paid (StakeInfo.rewarded); one request each time it opens, nothing kept, pools named from the device only.account_rewards is deprecated, and preprod’s refuses an order; account_reward_history is its flat replacement. db-sync lists the next epoch’s rewards before they’re paid, so “paid by now” is spendable_epoch at or before the current epoch, which is what account_info counts. The e2e test answers the route with its own context.route, registered after the fixture’s so it wins, and moves the recorded epochs so the newest is always next epoch’s.web-wallet/correctness-fixes, from main after PR #300). archive/plans/chunk-29-correctness-fixes.md is the plan and the record. The owner picked them for 1.5.0, and is keeping the list of features to go with them.
amount_in back exactly in nine live mainnet quotes (2026-10-11); a quote is free to ask for, so that’s the check to repeat if it ever looks otherwise. The test fakes used to answer the recorded 10 ₳ quote whatever was asked; both now answer for the amount and pair asked (tests/fixtures/minswap-echo.ts), so a test building a route by hand gets its paths shared out to add up. And the critical-set guard follows accountNumberAndName into a warning now: accounts.numbered is one of its shared words.web-wallet/activity-blind-spots, from main after PR #299). archive/plans/chunk-28-activity-blind-spots.md is the plan and the record.
credential_txs), the ones the balance reading asks about, so a payment to an enterprise address of the account, or to its key under someone else’s stake key, is listed and in the CSV. Past 75 keys it asks in groups and merges their pages (takePage). A public mix’s deposit and mixes read Into Lovejoin and Lovejoin mix, named from Lovejoin’s mix box among their inputs or outputs, so no record is needed and they read right after a restore too. No privacy policy entry: nothing new is sent.credential_txs route, checked row for row against mainnet Koios through a local run on home’s db-sync. Deploy the API before uploading this wallet: an older server answers the route 404, which puts the wallet’s public part on Koios for 5 minutes. account_txs stays, for the restore scan and wallets up to 1.4.1. account_txs is unchanged, so the deploy changes nothing 1.4.x wallets ask; credential_txs counts outputs from tx_out’s index before gathering them, in the same query, so a busy script’s credential is refused in 0.1 s rather than 4.9 s.cargo test -- --ignored --test-threads=1, 12 public tests in 4 s), and seedelf-data-api runs locally against it for a side-by-side with Koios. Stopping that local server by name needs the process’s 15-character name (seedelf-data-ap): pkill -f with its path matches the shell running the command too, and kills it.web-wallet/restore-scan, from main after PR #298). archive/plans/chunk-27-restore-scan.md is the plan and the record, and keys-and-accounts.md the design as built.
SessionService.scan walks the one-time stake keys 20 at a time, and adds each used account the book doesn’t know that holds something, or has an order open, as a session found (recovered), brought back by hand from the dApps page’s new After a restore section or Bring everything back. A restore runs it on its network, after the look for public accounts; Find leftovers runs it on any. Remove wallet and Forgot password now promise private sessions’ money back; the privacy policy has its 11 October entry.koios.ts sent. tx_info’s inputs-alone answer carries each output’s index and payment credential (Koios’s and the data layer’s alike), which is how an order is found through the swap that placed it; KoiosTxSpends now types outputs.account_addresses for 20 one-time stake keys), which the restore’s e2e test counts: a test that counts requests across a restore waits for the third account_addresses.web-wallet/patch-1.4.1, from main after PR #295). A patch: no plan file, each item a line of post-release-roadmap.md, and every fix has a test that fails without it. O10, the API: submittx and ogmios answer 503 behind while Ogmios’s /health says home’s node isn’t connected or its tip is over 3 minutes old, so a node catching up after a restart no longer refuses a valid spend of a newer UTxO as spent; the wallet already sent a 503 to Koios, and /health now says submit and node. The owner’s four picks: after a lost connection to the data layer, a Koios gateway’s own HTML page is maybe sent, not the node’s refusal (26b’s last “left as it is”); a cancel refused after Stop never reads “Cancelled” (C15, words each language had); this wallet’s Seedelf stays its own when names and since answer for different blocks (26b); a swap approved against Danogo’s pools never speaks of an order (C13, four strings in three languages, back-translated blind). Then the owner asked to review the reviews: three re-checks of every review’s open items against the code (launch, independent and crypto; privacy, usability and the blind test; the release reviews 1.1.0–1.4.0 and the data layer’s), then one review of the branch’s whole diff, which found no high or medium. No high or medium fix had come undone, but the launch review’s H5 hadn’t reached 1.4.0’s new path: a sealed record the store refused failed the private reading (fixed). They also found what made the wallet’s own Lovejoin box asked of our server while the wallet held its private part on Koios, which the policy said it never learns (now Koios alone, koiosAlone, with a dated Changes entry); a session step answered with another transaction’s id recorded as never sent (now maybe sent, as pending.ts has it); Settings’ server-switch note claiming neither server learns which Seedelfs are yours, when a transaction sent shows them (now scoped to what the wallet reads; a critical key, back-translated blind twice); and the Max tooltip’s order and word. Then the owner asked for the small things skipped: a return approved through Lovejoin whose spare ADA pays for no box now says so (sess.skip.warn.noBox, a critical key back-translated blind; the collateral-gone case had chosen silence, and says it too now); and the review records’ “left” markers that have moved since are annotated. Looked at and left: the collateral payment’s “Sent” in Activity (the collateral can be a UTxO already held, with no transaction), disabling the Lovejoin page’s buttons while a payment may go through (no shared in-flight state; the worker refuses them and says why), and networks.ts’s mixCost, unread but where three handoff notes record the measured mix. Docs: chunk 26’s three plans archived, what they left open moved into Kept in mind first; the stale lines the re-checks found; and the reviews’ calls left for the owner, the stake key’s certificates over another network’s transaction, and DX-8 (a quote’s amount_in unchecked, which only a live quote can settle) put where the owner answers from (Open questions 5, Kept in mind). The archived reviews themselves aren’t rewritten. Lists: tokens unchanged; mainnet DReps 459 → 462, none lost; preprod’s 62 unchanged; the API’s token decimals unchanged. The store images stay: none of the five screens changed. Since 1.4.0 the manifest is unchanged but its version. The READMEs link the store listing and the owner’s Seedelf Discord. Checks: Vitest 1,987 (2 skipped); seedelf-data 72 offline, clippy clean, and the 5 live submit tests against home’s submit-api and Ogmios; e2e 81 on the dev build. After the merge, in order: the API’s deploy from the merge commit and status, then curl -s https://mainnet.seedelf.logicalmechanism.io/health for "submit":"ok" and the node’s age; then the package on the merge commit, e2e on it, the upload, and the record PR. Done that way: PR #296 merged as fd412c0, and the API deployed from it through the workflow; the live API’s /health then said "submit": "ok", the node connected and 3 s behind its tip, and a junk submit went through it to the node (a 400, DecoderErrorDeserialiseFailure). The package, seedelf-wallet-1.4.1-mainnet.zip, is built from fd412c0 with Node 24.19.0 (its own zlib, 1.3.2.1-motley) and Ubuntu clang 18.1.3: SHA-256 35b576538f34f381da633fed46ce1a10c36e872b38ea58d600c8c6c6339245f7 (1,862,178 bytes, 21 files), and a second build of the same tree, made apart, gave the same hash. The whole e2e suite, 81 tests, passed on it.web-wallet/release-1.4.0, from main after PR #293). archive/plans/chunk-26c-data-layer-vps.md is the record. The store build gets the data layer’s origin, https://mainnet.seedelf.logicalmechanism.io, so every mainnet user reads it first, and the review was of #290’s code running with it. Five area reviewers and one across them found no high: the medium was the API keeping the account’s own DRep standing 10 minutes (now read live), and the lows a public-account payment held 2.5 hours behind one that can’t land, a submit’s 429 stalling every read, GETs left to the server’s no-store, and a refused cursor note failing a reading; all fixed with tests that fail without them. A home node that’s behind can still refuse a valid spend as “spent”: the wallet-side fix (ask Koios too) was taken back after the last review found it stopped chains whose resends home rightly calls spent; the fix is a node-sync gate on the API’s submit part, for 1.4.1. The owner’s calls: the preflight fix in 1.4.0 (the API marks every answer no-store, a POST to it asks default); the screens say “the server” where they named Koios for an answer the data layer may give (en, es, ja; the changed critical values back-translated blind, no correction); no user fixes this time; the store images stay. The policy has a 10 October entry, and the listing’s text and data-usage reasons name the server, with no box changed. Lists: tokens unchanged; mainnet DReps 454 → 459, none lost; preprod’s 62 unchanged; the API’s token decimals unchanged. Checks: Rust 548, Vitest 1,978 on both builds, seedelf-data 63 offline and the new live test, e2e 81 on the dev and store builds, and the data layer’s 3. Since 1.3.0 the manifest’s permissions and hosts are unchanged; its CSP’s connect-src gained the server. After the merge, in order: the API’s deploy from the merge commit, then curl for cache-control: no-store, then the package; the upload waits for the deploy. Done that way: the deploy ran from b5ad15a (PR #294’s merge commit), and the live API then answered the store’s ID with cache-control: no-store. The package, seedelf-wallet-1.4.0-mainnet.zip, is built from b5ad15a with Node 24.19.0 (its own zlib, 1.3.2.1-motley) and Ubuntu clang 18.1.3: SHA-256 b1ba594f3d4f2705c075e51c3ca561613db12528bbd76405ff045bcf74571c0a (1,861,429 bytes), and a second build of the same tree, made apart, gave the same hash. The whole e2e suite, 81 tests, passed on it.web-wallet/data-layer-vps, -ci and -drills: PRs #291–#293). archive/plans/chunk-26c-data-layer-vps.md is the plan and the record.
https://mainnet.seedelf.logicalmechanism.io, a DigitalOcean droplet in nyc1 (Ubuntu 26.04) running Caddy and the API, which reach home’s node, db-sync and Kupo over a WireGuard tunnel home dials..github/workflows/data-layer-deploy.yml, run by hand on main, with the owner approving each run. Its key runs one script on the VPS. data-layer.yml is seedelf-data/’s CI. All five drills passed..deb, and apt never updates it.nftables.service flushes every rule when it starts and stops, so home’s wg0 filter loads through its own unit, beside ufw.kupo_seedelf, and it lacked the HTTP log flag; a drop-in sets it now.cargo test --release relinks the release binary with the test features: ship what cargo build makes.bash <<SCRIPT heredoc, ssh reads the rest of the script as its input: use ssh -n.seedelf keeps sudo with a password.web-wallet/release-1.4.0: its record.2026-10-09: chunk 26b, the wallet’s side of the data layer (web-wallet/data-layer-wallet, from main after PR #289, the API). archive/plans/chunk-26b-data-layer-wallet.md is the plan and the record. Mainnet reads, submits and private watches go through seedelf-data first, each part (private, public, submit) to Koios on its own for 5 minutes when it can’t answer; preprod is untouched, and the store build has no data-layer origin until the VPS chunk, so this changes nothing for any user yet. The private side keeps a sealed cursor and the wallet’s own rows across a lock: an unlock asks one shared since, and names come from the shared names route. Checked live, free, on a dev build against the local API over home’s mainnet db-sync and Kupo, every request logged: a restore is snapshot, since and names; an unlock one since; each of the API stopped, a wrong origin, db-sync down and a 429 moved only the part it hit, which came back after the hold; the index’s contract at the tip equalled Koios’s scan field for field. Scale (wasm/scale.mjs): 1M rows is a 473 MB snapshot and 12 minutes of ownership checks, so the one-answer snapshot fits under the 10 s read timeout to about 60,000 rows; mainnet has 33. The surprise was the watches: they compared a cursor’s slot time with the send time less 10 minutes, so most sends asked tx_status anyway, and the stamps they compared were taken after a submit’s retries. They now date each cursor by when the wallet had it, against a first try stamped before the submit. The review (three area reviewers, then one over the fixes) also found a trap in the ownership check sealed as “not ours” for good, and submits after a lost connection read as never sent when Koios merely didn’t answer; all fixed with tests. Every POST to the API pays a CORS preflight, because cache: "no-store" bypasses Chrome’s preflight cache (measured): a follow-up, since the policy describes the cache. Left for the owner: parity on their own wallets and spends in ordinary use; then the VPS chunk, archive/plans/chunk-26c-data-layer-vps.md, on DigitalOcean.
web-wallet/release-1.3.0, from main after PR #285). The version bump first, with both lists refreshed, as every release now does (the owner, 2026-10-08): the token lists came back unchanged, and the DRep refresh lost names, three single-pass runs naming 371, 407 and 406 of the 449 committed mainnet DReps, Koios’s servers disagreeing. So npm run dreps now asks again for the DReps still unnamed, keeps a still-registered DRep’s committed name, and refuses a list that would lose more than 5% (development.md’s step 3); its first run named all 449 and 5 new ones, 454 in all, with preprod’s 62 unchanged. Then a release review of every PR since 1.2.0 (#283–#285): ten area reviewers, six cross-PR lenses and a completeness critic, and two independent verifiers on every finding, 47 of 50 confirmed (3 high, 15 medium, 24 low, 5 nits; #283 was clean). Stop’s own cancel, new in #284, is where the bugs were. The highs: a Splash or Spectrum order couldn’t be cancelled while the session held its 5 ₳ collateral and the usual change, since the cancel’s draft was priced at 1 ₳ and the collateral was spent only when nothing else was there; a stranger could pay a spent Splash order’s address an “order” reading as the session’s that nothing could spend, and the session waited on it forever; and a cancel Koios took that lost to the batcher’s fill left the swap’s change counted spent, so the session never closed. An order Minswap builds could be one nobody can ever spend (a Plutus V1 order holding its datum inline, one carrying a script, a datum malformed in a field the wallet doesn’t read), so every swap now builds each order’s cancel and runs it through the DEX’s real script before it signs (checkOrderCancels). Also: a Danogo swap selling for ADA could hide any network fee in its proceeds, and nothing held a swap approved against the pools to that route; a site’s request read for one dApp account could be signed with the next one’s keys; and Transaction details told a chain’s sent transactions they weren’t sent. All fixed with regression tests written first; a cross-area review of the merged fixes then found 8 more, all low, fixed too. Checked against the chain, with a few Koios reads: the new rule that a Danogo swap recreates each pool it spends at that pool’s own address, once, against 12 real mainnet swaps; and that Koios’s tx_info gives each input’s tx_hash and tx_index, which the Splash remnant walk needs. The new warnings were back-translated blind, and the first pass caught the same slip in Spanish and Japanese: the “now routed as an order” refusal read as if the order had been approved. The privacy policy now says the wallet runs a DEX order’s script, read from the chain, in its own evaluator, and fetches no JavaScript or WebAssembly (8 October), and the store listing’s remote-code text says the same; its answer stays No. Left for the owner (post-release-roadmap.md): whether a sent chain stays viewable until it lands (open question 4); seedelf-wallet-1.3.0-mainnet.zip, is built from 588377e (PR #286’s merge commit) with Node 24.19.0 (its own zlib) and Ubuntu clang 18.1.3: SHA-256 621744379288085c7302483915eab8c49d3648418803723f9c7255d390111c81 (1,854,642 bytes), and the whole e2e suite, 81 tests, passed on it. Since 1.2.0 the manifest is unchanged but for its version.web-wallet/small-fixes, from main after PR #284). No plan file: each item was a line of post-release-roadmap.md’s Owed or Kept in mind, ticked where it stands. Every service request leaves nothing in Chrome’s cache (cache: "no-store" on SERVICE_FETCH): Koios sends no caching headers at all, so nothing was ever reused, and CoinGecko’s 30 s of reuse was never relied on; a privacy-policy entry, and the policy’s effective date, which #284’s entry had left at 6 October. A chain through Lovejoin is open to the transaction view while it’s sent (O3): its row lists Its transactions, each on chain, sent, or not sent yet, and Home’s banner links its first transaction, “the first of 5”, until the last lands. The surprise: the Lovejoin page hides a mix from the public account from Chains while it sends, so its list lives in the section that counts its progress, read live from progress(). A site’s signature waiting when the dApp account changes is declined with CIP-30’s AccountChange, and refused if approved after. Lace pins an account to each site instead and guards only signData; the owner keeps one dApp account (Eternl’s model). Dates follow the language (O8, dateLocale()); numbers stay en-US’s, the owner’s call, matching the amount fields. A test pinned the browser’s own locale for the Lovejoin retry time, which now follows the language too. The lock countdown says a chain is being sent and that locking stops it (privacy review §6). Seven mainnet tokens added (MILK, C3, NMKR, MELD, WRT, WMTX: each the Minswap-verified one of several registry entries; and USDCx); BOOK stays off: its only registry entry is a joke token named HODOR. Vitest 1,764 on both builds, Playwright 81 (a public mix held mid-send drives the banner, the list and the countdown). One full Vitest run failed one unnamed test that two reruns and six runs of the touched files didn’t repeat, as in chunk 24.web-wallet/dapp-additions). post-release-roadmap.md’s chunk 24, and the record is archive/plans/chunk-24-dapp-additions.md. SundaeSwap V3 and Danogo are routed through Minswap’s router, SplashStable is out, routes are direct, and Stop’s cancel is the wallet’s own. The surprise: Minswap’s cancel-tx answered 404 when probed, and its pending-orders missed orders, so the owner’s preprod Stop of a V3 order sat on cancelling with nothing listed, and 1.2.0’s Stop cancels some orders and not others. The wallet now builds every DEX’s cancel itself from seedelf-core’s orders.json (each order script, redeemer, signer and reference UTxO, pinned by network and checked on chain), finds open orders on chain through Koios, and checkOrder refuses any order it couldn’t cancel. Pallas’s script data hash is wrong for a transaction with witness datums, which a Plutus V1 order’s cancel carries: with_integrity recomputes it (seedelf-platform/CLAUDE.md has it). A multi-hop order Minswap builds isn’t the sender’s: a V3 first leg pays Minswap’s adapter, owned by Minswap’s key. Minswap routes direct unless asked, so the wallet asks allow_multi_hops: false and refuses a longer path. Danogo’s build-tx comes signed already by the owner of Minswap’s collateral, and attachWitnesses keeps that signature. The DEX runs are mainnet’s: preprod’s V3 has had no scooper since about 2026-09-10, and preprod routes almost nothing the wallet can buy, so the plan lists mainnet pairs that each go through one DEX. The owner cancelled the stuck preprod order live, then ran every route but Splash and Spectrum on mainnet, and two Stops cancelled there, a Minswap V2 order and a Minswap V1 one, the Plutus V1 cancel whose script data hash is with_integrity’s: “everything works”. Rust’s orders_test cancels a real order of every routed DEX through its real script; Vitest 1,755 on both builds, Playwright 80. It goes out in 1.3.0 (the owner’s call).web-wallet/release-1.2.0, from main after PR #281). First a release review of every PR since 1.1.0 (#272–#281): one reviewer per PR, the two big ones split by area, six cross-PR lenses, and an independent verifier on every finding — 118 of 122 confirmed, in 60 clusters. Several accounts meeting everything after it is where the bugs were. The one high: Lovejoin’s public chains recorded no account, so Mix again from my public account on a second account spent the first account’s boxes alongside its own, under a review saying it tied nothing new; chains carry their account now, and a missing one reads as account 0, which is 1.1.0’s data. Also: the connector read locks, collateral and its cached view by the picker’s account instead of the dApp account; a resent top-up that was turned away could be stranded by Disconnect; accounts a restore found never reached the open page; and the copy trim had turned two privacy notes into false promises. All fixed with regression tests (Vitest 1,622 → 1,727; e2e 80). The owner’s calls: Remove chooses nothing where another account paid; a pasted 1,125 in Spanish is refused with both readings; a seed’s boxes keep coming back by themselves, so the docs and the seed reviews are what’s wrong; and a site’s sign window should show each DRep vote. The last two were built before the package (45e4fea), so 1.2.0 has them. The package, seedelf-wallet-1.2.0-mainnet.zip, is built from 72d111d (PR #282’s merge commit) with Node 24.19.0 (its own zlib) and Ubuntu clang 18.1.3: SHA-256 022a8a1ce0bdc5102d99fe9cf2643a1c9f505ff5c70248636de4d0e6977d07b8 (1,783,353 bytes), and the whole e2e suite passed on it. Since 1.1.0 the manifest’s permissions and hosts are unchanged; only its CSP’s connect-src gained the IPFS gateway, which the host justification already names.web-wallet/doc-review). post-release-roadmap.md’s step 5, brought forward by the owner ahead of the UX pass; the record is archive/plans/chunk-22-documentation-review.md. Every doc was checked against the code by nine reviewers in parallel, each owning its own files, and then the whole diff across them. The owner’s calls: finished plans move to archive/plans/ — all 22 went, after what they still held moved into the design docs and their open items into Owed and Kept in mind — and the docs no longer date releases or describe the store. The surprise was the agent-facing files: the root CLAUDE.md didn’t mention the web wallet at all, both CLAUDE.md files had the crate dependencies backwards, and post-release-roadmap.md had described the replaced connector model (a site bound to its account and refused) for two days. Anything a doc enumerates had rotted by exactly what the last chunks added — storage keys, exports, screens, Settings rows — and three fixture recorders outside CI’s globs had been broken since the crypto review. Two code bugs fell out and were fixed here: account 24301’, the private sessions’ one-time account, could be added as a public account (privacy.md’s rule 6 was enforced by nothing), and the CLI’s fund paid a register without build::is_payable. The privacy policy gained chunks 18 and 19, which never reached it, and goes live on merge.web-wallet/governance). post-release-roadmap.md’s last public-side item, and the plan is archive/plans/chunk-21-governance.md. The account’s DRep is CIP-105’s key 3/0, as Lace derives it (pinned by CIP-105’s test vector 1), and Lace has no screens for it at all: it hands GovTool that key over CIP-95. So the chunk is both, the owner’s call: the wallet’s own screens (21a: register with the account’s own vote, a profile only if asked for, vote on the live actions, update, retire) and CIP-95 in the connector (21b). The DRep’s certificates and votes ride the staking patch, and Staking::signers() now counts the stake key and the DRep key apart, so a registration with the account’s own vote is priced and signed with both. Koios refuses to order proposal_list by a column the select leaves out, which only the fixture recorder caught; and the metadata text is 70 KB of mainnet’s 3 live actions, so the select takes the title and abstract by JSON path (5.7 KB). A site without CIP-95 is told nothing of the DRep key: WebAssembly sees it as a stranger’s for that site. enable() still answers true, and the page asks getExtensions() for what was granted. The probe (tests/fixtures/probe-governance.mjs) had preprod’s node decode every DRep transaction at 0.17–0.18 ₳. The owner registered and voted on preprod the same day; retiring live, a live profile update and GovTool by hand are O6. The blind back-translations found “retirar” doing two jobs in Spanish (retiring a DRep, withdrawing rewards) twice.web-wallet/nft-images). post-release-roadmap.md’s P3, the last of feature parity, and the plan is archive/plans/chunk-20-nft-images.md. Show image in an NFT’s details: one Koios asset_info (both standards in two columns: CIP-68’s reference datum under "222", CIP-25’s label 721), then one fetch from IPFS. The surprise: the public IPFS gateways are mostly gone. ipfs.io and dweb.link answer 429, “switching to a service worker gateway only”, and of about twenty tried only ipfs.blockfrost.dev served the file, which is Lace’s too. It sends no CORS headers, and that decided the design: an <img src> would have worked with no grant, but it writes the image, and an address naming the NFT, to Chrome’s disk cache for a year, and sends the host’s cookies. So the worker fetches with cache: "no-store" and no credentials, through Chrome’s grant for that one host, asked by the page at the first click. That host is a part of the dApp connector’s optional https://*/*, so the manifest’s permissions are byte-for-byte the same and only connect-src grew. A request without the grant still reaches the gateway; only its answer is blocked. So the worker checks the grant before it asks anyone, Koios included. Only IPFS is fetched, because anyone can send you an NFT whose image is on a server of theirs; another gateway’s /ipfs/<cid> address is rewritten to Blockfrost’s, since it’s the same file. The images live in the page’s memory until the lock. Real metadata is messier than the CIPs: mediaType: "image/jpg", image: "ipfs://" with no CID, a 222-labelled NFT with no reference datum at all, a CIDv1 in base58; the test account’s NFTs have all of them, recorded by tests/fixtures/record-nft-images.mjs. The blind back-translation found the Settings line’s Spanish “No tiene cuentas” read as “the wallet has no accounts” beside “cuenta pública”, a slip from chunk 19 that this chunk’s edit to that key surfaced. Chrome’s dialog can’t be answered in e2e, so the test runs on the siteAccess build, whose grant is the one the dialog gives.web-wallet/language). One reviewer per language read every Spanish and Japanese entry; the plan’s section has what it found. The surprise: the worker never switched language — startI18n() only ran in the page, so about 300 messages were English for everyone — and making it switch would have broken code that matched its own English: /locked/i on the lock error (in Spanish or Japanese a lock mid-build would have sent a session’s ADA back unmixed) and a stored “Private session N” read back by regex. Both are typed errors or stored numbers now; never compare, parse or persist translated text. Also found: English left on screen in non-prose shapes (a component’s title prop, a raw vote enum, an English delete phrase a Japanese input method can’t type), Japanese sentences joined with spaces in source, and the critical set’s blind spots — warnings whose words come from helpers, props, state or the worker’s own refusals — now followed by tests/i18n-critical-helpers.test.ts and tests/i18n-worker-reasons.test.ts (298 critical keys, 195 at the build). Both locales’ vocabulary is a section of the glossary, marked as the review’s choices; Spanish “Configuración” replacing “Ajustes” is the one for the owner to confirm.web-wallet/language). post-release-roadmap.md’s P2, and the plan is archive/plans/chunk-19-language.md. All 2,471 strings out of source into en, es and ja; i18next alone, which has no transitive dependencies, so the whole chunk cost one MIT package. What made a 2,471-string bulk change safe was that the ~120 vitest files assert on rendered English: initialise i18next synchronously at import, in English, and they pass untouched — which promoted “English is byte-identical” to the acceptance test, and it held through every commit. The trap is any module-level const holding a sentence: ES imports run before main.tsx awaits the stored language, so an eager t() binds English for the session’s life. Fourteen of them existed; all are functions now, and four were missed by the first audit because their t() sat on the next line. English word order was load-bearing in about twenty places (many ? "They" : "It", plural(n, "box", "boxes"), `That ${what} isn't ready`) and each became whole-sentence keys, with a pronoun count passed as count where agreement was the only variable — Spanish then picks its own form and Japanese needs none, because Intl.PluralRules gives en one/other, es one/many/other and ja other alone (so key parity is by base key, never by exact key). The critical set is derived from the source, not hand-kept like Lace’s glob list: the keys inside <Callout tone="privacy"|"warn"> and role="alert", skipping button and input subtrees, plus any key named .privacy./.warn.; 195 of them, each back-translated and recorded, with the build failing on an unchecked draft. Its one blind spot, worth remembering: a warning whose sentences are assembled in consts above the JSX is invisible to the deriver — NotMixed was, and those keys moved under lovejoin.warn.notMixed.* so the name rule catches them. One real bug fell out: tokenText compared a token’s name against the literal "(no name)", which is t("format.noName")’s English, so in Spanish a token with an empty asset name would have been labelled “(sin nombre)” instead of by its fingerprint. The one that cost a CI run: a shared module pulled i18next into the content scripts. shared/dapp.ts gained a single t() call; both content scripts import it, and content/page.ts runs in the page’s own world where chrome is undefined — so i18next’s init threw, the script died before defining window.cardano.seedelf, and every dApp connector e2e test failed while all 1,186 unit tests passed. Each content script had also grown from 2/9 kB to 649/656 kB, carrying all three locale files. cutOff() is English again. vite.config.ts’s own graph had the same problem — it builds the manifest from src/manifest.ts → networks.ts, where a t() for POOL_SEEDABLE pulled i18next and every locale into the build’s config, which is what Vite’s seven configLoader: 'native' warnings meant; moving that constant to its only caller cleared them. tests/import-boundaries.test.ts walks both graphs, reading the config’s own ./src/… imports rather than listing them. Cost: +567 kB raw, +134 kB gzip, the bundles moving into the shared chunk (so sw.js and index both shrank); store zip 1,670 kB. The scan that found the strings hid every single-word one — it required whitespace in the trimmed value, so "Show", "Password", " to ", " hours" and every plural()/count() call survived it; the owner found four by opening the wallet and a scan without that filter found twenty-two. plural() is deleted rather than fixed, so the compiler stops it returning. Dates and numbers are still English: 19 toLocale*/Intl calls pin en-GB/en-US, named as a known gap. Still open, and the owner’s to decide: nobody who speaks Spanish or Japanese has read any of it, which docs/i18n/verified-critical-{es,ja}.json says in its own reviewer line (settled on 2026-10-03: accepted as recorded).web-wallet/several-accounts). post-release-roadmap.md’s P1, and the plan is archive/plans/chunk-18-several-accounts.md. Every key access already went through wallet.withKeys(), so making keys.cardano the active account’s left about 130 call sites right as they stood — the active index is read at every key use, and a switch writes the choice and nothing else. The real work was never the picker: it was that money made private from two accounts must not be co-spent, since the registers hid who the money went to, not where an input came from. MADE_PRIVATE became madePrivate(account) (public:<n>), and the bare public that wallets sealed before this chunk has to canonicalize to public:0 — read as two classes it would have the wallet claim a spend ties two accounts together when both are account 0. Two bugs the tests caught: historiesNote gated on its argument’s raw length, so a legacy public beside a fresh public:0 read as a merge of none; and sitesOn projected account away, which would have read every connected site as account 0’s. Discovery deliberately does not use usedStakeAddresses — one request for twenty stake addresses would tell Koios those twenty are one wallet’s. destination.ts’s own-account warning now covers every account, not only the active one. Two corrections from the owner afterwards, both worth keeping: MAX_ACCOUNTS = 25 capped the index, not the count, so a custom account like 1337 was unreachable — CIP-1852’s account component is hardened and runs to 2^31 - 1, and reaching one needs a number entry (Check it, one request; Add it, none at all, used or not) because sequential discovery stops at the first unused account. And “we are making a lot of assumptions about accounts not being linked when they in fact can and in some cases that was encouraged”: a first attempt refused a public Send to another of your own accounts; that was over-reach. The wallet never forbids a link the user chooses — it says what the payment reveals — and only avoids making one for them. Then three rounds of the owner looking at the built wallet, all of which only looking would catch: a 48px button where a .chip belonged; an accounts list with no row padding at all, because .list is a bare flex column, which a wallet with ten accounts made obvious; and a × to clear the destination fields, whose first aria-label repeated the field’s own label and so made that string match two elements. The plan’s After the owner looked at it has them. Then the owner tested a real many-account wallet on preprod and settled the rest, the big one being the dApp connector: Eternl’s model — one chosen dappAccount that sites always use, whichever account the picker is on. That removed the refusal and the per-site account entirely, and it needed the dapp.ts threading the plan had deferred — but one value made it small (withDappKeys, seven call sites, plus an account option on readAccountUtxos); per-site accounts would have been the hard version.web-wallet/transaction-view). The decoder takes CBOR and nothing else, so a fixture is a whole test of it, and the expected values are cardano-cli debug transaction view’s rather than our own output frozen. Pallas gives a Conway transaction Alonzo’s auxiliary data, whose typed form knows only keys 0–2, so every auxiliary script is read from the raw bytes instead. What it left open is post-release-roadmap.md’s O3.web-wallet/release-1.1.0). Minor, not patch, because it adds something a user can do. src/manifest.ts is byte-identical to 1.0.0, so no new justification and the data disclosure stands. Two tests were stale, and a release is what finds that: one asserted the version in About, one asserted a Koios page count that preprod’s growth had changed.0d5ec42, SHA-256 85c0fb093fa8bb157f5ad3d9ea1fd001db75d7273c3068aa52ea65932dee714b, 1,533,435 bytes, reproducible from that commit. Two things the dashboard asked that the docs didn’t cover: it refuses every upload until 2-Step Verification is on the developer account (and blames the file, not the account), and it gives one host-permission box rather than one per host.web-wallet/release-prep). Koios’s public tier allows 100 requests every 10 s from an IP, and the wallet keeps to 40 so two workers either side of a restart still fit; a 429 now holds every request back for as long as Retry-After asks, kept across a worker restart. Lovejoin could never start on mainnet — the pool was empty and the floor of 30 gated the deposit too — so a seed (depth 0, the deposit alone) now works on an empty pool. Then Lovejoin ran on mainnet end to end, and that run is where the mainnet mix_box hash c145c10f…1fad was confirmed against real transactions: it had none before, and Lovejoin’s own config/network.mainnet.json still has mix_script_hash: null upstream, so our value is right and upstream’s absence is not a reason to change it. A mix measured 0.8225 ₳, matching networks.ts’s mixCost.web-wallet/crypto-review). See archive/plans/independent-review.md. Most findings were bugs the earlier rounds’ own fixes brought in, where two features meet — which is why the merged fixes were then reviewed across areas, finding 13 more.web-wallet/crypto-review). The owner’s rule: the most private option is the default, with a control to turn it down. See archive/plans/privacy-review.md for what landed and what the owner declined.web-wallet/crypto-review). See archive/plans/launch-review.md; what it left open is in post-release-roadmap.md’s Owed and Kept in mind. Lovejoin on for mainnet, one build with both networks, and a Send that Koios didn’t answer is maybe sent — never paid twice.web-wallet/crypto-review). See archive/plans/crypto-review.md. Hedged proof nonces; private spends measured in the wallet, so no draft goes to Ogmios; storage closed to content scripts. The vault’s KDF was kept as-is, matching Lace’s, at the owner’s call.web-wallet/lovejoin). Mix my boxes again, and a countdown in the last two minutes before auto-lock. Chunk 16 ends here.rpc.ts’s list was dropped by the worker in silence; it’s a type error now.uplc 1.1.21 mis-costs protocol 11’s cost model and failed every mix; 1.1.23 matches the chain exactly — which is why Pallas moved to 0.35. No Pallas version up to 1.4.0 stages withdrawals or a Reward redeemer.web-wallet/dapp-connector). The longest note in the archive, and worth reading there. Koios’s public tier stopped sending CORS headers (open only with an API key now); the extension still reads it because host permissions skip CORS, but the connector’s off state removed https://*/* and Chrome took Koios’s and giveme.my’s grants with it, so every POST failed. Minswap’s transactions carry the order’s datum in the witness set, so a signature is spliced in byte for byte — re-encoding breaks the datum’s hash. Each session gets its own stake key (24301'/2/i). Minswap’s build-tx takes only a sender, which is why a swap can’t come from Seedelf directly and A2 waits on a batcher test.web-wallet/staking). Pallas can’t stage certificates or withdrawals, so staking.rs patches them into the built body and rewrites the body hash — patch first, sign after. Koios’s account_info returns no row at all for a stake key that was never registered.web-wallet/style-flow). The owner’s 29 findings. Koios refuses request bodies over 5,120 bytes, so credential_utxos asks about 75 keys a request. The account is read by payment key now, so an enterprise address, or our key with someone else’s stake key, is seen and spent.web-wallet/store). The zip is reproducible from its commit, and refuses a dist/ built with the dev key. 13 crates ship no licence file, so third-party.mjs falls back by licence and fails on anything else. The store’s rules as checked live are in store/README.md.web-wallet/size-and-live). wasm-opt shrinks the raw module but grows the compressed one, and the store downloads a zip — so opt-level = "z" is used and wasm-opt isn’t. Six flows ran live on preprod (hashes and fees in the archive). A Koios backend can lag twenty minutes, so spent.ts remembers what was submitted and never counts or builds on a spent UTxO.web-wallet/polish). Dark only, Inter, Lucide, Home as a tab switch. React reused the Seedelf tab’s round buttons for the Cardano tab, so each panel needs a key. The owner’s verdict afterwards: “kind of looks like Lace but also totally does not” — which is why a style and flow pass keeps recurring.web-wallet/withdraw). ScriptSpend::change_to(addr) sends change to a key address instead of the contract. Every CLI script spend ends in commands/spend.rs; only create and fund still build inline.web-wallet/transfer). Found here: Register::is_valid accepts the identity point. An identity public value is g^0, so anyone could take a payment to it — hence build::is_payable, which is what to check before paying a register.web-wallet/mint-first). The account-paid mint uses the account’s own UTxO as collateral, not giveme.my: that’s what the CLI’s create does, the transaction names the account anyway, and it lets WASM sign at review.web-wallet/create-seedelf). The node refused the first live mint with FeeTooSmallUTxO — 13 lovelace short — which is how the Byron fee-policy bug was found. The owner’s privacy correction: a stealth mint paid by money you moved in yourself links the account to the Seedelf, so stealth minting only helps when received Seedelf money pays for it.web-wallet/move-in). A rebuild under a running extension left the old worker asking for a deleted, hash-named WASM file, and the failure was cached — which is why a stale worker now says so.web-wallet/balances). The recorded preprod fixtures match what live Koios returns. Balances are read when Home opens if the last reading is over a minute old.web-wallet/vault-lock). Lace’s SecretBox seals the BIP39 entropy; the words themselves are never stored. Auto-lock after 15 minutes, passwords of at least 12 characters with no composition rules.web-wallet/extension-scaffold). Vite, React 19, TypeScript, MV3, and .github/workflows/web-wallet.yml.web-wallet/cardano-keys). CardanoAccount, verified against Lace’s @cardano-sdk/key-management across 12, 15 and 24 words, accounts 0 and 1, both networks.web-wallet/key-derivation). The v1 derivation, frozen, with nine vectors. BIP39 moved into Rust, so @scure/bip39 left the JS stack.web-wallet/wasm-foundation). The seedelf-wasm crate.