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 viabuy(charmWad); optionalbuyForonly fromtimeArenaBuyRouter; operator pause viapaused(notbuyFeeRoutingEnabled). DOUB prize split: 40% active / 30% seed / 30% admin (ArenaBuyRouting).TimeArenaBuyRouter:buyViaKumbaya(charmWad, codeHash, plantWarBowFlag, payKind, swapDeadline, amountInMaximum, path)— ETH / stable / CL8Y → KumbayaexactOutput→ DOUB →TimeArena.buyFor. Stable ingress uses balance-delta parity (#123 (closed)). Paths must end in DOUB (not CL8Y).
Frontend — /arena (Simple, partial v2)
TimeArenaPage→TimeCurveSimplePage+useTimeCurveSaleSession.- When
VITE_TIME_ARENA_ADDRESSmatches sessiontc,arenaV2SaleSessionBridgemapsTimeArenareads into TimeCurve-shaped multicall rows; direct DOUBbuy(charmWad)works. - Blocked:
isArenaV2 && payWith !== "cl8y"shows an error deferring ETH/USDM to #251 (closed) (useTimeCurveSaleSession.ts~1645). - Legacy gate still present: Simple UI still reads/displays
buyFeeRoutingEnabledfrom stubbed bridge rows and disables buys when false (TimeCurveSimplePage.tsx). - E2E:
anvil-arena-wallet-writes.spec.ts— DOUB buy passes; ETH test skipped unlessVITE_KUMBAYA_TIMECURVE_BUY_ROUTERis set (still TimeCurve-oriented env name).
Frontend — /timecurve/arena (Advanced, not migrated)
TimeCurvePage→useTimeCurveArenaModel(~3.7k LOC).- Still multicalls
timeCurveReadAbi(acceptedAsset,buyFeeRoutingEnabled,feeRouter,timeCurveBuyRouter, linear CHARM envelope,ended,redeemCharms, CL8Y WarBow burns, etc.) and submits viatimeCurveWriteAbi. - 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+resolveTimeCurveBuyRouterForKumbayaSingleTxagainsttimeCurveBuyRouter, nottimeArenaBuyRouter.
Shared Kumbaya helpers (TimeCurve-era)
timeCurveKumbayaSingleTx.ts,timeCurveKumbayaSwap.ts,kumbayaRoutes.ts— CL8YexactOutputsizing andTimeCurveBuyRouterABI.- Docs still describe TimeCurve router wiring (
docs/integrations/kumbaya.md).
Operator / product docs (legacy gate)
- TimeCurve
buyFeeRoutingEnabledgatedbuy+ WarBow CL8Y spend (#55 (closed)). Arena v2 usesTimeArena.pausedonly; WarBow spends DOUB (arena-v2.md).
Why this is needed
buyFeeRoutingEnabledon Arena surfaces is misleading and dead —TimeArenahas no such flag. Stubbedtruein 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.- #251 (closed) contract work is unusable in production UI until the frontend calls
TimeArenaBuyRouter.buyViaKumbayawith DOUB paths and readstimeArenaBuyRouter()— participants cannot pay with ETH/USDM on/arenadespite product spec (docs/frontend/arena-views.md). - Two divergent Arena implementations (Simple bridge vs Advanced TimeCurve model) increase bug risk, block removing
VITE_TIMECURVE_ADDRESSalias, and violate the #256 (closed) direction of a single Arena authority on/arenaand/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
timeArenaBuyRouterwhen set; no silent fallback to broken swaps (kumbayaRoutes.tspattern). - 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
isArenaV2TimeCurve-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 |
Recommended direction
1. Delete legacy buyFeeRoutingEnabled gate (Arena routes only)
- Remove multicall rows, session fields, UI copy, and write guards keyed on
buyFeeRoutingEnabledfrom 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.tsand WarBow components to dropbuyFeeRoutingEnabledprop. - Keep TimeCurve-only references only if a non-Arena route still exists elsewhere (should not apply to
/arenaor/timecurve/arenaafter this issue).
2. Implement ETH / USDM / router CL8Y pay (#251 (closed))
- Add
timeArenaReadAbientries:timeArenaBuyRouter(),doub(),charmPriceWad(),paused(). - Export
TimeArenaBuyRouterABI (or alias) withbuyViaKumbaya+PAY_*constants matchingTimeArenaBuyRouter.sol. - Generalize Kumbaya helpers:
resolveTimeArenaBuyRouterForKumbayaSingleTx(onchain, env)readingtimeArenaBuyRouter; env keyVITE_KUMBAYA_TIME_ARENA_BUY_ROUTER(migrate/rename fromVITE_KUMBAYA_TIMECURVE_BUY_ROUTERwith a single breaking rename in docs/scripts).- Build DOUB
exactOutputpaths (CL8Y→DOUB single hop; ETH/USDM multi-hop per router_validatePath). submitArenaKumbayaSingleTxBuy(or refactortimeCurveKumbayaSingleTxto asset-agnostic) calling router then relying onbuyFor.
- Direct DOUB pay: use
ensureDoubTimeArenaAllowance+TimeArena.buy/buy(referral)— stop reusingensureCl8yTimeCurveAllowancefor DOUB. - Pay toggles: enable
arena-paywith-eth/arena-paywith-usdmon Simple + Advanced when router configured; remove #251 (closed) deferral error string. - Deploy/script: wire
DeployKumbayaAnvilFixturesto deployTimeArenaBuyRouter,setTimeArenaBuyRouter, registry +VITE_*; finishscripts/verify-time-arena-buy-router-anvil.sh.
3. Fully migrate /timecurve/arena (no background compat)
- Refactor
useTimeCurveArenaModelto useTimeArena+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, CL8YacceptedAsset,buyFeeRoutingEnabled). - WarBow: DOUB approvals and burn constants from TimeArena; update labels/copy from “CL8Y” to “DOUB” where user-facing.
- Prefer one shared module (
useArenaSession/ extenduseTimeCurveSaleSession) consumed by Simple + Advanced to avoid a third bridge. - Delete or narrow
arenaV2SaleSessionBridgeTimeCurve-shaped stubs once Advanced reads TimeArena natively. addresses.ts: requireVITE_TIME_ARENA_ADDRESSfor Arena routes; stop aliasing throughVITE_TIMECURVE_ADDRESSfor game logic (footer/deploy docs may note migration).
Acceptance criteria
- No Arena route (
/arena,/timecurve/arena) reads, displays, or gates writes onbuyFeeRoutingEnabled. - Arena write pause is
TimeArena.pausedonly, with clear UI when paused. - DOUB direct buy on Simple and Advanced: correct allowance target (
ensureDoubTimeArenaAllowance),buysucceeds on Anvil. - ETH pay: single-tx
buyViaKumbayawithPAY_ETHwhentimeArenaBuyRouterset; E2E passes without skip. - USDM pay: same with
PAY_STABLEand configured stable; stable ingress parity errors surface readably. - CL8Y via router (optional product path):
PAY_CL8Yswap to DOUB thenbuyForwhen 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/arenafunctions against TimeArena-only deployment with notimeCurveReadAbimulticall for core session state. - Indexer ingest: decode
BuyViaKumbaya(or document follow-up issue if out of scope) — minimum: existingBuyevents still appear inGET /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/extendTimeArenaBuyRouterfork/local tests if missing.npm test— kumbaya route resolver, arena session mapping, warbow flag state.ANVIL_E2E=1— updateanvil-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.mdArena rows — DOUB buy, ETH/USDM pay, WarBow DOUB spend. - Grep repo: no
buyFeeRoutingEnabledunderfrontend/src/pages/timeCurveArenaor Arena-specific Simple paths. - Grep repo:
/timecurve/arenadoes not importtimeCurveReadAbifor core session (WarBow/time reads usetimeArenaReadAbi). - Production checklist: deploy
TimeArenaBuyRouter,setTimeArenaBuyRouter, update registry +VITE_KUMBAYA_TIME_ARENA_BUY_ROUTER, restart indexer. - Onchain spot-check after test buy:
PodiumVaults+AdminSellVaultbalances move 40/30/30 (ArenaPrizeRouting.t.sol).
Out of scope (separate issues)
- Retiring TimeCurve launchpad routes entirely (
/timecurveredirect 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).