Protocol AUDIT — Donate to pools card (indexer + wallet top-up)

Context

GitLab #261 adds onchain topUpPodiumPools — permissionless DOUB transferFrom(msg.sender) into active + next-round prize vaults with no platform take. Operators and participants need a single AUDIT-page card that (1) submits donations, (2) shows the required no-benefit disclosure, and (3) surfaces indexer-backed donation history and totals on the same card.

Parent epic: #238 (closed). Blocked by / pairs with: #261 (closed) (contract + event shape).

Follow the #231 (closed) pattern: one protocol section, one HTTP read-model, shared data-testid, invariants in QA issue #260 (closed).

Relevant files

Indexer

  • indexer/migrations/ — e.g. idx_arena_podium_pool_top_up (or idx_timecurve_podium_pool_top_up until arena rename)
  • indexer/src/decoder.rs — decode PodiumPoolsToppedUp (or final event name from #261 (closed))
  • indexer/src/api.rs — new route + SCHEMA_VERSION bump
  • indexer/tests/integration_stage2.rs
  • docs/indexer/design.md

Frontend

  • frontend/src/pages/TimeCurveProtocolPage.tsx (v2: arena protocol / audit route) — mount new section
  • New: TimeCurveProtocolDonatePoolsSection.tsx (rename to TimeArenaProtocolDonatePoolsSection.tsx when #256 (closed) lands)
  • frontend/src/lib/indexerApi.ts — fetch helper + types
  • New: useTimecurveProtocolDonatePools.ts (hook: indexer read + wagmi write)
  • frontend/src/lib/ensureCl8yTimeCurveAllowance.ts pattern → DOUB approve to TimeArena (exact allowance per #143 (closed))
  • docs/frontend/timecurve-views.md — AUDIT section doc
  • frontend/e2e/timecurve.spec.ts or arena equivalent

Indexer acceptance criteria

  • Persist each top-up: tx_hash, log_index, block_number, block_timestamp, donor_address (lowercase), amount_doub_wad
  • GET /v1/arena/podium-pool-donations (or /v1/timecurve/podium-pool-donations pre-rename) returns:
    • total_donated_doub_wad (network-wide sum)
    • unique_donors_count
    • recent[]: latest N donations { donor, amount_doub_wad, block_timestamp, tx_hash } (default N=10, paginated optional)
    • donor_summary when ?donor=0x… set: that wallet’s total_donated_doub_wad + count
  • Empty DB → zeros / empty arrays (not 404)
  • 500 bodies redacted per #157 (closed)

Frontend acceptance criteria — single card

Placement: protocol / AUDIT page (/timecurve/protocol today; /arena/protocol or equivalent after #256 (closed)). data-testid="timecurve-protocol-donate-pools".

Disclosure (required, always visible above the donate CTA):

Donating to the pools makes YieldOmega prizes more exciting, but does not provide you with any benefit.

Use a StatusMessage or equivalent warning variant — not dismissible, not fine print.

Write path (connected wallet):

  • DOUB amount input + Donate to pools CTA
  • ChainMismatchWriteBarrier + exact DOUB approve then topUpPodiumPools(amount) (#261 (closed))
  • Preflight: balance ≥ amount; clear success / revert copy via friendlyRevertFromUnknown
  • After mined tx: refetch indexer summary + wallet DOUB balance

Read path (same card, below or beside write UI):

  • Total donated (network) from indexer
  • Your donations when wallet connected (donor_summary)
  • Recent donations table/list (donor abbrev + amount + relative time); row click opens wallet profile modal (#258 (closed)) when available, else explorer link
  • Indexer unset/offline: EmptyDataPlaceholder / muted copy — no fake zeros (#200 (closed), #96 (closed))
  • Manual ↻ refresh + optional poll after successful donate

Out of scope for this issue: counting donations toward XP, CRED, CHARM, or platform-usage wallet spend totals unless spec #240 (closed) explicitly amends.

Verification checklist

Indexer

  • Migration up/down
  • integration_stage2: ingest synthetic PodiumPoolsToppedUp → HTTP totals + recent rows match
  • SCHEMA_VERSION documented in indexer/README.md

Frontend

  • Vitest: warning string present; empty/offline states
  • Anvil: approve + donate → card totals increment after indexer catches up
  • Manual QA: disclosure visible without connecting wallet; write gated by chain match (#95 (closed))

Docs / invariants (can land in #260 (closed) if split)

  • INV-INDEXER-*-DONATE-POOLS + INV-FRONTEND-*-DONATE-POOLS in docs/testing/invariants-and-business-logic.md
  • docs/frontend/timecurve-views.md § donate-pools
  • Manual QA checklist row