feat(frontend): Swap extreme-size / unfunded pay guidance (acquire UST1 + retail slippage) #678
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#678
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
Swap still quotes oversized, unfunded pay amounts as if they were executable. Community testers type values they do not hold (example: 50,000 UST1 → LUNC), see a live-looking You Receive (example: 1.587M LUNC, route UST1 → cLUNC → LUNC), and a red High expected slippage banner. Wallet is often disconnected. After connect the CTA becomes Insufficient Balance with no next step.
Suggested product direction (illustrative, not shipping copy): a short blocking dialog such as “You don’t have 50k UST1 — you need about N more vFDUSD on UST1 to start this trade” plus a Guide button. Bundle the acquire path, retail slippage wording, and window-cap honesty into one Swap/Trade change.
Current codebase
Quote theater is not gated on balance or connect
SwapPage.tsxrunssimQuerywheneverhasRoute && isPositiveDecimalAmount(debouncedInputAmount). Wallet is not required.balanceQueryisenabled: !!address && !!offerAssetInfo, so a disconnected session never computesinsufficientBalance.Connected shortfall only flips the CTA:
insufficientBalance→ button Insufficient Balance, disabled/ust1or/wrapDisconnected CTA is Connect Wallet. First click opens the wallet modal. If expected slippage is > 5%, a second click is required (
showImpactConfirm) with “High expected slippage warning / {n}% expected slippage — click again to confirm.” That two-step only runs after connect and after the insufficient-balance branch is skipped.Slippage copy is jargon; extreme gate is 30% / 99%
priceImpact > 5> 5> SWAP_EXPERT_MODE_SLIPPAGE_BLOCK_PCT(30) unless Expert Mode>= SWAP_EXTREME_SLIPPAGE_WARNING_PCT(99)Constants and helpers:
swapRouteSlippage.ts. Retail vs Settings label:slippageProtectionCopy.ts. Invariants:docs/swap-max-spread-ux.md.A 50k UST1 → LUNC quote that lands between 5% and 30% shows the red banner but is not blocked. In a disconnected session there is no balance row (
AmountBalanceActionsreturns null when!walletConnected), so nothing contradicts the fantasy size.UST1 acquire path exists — Swap does not point at it
Oracle mint/redeem is
/ust1only (#506, U1–U8):Ust1Page.tsxuseSearchParams. Lead already says this is not an AMM swap.ust1Window.tseffective_swap+ CW20Sendust1WindowMath.tsust1WindowGates.tsust1SecondaryMarket.tsust1SecondarySwapPath()is/only — Swap does not honor token query params.Published window caps (
docs/runbooks/ust1-window-ui.md):fee_bps=100, per-tx 1,000 UST1, rolling 24h 10,000 UST1. A 50,000 UST1 pay cannot be minted in one window tx or one rolling day./ust1insufficient copy is only “Balance is too low for this amount.”Wrap natives:
WrapPage.tsxsame Insufficient Balance dead-end. Swap already auto-wraps/unwraps on the UST1 → cLUNC → LUNC route; that is not how you obtain UST1.Trade market
TradeMarketOrderPanel.tsxquotes without a dedicated insufficient-balance CTA. Disconnected label is Connect Wallet. No UST1 acquire guidance. Same helper must cover Trade market so Swap and Trade do not diverge.Copy / cognitive load
#489 /
AGENTS_FRONTEND_COPY_COGNITIVE_LOAD.md: blocking errors ≤ one short sentence + optional Docs/guide control. No always-on lectures, no “oracle mint/redeem” essays on Swap. Do not add a new marketing guide site;/ust1is the guide.Why the new implementation is needed
/ust1— not from swapping vFDUSD on the AMM.This is a frontend product change. Do not change pair/router
max_spread, indexerroute/solve, or window contract limits.Constraints / guardrails
max_spreadpreflight, or the >5% confirm-again gate (#293, #134, #497).UST1_AMM_AS_MINT_FORBIDDEN. Guide target for UST1 shortfall is/ust1, never “Swap vFDUSD → UST1”.depositVfdusdToUst1using liveeffective_swap.oracle.rate+fee_bps. Fail closed if window env is off, query fails, rate is 0, or oracle is paused/stale.min(pay_shortfall_ust1, per_tx_ust1_limit, rollingRemainingUst1). If typed pay exceeds remaining window capacity, say the window cannot mint that size; do not promise 50k UST1./ust1,/wrap). No third-party URLs, nojavascript:, nowindow.locationbuilt from token symbols.docs/note for agents is fine; the in-app Guide is/ust1.ust1SecondarySwapPath()is/only).Relevant files
frontend-dapp/src/pages/SwapPage.tsxfrontend-dapp/src/utils/swapRouteSlippage.tsfrontend-dapp/src/utils/slippageProtectionCopy.tsfrontend-dapp/src/components/swap/ExpertModeModal.tsxfrontend-dapp/src/components/common/AmountBalanceActions.tsxfrontend-dapp/src/components/trade/TradeMarketOrderPanel.tsx/tradefrontend-dapp/src/pages/Ust1Page.tsxfrontend-dapp/src/utils/ust1WindowMath.tsfrontend-dapp/src/utils/ust1WindowGates.tsfrontend-dapp/src/utils/ust1SecondaryMarket.tsfrontend-dapp/src/pages/WrapPage.tsxdocs/swap-max-spread-ux.mddocs/runbooks/ust1-window-ui.mdskills/AGENTS_FRONTEND_COPY_COGNITIVE_LOAD.mdskills/AGENTS_UST1_WINDOW_UI.mddocs/frontend.mdRecommended direction
swapPayAcquireGuidance.ts) from{ payAsset, payRaw, payBalanceRaw, vfdusdBalanceRaw, windowView | null, wrapEnabled }→ discriminated result:okdisconnected_quote— quote-only; CTA remains Connectinsufficient_generic— “You don’t have enough {symbol}.”insufficient_ust1_window— shortfall + capped vFDUSD-in +/ust1hrefinsufficient_ust1_over_window— typed size exceeds per-tx or rolling remaininginsufficient_wrap— pay is native/wrapped and/wrapis the acquire pathhigh_impact— expected slippage > 5% and funded: “This size moves the pool. Try a smaller amount.” + optional Reducedata-testids below)./ust1prefill:?direction=deposit&amount=human vFDUSD, sanitized (isPositiveDecimalAmount), clamped to per-tx / rolling before apply. Ignore unknown params.ust1WindowMath.tswith bigint ceil; unit tests vs INV-SWAP-001.data-testid="swap-quote-only"). Do not show Min received as a promise. Do not run confirm-again while disconnected.assertSecondaryMarketCopyon every new string.Suggested shipping shape (not literal): “You don’t have 50,000 UST1. Deposit about 1,000 vFDUSD on UST1 (window max this tx).” [Get UST1] →
/ust1?direction=deposit&amount=1000(amount from inverse + caps, not a hardcoded 1000).Acceptance criteria
/ust1?direction=deposit&amount={capped human vFDUSD}using inverseeffective_swapmath./ust1; no “deposit 49,999 vFDUSD” that would fail on-chain.#489: no new always-on lecture on Swap. Guide is a control, not a paragraph stack./ust1ignores hostile query strings (XSS, huge exponents, negative, non-decimal).docs/swap-max-spread-ux.md+docs/runbooks/ust1-window-ui.md(Swap → window acquire). No new retail microsite.Test plan (all paths)
Unit (Vitest)
rate=0/ bad fee → null.assertSecondaryMarketCopyon all new retail strings./ust1query parse: empty,amount=abc,1e99, negative, extra keys, valid human clamped to remaining.Component
SwapPage: disconnected 50k UST1 fixture shows quote-only + Connect; connected 0 balance shows insufficient + Guide; over-window fixture shows over-capacity copy; funded >5% / >30% / ≥99% banners unchanged in gate behavior.TradeMarketOrderPanel: same insufficient + Guide.Ust1Page: prefill deposit amount; clamp; reject junk params.E2E / LocalTerra (when window + wrap env present)
/ust1with clamped amount.e2e/ust1-window.spec.ts).swap-enable-expert-mode).Regression:
make test-frontend,make lint-frontend,make verify-issue-506, Swap slippage tests inSwapPage.test.tsx/swapRouteSlippage.test.ts.Test plan (attack, hack, abuse)
<img>,javascript:)dangerouslySetInnerHTML; href allowlisted to/ust1or/wrap+ safe query?next=/ust1?amount=999999999999999getTokenBalancefor connected address; no indexer “portfolio” as spendableeffective_swapmissingSendmax_spreadUST1_TOKEN_ADDRESS), not ticker stringVerification criteria
/ust1with a legal deposit amount (≤ 1,000 UST1 notional / remaining 24h).make verify-issue-506still green. Newmake verify-issue-<iid>covers helper + Swap/Trade/Ust1 tests listed above.git commit --no-verify; no secrets in fixtures.Related
/ust1window UImarked as related to #293
marked as related to #489
marked as related to #506
marked as related to #508
marked as related to #497
marked as related to #134
mentioned in commit
88cc86f54ementioned in merge request !1184
mentioned in merge request !1185
mentioned in commit
36d64528b9mentioned in issue #686
marked as related to #686
Merged via !1185 (
36d64528) ontomain. SwapPage/TradeMarketOrderPanel auto-merged with !1182 mixed-hop gas.make verify-issue-678PASS (7/7). Manual leftover: disconnected oversized UST1 quote looks like Quote only; Get UST1 lands on/ust1with a legal deposit. LocalTerra e2e not run (chain down). Do not reopen unless I595/U1 acquire path is wrong. Post-merge ops: #686.Post-merge sanity (!1185 already on main)
Product claims PASS on current
main.make verify-issue-678→ 7/7. Shared helper on Swap + Trade market; quote-only / Min received / confirm-again gating; UST1 Guide + inverse deposit clamp;/ust1prefill safety; 5/30/99 Expert Mode unchanged; U1 Guide is/ust1.Leftover (ops only, no new issue): manual Coolify/disconnected 50k UST1 quote-only smoke + optional LocalTerra Guide→
/ust1e2e were never run (noted at close). Stack leftover #686 already closed the !1177–!1186 verify including #678.Do not reopen unless A678/U1 regresses.
mentioned in issue #701
mentioned in issue #711
marked as related to #711
mentioned in issue #713
mentioned in commit
d2ddde6fb1mentioned in issue #714
mentioned in issue #715