Branch web-wallet/dapp-additions, from main. One long branch for all of it (the owner, 2026-10-06), with a PR at the end.
Status: built and run live (2026-10-06 and 07). SundaeSwap V3 and Danogo are routed, routes are direct, and Stop cancels every order itself. The owner ran every DEX the wallet routes on mainnet but Splash and Spectrum, and two Stops there cancelled, and the stuck preprod V3 order cancelled live (Mainnet runs, Step 3). It goes out in 1.3.0 (the owner, 2026-10-07). What’s still open is in post-release-roadmap.md’s Owed (O2, O4) and Kept in mind.
MAINNET_PROTOCOLS, and the research confirmed their orders read the way checkOrder expects. All it lacks is the live smoke test.Checked against live chain data, APIs and repos.
chrome-extension:// origins, and it has no preprod.SplashStable comes off MAINNET_PROTOCOLS (step 0).Remove SplashStable from MAINNET_PROTOCOLS so mainnet routing leaves it out (excludedProtocols). Preprod already leaves it out (PREPROD_BROKEN).
What Minswap builds. Seen on chain, 25 open orders at V3’s order script, 2026-10-06:
fa6a58bbe2d0ff05534431c8e2f0ef2cbdc1602a8456e4b13c8f3077 under Minswap’s fixed staking part f217f435f5f34dba69830d9ada013b5c290a4eee6078371cae55298b. The same staking part is used for every sender and is unregistered.
[pool_ident?, owner, max_protocol_fee, destination, details, extension].owner = Signature(sender's stake key hash).destination = Fixed{sender's base address, NoDatum}.0e56d46a… with a datum hash, and the owner is one key shared by every such order (cc7dfaef…, Minswap’s). The session could never cancel it.c3e28c36… with an inline datum, owner the sender’s stake key.destination exactly.owner’s multisig to be satisfied, which here is the stake key’s signature.6ab62945d0d8d6288e243b3b6437ff9c099a38e088288f5a6b7c5e8b) uses the same datum layout. No Minswap-built one was found, so SundaeSwapStable stays out until one is seen.What the foundation adds:
SundaeSwapV3 sits beside another leg is refused at the quote, and again when the order is placed, the same way an unchecked DEX is (uncheckedProtocols).checkOrder reads a V3 order exactly. An output at the V3 order script counts as the session’s order only if all of these hold:
f217f435…. That pinned part is accepted at this script alone.owner is Signature of the session’s stake key hash.destination is Fixed{the session's address, NoDatum}.
Anything else at that script is refused, whatever names would say.ownStake). Otherwise 0/i alone, as now. Everything else refuseOddities refuses still stands: certificates, withdrawals, votes, mints, other people’s signatures. So a stake signature only meets a required signer.
inspectSessionTx already names the stake key as a signer when a required signer is its hash.signSessionTx already signs with 2/i (stakeIndex).ownStake carries the shared Seedelf stake key, which is never signed with. A V3 order for such a session is refused.Danogo has no order: Minswap’s transaction swaps against its pools in the same transaction. On preprod in chunk 15b that meant:
The session refuses all of it today. Chunk 15b listed what allowing it takes (archive). To be designed in detail from a real Minswap-built Danogo transaction recorded first (mainnet, read from chain):
d8b69fc5…, to be confirmed from the recorded transaction). Reference inputs are fine.withinFunding’s paid-out sum:
SundaeSwapV3 onto MAINNET_PROTOCOLS. Owner: one small mainnet swap that routes through it, then a Stop on a second one. Preprod may have no V3 pools.DanogoCLMMV1 routed on mainnet, once Foundation 2 is in. Owner: one small swap.SundaeSwapV4: when Minswap routes it (the owner, 2026-10-07). On 2026-10-07 Minswap’s router didn’t name it on either network. Sundae’s SDK 3.0.0 builds its orders: owner and a destination as V3’s, plus a service budget (3 ₳), partial fills (max_per_execution) and its batcher’s own two-pool routes. Partial fills need the runner to follow an order a fill leaves behind; a two-pool route meets the one-leg rule. Its contracts are private, and no audit is published.VyFinance: its owner is a 56-byte field (payment key hash ‖ stake key hash), the datum travels by hash, and it uses 313 enterprise order scripts.SundaeSwapStable, once a Minswap-built order is seen.CswapV1: closed source, pinned staking part ec39fae0….web-wallet/dapp-additions from main.checkOrder, the stake-signed cancel, tests.The DEX runs are mainnet’s: preprod’s pools route almost nothing the wallet can buy. Minswap’s mainnet estimate, asked as the wallet asks it (its exclusions, allow_multi_hops: false), routed each of these through one DEX alone, every token on the wallet’s list. A route moves with the pools: the approval names the DEX to check before approving.
| DEX | Pair | Notes |
|---|---|---|
| SundaeSwap V3 | 20 ₳ → SUNDAE, then SUNDAE → ₳ | 0.3% impact each way; INDY too |
| Danogo | 20 ₳ → NIGHT, then NIGHT → ₳ | 0.3–0.4%; USDM both ways, and USDM ↔ USDA, too |
| Minswap V2 | 20 ₳ → NTX, then NTX → ₳ | 0.3%; HOSKY too |
| Minswap (V1) | 20 ₳ → MIN | Plutus V1: the order’s script and datum go in its cancel |
| SundaeSwap (V1) | MIN → ₳ (3,294 MIN) | Plutus V1; 3% impact |
| WingRiders V2 | 50 ₳ → USDA, IAG or COPI | at 20 ₳, IAG goes through SundaeSwap V3 |
| WingRiders (V1) | 50 ₳ → LQ | Plutus V1; at 20 ₳ it goes through SundaeSwap V3 |
| MinswapStable | USDM → DJED, DJED → iUSD, iUSD → USDM (12 each) | |
| WingRiders StableV2 | iUSD → DJED (12) | 0.06% |
| Spectrum | IAG → ₳ (5.9%), ₳ → iBTC (33%) | only at a bad price: skip |
| Splash | none found | its cancel is tested in Rust only |
Minswap charges its aggregator fee (0.85 ₳; 1 ₳ for a stablecoin pair) on every route but its own pools.
Run (the owner, 2026-10-07): “everything works.” Every pair above but Spectrum’s and Splash’s, on a mainnet test wallet: each order placed, filled and its money back, Danogo’s filled in the swap itself; and a Stop on an NTX swap (Minswap V2) and a MIN one (Minswap V1), each cancelled before its fill and the money back. The MIN one is the first live Plutus V1 cancel: the order’s script and datum in the transaction, and its script data hash with_integrity’s, which the node took. With the WingRiders runs, that’s the smoke test. Splash and Spectrum stay open (O2).
Why. The owner’s preprod Stop of a SundaeSwap V3 order (70773ee9…#0, 14 USDR) never cancelled it:
POST /aggregator/cancel-tx answers 404, “Route … not found”, on mainnet and preprod. Its documentation still lists it. build-tx, pending-orders and finalize-and-submit-tx still answer. That’s what the research’s probes saw; the owner has seen 1.2.0’s Stop cancel some orders through it and not others (2026-10-07), so released wallets’ Stop is partly broken, not wholly.pending-orders doesn’t list every order. On preprod it lists nothing for that session; on mainnet it listed one of two V3 orders Minswap built.The owner’s call (2026-10-07): Stop must bring the money back. “No one is going to take a wallet seriously that gets the funds effectively stuck.” So the wallet builds every cancel itself, from the session’s own keys and collateral, and asks Minswap for nothing to get money back.
What every DEX’s cancel needs (four research passes, 2026-10-07: real owner cancels decoded from chain, checked against each DEX’s source or bytecode; the SundaeSwap V3 cancel of the stuck order dry-run through Ogmios):
| Minswap’s name | Order script, Plutus | Script | Cancel redeemer | Signer | Notes |
|---|---|---|---|---|---|
Minswap (V1) |
a65ca58a…, V1, both networks |
in the transaction (335 B) | d87a80 |
the datum’s sender’s payment key | datum by hash: the original bytes go in the witness set |
MinswapV2 |
mainnet c3e28c36…, preprod da952546…1b7a; V2 |
reference | d87a80 |
canceller = OAMSignature(payment key), field 0 |
the order’s own staking part is the sender’s |
MinswapStable |
one per pool: 13 mainnet, 3 preprod; V2 | reference, per pool | d87a80 |
the sender’s payment key, field 0 | Minswap’s SDK lists the wrong reference for 4 mainnet pools; the table has the on-chain ones |
SundaeSwap (V1) |
ba158766…, V1, mainnet |
in the transaction (2,121 B) | d87a80 |
the destination’s payment key (alternate unset) |
datum by hash; closed source, the rule found by Ogmios dry runs |
SundaeSwapV3 |
mainnet fa6a58bb…, preprod a989aa2f…; V2 |
reference | d87a80 |
owner = Signature(stake key): 2/i |
mainnet’s reference sits at Sundae’s own key |
SundaeSwapStable |
mainnet 6ab62945…, preprod eff5be0d…; V3 |
reference | d87a80 |
as V3 | not routed on mainnet; preprod has no scooper |
WingRiders (V1) |
86ae9eeb…, V1, mainnet |
in the transaction (883 B) | d87a80 |
the owner’s payment key | datum by hash; Minswap’s orders expire in about 8 h, then only the owner can reclaim |
WingRidersV2, WingRidersStableV2 |
mainnet c134d839…, 23680ea6…; preprod c25f7962…, acefcfe8…; V2 |
reference | d87a80 |
the owner’s payment key, field 2 | references held by a script that can never spend them |
Splash, Spectrum |
both 464eeee8… (Splash’s limit order), V2, mainnet |
reference | d87980 |
cancellation_pkh (field 10), the payment key |
output 0 must be the exact refund to the order’s redeemer address: one order a transaction. Partial fills recreate the order at a new outpoint. |
seedelf-core/src/orders.json. Every reference was checked on chain on 2026-10-07: unspent, holding a script of the expected hash. The three V1 scripts are bundled, checked by hash.The design.
ProtocolParameters carries Plutus V1 and V2’s too, and the local evaluator (eval.rs) takes all three.seedelf-core orders.rs): for an order output, which DEX’s it is, who must sign its cancel, and where it pays, read exactly from each DEX’s datum.cancel_orders): one group of orders (one Plutus version; a Splash order alone), the session’s own UTxOs for the fee, the session’s collateral with its return, every signer a required signer, the scripts by reference (V1’s in the transaction), budgets measured in the wallet, the fee settled. Everything goes back to the session, a Splash order’s exact refund first.read_dex_order and build_order_cancel.checkOrder): every order sits at a script the table knows, owned by the session’s own key, paying the session’s address. Anything else is refused before it’s signed. So every order the wallet places is one it can cancel itself.Built (2026-10-07).
orders.rs and orders.json: the table, each DEX’s reader (read, read_order, Session::owns), and cancel_orders. ProtocolParameters carries the V1 and V2 cost models (empty when Koios leaves them out), and eval::evaluate_with measures any Plutus version; resolve_reference hands it a reference script.pallas-txbuilder hashes witness datums as a plain list but writes them as a tagged set, so a cancel carrying a datum (any Plutus V1 order’s) would be refused at submit. with_integrity recomputes the script data hash from the transaction’s own witness bytes (integrity_hash, V1’s legacy language view included).readDexOrder and buildOrderCancel.checkOrder asks readDexOrder; any contract the table doesn’t hold is refused (sess.refuse.warn.notCancellable).liveOrders replaces Minswap’s pending-orders everywhere: Stop, the runner’s wait, the close, the manual return and Bring everything back.cancel and the manual cancelBuild build the wallet’s own cancel (cancelOf), checked by inspect as before; the stake key signs only where a spent order is SundaeSwap V3’s or Stableswaps’, as readDexOrder says.pendingOrders and cancelTx; Koios’s gained datumInfo and addressUtxos.swaps.now.orderOpen, swaps.step.openAtDex, mtpe). A 1.2.0 record that says it is cleared at its next run, and the order cancelled.sess.refuse.warn.notCancellable and sess.refuse.warn.noCollateral, back-translated blind twice; the first pass changed the Japanese “can’t cancel” (the wallet read as placing the order) and the Spanish “no collateral” (it lost the ADA-only UTxO).seedelf-core/tests/orders_test.rs, 7): for each DEX (Minswap V1, V2 on both networks, Stable; SundaeSwap V1, V3 on both networks, Stableswaps; WingRiders V1, V2 on both networks, StableV2; Splash), a real order from chain cancelled through its real script in the wallet’s evaluator, and refused by that script without the canceller’s signature; the script data hash recomputed for real V1, V2 and V3 cancels from chain, and matched by every cancel the wallet builds; the owner’s stuck order cancelled with exactly the budget every real single-key V3 cancel used. Mutation-checked: without the integrity patch, the V1 cancel’s hash is wrong.liveOrders’ address_utxos look). Splash is mainnet only, and the worker’s tests run on preprod. The Rust side cancels a real Splash order.70773ee9…#0, 14 USDR) cancelled at Refresh, and its money came back: the first cancel the wallet built itself, through a reference script, signed by both keys.cancel-tx, which cancels some orders and not others (the owner, 2026-10-07). This is the fix; when it ships is the owner’s call.Committed and pushed: the foundation (7886971), SundaeSwap V3’s swap part (29777c2), Danogo’s (04d0022), and Direct routes and Danogo’s costs (below, 00fde82).
dist/.tests/sundae-orders.test.ts (11) and tests/direct-swaps.test.ts (8).docs/i18n/verified-critical-*.json): sundaeNotOurs, sundaeCancelKeys, notPool and tooLittle.Sundae.
routeOf, outOfPlace).build-tx gets the same leave-out list (avoid), as Minswap routes it again.uncheckedProtocols stays as the backstop, on both networks.checkOrder reads a V3 order with sundaeV3Order. Minswap’s staking part is accepted only at V3’s order script.refuseOddities takes exactly ["0/i", "stake"] when the cancel spends an input at V3’s order script and the session has ownStake. A V3 cancel that the stake key wouldn’t sign is refused, since it couldn’t work.Danogo.
build-tx returns (preprod, 2026-10-06):
attachWitnesses keeps that signature.04041c3c….Withdraw[0]; the second try built cleanly. Preprod’s pools may be flaky.inspect). These all must hold:
directSpends: inputs only at DANOGO_POOL[network]; collateral only a key’s UTxO whose signature witnessedKeys finds in the transaction; withdrawals only zero, from the pool script or a spent pool’s staking script.partialSign. refuseOddities lets othersSign be exactly the collateral owners counted.checkDirect: outputs only to the session, to the pools, and at most one Minswap fee.checkProceeds: the session’s net gain of the token bought is at least the approved minimum. Bought ADA counts with the fee and Minswap’s fee added back.withinNet, in place of withinFunding: the session’s net spend is within its funding.direct, with orders: [].act counts it as filled once it lands, and brings everything back. Its proceeds are in its own transaction, which the order path would never count as arrived.RESEND_AFTER_MS, as any refused step is.SundaeSwap V3’s swap part (2026-10-06).
SundaeSwapV3 is on MAINNET_PROTOCOLS, off MAINNET_REFUSED.SUNDAE_V3), each read from a build Minswap made:
fa6a58…, Minswap’s staking part f217f435…;a989aa2f…, Minswap’s staking part c41401cd….checkOrder takes the network from the session address’s header.checkOrder, routing.31dde3db…) and tADA→PPEE through SundaeSwapV3 alone, at 10, 50 and 200 tADA.f0f202a8…55534472), USDRF and PPEE all sell to tADA through SundaeSwapV3 alone, at 1 to 20.
Danogo’s swap part (2026-10-06).
DanogoCLMMV1 is on MAINNET_PROTOCOLS, off DIRECT_PROTOCOLS. Chakra’s bonding curve and Djed’s minting stay out.againstPools (SwapQuote, SessionAuto). The recorded swap’s flag was renamed againstPools too, as auto.direct already means a return that skips Lovejoin.mtpe; not critical.MAINNET_PROTOCOLS.Wording outside the swap page: checked. Two lines on the approval were wrong for a pool swap, and are fixed:
swaps.review.approvesPools).Direct routes and Danogo’s costs (2026-10-06).
estimate and build-tx route through more than one pool only when asked with allow_multi_hops: true, which the wallet never sent. Its documentation gives no default.exclude_protocols: ["MinswapV2"]) is ignored, with no error.routed in minswap.ts: allow_multi_hops: false, in every estimate and build), so it doesn’t rest on a default nobody wrote down.outOfPlace): any DEX in a path of more than one leg is asked for again without, and refused at the quote and before the order if Minswap still routes so, on either network. It reuses the existing “routes this swap through …” refusal, so no new strings. ONE_LEG_ONLY is gone.0e56d46a… included.POOL_SWAP_FEE_ESTIMATE in ui/swap.ts), and the note says “about 0.75 ₳ for the swap against the DEX’s pools and 0.25 ₳ for the return”. Two new strings, mtpe; not critical.tests/swap-routes.test.ts (both routing rules), tests/swap-form.test.ts and tests/screens.test.ts (the cost rows). The routing rules and the hidden deposit row were each mutation-checked.