Frozen on 2026-10-02. This is roadmap.md exactly as it stood when v1 finished: every chunk’s full scope, and all 30 handoff notes, about 90 KB of them. The live file was compressed to an index on the same day; nothing was deleted, it was moved here.
Read this when a one-line entry in the live roadmap isn’t enough — when you need the reasoning behind a decision, what a chunk actually did, what surprised the session that built it, or a transaction hash and fee from a live run.
Line references to
roadmap.mdwritten before 2026-10-02 (such as the one in plans/privacy-review.md) resolve against this file, not the live one.Don’t edit this file. It’s a record. New handoff notes go in the live roadmap.md; what comes next is in post-release-roadmap.md.
Every relative link below has gained one
../, since this copy sits one directory deeper than the original. Nothing else in the text was changed.
The wallet is built in chunks, each about one working session.
Each chunk:
web-wallet/<topic> taken from main (from seedelf-web-wallet before v1.0.0). See development.md.main.Status: ✅ done · 🚧 in progress · ⬜ not started
Shipped. Every chunk below is done, and v1.0.0 went to the Chrome Web Store on 2026-09-28 (see the newest handoff note). Work from here is After v1.
| # | Chunk | Status | Scope |
|---|---|---|---|
| 1 | WASM foundation | ✅ | Create the seedelf-web-wallet/wasm crate (a workspace member). Expose register create, re-randomize, the ownership check and the Schnorr proof. Build with wasm-bindgen and smoke-test from JS. |
| 2 | Seedelf key derivation | ✅ | Implement the v1 HKDF spec (keys-and-accounts.md) in seedelf-crypto with frozen test vectors. Expose it through WASM and check the vectors from TS. |
| 3 | Cardano keys | ✅ | Phrase → CIP-1852 Cardano account (account 0'): receive, change and stake addresses, in Rust (pallas-wallet). Checked against Lace’s library (@cardano-sdk). |
| 4 | Extension scaffold | ✅ | Vite + React + TS and an MV3 manifest (preprod). Service worker, popup plus full tab, typed messaging, WASM loaded in the worker, load unpacked. CI for Rust and the extension on PRs. |
| 5 | Vault and lock | ✅ | SecretBox vault, create/restore onboarding (restore has per-word BIP39 autocomplete, like Lace and Eternl), unlock, chrome.storage.session, auto-lock, unlock back-off. Plan: plans/chunk-05-vault-and-lock.md. |
| 6 | Balance | ✅ | TS Koios client. Contract scan using the ownership check. Cardano account discovery: receive and change chains, gap limit 20. Balances, tokens, list of Seedelfs. QR code for the receive address. |
| 7 | Builder extraction + move in | ✅ | Merge main first. Gate seedelf-koios’s connect_timeout for wasm32 (the only thing that stops seedelf-core compiling to WASM). Split building from network calls in seedelf-core, starting with external sweep, and keep the CLI tests green. Then move in, end to end on preprod. |
| 8 | Create a Seedelf | ✅ | Stealth mint (util mint) with giveme.my collateral. Plan: plans/chunk-08-create-seedelf.md. |
| 8b | Mint first | ✅ | The first Seedelf is paid by the Cardano account (the CLI’s create, signed in WASM, the account’s own collateral), before any move-in. The stealth mint stays as a choice for a Seedelf balance holding received money. See flows.md. |
| 9 | Transfer | ✅ | Seedelf → Seedelf (transfer). Plan: plans/chunk-09-transfer.md. |
| 10 | Withdraw | ✅ | sweep and remove, on ScriptSpend and the extension’s script-spend.ts. Plan: plans/chunk-10-withdraw.md. |
| 11a | Style and flow pass | ✅ | The whole UI, much more like Lace’s dark mode (packages/lib/ui-toolkit/src/design-tokens/theme/dark.ts): its look, not its brand. Dark only, Inter, Lucide icons, and Home as a Seedelf / Cardano account switch with round actions. Plan: plans/chunk-11-polish.md. |
| 11b | Size and live runs | ✅ | A smaller WebAssembly module: a size-tuned cargo profile (wasm-opt measured and left out). Live preprod runs of every flow from the built extension. The loose ends from chunks 8b–10. Same plan. |
| 11c | Testers | ✅ | Everything needed to submit an unlisted, preprod-only Chrome Web Store listing, never submitted: npm run package (the store build, third-party notices, a reproducible zip), the listing text and privacy policy in store/, the images from npm run store:images, and a release checklist. Same plan. |
| 12 | Style and flow | ✅ | The user tested the built wallet and sent findings; 29 items, each decided with the user. Among them: a loading splash, a Tokens screen and a bundled token list, a token picker with any amount of each, Settings, Activity, Contacts, an incremental contract scan, Send from the Cardano account, the minimum ADA worked out, Receive on the Seedelf tab, spending everything under the account’s payment keys, a UTxOs screen on both sides with locks, the Cardano account’s collateral in Settings, and Refresh on UTxOs and Activity. Plan: plans/chunk-12-style-flow.md. |
| 13 | Staking and voting | ✅ | The wallet becomes a full Cardano wallet with Seedelf built in: a Staking page (one pool, rewards spent automatically or by hand), a pool browser, and voting delegation (Always abstain, No confidence, or a DRep). Certificates patched into Pallas’s transactions. Plan: plans/chunk-13-staking.md. |
| 14 | Style and flow, second pass | ✅ | The user’s second round of findings, each decided with the user, as in chunk 12. So far: Send from the Cardano account pays a Seedelf, one way to write the name (Seedelf, and Seedelf Wallet for the app), several recipients in one payment on both sides, and Private and Public in place of Seedelf and Cardano. Then what Lace and Eternl had that the wallet didn’t: a full tab by default or the side panel (no popup), hide balances, a note on a public send, the lock time, your handles on Receive (and a warning for a handle in Seedelf), a check of the written phrase, Activity as CSV, staking in the public Activity, and ADA’s value in a currency on mainnet. Readable dropdowns, and a code review’s fixes before the PR. Plan: plans/chunk-14-style-flow-2.md. |
| 15 | dApps: the public connector, and private swaps | ✅ | The first steps towards using contracts privately. Private swaps: Minswap’s aggregator swaps from a one-time account funded from the private balance, and everything comes back into it (step 2). CIP-30 for the public account, as Lace has it, behind a Settings switch that’s off by default: Chrome is asked for access to sites only when it’s turned on, and nothing is added to any page until then. A window to connect, sign transactions (what they do to the account, read in WebAssembly) and sign messages (CIP-8), connected sites to disconnect, and chaining on the account’s own unconfirmed outputs. Then private sessions, a dApp browser, and Minswap. Plan: plans/chunk-15-dapp-connector.md. |
| 16 | Lovejoin | ✅ | The mixer in the wallet. A private session’s spare ADA goes through Lovejoin on its way back: 10 ₳ boxes owned by the Seedelf key, a fan-out 3 wide (depth a setting, default 2) chained locally and paid by the session, then each box withdrawn into a fresh register after a random delay. The leftover and any tokens merge into the session’s funding change. A Lovejoin tile mixes from the private balance or the public account. Scripts are evaluated in WebAssembly (uplc), after Pallas moves to 0.35. Plan: plans/chunk-16-lovejoin.md. |
This section is closed, and so is this file. Chunk 17 was the last thing chosen from it. Everything after v1.1.0 is tracked in post-release-roadmap.md — what’s owed, what the owner wants, and what we’ve decided against — and a chunk picked up from there still lands its handoff note in this file.
| # | Chunk | Status | Scope |
|---|---|---|---|
| 17 | The transaction view | ✅ | Open the transaction itself — inputs, outputs, datums, scripts, certificates, votes, redeemers, metadata — decoded from the CBOR about to be signed, plus the raw bytes, from every review and from the connector’s sign window. A decoder in WebAssembly that takes CBOR and nothing else (wasm/src/decode.rs), the cheapest handler in the worker (background/tx-view.ts), one shared modal (ui/components/TxDetail.tsx), and a cross-check against cardano-cli debug transaction view for every fixture. Plan: plans/chunk-17-transaction-view.md. |
seedelf-web-wallet into mainmain and PRs into main (development.md). The old branch is finished and can be deleted whenever.Newest first. Keep each entry short: what landed, what’s next, and anything surprising.
web-wallet/transaction-view, from main). The first After v1 item, and the only one that was fully unblocked. Transaction details under every review and under the connector’s sign window: the transaction decoded from its own bytes, and a Raw CBOR tab.
wasm/src/decode.rs, decodeTx(network, txCbor)): no account, no keys, no UTxOs, so it makes no network call and a fixture is a whole test of it. cip30::inspect_tx was not touched — it answers a different question and is load-bearing for the approval decision — and background/cbor.ts stays the hardened pre-check it says it is.txhash#index with no lookup; no explorer link, a copy button instead; a modal, on every transaction the wallet builds, and the connector’s too.wasm/tests/fixtures/record-decode.mjs records ten transactions with what cardano-cli debug transaction view makes of each: the wallet’s own preprod transfer, stealth mint and remove, Minswap’s swap, a session swap, and five cardano-cli builds (a payment with a reference input and a reference script, certificates with a withdrawal and metadata, a mint and a burn, a pool registration, governance). decode_test.rs checks the id, the inputs, every output’s address and value, the datums, the collateral, the mint, the withdrawals, the validity range, the required signers, the script hashes and the budgets against that view, transaction by transaction. cardano-cli transaction build-raw needs no node, so the whole cross-check is offline.slot or ex_units past 2^53 rounded on the way through JSON.parse (every number the bytes decide is a decimal string now); bytes that are no address, which the CDDL allows, hid the whole transaction behind an error (they show as hex); a right-to-left override in a note or a token name could rewrite the line it was on (escaped, and the stylesheet isolates direction); and nothing checked the metadata against the body’s hash, so the view could show a note that could never reach the chain. Three things the page left out are shown too — a collateral return with no collateral inputs, a pool certificate’s parameters and a proposal’s own fields, and Byron witnesses — and the page now walks a certificate’s and a proposal’s fields rather than naming them, so a field the decoder gains can’t be dropped there either. See the plan’s The CBOR review.components/Hint.tsx. Privacy callouts and warnings stay where they are, since those are decisions rather than explanations. The owner’s wider point (“a lot of the paragraphs we have could be like that”) is theirs to pick from: about 150 plain note paragraphs sit across the screens, most of them argued over in chunks 12 and 14 or in the privacy review.register_detail ran on every datum, so another contract’s constructor 0 with two 48-byte fields was called “a register” — it now runs only for an output at Seedelf Wallet’s own contract, and not at all for a witness-set datum.#0 · a contract · Seedelf Wallet's contract · under a register line, where the Seedelf naming is a hash compare against the variant’s own script hash and the register adds the one thing a shape can’t say — whether a payment under it could be spent. Everything else in the view is already general, and a datum of another contract’s is named nothing.is_payable for each box’s register, Ogmios for the cost model before a chain starts, the sealed record so a chain cut short surfaces as not-mixed-yet. The one assumption that isn’t is Lovejoin’s own contract, which the review already says in words. Still open: a chain in flight is invisible to the view, since its transactions move to seedelf.session.chain.<network>.<index>, which BUILT_KEYS doesn’t name — the audit view wants that prefix scan. And Home’s banner watches the chain’s last transaction, so it shows a hash the user never saw; it may want to say “the chain” instead.web-wallet/release-1.1.0, from main after PR #270 merged chunk 17).
src/manifest.ts is byte-identical to the 1.0.0 release, so the four permissions and four hosts are unchanged: no new justification, and the data-use disclosure stands. The description gained one bullet for reading a transaction before it is signed. npm run store:images produced byte-identical images, so there are none to re-upload.Version1.0.0 in About, so every bump would have broken it: it reads the version from package.json now, as the manifest does. And tests/live.test.ts asserted that two readings make exactly two credential_utxos requests — preprod’s shared contract has grown past one page of 1,000, so a reading takes two. The wallet is right (the second reading still asks only block_height=gt.<last>, and agrees with the first); the test now asserts that invariant rather than the count.main, not from this branch, so the release record names a commit on main. The live preprod runs (e2e/live/run.mjs all, staking) and the by-hand preprod checklist are the owner’s, as they submit real transactions and look at real screens. Tag web-wallet/1.1.0 — with the slash, or release.yml ships signed CLI binaries.seedelf-web-wallet was merged into main first (PR #266, a fast-forward), so the listing’s homepage and privacy-policy URLs, and the wallet’s own Settings link, all point at main and resolve.
seedelf-wallet-1.0.0-mainnet.zip, built from 0d5ec42, SHA-256 85c0fb093fa8bb157f5ad3d9ea1fd001db75d7273c3068aa52ea65932dee714b (1,533,435 bytes). Checked before upload: no dev key, mainnet and preprod hosts, licenses/THIRD-PARTY.txt present, and the privacy URL in its own bundle points at main — which is what proves it was built from the merged tree. The zip is reproducible from that commit.MAINNET_PROTOCOLS routes, exclude_protocols, and Stop or a refund. None blocks the listing.web-wallet/release-prep, from seedelf-web-wallet).
BuildStage, stages on the request’s own port) instead of only greying its button; the swap and Lovejoin prose is shorter, minus nothing the privacy review requires.Retry-After asks, kept in session storage across a worker restart.64d636bd), twelve mixes two waves deep, then the return (c18a5207). The mainnet mix_box hash c145c10f…1fad is confirmed against real transactions — it had none at all before this, and Lovejoin’s own config/network.mainnet.json still has mix_script_hash: null (upstream). A mix measured 0.8225 ₳ across the twelve, which is networks.ts’s mixCost (825_000) and the launch review’s 0.822–0.828 ₳, now read off the chain rather than estimated.funding_for), so the three boxes it promised are the three the chain made.MAINNET_PROTOCOLS routes, exclude_protocols, and Stop or a refund. A detailed transaction view is recorded under After v1 for its own branch.web-wallet/crypto-review). A fresh review before mainnet, and its fixes, are in plans/independent-review.md (see its Status).
web-wallet/crypto-review). The owner’s rule is “the most private by default, with the ability to turn it down”. A review against it, and its fixes, are in plans/privacy-review.md (see its Status).
seedelf-web-wallet.web-wallet/crypto-review). The findings, the owner’s calls, what’s fixed and what’s still open are in plans/launch-review.md (see its Status).
Cargo.lock tracked and Rust pinned.sw.js, so every page ran a second worker. It’s fixed, and a build now fails if it happens again.hashes/ is a later, undeployed build: never copy it into the constants.web-wallet/crypto-review, from web-wallet/lovejoin). The findings, what’s fixed, and what’s left to decide are in plans/crypto-review.md.
storage.onChanged.web-wallet/lovejoin). The plan’s Built (third session) lists everything.
seedelf-web-wallet (the user, 2026-09-25: “we are at a really nice spot”).web-wallet/lovejoin). The plan’s Built (second session) lists everything.
rpc.ts’s list is dropped by the worker without a word. It’s a type error now.seedelf-web-wallet.web-wallet/lovejoin). Plan: plans/chunk-16-lovejoin.md, whose Built lists everything. Chunk 15 merged as PR #262.
uplc 1.1.23).uplc 1.1.21 (on our old Pallas) mis-costs protocol 11’s cost model, and it failed every mix. 1.1.23 matches the chain exactly.Reward redeemer.web-wallet/dapp-connector). Plan: plans/chunk-15-dapp-connector.md. Chunk 14 merged as PR #261.
wasm/src/cip30.rs): CIP-30’s encodings, what a dApp’s transaction does to the public account, signing it with the keys it needs, and CIP-8 data signatures. Exports: cip30Utxos, cip30Value, cip30Address, cip30ReadValue, inspectDappTx, signDappTx, dataSigner, signDappData.dapp.ts (the service), connector.ts (registering the content scripts), dapp-window.ts (the popup), the port listener in sw.ts, and Koios utxo_info.src/content/page.ts and bridge.ts, built as self-contained IIFEs by a plugin in vite.config.ts.screens/DappApprovals.tsx), and Settings’ Sites switch and Connected sites.scripting, and the sites as optional host permissions.cip30_test.rs), Vitest 10 (dapp.test.ts), Playwright 2. Totals: seedelf-wasm 57, Vitest 232 (+2 live).
cardano-verify-datasignature.https://*/* at every start, and Chrome’s remove takes Koios’s and giveme.my’s grants with it, so every Koios POST failed. Fixed: off keeps Chrome’s access, and a withheld Koios grant shows Ask Chrome again. The user chose to stay on the public tier with no API key (limits per IP address, nothing to leak). See the plan’s Koios and CORS.OneTimeAccounts, buildSessionReturn, inspectSessionTx/signSessionTx and attachWitnesses in WebAssembly; sessions.ts and minswap.ts in the worker; screens/Swaps.tsx from a Swaps row on Home’s Private tab.session_test.rs), Vitest 6 (sessions.test.ts), Playwright 1 (the whole swap against fakes).build-tx takes only a sender, so a swap can’t come from or go into Seedelf directly. Its V2 orders could (A2), which waits on a batcher test.attachWitnesses); re-encoding would break the datum’s hash.src/tokens/list.json can now carry a hand-vetted test-network token as unregistered.sessions.ts (one approval, then advance from the page, a one-minute alarm and unlock), a timeline page with Stop, pauses that say why, Home’s running rows, and screens/Dapps.tsx.exclude_protocols). See the 15b plan’s Found on the user’s first try.24301'/2/i, pinned against cardano-address. Older sessions keep their shared-stake address.list section on one element), and a second .steps rule from b858d22 had indented and muted Home’s Get started.seedelf) and asked whether signing without the password was safe.
dappPassword, on by default, a switch under Settings’ Sites). A wrong one leaves the request waiting and counts towards the unlock back-off (Wallet.checkPassword). The connector window’s Unlock says a site is waiting.enable() first and the unlock goes to that call. So Sign always asks.dappPassword is on.stakeIndex).stakeIndex and session data signing, site sessions in sessions.ts, every connector path resolving the site’s account, the connect window’s private path, and screens/SiteSessions.tsx.web-wallet/staking), in one PR rather than 13a and 13b. Plan: plans/chunk-13-staking.md, whose Status says where the build departed from it.
seedelf-core/src/staking.rs patches certificates and a withdrawal into a built account transaction and writes the new body hash into the BuiltTransaction, so signing is unchanged. build::account_staking (a payment to Payee::Nobody) delegates, delegates the vote, withdraws, or stops; move_in, account_send and account_mint take a &Staking for rewards riding along. ProtocolParameters gains key_deposit.buildStaking, poolId, drepId, and withdrawal on the move-in, send and account-mint requests; the stake key 2/0 signs inside it.account_info in every balance reading (Balances.cardano.staking), staking.ts (the pool list kept a day in chrome.storage.local, a pool’s and a DRep’s details, builds), preferences.ts (spendRewards, on by default), and staking refusals in plain words at submit.npm run dreps bundles every registered DRep with a name (src/dreps/: 61 on preprod, 452 on mainnet), and the vote page searches it by name or ID with no requests; a pasted ID still works for one registered since the release. Run it at each release, like npm run tokens.tests/fixtures/probe-staking.mjs. Every staking transaction decodes on the node (Ogmios answers []), and an account-paid mint with the rewards passes the real policy at the same budget.node e2e/live/run.mjs staking on the private test wallet (stake with TPREP, always abstain, then LOGIC), and later withdraw-rewards and unstake, once rewards arrive 15 to 20 days on.account_info returns no row at all for a stake key that was never registered.pool_list has tickers but no names, and only the active stake: saturation needs totals and optimal_pool_count too, so browsing costs 3 requests on preprod, not 1.select works on Koios’s POST endpoints (meta_json->body->givenName), so a DRep’s name comes without its metadata’s bulk, or its image.spent.test.ts).web-wallet/style-flow). Plan: plans/chunk-12-style-flow.md.
pallas-txbuilder (0.33, and 1.4.0 too) can’t build certificates or withdrawals; chunk 13 patches them into the built body.credential_utxos now asks about 75 keys a request.seedelf-core’s builders, so the CLI still refuses less; the web wallet raises a short amount in WebAssembly and says so on the review.build::move_in, account_send and account_mint no longer skip pure 5 ₳ UTxOs: they spend what they’re given, and the web wallet leaves out locked UTxOs and its collateral (items 21–23). The CLI doesn’t call them, and its own selection (collect_address_utxos) is unchanged.web-wallet/store). Plan: plans/chunk-11-polish.md. The materials only; the listing was never submitted.
seedelf-web-wallet branch.brand/README.md).description, at most 132 characters.npm run package: a store build, licenses/THIRD-PARTY.txt, and release/seedelf-wallet-1.0.0.zip.
8bf7dd79…fd14f53. It’s reproducible: a rebuild with the same toolchain gives the same bytes.dist/ with the dev key.e2e/support.ts holds what the two specs share. The tests read the extension’s ID from its worker, so a store build passes all 19, and CI now runs them on both builds.npm run store:images writes store/images/ from the fixtures and the public 12-word phrase.description, the store’s summary, is now “A Cardano stealth wallet. Pay and get paid through Seedelf, where no UTxO says who owns it.”blst and crc-catalog (they point at the Apache-2.0 text), base58 and bech32 (MIT, with the authors from Cargo.toml), and two CC0 bitcoin crates. third-party.mjs falls back by licence and fails on anything else.description did.npm run store:images) and upload them with the next version.main.web-wallet/size-and-live). Plan: plans/chunk-11-polish.md.
wasm-release profile with opt-level = "z", and no wasm-opt.Size (wasm/bench.mjs; a proof is about 1.8 ms and a transfer draft about 53 ms in every build):
| Build | Raw | gzip |
|---|---|---|
release (before) |
2,330 KB | 582 KB |
LTO, one codegen unit, stripped, opt-level 3 |
1,687 KB | 516 KB |
… opt-level = "s" |
1,276 KB | 442 KB |
… opt-level = "z" (used) |
1,223 KB | 424 KB |
… plus wasm-opt -Oz |
1,113 KB | 448 KB |
wasm-opt shrinks the raw file but grows the compressed one, and the Web Store’s download is a zip. The zipped dist/ went from 1,032 KB to 873 KB.Live on preprod (the private test wallet, node e2e/live/run.mjs):
| Flow | Transaction | Fee (₳) |
|---|---|---|
Create live-1, paid by the account |
c0f3449d…cfcb48 |
0.200988 |
| Move in 25.5 ₳ | 180277c5…9d7519 |
0.172145 |
Stealth mint live-2 (giveme.my) |
081a621d…286816 |
0.255934 |
Send 3.3 ₳ to live-1 (own, flagged) |
86eb9cb9…99d184c |
0.233912 |
| Withdraw 5.5 ₳ to its own account (flagged) | f7276736…c8ae240 |
0.23026 |
Remove live-2 into Seedelf |
8f00e464…aad7115 |
0.245746 |
preprod.koios.rest and www.giveme.my.live-1 (1.74986 ₳), 19.034148 ₳ in Seedelf and 9,977.877007 ₳ in the account.account_utxos still listed the UTxO it spent. After the move-in, a backend answered with the account as it was before the mint, 20 minutes earlier. account_utxos is a live db-sync query (koios-artifacts), so it’s a backend lagging, not a cache.
spent.ts. The worker remembers what it submits, reads again (up to 3 × 3 s) while an answer lists a spent UTxO, and never counts or builds on one.tests/spent.test.ts, with the fake Koios’s new spent set.TxSubmitConnectionError) twice. That answer means nothing was sent, so submittx retries it (after 1 s and 3 s), and says so plainly if it keeps failing. BadInputsUTxO now explains itself too.$buzzkill (plain) and $-2 (CIP-68), in the opt-in live test. No withdrawal to a handle has run live.e2e/live/{lib,flows,run}.mjs replace move-in.mjs and mint.mjs. They run in one browser session, as a user would. A fresh session knows nothing of what earlier sessions spent, so it’s more exposed to a lagging backend.web-wallet/store once this merges.web-wallet/polish). Plan: plans/chunk-11-polish.md.
styles.css: a near-black navy-tinted page, white-alpha surfaces, Lace’s radii and 4 px grid, teal accent with navy text on it. The contrast ratios are in its comment.font-src 'self'. The light wordmark is gone.Screen is every flow’s layout, with Back, the title, and a sticky foot holding the error and the primary action.ReviewRows, Callout (privacy, warn, info), ActionButton and Choice.Row and six of plural are gone. format.ts now has plural, adaWithTokens and tokenKey.test-results/popup-*.png).round-warning) instead of a class.style option is refused by the page CSP. snap() in the spec unsticks the foot through the CSSOM instead, and turns animations off.web-wallet/size-and-live from seedelf-web-wallet once this merges.web-wallet/polish, which 11a builds on.web-wallet/withdraw). Plan: plans/chunk-10-withdraw.md.
seedelf-core/src/build.rs):
ScriptSpend::change_to(addr) sends the change to a key address instead of the contract.sweep, sweep_from and sweep_all pay an address; sweep picks the token UTxOs first, as transfer does. remove burns the one Seedelf in a UTxO. is_payable_address is the CLI’s rule: Shelley, this network, no script part.sweep and remove are thin now. Every CLI script spend ends in commands/spend.rs, which also shortened util mint and transfer. Only create and fund still build inline.remove’s offline test now mocks Ogmios with real purposes (mount_evaluate_mint(1)); its assertions are unchanged.draftWithdraw/finishWithdraw (Max takes the 20 largest UTxOs, and left says how many stayed) and draftRemove/finishRemove.checkWithdrawAddress, and CardanoAccount.isOwnAddress, which checks for the account’s staking key in an address.withdraw.ts covers resolve, withdraw and remove, on script-spend.ts. The Koios client gains assetNftAddress for handles (plain, then CIP-68).TokenAmounts is shared with Transfer.extension/tests/fixtures/record-withdraw.mjs.
mint_test 24 (4 new); the CLI’s offline tests 11; seedelf-wasm native 20 and Node 29; Vitest 109 (+1 live); Playwright 18. The withdraw and remove e2e tests stop at Send, as the other Seedelf spends’ do.9f564d0f…). It also holds TAK1 and TAK2.TAK1 to the Cardano account, and record both tx hashes here.web-wallet/transfer). Plan: plans/chunk-09-transfer.md.
seedelf-core/src/build.rs):
transfer and transfer_from take Payment { register, lovelace, tokens }s. Each output is deposit_output, a fresh re-randomization of the register found on chain, checked against its minimum.Register::is_valid accepts the identity point. An identity public value is g^0, so anyone could take a payment to it. The new is_payable also refuses the identity; transfer and both mints use it.select_script_inputs takes the tokens being sent. The UTxOs holding them come first, with the biggest holdings of each token first, then pure ADA as before.NotEnough. The token UTxOs are picked up front, so only transfer_from with given UTxOs can hit that error, and it stays a hard error there.transfer run() is thin. Its offline tests pass unchanged.draftTransfer and finishTransfer. The recipient checks run here: a whole name, a wallet-contract UTxO holding that Seedelf, and a register datum. toSelf flags your own Seedelf.MintDraft is now SpendDraft, and TokenOut is now TokenAmount.script-spend.ts holds the shared flow (read, draft → Ogmios → finish, keep, then giveme.my → sign → submit → pending). mint.ts now uses it too, and chunk 10 plugs in.transfer.ts, plus the RPCs transfer-lookup, transfer-build and transfer-submit. PendingTx.kind gains "transfer".transfer-lookup, so the form shows “Found: tag” (or “No Seedelf with that name”) as soon as a whole name is pasted, before Review.credential_utxos query a balance reading makes. Koios never hears the recipient’s token.CopyButton, split out of CopyField), and the e2e test copies a name there and pastes it into the form.extension/tests/fixtures/record-transfer.mjs drafted 5 ₳ and 1 tUSDM to the live Seedelf “This is a test.” from the 12-word phrase’s synthetic UTxOs. It passes the real wallet script under Ogmios.
mint_test 20 (5 new: a fresh copy of the recipient’s register, budgets on a shuffled answer, part of a token from the UTxOs holding it, several recipients including yourself, and the refusals, among them a real torsion point and the identity); the CLI’s offline tests 11; seedelf-wasm native 15 and Node 26; Vitest 100 (+1 live); Playwright 16. The transfer e2e test stops at Send, as the stealth mint’s does.22585835…fc8d9c, from the public 12-word phrase to one of its own Seedelfs (the flagged pay-yourself case). “Everything worked.”
sweep and remove). Both are ScriptSpends with other outputs: sweep pays an address, and remove burns a Seedelf (ScriptSpend::mint with −1). Write docs/plans/chunk-10-*.md first.web-wallet/mint-first).
build::account_mint / AccountMint, which drafts and finalizes like a ScriptSpend.
0/0.change_outputs now takes its “not enough” error.create does, and the transaction names the account anyway.draftAccountMint and finishAccountMint (signed). Move-in’s path checks and per-key signing are now shared helpers.MintService.build(network, label, from); MintSummary.from.extension/tests/fixtures/record-account-mint.mjs records it.mint_test 15 (4 new: collateral choice, token collateral, overlap, errors, and fees against the ledger’s formula); seedelf-wasm native 12 and Node 24; Vitest 91; Playwright 15. The account path runs through Send to “Seedelf created”, since no giveme.my signature is needed.ca0fac00…ade137, the Seedelf TAK2 (5eed0e1f54414b32019dda2589…) with 1.74986 ₳.
web-wallet/transfer branch. Start there.web-wallet/create-seedelf). Plan: plans/chunk-08-create-seedelf.md.
move-in.mjs hasn’t run.seedelf-core/src/build.rs):
ScriptSpend is the shape every Seedelf spend shares. draft() carries placeholder budgets; finalize(budgets) puts in Ogmios’s and settles an even fee. Transfer, sweep and remove should reuse it.Budgets::from_ogmios). Ogmios errors become plain words.mint and mint_from. The CLI’s util mint run() is thin now; its test mocks Ogmios with real purposes (mount_evaluate_mint).seedelf_token_name refuses an output index past 255 (the policy takes one byte).seedelf-koios’s evaluate_transaction passes Ogmios’s 400 answer on.add_witnesses, tx_id and required_signers sign and read a transaction kept as CBOR.draftMint, finishMint and signScriptSpend.signScriptSpend checks giveme.my’s signature before adding it.MultiEraTx decode cost about 540 KB more, so Conway-only decoding is used.mint.ts, collateral.ts (giveme.my) and pending.ts. The pending watch moved out of move-in.ts, and PendingTx has a kind.evaluate.additionalUtxo).extension/tests/fixtures/record-mint.mjs and seedelf-core/tests/fixtures/ogmios/.mint_test 10 (value, minimums, registers, proofs bound to the one-time key, collateral, budgets on a shuffled answer, selection, a draft on a tight balance); seedelf-wasm native 10 and Node 23; Vitest 88 (+1 live); Playwright 14; the CLI’s offline tests (11) are green.
FeeTooSmallUTxO (255,788 supplied, 255,801 expected).
linear_fee used Pallas’s PolicyParams::default(), which is Byron’s 43.946 lovelace a byte, not the protocol’s 44.min_fee_a and min_fee_b from the protocol parameters (ProtocolParameters gained both).settle_fee.9dda2589…28bc8: five key inputs, 25 ₳ into the contract, change to 0/0. Fee 186,979 for 718 bytes. This is chunk 7’s live move-in.939ae7df…aa6ac: the Seedelf TAK1 (5eed0e1f54414b31009dda2589…) with 1.74986 ₳.
LIVE_KOIOS=1) finds both. That phrase is public, so anyone can spend them; the live test now checks their shape, not their count.e2e/live/*.mjs on the private test wallet still haven’t run (it’s unfunded).web-wallet/move-in). Plan: plans/chunk-07-move-in.md.
tx_status every 15 s, then refreshes.0/0.seedelf-core now compiles to WebAssembly. The only blocker was seedelf-koios’s reqwest timeouts, now gated off on wasm32.seedelf-core/src/build.rs holds network-free builders:
deposit_outputs: fresh re-randomized registers, tokens 20 to an output.settle_fee: one throwaway signature per signer, repriced until it covers the signed size.external_sweep: the CLI command is now a thin run() around it, and its offline tests pass unchanged.move_in: never spends a 5-ADA pure-ADA UTxO.ProtocolParameters::from_koios parses a Koios row. The platform CLAUDE.md’s “no build_*” rule is reversed.buildMoveIn checks each UTxO against its role/index, builds, and signs once per payment key. The module is now 1.6 MB (468 KB gzipped); wasm-opt is chunk 11.epochParams, submitTx (never retried) and txStatus.move-in.ts builds, holds the signed tx in session storage until Send, submits exactly it, and watches it.chrome.storage.session.AdaInput: at most 6 decimals (truncated, with a note), nothing above the 45 billion ADA supply, and a note when the amount exceeds the balance. The WASM builder refuses over-supply amounts too.seedelf-wasm native 6 (witnesses verify against the tx hash, and the signers are exactly the inputs’ keys, on the real recorded preprod UTxOs); Node 21; Vitest 79 + 1 live; Playwright 13; the CLI’s offline tests (11) are green.extension/.preprod-test-wallet.txt (gitignored) was never funded.npm run build, then node e2e/live/move-in.mjs 10. It restores the wallet, moves 10 tADA plus any tokens, waits for confirmation, and prints the result.util mint), is the first script spend. Its plan is ready: plans/chunk-08-create-seedelf.md. Start there, and finish chunk 7’s live move-in first.web-wallet/balances).
uqr, MIT, a port of Nayuki’s generator). Balances are read when Home opens, if the last reading is over a minute old, and on Refresh; there’s no background polling. (Since then, while unlocked, a one-minute alarm reads what’s running: swaps, Lovejoin chains and boxes due, a payment that may still go through. See architecture.md and the privacy review’s §6.)extension/src/background/koios.ts, chain.ts and balances.ts, plus the new Home. See architecture.md.
credential_utxos for the contract, and account_addresses plus account_utxos for the Cardano account’s stake key.wallet.withKeys, which also writes the session cache, so a lock can’t interleave. A reading that finishes after a lock is dropped.5eed0e1f, slightly stricter than the CLI’s filter.extension/tests/fixtures/) match what LIVE_KOIOS=1 reads today. The built extension, run against live preprod with no interception, shows the 12-word test phrase’s account (10,350.538725 ₳, 4 addresses used) and contacts only preprod.koios.rest. The receive QR decodes back to the exact address with zxing-cpp and OpenCV.abandon … art phrase’s stake key has a foreign script UTxO on preprod. Anyone can pair a stake key with their own payment part, so the account’s UTxOs count only at derived addresses.0014df10…), which the UI drops.docs/plans/chunk-07-*.md with a “Start here” list, and merge main first.web-wallet/vault-lock).
phrase_to_entropy, entropy_to_phrase and wordlist in seedelf-crypto::derivation. The frozen v1 derivation is untouched.
phraseToEntropy, entropyToPhrase and bip39Wordlist.SeedelfKey.fromEntropy and CardanoAccount.fromEntropy, so unlock never turns the phrase into a JS string.extension/src/background/secret-box/ (Apache-2.0, license and change list in that folder, no EMIP-003). The entropy is sealed under seedelf.vault.
argon2-cffi, cryptography): extension/tests/vectors/secret_box_sbv1.json.extension/src/background/wallet.ts, with the states no-wallet, locked and unlocked.
chrome.storage.session.chrome.alarms plus UI activity pings. The back-off is enforced in the worker and kept in chrome.storage.local.state-changed, so every open page follows a lock.validate-phrase, so restore can show the Rust core’s reason before asking for a password. status also carries retryAfterMs.brand/.
seedelf-wasm test. Node: wasm/tests/entropy.test.mjs.chrome.runtime.sendMessage from one page also reaches other open extension pages. The UI’s listener ignores anything that isn’t state-changed and never replies.page.goto doesn’t remount the app, so open a new page to test #create or #restore.SeedelfKey in wallet.ts. The Home screen is the placeholder to replace.web-wallet/extension-scaffold).
extension/, built with Vite 8 (Rolldown), React 19, TypeScript 7 and an MV3 manifest.
VITE_ENABLE_MAINNET, a strict CSP, and a dev key that pins the ID to jfekiogplaamnceifeehipmomhojngcb.src/shared/rpc.ts) only from the extension’s own pages.?view=tab).dist/ into Chromium, checks the pinned ID and module worker, and checks the popup derives the Lace-matching addresses. It also covers the full tab..github/workflows/web-wallet.yml, which runs on PRs touching seedelf-platform/. It covers Rust fmt/clippy/tests, the WASM build and tests, and the extension typecheck, tests, build and end-to-end run.
Cargo.lock is untracked, so CI resolves dependencies fresh and installs the matching wasm-bindgen-cli.rollupOptions to rolldownOptions.?url imports, so the WASM binary is imported by relative path.npx playwright install chromium.storage permission and replaces the preview screen with create/restore onboarding.web-wallet/cardano-keys).
seedelf-crypto/src/cardano.rs, CardanoAccount.
pallas-wallet, then m/1852'/1815'/account'/role/index.CardanoAccount and a Network enum. The module is now about 590 KB before wasm-opt; shrinking it is a job for chunk 4 or 11.seedelf-crypto/tests/vectors/cardano_account.json (12/15/24 words, accounts 0 and 1, preprod and mainnet) matches @cardano-sdk/key-management 0.29.13’s InMemoryKeyAgent on all 80 values.
0' only, but every function takes the index.24301' (0x5EED). Account 1' could be a real Lace account and would link one-time addresses back to the user.web-wallet/key-derivation).
seedelf-crypto/src/derivation.rs:
parse_phrase: checksum and case/whitespace normalization, with user-facing errors. New phrases are 24 words; restore accepts 12, 15 or 24, like Lace.generate_phrase, bip39_seed, okm_v1, scalar_from_okm, seedelf_key_v1.bip39 comes via pallas-wallet, and HKDF comes from cryptoxide.seedelf-crypto/tests/vectors/seedelf_key_v1.json.
hashlib/hmac, py_ecc for the public values, the Trezor BIP39 vector, and the well-known 12-word seed.derivation_test.rs) and the WASM tests (derivation.test.mjs) both read them.SeedelfKey.fromPhrase(phrase, account), generatePhrase() and validatePhrase(). The module is now about 350 KB, mostly the word list and SHA-512.@scure/bip39 is out of the JS stack.pallas-wallet (already in the tree) has an HD module and bip39. Check it for Icarus master-key and CIP-1852 derivation before reaching for a JS library.web-wallet/wasm-foundation).
seedelf-wasm crate in seedelf-web-wallet/wasm/.
SeedelfKey keeps the scalar inside WASM.Register, rerandomize, isValidRegister, registerToDatum, verifyProof.build.sh produces an ES module of about 260 KB.cargo test -p seedelf-wasm.node --test "seedelf-web-wallet/wasm/tests/*.test.mjs", run after build.sh.seedelf-crypto.wasm-bindgen-cli must match the crate version (0.2.128 today).seedelf-platform/Cargo.lock was gitignored (*.lock) then, so versions could float on a fresh clone. It’s tracked since the launch review (#61), and every build uses it (--locked).seedelf-koios connect_timeout gate moved to chunk 7, where seedelf-core first needs WASM.seedelf-crypto, then add SeedelfKey.fromPhrase().