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 (closed) is implemented onchain, and (3) fully migrating the advanced /timecurve/arena view to TimeArena semantics with no background TimeCurve compatibility shims.

Parent epic: #238 (closed). Depends on / closes the frontend gap left by #251 (closed).


Current codebase

Onchain (done in #251 (closed))

  • 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).
  • TimeArenaBuyRouter: buyViaKumbaya(charmWad, codeHash, plantWarBowFlag, payKind, swapDeadline, amountInMaximum, path)ETH / stable / CL8Y → Kumbaya exactOutput → DOUB → TimeArena.buyFor. Stable ingress uses balance-delta parity (#123 (closed)). Paths must end in DOUB (not CL8Y).

Frontend — /arena (Simple, partial v2)

Frontend — /timecurve/arena (Advanced, not migrated)

  • TimeCurvePageuseTimeCurveArenaModel (~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 + resolveTimeCurveBuyRouterForKumbayaSingleTx against timeCurveBuyRouter, not timeArenaBuyRouter.

Shared Kumbaya helpers (TimeCurve-era)

Operator / product docs (legacy gate)

  • TimeCurve buyFeeRoutingEnabled gated buy + WarBow CL8Y spend (#55 (closed)). Arena v2 uses TimeArena.paused only; WarBow spends DOUB (arena-v2.md).

Why this is needed

  1. buyFeeRoutingEnabled on Arena surfaces is misleading and deadTimeArena 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 (closed) 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).
  3. Two divergent Arena implementations (Simple bridge vs Advanced TimeCurve model) increase bug risk, block removing VITE_TIMECURVE_ADDRESS alias, and violate the #256 (closed) 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/testing/invariants-and-business-logic.md).
  • Do not reintroduce FeeRouter five-sink CL8Y routing, TimeCurve sale-end/redemption, or Leprechaun/Rabbit paths (#244 (closed), #243 (closed)).
  • Fail closed on Kumbaya: env router address must match onchain timeArenaBuyRouter when set; no silent fallback to broken swaps (kumbayaRoutes.ts pattern).
  • ERC-20 ingress: Preserve balance-delta parity expectations for DOUB pulls and stable router ingress (#123 (closed)).
  • Wallet / chain: Wrong-network write gating (#95 (closed)); session drift checks on multi-step buys (#144 (closed)).
  • MegaETH writes: Use waitForWriteReceipt / realtime RPC where applicable (#216 (closed)).
  • AGPL-3.0; follow 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/TimeArena.sol, contracts/script/DeployDev.s.sol, scripts/lib/anvil_deploy_dev.sh, scripts/verify-time-arena-buy-router-anvil.sh
Advanced Arena UI frontend/src/pages/timeCurveArena/useTimeCurveArenaModel.tsx, TimeCurveArenaView.tsx, WarbowHeroActions.tsx, WarbowClaimFlagButton.tsx, frontend/src/lib/warbowClaimFlagState.ts
Simple Arena UI useTimeCurveSaleSession.ts, arenaV2SaleSessionBridge.ts, TimeCurveSimplePage.tsx, FeeTransparency.tsx
Kumbaya / pay kumbayaRoutes.ts, timeCurveKumbayaSingleTx.ts, timeCurveKumbayaSwap.ts, kumbayaQuoter.ts, ensureDoubTimeArenaAllowance.ts
ABIs / env frontend/src/lib/abis.ts, frontend/.env.example, scripts/check-frontend-vite-env.sh, scripts/e2e-anvil.sh
Indexer (follow-up if events differ) indexer/src/decoder.rs, indexer/src/config.rs, registry JSON
Docs docs/frontend/arena-views.md, docs/integrations/kumbaya.md, docs/testing/e2e-anvil.md

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 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 (closed))

  • Add timeArenaReadAbi entries: timeArenaBuyRouter(), doub(), charmPriceWad(), paused().
  • Export TimeArenaBuyRouter ABI (or alias) with buyViaKumbaya + PAY_* constants matching 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 (closed) deferral error string.
  • Deploy/script: wire DeployKumbayaAnvilFixtures to deploy TimeArenaBuyRouter, setTimeArenaBuyRouter, registry + VITE_*; finish 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 (closed)
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 (closed))
11 Cooldown Back-to-back buy Second buy reverts / UI cooldown (#82 (closed))
12 claimWarbowFlag Eligible flag Still allowed when paused false; not tied to removed fee flag (final-signoff)

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; 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 (closed) 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 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).

Out of scope (separate issues)

  • Retiring TimeCurve launchpad routes entirely (/timecurve redirect already → /arena).
  • Full indexer decode of PodiumFunded / SeedFunded / AdminVaultFunded (#262 (closed)).
  • Mainnet deploy of TimeArena proxy (registry still zeroed for arena contracts on mainnet snapshot).