Frontend: finish CL8Y unlimited-approval localStorage rename (drop TimeCurve doc token)
Summary
Finish the Arena v2 rename of the opt-in unlimited CL8Y → TimeArena localStorage preference. Canonical storage already uses yieldomega.erc20.cl8yArenaUnlimited.v1, but docs and UI copy still cite the retired cl8yTimeCurveUnlimited.v1 key — the last TimeCurve token in docs/ called out in #276 (closed) follow-up.
Parent: #276 (closed) · Policy: #274 (closed) · Original sizing: #143 (closed) · Shared approve gate: #224 (closed).
Current codebase
Storage + sizing (already partially migrated)
frontend/src/lib/arenaDoubApprovalPreference.ts:
| Constant | Value | Role |
|---|---|---|
CL8Y_ARENA_UNLIMITED_APPROVAL_STORAGE_KEY |
yieldomega.erc20.cl8yArenaUnlimited.v1 |
Canonical write target |
CL8Y_ARENA_UNLIMITED_APPROVAL_LEGACY_KEY |
yieldomega.erc20.cl8yTimeCurveUnlimited.v1 |
Read-only fallback; removed on next enable write |
readArenaDoubUnlimitedApproval()returns true if either key is"1".writeCl8yArenaUnlimitedApproval(true)sets canonical key and removes legacy key.arenaDoubApprovalAmountWei(need, unlimited)returnstype(uint256).maxwhen opted in, elseneed + 50 bpsheadroom (#143 (closed)).
Consumers
frontend/src/components/ArenaDoubUnlimitedApprovalFieldset.tsx— checkbox on/arenabuy panels (data-testid="cl8y-arena-approval-pref").frontend/src/lib/ensureCl8yKumbayaAllowance.ts— reads preference viareadArenaDoubUnlimitedApproval(); approves CL8Y →timeArenaAddressonly whenallowance < approveAmt(#224 (closed)).frontend/src/lib/ensureDoubTimeArenaAllowance.ts— DOUB path uses same sizing helper.
Tests today
frontend/src/lib/arenaDoubApprovalPreference.test.ts — headroom math + canonical key roundtrip; no legacy-key migration test.
Stale surfaces (this issue)
| Location | Problem |
|---|---|
docs/frontend/wallet-connection.md §143 |
Documents cl8yTimeCurveUnlimited.v1 as active key |
ArenaDoubUnlimitedApprovalFieldset.tsx |
Label: “Remember unlimited CL8Y allowance for TimeCurve”; file comment still says TimeCurve |
ensureCl8yKumbayaAllowance.ts |
Comment/type names EnsureCl8yTimeCurveAllowanceParams, “allowance for TimeCurve” (spender is already timeArenaAddress) |
Doc gate impact
After #276 (closed), bash scripts/check-doc-satellite-retired-count.sh allows ≤25 mentions / ≤15 files. wallet-connection.md is the only non-invariant doc file still containing TimeCurve (embedded in the legacy key string).
Why this is needed
- Doc accuracy for agents/QA — Operators and third-party agents read
wallet-connection.md; citing a retired key misleads debugging and violates the #276 (closed) intent. - UX consistency —
/arenaparticipants should see Time Arena / TimeArena, not retired v1 product naming (#256 (closed)). - Complete the in-code migration — Canonical key exists; finish docs, UI copy, tests, and optional identifier cleanup without breaking existing wallets that still have the legacy key set.
Constraints and guardrails
Read .cursor/skills/yieldomega-guardrails/SKILL.md before implementation.
- Do not change approval economics — same 50 bps headroom, same
maxUint256when opted in (#143 (closed)). - Preserve legacy read path until users re-toggle or clear storage — do not drop
CL8Y_ARENA_UNLIMITED_APPROVAL_LEGACY_KEYread without a migration test. - Spender remains
TimeArenaproxy — preference only affects CL8Y approve sizing to the configured arena address; no new unlimited paths for DOUB, WETH, USDM, or routers. - Toggling off does not revoke onchain allowance — document-only behavior; unchanged.
- No new env vars —
VITE_TIME_ARENA_ADDRESSonly for arena writes (#266 (closed)). - Docs-only gate — updating
wallet-connection.mdshould dropdocs/mention count by 1; re-runcheck-doc-satellite-retired-count.sh. - Minimal diff — rename/comments/UI copy + tests; avoid unrelated TimeCurve comment cleanup elsewhere in this issue.
Relevant files
| Path | Action |
|---|---|
frontend/src/lib/arenaDoubApprovalPreference.ts |
Export legacy key constant for tests or document inline; no behavior change unless adding explicit one-shot migration helper |
frontend/src/lib/arenaDoubApprovalPreference.test.ts |
Add legacy read + migrate-on-write tests |
frontend/src/components/ArenaDoubUnlimitedApprovalFieldset.tsx |
Arena-neutral label + comment |
frontend/src/lib/ensureCl8yKumbayaAllowance.ts |
Optional: rename EnsureCl8yTimeCurveAllowanceParams → Arena-neutral alias (keep export alias if widely imported) |
frontend/src/lib/ensureCl8yKumbayaAllowance.test.ts |
Update if type renamed |
docs/frontend/wallet-connection.md |
Document cl8yArenaUnlimited.v1; one-line note that legacy key is read once then removed |
docs/testing/invariants-and-business-logic.md |
Optional: add INV-FRONTEND-* row under #276 (closed) satellite section if doc gate counts change |
Recommended solution direction
- Docs — Replace
cl8yTimeCurveUnlimited.v1withcl8yArenaUnlimited.v1inwallet-connection.md; add ≤1 line: “Legacy…cl8yTimeCurveUnlimited.v1is honored on read and removed when the user re-enables the checkbox.” - UI — Change checkbox text to e.g. “Remember unlimited CL8Y allowance for Time Arena (optional)” (match
arena-views.mdtone). - Tests —
readArenaDoubUnlimitedApproval()true when only legacy key set; afterwriteCl8yArenaUnlimitedApproval(true), legacy absent and canonical present. - Optional cleanup — Rename
EnsureCl8yTimeCurveAllowanceParamswith a type alias for backward compatibility; fix file header comments to say TimeArena. - Verification —
cd frontend && npm test;bash scripts/check-doc-satellite-retired-count.sh; manual/arenacheckbox smoke.
Acceptance criteria
-
docs/frontend/wallet-connection.mddocumentsyieldomega.erc20.cl8yArenaUnlimited.v1as the active key (legacy key mentioned at most once as retired read-only). -
rg 'cl8yTimeCurveUnlimited' docs/— zero matches (or ≤1 in a single “Retired” sentence if team prefers explicit migration note withoutTimeCurvetoken — prefer zero). -
bash scripts/check-doc-satellite-retired-count.sh— exit 0;wallet-connection.mdno longer matchesTimeCurve|FeeRouter. -
/arenaunlimited-approval checkbox label and component comment use Time Arena / TimeArena, not TimeCurve. - Legacy
localStoragepreference still works for wallets that only have the old key (automated test). - Enabling preference migrates legacy → canonical key (automated test).
- Approval behavior unchanged:
arenaDoubApprovalPreference.test.ts+ensureCl8yKumbayaAllowance.test.tsgreen. -
cd frontend && npm test— all green.
Test plan — functional paths
| # | Path | Steps | Expected |
|---|---|---|---|
| 1 | Fresh user, unlimited off | Clear storage → load /arena → buy CL8Y path |
Exact (+ headroom) approve when needed; checkbox unchecked |
| 2 | Opt in unlimited | Check box → buy | approve(TimeArena, max) once; subsequent buys skip approve if allowance sufficient |
| 3 | Opt out | Uncheck box → buy | Sized approve again; onchain max allowance may remain (user revokes in wallet if desired) |
| 4 | Legacy key only | Seed cl8yTimeCurveUnlimited.v1=1 only → load page |
Checkbox shows checked; buy uses unlimited sizing |
| 5 | Legacy migration | Legacy only → toggle off/on or enable write | Canonical key set; legacy key removed |
| 6 | Private mode / quota | Simulate localStorage throw |
Preference false; buys still work with per-tx exact approve |
| 7 | Wrong network | #95 (closed) gate | No approve/submit on wrong chain regardless of preference |
Automated: extend arenaDoubApprovalPreference.test.ts; run full Vitest suite.
Test plan — attack, hack, and abuse vectors
| Vector | Risk | Mitigation / test |
|---|---|---|
| Unlimited CL8Y approve to wrong spender | High if UI pointed approve at non-arena address | Assert ensureCl8yKumbayaAllowance always uses timeArenaAddress from env/config; wrong-network gate #95 (closed) |
| XSS exfiltrating localStorage | Low (boolean pref only; no secrets) | No change; preference is non-sensitive — do not store keys or mnemonics |
| Cross-site localStorage injection | N/A (same-origin) | No change |
| User tricked into unlimited approve | Medium (UX/social) | Keep opt-in checkbox + disclosure text; label must name Time Arena spender context clearly |
| Stale unlimited allowance after arena redeploy | Medium (old proxy still approved) | Out of scope for key rename — document that users revoke in wallet when addresses change; optional follow-up |
| Legacy key forever dual-read | Low (doc/tech debt) | Migration-on-write removes legacy key; test ensures no duplicate writes |
| Malicious extension rewriting localStorage | Low | Same as XSS — cannot force onchain approve without wallet signature |
Verification criteria
# Doc gates (#274, #276)
bash scripts/check-doc-retired-terms.sh
bash scripts/check-doc-anchors.sh
bash scripts/check-doc-satellite-retired-count.sh
# Doc token spot-check
rg 'TimeCurve|FeeRouter' docs/frontend/wallet-connection.md # expect: no matches
rg 'cl8yTimeCurveUnlimited' docs/ # expect: no matches
# Frontend
cd frontend && npm test
# Manual (Anvil)
bash scripts/start-local-anvil-stack.sh
# open /arena, exercise checkbox + one CL8Y buy with pref on/offManual QA cross-links: wallet-connection §143 · manual QA §260 · invariants §276.
Related
- #276 (closed) — satellite doc trim (follow-up called out legacy key rename)
- #274 (closed) — P0 retired-term gate
- #143 (closed) / #224 (closed) — approval sizing and idempotent approve gate