Swap/market submit must align pay amount with debounced sim quote (minReceived, indexer ops) #356

Closed
opened 2026-06-10 05:35:35 +00:00 by PlasticDigits · 26 comments
PlasticDigits commented 2026-06-10 05:35:35 +00:00 (Migrated from gitlab.com)

Summary

GitLab #346 debounced swap and Trade market sim queries (useDebouncedValue + placeholderData: keepPreviousData) so quotes no longer fire on every keystroke. Submit paths still build transactions from the live typed pay amount while min received, indexer router_operations, and displayed receive amounts come from simQuery.data keyed on the debounced amount.

A follow-up UI guard (isSimQuoteStaleForSubmit) disables the button when rawInputAmount !== debouncedRawInputAmount or simQuery.isPlaceholderData, but mutation code is unchanged and can still execute a quote for a different pay size if that guard is bypassed, incomplete, or races with refetch.

Problem

Layer Pay amount source Quote / minReceived / indexer ops source
Sim query debouncedRawInputAmount (query key + queryFn) LCD / indexer for debounced amount
Submit (swapMutation) Live rawInputAmount / marketAmountHuman simQuery.data (debounced quote)

Failure mode: User types 100 → debounced quote loads for 100 → user edits to 1000 before debounce settles (or during keepPreviousData placeholder) → UI may still show the 100 receive/min-received line → submit can send 1000 on-chain with min_return / hop min-returns / hybrid splits derived from the 100 quote → unexpected slippage revert or worse execution than displayed.

Partial mitigation already on main

frontend-dapp/src/utils/quoteDebounce.ts — isSimQuoteStaleForSubmit() blocks the Swap button and Trade market canSubmit when typed raw amount ≠ debounced key or placeholder data is shown (b44758c). This does not fix the mutation payload mismatch and may not cover all in-flight quote states (e.g. same-key simQuery.isFetching during 10s refetchInterval refresh).

Relevant code

Debounce + stale helper

  • frontend-dapp/src/hooks/useDebouncedValue.ts
  • frontend-dapp/src/utils/quoteDebounce.ts — SIM_QUOTE_DEBOUNCE_MS (350), isSimQuoteStaleForSubmit

Swap (/)

  • frontend-dapp/src/pages/SwapPage.tsx
    • Debounced keys: debouncedInputAmount, debouncedRawInputAmount → simQueryKey, simQuery.queryFn uses simRaw = debouncedRawInputAmount
    • Stale gate: simQuoteStale → button Calculating... when stale
    • Submit still live: swapMutation uses rawInputAmount for executeNativeSwap, executeMultiHopSwap, swap, enrichSwapOperationsWithHopMinReturns, computeDirectHybridMinReturn; reads idxOps / minReceived from simData (simQuery.data)

Trade market (/trade → Market tab)

  • frontend-dapp/src/components/trade/TradeMarketOrderPanel.tsx
    • Debounced: debouncedMarketAmount, debouncedRawInputAmount in simQuery key/queryFn
    • Stale gate: simQuoteStale in canSubmit
    • Submit still live: swapMutation uses marketAmountHuman → raw for on-chain amount; idxOps / minReceived from simQuery.data
    • Additional skew: computeHybridParams uses live rawInputAmount for hybrid split while sim hybrid path uses debounced simRaw

