feat: /charts UST1/USD hero — default UST1/cUSTC, ?price= token, page-wide invert #680
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#680
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
Product feedback: UST1/USD should be the hero on Charts — first chart on
/charts, not buried behind volume rank or the #524 other-side default. Bundle as one Charts product surface:/chartsopens the UST1/cUSTC pair (retail “UST1/USTC”) as the default chart./chartsand/charts/:pairAddraccept a URL parameter naming which pair leg is the priced token.Do not split default pair, URL param, and leftover invert into separate issues. They share one orientation state.
Related: #524 (display invert + sessionStorage), #543 (
invertUsdnot1/xUSD), #547 (/charts/:pairAddrdeep links), #564 / #565 (24h OHLC + TWAP), #666 (pair-scoped layout), #534 (catalog rank), #226 (finite candles), #151 (non-negative scale), #665 (share URLs strip query), #508 (U1 AMM ≠ mint).Current codebase
Factory pairs keep a fixed
asset_0/asset_1. Columbus-5 UST1/cUSTC and UST1/USTR were created with UST1 =asset_0. Indexer#466/#522priceis human quote-per-base;price_usd/ candle USD / pair-stats*_usdare USD of 1 humanasset_0./chartsChartsPagestarts with emptyselectedPairAddr.activePairAddr = selectedPairAddr || pairOptions[0]. The menu is the currentGET /pairspage (sort=volume_24h, limit 50) aftersortIndexerPairsByCatalog. There is no UST1/cUSTC pin. Catalog rank groups UST1 hubs then sorts by human quote volume / other-hub symbol — cLUNC/UST1 often ranks ahead of UST1/cUSTC when volumes are 0. Deep link/charts/:pairAddrstill works (#547).defaultDisplayInvertedis true when factory base is UST1. First visit of UST1/cUSTC shows USD of 1 cUSTC (~$0.005class). Switch-side shows UST1 USD (~$1)./tradeand/chartsshareusePairDisplayOrientation+sessionStoragekeycl8y-dex-trade-pair-invert:{pairAddr}. No query string.PriceChartpair pill toggles the hook. Candles + Last useapplyChartDisplayInvert/resolveDisplayTapeLastPriceUsd(invertUsdper bar). TapeTradesTablePrice + buy/sell color follow invert.PairTokenLinksreorders chips (payloads stay factory, T541-5).*_usdviapairStatsUsdField+formatPairStatsUsdOhlc(S564-7, P565-5). Price Change is indexerprice_change_pctof factory human open→close (not display-USD). Heading24h Stats — {label}uses factoryindexerPairMenuLabel(nodisplayInverted).formatTwapHumanPriceis factory token1-per-token0 only (S564-5). Invert does not reciprocal.chartsPairHrefreturns/charts/{bech32}only — no search. Hostilejavascript:/ HTML segments are rejected (chartsPairRoute.ts).buildCanonicalShareUrl({ kind: 'charts' })is path-only and strips query (TS-2).GET /api/v1/pairs/{addr}/statshas factoryhigh/low/open_price/close_price/price_change_pctplus*_usd. No*_humanOHLC on stats (candles already have additive*_humanfor #543).Live columbus-5 hero pair (from #524): UST1/cUSTC
terra1ceprjsxp86ggftf5e38wwt34l83e5gq7penkdnv4wsatkwcs8v6qccw55f. LocalTerra address comes from deploy env, not this pin.Why this is needed
#524 made UST1-as-base markets default to the floating token’s dollars so
/tradeis a cUSTC/USTR chart. Charts is a different job: visitors opening Charts & Analytics expect the protocol’s UST1/USD print as the first thing they see. Today they can land on another UST1 hub pair (cLUNC/UST1) and/or a cUSTC-priced candle, then click invert and still see factory ~$1 High/Low/Open/Close and an unflipped TWAP. Orientation is also not shareable or bookmarkable — onlysessionStoragefor this tab.Without this change:
Constraints / guardrails
swap_events.price/price_usd, candle storage, or aggregatorlast_price. Additive stats*_human(if added) are allowed so the dApp caninvertUsd— do not store a reciprocal as factory USD (T524-1, C543).invertUsd(factoryUsd, human)per value (price_usd / price). Never1 / price_usdon a USD series. Human TWAP / tape Price may useinvertFinitePositive/invertOhlc. Drop ≤ 0 / non-finite. NoNaN/Infinityinto lightweight-charts (T524-6, C543-9, #226). Y-axis stays non-negative (#151). Invert rewrite →setData, notseries.update()from the oldest bar.GET /oracle/historyonto pair charts (#568 C568-7)./oracle/price/ust1stays 400./chartsUST1/cUSTC (and other UST1-as-asset_0pairs when no?price=) default to UST1 USD (factory / not inverted)./tradekeeps T524-3 (other-side default). Do not let Charts’ new default leak into Trade via the sharedcl8y-dex-trade-pair-invert:key. Use a Charts-specific storage prefix or treat URL as Charts source of truth and stop writing Charts defaults into the Trade key.uusdand not “first catalog pair”. Detect both legs by known UST1 + cUSTC contracts (isUst1Leg+ cUSTC allowlist / exact symbolCUSTC/cUSTCafter trim/casefold). Never substring-matchcUSTCas UST1. Retail copy may say UST1/USTC; identity chips stay cUSTC (#630 natives are a different pair)./chartsonly. Valid/charts/:pairAddrdeep links must not snap back to the hero (#357 class). Hostile / unknown pair notices stay (CS-11). If UST1/cUSTC is missing (fresh LocalTerra before seed), fall back tofirstCatalogPairAddress/ first economic pair — do not hang or invent an address.?price=allowlist = the two legs of the selected pair only. Accept exact symbol (trim/casefold) or that leg’sterra1contract. Optional aliases only when that leg is on the pair:USTC→ cUSTC;LUNC→ cLUNC. Unknown / HTML /javascript:/ overlong / path-like values → ignore (product default), do not 404 the pair, do not echo the raw param into HTML or toasts.?price=stay in sync (replace: true). Pair switch re-keys the param to a valid leg or drops it. Do not add a second pair contract to the path (T524-9).pricequery only (allowlisted token). Keep TS-2 “nowindow.location.href” (no utm, WC URI, mnemonic). Do not hard-codedex.cl8y.com.charts-overview-*.indexer/into root Docker to add stats fields./limitsstandalone stays factory-oriented (T524-10).Relevant files
Must touch
frontend-dapp/src/pages/ChartsPage.tsx— hero auto-pick + navigate;useSearchParams; pass orientation into stats / TWAP / headings; stop binding factory OHLC/% when invertedfrontend-dapp/src/utils/chartsPairRoute.ts— parse/serializeprice(or agreed name); keep bech32 href hardeningfrontend-dapp/src/hooks/usePairDisplayOrientation.ts— Charts URL + page-specific default; do not clobber Trade storagefrontend-dapp/src/utils/tradePairDisplayOrientation.ts— Charts default helper; param ↔ invert; allowlist matchfrontend-dapp/src/utils/pairPriceUsd.ts/chartsPairStats.ts— display-USD OHLC + display%(invertUsd/ recompute from inverted open/close; not1/xof USD; not “just negate”price_change_pct)frontend-dapp/src/utils/pairCatalogRank.ts— optionalfirstUst1CustcPairAddress(do not change/tradefirstCatalogPairAddresssemantics)frontend-dapp/src/utils/ust1SecondaryMarket.ts— existing UST1 / cUSTC contract pinsfrontend-dapp/src/utils/pairMenuOptions.ts— Charts headings may passdisplayInvertedfrontend-dapp/src/utils/sharePageLink.ts— only if Share is wired; validated querydocs/frontend.md— Charts hero +?price=+ page-wide invert; revise T524-10 / C543-3 for Charts-only default; P565-5 / S564-5 / S564-7 for price tilesskills/AGENTS_FRONTEND_TRADE_PAIR_INVERT.md,skills/AGENTS_FRONTEND_CHARTS_PAIR_SCOPED.md,skills/AGENTS_FRONTEND_CHARTS_PAIR_STATS.md,skills/AGENTS_FRONTEND_USD_CANDLE_INVERT.mdMaybe
indexer/src/db/queries/swap_events.rs+frontend-dapp/src/types/index.ts— additiveopen_human/high_human/low_human/close_humanon pair stats so OHLC caninvertUsdwithout guessing. Alternative: derive 24h display OHLC from already-fetched candles. Prefer additive fields or candles — do not1/xfactory*_usd.frontend-dapp/src/pages/TradePage.tsx— optional same?price=parser for share parity; do not change Trade’s UST1 other-side default.frontend-dapp/src/pages/ChartsPage.test.tsx,chartsPairRoute.test.ts,tradePairDisplayOrientation.test.ts,chartsPairStats.test.ts,pairPriceUsd.test.tsDo not change (unless adding additive JSON)
/protocolcensus, factory wasm,place_limit_orderunits.Recommended direction
Hero resolve (bare
/chartsonly): find UST1/cUSTC in the loaded pair list (or a one-shotgetPairs/ known columbus-5 address whenVITE_NETWORK=mainnet).navigate('/charts/{addr}?price=UST1', { replace: true }). LocalTerra: match legs from env/UST1_TOKEN_ADDRESS+ cUSTC wrap pin, not the mainnet bech32.Param name:
price(token shown as Price USD / display base). Examples:/charts/{ust1custc}?price=UST1,?price=cUSTC,?price=USTC(alias). Booleaninvert=is a worse retail API.Orientation resolve order on Charts: valid
?price=→ else Charts session key → else Charts product default (UST1-as-base → not inverted). Trade keeps existing order: storage → T524-3.Page-wide price map (one flag):
pricetokeninvertUsdBASE/QUOTEDocs: new invariants (suggest C674-1–C674-n) +
make verify-issue-<iid>. Update #524/#543 Charts default sentences so agents do not “fix” UST1/cUSTC back to cUSTC USD.Acceptance criteria
/charts(no pair segment) selects UST1/cUSTC when that pair exists and replace-navigates to/charts/{addr}?price=UST1(or equivalent allowlisted form). Chart Last + candles are USD of 1 UST1 (~$1class), not cUSTC (~$0.005class)./charts/{ust1custc}withoutpriceuses the Charts product default (UST1 USD), not T524-3.?price=cUSTC/?price=USTC/ quote-leg contract on that pair shows cUSTC USD on Last, candles, 24h USD OHLC, Price Change, TWAP, headings, tape Price. Pill matches.replace) and all rows in C674-3. A second click /?price=UST1restores UST1 USD everywhere.?price=that is not a leg of that pair is ignored (that pair’s Charts default). InvalidpairAddrstill shows the existing notice; no stats/leaderboard fetch with the hostile string (CS-11)./tradeUST1/cUSTC first visit (no Charts storage write) still defaults other-side (T524-3 / C543-3). Convert-on-submit unchanged.1/xof factory USD; no CoinGecko stitch; no U1 mint copy; chrome nesting still green.Test plan (all paths)
Unit / component (Vitest)
chartsPairRoute/ new parser:UST1,ust1,cUSTC,USTCalias, leg contract, empty,javascript:alert(1),<script>,//evil, 4k string, token not on pair,price=UST1on UST1/USTR (valid),price=cUSTCon UST1/USTR (ignore)./chartswith UST1/cUSTC + cLUNC/UST1 + high-volume gem in the page → navigates to UST1/cUSTC +price=UST1; headline ~$1 class./chartswithout UST1/cUSTC → fallback economic pair; no throw./charts/{other}does not replace with hero.?price=sync; browser back/forward (if implemented) does not desync pill vs tiles.1/1and not-(factory %).—.chartsPairHrefstill null forjavascript:/ HTML. Share helper (if touched) only appends allowlistedprice.Indexer (only if additive human stats)
cargo testpair-stats JSON: factory*_usdunchanged; new*_humanoptional; unknown pair 404; bind$n(no string concat).Manual / browser
/charts→ UST1/cUSTC UST1 USD → copy URL → new session same orientation./charts/<script>?price=<script>→ invalid-pair notice, no script exec.Test plan (attack / hack / abuse)
?price=<script>,"><img>, encoded%3Cscript%3E. Must never becomeinnerHTML,href, or unescaped text. Match allowlist only; ignore otherwise.?price=javascript:alert(1),price=data:text/html,…,price=//evil. Ignore.chartsPairHref/ Share still reject non-bech32 pair ids.?price=https://evil,?next=, hashprice. Orientation parser must not assignwindow.location. Share must not use rawlocation.href(TS-2).mnemonic, WalletConnect URI,showGems=1(P562-1 forbids). Ignore; do not persist into Share.?price=UST1on a pair whose legs are not UST1; gem symbol spoofingUST1still hidden in browse (P562-2). Do not treat arbitrary strings as a third priced asset.price=set to the pair contract or an unrelatedterra1. Ignore unless it equals a leg contract.pricekeys (last vs first — pick one, document, ignore the rest). No unbounded regex on the raw string.sessionStoragethrow → URL + default still work.?price=cUSTCmust not apply to a pair without cUSTC. CandlesetDataon rewrite (#524 / #226).invertUsdso attackers cannot advertise a fake 24h range. Do not treat inverted TWAP as USD or as a limit/settlement price (S564-11).$n. Do not cache unknown-pair 404 as[](CS-10).aria-labelnaming both symbols (T524-11). No new nestedcard-glass(#653).Verification criteria
make verify-issue-<this-iid>(new script) + existingmake verify-issue-524andmake verify-issue-543still green (Trade other-side default preserved).ChartsPage.test.tsx, route/orientation/stats helpers. Chrome nesting:python3 scripts/check_chrome_nesting.py.cd indexer && cargo test --test api_pairs -- --test-threads=1(or the suite that coversGET .../stats) on the host (make setup-indexer-postgres). No root Docker cargo bind-mount./chartson a stack with UST1/cUSTC listed → UST1 hero;?price=cUSTCflips all price tiles;/tradefirst visit still other-side.Out of scope
/protocolhub card layout./tradedefault to UST1 USD (separate product call).uusd) as the hero pair.mentioned in merge request !1183
mentioned in commit
479f9012d7mentioned in issue #686
marked as related to #686
Merged via !1183 (
479f9012) ontomain36d64528.make verify-issue-680PASS (6/6). Manual leftover: production-shaped/chartsshould open UST1/cUSTC with USD of 1 UST1;/tradefirst visit of that pair stays other-side. Coolify leftover: frontend rebuild. Do not reopen unless C680 is wrong. Post-merge ops: #686.