Seedelf-Wallet

Chunk 29 · Three small correctness fixes

The owner picked these for 1.5.0 on 2026-10-11, from post-release-roadmap.md: the stake key’s signature over the other network’s transaction (Kept in mind, the launch review’s Still open #5), a public site’s prompt not naming another of the wallet’s public accounts (Kept in mind, the release review’s C05, its public half), and Minswap’s quote not held to the amount typed (the usability review’s DX-8, open question 5).

Status: ✅ built 2026-10-11 on web-wallet/correctness-fixes, from main after #300. One PR: the plan, the build and the docs together, so this file went straight to the archive. What still holds is in architecture.md (Signing, the connector’s ties, and the swap’s estimate) and flows.md (the connector’s transaction prompt, and the swap’s quote). The privacy policy is unchanged: nothing new is sent or kept.

1 · The stake key never signs what could be the other network’s

The hole. A certificate and a required signer name no network, and the account’s keys are the same on both. The stake key is the one key that signs over inputs the wallet can’t find (a site’s zero withdrawal), so a site connected while the wallet was on preprod could hand it a mainnet transaction, its inputs and fee the site’s own there, that delegates, votes or deregisters the account’s mainnet stake. The prompt showed each certificate, but as the preprod account’s.

What closes it (cip30.rs inspect): a signature is refused when nothing ties the transaction to the wallet’s network: no output, no withdrawal, no network ID, and no input or collateral the wallet found. Each of the first three names a network, the wallet already refuses each that names the other, and the ledger refuses each on any network but its own (WrongNetwork, WrongNetworkWithdrawal, WrongNetworkInTxBody). An input found was this network’s. So the other network’s transaction could only ever be one that pays no one.

2 · A public site’s prompt names another public account

The gap. For a site on the public account, the worker matched what a transaction pays against the private sessions alone (ties), so a payment to another of the wallet’s public accounts read “An address”, with no warning that signing ties the two on chain. Transaction details named it.

What changed:

3 · Minswap’s quote is held to what was asked

The gap. The wallet took Minswap’s estimate as it came, so a quote for another amount would have the user approve the minimum that amount gets, then pay the amount typed: 500 ₳ with the minimum for 1 ₳, which a front-runner could take nearly all of.

What changed (sessions.ts): quotesTheAsk refuses an estimate whose amount_in, token_in or token_out isn’t what was asked, or whose paths don’t each run from one token to the other with their amount_in adding up to the amount. routeOf, which every estimate goes through, checks it: a quote says so (“Minswap’s quote was for a different amount or pair from the one asked, so the wallet won’t swap on it. Try again in a moment.”, sess.otherQuote), Review it myself refuses, and the runner builds nothing and tries again later, a retry rather than a pause, since nothing went out.

Why a strict check is safe: the roadmap held it back in case Minswap rounds amount_in, which only a live quote could show. Nine mainnet quotes on 2026-10-11 gave it back exactly, from 7 lovelace to 250,000 ₳ split over two DEXes and 98,765 ₳ over seven, each route’s paths summing to it to the unit, for ADA→MIN, ADA→SNEK, ADA→iUSD, and MIN, SNEK and iUSD back to ADA. A quote moves nothing and costs nothing.

The fakes answered the recorded 10 ₳ quote whatever was asked, which is how DX-8 saw “906.5941 MIN” for both 1 and 500 ₳. Both now answer it for the amount and tokens asked (tests/fixtures/minswap-echo.ts), sharing the amount over the recorded paths, as Minswap does; what it gets stays the recording’s. The unit fake’s echo: false keeps the old answer, for the test that it’s refused.

Tests

Left for later