feat: /protocol global USD stats + USTC/LUNC/vFDUSD oracle in one card #550
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#550
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
Rebuild
/protocolso it is a DEX stats + oracle page: a global USD stats card on top, then one oracle card where the user can select USTC, LUNC, and vFDUSD. Bundle these into one issue. Do not split “ticker chips” vs “merge history” vs “overview 7d/30d fields” vs “new tokens/pairs.”Related: #515 (ticker-scoped USTC/LUNC oracle), #378 (factory/router audit rows stay on
/protocolonly), #281 / #333 (/overview24h rollup + 60s cache), #489 (anti-cognitive-overload), #522 (volume_usduses USTC feed), #506 (on-chain UST1↔vFDUSD window — different oracle), #520 (Bitget source investigation — do not block).Current codebase
/protocolis operator-oriented today: hardcoded USTC/USD, two oracle panels, no DEX-wide USD dashboard. Charts already shows a thin 24h overview strip; Protocol does not.ProtocolPage.tsx: title Protocol & Oracle, lecture paragraph about USTC vs TWAP, then On-chain contracts (audit) (protocol-contract-addresses), then USTC / USD (indexer oracle) snapshot card, then Recent USTC/USD history as a separateshell-panel-strong, then Hook events.getOraclePrice()andgetOracleHistory({ limit: 48 })with no ticker. Client defaults both toustc. Query keys are['indexer-oracle-price']/['indexer-oracle-history']— not ticker-scoped. Copy,aria-label, and heading are hardcoded USTC.GET /api/v1/oracle/priceand/historyare catalogs{ metadata, tickers: ["ustc","lunc"] }(X1). Snapshots need/price/{ticker}and/history/{ticker}(X2).OracleTickeris onlyUstc|Lunc(indexer/src/indexer/oracle.rs). Frontend already hasgetOraclePrice(ticker?)/getOracleHistory({ ticker? })/getOraclePriceCatalog()— Protocol never uses them.VITE_VFDUSD_TOKEN_ADDRESS, mainnetterra1mnl9azefrqpmu888ar2u6zrcwr80hxlt3avf4300r576cw5ar7esvxsvj3). Not an external oracle ticker./ust1uses the ust1-window contract rate (vFDUSD↔UST1), not CEX FDUSD/USD.GET /api/v1/overview(indexer/src/api/overview.rs):total_volume_24h,total_volume_24h_usd,total_trades_24h,pair_count,token_count,ustc_price_usd. 60s whole-response cache. Cache miss readsglobal_stats_24h(24h only).token_countisget_all_assets().len()(loads every row). No 7d/30d volume, no tokens/pairs added in 30d, no actively-traded pair count.total_volume_24hisSUM(offer_amount)across all offer assets — mixed decimals (6 vs 18). Not a human USD number. USD isSUM(volume_usd)using the USTC oracle only (X4 / #522). Token windows already exist (token_volume_stats24h/7d/30d); global rollup does not.assets.created_at/pairs.created_atare indexer first-seen (schema defaultNOW()). After a DB rebuild they all look “new.”pairs.created_at_blockexists; no dedicated 30d COUNT indexes.ChartsPage.tsxalready renders 24h volume (raw + USD), USTC/USD, 24h trades, pairs, tokens fromgetOverview(). Protocol shows none of this.ProtocolPage.test.tsxonly asserts factory/router/LCD/RPC (#378).e2e/protocol-page.spec.tssmoke: heading + audit rows. Indexer:api_oracle.rs,api_overview.rs,indexer_overview_global_stats.rs.docs/frontend.md§ Protocol: USTC-only.docs/runbooks/indexer-external-oracle.md+skills/AGENTS_INDEXER_EXTERNAL_ORACLE.md: tickersustc|lunc. Overview runbook:docs/runbooks/overview-global-stats-brin.md.Why this is needed
total_volume_24hnext to USD. Protocol should be the place for USD 24h / 7d / 30d volume, token/pair census, 30d listings, and actively traded pairs — numbers people actually ask for.total_volume_24his misleading if used as a headline (sum of incomparable offer amounts). The new card must lead with USD.$1peg. Need a real FDUSD/USD poll, labeled vFDUSD in the dApp.Constraints / guardrails
/api/v1/oracle/priceand/historyremain catalogs. Unknown ticker → 400. Do not restore a numeric body on the catalog path.volume_usd/ overview USD conversion stay on the USTC handle. Do not convert DEX volume with LUNC or FDUSD/vFDUSD./ust1. vFDUSD/USD here is wrapped FDUSD’s CEX reference, not the window rate.OracleTicker::Vfdusdwith path segmentvfdusd. Poll FDUSD CEX/aggregator symbols (not USTC, not LUNC): CoinGecko idfirst-digital-usd; MEXCFDUSDUSDT. KuCoin may lack FDUSD — soft-fail that source (same pattern as CoinGecko 429). Do not hardcode$1. UI label vFDUSD / USD.ustc|lunc|vfdusd. URL?ticker=(optional) must be allowlisted;javascript:, HTML,../, unknown → defaultustc.pathSegment()already encodes; still never interpolate raw user input into fetch URLs beyond the allowlist.shell-panel. Ticker control at the top of that card. Switching ticker refetches both price and history (query keys include ticker).AddressRowonto other pages (#378).total_volume_*_usdwith$+formatNum(..., 2)(or compact USD). Do not headline mixed-unittotal_volume_24h. Optional: keep 24h trade count (dimensionless).global_stats_24hor addglobal_statskeyed by window24h/7d/30d, refreshed byvolume_aggregator~5 min). Keep 60s/overviewresponse cache (V5). Cache-miss must stay O(1) / cheapCOUNT(*)— noSUM/COUNT(DISTINCT)over 30dswap_eventsper request (DoS; #281/#333).pair_idwith ≥1swap_eventsrow in the last 24h (materialized on the 24h rollup). Document in the runbook. Do not invent TVL from CGliquidity_in_usd(that field is currently 24h volume USD mislabeled).COUNT(*)wherecreated_at >= now() - 30 daysonassets/pairs. Document that a reindex/rebuild makes everything look new. Add supporting indexes if EXPLAIN needs them.token_countshould beSELECT COUNT(*) FROM assets, not load-all.total_volume_24h,total_volume_24h_usd,total_trades_24h,pair_count,token_count,ustc_price_usd. Charts strip may keep current fields; expanding Charts is optional, not required.24h volume,7d volume,30d volume,Tokens,New tokens (30d),New pairs (30d),Active pairs). No architecture essays. Keep market-data outage banner (#215) — noVITE_INDEXER_URLleak.make setup-indexer-postgres. Frontend smoke can mock indexer; do not skip LocalTerra when asserting the page against a live stack.Relevant files
frontend-dapp/src/pages/ProtocolPage.tsxfrontend-dapp/src/pages/ProtocolPage.test.tsxfrontend-dapp/e2e/protocol-page.spec.tsfrontend-dapp/src/services/indexer/client.tsgetOverview,getOraclePrice/History/ catalog; allowlisted tickerfrontend-dapp/src/types/index.tsIndexerOverview, oracle typesfrontend-dapp/src/components/ui/StatBox.tsxindexer/src/api/overview.rsindexer/src/db/queries/volume.rsindexer/src/indexer/volume_aggregator.rsindexer/src/api/oracle.rsindexer/src/indexer/oracle.rsindexer/src/db/queries/oracle.rsoracle_pricesby tickerindexer/tests/api_oracle.rsindexer/tests/api_overview.rsindexer/tests/indexer_overview_global_stats.rsdocs/runbooks/indexer-external-oracle.mdvfdusddocs/runbooks/overview-global-stats-brin.mddocs/indexer-invariants.mdskills/AGENTS_INDEXER_EXTERNAL_ORACLE.mddocs/frontend.mdscripts/qa/verify-issue-515.shverify-issue-<iid>Recommended direction
Indexer — oracle
vfdusdOracleTicker+OraclePriceHandleswithVfdusd(as_str() = "vfdusd").first-digital-usd; MEXCFDUSDUSDT; KuCoin: use a documented symbol or skip if unlisted (soft-fail, do not abort the tick).ustc,lunc,vfdusd. Storage already ticker-scoped (oracle_prices).fdusd(withoutv) is 400 unless you also alias it (prefer no alias — one path).ustc|lunc|vfdusd. X4 still USTC-only for volume.Indexer — overview census
Additive
OverviewResponsefields (names may match this set):total_volume_7d_usdvolume_usdover 7d (rollup)total_volume_30d_usdvolume_usdover 30d (rollup)tokens_added_30dCOUNT(*)assets withcreated_atin last 30dpairs_added_30dCOUNT(*)pairs withcreated_atin last 30dactive_pairs_24hAlso useful (include unless EXPLAIN says they blow the budget — then materialize):
total_trades_7d/total_trades_30dunique_traders_24hsenderin 24h (rollup; not per-requestCOUNT DISTINCT)Keep existing 24h USD + trade count +
pair_count+token_count. Prefer aglobal_statstable keyed bywindow(24h/7d/30d) mirroringtoken_volume_stats, or extra columns on the single-row table. Refresh in the same aggregator loop. Integration tests: window cutoffs (swap older than 7d not in 7d), empty DB zeros, EXPLAIN on cache-miss path does not scanswap_eventsfor 30d.Frontend —
/protocoldata-testid="protocol-global-stats") above oracle: 24h / 7d / 30d USD volume, tokens, new tokens 30d, new pairs 30d, active pairs 24h, 24h trades.StatBoxgrid; skeleton + RetryError + existing outage banner (include overview indetectMarketDataOutage).data-testid="protocol-oracle"):role="tablist"chips USTC / LUNC / vFDUSD (same pattern as Wrap/UST1 tabs). Selected ticker drives price + history. Sources table + history table inside this card. Heading like{symbol} / USD.?ticker=lunc(allowlist). Defaultustc.Acceptance criteria
/protocoloracle card can select USTC, LUNC, and vFDUSD. Selection loads that ticker’s price and history (/price/{ticker},/history/{ticker}). Default USTC.vfdusd.GET /price/vfdusdreturns FDUSD/USD from CEX/CG (not USTC, not LUNC, not hardcoded1). Soft-fail missing KuCoin.GET /api/v1/overviewwithout breaking existing keys. Charts still works.total_volume_24has USD or as “the” volume.swap_events.make verify-issue-<iid>+ indexer oracle/overview tests + RTL + Playwright (5 workers).Test plan (all paths)
Oracle UI
/protocol/price/ustc; history/history/ustc; one oracle cardlunc; heading LUNC/USD; history rows are LUNC scale (~1e-5) not USTC (~1e-3) when both mockedvfdusd; heading vFDUSD/USD; ~1.00 scale in mocks, not USTCrole="tab"; Arrow/Enter/Space move selection?ticker=lunc?ticker=btc/?ticker=../ustc/ HTML → USTC{ustc,lunc,vfdusd}even if catalog grows later without a label map (or map-only known three)Global stats UI
undefined/NaNtotal_volume_24hnot shown as the USD headlineIndexer oracle
GET /pricecatalogtickersincludesustc,lunc,vfdusd; noprice_usdon catalog/price/vfdusdticker=vfdusd; empty →price_usdnull/price/fdusd/price/btc,/price/USTC, path..%2f/history/vfdusdmake verify-issue-515Indexer overview
api_overview.rscreated_atinside/outside 30dswap_eventsaggregate on/overviewtoken_countCOUNT(*)not a truncated pageUnit / RTL
ProtocolPage.test.tsx: ticker clicks callgetOraclePrice('lunc'|'vfdusd')+ matching history; stats testids; factory/router still present; no second history heading.Playwright (5 workers,
e2e-smoke)/protocoldesign-tokens-visualprotocol shot still exists)Manual QA
Test plan — attack, hack, and abuse vectors
1/1.0. Depeg (mock0.87) must display.?ticker=and/price/{ticker}only allowlist.../,%2e%2e,ustc/../../,ticker[], SQL fragments → default or 400. No string-paste into SQL (enum parse only).innerHTML.javascript:query ignored.window.openindexerlogo_url.swap_eventsper GET. Burst still served from 60s cache + rollup. Add a test that EXPLAIN avoids seq-scan aggregates.limitcap (1000) unchanged; Protocol uses 48.volume_usd.Infinity.created_at, not a claim of on-chain genesis. After--freshDB, UI may show all as new — copy must not say “launched on chain this month.”active_pairs_24h. Document; do not add a USD floor in this issue unless cheap. Do not treat it as unique traders.ustc,lunc%00,VFDUSDpath variants except documented case-insensitive parse (todayto_ascii_lowercase— keep; do not acceptuѕtchomoglyphs as USTC beyond ASCII)..envnames (#215).position:fixedstats overlay on wallet/clickwrap.Verification criteria
make verify-issue-<iid>fails if: Protocol still callsgetOraclePrice()with no ticker; two oracle history headings remain;OracleTicker::ALLlength is 2; overview lacks 7d/30d USD + 30d listing + active-pair fields;get_all_assets().len()still implementstoken_count.cargo test --lib oracle;cargo test --test api_oracle --test api_overview --test indexer_overview_global_stats -- --test-threads=1.e2e-smoke).docs/frontend.mdProtocol section, oracle runbook + skill (X2 includesvfdusd), overview runbook +docs/indexer-invariants.md,AGENTS.mdlink tomake verify-issue-<iid>.make verify-issue-515still passes.Out of scope
volume_usdconversion./protocolor onto Swap confirmation.marked as related to #515
marked as related to #378
marked as related to #333
marked as related to #281
marked as related to #489
marked as related to #522
marked as related to #506
mentioned in commit
388bd558c9mentioned in merge request !1078
mentioned in commit
2f5b4ad14ementioned in commit
81b403d55ementioned in commit
9481c758e9mentioned in issue #548
Merged as !1078 onto
main(9481c758) after conflict resolution with !1076 (#548) and !1077 (#547).Product conflict resolved in the merge (not as originally written in this issue):
volume_usd: kept #548 P522-Q catalog (UST1 / USTC / LUNC / USTR). vFDUSD/FDUSD is not used for DEX volume. Overviewustc_price_usdstays the USTC ticker. Docs/skills/migration comments were updated; original P550-10 “USTC-only volume” was not kept.token_count: kept #548 C6 unique pair-leg assets (count_pair_leg_assets), notCOUNT(*) FROM assets.tokens_added_30dremains the assets first-seen census.Remaining / post-merge:
20260818120000_global_stats_windows_and_census.sqlafter the #548 backfill. The #548 migration’s rollupINSERTis 24h-only (it runs first); 7d/30d/active_pairs_24hstay 0 untilrefresh_global_stats(aggregator ~5 min or indexer restart). Do not re-run a 24h-onlyINSERTafter the census columns exist.protocol-pagee2e (dark shell exists indesign-tokens-visual).Tracking: new post-merge issue for deploy + remaining manual QA.
mentioned in issue #552
marked as related to #552
mentioned in issue #556
mentioned in issue #569
marked as related to #570
mentioned in issue #570
mentioned in issue #571
marked as related to #571
mentioned in issue #572
marked as related to #572
mentioned in issue #574
marked as related to #574
mentioned in issue #575
marked as related to #575
mentioned in issue #576
mentioned in issue #579
marked as related to #579
mentioned in issue #580
marked as related to #580
mentioned in merge request !1103
mentioned in merge request !1104
mentioned in issue #586
mentioned in issue #613
mentioned in issue #614
mentioned in issue #631
marked as related to #631
mentioned in issue #652
mentioned in issue #653
mentioned in issue #655
mentioned in issue #666
mentioned in issue #667
marked as related to #667
mentioned in issue #668