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

Closed
opened 2026-05-27 14:33:21 +00:00 by PlasticDigits · 11 comments
PlasticDigits commented 2026-05-27 14:33:21 +00:00 (Migrated from gitlab.com)

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. Blocked by / pairs with: #261 (contract + event shape).

Follow the #231 pattern: one protocol section, one HTTP read-model, shared data-testid, invariants in QA issue #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)
  • 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

Frontend acceptance criteria — single card

Placement: protocol / AUDIT page (/timecurve/protocol today; /arena/protocol or equivalent after #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)
  • 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) when available, else explorer link
  • Indexer unset/offline: EmptyDataPlaceholder / muted copy — no fake zeros (#200, #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 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)

Docs / invariants (can land in #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
## 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
PlasticDigits commented 2026-05-27 14:33:22 +00:00 (Migrated from gitlab.com)

marked as related to #238

marked as related to #238
PlasticDigits commented 2026-05-27 14:33:23 +00:00 (Migrated from gitlab.com)

marked as related to #261

marked as related to #261
PlasticDigits commented 2026-05-29 06:58:48 +00:00 (Migrated from gitlab.com)

mentioned in commit b78aa914bd

mentioned in commit b78aa914bd2ca30b307b8ffee55ca3fb6c3b8cf9
PlasticDigits commented 2026-05-29 06:58:53 +00:00 (Migrated from gitlab.com)

Implementation summary (merged to main)

Delivered GitLab #262 together with onchain #261 (topUpPodiumPools + PodiumPoolsToppedUp).

Contracts

  • TimeArena.topUpPodiumPools(uint256) — permissionless DOUB pull, 100% to eight prize vaults via ArenaBuyRouting.splitPrizeTopUpAmount (10:7.5 active:seed per category), no admin vault / no totalDoubRaised bump.
  • Forge: ArenaPrizeRouting.t.sol, TimeArena.t.sol::test_topUpPodiumPools_*.

Indexer (schema 2.1.0)

  • Migration idx_arena_podium_pool_top_up.
  • Decode/persist PodiumPoolsToppedUp.
  • GET /v1/arena/podium-pool-donations — totals, recent[], optional donor_summary when ?donor=0x….
  • integration_stage2 HTTP smoke.

Frontend (AUDIT /timecurve/protocol)

  • Card data-testid="timecurve-protocol-donate-pools" with required no-benefit disclosure, DOUB approve + donate write path, indexer read path, wallet profile on donor rows.
  • Vitest + Playwright smoke assertions.

Docs / invariants / play skill


Verification checklist

  • forge test --match-contract "ArenaPrizeRouting|TimeArena"
  • cd indexer && cargo clippy --all-targets -- -D warnings && cargo test (with YIELDOMEGA_PG_TEST_URL for full integration_stage2)
  • cd frontend && npm test and npm run typecheck
  • Deploy/migrate indexer; ingest a PodiumPoolsToppedUp tx; confirm GET /v1/arena/podium-pool-donations totals match
  • AUDIT page: disclosure visible without wallet; donate gated on correct chain; after donate, totals refresh
  • Anvil stack: approve + topUpPodiumPools → vault balances match 700e18 prize slice equivalence

@brouie — please run the checklist above on devnet/staging when you have a moment. Leaving this issue open until verified.

## Implementation summary (merged to `main`) Delivered GitLab **#262** together with onchain **#261** (`topUpPodiumPools` + `PodiumPoolsToppedUp`). ### Contracts - `TimeArena.topUpPodiumPools(uint256)` — permissionless DOUB pull, **100%** to eight prize vaults via `ArenaBuyRouting.splitPrizeTopUpAmount` (10:7.5 active:seed per category), **no** admin vault / no `totalDoubRaised` bump. - Forge: `ArenaPrizeRouting.t.sol`, `TimeArena.t.sol::test_topUpPodiumPools_*`. ### Indexer (schema **2.1.0**) - Migration `idx_arena_podium_pool_top_up`. - Decode/persist `PodiumPoolsToppedUp`. - `GET /v1/arena/podium-pool-donations` — totals, `recent[]`, optional `donor_summary` when `?donor=0x…`. - `integration_stage2` HTTP smoke. ### Frontend (AUDIT `/timecurve/protocol`) - Card `data-testid="timecurve-protocol-donate-pools"` with required no-benefit disclosure, DOUB approve + donate write path, indexer read path, wallet profile on donor rows. - Vitest + Playwright smoke assertions. ### Docs / invariants / play skill - `INV-TIME-ARENA-PODIUM-TOPUP`, `INV-INDEXER-262-DONATE-POOLS`, `INV-FRONTEND-262-DONATE-POOLS` - [arena-views.md § donate-pools](docs/frontend/arena-views.md#protocol-donate-pools-gitlab-262), [indexer design](docs/indexer/design.md#arena-podium-pool-donations-http-gitlab-262), [play-time-arena-doub](skills/play-time-arena-doub/SKILL.md) --- ### Verification checklist - [ ] `forge test --match-contract "ArenaPrizeRouting|TimeArena"` - [ ] `cd indexer && cargo clippy --all-targets -- -D warnings && cargo test` (with `YIELDOMEGA_PG_TEST_URL` for full `integration_stage2`) - [ ] `cd frontend && npm test` and `npm run typecheck` - [ ] Deploy/migrate indexer; ingest a `PodiumPoolsToppedUp` tx; confirm `GET /v1/arena/podium-pool-donations` totals match - [ ] AUDIT page: disclosure visible without wallet; donate gated on correct chain; after donate, totals refresh - [ ] Anvil stack: approve + `topUpPodiumPools` → vault balances match 700e18 prize slice equivalence @brouie — please run the checklist above on devnet/staging when you have a moment. Leaving this issue **open** until verified.
PlasticDigits commented 2026-05-29 08:06:26 +00:00 (Migrated from gitlab.com)

mentioned in issue #264

mentioned in issue #264
PlasticDigits commented 2026-05-29 08:10:14 +00:00 (Migrated from gitlab.com)

mentioned in issue #267

mentioned in issue #267
PlasticDigits commented 2026-05-29 08:10:14 +00:00 (Migrated from gitlab.com)

marked as related to #267

marked as related to #267
PlasticDigits commented 2026-05-29 08:47:23 +00:00 (Migrated from gitlab.com)

mentioned in issue #261

mentioned in issue #261
PlasticDigits commented 2026-06-01 09:44:10 +00:00 (Migrated from gitlab.com)

mentioned in commit 7ee04422ac

mentioned in commit 7ee04422acc02db64387b23393d45639e5bcfe12
PlasticDigits commented 2026-06-01 09:44:19 +00:00 (Migrated from gitlab.com)

Verification complete (GitLab #262)

Verified on worktree issue/262-verify-donate-pools, merged to main as 7ee0442.

What was verified

Contracts (#261 dependency)

  • forge test --match-test test_topUpPodiumPools — 5/5 pass
  • ArenaPrizeRouting.t.sol top-up split tests — pass

Indexer

  • postgres_stage2_persist_all_events_and_rollback_after with YIELDOMEGA_PG_TEST_URL — pass (includes api_podium_pool_donations_smoke)
  • Live Anvil ingest: bash scripts/verify-donate-pools-anvil.sh — mint → approve → topUpPodiumPools(700e18) → GET /v1/arena/podium-pool-donations total 700000000000000000000

Frontend

  • ArenaProtocolDonatePoolsSection.test.tsx — disclosure + indexer empty/offline placeholders (3 tests)
  • npm run typecheck — pass
  • e2e/arena.spec.ts — AUDIT card data-testid="arena-protocol-donate-pools" visibility

Docs / invariants

  • INV-INDEXER-262-DONATE-POOLS, INV-FRONTEND-262-DONATE-POOLS, arena-views § donate-pools, indexer design, play-time-arena-doub skill — already crosslinked
  • Added manual QA checklist §262 in docs/testing/manual-qa-checklists.md

Changes in this verification pass

  • Expanded Vitest coverage for disclosure + empty/offline UX
  • Added scripts/verify-donate-pools-anvil.sh for repeatable Anvil + indexer E2E
  • Manual QA checklist row with doc crosslinks

Checklist for follow-up QA

  • bash scripts/verify-donate-pools-anvil.sh (needs Anvil, Postgres on :5433, PGPASSWORD=password)
  • export YIELDOMEGA_PG_TEST_URL=postgres://yieldomega:password@127.0.0.1:5433/yieldomega_indexer && cd indexer && cargo test postgres_stage2
  • cd frontend && npm test -- src/pages/arena/ArenaProtocolDonatePoolsSection.test.tsx
  • /arena/protocol: disclosure visible without wallet; wrong-network write gate; connected donate refreshes totals after indexer catch-up
  • Recent donation row opens wallet profile modal (#258)
## Verification complete (GitLab #262) Verified on worktree `issue/262-verify-donate-pools`, merged to `main` as **7ee0442**. ### What was verified **Contracts (#261 dependency)** - `forge test --match-test test_topUpPodiumPools` — 5/5 pass - `ArenaPrizeRouting.t.sol` top-up split tests — pass **Indexer** - `postgres_stage2_persist_all_events_and_rollback_after` with `YIELDOMEGA_PG_TEST_URL` — pass (includes `api_podium_pool_donations_smoke`) - Live Anvil ingest: `bash scripts/verify-donate-pools-anvil.sh` — mint → approve → `topUpPodiumPools(700e18)` → `GET /v1/arena/podium-pool-donations` total **`700000000000000000000`** **Frontend** - `ArenaProtocolDonatePoolsSection.test.tsx` — disclosure + indexer empty/offline placeholders (3 tests) - `npm run typecheck` — pass - `e2e/arena.spec.ts` — AUDIT card `data-testid="arena-protocol-donate-pools"` visibility **Docs / invariants** - `INV-INDEXER-262-DONATE-POOLS`, `INV-FRONTEND-262-DONATE-POOLS`, arena-views § donate-pools, indexer design, play-time-arena-doub skill — already crosslinked - Added manual QA checklist §262 in `docs/testing/manual-qa-checklists.md` ### Changes in this verification pass - Expanded Vitest coverage for disclosure + empty/offline UX - Added `scripts/verify-donate-pools-anvil.sh` for repeatable Anvil + indexer E2E - Manual QA checklist row with doc crosslinks --- ### Checklist for follow-up QA - [ ] `bash scripts/verify-donate-pools-anvil.sh` (needs Anvil, Postgres on `:5433`, `PGPASSWORD=password`) - [ ] `export YIELDOMEGA_PG_TEST_URL=postgres://yieldomega:password@127.0.0.1:5433/yieldomega_indexer && cd indexer && cargo test postgres_stage2` - [ ] `cd frontend && npm test -- src/pages/arena/ArenaProtocolDonatePoolsSection.test.tsx` - [ ] `/arena/protocol`: disclosure visible without wallet; wrong-network write gate; connected donate refreshes totals after indexer catch-up - [ ] Recent donation row opens wallet profile modal (#258)
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-01 09:44:24 +00:00
PlasticDigits commented 2026-06-01 13:13:22 +00:00 (Migrated from gitlab.com)

Follow-up from #267 QA: donate-pools path (PodiumPoolsToppedUp → idx_arena_podium_pool_top_up + GET /v1/arena/podium-pool-donations) remains unchanged. Per-buy vault funding events (PodiumFunded / SeedFunded / AdminVaultFunded) are tracked separately in #267 — now verified and closed with bash scripts/verify-vault-funding-anvil.sh (no regression to this donate API).

Follow-up from #267 QA: donate-pools path (`PodiumPoolsToppedUp` → `idx_arena_podium_pool_top_up` + `GET /v1/arena/podium-pool-donations`) remains unchanged. Per-buy vault funding events (`PodiumFunded` / `SeedFunded` / `AdminVaultFunded`) are tracked separately in #267 — now verified and closed with `bash scripts/verify-vault-funding-anvil.sh` (no regression to this donate API).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
code/yieldomega#262
No description provided.