Frontend: CRED as buy payment option (balance, burn check, buyWithCred)
Summary
Add Play CRED as a payment option in the arena buy charm UI alongside CL8Y/ETH/USDM: read wallet CRED balance, compute required burn from selected CHARM, gate submit when insufficient, and call TimeArena.buyWithCred(charmWad) on the correct chain.
Bundled with onchain work in #268 (closed) (100 CRED per CHARM burn + first-buy bonus). This issue is frontend-only but must not ship burn math that disagrees with deployed TimeArena.
Parent: Arena v2 #238 (closed), arena mount #256 (closed), buy router #251 (closed) / #264 (closed).
Current codebase
| Area | Behavior |
|---|---|
| Pay asset type | PayWithAsset = "cl8y" | "eth" | "usdm" only (frontend/src/lib/kumbayaRoutes.ts). |
| Buy UI | TimeCurveSimplePage / useTimeCurveSaleSession — token picker SIMPLE_AMOUNT_PAY_TOKEN_OPTIONS (CL8Y, ETH, USDM). |
| Arena v2 submit | When isArenaV2, only payWith === "cl8y" allowed; approves DOUB (acceptedAsset) and calls buy(). |
buyWithCred |
In timeArenaReadAbi but never called from frontend. |
| CRED UX today | ArenaCharmCredCard — epoch CHARM, pendingCred, claimCred only (read/claim, not pay). |
| PlayCred address | Not in frontend/src/lib/addresses.ts; available onchain as TimeArena.playCred(). |
| Wallet profile | Indexer field cred_claimed in WalletProfileModal; no live CRED balance. |
Relevant files
frontend/src/lib/kumbayaRoutes.ts— extend pay type or parallelpayModefrontend/src/pages/TimeCurveSimplePage.tsx— token picker options, labels, slider copyfrontend/src/pages/timecurve/useTimeCurveSaleSession.ts— submit path, gates, sizingfrontend/src/lib/abis.ts—buyWithCred, ERC20balanceOfon PlayCredfrontend/src/lib/addresses.ts— optionalplayCredfrom env or derived readfrontend/src/pages/arena/ArenaCharmCredCard.tsx— may share CRED balance hookfrontend/src/components/ChainMismatchWriteBarrier.tsx— wrap writesfrontend/e2e/arenaE2eHelpers.ts—selectPayWithhelperdocs/frontend/arena-views.md(if present) — UX specdocs/testing/manual-qa-checklists.md— Arena v2 QA section
Why this is needed
buyWithCredexists onchain but is unreachable for normal users without wallet/script calls.- Participants who earned CRED via DOUB epoch claims cannot spend it in-product.
- Burn is CHARM-scaled (100 CRED per 1e18 CHARM after onchain issue); UI must show exact required CRED before sign.
Constraints and guardrails
- No offchain pricing authority — Burn formula must mirror
TimeArena(readCRED_PER_CHARM_WADconstant orcredBurnForCharm(charmWad)view if added onchain). - Wrong-network gating — Use existing
ChainMismatchWriteBarrier/chainMismatchWriteMessage(#95 (closed)). - Single-chain wagmi (#81 (closed)).
- Arena v2 only — CRED pay applies when
TimeArena/isArenaV2; do not break legacy TimeCurve paths if still mounted (#266 (closed)). - CRED non-transferable — No approve step; only
balanceOf+buyWithCred(no ERC20 allowance UX). - Cooldown — Same
nextBuyAllowedAt/ buy cooldown UX as DOUB buys. - CHARM band — Reuse existing min/max CHARM validation before submit.
- Referral / WarBow flags — CRED path onchain does not take referral today; UI must not send referral args on CRED buys (or file follow-up if product wants parity).
- Accessibility — CRED option in same picker pattern as other tokens;
aria-labelincludes CRED.
Recommended direction
- Resolve PlayCred address —
useReadContracttimeArena.playCred()when env unset; cache in session context. - Extend pay model — Add
"cred"to pay union (orpayWith+payAssetsplit). Picker shows CRED with distinct label/logo. - Balance read —
balanceOf(user)on PlayCred; poll/refetch after buy and on claim. - Burn preview —
requiredCred = charmWad * 100n / WAD(or read onchain helper post-deploy). Show in checkout summary and insufficient-CRED gate (mirror CL8Y insufficient pattern inTimeCurveSimplePage). - Submit —
writeContractbuyWithCred(charmWad)with gas buffer helper used elsewhere; no DOUB approve. - Slider / amount input — When CRED selected, slider still targets CHARM band; suffix shows “CRED cost” not DOUB/CL8Y.
- E2E — Anvil dev deploy mints CRED to test wallet; one spec selects CRED and buys (or mocks read if fork unavailable).
- First-buy bonus — No extra UI required if onchain schedules; optional tooltip “First buy grants 150 CRED next epoch” once onchain lands.
Acceptance criteria
- Connected wallet on MegaETH (configured chain) sees CRED in buy payment picker on
/arena(or unified simple buy surface). - UI displays wallet CRED balance and required burn for selected CHARM.
- Buy button disabled with clear message when
balance < requiredCredor CHARM out of band. - Successful submit calls
buyWithCredwith correctcharmWad(fresh sizing read before tx). - Wrong chain: writes blocked; message matches existing pattern.
- After tx, balance and
ArenaCharmCredCarddata refresh. - ETH/USDM modes unchanged; CRED mode does not require DOUB allowance.
- Unit/integration tests for burn math helper (if extracted).
- E2E or component test covers CRED selection path (Anvil/dev).
Test plan — functional paths
| # | Scenario | Expected |
|---|---|---|
| 1 | Select CRED, sufficient balance | buyWithCred tx succeeds |
| 2 | Select CRED, insufficient balance | Disabled submit + error copy |
| 3 | Switch CRED → CL8Y | DOUB approve path restored |
| 4 | CHARM at min/max band | Burn preview matches onchain |
| 5 | Cooldown active | Same error as DOUB buy |
| 6 | Not connected | Connect prompt |
| 7 | Wrong chain | Write barrier |
| 8 | playCred == address(0) |
CRED option hidden or disabled with copy |
| 9 | Post-buy | CRED balance decreases by burn amount |
| 10 | Arena paused | Buy disabled consistent with DOUB |
Test plan — attack vectors / UX failure modes
| Vector | Mitigation / test |
|---|---|
| Stale balance | Re-read balanceOf immediately before submit; show revert message on failure |
| Stale CHARM bounds | Reuse readFreshTimeCurveBuySizing or arena equivalent |
| Wrong burn constant in UI | Prefer onchain view/constant read; single shared credBurnForCharmWad() util tested against Forge expectations |
User selects CRED but session sends buy() |
E2E asserts function name / calldata |
| Phishing wrong PlayCred | Address only from TimeArena.playCred() on configured arena proxy |
| Double-submit | Disable button while isPending |
| Scientific notation input | Parse errors don’t submit tx |
Verification criteria
-
npm test/vitestfor any new burn helper. -
npx playwright testarena spec with CRED path (5 workers per repo convention). - Manual QA checklist updated under “Arena v2 — CRED pay”.
- No new secrets in env; optional
VITE_PLAY_CRED_ADDRESSonly as override if read fails. - Lighthouse/a11y: picker keyboard navigable.
Depends on
- #268 (closed): 100 CRED per CHARM (and ideally public view for burn). Merge order: contracts first, then frontend (or feature-flag until deployed).
Out of scope
- Implementing first-buy 150 CRED bonus (onchain issue).
- Indexer API for CRED balance (wallet can read RPC).
- Kumbaya / ETH / USDM → CRED swaps.