feat: default pair 24h volume to USD (fix UST1/USTR VOL 19T and UST1/cUSTC 48.2m) #544
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#544
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
Retail pair volume is still quote-token units (and often raw integers), so UST1/USTR shows an impossible VOL 19297048T and UST1/cUSTC shows vol 48.2m. The dApp must default to 24h volume in USD on Trade pair search and every other retail volume surface.
Related (do not treat as done): #534 human quote-decimal badges (P534-4), #540 Charts stats still
formatNum(raw), #522 human/price_usdcatalog, #515 oracles (X4 still USTC-only forvolume_usd), #243 pair-list rollup, #216 hybrid headline volume (L10).Current codebase
Indexer — pair list is quote-raw, not USD
GET /api/v1/pairs(indexer/src/api/pairs.rsPairResponse) exposes onlyvolume_quote_24h: a raw quote-side integer from thepair_volume_24hrollup (indexer/migrations/20260531143000_pair_volume_24h_rollup.sql,refresh_pair_volumes). The rollup storesSUMof quote-sideoffer_amount/return_amount— novolume_usdcolumn.sort=volume_24hcompares those raw amounts, so 18-dec USTR always outranks 6-dec cUSTC.GET /api/v1/pairs/{addr}/statsalready hasvolume_usd(SUM(swap_events.volume_usd)), but ingest is USTC-leg only:X4 (
docs/runbooks/indexer-external-oracle.md): indexervolume_usduses the USTC feed only. UST1/USTR has neither leg as USTC →volume_usdis NULL. UST1/cUSTC can get a USD sum, but the pair list never returns it, so pickers cannot show it.#522 already has a quote USD catalog in
pair_price_usd.rs(P522-Q): UST1 =$1; USTC/cUSTC/uusd= #515 USTC; LUNC/cLUNC/uluna= #515 LUNC; USTR =2.5 ×USTC. That catalog prices 1 human base, not swap notional.compute_volume_usddoes not use it.Overview
GET /api/v1/overviewalready hastotal_volume_24h_usd(rollupSUM(volume_usd)). Because ingest is USTC-only, the global USD total under-counts UST1/USTR (and any LUNC-quoted pair without a USTC leg).Frontend — quote units (or raw) everywhere retail sees “vol”
PairSearchSelect.tsxvol {formatQuoteVolume24h(raw, quoteDecimals)}vol 19.3USTR, not USD. Production still reports VOL 19297048T when decimals are missing (?? 6) or an older bundle stillformatNum(raw). Either way the retailer reads a token amount, not dollars./poolcardsPoolPage.tsx24h vol (quote, indexed): {formatQuoteVolume24h}volume_24his raw./charts24h statsChartsPage.tsxformatNum(stats.volume_base)/formatNum(stats.volume_quote)…Ton USTR (#540). Stats JSON hasvolume_usdbut the UI ignores it./chartsoverviewformatNum(total_volume_24h)(mixed rawoffer_amount); second box is USDpairCatalogRank.tsP534-3raw / 10^decimalsCandle histogram (
volume_quote/volume_baseper bar) is not a pair-search 24h figure — leave it unless converting viaprice_usdis free. CG/CMCbase_volume/target_volumestay raw quote/base (do not silently switch aggregators to USD).IndexerPairinfrontend-dapp/src/types/index.tshas novolume_usd_24h.IndexerPairStatsomitsvolume_usdeven though the API returns it.Why this is needed
volume_usd— it is just USTC-only and not on the pair list the pickers call. Without expanding the catalog, UST1/USTR can never show a truthful USD badge.sort=volume_24hand human-quote catalog rank let an 18-dec quote look like the deepest market.Constraints / guardrails
max_spread/min_return/ deadlines. Label 24h vol (USD) or badgevol $…— never imply a peg guarantee.compute_volume_usdthroughquote_usd_kind/usd_per_human_quoteinpair_price_usd.rs. Unknown quotes (gems, hostile tickers) →volume_usdNULL, not$0, not a guessed price.asset_0/asset_1for ingest.volume_usdis the USD of the consolidatedoffer_amount/return_amountonce. Do not addpool_leg+book_legorlimit_order_fills. Prefer the catalog-known oriented quote (human quote volume × quote USD). If only the other leg is in the catalog, use that leg. If both are known, use one side (quote preferred) — never sum both.10^decimalsof the priced leg (not a hardcoded1_000_000). USTR is 18-dec; UST1/cUSTC/cLUNC are 6. Do not reuse the USTC-onlydecimals_factor = 1_000_000.volume_quote_24h(raw) for integrators / #534 tests. Addvolume_usd_24h(human USD string, same scale as stats/overview). Do not humanizevolume_quote_24hin the indexer.pair_volume_24h+refresh_pair_volumes(~5 min). Pair list must notSUM(swap_events)per request. Pagination caps (V4) unchanged.volume_usd. Migration/job must recompute from stored amounts + catalog + latest (or snapshot) oracles, then refresh rollups. Document that USD is as-of ingest/backfill oracles, not a live mark-to-market of old trades.$0.00, when the pair has quote volume but no catalog USD. Do not fall back toformatNum(raw)or19297048T.base_volume,target_volume,last_price). Optionalvolume_usdon those feeds already exists where documented — do not swap units.price/price_usd, or hybrid attribution columns.vol $12.4Kor24h vol $12.4K. No “oracle-valued quote-side notional” essays. Tooltip ≤ one short sentence if needed (24h volume in USD).formatUsdVolume24h):$+formatNumon a human USD number, 2–3 sigfigs. Never pass a raw 18-dec integer in. Reject non-finite / negative.sort=volume_usd_24h; do not breaksort=volume_24hclients./pooluser-selected sort may keepvolume_24hor switch the label to USD once the field exists — pick one and document it.make verify-issue-544in the same MR. Invariants V544-1–V544-8. Update X4,docs/indexer-invariants.mdpair-list row,docs/frontend.mdP534-4, andskills/AGENTS_FRONTEND_PAIR_CATALOG_RANK.md.formatQuoteVolume24h/ base decimals) — that satisfies #540’s…Tbug without making quote the default.Relevant files
indexer/src/indexer/parser.rscompute_volume_usd(USTC-only today)indexer/src/indexer/pair_price_usd.rsindexer/src/db/queries/volume.rsrefresh_pair_volumes/ global USD rollupindexer/src/db/queries/pairs.rsvolume_quote_24hindexer/src/db/queries/swap_events.rsget_24h_stats_for_pair.volume_usdindexer/src/api/pairs.rsPairResponse/PairStatsResponseindexer/migrations/20260531143000_pair_volume_24h_rollup.sqlvolume_usd+ index (new migration, do not edit old)indexer/tests/indexer_pair_volume_pagination.rsswap_eventsscanindexer/tests/api_pairs.rsindexer/tests/api_oracle.rsvolume_usdfrontend-dapp/src/components/trade/PairSearchSelect.tsxfrontend-dapp/src/utils/formatAmount.tsformatQuoteVolume24hfor secondaryfrontend-dapp/src/utils/pairCatalogRank.tsfrontend-dapp/src/types/index.tsvolume_usd_24honIndexerPair;volume_usdon statsfrontend-dapp/src/pages/PoolPage.tsxfrontend-dapp/src/pages/ChartsPage.tsxfrontend-dapp/src/components/trade/__tests__/PairSearchSelect.issue534.test.tsxT; USD badge for USTR fixturedocs/frontend.mddocs/indexer-invariants.mdskills/AGENTS_FRONTEND_PAIR_CATALOG_RANK.mdskills/AGENTS_INDEXER_PAIR_PRICE_USD.mdskills/AGENTS_INDEXER_EXTERNAL_ORACLE.mdskills/AGENTS_INDEXER_VOLUME_PAGINATION.mdRecommended direction
compute_volume_usdwith catalog conversion of human oriented quote (else offer) ×usd_per_human_quote. Unit-test UST1/USTR (USTR × 2.5 × USTC), UST1/cUSTC (cUSTC × USTC), cLUNC/UST1 (UST1 = $1), gem/unknown →None. Oracle missing for a needed ticker →None(do not use a stale hardcoded peg except UST1 = $1 and the documented USTR multiplier).swap_events.volume_usdwhere NULL and a catalog leg exists; rebuild candles only if this issue touches them (it should not). Refreshpair_volume_24handglobal_stats_24h.pair_volume_24h.volume_usd.PairResponse.volume_usd_24h: Option<String>(skip_serializing_if). Stats already havevolume_usd— keep the name; frontend types must include it.formatUsdVolume24h(usd: string | null): string | null→$19.3/$48.2K/$1.20M. Shared by picker, pool, charts.volume_usd_24h. Title24h volume (USD). No badge when null/0. Offline factory fallback: no invented USD.24h vol $…. Keep quote vol intitleor<details>if useful.total_volume_24h_usd(drop or demote the raw box). Pair stats: primary Vol (USD) fromstats.volume_usd; optional secondary human base/quote (#540).PairCatalogVolumegainsusd; compare USD desc, then human quote, then symbol.Invariants:
…T/ impossible trillions. USTR badge is catalog USD (USTR × 2.5 × USTC), not raw and not “19.3 USTR” as the default.volume_usdingest uses P522-Q + human decimals; unknown/gem → NULL.swap_eventsrow (L10). No fill/leg double count.volume_quote_24hraw unchanged;volume_usd_24hadditive.$0and neverformatNum(raw).Acceptance criteria
$19.3/$1.2K). Must not match/19,?297,?048T/ior/vol\s+[\d.,]+T/i.48.2mof cUSTC. Order of magnitude matches oracle × human quote, not raw 6-dec dump./poolselected / card 24h vol defaults to the same USD helper. Quote-token remainder is secondary or hidden./chartsoverview primary 24h volume istotal_volume_24h_usd./chartspair 24h stats primary vol isstats.volume_usd. RawformatNum(volume_quote)is gone from the default row.$0). Economic catalog pairs with oracle down: — / hidden, not aTfallback.GET /api/v1/pairsitems includevolume_usd_24hwhen the rollup has it;volume_quote_24hstill raw.sort=volume_24hstill works.make verify-issue-544, X4 / pair-list invariant updated.Tmust not return on USTR fixtures even if USD is mocked.Test plan (all paths)
Indexer unit / lib
compute_volume_usd/ catalog helper: UST1 offer → human UST1 × $1; USTR ask → human USTR × 2.5 × USTC; cUSTC leg → human cUSTC × USTC; cLUNC × LUNC; gem/unknown →None; USTC oracle missing and kind needs it →None; UST1 still works without oracle.19300000000000000000→ human19.3× quote USD; 6-dec must not apply1e6to USTR.None.Indexer integration (
--test-threads=1)refresh_pair_volumes;GET /api/v1/pairsreturnsvolume_usd_24hmatchingSUM(volume_usd)and still rawvolume_quote_24h.GET /api/v1/pairs/{addr}/stats.volume_usdmatches rollup for those pairs.swap_eventsaggregate (indexer_pair_volume_pagination.rs).total_volume_24h_usdincludes UST1/USTR after catalog ingest (regression vs USTC-only under-count).volume_usdon a catalog pair becomes non-null; gem stays NULL.sort=volume_24h+ pagination caps unchanged; invalidsortstill 400.Frontend unit
formatUsdVolume24h:19.3→$19.3;48200→$48.2K;0/''/NaN/-1→null; neverTfrom a human USD in normal range.PairSearchSelect: mockedvolume_usd_24hrendersvol $…; USTR raw-only fixture (no USD) renders noTbadge (hide).$100ranks above pair with$10even if quote-raw is larger (18-dec).formatNum(volume_quote)assertion inverted.total_volume_24h_usdfor the primary Volume box.Frontend / QA visual
make dev+ indexer: open/tradepair search — UST1/USTR and UST1/cUSTC badges are$…, not19297048T/48.2m./limitssame combobox./poolcards and selected-pair 24h line./chartsoverview + pair stats.$0.00.Regression commands
(Optional live)
VERIFY540/ Charts UST1/USTR: primary vol is USD.Test plan (attack, hack, abuse)
symbol=UST1/USTR/cUSTCnot in factory catalogquote_usd_kindmust not price it as the real hub. Prefer denom/contract allowlist used for wrap/registry assets; unknown contract + spoofed ticker →volume_usdNULL. Test a gem renamed UST1 in fixtures.volume_usd_24h="><script>/ huge string /1e309dangerouslySetInnerHTML. Cap display length.…Tfrom raw. Pair list stays rollup (V1); reject widening to live 24h scans.volume_usdby 1e6 or byprice_usdagainvolume_usdis human USD; UI does not × decimals or × price.target_volumeswitched to USDpair_address).volume_usd = -1or dust1e-18Verification criteria
Issue is done when:
/tradepair search and sees USD badges whose magnitude matchesGET /api/v1/pairs/{addr}/statsvolume_usd(not quote units, notT)./pooland/chartsprimary 24h volume match that same USD number (rollup freshness ≤ ~5 min is OK).make verify-issue-544and listed indexer/frontend tests pass in CI.volume_quote_24hremains raw; retail default isvolume_usd_24h; X4 is catalog (not USTC-only); L10 / CG units unchanged.Out of scope: changing candle histogram units; trader leaderboard
total_volume(follow-up if it still prints raw offer sums); inventing USD for faucet gems; using USD volume in fee/settlement math.marked as related to #534
marked as related to #540
marked as related to #522
marked as related to #515
changed the description
mentioned in issue #545
marked as related to #548
mentioned in issue #548
mentioned in merge request !1076
mentioned in issue #552
mentioned in issue #553
marked as related to #553
mentioned in issue #565
marked as related to #565
mentioned in commit
e7bca66f81mentioned in merge request !1089
mentioned in issue #692
marked as related to #692