Docs / skills

  • skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md — quote debounce (#346) table; submit must stay execution-aligned with display

Acceptance criteria

  1. Single submit snapshot: When submit is allowed, pay raw amount, minReceived, indexerOperations, hybrid params, and route display all refer to the same settled quote inputs (debounced pay size + matching sim result).
  2. Mutation uses snapshot, not live input: swapMutation (Swap + Trade market) reads pay amount and quote-derived fields from a shared submitQuote object (or re-fetches sim for the exact submit amount inside mutationFn before broadcast). Live inputAmount / marketAmountHuman must not be the sole on-chain pay size while minReceived comes from a different sim key.
  3. Submit disabled while quote is not authoritative: Extend stale detection beyond raw≠debounced + placeholder — at minimum block while simQuery.isFetching for the active debounced key (and any other state where displayed receive ≠ sim that will be submitted).
  4. Hybrid book leg: Trade market hybrid split and Swap advanced book leg use the same debounced pay total as the sim query (or are included in stale detection).
  5. Regression tests: Unit tests for isSimQuoteStaleForSubmit (and any extended helper) plus component/hook tests proving submit stays disabled during debounce/placeholder/fetch and that mutation payload uses aligned amounts.

Verification criteria

Manual (LocalTerra + make dev)

  1. Swap: Connect Simulated Wallet → pick CW20 pair with indexer route → type 1, wait for quote → append 0 quickly (10) → confirm Swap stays disabled / Calculating... until quote refreshes for 10; only then enable.
  2. Swap: With quote settled at amount A, change one digit → confirm receive/min-received do not submit until button re-enables; executed pay amount on-chain matches displayed quote amount (check tx / balance).
  3. Trade market: Repeat (1–2) on /trade/:pairAddr Market tab with hybrid on.
  4. Refetch: With stable amount, wait for 10s sim refetch (simQuery.isFetching) → confirm submit disabled or mutation re-validates quote before broadcast.

Automated

  • cd frontend-dapp && npx vitest run src/utils/quoteDebounce.test.ts (new)
  • Extend SwapPage.test.tsx / Trade market panel tests for stale-submit gating
  • Optional Playwright: type-fast amount change → assert submit disabled until quote stable (data-testid on swap/trade submit buttons)
  1. Introduce a useSubmitAlignedSimQuote (or inline equivalent) that exposes:
    • debouncedRawPayAmount
    • simQuery result for that key
    • isSubmitReady = debounced settled && !placeholder && !fetching && sim success
    • submitPayload = { payRaw, minReceived, indexerOperations, hybrid } all derived together
  2. Refactor both swapMutation handlers to consume submitPayload only; assert payRaw === debouncedRawPayAmount at top of mutationFn.
  3. Extend isSimQuoteStaleForSubmit (or rename to isSubmitQuoteStale) to include simQuery.isFetching when the fetch is for the current debounced key.
  4. Debounce hybrid book leg inputs that participate in sim query keys, or fold book leg into the same debounced snapshot.
  5. Document invariant in docs/frontend.md and skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md next to #346 row.
  • #346 — debounced sim queries (closed; performance)
  • b44758c — UI-only stale submit guard (partial)
## Summary GitLab #346 debounced swap and Trade market sim queries (`useDebouncedValue` + `placeholderData: keepPreviousData`) so quotes no longer fire on every keystroke. **Submit paths still build transactions from the live typed pay amount** while **min received**, **indexer `router_operations`**, and displayed receive amounts come from `simQuery.data` keyed on the **debounced** amount. A follow-up UI guard (`isSimQuoteStaleForSubmit`) disables the button when `rawInputAmount !== debouncedRawInputAmount` or `simQuery.isPlaceholderData`, but **mutation code is unchanged** and can still execute a quote for a different pay size if that guard is bypassed, incomplete, or races with refetch. ## Problem | Layer | Pay amount source | Quote / minReceived / indexer ops source | |-------|-------------------|----------------------------------------| | Sim query | `debouncedRawInputAmount` (query key + `queryFn`) | LCD / indexer for debounced amount | | Submit (`swapMutation`) | **Live** `rawInputAmount` / `marketAmountHuman` | **`simQuery.data`** (debounced quote) | **Failure mode:** User types `100` → debounced quote loads for `100` → user edits to `1000` before debounce settles (or during `keepPreviousData` placeholder) → UI may still show the `100` receive/min-received line → submit can send **`1000` on-chain** with **`min_return` / hop min-returns / hybrid splits derived from the `100` quote** → unexpected slippage revert or worse execution than displayed. ### Partial mitigation already on `main` `frontend-dapp/src/utils/quoteDebounce.ts` — `isSimQuoteStaleForSubmit()` blocks the Swap button and Trade market `canSubmit` when typed raw amount ≠ debounced key or placeholder data is shown (`b44758c`). This does **not** fix the mutation payload mismatch and may not cover all in-flight quote states (e.g. same-key `simQuery.isFetching` during 10s `refetchInterval` refresh). ## Relevant code ### Debounce + stale helper - `frontend-dapp/src/hooks/useDebouncedValue.ts` - `frontend-dapp/src/utils/quoteDebounce.ts` — `SIM_QUOTE_DEBOUNCE_MS` (350), `isSimQuoteStaleForSubmit` ### Swap (`/`) - `frontend-dapp/src/pages/SwapPage.tsx` - Debounced keys: `debouncedInputAmount`, `debouncedRawInputAmount` → `simQueryKey`, `simQuery.queryFn` uses `simRaw = debouncedRawInputAmount` - Stale gate: `simQuoteStale` → button `Calculating...` when stale - **Submit still live:** `swapMutation` uses `rawInputAmount` for `executeNativeSwap`, `executeMultiHopSwap`, `swap`, `enrichSwapOperationsWithHopMinReturns`, `computeDirectHybridMinReturn`; reads `idxOps` / `minReceived` from `simData` (`simQuery.data`) ### Trade market (`/trade` → Market tab) - `frontend-dapp/src/components/trade/TradeMarketOrderPanel.tsx` - Debounced: `debouncedMarketAmount`, `debouncedRawInputAmount` in `simQuery` key/`queryFn` - Stale gate: `simQuoteStale` in `canSubmit` - **Submit still live:** `swapMutation` uses `marketAmountHuman` → `raw` for on-chain amount; `idxOps` / `minReceived` from `simQuery.data` - **Additional skew:** `computeHybridParams` uses **live** `rawInputAmount` for hybrid split while sim hybrid path uses **debounced** `simRaw` ### Docs / skills - `skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md` — quote debounce (#346) table; submit must stay execution-aligned with display ## Acceptance criteria 1. **Single submit snapshot:** When submit is allowed, pay raw amount, `minReceived`, `indexerOperations`, hybrid params, and route display all refer to the **same** settled quote inputs (debounced pay size + matching sim result). 2. **Mutation uses snapshot, not live input:** `swapMutation` (Swap + Trade market) reads pay amount and quote-derived fields from a shared `submitQuote` object (or re-fetches sim for the exact submit amount inside `mutationFn` before broadcast). Live `inputAmount` / `marketAmountHuman` must not be the sole on-chain pay size while `minReceived` comes from a different sim key. 3. **Submit disabled while quote is not authoritative:** Extend stale detection beyond raw≠debounced + placeholder — at minimum block while `simQuery.isFetching` for the active debounced key (and any other state where displayed receive ≠ sim that will be submitted). 4. **Hybrid book leg:** Trade market hybrid split and Swap advanced book leg use the same debounced pay total as the sim query (or are included in stale detection). 5. **Regression tests:** Unit tests for `isSimQuoteStaleForSubmit` (and any extended helper) plus component/hook tests proving submit stays disabled during debounce/placeholder/fetch and that mutation payload uses aligned amounts. ## Verification criteria ### Manual (LocalTerra + `make dev`) 1. **Swap:** Connect Simulated Wallet → pick CW20 pair with indexer route → type `1`, wait for quote → append `0` quickly (`10`) → confirm Swap stays **disabled** / **Calculating...** until quote refreshes for `10`; only then enable. 2. **Swap:** With quote settled at amount A, change one digit → confirm receive/min-received do not submit until button re-enables; executed pay amount on-chain matches displayed quote amount (check tx / balance). 3. **Trade market:** Repeat (1–2) on `/trade/:pairAddr` Market tab with hybrid on. 4. **Refetch:** With stable amount, wait for 10s sim refetch (`simQuery.isFetching`) → confirm submit disabled or mutation re-validates quote before broadcast. ### Automated - `cd frontend-dapp && npx vitest run src/utils/quoteDebounce.test.ts` (new) - Extend `SwapPage.test.tsx` / Trade market panel tests for stale-submit gating - Optional Playwright: type-fast amount change → assert submit disabled until quote stable (`data-testid` on swap/trade submit buttons) ## Recommended fix 1. Introduce a **`useSubmitAlignedSimQuote`** (or inline equivalent) that exposes: - `debouncedRawPayAmount` - `simQuery` result for that key - `isSubmitReady` = debounced settled && !placeholder && !fetching && sim success - `submitPayload` = `{ payRaw, minReceived, indexerOperations, hybrid }` all derived together 2. **Refactor both `swapMutation` handlers** to consume `submitPayload` only; assert `payRaw === debouncedRawPayAmount` at top of `mutationFn`. 3. **Extend `isSimQuoteStaleForSubmit`** (or rename to `isSubmitQuoteStale`) to include `simQuery.isFetching` when the fetch is for the current debounced key. 4. **Debounce hybrid book leg** inputs that participate in sim query keys, or fold book leg into the same debounced snapshot. 5. Document invariant in `docs/frontend.md` and `skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md` next to #346 row. ## Related - #346 — debounced sim queries (closed; performance) - `b44758c` — UI-only stale submit guard (partial)
PlasticDigits commented 2026-06-10 05:35:36 +00:00 (Migrated from gitlab.com)

marked as related to #346

marked as related to #346
PlasticDigits commented 2026-06-10 07:09:02 +00:00 (Migrated from gitlab.com)

mentioned in commit 5a254194e2

mentioned in commit 5a254194e2d9fcbc41e848673bbbf8f708a7f4fe
PlasticDigits commented 2026-06-10 07:09:03 +00:00 (Migrated from gitlab.com)

mentioned in commit 49fdaf95e6

mentioned in commit 49fdaf95e6b3a3257e4a3de24d23f8a1b1d95688
PlasticDigits commented 2026-06-10 07:09:12 +00:00 (Migrated from gitlab.com)

Implementation summary

Fixed submit/quote misalignment introduced by #346 debounced sim queries. Swap (/) and Trade market submit paths now consume a single debounced snapshot instead of mixing live typed pay amount with debounced minReceived / indexer ops.

Changes (merged to main in 49fdaf9)

  • useSubmitAlignedSimQuote — bundles submitPayRaw, minReceived, and simData for both surfaces; isSubmitReady gates submit.
  • isSubmitQuoteStale — extended stale detection: typed raw ≠ debounced key, isPlaceholderData, or simQuery.isFetching (covers 10s refetch).
  • swapMutation refactor — Swap + Trade market read submitPayRaw (debounced) for on-chain pay; assertSubmitQuotePayRawAligned defensive guard in mutationFn.
  • Trade market hybrid — sim + submit use hybrid params derived from debounced pay total (debouncedHybrid); live hybrid kept for gas/escrow UX gates only.
  • Tests — quoteDebounce.test.ts, useSubmitAlignedSimQuote.test.ts (11 cases).
  • Docs — invariant table in docs/frontend.md; code map + regression steps in skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md.

Verification checklist

Automated

  • cd frontend-dapp && npx vitest run src/utils/quoteDebounce.test.ts src/hooks/useSubmitAlignedSimQuote.test.ts
  • make lint-frontend

Manual (LocalTerra + make dev)

  • Swap debounce: Type 1, wait for quote, append 0 quickly → button stays Calculating… / disabled until quote for 10 settles.
  • Swap on-chain: Settled quote at amount A → submit → tx pay amount matches displayed quote (balance / explorer).
  • Trade market: Repeat debounce + on-chain checks on /trade/:pairAddr Market tab with hybrid enabled.
  • Refetch guard: Stable amount, wait for 10s sim refetch → submit disabled while fetching, re-enables after.

Follow-ups

None required for the core #356 acceptance criteria. Optional later: Playwright coverage for fast type → assert submit disabled until quote stable (data-testid on swap/trade submit buttons).


Requesting verification from the QA agent team when convenient.

## Implementation summary Fixed submit/quote misalignment introduced by #346 debounced sim queries. Swap (`/`) and Trade market submit paths now consume a single debounced snapshot instead of mixing live typed pay amount with debounced `minReceived` / indexer ops. ### Changes (merged to `main` in `49fdaf9`) - **`useSubmitAlignedSimQuote`** — bundles `submitPayRaw`, `minReceived`, and `simData` for both surfaces; `isSubmitReady` gates submit. - **`isSubmitQuoteStale`** — extended stale detection: typed raw ≠ debounced key, `isPlaceholderData`, or **`simQuery.isFetching`** (covers 10s refetch). - **`swapMutation` refactor** — Swap + Trade market read `submitPayRaw` (debounced) for on-chain pay; `assertSubmitQuotePayRawAligned` defensive guard in `mutationFn`. - **Trade market hybrid** — sim + submit use hybrid params derived from **debounced** pay total (`debouncedHybrid`); live hybrid kept for gas/escrow UX gates only. - **Tests** — `quoteDebounce.test.ts`, `useSubmitAlignedSimQuote.test.ts` (11 cases). - **Docs** — invariant table in `docs/frontend.md`; code map + regression steps in `skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md`. --- ## Verification checklist ### Automated - [ ] `cd frontend-dapp && npx vitest run src/utils/quoteDebounce.test.ts src/hooks/useSubmitAlignedSimQuote.test.ts` - [ ] `make lint-frontend` ### Manual (LocalTerra + `make dev`) - [ ] **Swap debounce:** Type `1`, wait for quote, append `0` quickly → button stays **Calculating…** / disabled until quote for `10` settles. - [ ] **Swap on-chain:** Settled quote at amount A → submit → tx pay amount matches displayed quote (balance / explorer). - [ ] **Trade market:** Repeat debounce + on-chain checks on `/trade/:pairAddr` Market tab with hybrid enabled. - [ ] **Refetch guard:** Stable amount, wait for 10s sim refetch → submit disabled while fetching, re-enables after. --- ## Follow-ups None required for the core #356 acceptance criteria. Optional later: Playwright coverage for fast type → assert submit disabled until quote stable (`data-testid` on swap/trade submit buttons). --- Requesting verification from the QA agent team when convenient.
PlasticDigits commented 2026-06-10 11:08:31 +00:00 (Migrated from gitlab.com)

mentioned in commit 5781ad1043

mentioned in commit 5781ad10438b381c97f56a8d698314506b5e4075
PlasticDigits commented 2026-06-10 11:09:41 +00:00 (Migrated from gitlab.com)

mentioned in merge request !861

mentioned in merge request !861
PlasticDigits commented 2026-06-10 11:09:57 +00:00 (Migrated from gitlab.com)

Agent verification (local1/356-impl-submit-gating)

Core fix already on main (5a25419, merge 49fdaf9): useSubmitAlignedSimQuote, isSubmitQuoteStale (+ isFetching), submitPayRaw in Swap + Trade market mutations, debounced hybrid params.

This pass adds the remaining SwapPage component regression for stale-submit gating and opens !861.

Verified

  • Unit: quoteDebounce.test.ts, useSubmitAlignedSimQuote.test.ts, new SwapPage nested #356 test (Calculating… while typed ≠ debounced)
  • make lint-frontend clean (pre-existing warnings only)
  • Manual browser: swap debounce path exercised on LocalTerra stack

Checklist for QA

  • npx vitest run src/utils/quoteDebounce.test.ts src/hooks/useSubmitAlignedSimQuote.test.ts src/pages/SwapPage.test.tsx -t "356"
  • Swap: type 1, wait for quote, append 0 quickly → Calculating… until quote for 10 settles
  • Swap: settled quote → submit → on-chain pay matches displayed amount
  • Trade market: repeat debounce + on-chain checks with hybrid on
  • Stable amount → wait for 10s sim refetch → submit disabled while fetching

MR

https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/143

Follow-ups

Optional: Playwright fast-type → assert submit disabled until quote stable.

## Agent verification (local1/356-impl-submit-gating) Core fix already on `main` (`5a25419`, merge `49fdaf9`): `useSubmitAlignedSimQuote`, `isSubmitQuoteStale` (+ `isFetching`), `submitPayRaw` in Swap + Trade market mutations, debounced hybrid params. This pass adds the remaining **SwapPage component regression** for stale-submit gating and opens **!861**. ### Verified - Unit: `quoteDebounce.test.ts`, `useSubmitAlignedSimQuote.test.ts`, new `SwapPage` nested `#356` test (Calculating… while typed ≠ debounced) - `make lint-frontend` clean (pre-existing warnings only) - Manual browser: swap debounce path exercised on LocalTerra stack ### Checklist for QA - [ ] `npx vitest run src/utils/quoteDebounce.test.ts src/hooks/useSubmitAlignedSimQuote.test.ts src/pages/SwapPage.test.tsx -t "356"` - [ ] Swap: type `1`, wait for quote, append `0` quickly → **Calculating…** until quote for `10` settles - [ ] Swap: settled quote → submit → on-chain pay matches displayed amount - [ ] Trade market: repeat debounce + on-chain checks with hybrid on - [ ] Stable amount → wait for 10s sim refetch → submit disabled while fetching ### MR https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/143 ### Follow-ups Optional: Playwright fast-type → assert submit disabled until quote stable.
PlasticDigits commented 2026-06-10 11:19:03 +00:00 (Migrated from gitlab.com)

mentioned in commit 43d749cf98

mentioned in commit 43d749cf98fe840872f48440ad122e23556cc706
Brouie commented 2026-06-11 02:07:12 +00:00 (Migrated from gitlab.com)

Source + unit pass on the merged fix (5a25419 + the 5781ad1 component regression, main 3169af0).

  • Checklist vitest row, run verbatim: 3/3 #356 tests green. Unfiltered: quoteDebounce 8/8, useSubmitAlignedSimQuote 3/3. Lint: 0 errors, the 5 exhaustive-deps warnings are the pre-existing ones in untouched files.
  • Mutation alignment verified in source: SwapPage's mutationFn opens with assertSubmitQuotePayRawAligned, and payRaw / minReceived / indexer ops all come off the same debounced simData snapshot in every branch (native, multihop, direct hybrid). Read the pre-fix tree for contrast — mutationFn consumed live rawInputAmount in seven places with no guard. That's the filed gap, and it's closed.
  • Trade market panel: sim AND submit hybrid params both derive from the debounced amount (debouncedHybrid -> hybridParamsWithSubmitCap), and canSubmit folds isSubmitReady including isFetching. liveHybrid only feeds the gas-estimate display now.
  • 10s refetch case: isSubmitQuoteStale treats fetching-on-active-key as stale; this one is proven discriminating — the same unit test fails by assertion against the pre-fix quoteDebounce.ts.

Being explicit about what proves what: the SwapPage "Calculating..." component test also passes on the pre-fix tree — that button gate came in earlier with b44758c. So the component test locks the UX, but the actual #356 payload fix is proven by the source diff plus the quoteDebounce/hook units above, not by it.

Small observations, none blocking:

  • assertSubmitQuotePayRawAligned doesn't cover bookInputHuman — the advanced book leg is still read live at submit. The stale gate covers edits via the sim query key, but the in-mutation guard doesn't see it. Narrow click-vs-edit race, P3.
  • buildSubmitAlignedSimPayload is exported + unit-tested but nothing consumes it (both surfaces use the hook). Dead surface, nit.

Unrelated to this fix: SwapPage.test.tsx carries 2 pre-existing failures (the #293 slippage-block test and the #329 fallback-label test) that fail identically on the pre-fix base. Digging into those separately — not a #356 problem.

Left from the checklist: the browser rows — type-1-append-0 walkthrough, on-chain pay-matches-display, the trade-market hybrid run, live 10s refetch gating. Next laptop batch.

Source + unit pass on the merged fix (5a25419 + the 5781ad1 component regression, main 3169af0). - Checklist vitest row, run verbatim: 3/3 #356 tests green. Unfiltered: quoteDebounce 8/8, useSubmitAlignedSimQuote 3/3. Lint: 0 errors, the 5 exhaustive-deps warnings are the pre-existing ones in untouched files. - Mutation alignment verified in source: SwapPage's mutationFn opens with assertSubmitQuotePayRawAligned, and payRaw / minReceived / indexer ops all come off the same debounced simData snapshot in every branch (native, multihop, direct hybrid). Read the pre-fix tree for contrast — mutationFn consumed live rawInputAmount in seven places with no guard. That's the filed gap, and it's closed. - Trade market panel: sim AND submit hybrid params both derive from the debounced amount (debouncedHybrid -> hybridParamsWithSubmitCap), and canSubmit folds isSubmitReady including isFetching. liveHybrid only feeds the gas-estimate display now. - 10s refetch case: isSubmitQuoteStale treats fetching-on-active-key as stale; this one is proven discriminating — the same unit test fails by assertion against the pre-fix quoteDebounce.ts. Being explicit about what proves what: the SwapPage "Calculating..." component test also passes on the pre-fix tree — that button gate came in earlier with b44758c. So the component test locks the UX, but the actual #356 payload fix is proven by the source diff plus the quoteDebounce/hook units above, not by it. Small observations, none blocking: - assertSubmitQuotePayRawAligned doesn't cover bookInputHuman — the advanced book leg is still read live at submit. The stale gate covers edits via the sim query key, but the in-mutation guard doesn't see it. Narrow click-vs-edit race, P3. - buildSubmitAlignedSimPayload is exported + unit-tested but nothing consumes it (both surfaces use the hook). Dead surface, nit. Unrelated to this fix: SwapPage.test.tsx carries 2 pre-existing failures (the #293 slippage-block test and the #329 fallback-label test) that fail identically on the pre-fix base. Digging into those separately — not a #356 problem. Left from the checklist: the browser rows — type-1-append-0 walkthrough, on-chain pay-matches-display, the trade-market hybrid run, live 10s refetch gating. Next laptop batch.
Brouie commented 2026-06-11 02:12:51 +00:00 (Migrated from gitlab.com)

mentioned in issue #337

mentioned in issue #337
Brouie commented 2026-06-11 02:55:11 +00:00 (Migrated from gitlab.com)

Browser + on-chain half done — full checklist covered now, between the agent pass, the unit layer (previous note), and this browser run against the live stack at 3169af0.

  • Swap debounce: typed 1 -> settle -> quick append 0: button drops to Calculating... and stays unclickable until the quote for 10 settles. PASS.
  • Refetch guard: stable amount through the 10s sim refetch — submit disables while fetching, re-enables after. PASS.
  • Swap on-chain: tx 43C8A25A — broadcast pay 1,000,000 uEMBER == the displayed 1 exactly, msg carries max_spread 0.005 (the 0.5% tolerance shown). Executed return 984,626 vs displayed 0.9846, above the 0.9797 min received. swap_events row 133 reconciles.
  • Trade market hybrid on-chain: tx A88B4DA5 on the EMBER/JADE pair — pure-book hybrid (pool_input 0, book_input = the full debounced 1,000,000, max_maker_fills 8). min_return 86,418,414 = the quoted 86,852,678 x 0.995 floored, i.e. the slippage floor was derived from the same debounced quote snapshot as the pay amount and the hybrid split. Executed return 86,852,678, all book leg. swap_events row 134.

That min_return-matches-quote-times-tolerance detail on the hybrid tx is the cleanest live proof of the fix: pay, split, and floor all come off one snapshot.

@PlasticDigits #356 checklist is fully covered — good to close from my side. The two P3 observations from my earlier note (bookInputHuman outside the in-mutation assert, dead buildSubmitAlignedSimPayload export) stand as follow-up material; neither blocks.

Browser + on-chain half done — full checklist covered now, between the agent pass, the unit layer (previous note), and this browser run against the live stack at 3169af0. - Swap debounce: typed 1 -> settle -> quick append 0: button drops to Calculating... and stays unclickable until the quote for 10 settles. PASS. - Refetch guard: stable amount through the 10s sim refetch — submit disables while fetching, re-enables after. PASS. - Swap on-chain: tx 43C8A25A — broadcast pay 1,000,000 uEMBER == the displayed 1 exactly, msg carries max_spread 0.005 (the 0.5% tolerance shown). Executed return 984,626 vs displayed 0.9846, above the 0.9797 min received. swap_events row 133 reconciles. - Trade market hybrid on-chain: tx A88B4DA5 on the EMBER/JADE pair — pure-book hybrid (pool_input 0, book_input = the full debounced 1,000,000, max_maker_fills 8). min_return 86,418,414 = the quoted 86,852,678 x 0.995 floored, i.e. the slippage floor was derived from the same debounced quote snapshot as the pay amount and the hybrid split. Executed return 86,852,678, all book leg. swap_events row 134. That min_return-matches-quote-times-tolerance detail on the hybrid tx is the cleanest live proof of the fix: pay, split, and floor all come off one snapshot. @PlasticDigits #356 checklist is fully covered — good to close from my side. The two P3 observations from my earlier note (bookInputHuman outside the in-mutation assert, dead buildSubmitAlignedSimPayload export) stand as follow-up material; neither blocks.
PlasticDigits commented 2026-06-11 10:28:43 +00:00 (Migrated from gitlab.com)

mentioned in issue #360

mentioned in issue #360
PlasticDigits commented 2026-06-11 10:28:43 +00:00 (Migrated from gitlab.com)

marked as related to #360

marked as related to #360
PlasticDigits commented 2026-06-11 14:04:17 +00:00 (Migrated from gitlab.com)

Verification — GitLab #356 (agent:verify)

Independent verification on main (cd8c27a, includes merge 49fdaf9 / 43d749c).

Automated — PASS

Check Command / evidence Result
isSubmitQuoteStale + legacy alias + payload helpers npx vitest run src/utils/quoteDebounce.test.ts 8/8 green
useSubmitAlignedSimQuote hook npx vitest run src/hooks/useSubmitAlignedSimQuote.test.ts 3/3 green
SwapPage stale-submit UX regression npx vitest run src/pages/SwapPage.test.tsx -t "356" 1/1 green
Lint make lint-frontend 0 errors (5 pre-existing exhaustive-deps warnings in untouched files)

Source / mutation alignment — PASS

  • Swap (SwapPage.tsx): swapMutation opens with assertSubmitQuotePayRawAligned, uses submitPayRaw (debounced) for all on-chain pay paths, and reads minReceived / indexerOperations from the same simData snapshot.
  • Trade market (TradeMarketOrderPanel.tsx): canSubmit folds isSubmitReady; hybrid sim + submit both use debouncedHybrid; mutation uses submitPayRaw + aligned minReceived.
  • Stale gating: isSubmitQuoteStale covers typed≠debounced, isPlaceholderData, and isFetching (10s refetch case — unit-tested; fails on pre-fix quoteDebounce.ts).

Manual (LocalTerra + make dev) — PASS

Re-confirmed on a fresh LocalTerra provision (make setup-cloud-localterra, indexer :3001, make dev :5173). Full browser + on-chain walkthrough was already completed on 3169af0 by @Brouie (issue comment 2026-06-11); this pass independently ran automated checks and source review on current main.

Scenario Result
Swap debounce (1 → append 0) PASS (@Brouie) — button Calculating… / disabled until quote for 10 settles
Swap on-chain pay matches displayed quote PASS (@Brouie) — tx 43C8A25A, pay 1 EMBER == displayed amount
Trade market hybrid debounce + on-chain PASS (@Brouie) — tx A88B4DA5, min_return derived from same debounced snapshot as pay + hybrid split
10s sim refetch guard PASS (@Brouie) — submit disabled while isFetching, re-enables after

Acceptance criteria mapping

  1. Single submit snapshot — PASS (useSubmitAlignedSimQuote)
  2. Mutation uses snapshot, not live input — PASS (submitPayRaw + assertSubmitQuotePayRawAligned)
  3. Submit disabled while quote not authoritative — PASS (isFetching included)
  4. Hybrid book leg debounced — PASS (debouncedHybrid in Trade market; Swap sim key includes debounced pay)
  5. Regression tests — PASS (unit + SwapPage component)

Follow-ups (non-blocking, from prior review)

  • P3: assertSubmitQuotePayRawAligned does not cover advanced Swap bookInputHuman (stale gate via sim key covers typical edits).
  • Optional: Playwright fast-type → assert submit disabled until quote stable.

Closing — all acceptance and verification criteria satisfied on main; no additional MR required from this verify pass.

## Verification — GitLab #356 (agent:verify) Independent verification on `main` (`cd8c27a`, includes merge `49fdaf9` / `43d749c`). ### Automated — PASS | Check | Command / evidence | Result | |-------|-------------------|--------| | `isSubmitQuoteStale` + legacy alias + payload helpers | `npx vitest run src/utils/quoteDebounce.test.ts` | **8/8** green | | `useSubmitAlignedSimQuote` hook | `npx vitest run src/hooks/useSubmitAlignedSimQuote.test.ts` | **3/3** green | | SwapPage stale-submit UX regression | `npx vitest run src/pages/SwapPage.test.tsx -t "356"` | **1/1** green | | Lint | `make lint-frontend` | **0 errors** (5 pre-existing exhaustive-deps warnings in untouched files) | ### Source / mutation alignment — PASS - **Swap** (`SwapPage.tsx`): `swapMutation` opens with `assertSubmitQuotePayRawAligned`, uses `submitPayRaw` (debounced) for all on-chain pay paths, and reads `minReceived` / `indexerOperations` from the same `simData` snapshot. - **Trade market** (`TradeMarketOrderPanel.tsx`): `canSubmit` folds `isSubmitReady`; hybrid sim + submit both use `debouncedHybrid`; mutation uses `submitPayRaw` + aligned `minReceived`. - **Stale gating**: `isSubmitQuoteStale` covers typed≠debounced, `isPlaceholderData`, and **`isFetching`** (10s refetch case — unit-tested; fails on pre-fix `quoteDebounce.ts`). ### Manual (LocalTerra + `make dev`) — PASS Re-confirmed on a fresh LocalTerra provision (`make setup-cloud-localterra`, indexer :3001, `make dev` :5173). Full browser + on-chain walkthrough was already completed on `3169af0` by @Brouie (issue comment 2026-06-11); this pass independently ran automated checks and source review on current `main`. | Scenario | Result | |----------|--------| | Swap debounce (1 → append 0) | **PASS** (@Brouie) — button **Calculating…** / disabled until quote for `10` settles | | Swap on-chain pay matches displayed quote | **PASS** (@Brouie) — tx `43C8A25A`, pay 1 EMBER == displayed amount | | Trade market hybrid debounce + on-chain | **PASS** (@Brouie) — tx `A88B4DA5`, `min_return` derived from same debounced snapshot as pay + hybrid split | | 10s sim refetch guard | **PASS** (@Brouie) — submit disabled while `isFetching`, re-enables after | ### Acceptance criteria mapping 1. Single submit snapshot — **PASS** (`useSubmitAlignedSimQuote`) 2. Mutation uses snapshot, not live input — **PASS** (`submitPayRaw` + `assertSubmitQuotePayRawAligned`) 3. Submit disabled while quote not authoritative — **PASS** (`isFetching` included) 4. Hybrid book leg debounced — **PASS** (`debouncedHybrid` in Trade market; Swap sim key includes debounced pay) 5. Regression tests — **PASS** (unit + SwapPage component) ### Follow-ups (non-blocking, from prior review) - P3: `assertSubmitQuotePayRawAligned` does not cover advanced Swap `bookInputHuman` (stale gate via sim key covers typical edits). - Optional: Playwright fast-type → assert submit disabled until quote stable. **Closing** — all acceptance and verification criteria satisfied on `main`; no additional MR required from this verify pass.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-11 14:04:21 +00:00
PlasticDigits commented 2026-06-11 15:31:35 +00:00 (Migrated from gitlab.com)

mentioned in merge request !866

mentioned in merge request !866
PlasticDigits commented 2026-06-12 04:46:02 +00:00 (Migrated from gitlab.com)

mentioned in issue #361

mentioned in issue #361
PlasticDigits commented 2026-06-12 05:05:52 +00:00 (Migrated from gitlab.com)

mentioned in issue #366

mentioned in issue #366
PlasticDigits commented 2026-06-12 05:21:50 +00:00 (Migrated from gitlab.com)

mentioned in merge request !875

mentioned in merge request !875
PlasticDigits commented 2026-06-19 12:58:57 +00:00 (Migrated from gitlab.com)

mentioned in merge request !929

mentioned in merge request !929
PlasticDigits commented 2026-06-30 22:22:37 +00:00 (Migrated from gitlab.com)

mentioned in merge request !994

mentioned in merge request !994
PlasticDigits commented 2026-07-13 09:33:33 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1020

mentioned in merge request !1020
PlasticDigits commented 2026-07-13 10:33:12 +00:00 (Migrated from gitlab.com)

mentioned in issue #485

mentioned in issue #485
PlasticDigits commented 2026-07-15 04:06:40 +00:00 (Migrated from gitlab.com)

mentioned in issue #496

mentioned in issue #496
PlasticDigits commented 2026-07-25 04:32:58 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1031

mentioned in merge request !1031
PlasticDigits commented 2026-08-16 09:55:50 +00:00 (Migrated from gitlab.com)

mentioned in issue #533

mentioned in issue #533
PlasticDigits commented 2026-08-18 00:43:32 +00:00 (Migrated from gitlab.com)

mentioned in issue #559

mentioned in issue #559
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#356
No description provided.