Swap/market submit must align pay amount with debounced sim quote (minReceived, indexer ops) #356
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:implement
agent:implement
agent:open_issues
agent:ready
agent:research
agent:security_audit
agent:verify
architecture
backend
blocker:hybrid
blocker:launch
blocker:limit-orders
blocker:v2
block:log_only
block:security
bug
ci
contracts
correctness
deploy
dev
devops
docs
documentation
duplicate
e2e
enhancement
epic
feature
frontend
functional-completion
gas
good first issue
governance
help wanted
high-risk
hooks
hybrid
indexer
infra
infrastructure
integrators
invalid
launch-blocker
limit-orders
localnet
localterra
low priority
missing-implementation
needs-design
ops
performance
priority
high
priority
medium
product
qa
QA
question
ready
ready
research
scripts
security
security-hardening
smartcontracts
tech-debt
testing
ux
UX
v2
verification
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#356
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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, indexerrouter_operations, and displayed receive amounts come fromsimQuery.datakeyed on the debounced amount.A follow-up UI guard (
isSimQuoteStaleForSubmit) disables the button whenrawInputAmount !== debouncedRawInputAmountorsimQuery.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
debouncedRawInputAmount(query key +queryFn)swapMutation)rawInputAmount/marketAmountHumansimQuery.data(debounced quote)Failure mode: User types
100→ debounced quote loads for100→ user edits to1000before debounce settles (or duringkeepPreviousDataplaceholder) → UI may still show the100receive/min-received line → submit can send1000on-chain withmin_return/ hop min-returns / hybrid splits derived from the100quote → unexpected slippage revert or worse execution than displayed.Partial mitigation already on
mainfrontend-dapp/src/utils/quoteDebounce.ts—isSimQuoteStaleForSubmit()blocks the Swap button and Trade marketcanSubmitwhen 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-keysimQuery.isFetchingduring 10srefetchIntervalrefresh).Relevant code
Debounce + stale helper
frontend-dapp/src/hooks/useDebouncedValue.tsfrontend-dapp/src/utils/quoteDebounce.ts—SIM_QUOTE_DEBOUNCE_MS(350),isSimQuoteStaleForSubmitSwap (
/)frontend-dapp/src/pages/SwapPage.tsxdebouncedInputAmount,debouncedRawInputAmount→simQueryKey,simQuery.queryFnusessimRaw = debouncedRawInputAmountsimQuoteStale→ buttonCalculating...when staleswapMutationusesrawInputAmountforexecuteNativeSwap,executeMultiHopSwap,swap,enrichSwapOperationsWithHopMinReturns,computeDirectHybridMinReturn; readsidxOps/minReceivedfromsimData(simQuery.data)Trade market (
/trade→ Market tab)frontend-dapp/src/components/trade/TradeMarketOrderPanel.tsxdebouncedMarketAmount,debouncedRawInputAmountinsimQuerykey/queryFnsimQuoteStaleincanSubmitswapMutationusesmarketAmountHuman→rawfor on-chain amount;idxOps/minReceivedfromsimQuery.datacomputeHybridParamsuses liverawInputAmountfor hybrid split while sim hybrid path uses debouncedsimRawDocs / skills
skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md— quote debounce (#346) table; submit must stay execution-aligned with displayAcceptance criteria
minReceived,indexerOperations, hybrid params, and route display all refer to the same settled quote inputs (debounced pay size + matching sim result).swapMutation(Swap + Trade market) reads pay amount and quote-derived fields from a sharedsubmitQuoteobject (or re-fetches sim for the exact submit amount insidemutationFnbefore broadcast). LiveinputAmount/marketAmountHumanmust not be the sole on-chain pay size whileminReceivedcomes from a different sim key.simQuery.isFetchingfor the active debounced key (and any other state where displayed receive ≠ sim that will be submitted).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, wait for quote → append0quickly (10) → confirm Swap stays disabled / Calculating... until quote refreshes for10; only then enable./trade/:pairAddrMarket tab with hybrid on.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)SwapPage.test.tsx/ Trade market panel tests for stale-submit gatingdata-testidon swap/trade submit buttons)Recommended fix
useSubmitAlignedSimQuote(or inline equivalent) that exposes:debouncedRawPayAmountsimQueryresult for that keyisSubmitReady= debounced settled && !placeholder && !fetching && sim successsubmitPayload={ payRaw, minReceived, indexerOperations, hybrid }all derived togetherswapMutationhandlers to consumesubmitPayloadonly; assertpayRaw === debouncedRawPayAmountat top ofmutationFn.isSimQuoteStaleForSubmit(or rename toisSubmitQuoteStale) to includesimQuery.isFetchingwhen the fetch is for the current debounced key.docs/frontend.mdandskills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.mdnext to #346 row.Related
b44758c— UI-only stale submit guard (partial)marked as related to #346
mentioned in commit
5a254194e2mentioned in commit
49fdaf95e6Implementation 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 debouncedminReceived/ indexer ops.Changes (merged to
mainin49fdaf9)useSubmitAlignedSimQuote— bundlessubmitPayRaw,minReceived, andsimDatafor both surfaces;isSubmitReadygates submit.isSubmitQuoteStale— extended stale detection: typed raw ≠ debounced key,isPlaceholderData, orsimQuery.isFetching(covers 10s refetch).swapMutationrefactor — Swap + Trade market readsubmitPayRaw(debounced) for on-chain pay;assertSubmitQuotePayRawAligneddefensive guard inmutationFn.debouncedHybrid); live hybrid kept for gas/escrow UX gates only.quoteDebounce.test.ts,useSubmitAlignedSimQuote.test.ts(11 cases).docs/frontend.md; code map + regression steps inskills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md.Verification checklist
Automated
cd frontend-dapp && npx vitest run src/utils/quoteDebounce.test.ts src/hooks/useSubmitAlignedSimQuote.test.tsmake lint-frontendManual (LocalTerra +
make dev)1, wait for quote, append0quickly → button stays Calculating… / disabled until quote for10settles./trade/:pairAddrMarket tab with hybrid enabled.Follow-ups
None required for the core #356 acceptance criteria. Optional later: Playwright coverage for fast type → assert submit disabled until quote stable (
data-testidon swap/trade submit buttons).Requesting verification from the QA agent team when convenient.
mentioned in commit
5781ad1043mentioned in merge request !861
Agent verification (local1/356-impl-submit-gating)
Core fix already on
main(5a25419, merge49fdaf9):useSubmitAlignedSimQuote,isSubmitQuoteStale(+isFetching),submitPayRawin Swap + Trade market mutations, debounced hybrid params.This pass adds the remaining SwapPage component regression for stale-submit gating and opens !861.
Verified
quoteDebounce.test.ts,useSubmitAlignedSimQuote.test.ts, newSwapPagenested#356test (Calculating… while typed ≠ debounced)make lint-frontendclean (pre-existing warnings only)Checklist for QA
npx vitest run src/utils/quoteDebounce.test.ts src/hooks/useSubmitAlignedSimQuote.test.ts src/pages/SwapPage.test.tsx -t "356"1, wait for quote, append0quickly → Calculating… until quote for10settlesMR
https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/143
Follow-ups
Optional: Playwright fast-type → assert submit disabled until quote stable.
mentioned in commit
43d749cf98Source + unit pass on the merged fix (
5a25419+ the5781ad1component regression, main3169af0).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:
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.
mentioned in issue #337
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.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.
mentioned in issue #360
marked as related to #360
Verification — GitLab #356 (agent:verify)
Independent verification on
main(cd8c27a, includes merge49fdaf9/43d749c).Automated — PASS
isSubmitQuoteStale+ legacy alias + payload helpersnpx vitest run src/utils/quoteDebounce.test.tsuseSubmitAlignedSimQuotehooknpx vitest run src/hooks/useSubmitAlignedSimQuote.test.tsnpx vitest run src/pages/SwapPage.test.tsx -t "356"make lint-frontendSource / mutation alignment — PASS
SwapPage.tsx):swapMutationopens withassertSubmitQuotePayRawAligned, usessubmitPayRaw(debounced) for all on-chain pay paths, and readsminReceived/indexerOperationsfrom the samesimDatasnapshot.TradeMarketOrderPanel.tsx):canSubmitfoldsisSubmitReady; hybrid sim + submit both usedebouncedHybrid; mutation usessubmitPayRaw+ alignedminReceived.isSubmitQuoteStalecovers typed≠debounced,isPlaceholderData, andisFetching(10s refetch case — unit-tested; fails on pre-fixquoteDebounce.ts).Manual (LocalTerra +
make dev) — PASSRe-confirmed on a fresh LocalTerra provision (
make setup-cloud-localterra, indexer :3001,make dev:5173). Full browser + on-chain walkthrough was already completed on3169af0by @Brouie (issue comment 2026-06-11); this pass independently ran automated checks and source review on currentmain.10settles43C8A25A, pay 1 EMBER == displayed amountA88B4DA5,min_returnderived from same debounced snapshot as pay + hybrid splitisFetching, re-enables afterAcceptance criteria mapping
useSubmitAlignedSimQuote)submitPayRaw+assertSubmitQuotePayRawAligned)isFetchingincluded)debouncedHybridin Trade market; Swap sim key includes debounced pay)Follow-ups (non-blocking, from prior review)
assertSubmitQuotePayRawAligneddoes not cover advanced SwapbookInputHuman(stale gate via sim key covers typical edits).Closing — all acceptance and verification criteria satisfied on
main; no additional MR required from this verify pass.mentioned in merge request !866
mentioned in issue #361
mentioned in issue #366
mentioned in merge request !875
mentioned in merge request !929
mentioned in merge request !994
mentioned in merge request !1020
mentioned in issue #485
mentioned in issue #496
mentioned in merge request !1031
mentioned in issue #533
mentioned in issue #559