Protocol AUDIT — Donate to pools card (indexer + wallet top-up)
## Context [GitLab #261](https://gitlab.com/PlasticDigits/yieldomega/-/work_items/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](https://gitlab.com/PlasticDigits/yieldomega/-/work_items/238). **Blocked by / pairs with:** [#261](https://gitlab.com/PlasticDigits/yieldomega/-/work_items/261) (contract + event shape). Follow the [#231](https://gitlab.com/PlasticDigits/yieldomega/-/issues/231) pattern: one protocol section, one HTTP read-model, shared `data-testid`, invariants in QA issue [#260](https://gitlab.com/PlasticDigits/yieldomega/-/work_items/260). ## 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) - `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 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](https://gitlab.com/PlasticDigits/yieldomega/-/issues/143)) - `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](https://gitlab.com/PlasticDigits/yieldomega/-/issues/157) ## Frontend acceptance criteria — single card **Placement:** protocol / **AUDIT** page (`/timecurve/protocol` today; `/arena/protocol` or equivalent after [#256](https://gitlab.com/PlasticDigits/yieldomega/-/work_items/256)). **`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](https://gitlab.com/PlasticDigits/yieldomega/-/work_items/261)) - [ ] 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](https://gitlab.com/PlasticDigits/yieldomega/-/work_items/258)) when available, else explorer link - [ ] Indexer unset/offline: **`EmptyDataPlaceholder`** / muted copy — **no fake zeros** ([#200](https://gitlab.com/PlasticDigits/yieldomega/-/issues/200), [#96](https://gitlab.com/PlasticDigits/yieldomega/-/issues/96)) - [ ] 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](https://gitlab.com/PlasticDigits/yieldomega/-/work_items/240) 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](https://gitlab.com/PlasticDigits/yieldomega/-/issues/95)) **Docs / invariants (can land in [#260](https://gitlab.com/PlasticDigits/yieldomega/-/work_items/260) 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
issue