feat(frontend): typeable You Receive on wrap/native Swap #1256
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#1256
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
Follow-up to #713 / !1200. Retail Swap still treats You Receive as quote chrome on the routes traders actually land on.
#713 Q713-4 made You Receive an independent
<input>only whencanReverseQuoteis true: a direct factory CW20 pair, not wrap/unwrap, not native wrap-mapper. Default retail pay/receive is native LUNC plus a listed wrapped hub token (defaultRetailSwapTokenPair→uluna+ cUSTC when present). That path setsnativeRouteInfo, socanReverseQuoteis false and You Receive is a<div>(data-testid="swap-you-receive"). Traders cannot type the amount they want to receive.Do not reopen #713 (URL rewrite, Share, Create/Trade prefill already merged; leftover verify is #714). This issue is the remaining product slice: You Receive is always the field you type when you care about output size, including wrap/native and client-BFS multi-hop, without an indexer exact-out solver.
Current codebase
Receive is an input only on direct CW20
SwapPage.tsx:When true, You Receive is
<input onChange={applyTypedAmount(..., 'output')}>and You Pay showsreverseSimulateSwap(hybrid_reverse_simulation, #190). When false, You Receive is a non-editable<div>of the forward quote.applyTypedAmountalready switchesexactFieldto'output'and writesexactField=outputinto canonical search. The gate is the render, not the parser.Reverse helpers already exist off Swap
reverseSimulateSwaphybrid_reverse_simulationreverseSimulateMultiHopSwapReverseSimulateSwapOperationssimulateNativeSwapwrapGrossForNetCw20netNativeAfterUnwrapGET /route/solve(quoteCw20ViaRouteSolve) is still offer-in. Q713-10 / #713 out-of-scope: no indexer exact-out. Keep that.Execute is offer-in
Reverse UI must not change broadcast: offer amount +
min_return/ hop floors. #678 quote-only / insufficient / 5% confirm-again / 30% Expert / 99% / blacklist / pause / freeze stay. Max and fraction buttons alreadysetExactField('input')(!1200 merge fix).Why the new implementation is needed
/that field is not an input.GET /route/solve.This is Swap quote UI + wrap/native reverse accounting. No contract migrate, no indexer exact-out endpoint, no Coolify env.
Constraints / guardrails
slippage/expertMode/recipient/pool_only/hybrid_optimizefrom the URL (#596).GET /route/solve. If the live path has no client operations (indexer-only CW20 when BFS misses), ignoreexactField=output(silent #489) and keep pay-sided — same as today’s multi-hop ignore, but the control may still be an<input>that does not reverse until operations exist.wrapGrossForNetCw20) so post-fee_wrap_bpsCW20 covers the reverse offer. Unwrap-output reverse: typed You Receive is post-unwrap-fee then post–burn-tax;routerMinReceiveBasestays post-fee pre-tax (R3). Do not burn-tax wrap_deposit. Do not claim a flat 2% if tax applies.reverseSimulateSwapon the factory pair; do not send natives into pair reverse sim.replacesexactAmount+exactField=outputvia existingcanonicalSwapSearch. Amount debounce =SIM_QUOTE_DEBOUNCE_MS. No history spam. Gems: production does not write gem ids (#562).assertSubmitQuotePayRawAligned/ hybrid assert still apply.min_returnstays pre-tax (executeAmountOut). Do not double-count extra-debit.exactField=input). Do not write spendable pay-human into You Receive.localStorage. Do not change nginx. U1: UST1 Swap links remain AMM.Relevant files
frontend-dapp/src/pages/SwapPage.tsx<input>; broadencanReverseQuote; reverse query by route classfrontend-dapp/src/services/terraclassic/router.tsreverseSimulateMultiHopSwap; wrap/native reverse using existing wrap fee helpersfrontend-dapp/src/services/terraclassic/pair.tsreverseSimulateSwapunchangedfrontend-dapp/src/utils/payInvoice.tswrapGrossForNetCw20— extract to a shared wrap-math helper if Swap imports it (do not copy Swap execute into invoice)frontend-dapp/src/utils/cw20RouteSolveQuote.tsfrontend-dapp/src/utils/quoteDebounce.tsdocs/frontend.mdskills/AGENTS_FRONTEND_SWAP_URL_SYNC.mdskills/AGENTS_WRAP_UNWRAP_BURN_TAX.mdfrontend-dapp/src/pages/SwapPage.queryParams.test.tsxscripts/qa/verify-issue-713.shverify-issue-<this>Recommended direction
<input>(same classes /inputMode="decimal"as You Pay) whenever a receive token is selected. Calculating / quoting remainsaria-busy+ placeholder empty, not a<div>swap. Direct wrap/unwrap (mapper-only, no pair) can reverse via wrap math alone.reverseSimulateSwap(today).findRoute/findRouteWithNativeSupport) →reverseSimulateMultiHopSwap(ask, operations)then wrap-input gross-up / unwrap-output ask gross-up so display You Receive matches W8/W9.offer_amount(native gross if wrap-input) feeds the existing forward sim + min-receive / hop floors. Do not broadcast ask size assend.amount.payInvoice.tsinto a tiny wrap helper used by Swap + invoice so fee bps cannot drift.<div>on wrap/native” with the new Q713-4. Keep “no indexer exact-out”.Acceptance criteria
/native LUNC ↔ listed wrapped hub token): You Receive is an<input>. Typing a legal amount (≤ 24 chars,isPositiveDecimalAmount) setsexactField=output, updates You Pay from reverse wrap/native quote, URLreplacesexactAmount+exactField=output.reverseSimulateSwap; execute offer-in).simulateNativeSwapinverses within rounding of 1 raw unit.operations: reverse viaReverseSimulateSwapOperationson those ops; still offer-in execute; hop floors unchanged./route/solveexact-out. No crash. Silent ignore of output field as reverse source.min_returnpre-tax (#615).make verify-issue-713/verify-issue-678/verify-issue-512(or wrap tax greps) stay green. Newmake verify-issue-<id>.Test plan (functional paths)
/default native↔wrapped<input>; type1→ pay reverse-quoted; URL hasexactField=outputexactField=outputrouterMinReceiveBaseexactAmountomitted; exactField dropsexactField=outputpreserved; reverse re-runs on new pair class/route/solvebrute forceVitest: table-driven wrap reverse gross/net; native route reverse uses
reverseSimulateMultiHopSwapmock; Swap RTL default pair receive input. Playwright (PLAYWRIGHT_SKIP_CHAIN=1OK): receive input present on/; typing updates pay. 5 workers.Test plan (attack, hack, and abuse)
Wallet/submit surface — focused, not a full DeFi suite.
isPositiveDecimalAmount/ 24-char cap; ignore fieldexactField=outputon paused/frozen listedVerification criteria
make verify-issue-<id>(RTL receive input on default pair + wrap reverse math + docs/skills greps for Q713-4).make verify-issue-713,make verify-issue-678, wrap tax verify if present,python3 scripts/check_chrome_nesting.pystay green./You Receive is an input; typing a small amount rewritesexactField=output.make dev→/→ type You Receive on LUNC↔cUSTC (or listed wrap hub) → You Pay moves → Connect still required → no auto-submit. Repeat a listed CW20/CW20 pair (Q713-4). Hostile/overlong amount ignored.Out of scope
GET /route/solve./tradeticket exact-out.hybrid_reverse_simulation/ router query schema.Definition of Ready
Given / When / Then
G1. Given Swap
/with the default retail pair (native LUNC and listed wrapped hub token), when a trader types a legal amount into You Receive, then You Pay shows the wrap/native reverse offer, the bar isexactField=output, and Swap does not broadcast until the existing forward quote + min-received path is satisfied.G2. Given a direct factory CW20 pair, when they type You Receive, then You Pay is
hybrid_reverse_simulationoffer and execute remains offer-in + min received (Q713-4).G3. Given a path with no client swap operations, when they type You Receive, then the app does not call indexer exact-out and does not submit an ask-sized
send.First-pass model recommendation
Recommendation: grok-high
Rationale: Not Composer. Scope is Swap quoting plus wrap-mapper fee/tax and router reverse operations — more than three production files and a cross-cutting quote/execute protocol. Wrong wrap gross-up over-debits native LUNC; unwrap reverse that forgets InstantWithdraw tax lies about You Receive (W8/W9). Comparable to control-plane PRs that span quoting plus persisted submit alignment, not a single local helper. Verify with wrap numeric fixtures, Swap RTL, and
make verify-issue-713still green.