feat: /protocol stats — inline Δ%, volume prior-window %, daily series, flatten nesting #652
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#652
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
/protocolGlobal stats shows Total liquidity as$57Kin one box and 24h liquidity+13.48%/ 30d liquidity—in separate boxes. Last 24h vol has no prior-period Δ%. Fees on the same page already compute trailing-window flow Δ% but also split value vs % into sixStatBoxcards. The section is ashell-panelwrapping a grid ofcard-glassStatBoxes (chrome-in-chrome).Bundle into one issue: inline Δ% on each metric, volume prior-window Δ% (24h / 7d / 30d), flatten Global stats + Protocol fees nesting, and a UTC-day volume series (7d / 30d) under the volume row. Do not split “volume %” vs “merge boxes” vs “daily chart” vs “fees inline Δ%”.
Related: #550 (USD census), #569 (TVL + snapshot Δ%), #586 (fee totals +
flow_change_pct), #576 (trailing vs calendar copy), #577 (window decay), #489 (short copy), #631 (Llama UTC-day rollup — wrong shape to N+1 from the browser), #561 L561-2 (one chrome layer). Companion chrome pass: follow-up issue filed in the same batch (anti-nesting invariant + dApp audit).Current codebase
/protocolis the DEX USD census page. Global stats and fees already have the numbers; the layout treats Δ% as extra cards and volume has no prior-window % at all.ProtocolGlobalStats.tsx:shell-panel(protocol-global-stats) + 11-entrySTATSgrid. Each cell wrapsStatBox(card-glass !p-3). Liquidity is three boxes:protocol-stat-liquidity(formatProtocolUsd(total_liquidity_usd)),protocol-stat-liquidity-24h/protocol-stat-liquidity-30d(formatProtocolPct). Volume 24h / 7d / 30d are USD only — novolume_change_*. Census (tokens, new tokens/pairs 30d, active pairs, 24h trades) is count-only.ProtocolFeeStats.tsx: sameshell-panel+StatBoxnest. Six boxes: 24h/7d/30d USD and three sibling “fee change” cards (protocol-stat-fees-*-chg). Source/token tables sit below.card-glass. No delta column, noflatvariant. Value is a single string.overview.rs: 60s cache. Hastotal_volume_{24h,7d,30d}_usd,total_liquidity_usd,liquidity_change_{24h,30d}_pct,total_fees_*_usd,fees_change_{24h,7d,30d}_pct. Novolume_change_*_pct. Liquidity Δ% is vs snapshots (stock). Fee Δ% is vs prior equal trailing window (flow).refresh_global_statsaggregatesswap_eventsfornow−24h/now−7d/now−30donly. Does not compute[48h,24h),[14d,7d),[60d,30d)priors. GET live path (OVERVIEW_GLOBAL_STATS_LIVE) also lacks priors.protocol_fees.rs+flow_change_pct:(current − prior) / prior × 100whenprior > 0; elseNone(never Inf). Aggregator-only; GET readsglobal_stats_24h.global_liquidity_snapshots(P569-2).then=0/ missing →null→ em-dash. Not a flow Δ%.GET /api/v1/defillama/dailyis one UTCtimestamp(L631-7).from/toignored. No range dump. Llama volume excludes gems, wrap/unwrap, UST1 window (L631-3–L631-5) — not the same as Protocoltotal_volume_*_usd. Frontend has no Protocol volume chart.PriceChartis pair candles, not DEX-wide daily bars.trailingWindowCopy.ts: volume labels are trailingUtc::now()−N, not calendar. Fee change titles already say “prior window / em-dash until both windows fill.”ProtocolPage.test.tsxasserts separate liquidity-24h / 30d boxes and fee-chg boxes.e2e/protocol-page.spec.tssame.make verify-issue-550/569/586/576.AGENTS_FRONTEND_PROTOCOL_STATS.mdP569-1 / P569-7 require liquidity Δ% as boxes insideprotocol-global-stats. PFee-2 is fee Δ% vs prior window.docs/design-system.md“One chrome layer per region” still allows nestedcard-glassinsideshell-panelfor “distinct inner blocks.”Why this is needed
$57Kwith+13.48% 24h(and 30d) on the same row — same for volume and fees.shell-panel+ 11×card-glassis box-in-box for the same metric region. #561 audited Trade/Swap/Pool/Limits/Charts strip wording, but Protocol (andStatBoxitself) still nests. Fix this page here; the repo-wide pass is the companion issue.flow_change_pct/ PFee-2 /#576.Constraints / guardrails
GET /overviewstays 60s cache + O(1) rollup. Do notSUM60dswap_eventson the request path. Volume priors are computed on the aggregator (~5 min), same as fees.flow_change_pct. Windows: currentnow−Wvs prior[2W, W).prior ≤ 0/ missing / overflow → JSONnull, UI em-dash. NeverInfinity/NaN/"∞%"/ clientnow/thenwithout a zero guard.--fresh/ young indexer → em-dash until 2×W fills (PFee-9 / P569-8 pattern).$1UST1 or2.5×USTR. Activity + all unpriced → volume USDnulland Δ%null(do not treat as$0then−100%). Idle (trades=0) →$0; Δ% vs a positive prior may be−100%(same as fees).volume_change_24h_pct/volume_change_7d_pct/volume_change_30d_pctoptional onIndexerOverviewuntil indexer ships. Old indexers → volume tiles show USD + em-dash Δ%. Charts overview strip stays additive-compatible.protocol-stat-liquidity(value +% 24h+% 30d). Merge each fee window’s chg box into the USD tile. Keepprotocol-stat-liquidity-24h/protocol-stat-liquidity-30d/protocol-stat-fees-*-chgas child testids on the Δ% nodes so #569 / #586 / e2e can be updated, not silently dropped.protocol-global-statsandprotocol-fee-stats, metric cells must not becard-glass(noStatBoxdefault nest). Use a flatStatRow/StatBox variant="flat": label, value, optional Δ% column (positive--color-positive, negative--color-negative). Oneshell-panelper section. Do not wrap the grid in a second panel. Light + dark; 375px wrap: value and Δ% stay one cell (Δ% to the right of the value, not a new tile).24h/7d/30dis enough. Depth intitle/aria-label(reuse fee prior-window titles). Daily chart must say UTC calendar day so it is not read as the trailing 24h/7d/30d tiles. No lecture banner. No “launched on chain” / genesis claims.GET /defillama/daily7–30 times. Do not add unboundedfrom/toto Llama (L631-7). AddGET /api/v1/protocol/volume/daily?days=7|30(allowlist; else 400). 60s cache. Reads a capped daily rollup only (prune ≥ 35 days). Aggregator refresh, not GET-pathswap_eventsscan.OVERVIEW_GLOBAL_STATS_LIVE=1must still not 60d-SUM on GET.SUM(swap_events.volume_usd)with the same P522-Q catalog as overview. Do not silently reusedefillama_daily_stats(gem / wrap / window exclusions). If a future change shares storage, copy must label the exclusion. Idle day →"0"; activity + unpriced →nullbar (not$0). Cap 30 points. No CSV / no per-pair dump in v1.PriceChart. No trading overlay. Empty / loading / error + retry. Hide the chart if the range endpoint is missing (old indexer), do not invent zeros. Default 7d; 30d toggle. Do not headline unique traders.protocol-global-stats) → Protocol fees → hub → oracle → audit → hooks. Do not clone factory/routerAddressRow(#378).docs/frontend.md§ Protocol +docs/indexer-invariants.md. Newmake verify-issue-*for this ticket; keepverify-issue-550/569/586/576/577/631green (adjust selectors, do not weaken rollup/DoS tests)./ust1window.Relevant files
frontend-dapp/src/components/protocol/ProtocolGlobalStats.tsxfrontend-dapp/src/components/protocol/ProtocolFeeStats.tsxfrontend-dapp/src/components/ui/StatBox.tsxflatvariant + optional deltafrontend-dapp/src/utils/formatProtocolStats.tsfrontend-dapp/src/utils/trailingWindowCopy.tsfrontend-dapp/src/types/index.tsIndexerOverviewvolume_change_*frontend-dapp/src/pages/ProtocolPage.test.tsxfrontend-dapp/e2e/protocol-page.spec.tsindexer/src/api/overview.rsindexer/src/db/queries/volume.rsindexer/src/indexer/protocol_fees.rsflow_change_pctindexer/src/indexer/volume_aggregator.rsindexer/src/api/defillama.rsindexer/src/api/protocol_volume_daily.rs(or equivalent)days=7|30allowlist + 60s cacheglobal_stats_24h+ daily table (35d prune)indexer/tests/indexer_overview_global_stats.rsindexer/tests/indexer_protocol_fees.rsflow_change_pctparity for volumeskills/AGENTS_FRONTEND_PROTOCOL_STATS.mddocs/frontend.md§ Protocoldocs/indexer-invariants.mdRecommended direction
refresh_global_stats(and live helper used only by tests) with prior equal windows, same cutoffs as fees (c48/c14/c60). Persistvolume_change_*_pctonglobal_stats_24h. Callflow_change_pct. Do not walk snapshots.volume_usd+trade_count, prune 35d. New GET withdaysallowlist7|30, newest-last array, 60s cache. EXPLAIN tests: noswap_eventson GET.variant?: 'card' \| 'flat'(defaultcardso Swap/Trade callers stay).flat= nocard-glass. Optionaldelta+deltaLabel(24h). Protocol Global stats + fees useflat.data-testid="protocol-volume-daily-chart".Acceptance criteria
GET /overviewincludesvolume_change_{24h,7d,30d}_pct(stringornull). Cache-miss does not scanswap_events/protocol_fee_events/ snapshot history.flow_change_pctvs prior equal trailing window.prior ≤ 0→null. Unpriced activity →null. Overflow →null. Never Inf./protocolGlobal stats: Total liquidity shows USD and 24h/30d Δ% in that cell. No standalone liquidity-% tiles.card-glassinsideprotocol-global-statsorprotocol-fee-statsmetric grids (oneshell-paneleach).$0, unpriced—.GET /api/v1/protocol/volume/daily?days=only7|30; else 400. No Llama range dump. Methodology = Protocol catalog, not Llama gem-exclude, unless explicitly labeled (do not silently mix).aria-labelincludes trailing-window / prior-window / UTC-day text so mobile is not hover-only (#576).make verify-issue-*for this ticket.verify-issue-550/569/586/576/577/631still pass.Test plan (all paths)
Indexer — volume Δ%
now−24hand[48h,24h): Δ% =(cur−prior)/prior×100(fixture parity withflow_change_pct).prior=0→ JSONnull.trades=0, usd=0) + positive prior →−100(or documented equivalent); UI shows$0+ negative %.volume_usdall null → overview volumenull, Δ%null(not−100%from a fake$0).[14d,7d),[60d,30d)).--fresh/ no history → all threenull.OVERVIEW_GLOBAL_STATS_LIVE=1GET still does not 60d-SUM fees or volume priors fromswap_events(rollup read). EXPLAIN on GET/overviewand GET daily: noswap_events.Indexer — daily series
days=7→ ≤7 points;days=30→ ≤30;days=1/days=90/days=/days=7%3b/days[]=→ 400.23:59Zvs00:00Zland on the correct day."0"; activity+unpriced →null; no row yet → omit or"0"consistently (document).volume_usd; do not copy Llama exclusions without a label test.Frontend — layout + Δ%
protocol-stat-liquidity;queryByTestIdstandalone 24h/30d cards absent; child Δ% testids present.%; 7d/30d same.protocol-stat-fees-24hcontains USD + Δ%; chg testid is a child, not a sibling card.null/"Infinity"/"NaN"/1e309→ em-dash (existingformatProtocolPct).total_volume_24h_usd/ Δ% / daily point ="><script>/javascript:render as text, not HTML.RetryError; daily error → retry on chart only (do not hide tiles).trailingWindowCopytest).Frontend — daily chart
days=30only.$0sparkline).PriceChart/ pair interval chips.--focus-ring).E2E / verify
e2e/protocol-page.spec.ts: page order, one liquidity tile, fee tiles, chart testid when API present.make verify-issue-550569586576577631+ new verify script.Test plan (attack, hack, and abuse)
days=999999, negative, array, SQL-looking strings → 400, no scan. Parallel burst of/overview+ daily stays 60s cache (no per-request 60dSUM)./defillama/daily. Addingfrom/toto Llama in this ticket is a fail.total_volume_*_usdwithout label.NUMERIC(38,18)→null, not a 1e400 string that breaks UI or JSON.−100%on a$0that is actually unpriced activity.?ticker=; still allowlist oracle chips (P550-2).days.updated_at>15 min: log + serve (D6); do not fall back to live 60d scan.Verification criteria
make setup-indexer-postgresthen newmake verify-issue-*(lib + overview + daily integration,--test-threads=1).make test-frontendincludes ProtocolPage / StatBox / trailingWindowCopy / formatProtocolStats./protocolat 375px and 1280px, light + dark — liquidity is one cell ($…+% 24h+% 30d); 24h vol shows a % or em-dash; fees are three tiles; chart 7d/30d; no card-in-card rings in those two panels.StatBoxAPI backward compatibility.mentioned in issue #653
Companion chrome pass (invariant + Charts/Trader/oracle grids): #653.
mentioned in merge request !1151
mentioned in commit
df236fcc9bmentioned in merge request !1152
mentioned in commit
640bb28f36mentioned in commit
7245ac1c74mentioned in issue #654
marked as related to #654
Merge !1152 is on
main(640bb28f). Localmake verify-issue-652was 8/8: docs/source guards, indexer lib (protocol_volumeallowlist +flow_change_pct), integration (indexer_protocol_volume+api_overview), Protocol/StatBox/trailing-window RTL, Playwrighte2e/protocol-page.spec.tse2e-smoke (5 workers), plus relatedverify-issue-550andverify-issue-586.Problems / leftovers (tracked on #654 — do not reopen this issue unless a merged invariant is wrong):
ci_quota_exceeded(not a code defect). Did not wait for CI.20260826120000_protocol_volume_change_and_daily.sqlyet. Live/overviewvolume_change_*and/api/v1/protocol/volume/dailyare unconfirmed on production./protocol375 + 1280, light + dark (one liquidity cell, inline Δ%, no card-in-card, 7d/30d chart) was not run.verify-issue-569/576/577/631were not re-run this pass (550 + 586 related path did run).mentioned in issue #666
mentioned in issue #667
marked as related to #667
mentioned in issue #668
marked as related to #668
mentioned in merge request !1158
mentioned in merge request !1163
mentioned in merge request !1164
mentioned in issue #677
marked as related to #677
mentioned in issue #689
marked as related to #689
mentioned in issue #703
marked as related to #703