feat(frontend): typeable You Receive on wrap/native Swap #1256

Open
opened 2026-09-12 13:25:10 +00:00 by PlasticDigits · 0 comments

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 when canReverseQuote is 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 sets nativeRouteInfo, so canReverseQuote is 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:

const canReverseQuote = Boolean(directPair) && isDirect && !isWrapOrUnwrap && !nativeRouteInfo

When true, You Receive is <input onChange={applyTypedAmount(..., 'output')}> and You Pay shows reverseSimulateSwap (hybrid_reverse_simulation, #190). When false, You Receive is a non-editable <div> of the forward quote.

applyTypedAmount already switches exactField to 'output' and writes exactField=output into canonical search. The gate is the render, not the parser.

Reverse helpers already exist off Swap

Helper What it does Used by Swap reverse today?
reverseSimulateSwap Pair hybrid_reverse_simulation Direct CW20 only
reverseSimulateMultiHopSwap Router ReverseSimulateSwapOperations No (invoice #595)
simulateNativeSwap Wrap fee + hop forward sim + unwrap tax Forward only
wrapGrossForNetCw20 Native gross so wrap mint ≥ net CW20 Invoice wrap-input, not Swap
netNativeAfterUnwrap Unwrap fee then InstantWithdraw burn tax (W9) Forward display

GET /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 already setExactField('input') (!1200 merge fix).

Why the new implementation is needed

  1. Default Swap cannot take an output size. Uniswap-style “I want N of the receive token” is how people size a trade. On dex.cl8y.com / that field is not an input.
  2. #713 did not leave this as a bug in Q713-4. Direct-only was intentional because wrap/native reverse was not wired. The gap is now the product request: type You Receive for the amount they want to receive.
  3. The LCD reverse path is already on the router. Invoice checkout reverse-sims operations then grosses wrap. Swap should reuse that family, not invent a second solver and not binary-search 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

  • Execute stays offer-in + min received. UI exact-out is a quote direction only. Do not skip #678 / 5–30–99 / Expert / blacklist / pause / freeze. Do not honor slippage / expertMode / recipient / pool_only / hybrid_optimize from the URL (#596).
  • No indexer exact-out. Do not fake exact-out by binary-searching GET /route/solve. If the live path has no client operations (indexer-only CW20 when BFS misses), ignore exactField=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.
  • Wrap / unwrap honesty (W8–W11, #512 / #516). Wrap-input reverse: native You Pay is wrap-mapper gross (wrapGrossForNetCw20) so post-fee_wrap_bps CW20 covers the reverse offer. Unwrap-output reverse: typed You Receive is post-unwrap-fee then post–burn-tax; routerMinReceiveBase stays post-fee pre-tax (R3). Do not burn-tax wrap_deposit. Do not claim a flat 2% if tax applies.
  • Direct CW20 Q713-4 stays. reverseSimulateSwap on the factory pair; do not send natives into pair reverse sim.
  • Canonical URL. Typing receive still replaces exactAmount + exactField=output via existing canonicalSwapSearch. Amount debounce = SIM_QUOTE_DEBOUNCE_MS. No history spam. Gems: production does not write gem ids (#562).
  • Submit alignment (#356). Reverse mode: submit pay raw is the reverse offer (then existing forward quote + min received), not the typed receive human. assertSubmitQuotePayRawAligned / hybrid assert still apply.
  • Tax tokens (#615). You Receive typed amount is net when the receive token is community-tax. Execute min_return stays pre-tax (executeAmountOut). Do not double-count extra-debit.
  • Max / fraction. Stay pay-sided (exactField=input). Do not write spendable pay-human into You Receive.
  • No lecture banner when reverse is unavailable (#489).
  • Do not persist amounts in localStorage. Do not change nginx. U1: UST1 Swap links remain AMM.

Relevant files

Path Why
frontend-dapp/src/pages/SwapPage.tsx Always render receive <input>; broaden canReverseQuote; reverse query by route class
frontend-dapp/src/services/terraclassic/router.ts reverseSimulateMultiHopSwap; wrap/native reverse using existing wrap fee helpers
frontend-dapp/src/services/terraclassic/pair.ts Direct reverseSimulateSwap unchanged
frontend-dapp/src/utils/payInvoice.ts wrapGrossForNetCw20 — extract to a shared wrap-math helper if Swap imports it (do not copy Swap execute into invoice)
frontend-dapp/src/utils/cw20RouteSolveQuote.ts Offer-in only — do not fake exact-out
frontend-dapp/src/utils/quoteDebounce.ts Submit alignment in reverse mode
docs/frontend.md Amend Q713-4 / Q711-6 (receive input on wrap/native; still no indexer exact-out)
skills/AGENTS_FRONTEND_SWAP_URL_SYNC.md Q713-4: wrap/native reverse; indexer-only still ignore
skills/AGENTS_WRAP_UNWRAP_BURN_TAX.md Reverse quotes must keep W8/W9
frontend-dapp/src/pages/SwapPage.queryParams.test.tsx Q713-4 plus wrap/native receive typing
scripts/qa/verify-issue-713.sh Stay green; add verify-issue-<this>
  1. Always render You Receive as <input> (same classes / inputMode="decimal" as You Pay) whenever a receive token is selected. Calculating / quoting remains aria-busy + placeholder empty, not a <div> swap. Direct wrap/unwrap (mapper-only, no pair) can reverse via wrap math alone.
  2. Route-class reverse quote (pure helper + Vitest first):
    • Direct CW20 pair → reverseSimulateSwap (today).
    • Client operations (findRoute / findRouteWithNativeSupport) → reverseSimulateMultiHopSwap(ask, operations) then wrap-input gross-up / unwrap-output ask gross-up so display You Receive matches W8/W9.
    • Direct wrap / unwrap → wrap math only (no router).
    • No operations (indexer-only) → do not reverse; treat typed receive as pay-sided or leave quote-only (pick one and test it; silent).
  3. Execute. Reverse offer_amount (native gross if wrap-input) feeds the existing forward sim + min-receive / hop floors. Do not broadcast ask size as send.amount.
  4. Extract wrap gross-up from payInvoice.ts into a tiny wrap helper used by Swap + invoice so fee bps cannot drift.
  5. Docs. Replace “You Receive is a <div> on wrap/native” with the new Q713-4. Keep “no indexer exact-out”.

Acceptance criteria

  • AC1. Default retail Swap (/ native LUNC ↔ listed wrapped hub token): You Receive is an <input>. Typing a legal amount (≤ 24 chars, isPositiveDecimalAmount) sets exactField=output, updates You Pay from reverse wrap/native quote, URL replaces exactAmount + exactField=output.
  • AC2. Direct CW20 factory pair: existing Q713-4 still holds (reverseSimulateSwap; execute offer-in).
  • AC3. Direct wrap and unwrap: typing receive sizes native/CW20 pay using mapper fee (+ unwrap burn tax on receive display). W8/W9 numbers match forward simulateNativeSwap inverses within rounding of 1 raw unit.
  • AC4. Client multi-hop with operations: reverse via ReverseSimulateSwapOperations on those ops; still offer-in execute; hop floors unchanged.
  • AC5. Indexer-only path (no client ops): no /route/solve exact-out. No crash. Silent ignore of output field as reverse source.
  • AC6. Max / 25/50/75 stay pay-sided. Reverse mode still hits #678 quote-only / insufficient / impact confirms. Blacklist / pause / freeze still block.
  • AC7. Community-tax receive: typed amount is net; min_return pre-tax (#615).
  • AC8. make verify-issue-713 / verify-issue-678 / verify-issue-512 (or wrap tax greps) stay green. New make verify-issue-<id>.
  • AC9. No new chrome layer; no lecture banners; no indexer API change.

Test plan (functional paths)

# Path Expect
T1 / default native↔wrapped Receive is <input>; type 1 → pay reverse-quoted; URL has exactField=output
T2 Direct CW20 exactField=output Q713-4 unchanged
T3 Direct wrap: type receive CW20 Pay native = wrap gross; mint net matches typed (W8)
T4 Direct unwrap: type receive native Pay CW20 such that post-fee post-tax = typed (W9); min_receive uses routerMinReceiveBase
T5 Native wrap-input + 1-hop CW20 Reverse ops then wrap gross; execute still wrap+swap msgs
T6 Unwrap_output native receive Display post-tax; router min post-fee pre-tax
T7 Clear receive exactAmount omitted; exactField drops
T8 Max while reverse Switches to pay-sided; receive becomes forward quote
T9 Flip while reverse exactField=output preserved; reverse re-runs on new pair class
T10 Indexer-only / no ops No reverse LCD; no /route/solve brute force
T11 Rapid typing Debounced one reverse query; replace not push
T12 Existing Swap RTL + #678 acquire Still green

Vitest: table-driven wrap reverse gross/net; native route reverse uses reverseSimulateMultiHopSwap mock; Swap RTL default pair receive input. Playwright (PLAYWRIGHT_SKIP_CHAIN=1 OK): receive input present on /; typing updates pay. 5 workers.

Test plan (attack, hack, and abuse)

Wallet/submit surface — focused, not a full DeFi suite.

# Vector Expect
A1 Reverse quote as execute bypass Still offer-in; slippage / Expert / blacklist still block
A2 Typed receive 10^40 / 25+ chars isPositiveDecimalAmount / 24-char cap; ignore field
A3 Wrap reverse omitting mapper fee Pay would under-fund mint; test requires wrap gross ≥ reverse CW20
A4 Unwrap reverse quoting pre-tax as You Receive Display is post-tax; min_receive is not the display number
A5 Submit pay raw = typed receive human Forbidden; submit uses reverse offer raw
A6 exactField=output on paused/frozen listed Existing banners; no execute
A7 Hostile amount in URL Existing parser; not echoed into input as HTML

Verification criteria

  • New 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.py stay green.
  • Playwright: / You Receive is an input; typing a small amount rewrites exactField=output.
  • Manual: 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

  • Indexer exact-out GET /route/solve.
  • Reopening #713 / Coolify leftover #714.
  • /trade ticket exact-out.
  • Invoice module behavior (#595) except shared wrap gross-up extract.
  • Contract changes to 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 is exactField=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_simulation offer 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-713 still green.

## Summary Follow-up to [#713](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/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 when `canReverseQuote` is 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 sets `nativeRouteInfo`, so `canReverseQuote` is 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`](frontend-dapp/src/pages/SwapPage.tsx): ```ts const canReverseQuote = Boolean(directPair) && isDirect && !isWrapOrUnwrap && !nativeRouteInfo ``` When true, You Receive is `<input onChange={applyTypedAmount(..., 'output')}>` and You Pay shows `reverseSimulateSwap` (`hybrid_reverse_simulation`, #190). When false, You Receive is a non-editable `<div>` of the forward quote. `applyTypedAmount` already switches `exactField` to `'output'` and writes `exactField=output` into canonical search. The gate is the render, not the parser. ### Reverse helpers already exist off Swap | Helper | What it does | Used by Swap reverse today? | |--------|----------------|------------------------------| | [`reverseSimulateSwap`](frontend-dapp/src/services/terraclassic/pair.ts) | Pair `hybrid_reverse_simulation` | Direct CW20 only | | [`reverseSimulateMultiHopSwap`](frontend-dapp/src/services/terraclassic/router.ts) | Router `ReverseSimulateSwapOperations` | No (invoice #595) | | [`simulateNativeSwap`](frontend-dapp/src/services/terraclassic/router.ts) | Wrap fee + hop forward sim + unwrap tax | Forward only | | [`wrapGrossForNetCw20`](frontend-dapp/src/utils/payInvoice.ts) | Native gross so wrap mint ≥ net CW20 | Invoice wrap-input, not Swap | | [`netNativeAfterUnwrap`](frontend-dapp/src/services/terraclassic/router.ts) | Unwrap fee then InstantWithdraw burn tax (**W9**) | Forward display | `GET /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 already `setExactField('input')` (!1200 merge fix). ## Why the new implementation is needed 1. **Default Swap cannot take an output size.** Uniswap-style “I want N of the receive token” is how people size a trade. On dex.cl8y.com `/` that field is not an input. 2. **#713 did not leave this as a bug in Q713-4.** Direct-only was intentional because wrap/native reverse was not wired. The gap is now the product request: type You Receive for the amount they want to receive. 3. **The LCD reverse path is already on the router.** Invoice checkout reverse-sims operations then grosses wrap. Swap should reuse that family, not invent a second solver and not binary-search `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 - **Execute stays offer-in + min received.** UI exact-out is a quote direction only. Do not skip #678 / 5–30–99 / Expert / blacklist / pause / freeze. Do not honor `slippage` / `expertMode` / `recipient` / `pool_only` / `hybrid_optimize` from the URL (#596). - **No indexer exact-out.** Do not fake exact-out by binary-searching `GET /route/solve`. If the live path has no client operations (indexer-only CW20 when BFS misses), ignore `exactField=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. - **Wrap / unwrap honesty (W8–W11, #512 / #516).** Wrap-input reverse: native You Pay is wrap-mapper **gross** (`wrapGrossForNetCw20`) so post-`fee_wrap_bps` CW20 covers the reverse offer. Unwrap-output reverse: typed You Receive is **post-unwrap-fee then post–burn-tax**; `routerMinReceiveBase` stays post-fee pre-tax (**R3**). Do not burn-tax wrap_deposit. Do not claim a flat 2% if tax applies. - **Direct CW20 Q713-4 stays.** `reverseSimulateSwap` on the factory pair; do not send natives into pair reverse sim. - **Canonical URL.** Typing receive still `replace`s `exactAmount` + `exactField=output` via existing `canonicalSwapSearch`. Amount debounce = `SIM_QUOTE_DEBOUNCE_MS`. No history spam. Gems: production does not write gem ids (#562). - **Submit alignment (#356).** Reverse mode: submit pay raw is the reverse offer (then existing forward quote + min received), not the typed receive human. `assertSubmitQuotePayRawAligned` / hybrid assert still apply. - **Tax tokens (#615).** You Receive typed amount is **net** when the receive token is community-tax. Execute `min_return` stays pre-tax (`executeAmountOut`). Do not double-count extra-debit. - **Max / fraction.** Stay pay-sided (`exactField=input`). Do not write spendable pay-human into You Receive. - **No lecture banner** when reverse is unavailable (#489). - **Do not** persist amounts in `localStorage`. Do not change nginx. U1: UST1 Swap links remain AMM. ## Relevant files | Path | Why | |------|-----| | `frontend-dapp/src/pages/SwapPage.tsx` | Always render receive `<input>`; broaden `canReverseQuote`; reverse query by route class | | `frontend-dapp/src/services/terraclassic/router.ts` | `reverseSimulateMultiHopSwap`; wrap/native reverse using existing wrap fee helpers | | `frontend-dapp/src/services/terraclassic/pair.ts` | Direct `reverseSimulateSwap` unchanged | | `frontend-dapp/src/utils/payInvoice.ts` | `wrapGrossForNetCw20` — extract to a shared wrap-math helper if Swap imports it (do not copy Swap execute into invoice) | | `frontend-dapp/src/utils/cw20RouteSolveQuote.ts` | Offer-in only — do not fake exact-out | | `frontend-dapp/src/utils/quoteDebounce.ts` | Submit alignment in reverse mode | | `docs/frontend.md` | Amend **Q713-4** / **Q711-6** (receive input on wrap/native; still no indexer exact-out) | | `skills/AGENTS_FRONTEND_SWAP_URL_SYNC.md` | Q713-4: wrap/native reverse; indexer-only still ignore | | `skills/AGENTS_WRAP_UNWRAP_BURN_TAX.md` | Reverse quotes must keep **W8**/**W9** | | `frontend-dapp/src/pages/SwapPage.queryParams.test.tsx` | Q713-4 plus wrap/native receive typing | | `scripts/qa/verify-issue-713.sh` | Stay green; add `verify-issue-<this>` | ## Recommended direction 1. **Always render You Receive as `<input>`** (same classes / `inputMode="decimal"` as You Pay) whenever a receive token is selected. Calculating / quoting remains `aria-busy` + placeholder empty, not a `<div>` swap. Direct wrap/unwrap (mapper-only, no pair) can reverse via wrap math alone. 2. **Route-class reverse quote** (pure helper + Vitest first): - Direct CW20 pair → `reverseSimulateSwap` (today). - Client operations (`findRoute` / `findRouteWithNativeSupport`) → `reverseSimulateMultiHopSwap(ask, operations)` then wrap-input gross-up / unwrap-output ask gross-up so **display** You Receive matches W8/W9. - Direct wrap / unwrap → wrap math only (no router). - No operations (indexer-only) → do not reverse; treat typed receive as pay-sided or leave quote-only (pick one and test it; silent). 3. **Execute.** Reverse `offer_amount` (native gross if wrap-input) feeds the existing forward sim + min-receive / hop floors. Do not broadcast ask size as `send.amount`. 4. **Extract wrap gross-up** from `payInvoice.ts` into a tiny wrap helper used by Swap + invoice so fee bps cannot drift. 5. **Docs.** Replace “You Receive is a `<div>` on wrap/native” with the new Q713-4. Keep “no indexer exact-out”. ## Acceptance criteria - [ ] **AC1.** Default retail Swap (`/` native LUNC ↔ listed wrapped hub token): You Receive is an `<input>`. Typing a legal amount (≤ 24 chars, `isPositiveDecimalAmount`) sets `exactField=output`, updates You Pay from reverse wrap/native quote, URL `replace`s `exactAmount` + `exactField=output`. - [ ] **AC2.** Direct CW20 factory pair: existing Q713-4 still holds (`reverseSimulateSwap`; execute offer-in). - [ ] **AC3.** Direct wrap and unwrap: typing receive sizes native/CW20 pay using mapper fee (+ unwrap burn tax on receive display). W8/W9 numbers match forward `simulateNativeSwap` inverses within rounding of 1 raw unit. - [ ] **AC4.** Client multi-hop with `operations`: reverse via `ReverseSimulateSwapOperations` on those ops; still offer-in execute; hop floors unchanged. - [ ] **AC5.** Indexer-only path (no client ops): no `/route/solve` exact-out. No crash. Silent ignore of output field as reverse source. - [ ] **AC6.** Max / 25/50/75 stay pay-sided. Reverse mode still hits #678 quote-only / insufficient / impact confirms. Blacklist / pause / freeze still block. - [ ] **AC7.** Community-tax receive: typed amount is net; `min_return` pre-tax (#615). - [ ] **AC8.** `make verify-issue-713` / `verify-issue-678` / `verify-issue-512` (or wrap tax greps) stay green. New `make verify-issue-<id>`. - [ ] **AC9.** No new chrome layer; no lecture banners; no indexer API change. ## Test plan (functional paths) | # | Path | Expect | |---|------|--------| | T1 | `/` default native↔wrapped | Receive is `<input>`; type `1` → pay reverse-quoted; URL has `exactField=output` | | T2 | Direct CW20 `exactField=output` | Q713-4 unchanged | | T3 | Direct wrap: type receive CW20 | Pay native = wrap gross; mint net matches typed (W8) | | T4 | Direct unwrap: type receive native | Pay CW20 such that post-fee post-tax = typed (W9); min_receive uses `routerMinReceiveBase` | | T5 | Native wrap-input + 1-hop CW20 | Reverse ops then wrap gross; execute still wrap+swap msgs | | T6 | Unwrap_output native receive | Display post-tax; router min post-fee pre-tax | | T7 | Clear receive | `exactAmount` omitted; exactField drops | | T8 | Max while reverse | Switches to pay-sided; receive becomes forward quote | | T9 | Flip while reverse | `exactField=output` preserved; reverse re-runs on new pair class | | T10 | Indexer-only / no ops | No reverse LCD; no `/route/solve` brute force | | T11 | Rapid typing | Debounced one reverse query; replace not push | | T12 | Existing Swap RTL + #678 acquire | Still green | Vitest: table-driven wrap reverse gross/net; native route reverse uses `reverseSimulateMultiHopSwap` mock; Swap RTL default pair receive input. Playwright (`PLAYWRIGHT_SKIP_CHAIN=1` OK): receive input present on `/`; typing updates pay. 5 workers. ## Test plan (attack, hack, and abuse) Wallet/submit surface — focused, not a full DeFi suite. | # | Vector | Expect | |---|--------|--------| | A1 | Reverse quote as execute bypass | Still offer-in; slippage / Expert / blacklist still block | | A2 | Typed receive 10^40 / 25+ chars | `isPositiveDecimalAmount` / 24-char cap; ignore field | | A3 | Wrap reverse omitting mapper fee | Pay would under-fund mint; test requires wrap gross ≥ reverse CW20 | | A4 | Unwrap reverse quoting pre-tax as You Receive | Display is post-tax; min_receive is not the display number | | A5 | Submit pay raw = typed receive human | Forbidden; submit uses reverse offer raw | | A6 | `exactField=output` on paused/frozen listed | Existing banners; no execute | | A7 | Hostile amount in URL | Existing parser; not echoed into input as HTML | ## Verification criteria - New `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.py` stay green. - Playwright: `/` You Receive is an input; typing a small amount rewrites `exactField=output`. - Manual: `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 - Indexer exact-out `GET /route/solve`. - Reopening #713 / Coolify leftover #714. - `/trade` ticket exact-out. - Invoice module behavior (#595) except shared wrap gross-up extract. - Contract changes to `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 is `exactField=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_simulation` offer 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-713` still green.
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#1256
No description provided.