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.buyer
  • idx_timecurve_warbow_steal.attacker or .victim
  • idx_timecurve_warbow_revenge.avenger or .stealer
  • idx_timecurve_warbow_guard.player
  • idx_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.

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
  }
}

2. Frontend

  • fetchTimecurvePlatformUsage in indexerApi.ts.
  • Presentational section e.g. TimeCurveProtocolPlatformUsageSection.tsx:
    • PageSection title “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_URL unset or connectivity offline (#96 (closed)): muted status + placeholders — no fake zeros.
  • data-testid="timecurve-protocol-platform-usage" for E2E.

3. Performance / ops

  • Wallet GROUP BY may grow large on mainnet; require server-side pagination (default limit 50).
  • Consider optional ?min_buys=1 later; 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.ts patterns).

Manual QA

  • /timecurve/protocol shows 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)

Dependencies / decisions

  1. Confirm unique wallets union (sale + WarBow only vs include referrals/Rabbit).
  2. Confirm wallet table default sort (CL8Y spent vs buy count).
  3. Page size default (50 vs 100) for wallet list.