feat: /protocol UTC volume chart — USD axis, hover, hourly/daily/monthly, responsive bars #668
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#668
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
/protocolUTC daily vol (protocol-volume-daily-chart) ships unlabeled bars with a7d/30dtoggle. Retail cannot read a USD scale, hover does not surface a designed Day + USD tooltip, and the window selector is the wrong grain. Bundle into one issue: visible USD value axis, hover/focus tooltip (period + USD), selector Hourly / Daily / Monthly, and viewport-sized bar count. Do not split axis vs tooltip vs grain vs responsive fetch.Related: #652 (UTC-day series +
P652-1–P652-7), #550 (USD census), #576 (trailing tiles ≠ calendar bars), #489 (short copy), #653 (one chrome layer), #631 (Llama daily — wrong shape to N+1).Current codebase
#652 added an additive UTC-day Protocol volume series. The chart exists; it is not yet a readable time-grain volume chart.
ProtocolVolumeDailyChart.tsx: custom SVG (viewBox="0 0 320 96",h-24). Bars only — no Y-axis ticks, no X-axis labels. Height isn / peak * 80. Hosted insideprotocol-global-stats(ProtocolGlobalStats.tsxdailyDaysstate7 | 30).<title>{utc_day} {formatProtocolUsd}</title>only. No designed tooltip, no keyboard focus, unreliable on touch / in-app browsers.7d/30d(protocol-volume-daily-7d/-30d). Copy: UTC daily vol / UTC calendar day (trailingWindowCopy.tsPROTOCOL_VOLUME_DAILY_*).series.length(7 or 30). FixedviewBoxwidth; not viewport-aware. Phone and desktop get the same 7 or 30 bars.getProtocolVolumeDailyallowlists7 | 30only.useProtocolVolumeDailyQuerykeys['indexer-protocol-volume-daily', days]. Hide on 404/501.GET /api/v1/protocol/volume/daily?days=: allowlist7|30else 400. 60s cache keyed bydays. Newest-last. Idle"0"; activity+unpricednull.protocol_daily_volume: UTC day only. Refresh involume_aggregator.rsviarefresh_protocol_daily. Lookback 34d, prune ≥ 35d. No hourly table. No monthly table.total_volume_*_usd(includes gems / wrap / window). Notdefillama_daily_stats.ProtocolPage.test.tsxasserts 7d default, 30d click, hide on 404, XSS text.e2e/protocol-page.spec.tslooks forprotocol-volume-daily-7d.indexer_protocol_volume.rsallowlist + idle/unpriced + cache.make verify-issue-652.days=7|30, default 7d / 30d toggle, noPriceChart, UTC calendar-day copy. P652-4: cap 30 points. These must be extended, not silently dropped.Why this is needed
<title>is not hover UX. Retail needs a tooltip that shows the UTC period and the USD amount on pointer and keyboard focus. A browser tooltip is easy to miss and fails on coarse pointers.7d/30dis the wrong control. Those windows already exist as trailing tiles (#576). The chart is a calendar-bucket series. The selector should be Hourly / Daily / Monthly (UTC), not a second copy of 7d/30d.Constraints / guardrails
formatProtocolUsdticks — never Inf /NaN/ rawuluna). The category axis stays time (UTC hour / day / month). Do not rotate into a horizontal bar chart (that fights time grain). Do not put USD on the category axis.SUMswap_eventson the request path. Do not N+1GET /defillama/daily. Do not add Llamafrom/to.OVERVIEW_GLOBAL_STATS_LIVE=1still must not 60d-SUM volume.hourly|daily|monthlyonly. Point count is a capped integer allowlist (see Recommended). Unknown / injection / arrays / negatives → 400. No free-formfrom/todates. No?ticker=.days=7\|30as a daily alias somake verify-issue-652stays green. Whengrainis omitted anddaysis7or30, behavior stays UTC-day newest-last (P652-3). New clients usegrain+limit."0"; activity + unpricednull(do not invent$0then a fake bar). Missing rollup row → idle"0". Newest-last.swap_events). Monthly needs a monthly rollup (or daily history long enough to fill the capped monthly window — prefer a monthly table so GET stays one-row-per-bucket).(grain, limit)(and thedaysalias).PriceChart/ lightweight-charts (P652-5). No trading overlay, no pair candles, no TV attribution on this census chart.protocol-global-stats. Do not wrap the plot in a nestedcard-glassgrid (C653). Labels ≤ ~5 words (#489): Hourly / Daily / Monthly. Subtitle must name the UTC bucket (hour / calendar day / calendar month). No unique-trader headline.formatProtocolUsd. Unpriced → em-dash. XSS /javascript:/"><script>inutc_dayorvolume_usdrender as text. NoinnerHTML. Noeval.limitfrom plot width (ResizeObserver), then clamp to grain[min, max]. Do not refetch on every pixel; debounce / snap to a small step so resize is not a request flood./ust1window.docs/frontend.md§ Protocol,docs/indexer-invariants.md,docs/runbooks/overview-global-stats-brin.md. Newmake verify-issue-*. Keepverify-issue-652/550/569/586/576/577/631/653green.Relevant files
frontend-dapp/src/components/protocol/ProtocolVolumeDailyChart.tsxfrontend-dapp/src/components/protocol/useProtocolVolumeDailyQuery.tsfrontend-dapp/src/components/protocol/ProtocolGlobalStats.tsx7 | 30days statefrontend-dapp/src/utils/formatProtocolStats.tsfrontend-dapp/src/utils/trailingWindowCopy.tsfrontend-dapp/src/services/indexer/client.tsdayshelper for tests)frontend-dapp/src/types/index.tsfrontend-dapp/src/pages/ProtocolPage.test.tsxfrontend-dapp/e2e/protocol-page.spec.tsindexer/src/api/protocol_volume.rsdays=7|30aliasindexer/src/db/queries/protocol_volume.rsindexer/src/indexer/volume_aggregator.rsprotocol_hourly_volume,protocol_monthly_volume; daily retain if needed)indexer/tests/indexer_protocol_volume.rsskills/AGENTS_FRONTEND_PROTOCOL_STATS.mddocs/frontend.md§ Protocoldocs/indexer-invariants.mddocs/runbooks/overview-global-stats-brin.mdscripts/qa/verify-issue-652.shRecommended direction
protocol_hourly_volume(UTC hour, prune ~8–10d) andprotocol_monthly_volume(UTC calendar month, keep ≥ 24 months). Refresh in the volume aggregator with the same P522-Q / idle / unpriced rules as daily. Do not scanswap_eventson GET. Extend daily retain only if Monthly is derived from daily rows; prefer a monthly table.GET /api/v1/protocol/volume/daily. Acceptgrain=hourly\|daily\|monthlyandlimit=(integer, grain-capped). Omitgrain+days=7\|30→ current daily series (alias). Response:grain,limit,timezone: "UTC",methodology: "protocol_catalog",series[]with a stable period key (utc_hour/utc_day/utc_monthasYYYY-MM-DDTHH/YYYY-MM-DD/YYYY-MM) +volume_usd+trade_count. 60s cache. EXPLAIN: noswap_events.ResizeObserveron the plot:limit = clamp(floor(width / barSlotPx), min, grainMax)(suggest bar slot ~10–14px including gap). Debounce. Query key includes grain + limit.PriceChart): 3–5 Y ticks viaformatProtocolUsdincluding$0baseline; sparse X time labels (do not label every hourly bar). Pointer + keyboard tooltip: period + USD (or em-dash). Peak0→ flat baseline, no divide-by-zero. Unpriced = outlined / empty bar, not$0.make verify-issue-*. Rewrite P652-5 selector language. Keep 652 daily-alias tests.Acceptance criteria
formatProtocolUsdticks). Bars are still a UTC time series (not a horizontal-bar USD category chart).<title>-only.7d/30d). Default Daily. Trailing 24h/7d/30d tiles unchanged.GETgrain + limit allowlisted; injection / missing / over-max → 400.days=7\|30withoutgrainstill returns the #652 daily series.swap_eventsSUM. No Llama. Idle"0"; unpricednull.--fresh/ young indexer shows idle zeros / available months, not a GET-path backfill.PriceChart. No nestedcard-glassaround the plot.volume_usdrender as text. Axis / tooltip neverInfinity.make verify-issue-*for this ticket.verify-issue-652/550/569/586/576/631/653still pass.Test plan (all paths)
Indexer — allowlist + cache
grain=daily&limit=14→ 14 newest UTC days, newest-last,methodology=protocol_catalog.days=7anddays=30with nograin→ same as #652 (len 7 / 30).grain=week,grain=daily%3b,grain[]=,limit=-1,limit=999999,limit=,from=/to=,days=90without valid grain → 400.FROM swap_events.Indexer — methodology
"0",trade_count=0.volume_usdJSONnull,trade_count>0."0".[hour, hour+1)UTC. Monthly = UTC calendar month, not trailing 30d.Frontend — axis, tooltip, grain, width
$/ em-dash), time labels present, no7d/30dtabs.aria-labelnames the UTC bucket.formatProtocolUsd; unpriced—.limitat grain min; bars do not overflow.limit> phone count and ≤ grain max.$0baseline, no crash.PriceChart/price-charttestid absent. Nocard-glasswrapper on the plot.protocol-global-stats; grain tabs visible when endpoint exists.Copy / a11y
aria-labelincludes UTC grain so mobile is not hover-only.Test plan (attack, hack, and abuse)
API / DoS
limit=999999,limit=1e308, overflow strings, repeatedlimit→ 400, no huge series, no GETswap_eventsscan.grain=hourly&from=1970-01-01&to=2099-12-31ignored or 400 (no unbounded range dump).?foo=1..n.grain/limit/days(7;,7%3b,daily' OR 1=1) → 400, parameterized reads only.Spoofed / hostile payload (XSS, prototype)
volume_usd/ period ="><script>alert(1)</script>,javascript:alert(1),{{constructor}}render as text in axis, tooltip, andaria-label. Noscriptnode. Nojavascript:navigation.Infinity/NaN/1e309→ em-dash on axis and tooltip (same asformatProtocolUsd).methodology/timezonestrings are not executed; page does not mount third-party scripts from JSON.Semantic abuse (fake volume)
$0bars that look like a crash to$0(staynull/ outlined).GET /defillama/dailyfrom/protocol).Client fetch abuse
getProtocolVolumeDaily/ new helper rejects non-allowlisted grain/limit before fetch (same as today’sdaysguard).Chrome / clickjack-adjacent
javascript:URL, no external iframe). Chart stays inside Global stats; no second chrome layer that could overlay wallet CTAs.Verification criteria
make verify-issue-<this>(new script): skill invariants, allowlist grep, no Llama N+1 from Protocol chart, no GETswap_events, frontend grain/axis/tooltip tests, indexer allowlist + idle/unpriced + cache.make verify-issue-652still passes (days=7\|30alias + hide-on-404 + XSS).make verify-issue-550/569/586/576/577/631/653green./protocolGlobal stats → chart has USD ticks; hover a bar → day/hour/month +$…; switch Hourly / Daily / Monthly; 375px vs 1280px bar counts differ and stay readable.AGENTS_FRONTEND_PROTOCOL_STATS.md,docs/frontend.md,docs/indexer-invariants.md,AGENTS.mdverify target.marked as related to #652
mentioned in commit
2658686220mentioned in merge request !1163
mentioned in merge request !1164
Implementation is in !1164 (
feat/668-protocol-volume-chart).Done in this change: USD value axis, pointer+keyboard tooltip, Hourly/Daily/Monthly (default Daily), viewport-clamped bar count, GET
grain+limitallowlist withdays=7|30alias, hourly/monthly rollup tables, hide on 404/501, XSS as text, P668-1–P668-8,make verify-issue-668.make verify-issue-652still passes.Not done here: live Chrome light/dark at 375px vs 1280px; related
verify-issue-550/569/586/576/577/631/653ladder; Coolify migrate of the new rollup tables on deploy.mentioned in commit
c9cf7106b6mentioned in commit
eb0238d68ementioned in issue #667
Merged to
mainvia !1164. Playwright--open falsekept. Docs merged with #667 Δ% prose.Leftover: Coolify must apply indexer migration
20260826180000_protocol_volume_hourly_monthly.sqland redeploy indexer, then frontend rebuild. Confirm Hourly/Daily/Monthly on/protocol.make verify-issue-668.mentioned in issue #673
mentioned in issue #677
marked as related to #677
mentioned in issue #689
marked as related to #689
mentioned in issue #692
mentioned in issue #703
marked as related to #703