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