Privacy
The protocol-level analysis lives in the root README: stealth addresses, re-randomization, and the known de-anonymizing attacks. This doc covers what the web wallet has to do on top of that.
The screens call the Seedelf balance the private balance and the Cardano account the public account; Make private is a move-in and Make public a withdrawal (flows.md, Names). This doc keeps the older names.
The owner’s rule (2026-09-27): the most private by default, with the ability to turn it down where it costs the user. Default never means only: a control turns a choice down where it costs fees, time or lookups. And the wallet never forbids a link the user chooses (2026-10-02): it says what a payment or a connection reveals, and only avoids making a link on the user’s behalf. The privacy-by-default review of 2026-09-27 tested the wallet against that rule, and the owner declined five of its proposals, so they shouldn’t come back as findings: a longer default Lovejoin wait (§3.5: it stays 1 to 6 hours), the currency off by default (§3.8: it stays US dollars), balances hidden by default (§3.9), a switch or a confirmation before money with different histories is spent together (§4.2), and a setting for whether new sites start on a private session or the public account (§4.3: the connect window preselects neither). archive/plans/privacy-review.md is the record; what’s still open is in post-release-roadmap.md. This doc says what the wallet does now.
What is hidden and what isn’t
Hidden, from people reading the chain:
- Who owns a UTxO. Registers are re-randomized, so they can’t be linked back to a user. This relies on ECDDH.
- Who spends it. Spends use a Schnorr proof, a fresh one-time signing key and shared collateral.
Visible:
- Amounts and tokens. Seedelf does not hide or mix value.
- The transaction graph and timing.
- Which normal addresses paid into or received from the contract, such as the Cardano account and one-time accounts.
- Everything the Cardano account does, staking included: its pool, where its vote goes, and its rewards.
- The user’s IP address, as seen by the Cardano server the wallet reads (on mainnet its own data layer, and Koios for a part it can’t answer; on preprod Koios: Network below), by giveme.my for every Seedelf spend, on mainnet by CoinGecko for ADA’s price, by Minswap for a swap, and by Blockfrost’s IPFS gateway for an NFT’s image the user asks to see.
- To the Cardano server and giveme.my, much of what the chain hides (see Network below): every transaction goes to the server from the IP address that reads the Cardano account, and every Seedelf spend goes to giveme.my from it too. Seedelf and Lovejoin hide from people reading the chain, not from the services that carry the transactions.
Rules the wallet enforces
These are not user settings:
- A new one-time signing key for every Seedelf spend.
- The CLI draws it at random (
transfer.rs, sweep.rs, util/mint.rs).
- The web wallet derives it inside WebAssembly from the Seedelf key and a fresh random seed, so it survives a worker restart between review and Send without ever reaching JavaScript. A new seed gives a new key (see architecture.md).
- Seedelf spends take their collateral from the shared giveme.my service, never from a user UTxO. A user’s own collateral would tag every private spend with their address.
- The one exception is a private session’s return merged into its funding change (chunk 16). It puts up the session’s own 5 ₳ collateral, which the same transaction spends. It tags nothing new: the transaction already spends the session’s UTxOs.
- Its proof is still bound to a new one-time key (rule 1), never the session’s key, which a connected site can ask to sign.
- Re-randomization scalars (
d) are toxic waste.
- They are full-size and come from a secure random source.
- They are never stored or logged.
- They exist only inside
seedelf-crypto.
- Registers are only built by
seedelf-crypto, never assembled in TypeScript. This guarantees the same d is applied to both points and that the points are torsion-free. A mistake here locks funds permanently (see the root CLAUDE.md, “Core protocol invariants”).
- A payment to someone’s Seedelf goes under a fresh re-randomization of their register, never the register as found.
- The wallet refuses to pay a register whose points don’t decode, lie outside the prime-order subgroup, or are the identity. A payment to any of those is locked for good, or anyone can take it.
- A Seedelf is linked to whatever pays for it, so the first one is minted before any move-in (see flows.md).
- By default the Cardano account pays (chunk 8b), and the Cardano card says to create a Seedelf before moving money in.
- A stealth mint from the Seedelf balance is the other choice. It hides the payer only when that balance came from other people’s Seedelf payments. For it, the mint service passes only Seedelf UTxOs to WebAssembly, and WebAssembly refuses any it doesn’t own. Its coin selection takes received money first (see Co-spending).
- This rule used to say “stealth mint from the Seedelf balance, never paid by the Cardano account”. That was wrong whenever your own move-in funded the balance: the mint spends that deposit and ties the account, the name and the mint’s change together.
- The Cardano account is never a one-time account. Each one-time account is used for a single session. One-time accounts use a reserved account index (
24301'), never a low index like 1' that a restored Lace wallet may already use: sharing payment keys with a real account would link every one-time address back to the user.
- No analytics or telemetry. The wallet talks to Koios and giveme.my, on mainnet to its own data layer first (Network) and to CoinGecko for ADA’s price unless the currency is set to nothing, to Minswap’s aggregator only when the user swaps, and to Blockfrost’s IPFS gateway only when the user asks to see an NFT’s image, and to nothing else. Every request goes without cookies or a referrer (
credentials: "omit", referrerPolicy: "no-referrer", privacy review §2.14), and none is kept in Chrome’s cache on the disk (cache: "no-store"). The data layer’s requests ask with cache: "default" instead, for a POST, so Chrome reuses its CORS preflight (in memory, up to 2 hours, keyed by the POST’s URL, query included, never its body), and the API answers every request with Cache-Control: no-store, so nothing it says is kept either (1.4.0, the owner’s call). A GET needs no preflight and stays no-store: its address can name an ADA Handle or a DRep ID, and only no-store keeps that off the disk whatever a server says (the release review). The query of a POST to the data layer can carry a block height the wallet last read, and past 1,000 rows an outpoint of the account’s: in memory alone, for up to 2 hours.
- giveme.my and the data layer are ours: Logical Mechanism, who make Seedelf Wallet (and Lovejoin), run them. giveme.my sees the IP address and every Seedelf spend, and the data layer what Koios would (see Network). Koios, CoinGecko, Minswap and Blockfrost are other people’s.
- CoinGecko is the one default that asks a third party for a convenience: the currency is US dollars unless the user changes it (the owner’s call, 2026-09-27; privacy review §3.8). “Nothing” asks no one.
- Lovejoin is a contract, reached through Koios: nothing goes to it as a service. Its makers see each box brought back through giveme.my, as any Seedelf spend.
- It asks the network the user has chosen, and the other only to finish what’s on its way there (the worker’s runs, architecture.md).
- A site is given the public account, or a private session’s one-time account, never the private balance (the dApp connector, chunks 15 and 15c). The Seedelf key and the Seedelfs are never offered over CIP-30. A private session’s funding is public, though, and leads back into the private balance (see A site connected to a private session). The connector is off until the user turns it on, and until then nothing is added to any web page.
Known links
The wallet can’t prevent these, so it should make them visible to the user instead of hiding them.
- Entry: move-in links the Cardano account, and whoever funded it, to the register UTxOs it created. For a restored Lace or Yoroi phrase, the Cardano account is the user’s public identity.
- A move-in on its own doesn’t link to a Seedelf name: from outside it looks the same as paying someone else’s Seedelf.
- Minting: a Seedelf is linked to whatever pays for it.
- Stealth mint from moved-in money: the mint spends the deposit and returns the change in the same transaction. That links the account, the name and the remaining balance.
- Mint paid by the account, then move in: only the account and the name are linked. The balance stays ambiguous. This is the default.
- Stealth mint from received money: hides the payer. That’s the case the stealth mint is for, and what its coin selection takes first.
- Exit: withdrawing to where the money came from re-links the chain. This is the second implicit tracking method in the root README. Withdraw somewhere else, or keep the funds in Seedelf.
- The withdraw form says so. It warns when the destination is any of this wallet’s own Cardano accounts (chunk 18): an address carrying one’s staking key, or whose payment key is one of its own (the first 20 of each chain, and, for the account the wallet is on, any the last balance reading found; privacy review §2.17). Every account the wallet knows is checked, not only the active one — the money is re-linked to that account either way, and the other accounts’ keys are derived on the device, asking no one. That links the account to the Seedelf UTxOs spent, and so to whoever paid them in.
- Max spends up to 20 UTxOs in one transaction, the largest, whatever their histories, which ties them together (see Co-spending). The review says which histories it merges.
- Sending from the Cardano account (chunk 12) is an ordinary Cardano payment: anyone can see it came from the account, and so from whoever funded it. The form says so, and that paying from the Seedelf balance instead avoids the link.
- To someone’s Seedelf (chunk 14), it looks like a move-in from outside: the account paid into the wallet contract, under a fresh re-randomization of the recipient’s register, so nothing on chain says whose Seedelf it is. The recipient can see it came from your account. The form says both.
- Removing a Seedelf: its ADA goes somewhere, and that’s linked to the Seedelf’s name.
- The default follows who paid for it (privacy review §3.2): the Cardano account for a Seedelf the account paid for, which is linked to it already, and the Seedelf balance for a stealth-minted one, whose mint the balance paid.
- The wallet keeps who paid at each mint’s Send, naming the public account its review was built and signed on, even if another window switched accounts before Send (release review), in a sealed per-network record (
mintedBy.<network>), apart from the Seedelf history, which is trimmed. For a Seedelf minted before that, or in another browser, it works it out from what it holds, never asking Koios about the mint: another of its contract UTxOs from the mint’s transaction is a stealth mint’s change; one of the account’s UTxOs or Activity entries from it is an account-paid mint’s. Otherwise nothing is chosen, and Review waits for the user.
- Each side’s note says what it links for this Seedelf, with a warning where it links something new: to the account, a stealth-minted Seedelf ties its name, and the mint’s private inputs and change, to the account; into the Seedelf balance, an account-paid one ties the name to that new UTxO, and to whatever it’s later spent with.
- Unique amounts and timing: depositing 1,234.567 ADA and withdrawing roughly 1,234.4 ADA an hour later is an easy match. The UI should nudge users towards round amounts and not rushing.
- Transfer: the payment can’t be linked to the recipient’s Seedelf. The payer’s side is an ordinary spend, though.
- Its inputs, and the change in the same transaction, trace back through the transaction graph to where that money came from.
- If you moved the money in yourself, the chain leads from your Cardano account to this payment, though not to your Seedelf’s name, nor to the recipient’s.
- Its amount and timing are public. Sending right after a move-in is easy to match by timing, and the form says so.
- Its outputs are shuffled (privacy review §3.7, Lovejoin’s Fisher–Yates over the OS’s random source), so the change isn’t always last. A round payment next to non-round change still shows which output is the payment.
- Paying your own Seedelf is allowed, but it only moves money in a circle.
- Co-paying (chunk 14): paying several recipients in one transaction shows they were paid together, by one payer.
- From the Cardano account that payer is you, in the open. From the Seedelf balance it isn’t, but the addresses a withdrawal pays together are tied to each other. Payments to Seedelfs stay unlinkable to their Seedelfs either way.
- The Send and Withdraw forms say so once there’s a second recipient.
- Co-spending: spending several UTxOs in one transaction suggests they share an owner. Coin selection keeps money with different histories apart where it can (privacy review §2.3):
- Each Seedelf UTxO has a history, worked out on the device from the sealed Seedelf history by the transaction that made it, never by asking Koios: made private from a public account (a move-in — one history per account since chunk 18, see Several public accounts), each payment received (one history each), each box back from Lovejoin (one each), all of a private session’s money (what each funding and top-up left, and its return: one per session, carrying the histories of what paid for each funding, a box or a payment received, so a later review’s note counts those too; independent review L41), or Unknown. A spend’s change keeps its inputs’ histories, merged. Unknown money is one history only while none of the UTxOs a spend could take has a known one; once any has, each transaction’s Unknown money is a history of its own (independent review L40).
- WebAssembly picks (
seedelf-core’s build::Histories): first one UTxO that pays alone, the smallest that does; then UTxOs of one history; and only then several histories, boxes back from Lovejoin last of all and one at a time, in the spend’s order within each (independent review L39). A token send first tries the UTxOs holding its tokens with one UTxO of another history, never a box. When merging would need more than one transaction can spend (about 22 Seedelf inputs, or 16 KiB), it tries other orders (independent review M6): the same without the spend’s order, as it merged before; then as few boxes as pay, none first, then one more at a time, each time with the other UTxOs largest first, token-bearing ones included, then with those last; and at the last the CLI’s own order. So received token UTxOs are spent before a second box, and one too large to merge can be left out for a box. The order depends on the spend: Send and Make public take money you made private first and a box last; a stealth mint takes received money first; a session’s funding takes received money or a single box, and another session’s money last (a top-up takes its own session’s first; for a session brought back by hand, what its fundings, its top-ups and the transactions its key signed left in the private balance count as its own whatever the history says, so after a restore too: 1.5.0’s release review, C36). At most 12 token-bearing UTxOs are tried alone, so a wallet holding many small NFTs doesn’t stall.
- It never refuses what the CLI’s order would pay, and asks nothing (the owner’s call, 2026-09-27; independent review M6): when what it tried without merging doesn’t pay (the change must exist, or the payment is large), it merges, and the review says what’s spent together in a plain note (“This spends 2 boxes back from Lovejoin and money you made private together. Anyone can see they’re one owner’s, which ties them to each other…”). The search is best effort, so the note never says nothing else could have paid (independent review L39). A funding also names money another session left. There’s no switch to turn it off.
- The UTxOs screen tags each one (Back from Lovejoin, Received, Made private, Private session N, Unknown; a session’s UTxO can carry several), so a lock by hand is an informed choice. With more than one public account it says which — Made private (account 2). Locking a UTxO (chunk 12) keeps it out of every spend, Max included.
- After a restore, or with the phrase in another browser, the history this device starts notes what its first reading finds as Unknown, not received: Activity calls it Already in your private balance, and the UTxOs screen tags it Unknown (independent review L38). A UTxO whose block came more than an hour after the history began (a payment that arrived while its first readings failed, say) is received. While none of them has a known history, they’re all one Unknown history, and selection picks as the CLI does (pure ADA, largest first). A new wallet’s first reading finds nothing, so nothing changes for it. The history also starts from when this wallet first saw each payment, and keeps every entry whose money is still in the private balance, filling up to 500 entries with the newest others, so a box still held never turns Unknown (independent review L40).
- What it can’t fix (§5.5): a payment above one box’s worth, from money that is all Lovejoin boxes, has to merge boxes, and largely undoes the mix. With one private UTxO, every session chains to the last; only splitting it or mixing it first (the Lovejoin tile, at its fees) avoids that. A session funded from money you moved in yourself traces back to your public account through its funding, as a transfer does.
- Several public accounts (chunk 18). A phrase can have more than one CIP-1852 account, and the wallet works on one at a time. Some people run several accounts as a form of privacy, so what the wallet must not do is link them.
- What the wallet owes a user here is honesty, not enforcement. Accounts may be linked or not, and some of what the wallet already does links them — a move-in, Make public back to an account, a Seedelf the account paid for. So nothing below forbids a link: selection prefers not to make one, and every screen that could make one says what it would reveal.
- The private balance is one for the whole phrase — the Seedelf key stays on account 0 (the owner, 2026-10-02) — and that costs nothing, because stealth addressing already unlinks the move-ins: two accounts paying the same Seedelf create re-randomized registers
(g^d1, u^d1) and (g^d2, u^d2), which can’t be tied to each other or back to (g, u) without d. Account 0 is, in effect, the nonce.
- What that does not cover is co-spending, and it is the real work. A contract UTxO’s creating transaction is public: the registers hid who the money went to, not where an input came from. One private spend taking a UTxO that came from account 0 together with one from account 1 ties those two accounts to one owner, in the open. So each account’s money made private is its own history class (
public:<n>), selection keeps them apart where a choice that doesn’t merge them pays, and the review names the accounts when it can’t: “It spends money you made private from accounts 1 and 2, so anyone can tie those accounts to each other.”
- Discovery asks Koios about one account at a time. A single
account_addresses request for twenty stake addresses would tell Koios those twenty are one wallet’s. The requests go separately instead — and, said plainly, Koios still sees them from one IP seconds apart, so this is less linkage, not none. It runs on a restore and on demand, never on unlock.
- Reading is one account at a time, too. The wallet never reads two accounts in one go and never shows a total across them, so neither Koios nor the screen puts them together on its own. The user can still link them whenever they choose to. One exception, from the same IP: while a connected site uses a dApp account other than the one on screen, the site’s reads ask Koios about that account and the screens about the other, so Koios sees both from one IP address — every 30 s while a site polls.
- Lovejoin keeps them apart too (release review C01): each mix from the public side records the account that paid it, and Mix again from my public account takes only the active account’s boxes. Another account’s are named on the Lovejoin page, to mix again from that account. A record 1.1.0 kept names none and reads as account 0’s, the one account it had. Pay from my private balance anyway still takes every account’s, after its warning.
- Paying one of your own accounts from another is allowed, and said. It is an ordinary Cardano payment: anyone can see the two accounts paying each other and tell they are one wallet’s. The Send form names the account and says so, and the user decides — the same treatment every other known link gets. It reads the resolved address, so Contacts and ADA Handles read the same.
- Why not refuse it (the owner, 2026-10-02): people do want to move money between their own accounts, and this section’s whole point is that the wallet cannot prevent these links — it makes them visible. Treating accounts as though they were certainly unlinked would also be wrong: several things the wallet already does link them, sometimes deliberately.
- The Send form offers them (Your accounts, beside Contacts), which makes the link one click away — so the note matters more, not less, and the picker carries it too. The alternative was no worse for privacy and much worse to use: switch account, copy the address, switch back, paste. The addresses are derived on the device, so opening the picker asks nobody anything. Make public offers them too, and there the picker’s note says anyone can link the account picked to this money and to whoever made it private. It showed the public Send’s note before, whose “sending from your private balance avoids that” said the opposite there (release review).
- A custom account is reached by number, and adding one asks nobody anything. Checking an account by number asks Koios about that one stake address, the same leak as any discovery; adding it makes no request at all, so a user who starts a fresh custom-numbered account tells Koios nothing until they use it.
- One account is the dApp account, and connected sites always use it (Settings → Sites), whichever account the wallet is working on. So switching accounts shows a site nothing new, and no site is refused for being on the “wrong” one: the choice is made once, on purpose, rather than inferred from wherever the picker happens to be. Changing it shows every connected site the new account — which anyone watching both can see is the same wallet, and Settings says so.
- Removing a Seedelf names the account that paid for it. The mint is public, so it already links the Seedelf to that account; sending the freed ADA to a different one links that account to the same name, and anyone can join the two through it. The record keeps which account paid (
account:<n>, and a bare account from before chunk 18 is account 0’s), and Remove chooses nothing for the user (the owner’s call, 2026-10-06) and warns rather than letting a default do it quietly, under the cards and on the public account’s card too: “Account 1 · Savings paid for this Seedelf. Sending its ADA to Account 2 lets anyone tie the two accounts together…” An account is named by its number and, once renamed, its name, since the picker shows a renamed account by its name alone.
- What is on the disk unsealed: which account is active, one integer (
seedelf.account), beside the network and for the same reason — the keys are derived from it before anything is unlocked. So is which account connected sites use (dappAccount), among the settings, which aren’t sealed. How many accounts the phrase has, and what they are called, are sealed with the other private records.
- Each account’s staking is its own (its own
2/0), so stake is spread across however many accounts are used. That is public, as all staking is.
- The one-time account’s staking part (decided 2026-09-25, chunk 15b): each session’s own stake key,
24301'/2/i beside its payment key 24301'/0/i, which the wallet never registers: a site on the session asking for it is refused (the launch review, #15). An old-style registration needs no signature of the key’s, though, nor does a stake pool’s registration that makes it the pool’s reward account, so a site on another account can put one in a transaction it asks the wallet to sign; the prompt then names the session, and warns that signing ties it to the account the site sees (1.5.0’s cross-area review, X05; 1.5.0’s last review, L1). A registration’s deposit, paid by that account, then stays under the session’s key, which nothing in the wallet deregisters: the session’s return and Disconnect read its UTxOs only (L2).
- No two sessions share a key, so nothing in the address ties one session to another, or to anyone else.
- A SundaeSwap V3 order’s cancel is signed by it (chunk 24): the order names the stake key as its owner. The signature shows the key itself, whose hash the address and the order already show, so it ties nothing new. A V3 order sits under Minswap’s staking part, the same for every sender, not the session’s.
- Before, it was the shared Seedelf staking hash (the CLI’s External Wallet’s,
dapp_address). On preprod that one stake key sat behind 385 UTxOs at 78 addresses, many of them other contracts’, and it tied every user’s sessions together. The user ruled it out. Sessions recorded before keep that address, since that’s where their money is.
- The restore scan asks about the one-time accounts together (O5, chunk 27). After a restore, or from Find leftovers on the dApps page, the wallet asks the Cardano server about the one-time stake keys 20 at a time, then about the used ones’ payment keys together (75 a request) and each one’s transactions, all from one IP address within seconds. So the server can tell those sessions are one wallet’s, which their accounts’ own keys never tie on chain; and the requests come from the IP address that reads the public account. The batches name the sessions to come too (1.5.0’s release review, C06): the walk stops only at a batch of 20 with none used, so it always asks about the accounts after the last used one, which the next sessions take: 20 of them for a phrase that never used one, 20 to 39 for one that did (the rest of the last batch with a hit, and the next 20). Once those sessions’ fundings put their stake keys on chain, the server can tie them to the scan’s requests, from whatever IP address they’re opened: launch review #22’s tie, made once a scan. It stays because a scan that stops at a gap has to ask past the last used index, and each of those sessions goes through the same server, from the IP address that reads the public account, anyway (A new session’s index, below). On mainnet that server is our own, which logs neither the address nor what’s asked; with it down, with Read Cardano through Koios only on (which Remove wallet and Forgot password keep, so a restore after them asks Koios alone: C08), or on preprod, it’s Koios. That’s why it runs only at a restore (on the network restored on) or when asked, never as the wallet opens, and the button’s privacy note says so before it’s pressed. Each session’s funding already ties it to the private UTxOs that paid it, on chain: the scan adds the server’s view of them all together, nothing on chain. Found sessions come back by hand, each in its own transaction, as Bring everything back’s do. One whose order waits with no UTxO of ADA alone of 5 ₳ or more at its account puts 5 ₳ in from the private balance first, for the cancel’s collateral (chunk 32). One whose return would go through Lovejoin, but whose account holds no UTxO of exactly 5 ₳ of ADA alone for the mixes to put up, may put 5 ₳ in for them, from a button beside Bring it back (C53). Either payment takes the session’s own private money first where some is left, which ties nothing new (C36); otherwise it links the private UTxOs it spends to the session’s account, as its funding did, and its review says so.
- Crowd size: privacy grows with the number of honest users (the flood-attack section of the root README). With few users, timing and amounts carry most of the risk. The wallet should say that plainly and not overpromise.
- Network: the Cardano server and giveme.my see the user’s IP address.
- On mainnet the server is the wallet’s own data layer first (chunk 26, since 1.4.0):
mainnet.seedelf.logicalmechanism.io, Logical Mechanism’s, on a DigitalOcean droplet in New York. It reads home’s node, db-sync and Kupo through WireGuard, so home never sees a user. Each part (the public routes, the private index, submits) goes to Koios on its own for 5 minutes when it can’t answer; a request too large for it (a credential or an address with more than 20,000 unspent outputs, transactions gathered from more than 200,000 outputs, or a page of transactions more than 10,000 in) goes to Koios, that request alone, with no hold. Settings → Network’s Read Cardano through Koios only turns it off, and Remove wallet and Forgot password keep it, as they keep the network (1.5.0’s release review, C08); preprod reads Koios alone. So on mainnet, “Koios” in the rest of this section means whichever of the two answered. What’s different about ours:
- The public side and submits tell it what they told Koios: the stake address, the transactions sent, from the IP address. That’s now Logical Mechanism, who also run giveme.my, so one operator sees a private spend’s collateral request and its submit.
- The private side tells it less. The contract comes as a snapshot at a restore and as what changed since a sealed cursor after, the same answer for everyone at that cursor; Seedelf names as one shared list; Lovejoin’s pool with what made every box (
made_by), so the wallet no longer asks about its own boxes’ making while ours answers; and while the wallet holds the private index on Koios, it asks Koios alone about them, never ours (koiosAlone, 1.4.1: before, ours could be asked while its public part answered). It learns roughly when the wallet last read (the cursor), and while a private transaction is watched on the feed, that it’s polled every 5 s. It never learns a UTxO, a register, a box or an owner from those reads.
- What it keeps: Caddy has no access log, and neither Caddy’s own log nor the API’s has a client address or a path (the runbook’s §5, checked 2026-10-09). The per-IP buckets live in memory alone. That’s a policy and an open-source audit trail, not a proof.
- A submitted transaction’s first hop is Logical Mechanism’s stake pool relay, whose registration publishes its IP.
- Its name is on the user’s network: every reading’s DNS lookup and TLS handshake name
mainnet.seedelf.logicalmechanism.io, so an internet provider, a Wi-Fi’s owner or a DNS resolver can tell the device runs Seedelf Wallet on mainnet, and when. Koios’s name said only “some Cardano tool”; giveme.my’s already said Seedelf, but only at a private spend (26c’s decision table weighed this against an IP address).
- The screens say “the server” where they used to name Koios (the owner, 2026-10-09): on mainnet it’s usually ours, on preprod Koios. Settings → Network says on mainnet which answers first, and the privacy policy names both. Koios has no onion service, and whether it takes Tor exits is unchecked, so the wallet doesn’t suggest Tor. A VPN helps with some of it; see the root README’s IP-tracking section.
- Sending is different from reading (privacy review §5.1). Koios sends every transaction the wallet makes: Seedelf spends, session steps, Lovejoin chains and each box brought back, a site’s submits, and a status check (
tx_status) for each. A submit names the UTxOs it spends, and it comes from the IP address that reads your public account. So Koios, and giveme.my for private spends, can tie your private payments, your sessions and your Lovejoin boxes to your public account, both ends of a Lovejoin trip included. A VPN hides who you are, not that the requests come from one place. Only a Koios you run yourself would take Koios out, and the wallet can’t use one yet (§4.7). The timing changes this round (nothing at the unlock, the private side read alone after a private spend) help only on an IP address others share.
- giveme.my sees every private transaction (§5.2). Shared collateral means it must see each one to check and sign it: every Seedelf spend, a session’s funding and each box brought back from Lovejoin, with the IP address, before it’s on chain. From one IP it can group them as one person’s, including a session’s funding with its Lovejoin boxes coming back. Lovejoin and the random waits hide your boxes from people watching the chain, not from the services that carry them. Its logs leave out raw IP addresses and, on success, transaction hashes; its rate limiter keeps IP addresses briefly. Switching VPN server between a mix and its boxes’ return helps. It stays the one collateral provider (
constants.rs): your own would put your UTxO in every private spend. Every review that asks it says so where it’s read: who runs it, that it lends 5 ₳ of its own and charges nothing for it (its fixed UTxO is the collateral, the return goes back to it, and nothing in the transaction pays it), and that it sees the transaction with the IP address (blind test §9.5); its refusals name it rather than guessing at the user’s money.
- No Koios API key (decided 2026-09-25): the wallet uses Koios’s public tier, so there’s no key that names who you are. The requests themselves name the public account’s stake address. A key would tie every request to whoever registered it.
- A balance reading asks Koios about the Cardano account and the whole wallet contract at the same moment (between full reads, the part of it after the last block seen). Koios can tell that the account’s owner uses Seedelf, though not, from the reading, which contract UTxOs are theirs: the ownership check runs in the extension, on every row. That lasts until one is spent: the spend names it (above).
- When the wallet reads: the balance only when Home opens (at most once a minute) or on Refresh. The public account’s Activity only while Activity is open (
credential_txs by the account’s payment keys, which the balance reading already sent, one request for every 75, and one tx_info for each page of 20; a Load more that finds only transactions listed already reads on in the same press, up to ten pages, at most one more request a group for each); the private Activity asks no one, since it’s kept on the device. While unlocked, a one-minute alarm reads only what’s running: a swap’s account and its orders, from Koios and never Minswap (where a Splash order was filled in part, also the transactions that left the rest at its address, tx_info, each asked once), a Lovejoin chain being sent, a box that’s due, a payment that may still go through (looked for each minute, sent again every two minutes), and a box brought back that may have gone through (looked for each minute, for up to two hours). At unlock, Lovejoin’s pool is read once on each network where the wallet has something open there. A pool read that finds more boxes than the wallet has due times for (after a restore, say) also asks Koios what made the boxes no record accounts for (tx_info), each transaction once, or again at each read while Koios hasn’t answered; and taking a box of unknown origin asks it first (see Lovejoin, independent review M14). While a payment is on its way, Home asks Koios for its status every 15 s. While a mix from the public account stopped at a transaction that may have gone through is unsettled, it’s looked for (tx_status, then utxo_info of what it spends) at most every two minutes on each network: by the one-minute alarm, with no page open and at the unlock too (final review F2), or by Home (every 20 s while it’s open), the UTxOs page and the Lovejoin page on the network shown; its Review and Send look each time (independent review L5). After the same phrase is restored, one Remove wallet kept is looked for the same way (final review F1). Locking stops all of it; so a VPN must stay on for as long as the wallet is unlocked with something running.
- A private spend landing reads only the private side again (privacy review §2.9): the contract from the last block seen, not the public account, so Koios doesn’t see the account read in the second the private transaction lands. So do a box brought back from Lovejoin, a session’s return and a private session’s site sending a transaction; a swap, a cancel or a chain’s mix reads neither again (independent review M8). What touches the account (a move-in, a public send, a Make public or a removal to it, staking, a mix from the public account, a public site’s transaction) and Refresh still read both.
- Hiding the balances (chunk 14) only changes what’s drawn: the wallet reads the same and asks no one anything more. It hides how many Lovejoin boxes the wallet holds too, since every box is 10 ₳.
- Reviewing a private spend tells Koios nothing new (since the crypto review, 2026-09-25): its scripts are measured in the wallet. Before, a draft went to Koios’s Ogmios, and its proofs said which contract UTxOs were yours, even for a review never sent. Only Send submits it. Two reviews still go to Ogmios (privacy review §3.10, open): an account-paid mint’s draft, which shows Koios the tag and the account’s inputs, and a Lovejoin chain’s first mix with its unsent deposit.
- Token names and logos come from a list inside the extension (
src/tokens/, refreshed at each release). Asking Koios about the tokens in the Seedelf balance would tell it which contract UTxOs are yours, so the wallet never does on its own. The one exception is the user’s: Show image on an NFT asks Koios about that one token (see An NFT’s image below), and says so first.
- Finding a recipient uses the contract as the balance reading sees it, and picks the Seedelf’s UTxO in the extension. The wallet never asks Koios about the recipient’s token (
asset_utxos and the like): that would tell Koios exactly who is being paid.
- An ADA Handle can’t be found that way: withdrawing or sending to
$name asks Koios who holds that handle (asset_nft_address), so Koios learns it. The transaction names the address anyway once it’s submitted. Pasting the address instead asks Koios nothing.
- Cardanoscan links open in the browser’s own profile, and only when clicked: Cardanoscan then sees the IP address, its own cookies and what was opened, and the browser keeps it in its history (Google’s too, with History sync). On a private transaction (the private Activity, Home’s banner for a private kind or a stealth mint, a swap’s steps, a session’s funding, a site session’s account) the link says so: “Opening this tells Cardanoscan and your browser history that this transaction is yours” (privacy review §3.4).
- Settings’ links (the source code, the privacy policy, Report a translation error) open GitHub in the browser’s own profile, and only when clicked: GitHub then sees the IP address and its own cookies, and an issue filed there is public, under the GitHub account that files it.
- On this device: which contract UTxOs are the user’s is kept in memory and
chrome.storage.session, whose copy is wiped on lock, and since 1.4.0, on mainnet, sealed with the vault in the contract.mainnet record (chrome.storage.local, with the private index’s cursor), so an unlock asks only what changed. Never on disk unencrypted.
- Contacts (who the user pays), the Seedelf history, and the UTxOs the user locked (with the collateral chosen, and since chunk 31 the transactions that set it by payment, for Activity to name) are kept on disk, but never unencrypted (decided in chunk 12): sealed under a key derived from the phrase, unreadable while locked, deleted with the wallet. Saving or checking a contact, or locking a UTxO, asks no one anything.
- A payment being sent is sealed on disk the same way from just before it goes to Koios (independent review M1): the signed transaction, the UTxOs it spends and what it pays. Once Koios takes it, or refuses it outright, the record is deleted. One Koios didn’t answer may still go through, and its record stays until it lands or can’t any more, then is deleted. A lock, a closed browser or a stopped worker then can’t let a second payment go out beside it (final review money-submit-4). A box brought back from Lovejoin is sealed the same way before it’s sent, in
lovejoin.<network>.
- Sealing hides what a record says, not that it exists (privacy review §3.13). Each record’s name is readable (
lovejoin.mainnet there means Lovejoin was used on mainnet), though its size no longer counts boxes or entries: each is padded to 1 KiB, or the next power of two, before sealing. A vault made since that review keeps no creation time (one made before keeps it until the next password change), and ADA’s price and when it was read stay in session storage.
- The language (chunk 19) is kept unsealed (
seedelf.language), beside the network and the account, because the screens before the unlock are shown in it: anyone reading the profile’s storage sees which language the wallet is in. Every language ships inside the extension, so choosing one asks no one anything.
- Remove wallet deletes the vault, every sealed record but two (below), the settings, the pool list, the governance actions and any price kept on disk; so does Forgot password. Where the wallet opens, which network it’s on, Read Cardano through Koios only and the language stay: they’re the browser’s, and the restore after it asks only the server that switch chose (1.5.0’s release review, C08: the switch is written back after the reset when it’s on, every other setting at its default). So does which public account it was on (
seedelf.account), until a wallet is next made or restored here, which starts on account 0. It isn’t a secure erase: the browser’s storage may keep old copies on the disk.
- It first lists what’s still open on each network (independent review M5): a payment that may still go through, private sessions still open or with something left behind at their accounts (a restore’s scan finds what those hold, chunk 27, but not what each was for, nor what a site’s session left open at the site, which Remove wallet’s note says to close there first: 1.5.0’s release review, C17), a Lovejoin chain being sent, and a mix from the public account stopped at a transaction that may have gone through (final review F1). Then it needs Remove it anyway, leaving that behind as well as the typed confirmation.
- It keeps a payment that may still go through (
maybeSent.<network>, independent review M2), and so does Forgot password: sealed under the phrase’s key, the record stays until a wallet is next made or restored in this browser. Restoring the same phrase watches it again, and holds new payments here back until it settles; any other phrase’s wallet deletes it. Nothing watches it in between, and its name shows which network it’s on.
- It keeps a mix from the public account that may have gone through (
lovejoinMaybe.<network>, final review F1): what that transaction spends and its id, sealed the same way, until a wallet is next made or restored here. Restoring the same phrase looks for it before another mix from the account is built, and deletes it once settled; any other phrase’s wallet deletes it. Only Remove wallet, unlocked, writes it: Forgot password can’t read the Lovejoin record, so it adds none.
- Two times stay in memory until the browser closes, through a lock and Remove wallet too: when the wallet last sent something, and when the browser’s session began, and nothing of what was sent (
seedelf.sends, for Lovejoin’s quiet rule, independent review M10).
- Nothing typed is spell-checked (privacy review §2.19): with Chrome’s enhanced spell check on, what’s typed goes to Google, a contact’s name among it.
- Chrome’s Translate is left on, and disclosed (the owner’s call, 2026-10-06, release review C22): in a language the wallet doesn’t speak, Chrome may offer to translate its page, or translate it unasked after Always translate, and that sends the page’s text to Google, balances, addresses and Seedelf names among it. Using the wallet in English, Spanish or Japanese avoids it. The privacy policy says so.
- Copying an address, an ID, a Seedelf’s name or a transaction’s raw bytes (its view’s Raw CBOR tab, chunk 17) puts it where the wallet can’t reach: the system’s clipboard history, and other devices where the clipboard syncs. The wallet doesn’t clear the clipboard on a timer, which wouldn’t reach those, and could wipe something copied since. The recovery phrase can’t be copied. Pasting a private transaction’s bytes into a site, an online decoder say, tells that site which contract UTxOs are yours, even for a review never sent.
- The UTxOs screen lists the Seedelf UTxOs by outpoint, from the last reading, each tagged with where it came from. It says that looking one up on an explorer tells that site which UTxO you care about.
- What the wallet can’t prevent (§5.10): the Chrome Web Store and Chrome know the extension is installed, and Chrome Sync, if it syncs extensions, installs it in the user’s other signed-in browsers (never the wallet itself). While the wallet is unlocked, anyone at the browser sees its balances and history, and someone who knows Chrome’s developer tools can read the unlocked session’s storage, keys included (crypto review, Not fixed #1). The system may write memory to disk.
- The Cardano account’s collateral (chunk 12) is only ever put up by what the account signs anyway (an account-paid mint). Seedelf spends never use it: giveme.my lends its own (rule 2), so no UTxO of the user’s tags a private spend. Setting one by payment is a 5 ₳ payment from the account to itself, in the open.
- Staking and voting (chunk 13) are the Cardano account’s, and public, as in any wallet: the pool, the vote delegation, the rewards and every withdrawal name the account’s stake key. The Staking page, the vote page and each review say so.
- Seedelf money can’t be staked. It has no staking part, so it earns nothing while it’s in Seedelf, and no stake key links a user’s Seedelfs. The Staking page says so.
- Rewards spent along with a payment (on by default, a Settings switch) add a withdrawal to a send, a move-in or an account-paid mint. It names the stake key, which the account’s base addresses carry anyway, so it links nothing new. A move-in with rewards moves them into Seedelf with the rest.
- What Koios learns: every balance reading asks for the account’s
account_info, and its pool’s pool_info once a session. Rewards history (chunk 30) asks for the stake key’s account_reward_history each time it opens, and only then: the same key every reading names, so it tells the server nothing the chain doesn’t, only that the history was looked at. Nothing of it is kept, and its pools are named from the device, never asked about. The pool list is the same for everyone and kept on the device for a day. Searching DReps asks no one: the list of named DReps ships with the wallet. Picking one reads it from Koios, which then knows which one you’re considering, as delegating to it will tell everyone.
- Nothing from anywhere else: a DRep’s metadata is read through Koios, its name only. Its image, which could be on any site, is never fetched.
- The account as its own DRep (chunk 21) is public, as staking is, and the screens and every review say so. The DRep key is CIP-105’s
3/0 under the account, the one Lace derives, so the same phrase is the same DRep in any wallet.
- The DRep is the account. Its registration, its updates and every vote are paid for from the account’s UTxOs, so the chain ties the DRep, and how it votes, to the account in the open. Delegating the account’s own vote to it (on by default when registering, a switch) ties the stake key to it too, which the fee’s inputs had done already. Every vote is public and permanent: one replaced before voting closes stays on chain too.
- A profile is the user’s choice, and off by default. None is needed to vote. With one, the wallet writes the CIP-119 file and its hash and the user publishes it; the wallet never fetches it, so no new host is reached. Koios, explorers and tools like GovTool will, and anyone can read it. “Ask DRep directories not to list it” (CIP-119’s
doNotList) is on by default: the profile still reads wherever the DRep is looked up.
- Private money carries no voting power. It has no stake key, so what’s made private stops counting toward the DRep’s, and the DRep card says so.
- What Koios learns: opening Staking asks
drep_info about the account’s DRep ID, and epoch_params for what registering costs, or drep_metadata for whether a profile matched its hash. Asking about an ID nobody has registered tells Koios nothing the chain will: once it’s registered, the chain ties it to the account anyway. The governance actions are the same for everyone and kept on the device for an hour (proposal_list); a DRep’s own votes on them are one vote_list. Their titles and summaries are Koios’s reading of each action’s anchor: the wallet fetches no anchor, and shows a hash mismatch Koios found as a warning. Read its full text is a link the user opens in a tab, like the explorer’s (chunk 23’s second review), for an IPFS text only (ipfs://, or another gateway’s address for a CID): through the IPFS gateway Blockfrost runs, the one an NFT’s image comes through, and the line under it says Blockfrost sees the IP address ask for it, and that the browser keeps it in its history. Any other address is a host the proposer chose: it’s only offered to copy, as an NFT’s image on another server is.
- A note on a public send (chunk 14) is CIP-20’s message on the transaction: anyone can read it, for good. The form says so, and when a recipient is a Seedelf, that the note could say whose Seedelf is paid (the payment itself doesn’t). There’s no note on the private side’s flows: it would be public words on a private payment. Notes from others show in the public Activity as text, and Save as CSV never lets one run as a spreadsheet formula.
- ADA’s price (chunk 14, mainnet only) comes from CoinGecko’s public API: one request for every currency, when Home or the swap form opens, at most every five minutes, none with the currency set to nothing. The currency is US dollars by default (rule 7). It says nothing about the wallet: CoinGecko learns only that someone at this IP address uses it. Only ADA is priced; asking about the tokens held would say what the wallet holds.
- An NFT’s image (chunk 20) is shown only when the user asks, one NFT at a time, from its details: nothing is fetched for a wallet that merely holds NFTs, or for opening Tokens. The owner’s design (2026-10-02): click to show, fetched by this browser, with no image host or proxy of ours.
- A click costs two requests. Koios’s
asset_info for that token’s metadata (CIP-25’s and CIP-68’s, two columns), and, when the image is on IPFS, one fetch from Blockfrost’s gateway (ipfs.blockfrost.dev, the one Lace uses, and the only one of about twenty public gateways tried on 2026-10-04 that still served files). Each sees this IP address ask about this NFT. An image written on chain in the metadata needs the Koios request alone.
- On the private side it’s a real link, and the details say so before the click: the chain shows which contract UTxO holds the NFT, so Koios or the gateway, either alone, could tie this IP address to that part of the private balance. On the public side Koios already reads the account; the gateway is the new party.
- Only IPFS is fetched. An image on any other server is given as its address, to copy, never opened: anyone can send the wallet an NFT, and one whose image sits on a server its sender chose would tell that server the IP address of whoever looks. A gateway’s address in the metadata (
https://ipfs.io/ipfs/…) is the same file under its CID, so it comes from Blockfrost’s gateway instead.
- Chrome’s grant comes first. The gateway sends no CORS headers, so the worker reads it through Chrome’s access to that one host, which the first Show image asks Chrome for: a part of the optional access to sites the dApp connector declares, so installing asks for nothing new. When the access given for connecting sites (
https://*/*, never handed back) covers it already, Chrome answers with no dialog, and the details leave out that it asks. Turned down, nothing is asked of anyone, Koios included. Without the grant a request would still reach the gateway and only its answer would be blocked, which is why the worker checks before it fetches.
- Nothing is kept on the disk. The fetch goes without cookies or a referrer, and with
cache: "no-store", so Chrome’s cache never records which NFTs this wallet looked at. The image comes to the page as data and stays in that window’s memory until the wallet locks. A Seedelf’s own token is never asked about: it has no image, and asking would tie this IP address to that Seedelf.
- An ADA Handle in the private balance (chunk 14) isn’t private and isn’t safe: whoever pays
$name from another wallet pays the address holding it, the Seedelf contract, with no register, and the contract lets such a UTxO go to anyone. The wallet warns before a handle goes into a Seedelf and while one sits in the private balance.
- Connecting a site (chunk 15, 15c): the connect window chooses nothing for the user (privacy review §3.3). It says what each option costs, the public account (the site sees its addresses, balance and UTxOs, and keeps what it saw) or a private session (its fees, its 5 ₳ of collateral and a minute’s wait), and Connect waits for a choice. There’s no setting for which one new sites start on.
- On the public account the site sees its addresses, its balance and its UTxOs, and everything the user signs for it. That’s what any wallet’s CIP-30 gives. It can recognize this browser later, even when the user connects it to a private session then, and the window says so.
- The site learns nothing about Seedelf from the connector: no Seedelf UTxO, register or key is offered, and a transaction it asks to sign is read against its own account only. For the prompt, the worker also matches what it pays against the wallet’s other accounts, asking no one, and never refuses for it (below).
- Nor about the wallet’s other accounts (privacy review §2.1, §2.2): a signature is checked only against its own account’s Lovejoin chain, and among the wallet’s recent sends a site finds only the outputs that pay its own account (so it can build on them before they land). Anything else it asks about goes through its lookup budget and Koios, as a stranger’s does, and gets the stranger’s answer.
- A payment into Seedelf Wallet’s contract in a site’s transaction is shown: under a register (a Seedelf payment, whose owner stays hidden, from the account in the open), or without one (anyone can take it, and the window warns).
- Locked or not: a connected site’s
isEnabled() says only that it’s connected, whether the wallet is locked or not (privacy review §2.11), as long as the worker has read the connected sites since it started: one restarted while locked says false until the unlock. While it’s locked, a site’s reads are refused at once, in the words a stranger hears, and open no window; enable(), signatures and submits open the unlock window, which names the sites waiting. Once that window is closed, the sites that were waiting hear the words a site that isn’t connected hears for a while (10 s, then longer for each close in a row; chunk 23’s second review, CW-3), never that the wallet is locked (independent review L36). A connected site can still tell locked from unlocked by whether its reads are answered, and a call the wallet locks during can still fail, saying only that the wallet couldn’t answer.
- Governance (CIP-95, chunk 21b) is asked for by the site and agreed to by the user, never given otherwise: the connect window (or, for a site connected already, a question of its own) says what it lets the site do before the user agrees. On the connect window it’s a switch, off by default: connecting a site that asks for it still hands over nothing of the DRep key unless the user turns it on. Declined, it isn’t asked again until the site connects anew, and the site stays connected. With it, the site learns the dApp account’s DRep public key and whether its stake key is registered (one
account_info), and can ask the DRep key to sign: the account’s own DRep certificates, its votes, and messages as the DRep. Each one is shown first, and its privacy note says the DRep and every vote are public and tied to the account. A site without it is told nothing of the DRep key: WebAssembly treats that key as anyone’s for it, so a transaction naming it reads as needing someone else’s signature, and a message for it as no key of the account’s. A private session has no DRep, so a site on one gets no governance, and the window says so. Settings’ list of connected sites shows which have it; disconnecting takes it away.
- Which sites are connected is a sealed private record, like Contacts, with each page’s title as it asked to connect, which the lists show after the address (blind test E03). The site’s origin comes from Chrome, not the page. Disconnecting a site means it has to ask again before it sees anything more; it keeps what it already saw, and an open page of it may still show what it read until it’s reloaded, which the wallet says, since CIP-30 has no way to tell the page. The sites the user declined are kept in session storage, which is in memory, gone when the browser closes and kept from content scripts: their origins, waits and counts, never a page’s title. A lock keeps them, and nothing clears one when its wait ends: it stays until the browser closes, or until the list is next written (another decline, say) 10 minutes or more after its wait. They’re shown only while still waiting, and only while the wallet is unlocked.
- What Koios learns: reading the account for a site is the same query as a balance reading (at most every 30 s). Signing a site’s transaction that spends UTxOs the account doesn’t hold asks Koios about those (
utxo_info); they’re the site’s, and the transaction names them anyway.
- Turning it on asks Chrome to let the wallet onto every https site, which is what adds
window.cardano.seedelf. The wallet doesn’t read or change the pages beyond that one entry. A site hears the wallet’s refusals and errors in English whatever language it’s set to, so a site doesn’t learn its language (the release review). Pages already open get it as the switch is turned on, not at their next load (blind test §9.2, T15), so a page left open could tell roughly when that was; the switch says so, before it’s turned on and after. A page that has the entry already, an earlier version’s say, is left alone. But every https site you open, and the scripts on it, can then see that you use Seedelf Wallet, even one you never connect (not your addresses or balance until you connect it), as with any CIP-30 wallet (privacy review §2.20, §5.9). Settings says so. With the connector off, the default, no site can tell the wallet is installed.
- A site sees the browser (§5.7): no CIP-30 wallet can stop a site’s own records, its IP address, cookies, logins, the browser’s fingerprint, and the scripts in its top frame, or a tracker it shares with a site that saw the public account. A separate Chrome profile keeps cookies apart, and a VPN hides the IP address. Neither does both alone.
- Restoring the phrase in another profile skips the sessions’ accounts already used: the wallet asks the chain about a session’s account at Review, and again just before its funding is sent. Two profiles funding sessions at about the same time, before either lands, can still share one (independent review M13).
- A private swap (chunk 15, a private session): a one-time account (
24301'/0/i) is funded from the private balance, Minswap’s aggregator builds the swap for it, and everything comes back into the private balance.
- What links, on chain: the funding spend’s private UTxOs and change to the one-time account, as Make public does; the account to the order and the proceeds; the return to the private UTxO the funding left, as Make private does. Anyone can follow the whole path. The public account isn’t in any of its transactions, but money you made private yourself leads on to it through the funding (see Co-spending).
- The funding’s shape marks it (§5.6): a spend of the Seedelf contract with giveme.my’s collateral, paying an amount and exactly 5 ₳ to one new address, whose stake key the wallet never registers. So anyone watching can tell a Seedelf Wallet session as soon as it lands, before its account places an order or connects; everything after links to it anyway. A session hides among Seedelf’s users, not all Cardano wallets. The 5 ₳ stays exact: it matches common collateral, where a random amount would mark Seedelf alone.
- The return merges into the funding’s change (chunk 16) while that’s still in the private balance, so the session leaves one private UTxO tied to it, not two. When it’s been spent, the return makes new ones, which are then tied to the session too.
- The amounts and the times tie the two ends together, as the path does anyway. The account’s stake key is its own, used once, so it ties the session to nothing else.
- A swap against Danogo’s pools (chunk 24) has no order: the account swaps with the pools in one transaction, which carries Minswap’s own collateral, signed by Minswap, beside the account’s signature. That collateral is Minswap’s and in many people’s swaps, so it ties the account to Minswap’s aggregator, as its note on the transaction already does, and to nothing of the user’s.
- Minswap sees the account’s address, the tokens and amounts, what’s searched for in its token list, and the IP address. Its note on the order (CIP-20 metadata, which anyone can read) names the account’s address too. Its IP address lets it group every swap made from that address, those running together and those after, and tie them to anything else done at Minswap from it (its own site with your public account, say); a VPN hides the address (§5.8). The token picker searches the wallet’s own list on the device first, and asks Minswap only when nothing there matches, or on Search Minswap. Half and Max are worked out from what the private balance holds, so they round down to a whole ₳ or token unit (what’s left stays in the private balance), and the form says they tell Minswap roughly how much it holds (privacy review §2.13).
- Koios is asked about every open session’s account together (one request, only when the Swaps screen reads the chain), so it can tie them to each other and to the IP address. A swap’s orders are read from it too, never from Minswap: the orders themselves, and, for a Splash order filled in part, what’s at its address and the transactions that put it there (
tx_info, each asked once). Placing an order also asks it for the protocol parameters and, the first time the worker places one through a DEX, for that DEX’s order script (its reference UTxO, the same for everyone), so the wallet can run each order’s cancel before it signs (1.3.0’s release review, C05). Neither names the account, and Koios is sent the swap itself moments later.
- A new session’s index is checked with Koios first (
account_addresses), in case a restore or another browser used it: that one stake address alone, which its funding puts on chain anyway. Only when it was used does the wallet ask about the next 20 together, which tells Koios those are one wallet’s, the used ones and those to come (the launch review, #22). After a restore, or Find leftovers, the restore scan’s batches have named the next ones already (The restore scan asks about the one-time accounts together, above; 1.5.0’s release review, C06). Reviews that aren’t sent ask about the same index again, so Koios can tie them to the session that’s sent at it, but to no other. Send asks once more (independent review M13): about that stake address, and, if it shows nothing, the account’s payment key (credential_utxos), both of which its funding puts on chain anyway. Neither sees a mempool, so two browsers with the phrase funding sessions at about the same time can still share an account.
- On this device: the list of sessions (their indexes, stages and transactions, and what those paid each account) is a sealed private record, like Contacts. It also says, per network, whether a restore’s scan has finished there (
unscanned, 1.5.0’s release review C19), so right after a restore both networks’ records exist, whatever was used, and their readable names (sessions.mainnet, sessions.preprod) show that the wallet was restored. A session the restore scan found that ends with nothing sent for it from here and nothing left at its account (another browser emptied it, say) leaves the list, its record with it (C02).
- Each account is used for one session, then never again (rule 6), but for that race between two browsers. Its index comes from the phrase in order, so the restore scan finds what one-time accounts still hold after a restore, or in another browser from Find leftovers (chunk 27; The restore scan asks about the one-time accounts together, above; keys-and-accounts.md). It runs by itself at a restore, which the restore’s screens don’t say (the policy does; in the wallet only notes afterwards imply it: Home’s when the scan found sessions or didn’t finish, and the dApps page’s After a restore while a network’s look hasn’t finished, the other network’s included; once it has looked, nothing says it did); whether it should is the owner’s question (1.5.0’s release review, C07). Remove wallet and Forgot password say the phrase brings back what private sessions’ accounts hold, not what a site’s session left open at the site, like a listing or an order the wallet can’t cancel (C17).
- A site connected to a private session (chunk 15c, private CIP-30): the same one-time account as a swap’s, funded from the private balance in the connector’s window, and used by the site as an ordinary wallet.
- What links, on chain: the funding to the account, as for a swap; whatever the site does with the account, in the open; each top-up; and the return to new private UTxOs. Your public accounts aren’t in these transactions unless the site builds one that pays one of them or spends from one, or pays or spends from someone else’s address, or a contract’s, that carries one’s stake key: any the wallet knows, the dApp account included, not only the one on screen. One is in a transaction too when a certificate or a withdrawal in it names that account’s stake key: an old-style registration needs no signature of that key’s, so the session’s alone completes it (1.5.0’s cross-area review, X05), and a stake pool’s registration that makes it the pool’s reward account needs only the pool’s own signatures, which a site that runs the pool adds after the wallet’s (1.5.0’s last review, L1). The prompt names “Your public account” (by number and name with several) only for an output one of its payment keys spends: its first 20 on each chain (and, for the account on screen, any past them its last balance reading found) at any address, and its keys past those, up to the 500th on each chain, only under that account’s own stake key (X06). Such a key with no staking part, or under a stranger’s stake key, isn’t recognised, so the prompt can say your public account isn’t in it; under another of your accounts’ stake keys it reads as someone else’s address (L11). For an address that only carries its stake key, it says apart that the money isn’t yours; a certificate’s or a withdrawal’s line says whose stake key it is (“A certificate for the stake key of your public account”), where it said “a stake key that isn’t yours” (X05), and a stake pool’s registration keeps its own line, with the warning (L1). When the wallet couldn’t check (its record of the accounts or of the sessions unreadable), the line still says the stake key or the reward account “isn’t yours”, and the prompt doesn’t say the other side isn’t in it (L3). Each way it warns that signing ties it to the session on chain, and it says the other side isn’t in it only when it checked and found none of these (independent review M12; the release review; 1.5.0’s release review, C01). Anyone, the site included, can follow the money back into the private balance, and money you made private yourself leads on to the public account. The funding’s review names the change it leaves in the private balance, which the site can follow too.
- The site is given the account’s address, its reward address (its own stake key, which the wallet never registers), its UTxOs and collateral, and what the user signs for it. It sees the same account on every visit until the session ends, like a pen name, so its visits link to each other. It also sees this browser: if it has seen your public account here, it can tell the session is yours (above).
- On this device: which site has which session is part of the sealed list of connected sites. Once a site’s session is disconnected, its record goes, and with it the site’s origin; one with something left behind still at its account when it’s disconnected, as Koios lists it then, stays, closed, to point to it, with the site’s origin and its transactions, its left-behind list cut to what’s still there (privacy review §3.12; independent review L19).
- Lovejoin (chunk 16; on preprod, and on mainnet since 2026-09-26): a mixer of 10 ₳ boxes. A box goes in from one place and comes out, after other people’s mixes, as one of many boxes.
- It has had no third-party audit, only its makers’ own review (its README and SECURITY.md). The wallet says so where Lovejoin is chosen: Settings, the Lovejoin tile and its reviews, a swap’s approval (an ADA→token swap’s too, for what a stop or a refund would send through it), a return’s review, and Bring everything back’s review. No copy may say otherwise.
- It’s on by default for private sessions’ returns, and can be turned off (privacy review §4.1): Settings’ Bring private sessions back through Lovejoin. Off, a session’s ADA comes back directly, tied on chain to the session and, through its funding, to the private UTxOs that paid for it; it saves each box’s mixes (about 3.3 ₳ a box at 2 waves on mainnet), about 0.3 ₳ to bring it back, and the hours of waiting. A mix from the Lovejoin tile still mixes. And each flow can come back directly on its own: a swap’s approval (Bring it back through Lovejoin, kept with the swap), its Stop (Stop and bring it back directly), each return’s review (its own switch, Bring it back through Lovejoin, first on it: a link under the costs before, blind test §9.8), and Bring everything back’s review (its switch, Bring them back through Lovejoin).
- A box hides only among other people’s. On mainnet the wallet draws from the pool only once it holds 30 real boxes that aren’t the wallet’s (the pool floor,
networks.ts); below it a return comes back directly, and says why. A swap’s approval reads the pool at Review (kept for five minutes), counts only the boxes it has room for, and says when the ADA would come back directly (privacy review §2.7). 30 boxes means enough to mix with, not 30-way hiding: the floor counts boxes, not owners, however long they’ve sat, and boxes seeded by the pool’s operator alone would hide nothing from the operator. Seeding (the public account, depth 0, no mixes) is what starts an empty pool, since a mix needs others’ boxes and the wallet’s own never count: it hides nothing of the seeder’s, which its review says in a warning, and its boxes come back by themselves after the delay set in Settings, as a mix’s do, unmixed (the owner kept this, 2026-10-06), or sooner when brought back by hand. Making no mixes, it puts in as many boxes as one transaction holds (96), so a pool can be started in a single deposit.
- What it hides, and from whom: which box coming out is yours, from people reading the chain, not from Koios or giveme.my, which see your device send both ends (Network above). Settings, the tile and each review say so. At best a box is one of the fan-out’s
3^depth leaves (up to 9 at the default depth 2), and usually fewer (§5.3):
- A box’s way out shows where it goes. The wallet’s go into the Seedelf contract under a register, one box a transaction, paid from itself with giveme.my’s collateral; Lovejoin’s own app writes no datum. So a box hides only among the fan-out’s leaves whose owners also bring them back into a Seedelf around the same time, not among all
3^depth leaves or the 30 boxes the floor counts. While few people do, a box coming back a few hours after its mixes can be picked out: for a session’s return that ties it to the session, for a mix from the public account, to the account. The public mix’s review says so.
- The session, or the public account, pays every mix and backs them with its collateral, in the open, so its leaves are public.
- The ecosystem fix is Lovejoin’s own app writing a fresh register when it withdraws to a Seedelf, so both apps’ exits look alike (chunk 16’s issue).
- What still links: the deposit, to whatever paid it (a session, or the public account in the open), and the boxes going in. Nothing on a box’s way back names a session or an account.
- Tokens and less than 10 ₳ don’t go in. They come back with the rest, merged into the funding’s change.
- Spending boxes that came back together, or with the change the session’s funding left, narrows what Lovejoin hid: it ties them to each other and back to the session or the account. Coin selection keeps them apart where it can (Co-spending); a payment above one box’s worth from money that’s all boxes can’t. The reviews say so.
- Timing: each box waits a random while (1 to 6 hours by default, drawn from the browser’s secure random source; 2 to 12 and 6 to 24 are the other choices; the owner kept the shortest as the default, 2026-09-27) and comes back 2 to 20 minutes into the first time the wallet is unlocked after it, or later when the wallet has just sent something (privacy review §3.1):
- never the moment it unlocks: a box that came due while it was locked, or comes due in the first 2 minutes after the unlock, waits a fresh secure draw inside the stretch the unlock keeps it open (from 2 minutes in to 2 minutes before the auto-lock, never past 20), once, so a short unlock never holds it for good. Whichever run gets there first after the unlock makes the draw, before any Koios read, so a failed read at the unlock, or a run already under way, can’t skip it (independent review L10, L11, L13);
- never in a run that sent anything else, nor within 5 minutes of the wallet’s own send (pushed 3 to 10 minutes, three times at most). A lock keeps the last send’s time (in memory only: two times, nothing of what was sent), so the rule holds across a lock. After a closed browser or an update, a send is counted as late as the browser’s start, and that push leaves the next unlock its fresh draw. An unlock alone isn’t a send (independent review M10);
- a box brought back that may have gone through (Koios didn’t answer its submit) is sent again under the same rules: a fresh draw into an unlock, never in a run that sent anything else, and pushed within 5 minutes of another send. Each resend counts as the wallet’s send, and so does each try of a payment that may still go through, even after it’s let go as unseen (independent review M11, L8, final review F8);
- one at a time: one goes, and the others each wait a fresh 5 to 60 minutes, so a wallet locked for hours never sends them in one burst;
- the box that goes has waited its own chain’s least, when the chain recorded a wait (a swap’s delay as approved, a return’s as reviewed), since that chain was recorded; one someone else’s mix moved waits the longest of the chains recorded before that move (final review F9). Among those, it’s one that has waited the delay’s least since a mix last moved it, one someone else’s mix has moved since the wallet’s chain left it, when there is one, then the oldest (privacy review §3.6): the oldest is usually a leaf of the wallet’s own last mix, which points straight back at its chain.
- Bringing one back early, or all at once, makes them easier to match. The boxes are all 10 ₳, so their amounts say nothing.
- Return times still follow when you use the wallet (§5.4): boxes can only come back while the wallet is unlocked (the proof needs the key), so their withdraws fall within the times you use it; compared with your public account’s transactions over many boxes, that can hint they’re yours. Bringing one back now makes it plainer.
- The public account’s mix (the tile) names the account as the depositor, as any payment of it does, and pays every mix in the open.
- Mixing the boxes again (the tile): a box a mix from a public account put in, and didn’t finish mixing, is that account’s (privacy review §2.10), and the wallet records which account paid each mix from the public side. So is a box no record accounts for (after a restore, say) whose making transaction Koios says spent one of a public account’s payment keys. That is checked against every account the wallet knows, the active one and the first 24, derived on the device, and never by its stake key alone, which anyone can pair with their own payment key (independent review M14). Mix again from my public account mixes the active account’s own, paid by it with no deposit, which ties nothing new. It never takes another account’s: this account paying for them would tie the two accounts together, so the page names the account that put them in and says to switch to it (release review C01). Mix my boxes again, from the private balance, takes only the others, and can’t be used while Koios hasn’t said how a box no record accounts for went in (independent review M14): a one-time account pays for mixes that each take one of your boxes and two others, so one of the three going into each first mix is likely yours, and the funding links the account to the private UTxOs it spends. The fan-out after hides which box coming out is yours as far as a first mix does, and each waits a fresh delay before it comes back. Pay from my private balance anyway takes them all, after a warning that it ties the private UTxOs it spends, and their change, to the public account. (The user chose the private balance over the public account, which would tie the boxes to it, on 2026-09-25, for boxes from private sessions.)
- After a restore (Remove wallet, Forgot password, another browser or device), the wallet has no chain records. A box Koios says a deposit made is held as not mixed yet: it never comes back by itself, and is offered to Mix my boxes again, or brought back anyway behind a warning. A box a mix made comes back as any box does. A box Koios hasn’t answered for waits, not known to be mixed yet, until it does (the owner’s call on independent review M14).
- A stopped or refunded swap’s ADA (privacy review §2.8): an ADA→token swap that’s stopped, cancelled or refunded sends its whole funding through Lovejoin, unless the user chooses direct: its approval prices that too, with the no-audit note, and Stop offers Stop and bring it back directly. A direct return joins the funding’s change, already tied to the session.
- A session whose collateral was spent (a site’s transaction took it, an earlier return, which takes it too, or a Stop’s cancel whose fee nothing else at the account could pay) comes back directly, and says Lovejoin was left out and why (privacy review §2.15). A top-up puts a new 5 ₳ collateral back when the account holds none it could put up (independent review M9). A session brought back by hand, as one the restore scan found, has no top-up: while no order of it waits, it’s offered Put in 5 ₳ for Lovejoin instead, from the private balance, reviewed with a top-up’s privacy note. Bring it back beside it still comes back directly, and says why (1.5.0’s release review, C53). While that 5 ₳ is on its way, the page says to wait for it, Bring it back is the secondary button, and a return built then says the 5 ₳ isn’t at the account yet, rather than gone (1.5.0’s cross-area review, X20). A chain’s fallback collateral is only an exact 5 ₳ with no tokens, no datum and no reference script, so a stranger’s 5 ₳ carrying a datum can’t stand in and make it skip (independent review L20).
- What Koios learns: a chain’s pool read, and its first mix evaluated with the unsent deposit, at review, even for a review never sent (§3.10); then every transaction as it’s sent, and each box’s withdraw. At an unlock, a wallet with something open in Lovejoin on a network (boxes on their way back, a chain, boxes not mixed yet) reads that network’s pool again, and none reads it otherwise (privacy review §2.18). The ownership check runs in the extension, so Koios doesn’t learn from a pool read which boxes are yours, though it can guess that an IP address reading the pool uses Lovejoin, and it sees both ends of the trip sent from it.
- Except where the wallet asks what made a box (independent review M14; the owner accepted the cost, and no decoys are added): for a box no record of the wallet’s accounts for, it asks Koios, from its IP address, for the inputs of the transaction that made it (
tx_info, 20 transactions a request). That tells Koios which of the pool’s boxes are this wallet’s. It happens at a pool read when more boxes could come back than the wallet has due times for (after a restore, a box from another device, or after Mix my boxes again took boxes not mixed yet), asking about all such boxes together, which groups them as one person’s before any is withdrawn or mixed again. And it happens in any wallet wherever a box of unknown origin is taken (a box someone else’s mix moved is one): Mix my boxes again asks about all of them together; Bring one back now about those ranked ahead of the first known one; and a box coming back by itself alone, just before its withdraw, which then shows Koios that box anyway if it goes. Each transaction is asked about once and the answer kept; one Koios doesn’t answer for is asked again at every pool read until it does.
- On this device: the boxes’ due times and each chain the wallet sends (its deposit, its mixes and its leaves, and for a mix from the public side the account that paid it, kept while their boxes sit unmoved), sealed per network, with what Koios said made each box no record accounts for (a mix, a deposit, which public account’s) and the transactions it hasn’t answered for yet, each kept until 3 hours after a pool read last listed a box of it. The boxes themselves are found again by the Seedelf key.
- Bring everything back returns each session in its own transaction, so no transaction ties two sessions together. They’re sent one after another, though: returns into Seedelf that land within a block or two of each other hint that their one-time accounts are one person’s. With Lovejoin, the sessions’ deposits land in the same block or two and their mixes share blocks, and a shallow pool serves the first sessions while the rest come back directly. Bringing each back from its own page, hours apart, avoids both; the review opens with a switch, Bring them back through Lovejoin, to bring them back directly instead, and says Lovejoin has had no audit. Spreading them out by itself isn’t built; the user took sending them together, for now (2026-09-25).
- Preprod and mainnet share keys (the network switch, launch review M3). A recovery phrase has the same payment keys, stake key and Seedelf key on both networks, only the addresses’ prefix differs, so anyone comparing the two chains can tell a preprod account is the same person’s mainnet one. So do private sessions’ one-time accounts: each network counts its sessions from 0, and a new session’s index is checked only on the network in use (
freshIndex), so a preprod session and a mainnet one at the same index share their keys, and anyone comparing the chains can tie them together (crypto review, Not fixed #2, accepted). Settings says so before moving to preprod, and preprod’s note says to test with a recovery phrase you don’t use on mainnet.
- What’s kept per network stays apart: balances, the Seedelf history, sessions, Lovejoin, connected sites and the rest (architecture.md). A site connected on one network isn’t on the other.
- The choice itself (
seedelf.network) is kept unsealed in local storage, like where the wallet opens: it says only which network the wallet is on.
- Save as CSV (chunk 14) writes the listed Activity to a file on the device, unencrypted. For the private side that’s more than the payments: each row has its transaction’s ID, so whoever has the file can find every one on the chain. It ties the private payments, the private sessions and the Lovejoin boxes to each other and to the public account, and shows which Seedelf each payment went to. The screen says so, and to give it only to someone you’d show all of that (privacy review §2.21). There’s no export without IDs: amounts, fees and times identify the transactions anyway.
Holding and staking
Seedelf funds sit at an address with no staking part, so they earn no staking rewards. That’s the cost of not linking a user’s Seedelfs through a staking key. Since chunk 13, money that sits can stay in the Cardano account, staked with a pool, and move into Seedelf when it should move privately.