feat: /trade UST1 pair invert — default other-side Price (USD), pair pill, ticket flip #524
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#524
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
On
/trade, UST1 pairs (UST1/cUSTC, UST1/USTR, and any future UST1/* factory pair) currently chart and headline Price (USD) as the USD of 1 UST1 (~$1 after #522). Traders opening those markets need the other token’s USD price by default (cUSTC or USTR), a pair-orientation pill that inverts pricing on click, and a ticket heading that drops the unused Order ticket eyebrow in favor of Buy {token} plus a swap/invert control that flips which token is being bought.This is one product surface: display + ticket orientation. Do not split into separate issues.
Related: #522 (USD of factory
asset_0), #466 (indexer quote-per-base), #508 (UST1 secondary AMM), #489 / #412 (Buy {base} copy), #154 / #155 (limit price + escrow USD), #151 (non-negative USD scale).Current codebase
Factory pairs store a fixed base/quote (
asset_0/asset_1). Indexer and UI treat that as canonical:create_pairkeeps callerasset_infosorder. UST1 secondary scripts pass UST1 first, so columbus-5 UST1/cUSTC and UST1/USTR have UST1 =asset_0.swap_events.price= human quote per base.price_usd= USD of 1 humanasset_0. Candles OHLC useCOALESCE(price_usd, price). Quote catalog: UST1=$1, cUSTC=#515 USTC, USTR=2.5×USTC./tradechartTradePage→resolveTapeLastPriceUsd→PriceChartPrice (USD) + Last headline. For UST1-as-base pairs this is ~$1, not the cUSTC/USTR print traders care about.PairSearchSelect/pairInfoMenuLabelalways renderasset_0 / asset_1(e.g.UST1 / cUSTC). The closed control is a search combobox — click opens search, it does not invert. There is no pair-orientation pill.TradeOrderTicketeyebrow Order ticket + heading Buy {token0} / Sell {token0} viatradeDirectionSideLabels(token0). Bid = buy factory base (pay quote); ask = sell factory base.LimitOrderSideFlipButtonalready flips bid ↔ ask, not pair orientation.token1/token0. Book Edit prefills that same convention (#178). Crossing preflight compares typed factory price to book head (#154)./chartsPriceChart+resolveTapeLastPriceUsd— UST1 pairs also show ~$1.Live pairs (columbus-5):
terra1ceprjsxp86ggftf5e38wwt34l83e5gq7penkdnv4wsatkwcs8v6qccw55fterra16vxrhpvpcucu05y0nr862vf9hnqeh274uaff4s7hz4n0ea74006qf5hgqyWhy this is needed
After #522, the chart is correct as “USD of 1 UST1” and useless as a trading chart: UST1 is the $1 peg unit. Retail flow for these markets is “what is cUSTC (or USTR) worth?” — the other side.
Without a first-class invert:
Constraints / guardrails
#466/#522price/price_usd, candle storage, or aggregatorlast_price. Invert is a frontend display of the same series (reciprocal + high/low swap).place_limit_orderprice is still token1 per token0. Market swap still buys/sells factory base vs quote. Display invert must convert before simulate/submit and when applying book Edit prefills.LimitOrderSideFlipButtonas pair invert. Bid/ask (Buy/Sell the displayed base) and pair invert (which token is the displayed base) are different. Keep the existing radiogroup (#153 / #412).PairSearchSelectclick. Combobox click/focus must still open pair search (#181, #180). Pair invert is a separate pill (chart header and/or ticket). Selected-pair label text may follow display order (cUSTC / UST1).asset_1, factory orientation already shows the other token — do not invert by default. Detect UST1 by symbolUST1(case-insensitive) and/or known mainnet/local contract, not a substring that matchescUSTC.Infinity/NaNinto lightweight-charts (#226).pairAddrif you persist at all./trade/:pairAddrstill identifies the factory pair. Do not invent a second pair contract or swapasset_infoson chain.token0/token1/bid/askin retail chrome. No “ORDER TICKET”. Invert control needs anaria-label(e.g.Show UST1 / cUSTC pricing). Keep U1: never describe AMM invert as mint/redeem (#508)./limitsstandalone is out of scope unless it shares the new helper; do not silently invert limit prices there without the same convert-on-submit rule./chartsshould use the same default + pill onPriceChartso UST1 pairs are not ~$1 there either. Ticket chrome is/tradeonly.Relevant files
Must touch
frontend-dapp/src/pages/TradePage.tsx— orientation state; pass invert into chart + ticket; pair-pill placementfrontend-dapp/src/components/trade/TradeOrderTicket.tsx— remove Order ticket; Buy {displayBase} + invert icon; map display side ↔ factorybid/askfrontend-dapp/src/components/charts/PriceChart.tsx— pair pill + inverted headline/seriesfrontend-dapp/src/components/charts/priceChartCandles.ts/chartHeadlinePrice.ts— reciprocal OHLC + finite dropfrontend-dapp/src/utils/pairPriceUsd.ts— USD of displayed base (factoryprice_usdorprice_usd / human_price/ quote catalog)frontend-dapp/src/utils/tradeDirectionSideLabels.ts— labels follow display basefrontend-dapp/src/utils/pairMenuOptions.ts— optional display-order labelsfrontend-dapp/src/components/trade/PairSearchSelect.tsx— selected label may follow display order; do not invert on combobox clickfrontend-dapp/src/utils/limitOrderPriceReference.ts— display rate vs factorytoken1/token0convertfrontend-dapp/src/components/trade/LimitOrderPriceField.tsx— “When 1 {displayBase} is worth”frontend-dapp/src/components/trade/TradeMarketOrderPanel.tsx— pay/receive + quote/submit follow factory side after mappingfrontend-dapp/src/components/trade/OrderBookPanel.tsx— if book prices follow invert, convert Edit prefill backfrontend-dapp/src/pages/ChartsPage.tsx— same default + pilldocs/frontend.md— new invariants sectionskills/AGENTS_FRONTEND_PRICE_CHART.md,skills/AGENTS_FRONTEND_TRADE_PAGE_LAYOUT.md,skills/AGENTS_INDEXER_PAIR_PRICE_USD.md(UI invert note; indexer math unchanged)New helper (recommended)
frontend-dapp/src/utils/tradePairDisplayOrientation.ts(name flexible) —isUst1Leg,defaultInverted,displayAssets,invertUsd,invertOhlc,displayPriceToFactoryToken1PerToken0,factorySideFromDisplayTests / QA
frontend-dapp/src/pages/TradePage.test.tsxfrontend-dapp/src/components/charts/__tests__/PriceChart.test.tsxfrontend-dapp/src/utils/__tests__/pairPriceUsd.test.tsfrontend-dapp/src/utils/__tests__/tradeDirectionSideLabels.test.tsfrontend-dapp/e2e/trade-page-responsive.spec.ts/ price-chart smokescripts/qa/verify-issue-NNN.sh+make verify-issue-NNNDo not change for this issue
indexer/src/indexer/swap_orientation.rs,pair_price_usd.rs, candle migrationsplace_limit_order/ swap executeRecommended direction
factory|inverted), defaulting to inverted iffasset_0is UST1 andasset_1is not.{displayBase}/{displayQuote}(e.g.cUSTC/UST1) in the chart header next to Price (USD). Click toggles orientation. Same toggle from a swap icon beside the ticket heading Buy {displayBase}.price_usd / human_quote_per_base.asset_0. Buy UST1 maps to factory bid.token1/token0at gate + submit. Crossing preflight and % chips use the same space as the typed field.pairAddrinsessionStorage(orlocalStorage) so refresh keeps the user’s last choice; first visit uses the UST1 default.LimitOrderSideFlipButtonas bid/ask of the displayed base (Pay ↔ Receive).Acceptance criteria
/tradeon UST1/cUSTC or UST1/USTR (UST1 =asset_0) shows pair pillcUSTC/UST1orUSTR/UST1, Price (USD) / Last / candles ≈ USD of 1 cUSTC or 1 USTR (not ~$1 UST1), and ticket heading Buy cUSTC / Buy USTR.UST1/cUSTC), chart + Last to USD of 1 UST1 (~$1), and ticket heading to Buy UST1. Second click restores AC1. Combobox still opens pair search and does not invert.aria-label. Not the existing bid/ask flip./trade(desktop + sub-lg).token1/token0prices. Book Edit → ticket does not place at1/P. Crossing gate uses one orientation end-to-end.asset_1, default is factory (other token already base)./charts. Invert does not leak across pair addresses./chartsUST1 pairs use the same default + pill.docs/frontend.md+ listed skills record invert as UI-only and the convert-on-submit rule./trade+/chartsstays clean (#214). Chartaria-livesummary names the displayed token, not a stale UST1 price.Test plan — functional paths
Unit / Vitest
cUSTC, UST1/USTR, UST1/vFDUSD→ inverted;cUSTC/UST1 (UST1 already quote) → not inverted;CL8Y/cLUNC→ not inverted; both legs UST1 or unknown → not inverted; symbolcustcmust not match UST1.price_usd≈ 1, human price ≈ 206 cUSTC/UST1 → displayed USD ≈1/206(or catalog USTC). Inverted-back equals factoryprice_usdwithin float tolerance. Missingprice_usduses existingresolveTapeLastPriceUsdfallback then invert.{o,h,l,c}→{1/o, 1/l, 1/h, 1/c}(high/low swap).0,NaN,Infinitydropped.asset_0, inverted, display Buy → factoryask; display Sell → factorybid. Non-inverted: Buy →bid.0.00485UST1 per cUSTC → factorytoken1/token0≈206.2. Reverse convert for book prefill.Order tickettext; headingBuy cUSTC; invertdata-testidpresent.#152tabs,#500sticky CTA,#178book Edit,#154/#155gates,#412Buy/Sell verbs,#226stale candles,#151non-negative scale,#522never pass rawtrades[].priceas USD.Integration / Playwright
/trade/{UST1-cUSTC}: pillcUSTC/UST1(orCUSTC/UST1), Last not ≈1.00when USTC oracle is ~0.005; ticket Buy matches pill base.1; heading Buy UST1; click again restores.lgand desktop workspaces both hide Order ticket and show one invert icon (single ticket mount, #178)./chartsUST1 pair: same default + pill.Manual / LocalTerra
make has-localterra/make setup-cloud-localterraif needed. Seed or use existing UST1-like pair./trade/:pair— UST1 default holds; after invert + reload, persisted choice holds.Test plan — attack, hack, and abuse
Treat invert as a trust-boundary transform. A bug here is equivalent to signing the wrong price.
token1/token0(order at1/P, taker/maker grief or self-drain)#155helper tests with invert onpairAddr; chartkeyincludes orientation or remaps in render0/1e-18→Infinitycandles or Y-axis explode#151clamppriceChartCandlesinvert casesUST1/Ust1/cUST1forces invert on a random pairUST1after trim/casefold onlyaria-labeldangerouslySetInnerHTML<img onerror=…>#501submit-align asserts still holdLimitBookTicketDraftwithout convert#178test with invert=true/trade/:addrand thinks Last is UST1?inv=only if documented; never requiredust1SecondaryMarket.ts)useTradingBlacklist+ pause still keyed by pair contract, not display symbols/limitsor CSV export accidentally emit inverted prices as if factoryVerification criteria
Close only when all of the following are true:
asset_0.make test-frontend(or scoped Vitest listed above) green, including new invert helper tests and no#522raw-price-as-USD regression./tradeUST1 default + pill toggle + ticket icon sync; pair search still opens; a11y critical routes pass.1/Pas factory price (tx + indexer row or mockedplaceLimitOrderargs).docs/frontend.mdsection exists; skills updated;make verify-issue-NNN(or documented equivalent) added and run./tradeon UST1/cUSTC and see cUSTC dollars on the chart without opening DevTools.Labels / owner
priority::medium) — live UST1 markets already show a correct-but-wrong-token ~$1 chartmarked as related to #522
marked as related to #508
marked as related to #489
marked as related to #412
marked as related to #466
marked as related to #154
marked as related to #155
marked as related to #151
mentioned in merge request !1057
MR !1057 merge review (no automerge, did not wait for CI)
Merge conflicts: none.
feat/524-trade-ust1-pair-invertwas already based on currentmain(887696c). Localgit merge-treewas clean.CI: pipeline 2762610231 failed
gitleaks,test-frontend, andtest-frontend-buildwithci_quota_exceeded(jobs never started). Not a product test failure. Localmake verify-issue-524is green (9/9), including the H1 pill-price test added before merge.H1 fix landed on the MR (
96efefd): chart pill previously calledtoggleInvertedwithout reciprocating the typed limit price; ticket icon did reciprocate. After a typed display price + pill toggle, submit could broadcast factory1/P. Ticket now reciprocates on any same-pair orientation change (pill or icon).AC vs !1057
placeLimitOrderWithAllowancespy / sim-wallet tx proofStill open vs issue close checklist
/charts, keyboard) was not added.1/P(verification item 4).TradeOrderTicket.invert.test.tsxstill asserts helper math only.Do not close #524 on merge alone until the remaining verification items above are done or explicitly waived.
mentioned in commit
a0a75d5d61GitLab auto-closed this issue via
Closes #524on merged !1057. Remaining gaps from the merge-review note still apply (Playwright smoke, sim-wallet/on-chain 1/P proof, H2/H8/H9 tests, columbus-5 Last-is-cUSTC walkthrough). Reopen if those are still required before calling #524 done.mentioned in issue #527
mentioned in issue #528
mentioned in issue #530
mentioned in issue #541
mentioned in issue #543
marked as related to #543
mentioned in issue #544
mentioned in commit
7dd968c4e1mentioned in merge request !1070
mentioned in issue #547
mentioned in issue #548
mentioned in issue #556
mentioned in issue #557
mentioned in merge request !1083
mentioned in issue #561
mentioned in issue #563
mentioned in issue #564
mentioned in issue #565
mentioned in issue #568
marked as related to #568
mentioned in merge request !1089
mentioned in merge request !1092
mentioned in issue #664
mentioned in issue #666
mentioned in issue #680
mentioned in merge request !1183
mentioned in issue #692
mentioned in issue #693
mentioned in issue #705
mentioned in issue #717