AUDIT page: platform usage stats card (wallets, buys, WarBow CL8Y)
Context
The TimeCurve AUDIT surface (/timecurve/protocol, subnav AUDIT, TimeCurveProtocolPage) is the operator-facing protocol view. It already shows semilive onchain sale reads, indexer-backed Live buys, WarBow governance helpers, and a collapsed raw accordion — but there is no network-wide usage summary for how participants are using the sale + WarBow product.
Product ask: add a Platform usage stats card/section on this page covering wallet participation, buy distribution, per-wallet spend, and WarBow action volume with CL8Y spend breakdown.
Related (separate scope): GitLab #229 — DOUB projection economics card on the same page.
Metrics (acceptance)
Participation & buys
| Stat | Definition |
|---|---|
| Unique wallets | Count of distinct 0x addresses that have interacted with TimeCurve sale + WarBow on the indexed chain (see union below). |
| Total buys | COUNT(*) from idx_timecurve_buy (canonical Buy rows). |
| Mean buys per wallet | total_buys / unique_buyers (buyers = distinct buyer in idx_timecurve_buy). |
| Median buys per wallet | Median of per-wallet buy counts (Postgres percentile_cont(0.5)). |
| Wallet leaderboard table | All wallets with ≥1 buy: address, buy count, total CL8Y spent on buys (SUM(amount) wei). Sort default: spend desc, then buys desc. Paginated (operator-scale; avoid unbounded DOM). |
Unique-wallet union (recommended v1): distinct addresses appearing in any of:
idx_timecurve_buy.buyeridx_timecurve_warbow_steal.attackeror.victimidx_timecurve_warbow_revenge.avengeror.stealeridx_timecurve_warbow_guard.playeridx_timecurve_warbow_flag_claimed.player
Out of scope unless product expands: Rabbit Treasury deposits/withdrawals, referral registerCode burn-only wallets with zero buys/WarBow.
WarBow actions & CL8Y
| Stat | Source | Notes |
|---|---|---|
| Steals | idx_timecurve_warbow_steal |
Count rows; CL8Y spent = SUM(burn_paid_wad). |
| Steal overrides | same table | Count where bypassed_victim_daily_limit = true; CL8Y = SUM(burn_paid_wad) for those rows only. |
| Revenges | idx_timecurve_warbow_revenge |
Count; CL8Y = SUM(burn_paid_wad). |
| Guards | idx_timecurve_warbow_guard |
Count; CL8Y = SUM(burn_paid_wad). |
Display each action type as count + total CL8Y (18-decimal formatting via existing AmountDisplay / compact helpers). Optional secondary row: mean CL8Y per action if it fits the grid without clutter.
Historical WarBowCl8yBurned rows (idx_timecurve_warbow_cl8y_burned): pre–FeeRouter era only; do not double-count with burn_paid_wad on action tables for totals (either exclude from v1 totals or document as legacy appendix — recommend exclude for v1; action tables are authoritative post-routing change).
Current codebase (research)
| Area | Location | Relevance |
|---|---|---|
| AUDIT page | TimeCurveProtocolPage.tsx |
Mount new section (suggest after Live buys or before Immutable parameters). |
| Per-wallet stats (connected wallet only) | GET /v1/timecurve/buyer-stats in indexer/src/api.rs |
Returns indexed_buy_count + indexed_charm_weight for one buyer — not network aggregates. |
| Indexed tables | idx_timecurve_buy (amount = CL8Y wei, charm_wad), WarBow migrations 20260403150000_timecurve_warbow_actions.up.sql |
All metrics are indexer read-model derivations (INV-INDEXER-112). |
| Stat UI patterns | StatCard, #200 empty states |
Use EmptyDataPlaceholder / statFromContractRead when indexer offline. |
| Pagination precedent | Referral leaderboard #225 (closed) | Numbered pages + total for wallet table. |
Gap: No GET /v1/timecurve/platform-usage (name TBD) returning summary + paginated wallet rows. Frontend must not scrape all buys client-side.
Recommended solution
1. Indexer API (new)
Add GET /v1/timecurve/platform-usage (or /usage-stats) with:
Query: offset, limit (wallet table page; clamp like other list routes).
Response (sketch):
{
"unique_wallets": "1234",
"total_buys": "5678",
"unique_buyers": "1200",
"mean_buys_per_wallet": "4.73",
"median_buys_per_wallet": "3",
"warbow": {
"steals": { "count": "...", "cl8y_spent_wei": "..." },
"steal_overrides": { "count": "...", "cl8y_spent_wei": "..." },
"revenges": { "count": "...", "cl8y_spent_wei": "..." },
"guards": { "count": "...", "cl8y_spent_wei": "..." }
},
"wallets": {
"total": "...",
"items": [
{ "wallet": "0x…", "buy_count": "…", "cl8y_spent_wei": "…" }
],
"next_offset": 50
}
}- All numeric fields as decimal strings (existing API convention).
- SQL: single summary query +
GROUP BY buyerfor wallet aggregates; WarBow counts via separate aggregates or CTEs. - Address predicates:
column = $nwith lowercase bind (INV-INDEXER-149-ADDR-EQ,INV-INDEXER-165). - Bump
SCHEMA_VERSION+ document inindexer/README.md/docs/indexer/design.md. - Integration test in
indexer/tests/integration_stage2.rswith seeded buys + WarBow rows.
2. Frontend
fetchTimecurvePlatformUsageinindexerApi.ts.- Presentational section e.g.
TimeCurveProtocolPlatformUsageSection.tsx:PageSectiontitle “Platform usage”, badge indexer-backed.- Top
stats-grid: unique wallets, total buys, mean/median buys per wallet, WarBow quadrants (count + CL8Y). - Wallet table with
AddressInline, explorer links (#98 (closed)), pagination controls. - When
VITE_INDEXER_URLunset or connectivity offline (#96 (closed)): muted status + placeholders — no fake zeros.
data-testid="timecurve-protocol-platform-usage"for E2E.
3. Performance / ops
- Wallet
GROUP BYmay grow large on mainnet; require server-side pagination (default limit 50). - Consider optional
?min_buys=1later; v1 returns all buyers with ≥1 buy. - Document that stats reflect indexed chain history only (ingestion lag, reorgs handled by indexer policy).
Verification criteria
Automated
- Indexer integration test: seeded data → expected counts, median, wallet page, WarBow CL8Y sums, steal_override subset.
- Frontend unit test: API path helper + offline/empty rendering (mirror
indexerApi.test.tspatterns).
Manual QA
-
/timecurve/protocolshows Platform usage without expanding raw accordion. - With local stack + bot swarm: unique wallets and total buys increase; wallet table shows bot addresses and spend.
- Steal with bypass (steal override) increments override count and CL8Y subtotal separately from normal steals.
- Indexer stopped: section shows offline/empty state, not misleading numbers (#200 (closed)).
- Mobile: stats grid wraps; table scrolls horizontally if needed.
Docs (light)
- Cross-link in
docs/frontend/timecurve-views.mdAUDIT section. - Optional
INV-INDEXER-*/INV-FRONTEND-*rows in invariants map.
Dependencies / decisions
- Confirm unique wallets union (sale + WarBow only vs include referrals/Rabbit).
- Confirm wallet table default sort (CL8Y spent vs buy count).
- Page size default (50 vs 100) for wallet list.