Frontend: remove buyFeeRoutingEnabled gate, wire TimeArenaBuyRouter pay modes, migrate /timecurve/arena to Arena v2
## Summary Complete the Arena v2 frontend buy/WarBow surface by (1) removing the legacy **`buyFeeRoutingEnabled`** pause gate, (2) wiring **ETH / USDM** (and CL8Y-via-router) pay through **`TimeArenaBuyRouter`** now that [#251](https://gitlab.com/PlasticDigits/yieldomega/-/issues/251) is implemented onchain, and (3) **fully migrating** the advanced **`/timecurve/arena`** view to **`TimeArena`** semantics with **no** background TimeCurve compatibility shims. Parent epic: [#238](https://gitlab.com/PlasticDigits/yieldomega/-/issues/238). Depends on / closes the frontend gap left by [#251](https://gitlab.com/PlasticDigits/yieldomega/-/issues/251). --- ## Current codebase ### Onchain (done in #251) - **`TimeArena`**: DOUB-primary buys via `buy(charmWad)`; optional `buyFor` only from **`timeArenaBuyRouter`**; operator pause via **`paused`** (not `buyFeeRoutingEnabled`). DOUB prize split: 40% active / 30% seed / 30% admin ([`ArenaBuyRouting`](contracts/src/arena/libraries/ArenaBuyRouting.sol)). - **`TimeArenaBuyRouter`**: `buyViaKumbaya(charmWad, codeHash, plantWarBowFlag, payKind, swapDeadline, amountInMaximum, path)` — **ETH / stable / CL8Y** → Kumbaya **`exactOutput`** → DOUB → **`TimeArena.buyFor`**. Stable ingress uses balance-delta parity ([#123](https://gitlab.com/PlasticDigits/yieldomega/-/issues/123)). Paths must end in **DOUB** (not CL8Y). ### Frontend — `/arena` (Simple, partial v2) - [`TimeArenaPage`](frontend/src/pages/TimeArenaPage.tsx) → [`TimeCurveSimplePage`](frontend/src/pages/TimeCurveSimplePage.tsx) + [`useTimeCurveSaleSession`](frontend/src/pages/timecurve/useTimeCurveSaleSession.ts). - When `VITE_TIME_ARENA_ADDRESS` matches session `tc`, [`arenaV2SaleSessionBridge`](frontend/src/pages/timecurve/arenaV2SaleSessionBridge.ts) maps `TimeArena` reads into TimeCurve-shaped multicall rows; **direct DOUB** `buy(charmWad)` works. - **Blocked:** `isArenaV2 && payWith !== "cl8y"` shows an error deferring ETH/USDM to #251 ([`useTimeCurveSaleSession.ts`](frontend/src/pages/timecurve/useTimeCurveSaleSession.ts) ~1645). - **Legacy gate still present:** Simple UI still reads/displays `buyFeeRoutingEnabled` from stubbed bridge rows and disables buys when false ([`TimeCurveSimplePage.tsx`](frontend/src/pages/TimeCurveSimplePage.tsx)). - E2E: [`anvil-arena-wallet-writes.spec.ts`](frontend/e2e/anvil-arena-wallet-writes.spec.ts) — DOUB buy passes; ETH test **skipped** unless `VITE_KUMBAYA_TIMECURVE_BUY_ROUTER` is set (still TimeCurve-oriented env name). ### Frontend — `/timecurve/arena` (Advanced, **not** migrated) - [`TimeCurvePage`](frontend/src/pages/TimeCurvePage.tsx) → [`useTimeCurveArenaModel`](frontend/src/pages/timeCurveArena/useTimeCurveArenaModel.tsx) (~3.7k LOC). - Still multicalls **`timeCurveReadAbi`** (`acceptedAsset`, `buyFeeRoutingEnabled`, `feeRouter`, `timeCurveBuyRouter`, linear CHARM envelope, `ended`, `redeemCharms`, CL8Y WarBow burns, etc.) and submits via **`timeCurveWriteAbi`**. - **Incompatible** with a TimeArena-only deployment: wrong ABI surface, wrong spend asset semantics (CL8Y vs DOUB), wrong pause model. - Kumbaya single-tx path uses [`timeCurveKumbayaSingleTx.ts`](frontend/src/lib/timeCurveKumbayaSingleTx.ts) + [`resolveTimeCurveBuyRouterForKumbayaSingleTx`](frontend/src/lib/kumbayaRoutes.ts) against **`timeCurveBuyRouter`**, not **`timeArenaBuyRouter`**. ### Shared Kumbaya helpers (TimeCurve-era) - [`timeCurveKumbayaSingleTx.ts`](frontend/src/lib/timeCurveKumbayaSingleTx.ts), [`timeCurveKumbayaSwap.ts`](frontend/src/lib/timeCurveKumbayaSwap.ts), [`kumbayaRoutes.ts`](frontend/src/lib/kumbayaRoutes.ts) — CL8Y `exactOutput` sizing and `TimeCurveBuyRouter` ABI. - Docs still describe TimeCurve router wiring ([`docs/integrations/kumbaya.md`](docs/integrations/kumbaya.md)). ### Operator / product docs (legacy gate) - TimeCurve **`buyFeeRoutingEnabled`** gated `buy` + WarBow **CL8Y** spend ([#55](https://gitlab.com/PlasticDigits/yieldomega/-/issues/55)). Arena v2 uses **`TimeArena.paused`** only; WarBow spends **DOUB** ([`arena-v2.md`](docs/product/arena-v2.md)). --- ## Why this is needed 1. **`buyFeeRoutingEnabled` on Arena surfaces is misleading and dead** — `TimeArena` has no such flag. Stubbed `true` in the v2 bridge hides the problem; Advanced Arena still reads a non-existent selector and blocks writes when the read fails or legacy env points at TimeCurve. 2. **#251 contract work is unusable in production UI** until the frontend calls **`TimeArenaBuyRouter.buyViaKumbaya`** with **DOUB** paths and reads **`timeArenaBuyRouter()`** — participants cannot pay with ETH/USDM on `/arena` despite product spec ([`docs/frontend/arena-views.md`](docs/frontend/arena-views.md)). 3. **Two divergent Arena implementations** (Simple bridge vs Advanced TimeCurve model) increase bug risk, block removing `VITE_TIMECURVE_ADDRESS` alias, and violate the #256 direction of a single Arena authority on **`/arena`** and **`/timecurve/arena`**. --- ## Constraints and guardrails - **Onchain authority:** No change to 40/30/30 DOUB routing, CHARM band (0.99–10), CRED accrual, or WarBow DOUB costs unless a separate contract issue says so ([`docs/product/arena-v2.md`](docs/product/arena-v2.md), [`docs/testing/invariants-and-business-logic.md`](docs/testing/invariants-and-business-logic.md)). - **Do not reintroduce** FeeRouter five-sink CL8Y routing, TimeCurve sale-end/redemption, or Leprechaun/Rabbit paths ([#244](https://gitlab.com/PlasticDigits/yieldomega/-/issues/244), [#243](https://gitlab.com/PlasticDigits/yieldomega/-/issues/243)). - **Fail closed** on Kumbaya: env router address must match onchain `timeArenaBuyRouter` when set; no silent fallback to broken swaps ([`kumbayaRoutes.ts`](frontend/src/lib/kumbayaRoutes.ts) pattern). - **ERC-20 ingress:** Preserve balance-delta parity expectations for DOUB pulls and stable router ingress ([#123](https://gitlab.com/PlasticDigits/yieldomega/-/issues/123)). - **Wallet / chain:** Wrong-network write gating ([#95](https://gitlab.com/PlasticDigits/yieldomega/-/issues/95)); session drift checks on multi-step buys ([#144](https://gitlab.com/PlasticDigits/yieldomega/-/issues/144)). - **MegaETH writes:** Use `waitForWriteReceipt` / realtime RPC where applicable ([#216](https://gitlab.com/PlasticDigits/yieldomega/-/issues/216)). - **AGPL-3.0**; follow [`docs/testing/strategy.md`](docs/testing/strategy.md). - **No background compatibility:** Remove `isArenaV2` TimeCurve-shaped bridge for Advanced view; do not keep dual code paths “just in case” TimeCurve is wired — target **TimeArena-only** for Arena routes. --- ## Relevant files | Area | Files | |------|--------| | **Contracts (reference / deploy)** | [`contracts/src/arena/TimeArenaBuyRouter.sol`](contracts/src/arena/TimeArenaBuyRouter.sol), [`contracts/src/arena/TimeArena.sol`](contracts/src/arena/TimeArena.sol), [`contracts/script/DeployDev.s.sol`](contracts/script/DeployDev.s.sol), [`scripts/lib/anvil_deploy_dev.sh`](scripts/lib/anvil_deploy_dev.sh), [`scripts/verify-time-arena-buy-router-anvil.sh`](scripts/verify-time-arena-buy-router-anvil.sh) | | **Advanced Arena UI** | [`frontend/src/pages/timeCurveArena/useTimeCurveArenaModel.tsx`](frontend/src/pages/timeCurveArena/useTimeCurveArenaModel.tsx), [`TimeCurveArenaView.tsx`](frontend/src/pages/timeCurveArena/TimeCurveArenaView.tsx), [`WarbowHeroActions.tsx`](frontend/src/pages/timeCurveArena/WarbowHeroActions.tsx), [`WarbowClaimFlagButton.tsx`](frontend/src/pages/timeCurveArena/WarbowClaimFlagButton.tsx), [`frontend/src/lib/warbowClaimFlagState.ts`](frontend/src/lib/warbowClaimFlagState.ts) | | **Simple Arena UI** | [`useTimeCurveSaleSession.ts`](frontend/src/pages/timecurve/useTimeCurveSaleSession.ts), [`arenaV2SaleSessionBridge.ts`](frontend/src/pages/timecurve/arenaV2SaleSessionBridge.ts), [`TimeCurveSimplePage.tsx`](frontend/src/pages/TimeCurveSimplePage.tsx), [`FeeTransparency.tsx`](frontend/src/components/FeeTransparency.tsx) | | **Kumbaya / pay** | [`kumbayaRoutes.ts`](frontend/src/lib/kumbayaRoutes.ts), [`timeCurveKumbayaSingleTx.ts`](frontend/src/lib/timeCurveKumbayaSingleTx.ts), [`timeCurveKumbayaSwap.ts`](frontend/src/lib/timeCurveKumbayaSwap.ts), [`kumbayaQuoter.ts`](frontend/src/lib/kumbayaQuoter.ts), [`ensureDoubTimeArenaAllowance.ts`](frontend/src/lib/ensureDoubTimeArenaAllowance.ts) | | **ABIs / env** | [`frontend/src/lib/abis.ts`](frontend/src/lib/abis.ts), [`frontend/.env.example`](frontend/.env.example), [`scripts/check-frontend-vite-env.sh`](scripts/check-frontend-vite-env.sh), [`scripts/e2e-anvil.sh`](scripts/e2e-anvil.sh) | | **Indexer (follow-up if events differ)** | [`indexer/src/decoder.rs`](indexer/src/decoder.rs), [`indexer/src/config.rs`](indexer/src/config.rs), registry JSON | | **Docs** | [`docs/frontend/arena-views.md`](docs/frontend/arena-views.md), [`docs/integrations/kumbaya.md`](docs/integrations/kumbaya.md), [`docs/testing/e2e-anvil.md`](docs/testing/e2e-anvil.md) | --- ## Recommended direction ### 1. Delete legacy `buyFeeRoutingEnabled` gate (Arena routes only) - Remove multicall rows, session fields, UI copy, and write guards keyed on `buyFeeRoutingEnabled` from Arena code paths. - Replace operator pause UX with **`TimeArena.paused`** (read + clear messaging). WarBow write gating: **`paused`** + existing BP/silence rules — **not** fee-routing flag. - Update [`warbowClaimFlagState.ts`](frontend/src/lib/warbowClaimFlagState.ts) and WarBow components to drop `buyFeeRoutingEnabled` prop. - Keep TimeCurve-only references only if a **non-Arena** route still exists elsewhere (should not apply to `/arena` or `/timecurve/arena` after this issue). ### 2. Implement ETH / USDM / router CL8Y pay (#251) - Add **`timeArenaReadAbi`** entries: `timeArenaBuyRouter()`, `doub()`, `charmPriceWad()`, `paused()`. - Export **`TimeArenaBuyRouter`** ABI (or alias) with `buyViaKumbaya` + `PAY_*` constants matching [`TimeArenaBuyRouter.sol`](contracts/src/arena/TimeArenaBuyRouter.sol). - Generalize Kumbaya helpers: - `resolveTimeArenaBuyRouterForKumbayaSingleTx(onchain, env)` reading **`timeArenaBuyRouter`**; env key **`VITE_KUMBAYA_TIME_ARENA_BUY_ROUTER`** (migrate/rename from `VITE_KUMBAYA_TIMECURVE_BUY_ROUTER` with a single breaking rename in docs/scripts). - Build **DOUB `exactOutput`** paths (CL8Y→DOUB single hop; ETH/USDM multi-hop per router `_validatePath`). - `submitArenaKumbayaSingleTxBuy` (or refactor `timeCurveKumbayaSingleTx` to asset-agnostic) calling router then relying on `buyFor`. - **Direct DOUB pay:** use `ensureDoubTimeArenaAllowance` + `TimeArena.buy` / `buy(referral)` — stop reusing `ensureCl8yTimeCurveAllowance` for DOUB. - **Pay toggles:** enable `arena-paywith-eth` / `arena-paywith-usdm` on Simple + Advanced when router configured; remove #251 deferral error string. - Deploy/script: wire `DeployKumbayaAnvilFixtures` to deploy **`TimeArenaBuyRouter`**, `setTimeArenaBuyRouter`, registry + `VITE_*`; finish [`scripts/verify-time-arena-buy-router-anvil.sh`](scripts/verify-time-arena-buy-router-anvil.sh). ### 3. Fully migrate `/timecurve/arena` (no background compat) - Refactor **`useTimeCurveArenaModel`** to use **`TimeArena` + `timeArenaReadAbi` / write ABI** (or shared hook extracted from sale session) — same source of truth as `/arena`. - Remove dependence on TimeCurve sale lifecycle (`ended`, `redeemCharms`, `feeRouter`, envelope curve, CL8Y `acceptedAsset`, `buyFeeRoutingEnabled`). - WarBow: DOUB approvals and burn constants from TimeArena; update labels/copy from “CL8Y” to “DOUB” where user-facing. - Prefer **one shared module** (`useArenaSession` / extend `useTimeCurveSaleSession`) consumed by Simple + Advanced to avoid a third bridge. - Delete or narrow **`arenaV2SaleSessionBridge`** TimeCurve-shaped stubs once Advanced reads TimeArena natively. - **`addresses.ts`:** require `VITE_TIME_ARENA_ADDRESS` for Arena routes; stop aliasing through `VITE_TIMECURVE_ADDRESS` for game logic (footer/deploy docs may note migration). --- ## Acceptance criteria - [ ] No Arena route (`/arena`, `/timecurve/arena`) reads, displays, or gates writes on **`buyFeeRoutingEnabled`**. - [ ] Arena write pause is **`TimeArena.paused`** only, with clear UI when paused. - [ ] **DOUB direct buy** on Simple and Advanced: correct allowance target (`ensureDoubTimeArenaAllowance`), `buy` succeeds on Anvil. - [ ] **ETH pay:** single-tx `buyViaKumbaya` with `PAY_ETH` when `timeArenaBuyRouter` set; E2E passes without skip. - [ ] **USDM pay:** same with `PAY_STABLE` and configured stable; stable ingress parity errors surface readably. - [ ] **CL8Y via router (optional product path):** `PAY_CL8Y` swap to DOUB then `buyFor` when user selects CL8Y but router is preferred over wallet DOUB balance (align with product — if only DOUB wallet path is desired, document and test one CL8Y path only). - [ ] Env router mismatch **fail closed** with actionable error (no silent two-step TimeCurve fallback on Arena). - [ ] **`/timecurve/arena`** functions against **TimeArena-only** deployment with **no** `timeCurveReadAbi` multicall for core session state. - [ ] Indexer ingest: decode **`BuyViaKumbaya`** (or document follow-up issue if out of scope) — *minimum:* existing `Buy` events still appear in `GET /v1/arena/buys`. - [ ] Docs updated: arena-views, kumbaya integration, e2e-anvil, `.env.example`. --- ## Test plan — functional paths | # | Path | Steps | Expected | |---|------|--------|----------| | 1 | DOUB direct buy (Simple) | Anvil + mock wallet, `/arena`, min CHARM, Buy | Receipt success; vault 70/30 split unchanged (Forge or explorer spot-check) | | 2 | DOUB direct buy (Advanced) | `/timecurve/arena`, same | Same as #1 | | 3 | ETH single-tx | Router deployed, `arena-paywith-eth`, buy | One tx via router; `Buy` event; no UI error | | 4 | USDM single-tx | Stable configured + liquidity on fork/Anvil | `buyViaKumbaya` succeeds; refund/dust to `doubSurplusRecipient` if any | | 5 | Router unset | Zero `timeArenaBuyRouter` | ETH/USDM toggles disabled or clear “router not configured”; DOUB direct still works | | 6 | Env mismatch | `VITE_*_BUY_ROUTER` ≠ onchain | Submit blocked with mismatch message | | 7 | Referral + buy | Pending code, `buy(charm, codeHash)` or router equivalent | Referral events / CRED applied per spec | | 8 | WarBow steal/guard | DOUB balance + approve | Succeeds when not `paused`; no `buyFeeRoutingEnabled` check | | 9 | Paused arena | `setPaused(true)` | Buys and WarBow writes blocked with pause copy | | 10 | Wrong chain | Wallet on wrong chainId | Write barrier; no submit ([#95](https://gitlab.com/PlasticDigits/yieldomega/-/issues/95)) | | 11 | Cooldown | Back-to-back buy | Second buy reverts / UI cooldown ([#82](https://gitlab.com/PlasticDigits/yieldomega/-/issues/82)) | | 12 | `claimWarbowFlag` | Eligible flag | Still allowed when `paused` false; **not** tied to removed fee flag ([final-signoff](docs/operations/final-signoff-and-value-movement.md)) | **Automated:** - `forge test` — add/extend `TimeArenaBuyRouter` fork/local tests if missing. - `npm test` — kumbaya route resolver, arena session mapping, warbow flag state. - `ANVIL_E2E=1` — update [`anvil-arena-wallet-writes.spec.ts`](frontend/e2e/anvil-arena-wallet-writes.spec.ts); add Advanced arena spec if absent. - `cargo test` (indexer) if decoder extended. --- ## Test plan — attack / abuse vectors | Vector | Test | Expected mitigation | |--------|------|---------------------| | **Router spoofing** | Point env at attacker contract; user signs | Env/onchain mismatch fails closed; only `timeArenaBuyRouter` can `buyFor` | | **Malicious path** | Craft path ending in wrong token / pool | `TimeArenaBuyRouter__BadPath` / insufficient DOUB gained → revert | | **Stable fee-on-transfer** | Mock deflationary stable on Anvil | `StableIngressParity` revert; UI shows ingress error | | **Underpay swap** | Stale quote / low `amountInMaximum` | Swap or `gained < grossDoub` reverts; user funds not partially drained into arena | | **Reentrancy** | Regression via existing router tests | `nonReentrant` on router + arena holds | | **Expired deadline** | `swapDeadline` in the past | `SwapExpired` revert | | **CHARM out of band** | Below 0.99 or above 10 CHARM | Bounds revert before pull | | **Paused bypass** | Call `buyFor` directly without router | `not router` revert | | **Allowance phishing** | UI must approve **Doubloon → TimeArena** or router only | No unlimited approve to unknown spender in UI flows | | **Session drift** | Switch wallet mid two-step (if any legacy path removed) | [#144](https://gitlab.com/PlasticDigits/yieldomega/-/issues/144) guard throws before second tx | | **Indexer replay** | Re-org same `Buy` log | `ON CONFLICT DO NOTHING` on `(tx_hash, log_index)` | --- ## Verification criteria - [ ] CI green: `forge test`, frontend unit tests, Anvil E2E job (if in pipeline). - [ ] Manual: [`docs/testing/manual-qa-checklists.md`](docs/testing/manual-qa-checklists.md) Arena rows — DOUB buy, ETH/USDM pay, WarBow DOUB spend. - [ ] Grep repo: no `buyFeeRoutingEnabled` under `frontend/src/pages/timeCurveArena` or Arena-specific Simple paths. - [ ] Grep repo: `/timecurve/arena` does not import `timeCurveReadAbi` for core session (WarBow/time reads use `timeArenaReadAbi`). - [ ] Production checklist: deploy `TimeArenaBuyRouter`, `setTimeArenaBuyRouter`, update registry + `VITE_KUMBAYA_TIME_ARENA_BUY_ROUTER`, restart indexer. - [ ] Onchain spot-check after test buy: `PodiumVaults` + `AdminSellVault` balances move 40/30/30 ([`ArenaPrizeRouting.t.sol`](contracts/test/ArenaPrizeRouting.t.sol)). --- ## Out of scope (separate issues) - Retiring **TimeCurve launchpad** routes entirely (`/timecurve` redirect already → `/arena`). - Full indexer decode of `PodiumFunded` / `SeedFunded` / `AdminVaultFunded` ([#262](https://gitlab.com/PlasticDigits/yieldomega/-/issues/262)). - Mainnet deploy of TimeArena proxy (registry still zeroed for arena contracts on mainnet snapshot).
issue