Chunk 13 made the wallet a full Cardano wallet with Seedelf built in. This chunk is the second pass over the user’s own findings, as chunk 12 was (plans/chunk-12-style-flow.md). Branch web-wallet/style-flow-2, one PR into seedelf-web-wallet.
How it runs: the user tests the built extension and sends findings. Each finding goes in the list below with what was decided. Batches land with side panel and tab screenshots for the user to check before they rebuild.
git fetch origin && git checkout web-wallet/style-flow-2seedelf-platform/seedelf-web-wallet/extension): npm run build && npm test && npm run e2e.e2e/extension.spec.ts in the same commit.tests/words.test.ts checks it.Items 5 to 13 came from a look at what Lace and Eternl have that the wallet didn’t (2026-09-25). The user asked for item 5, and for the rest, except: the dApp connector (its own chunk), a light theme, several accounts, background polling for incoming payments, and NFT images (a better way to fetch them first). Hardware wallets, swaps, buying ADA, voting on proposals and the rest of the README’s Not planned stay out.
| # | Finding | Decision | Status |
|---|---|---|---|
| 1 | The Cardano side can’t pay a Seedelf. Paste one into Send and pay it properly: a move-in, but into someone else’s Seedelf. | Send’s To takes a Seedelf’s whole name as well as an address or $handle. It’s found as Send to a Seedelf finds it, in the contract as the scan has it, and Koios is never asked about its token. Contacts offers both kinds. Core: a Payee::Seedelf with the recipient’s register (the CLI’s fund, from the account) pays like a move-in, but under a fresh re-randomization of that register, and an unsafe one is refused (is_payable). It was built as build::account_fund and folded into item 3’s account_send_many. WebAssembly: buildAccountSend takes the name with the contract UTxO holding it (recipient), checked as a transfer’s is. Your own Seedelf is refused, pointing to Move in, which does the same thing (and a payment to yourself would show as “received” in the Seedelf history). The review shows the tag and short name, and says only its owner can spend it. The privacy note adds that anyone can see the money went into Seedelf, though not whose Seedelf it is. Withdraw, given a Seedelf’s name, now says Send pays Seedelfs. Koios: a pasted name costs one request (what’s new in the contract), and Review one more on top of an address send’s four. |
✅ built; the user checks |
| 2 | “seedelf” is written every which way. There should be one style: always Seedelf, or Seedelf Wallet where that fits. | Seedelf, always; Seedelf Wallet for the app (architecture.md). 55 strings on screen and from the worker, the top bar’s wordmark (it said “seedelf”; the logo says Seedelf), 24 messages from core and WebAssembly (shared with the CLI), the store listing and its image captions, and every web wallet doc. “A Seedelf wallet” became “Seedelf Wallet” where the app is meant. A UTxO row’s spoken name keeps its tag’s case (“Seedelf”, “Locked”). Code keeps lowercase: identifiers, storage keys, test ids, and the frozen derivation strings, which can never change. tests/words.test.ts parses every source file with Vite’s parser and fails on a lowercase one in anything a person reads (JSX text, shown attributes, any string with a space); a probe file checks the check. No requests. |
✅ built; the user checks |
| 3 | Several recipients when sending, on both sides, as Eternl has. | Up to 20 in one transaction on Send (the Cardano account’s), Send to a Seedelf and Withdraw, which stay separate screens (the user’s choice). One recipient looks as it always did; Add recipient puts each in a card with its own To, amount and tokens, × takes one off. Max pays a single recipient (the user’s choice): a second turns it off. A recipient’s token boxes offer only what the others haven’t taken, and “Together that’s X ₳, more than…” stops Review early. The review shows each under “Recipient N”, then the total. Core: account_send_many (AccountPay each; account_send is its one-address case) and sweep_many; build::transfer already paid several. settle now refuses a transaction over 16 KiB in words (MAX_TX_SIZE). WebAssembly: the send, transfer and withdrawal requests take payments (one to 20) and answer per recipient. Privacy: the forms say recipients paid together can be seen to be; privacy.md’s Co-paying. Koios: what one recipient costs, plus each $handle; and a Seedelf’s lookup as it’s pasted now tries the kept contract view first, so a name Home’s reading saw costs nothing (Send to a Seedelf gains this too). The Seedelf history names “first and N more”. |
✅ built; the user checks |
| 4 | The tabs say Seedelf and Cardano, as if they were two chains (Lace’s Cardano and Midnight). Both are Cardano: it’s one wallet with a private side and a public side. | Private and Public, all the way through (the user’s choice of the three offered, and the wording they approved): tabs Private / Public; the private balance and the public account; Make private (move in), Make public (withdraw), and Send on each side (announced as Send privately / Send publicly; the private one’s screen is titled Send); Private / Public UTxOs, Activity and Tokens. Banners: “Payment into your private balance sent” → “Made private”, “Payment from your private balance” → “Made public”, “Private payment” for a transfer. Seedelf stays the technology’s name and the named tokens’ (“Create a Seedelf”, “Seedelf name”); staking’s “withdraw rewards” is Cardano’s term and stays. The manifest summary and the store listing follow (“A Cardano wallet with a private balance built in…”), for the user to resubmit. The code, storage keys and test ids keep the older names, and so do the docs’ internals (flows.md, Names). No requests. | ✅ built; the user checks |
| 5 | The wallet should open in a full tab by default, with a setting to open it in the side panel as Lace does. | A full tab by default; Chrome’s side panel as the choice (Settings, Open Seedelf Wallet in), after Lace 2.4’s view mode (views-extension/src/default-open-mode/: sidePanel or tab, its default the panel). No popup (the user’s go, following Lace): the manifest drops default_popup and gains side_panel (index.html?view=panel), the sidePanel permission and minimum_chrome_version 116. With the tab, the worker’s action.onClicked brings back the wallet’s open tab (runtime.getContexts) or opens one; with the panel, Chrome opens it (setPanelBehavior), applied again at install and start-up. The choice is seedelf.openIn in chrome.storage.local, the browser’s, so removing the wallet keeps it. Switching opens the wallet the new way at once and closes the page it was chosen on, as Lace’s does. The narrow layout is the popup’s, at the panel’s width (up to 520 px); onboarding still runs in a tab. No requests. |
✅ built; the user checks |
| 6 | Hide balances, as Eternl’s eye does. | The eye beside each balance’s label, a setting (hideBalances), so the wallet opens as it was left. Hidden, amounts show as “••••” on Home (balances, tokens, rewards, what’s locked, the value in a currency), Tokens, UTxOs, Activity, Receive and Staking (useAmounts); the forms and reviews still show theirs. No requests. |
✅ built; the user checks |
| 7 | A note on a payment, as Lace’s “Add a note” and Eternl’s message. | On Send from the public account only: one line of at most 64 characters (Lace’s limit), CIP-20’s message (label 674) on the transaction. Core: note.rs, patched in like the staking certificates (Pallas can’t stage metadata), after them and before signing, with the fee paying for its bytes (0.00242 ₳ for “Invoice 42”); account_send_many takes it. Refused: control characters, more than 64 characters; over 64 bytes it’s split into CIP-20’s lines at a space. WebAssembly: note on the send request, and the note as carried on the result. The form counts the characters and says anyone can read it, for good (paying a Seedelf: it could say whose Seedelf this pays); the review shows it. Not offered on the private side, where it would be public words on a private payment, nor on Make private or staking. Checked on preprod without spending (tests/fixtures/probe-note.mjs): each shape decodes on the node. No requests. |
✅ built; the user checks |
| 8 | The auto-lock time as a setting, as Lace’s app lock timeout. | Lock after 1, 5, 15, 30 or 60 minutes without activity (Lace’s choices less “never”), 15 by default, in Settings’ Security. The wallet reads it on every check (lockAfterMs), so a change applies at once. No requests. |
✅ built; the user checks |
| 9 | Your own ADA Handle on Receive, as Lace shows it. | Your ADA Handles on the public Receive, from the account’s tokens (the handle policy’s plain and CIP-68 user tokens, not the reference token), with Copy. Found while building it: the wallet contract lets a UTxO without a register go to anyone, so a handle in the private balance means another wallet paying $name pays the contract, where anyone can take the payment. The Private tab warns when the balance holds one, and Make private and both Sends warn before a handle goes into a Seedelf (HandleWarning). This wallet’s own sends already refuse a handle that points at a script. No requests. |
✅ built; the user checks |
| 10 | A check of the written recovery phrase, as Lace’s recovery phrase verification. | Check recovery phrase in Settings’ Security: type or paste the phrase (the restore boxes), and the worker says only whether it’s this wallet’s (check-phrase), never which words differ, so it tells someone at an unlocked browser nothing more than a whole phrase they have. No password, nothing kept (a match empties the boxes), and it doesn’t count as a wrong password. A phrase that isn’t one gets the Rust core’s reason. No requests. |
✅ built; the user checks |
| 11 | Export Activity, as Eternl’s CSV export (for taxes). | Save as CSV on each Activity: what’s listed, saved on the device: the date (UTC), kind, direction, signed ADA, fee, each token with its sign, who or where, note, pool, vote, deposit and its return, rewards withdrawn, and the transaction. The public side has what’s been read and says to Load more first for older ones (the Koios budget: no export reads the whole history). The private side says the file isn’t encrypted. Someone else’s words (a note, a tag, a token’s or DRep’s name) can’t run as a spreadsheet formula. Entries now keep each token that moved, signed (older Seedelf entries keep only the count). No requests. | ✅ built; the user checks |
| 12 | Staking changes in the public Activity (chunk 13’s follow-up). | From the same tx_info, now with metadata, withdrawals and certificates: Staked (the pool by its ticker when the device has it, the deposit), Delegated voting power (a DRep by its name from the wallet’s list), Withdrew rewards, Stopped staking (the deposit back); a payment that spent the rewards stays Sent, and says how much. Only certificates naming the account’s stake address count. A transaction’s note (CIP-20) shows in its details. Koios: no more requests (each tx_info answer is somewhat bigger). |
✅ built; the user checks |
| 13 | ADA’s value in a currency, as Lace and Eternl show it. | Mainnet only, as in Lace (test ADA has no value; the preprod build asks no one and says so in Settings). CoinGecko’s public simple/price, one request for all eight currencies (USD by default; EUR, GBP, JPY, CAD, AUD, CHF, BRL; or “nothing”, which asks for no prices), kept on the device five minutes, read only when Home opens or is refreshed; a failed read shows the last price for up to an hour. “≈ $1,234.56” under each balance. Only ADA is priced: asking about tokens would say what the wallet holds. CoinGecko learns that someone at this IP uses the wallet, and Settings’ About says the wallet talks to it. api.coingecko.com is a host permission, and in the CSP, only in a mainnet build, so the preprod listing is unchanged. |
✅ built; the user checks |
| 14 | The dropdowns (Show ADA’s value in, Lock after) open white text on white; an option shows only under the pointer. | Every select’s options on an opaque dark (--menu, the suggestion menu’s colour). Chrome draws the open list with the select’s background, and the see-through field surface landed on white. The Settings e2e test checks the options’ background. |
✅ built; the user checks |
A code review of the whole branch, fixed before it went up:
Note::text() now returns the note as typed, where it used to rejoin CIP-20’s lines with a space even where a line was cut between characters.build::account_fund went: the WebAssembly pays Seedelfs through account_send_many with a Payee::Seedelf, and its tests (the fresh register, Max, every unsafe register refused) moved there. The docs say so.showWalletTab, shared with the worker), instead of opening a second one. A start screen (#create, #restore) still opens a new tab.destinationResolver, for Send and Make public), and Activity reads each staking entry’s pool from the device once per page.useAmounts.GoogleChrome/modern-web-guidance, skill chrome-extensions): Manifest V3 with no popup beside setPanelBehavior, real icons at each size, listeners registered at the worker’s top level, return true for async replies, the auto-lock on an alarm, state in chrome.storage, and a justification for each permission. Two fixes came out of it: showWalletTab opens a new tab if the one it found closed in the meantime, and the auto-lock tick no longer leaves an unhandled rejection when the worker can’t start. The store screenshots were stale (no eye beside the balance) and are regenerated.Tests: Rust workspace green (core build_test 16, note_test 4); seedelf-wasm Node 33; Vitest 222 (+2 skipped); Playwright 41.