feat: Charts overview 24h volume USD-only (fix $0 / 10,000,000T) and audit other stats #548
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#548
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 Charts & Analytics (
/charts) the overview strip still shows a meaningless raw 24h Volume (10,000,000T) next to 24h Volume (USD) = 0, even when 24h trades exist. Retail should see one 24h volume figure, in USD, with a correct (non-zero when catalog-priced volume exists) number. Also audit the other overview boxes (USTC/USD, 24h Trades, Pairs, Tokens) so they match documented indexer semantics.Observed (columbus-5 / dex.cl8y.com):
10,000,000TformatNum(total_volume_24h)on mixed rawSUM(offer_amount)— sameTclass as #534 / #5440(no$)volume_usdis USTC-leg only (X4);COALESCE(SUM(NULL),0)→"0"; UI treats"0"as a real value$0.004878004swap_eventscount (L10, not fills)1312assetsrowRelated (do not treat as done): #544 pair-list / picker USD volume + shared ingest catalog; #540 pair stats strip still
formatNum(raw); #522 P522-Q quote USD catalog; #515 oracles; #281 / #333 overview rollup + 60s cache.This issue uniquely owns the
/chartsoverview strip. Pair-search //poolbadges stay on #544. Pair-level 24h stats (Vol (AAA)/Vol (BBB)) stay on #540 / #544. Share onecompute_volume_usdcatalog change — do not invent a second USD formula.Current codebase
Frontend — two volume boxes, raw first
frontend-dapp/src/pages/ChartsPage.tsxloadsGET /api/v1/overviewviagetOverviewand renders sixStatBoxes:total_volume_24his a raw mixed-decimal integer (SUM(offer_amount)across all offer assets). Passing it toformatNumcompact-formats ≥1e12 asT. Ten human USTR (18 decimals) is1e19raw →10,000,000T. There is no unit on the label.total_volume_24h_usd:formatNum(x, 2)with no$. The empty check allows"0"/"0.0"through, so a true zero and a missing-USD rollup both render0. Contrast USTC/USD, which prefixes$.ChartsPage.test.tsxmocks both volumes as'0'and never asserts the overview strip labels or values.lg:grid-cols-6; dropping one box should reflow (5 boxes).Indexer — overview JSON + USTC-only
volume_usdGET /api/v1/overview(OverviewResponse):total_volume_24hglobal_stats_24h.total_volumeSUM(offer_amount)raw — not human, not USDtotal_volume_24h_usdglobal_stats_24h.total_volume_usdCOALESCE(SUM(volume_usd), 0)total_trades_24hglobal_stats_24h.total_tradesCOUNT(*)ofswap_eventsin 24hpair_countSELECT COUNT(*) FROM pairstoken_countassets::get_all_assets(&pool)?.len()assetsrow into memory just to countustc_price_usdstate.oracle_prices.ustcOption→ JSONnullRollup refresh ~5 min (
refresh_global_stats); whole response cached 60s (#281 / #333). Live fallback only ifOVERVIEW_GLOBAL_STATS_LIVE=1or rolluptotal_trades == 0but recent swaps exist.Why USD is 0 while raw volume and 4 trades are non-zero: ingest
compute_volume_usdreturnsSomeonly when offer or ask is USTC (uusd/ configuredustc_denom), using a hardcoded1_000,000decimals factor and the USTC oracle. Anything else (UST1/USTR, UST1/cLUNC, gem/UST1, …) →None.SUMof NULLs is 0. #522 already has P522-Q inpair_price_usd.rs(UST1=$1; USTC/cUSTC/uusd=#515 USTC; LUNC/cLUNC/uluna=#515 LUNC; USTR=2.5×USTC) forprice_usd, butcompute_volume_usddoes not use it. Documented X4 still says volume_usd is USTC-only (docs/runbooks/indexer-external-oracle.md).Worked example matching the screenshot: ~10 human USTR offered → raw
1e19→ UI10,000,000T. USD should be10 × 2.5 × ~$0.004878 ≈ $0.12, not0.Other overview numbers (audit targets)
—, not$0.formatNum(..., 6)pads$0.00487800. Stale feed has no UI TTL.COUNT(*)swap_eventsin the 24h windowlimit_order_fills. Rollup lag ~5 min.COUNT(*)frompairsis_activeis hardcodedtrue— count is “indexed pairs”, not “pairs with liquidity”.get_all_assets().len()assetsrows (natives + CW20s ever upserted), not necessarily unique pair legs. LP tokens live onpairs.lp_token(usually notassets). Loading the full table to count is wasteful and a mild DoS footgun as the catalog grows.Frontend types:
IndexerOverview(total_volume_24h_usd?optional even though the API always sends a string).Why this is needed
10,000,000Tis not a market size;0USD next to 4 trades is a lie. Price (USD) on the same page is already oracle-valued (#522); volume still speaks mixed raw integers.total_volume_24hon the JSON.$0vs missing vs dust.COALESCE(SUM(NULL),0)plusformatNum("0")cannot distinguish “no priced volume” from “no trades”. After catalog ingest, UST1/USTR (and other P522-Q legs) must contribute; remaining unknown quotes must not display as$0.$0.000000on a missing oracle — all need an explicit retail contract and tests.Constraints / guardrails
24h volume in USD).quote_usd_kind/usd_per_human_quoteas #544. One ingest implementation shared with #544 — do not fork the formula. Unknown quotes →volume_usdNULL, not$0, not a guessed price.10^decimalsof the priced leg (USTR 18, UST1/cUSTC/cLUNC 6). Do not reuse hardcodeddecimals_factor = 1_000_000.offer_amount/return_amountonce. Prefer catalog-known oriented quote (human quote × quote USD). If only the other leg is in the catalog, use that leg. If both known, one side (quote preferred) — never sum both, never add fills/legs.total_volume_24h. Additive JSON. Charts must not render it. Do not humanize it in the indexer (still rawSUM(offer_amount)).—, not$0.00, not0. Iftotal_trades_24h > 0but catalog USD is 0/empty (all legs unknown or oracle down), show —. True idle DEX (total_trades_24h == 0) may show$0or—— pick one and test it ($0only when there were no trades).$+ compact human USD (formatNumon a human number, 2–3 sigfigs). Never pass raw integers. Reject non-finite / negative. Do not compact-format the USTC spot asT(use a price formatter /formatPairPrice, keep$).SUM(swap_events)on/overviewin production. Backfillswap_events.volume_usdthenrefresh_global_stats(same as #544). Document ~5 min rollup + 60s response cache.base_volume/target_volume, candle histogram units, #522price/price_usd, hybrid columns, or trader leaderboardtotal_volume(follow-up if it still prints raw).swap_eventsrows only (L10).COUNT(*)indexed factory pairs (current SQL is OK if provenance holds).pairs.asset_0_idorasset_1_id(notget_all_assets().len(), not LP tokens). UseCOUNTSQL, do not load the table.—. Do not use this box as volume.make verify-issue-<iid>in the same MR. Update X4 together with #544 (catalog, not USTC-only). Overview row indocs/indexer-invariants.md. Charts overview indocs/frontend.md.Relevant files
frontend-dapp/src/pages/ChartsPage.tsxfrontend-dapp/src/pages/ChartsPage.test.tsxfrontend-dapp/src/types/index.ts,frontend-dapp/src/services/indexer/client.tsfrontend-dapp/src/utils/formatAmount.tsindexer/src/api/overview.rsindexer/src/db/queries/volume.rs,indexer/src/indexer/volume_aggregator.rsindexer/src/indexer/parser.rs(compute_volume_usd)indexer/src/indexer/pair_price_usd.rsindexer/src/db/queries/assets.rs(get_all_assets)indexer/tests/api_overview.rs,indexer/tests/indexer_overview_global_stats.rsdocs/indexer-invariants.md,docs/runbooks/indexer-external-oracle.md,docs/runbooks/overview-global-stats-brin.mdskills/AGENTS_INDEXER_VOLUME_PAGINATION.md,skills/AGENTS_INDEXER_PAIR_PRICE_USD.md,skills/AGENTS_INDEXER_EXTERNAL_ORACLE.mdRecommended direction
compute_volume_usdthrough P522-Q; humanize with per-asset decimals; backfill; refreshglobal_stats_24h. Assert a UST1/USTR swap produces non-NULLvolume_usd≈ human USTR × 2.5 × USTC.token_count: replaceget_all_assets().len()with aCOUNT(DISTINCT …)over pair legs. Keeppair_countasCOUNT(*) FROM pairs. Keeptotal_trades_24has swap-row count.total_volume_24hfor integrators. Optionally add nothing new — Charts just stops reading it. Iftotal_volume_24h_usdis"0"because of NULL sum, consider omitting / using JSONnullwhen there is 24h trade activity but no priced USD so the UI can show—without guessing. Prefer an explicitnullover"0"for “unpriced”.StatBox. Keep one USD volume box ($+ compact).—when unpriced.$0(or—) only whentotal_trades_24h === 0. Prefix USTC with$; missing →—. Adddata-testids:charts-overview-volume-usd,charts-overview-ustc-usd,charts-overview-trades,charts-overview-pairs,charts-overview-tokens. Do not leave a testid for the removed raw volume box.make verify-issue-<iid>: Vitest overview strip; indexer overview + ingest USD; optional Playwright/chartswith mocked overview.If #544 lands the ingest/backfill first, this issue is frontend + token_count SQL + overview display contract + verify script. If this lands first, #544 must consume the same ingest helper.
Acceptance criteria
/chartsoverview shows exactly one 24h volume control, in USD ($+ compact human). No raw24h Volumebox, no…Tfromtotal_volume_24h.GET /api/v1/overviewtotal_volume_24h_usd(rollup lag ≤ ~5 min OK) — not0.—, not$0/0. Zero trades → documented$0or—.$+ human USTC spot from overviewustc_price_usd; null/invalid →—; never LUNC; neverT.total_trades_24h= 24hswap_eventscount (not fills, not LP events).COUNT, not full table load). Fixture: N pairs / M distinct legs → Tokens = M.total_volume_24h(raw) for integrators; Charts does not display it.make verify-issue-<iid>exists and is listed in the Makefile help.VITE_INDEXER_URLleak.Test plan (all paths)
Indexer
volume_usd≈ human USTR × 2.5 × USTC (or human UST1 × $1); overview SUM includes it after refreshvolume_usd≈ human cUSTC × USTC (or UST1 × $1); one side, not bothvolume_usdNULL; overview USD does not invent a numbervolume_usd; fills not added (L10)refresh_global_statsvs livetotal_volume_24h_usd/total_trades_24hmatch live after refreshtoken_countassetsrow does not increment; LP address onpairs.lp_tokendoes not incrementpair_countCOUNT(*) FROM pairsustc_price_usdnullwhen unset; never LUNC value/overview60s cacheSUM(swap_events)on the hot pathFrontend (Vitest)
total_volume_24h: '10000000000000000000',total_volume_24h_usd: '1234.56', trades 410,000,000T/ raw volume label; one USD box~$1.235K(or$1,235);$present; no second volume boxtotal_volume_24h_usd: '0'or'0.00'andtotal_trades_24h > 0—, not0'0',total_trades_24h: 0$0or—)total_volume_24h_usdmissing/null/empty—ustc_price_usd: '0.004878'$+ human (noT);null→—;''→—pair_count/token_count/total_trades_24hT0NaN/1e309/ HTML string in USD field—; no exception; no HTML injectManual / LocalTerra / mainnet
/chartson dex.cl8y.com (or LocalTerra with UST1/USTR + UST1/cUSTC flow)curl $INDEXER/api/v1/overviewtotal_volume_24h_usd; not0when trades exist and legs are catalog-priced/api/v1/pairs?limit=1totalGET /api/v1/oracle/price/ustc$prefixlglayoutTest plan (attack, hack, abuse)
symbol=UST1/USTRnot in factory catalogvolume_usdNULL; overview USD not inflated. Prefer denom/contract allowlist (same as #544 A1).volume_usdonly. Adding fills must fail the test.total_volume_24h_usd="><script>/ huge string—. NodangerouslySetInnerHTML. Cap display length.total_volume_24hformatNum. Even if API sends it, unused.price_usdagain on already-human USD—, not last-good forever without documented TTL (#515). Do not use overview USD for settlement/fees.assetsrowsCOUNTSQL / pair-leg distinct; noget_all_assetsfull fetch on/overview.volume_usd = -1or1e-18—/ clamp; never negative compact.total_volume_24hfrom JSONVerification criteria
Issue is done when:
/chartsand sees one 24h volume figure in USD, matchingGET /api/v1/overviewtotal_volume_24h_usd, not0when catalog-priced 24h swaps exist, and not10,000,000T.make verify-issue-<iid>and listed indexer/frontend tests pass in CI.total_volume_24hremains raw for API clients; X4 catalog (shared with #544); L10 / CG units unchanged; token_count = pair-leg distinct.Out of scope: Trade/Pool pair-search USD badges (#544); Charts pair stats
Vol (token)formatting (#540/#544); candle histogram; trader leaderboard raw volume; inventing USD for faucet gems; settlement/fee math.marked as related to #544
marked as related to #540
marked as related to #522
marked as related to #515
marked as related to #281
marked as related to #333
mentioned in commit
7393dfed7amentioned in merge request !1076
mentioned in commit
11b1117edamentioned in commit
0536b093ebmentioned in commit
81b403d55eMerged as !1076 onto
main(11b1117e). Follow-up merge !1078 kept this issue’s P522-Q catalogvolume_usd, nullabletotal_volume_24h_usd, and pair-legtoken_count(did not revert to USTC-only X4).Remaining / post-merge:
indexer/migrations/20260817120000_backfill_swap_volume_usd_catalog.sql(and the later #550 census migration). After backfill, wait forrefresh_global_stats(~5 min) plus the 60s overview cache, then confirm/charts24h USD matchesGET /api/v1/overviewtotal_volume_24h_usd.Tnotation; pair-search USD badges stay #544) is still open.H2deep-link RTL logsQuery data cannot be undefinedforindexer-pair-one(test still passes). Harmless noise, not a product miss.Tracking: new post-merge issue for deploy + remaining manual QA.
mentioned in issue #547
mentioned in issue #550
mentioned in issue #552
marked as related to #552
mentioned in issue #553
marked as related to #553
mentioned in issue #556
mentioned in issue #557
mentioned in issue #562
mentioned in issue #564
mentioned in issue #565
marked as related to #565
mentioned in issue #568
mentioned in merge request !1088
mentioned in issue #569
mentioned in merge request !1089
mentioned in issue #576
mentioned in issue #586
mentioned in issue #631
marked as related to #631
mentioned in issue #653
mentioned in issue #666
marked as related to #666
mentioned in issue #682
mentioned in issue #683
mentioned in issue #692
marked as related to #692