-
Read docs/agent-phases.md, then read the doc for the matching phase before implementing.
-
Respect LICENSE and docs/licensing.md: default new project code to AGPL-3.0, preserve license headers, and do not change licensing unless the maintainer asks.
-
Keep game rules, fund flows, and authoritative state onchain. indexer/ and frontend/ are derived read-model and UX layers, not sources of truth.
-
Follow docs/testing/strategy.md before claiming completion: run the relevant tests when possible, or clearly state what could not be run. For contract or business-logic changes, cross-check the spec ↔ test map in docs/testing/invariants-and-business-logic.md when touching covered areas. Indexer emits: deployed protocol events must decode + persist in Postgres with rollback_after rows — INV-INDEXER-112, GitLab #112. Indexer transactional block ingest: each block's event inserts + indexed_blocks + chain_pointer commit in one Postgres transaction — INV-INDEXER-140, GitLab #140. Indexer public HTTP 500 bodies: on unexpected sqlx failures, JSON responses must not echo raw DB/driver strings — INV-INDEXER-157, GitLab #157, indexer/src/api.rs. Indexer ingestion resilience (GitLab #168): supervised ingestion::run retries, INDEXER_RPC_REQUEST_TIMEOUT_SEC on JSON-RPC HTTP clients, GET /v1/status ingestion_alive / last_indexed_at_ms — INV-INDEXER-168, indexer/README.md. FeeRouter DistributableTokenUpdated / ERC20Rescued are part of that matrix (GitLab #122, audit L-04). TimeCurve PodiumResidualRecipientSet and TimeCurveBuyRouter EthRescued / Erc20Rescued (distinct from FeeRouter rescue) have dedicated idx_* tables under INV-INDEXER-139 (GitLab #139). Indexer production env (GitLab #142): with INDEXER_PRODUCTION=1, ensure_production_database_url rejects placeholder substrings in DATABASE_URL — INV-INDEXER-142, indexer/README.md. Indexer production registry (GitLab #156): with INDEXER_PRODUCTION=1, ensure_production_address_registry rejects CHAIN_ID / ADDRESS_REGISTRY mismatches, invalid addresses, empty ingestion filters, and deploy_block == 0 on non-Anvil chains — INV-INDEXER-156, indexer/README.md. Placeholder .split-layout scene cards (GitLab #163): INV-FRONTEND-163, index.css.
-
Local Anvil stack + bot swarm: If you touch scripts/start-local-anvil-stack.sh or bots/timecurve/, keep Python install docs aligned: venv is preferred; PEP 668 fallback lives in bots/timecurve/README.md and is summarized in the Local stack bot swarm row of docs/testing/invariants-and-business-logic.md (issue #50). YIELDOMEGA_SWARM_REFERRALS=0 (skip shared referral bootstrap) and standalone run_swarm() prerequisites: issue #102, e2e-anvil.md. For UUPS DeployDev addresses, never point bots or shell scripts at the implementation row in run-latest.json (contractName still says TimeCurve / RabbitTreasury); use the ERC1967 proxy — scripts/lib/broadcast_proxy_addresses.sh, contracts/script/anvil_rich_state.sh, DeployDev broadcast JSON row in invariants (issue #61).
-
For TimeCurve prizes, treat docs/product/primitives.md as the source of truth for the four fixed v1 podium categories (last buy, WarBow / top Battle Points, defended streak, time booster). Do not reintroduce or assume legacy category sets (e.g. most-buys, biggest-buy, cumulative-CHARM podiums, opening/closing-window tracks, or a removed “activity leader” prize).
-
For Kumbaya routing, VITE_KUMBAYA_* env, or multi-asset TimeCurve entry, read docs/integrations/kumbaya.md (GitLab #46): Kumbaya-xyz/integrator-kit address parity, fail closed behavior, and localnet vs MegaETH runbooks.
-
TimeCurve (frontend) sale phase: do not drive derivePhase / pre-start UX from latestBlock alone when the app already has useTimecurveHeroTimer. Follow ledgerSecIntForPhase in timeCurveSimplePhase.ts and the invariants in docs/frontend/timecurve-views.md (cross-linked from docs/testing/invariants-and-business-logic.md, issue #48). If you add a new “is the sale live?” branch, it must use the same preferred clock or document why not.
-
TimeCurve redemption economics: Preserve the no-referral invariant that CL8Y value of DOUB per CHARM does not decrease through the sale (DOUB per CHARM falls; implied CL8Y per DOUB rises) — docs/product/primitives.md, docs/testing/invariants-and-business-logic.md. Also preserve the launch-anchor 1.275× rule (GitLab #158) (DoubLPIncentives seeds DOUB/CL8Y locked LP at 1.275× the per-CHARM clearing price → a participant's CHARM is worth charmWeight × pricePerCharm × 1275 / (1000 × 1e18) CL8Y at launch); shared helpers live in frontend/src/lib/timeCurvePodiumMath.ts (LAUNCH_LIQUIDITY_ANCHOR_NUM / LAUNCH_LIQUIDITY_ANCHOR_DEN, launchLiquidityAnchorWad, participantLaunchValueCl8yWei) with tests in timeCurvePodiumMath.test.ts (launch-anchor invariant: launch price = final per-CHARM price × 1.275). During the sale, participant UX should prefer CL8Y-at-launch over wallet-level DOUB count (DOUB-per-CHARM dilutes; CL8Y-at-launch is non-decreasing). After redeemCharms (charmsRedeemed), TimeCurveStakeAtLaunchSection.tsx surfaces redeemed DOUB, strikes through the CL8Y projection, and shows Settled chrome — without replacing the CL8Y line with DOUB-only “worth at launch” (misleading across CL8Y / ETH / USDM entry rails; issue #90, docs/frontend/timecurve-views.md).
-
Final signoff / value-movement gates (issue #55): TimeCurve — buyFeeRoutingEnabled gates buy and WarBow CL8Y burns (steal / revenge / guard), charmRedemptionEnabled / reservePodiumPayoutsEnabled for post-end flows; reservePodiumPayoutsEnabled is also required when PodiumPool balance is zero at distributePrizes — empty settlement emits PrizesSettledEmptyPodiumPool and sets prizesDistributed (no refill/retry, GitLab #133, INV-INDEXER-112); DoubPresaleVesting (claimsEnabled) — spec in docs/operations/final-signoff-and-value-movement.md; test map in docs/testing/invariants-and-business-logic.md. Keep simple-page UX in sync with onchain reads (useTimeCurveSaleSession.ts).
-
TimeCurve Simple — Kumbaya quote UX (issue #56): ETH/USDM pay modes must keep swapQuoteLoading aligned with isPending || isFetching on the quoter read and surface Refreshing quote… on the primary CTA while in flight — docs/frontend/timecurve-views.md, docs/testing/invariants-and-business-logic.md.
-
EVM wallet modal: RainbowKit + wagmi config lives in wagmi-config.ts. VITE_WALLETCONNECT_PROJECT_ID enables WalletConnect; SafePal and EIP-6963 expectations are documented in docs/frontend/wallet-connection.md (issue #58).
-
TimeCurve single-tx Kumbaya buy (issue #65, issue #66, issue #118): TimeCurveBuyRouter + TimeCurve.buyFor, buyViaKumbaya in Simple / Arena when timeCurveBuyRouter ≠ 0 — plantWarBowFlag matches buy / buyFor (issue #63); owner sets setTimeCurveBuyRouter; gross CL8Y for swap is derived from the same price views as buy; buyViaKumbaya must enforce block.timestamp >= saleStart() once scheduled (INV-BUYROUTER-118). Audit M‑02 / ETH·stable refunds + rescue: buyViaKumbaya repays marginal unused WETH/stable only (pre-funding balanceOf snapshot); stranded inputs stay until Ownable2Step rescueETH/rescueERC20 (issue #117; INV-BUYROUTER-117* invariants). Path invariants and env runbooks live in docs/integrations/kumbaya.md and the test map in docs/testing/invariants-and-business-logic.md. Optional VITE_KUMBAYA_TIMECURVE_BUY_ROUTER must match onchain if set. AnvilKumbayaRouter multi-hop quoteExactOutput must stay aligned with exactOutput (local fixture; see docs/testing/local-swap-testing.md); helper: timeCurveKumbayaSingleTx.ts.
-
Indexer buyFor / BuyViaKumbaya (issue #67): Keep TimeCurve Buy as the canonical buy row (event buyer = participant). Ingest BuyViaKumbaya only when TimeCurveBuyRouter is in ADDRESS_REGISTRY; correlate by tx_hash + buyer + charm_wad; API schema in indexer/src/api.rs (SCHEMA_VERSION, optional entry_pay_asset / router_attested_gross_cl8y; GET /v1/timecurve/prize-distributions includes kind / podium_pool from 1.14.0). Cross-links: docs/integrations/kumbaya.md, docs/indexer/design.md, skills/README.md.
-
/referrals visual + register UX (issue #64); referral registerCode wallet session drift (issue #155); share-link copy confirmation (issue #86); indexer leaderboard + wallet earnings (issue #94); referral registration ordering / mempool fairness (issue #121): Keep shell, registry resolution (VITE_REFERRAL_REGISTRY_ADDRESS or TimeCurve.referralRegistry()), wallet-gated register, and post-register share links aligned with docs/product/referrals.md (INV-REFERRAL-121-UX: ReferralRegisterSection referrals-register-ordering-disclosure; INV-REFERRAL-SESSION-155: captureWalletBuySession / assertWalletBuySessionUnchanged on onRegister — walletBuySessionGuard.ts, invariants §155) and the E2E map in docs/testing/invariants-and-business-logic.md; GET /v1/referrals/referrer-leaderboard / GET /v1/referrals/wallet-charm-summary must aggregate ReferralApplied only (no synthetic metrics) — § #94. idx_timecurve_referral_applied SQL (GitLab #165): bare column = $n on stored-lowercase addresses (no lower(column) in WHERE / JOIN on those columns) — INV-INDEXER-165. Copy must surface success (referralShareCopyFeedback.ts banner + Copied! button) and failure StatusMessages, not silent no-ops. Browser storage (issue #85): pending capture uses yieldomega.ref.v1 (local + session); registered “my code” cache uses yieldomega.myrefcode.v1.<walletLowercase> (local only). Manual QA checklists (contributors): ../../../docs/testing/manual-qa-checklists.md#manual-qa-issue-64 · #121 disclosure · #155; invariants §121; audit L‑02. Third-party agent index: skills/README.md.
-
WarBow flag plant opt-in (issue #63): Default buy(charmWad) does not set warbowPendingFlag*; plantWarBowFlag on buy / buyFor / buyViaKumbaya controls planting and Buy.flagPlanted. Explicit Forge coverage for referral buy(..., codeHash, plant) and router buyFor wiring: issue #77. Map: docs/testing/invariants-and-business-logic.md; play skill: skills/play-timecurve-warbow/SKILL.md.
-
Frontend audio (Blockie Hills BGM + SFX, issue #68; inventory restore #108; BGM resume issue #71; mobile dock vs nav issue #103): BGM + SFX use Web Audio buses (frontend/src/audio/). BGM attempts autoplay on load; if the browser blocks it, the first user gesture (anywhere on the page or the dock Play control) unlocks and starts BGM at the persisted track + offset (localStorage playbackState, 7d TTL on offset, ~4s write throttle while playing). coin_hit_shallow fires on TimeCurve.buy / buyViaKumbaya submit (tx hash, before receipt · playGameSfxCoinHitBuySubmit); warbow_twang is throttled (~18 s) via warbowRankSfxPolicy (indexed top‑3 entry or moves among ranks ≤3, useArenaWarbowRankSfx, INV-AUDIO-68-WIRE); kumbaya_whoosh is deferred (no prefetch until pay‑mode/route wiring lands per #68 discussion); keep Kumbaya / quote SFX off hot refetch paths elsewhere (issue #56); timer cues respect prefers-reduced-motion. INV-AUDIO-103: On max-width: 720px, .app-header margin-top clears the fixed AlbumPlayerBar — mobileAlbumDockLayout.ts ↔ index.css (Vitest reads CSS for parity — GitLab #107); play skill for contributors: skills/contributor-mobile-album-dock/SKILL.md. Docs: docs/frontend/sound-effects-recommendations.md, docs/testing/invariants-and-business-logic.md, docs/frontend/timecurve-views.md; manual QA checklist: ../../../docs/testing/manual-qa-checklists.md#manual-qa-issue-71 (GitLab #100 consolidated BGM verification here; skills/verify-yo-album-bgm-resume/SKILL.md removed), #103.
-
MegaEVM bytecode limits vs EIP-170 / Anvil (issue #72): MegaETH enforces 512 KiB max deployed bytecode and 536 KiB max initcode (Contract limits), not Ethereum’s EIP-170 cap (0x6000 / 24,576 bytes). Nested calls use MegaEVM 98/100 gas forwarding (Gas forwarding). This repo’s Anvil entrypoints use --code-size-limit 524288 (decimal; Foundry rejects hex) — see start-local-anvil-stack.sh, e2e-anvil.sh, anvil-export-bot-env.sh; start-local-anvil-stack.sh also sets --gas-limit ${YIELDOMEGA_ANVIL_GAS_LIMIT:-60000000} so the full local DeployDev broadcast is not capped by Anvil's 30M default. forge script needs the same code-size limit for pre-broadcast simulation — see anvil_deploy_dev.sh, anvil_rich_state.sh, anvil_same_block_drill.sh; a vanilla anvil without that flag still enforces 0x6000—see docs/contracts/foundry-and-megaeth.md and the test map in docs/testing/invariants-and-business-logic.md.
-
TimeCurve.currentCharmBoundsWad (issue #73); TS mirror (issue #109): _charmBounds must not div-by-zero when initialMinBuy == 0 (uninitialized implementation / degenerate storage, often after mis-parsing DeployDev run-latest.json per issue #61). The view returns the base CHARM envelope (0.99e18, 10e18) in that case; operational reads and buys still require the ERC1967 proxy. minCharmWadAt / maxCharmWadAt / *Float in timeCurveMath.ts return the same CHARM_*_BASE_WAD when charmEnvelopeRefWad === 0n (no JS div-by-zero). Map: docs/testing/invariants-and-business-logic.md; tests: test_currentCharmBoundsWad_zero_initialMinBuy_returns_base_envelope, timeCurveMath.test.ts (ref-zero mirror).
-
TimeCurve post-end gates — live Anvil (issue #79): The #55 redeemCharms / distributePrizes owner gates are covered in Forge; for one-chain cast evidence use ANVIL_RICH_END_SALE_ONLY=1 bash contracts/script/anvil_rich_state.sh (Part1 + warp + SimulateAnvilRichStatePart2EndSaleOnly — resets DeployDev convenience flags, then endSale()), then bash scripts/verify-timecurve-post-end-gates-anvil.sh. Full anvil_rich_state (no flag) already enables gates in Part2 — not suitable for the “gate off” rows on the same state. Map: docs/testing/invariants-and-business-logic.md, docs/operations/final-signoff-and-value-movement.md, manual QA checklist ../../../docs/testing/manual-qa-checklists.md#manual-qa-issue-79.
-
TimeCurveBuyRouter Anvil verification (issue #78); local registry + VITE_* parity (issue #84): scripts/start-local-anvil-stack.sh deploys DeployDev only unless YIELDOMEGA_DEPLOY_KUMBAYA=1 (then DeployKumbayaAnvilFixtures, registry key TimeCurveBuyRouter, Kumbaya VITE_* in frontend/.env.local — scripts/lib/kumbaya_local_anvil_env.sh; KEY=value lines are written literally via kumbaya_env_set_line.py — #154, INV-KUMBAYA-ENV-154 in invariants). Default stack: timeCurveBuyRouter stays 0 until fixtures. scripts/e2e-anvil.sh / anvil_deploy_dev.sh deploy fixtures. bash scripts/verify-timecurve-buy-router-anvil.sh (optional YIELDOMEGA_DEPLOY_KUMBAYA=1) runs fork tests and merges contracts.TimeCurveBuyRouter into local-anvil-registry.json; restart the indexer so BuyViaKumbaya is ingested (issue #67). Map: docs/testing/invariants-and-business-logic.md, docs/integrations/kumbaya.md, ../../../docs/testing/manual-qa-checklists.md#manual-qa-issue-78.
-
Single-chain wagmi / default Anvil (issue #81): wagmi-config.ts declares only configuredChain() — no extra mainnet / sepolia entries that trigger viem default transports (e.g. eth.merkle.io). Default when VITE_CHAIN_ID / VITE_RPC_URL unset: 31337 + http://127.0.0.1:8545, aligned with start-local-anvil-stack.sh. Docs: docs/frontend/wallet-connection.md, docs/testing/invariants-and-business-logic.md; manual QA checklist: ../../../docs/testing/manual-qa-checklists.md#manual-qa-issue-81.
-
TimeCurve buy — submit-time CHARM sizing (issue #82): useTimeCurveSaleSession / useTimeCurveArenaModel must readFreshTimeCurveBuySizing before buy / buyViaKumbaya calldata; effective max CHARM uses 99.5% slack under live maxCharmWad; effective min uses 100.5% headroom above live minCharmWad (symmetric 50 bps margins for inclusion-time drift at both band edges); friendlyRevertFromUnknown(..., { buySubmit: true }) for bare reverts. Docs: docs/frontend/timecurve-views.md, docs/testing/invariants-and-business-logic.md, docs/integrations/kumbaya.md; manual QA checklist: ../../../docs/testing/manual-qa-checklists.md#manual-qa-issue-82.
-
Kumbaya swap deadline vs Anvil warp (issue #83): fetchSwapDeadlineUnixSec (head block.timestamp + buffer) must be used for exactOutput / buyViaKumbaya — not Date.now() — so anvil_increaseTime / anvil_rich_state.sh cannot make AnvilKumbayaRouter Expired() spuriously. Two-step: fetch after wrap/approve, before swap write; single-tx: after USDM approve if any. Stacks using anvil_increaseTime: anvil_rich_state.sh, default start-local-anvil-stack.sh unless SKIP_ANVIL_RICH_STATE=1. Option B (fresh Anvil, skip rich state, evidence before warp): docs/integrations/kumbaya.md. Map: docs/testing/invariants-and-business-logic.md, docs/frontend/timecurve-views.md; manual QA checklists: ../../../docs/testing/manual-qa-checklists.md#manual-qa-issue-82, ../../../docs/testing/manual-qa-checklists.md#manual-qa-issue-78.
-
Anvil E2E Playwright (issue #87): bash scripts/e2e-anvil.sh uses one Anvil and one mock wallet; e2e/anvil-*.spec.ts must not run with multi-worker Playwright (cross-file nonce / state races). With ANVIL_E2E=1, playwright.config.ts uses workers: 1 and fullyParallel: false. Pay mode toggles use data-testid="timecurve-simple-paywith-{cl8y,eth,usdm}" — not removed input[name="timecurve-pay-with"]. Map: docs/testing/e2e-anvil.md, docs/testing/invariants-and-business-logic.md; manual QA checklist: ../../../docs/testing/manual-qa-checklists.md#manual-qa-issue-87.
-
DeployDev short buy cooldown — local QA (issue #88): DeployDev.s.sol resolves TimeCurve buyCooldownSec via DeployDevBuyCooldown.sol. Default remains 300 when unset. YIELDOMEGA_DEPLOY_NO_COOLDOWN=1 defaults the initializer arg to 1 s (unless YIELDOMEGA_ANVIL_BUY_COOLDOWN_SEC is set); never 0 (TimeCurve init + DeployDev require). Same env is read by start-local-anvil-stack.sh, e2e-anvil.sh / anvil_deploy_dev.sh (inherited shell env). Map: docs/testing/e2e-anvil.md, docs/testing/invariants-and-business-logic.md; manual QA checklist: ../../../docs/testing/manual-qa-checklists.md#manual-qa-issue-88; tests: DeployDevBuyCooldown.t.sol.
-
Launch UX F-11 — /referrals not a placeholder (issue #91): launchplan-timecurve.md §6 + YO-DOUB-Launch-UX-Flows.md (F-11) list /referrals as a shipping surface (ReferralsPage, share links, registry UX), not UnderConstruction, aligned with #64. Map: docs/testing/invariants-and-business-logic.md; manual QA checklist: ../../../docs/testing/manual-qa-checklists.md#manual-qa-issue-64.
-
/vesting — DoubPresaleVesting presale UX (issue #92): Hidden from RootLayout nav (direct URL / share only). Env: VITE_DOUB_PRESALE_VESTING_ADDRESS = ERC-1967 proxy. DeployDev funds a dev vesting, enables claims, startVesting(), logs DoubPresaleVesting: for start-local-anvil-stack.sh / e2e-anvil.sh. claim onClick surfaces chainMismatchWriteMessage to the UI on wrong-chain races (issue #106); aligned with useTimeCurveSaleSession / referrals register. Stale wagmi claimError after switching back to target (issue #166): usePresaleVestingChainWriteEffects clears via reset() only on a wrong-chain → target transition — INV-FRONTEND-166. Claim failure copy (issue #145): friendlyRevertFromUnknown + redactSensitiveUrlsInUserMessage — no raw wagmi message with VITE_RPC_URL / hosted-RPC keys in StatusMessage. Owner rescueERC20 (issue #137): onlyOwner recovery of excess vesting DOUB + stray ERC20; beneficiary entitlements unchanged — invariants § #137. Docs: docs/frontend/presale-vesting.md, invariants § #92 · #106 · #166 · #145; manual QA: ../../../docs/testing/manual-qa-checklists.md#manual-qa-issue-92, #106, #166, #145.
-
Wallet chainId must match build target for writes (issue #95): Target id is configuredTargetChainId() (VITE_CHAIN_ID / VITE_RPC_URL; default 31337 Anvil). ChainMismatchWriteBarrier + SwitchToTargetChainButton (EIP-3326 switch) gate TimeCurve Simple buy panel, Arena buy hub / standings+post-end / WarBow, /referrals register, /vesting claim; chainMismatchWriteMessage preflights submitBuy / submitRedeem, Arena handleBuy, runVoid, WarBow txs, referral register, and vesting claim. Map: docs/frontend/wallet-connection.md, docs/frontend/timecurve-views.md, docs/testing/invariants-and-business-logic.md; manual QA checklist: ../../../docs/testing/manual-qa-checklists.md#manual-qa-issue-95.
-
Indexer offline signal + poll backoff (issue #96); HTTP 200 malformed JSON (issue #111): Shared reportIndexerFetchAttempt / getIndexerBackoffPollMs in indexerConnectivity.ts; IndexerConnectivityProvider + IndexerStatusBar Indexer offline · retrying after debounced failure streak; useTimecurveHeroTimer and fetchTimecurveBuys (Simple + Arena) use backoff; Simple mirrors footer pill and fixes Recent buys empty copy. getJson / fetchTimecurveChainTimer in indexerApi.ts must await res.json() so parse errors return null and failures reach reportIndexerFetchAttempt(false), not escaped promise rejections. Docs: timecurve-views.md §96, invariants §96, invariants §111; manual QA checklist: ../../../docs/testing/manual-qa-checklists.md#manual-qa-issue-96.
-
Keyboard focus visible — WCAG 2.4.7 (issue #97): Global :focus-visible rings use --yo-focus-ring in index.css; the same selector list is scoped under [data-rk] so RainbowKit’s outline: none reset (higher specificity on bare button:focus-visible) does not hide keyboard focus in connect / account UI. Includes [role="button"], summary, and focusable [tabindex] (excluding -1). Docs: docs/frontend/design.md, wallet-connection.md, invariants §97; manual QA checklist: ../../../docs/testing/manual-qa-checklists.md#manual-qa-issue-97.
-
Canonical fee sinks — mobile address affordance + readable protocol labels (issue #93); explorer base shared with tx URLs (issue #98): MegaScannerAddressLink uses explorerAddressUrl (VITE_EXPLORER_BASE_URL, default https://mega.etherscan.io); ≤479px abbreviates full 0x + 40 hex to four leading + four trailing glyphs via abbreviateAddressEnds. humanizeKvLabel on TimeCurveProtocolPage + Arena raw accordion. Docs: docs/frontend/timecurve-views.md, invariants §93; manual QA checklist: ../../../docs/testing/manual-qa-checklists.md#manual-qa-issue-93.
-
Canonical address display — blockie + explorer link (issue #98): AddressInline pairs blockie + label + external explorer link (target="_blank", rel="noreferrer noopener"); invalid / zero address → fallback text. LiveBuyRow uses a focusable row hit target (not <button> around <a>) so buyer explorer links work without invalid nested interactives. Map: docs/frontend/wallet-connection.md, invariants §98; manual QA checklist: ../../../docs/testing/manual-qa-checklists.md#manual-qa-issue-98.
-
Arena WarBow hero actions (issue #101); steal ranking / UTC caps (issue #134); indexer ladder + feed refresh (GitLab #182); Chasing pack full ladder + scroll (GitLab #189); post-timer settlement CTAs (GitLab #188): PageHeroArcadeBanner surfaces Steal / Guard / Revenge via WarbowHeroActions; suggested steal targets are discovery-only; onchain reads (battlePoints, stealsReceivedOnDay, stealsCommittedByAttackerOnDay) and describeStealPreflight remain the gate — invariants §101, invariants §134. INV-FRONTEND-182-WARBOW-IDX: Arena indexer warbow/leaderboard + warbow/battle-feed refetch on refetchAll and ~5s backoff poll — invariants §182, useTimeCurveArenaModel.tsx. INV-FRONTEND-189-WARBOW-CHASING-PACK: Chasing pack maps the full leaderboard (warbowLeaderboardForChasingPackDisplay) with .warbow-chasing-pack-scroll — invariants §189. INV-FRONTEND-188-ARENA-SETTLEMENT: saleExpiredAwaitingEnd shows End sale + visible Redeem / Distribute (disabled until ended) — invariants §188, TimeCurveArenaView.tsx. Map: docs/frontend/timecurve-views.md; participant play skill: skills/play-timecurve-warbow/SKILL.md.
-
Bot swarm vs frozen Anvil block.timestamp (issue #99): With SKIP_ANVIL_RICH_STATE=1, START_BOT_SWARM defaults on. start-local-anvil-stack.sh starts Anvil with --block-time when it launches the node for the swarm (YIELDOMEGA_ANVIL_BLOCK_TIME_SEC, default 12; 0 = omit). It notes when DeployDev would keep 300 s buyCooldownSec without YIELDOMEGA_DEPLOY_NO_COOLDOWN / explicit YIELDOMEGA_ANVIL_BUY_COOLDOWN_SEC. Operators still use #88 env for dense continuous Buy traffic. No evm_increaseTime in Python bots. Map: docs/testing/e2e-anvil.md, docs/testing/invariants-and-business-logic.md; manual QA checklist: ../../../docs/testing/manual-qa-checklists.md#manual-qa-issue-99.
-
Local full stack QA orchestrator (issue #104); --help text (issue #105); Vite interrupt cleanup (issue #153): Procedural QA for Postgres + Anvil + indexer + frontend/.env.local + optional Vite uses docs/testing/qa-local-full-stack.md and scripts/start-qa-local-full-stack.sh, which delegate only to start-local-anvil-stack.sh — no duplicated deploy/indexer logic. INV-QA-FULLSTACK-HELP-105: --help must not leak shell setup (set -euo pipefail) — extract leading # banner lines only (stop at first non-comment line). INV-QA-FULLSTACK-153: optional Vite start uses scripts/lib/qa_local_full_stack_frontend.sh — INT/TERM + EXIT traps, exec npm, curl --connect-timeout / --max-time during readiness; hermetic smoke verify-qa-orchestrator-frontend-trap.sh. Playwright stays in e2e-anvil.sh. Invariants + manual checklist: invariants-and-business-logic.md, invariants §153, manual-qa-checklists.md#manual-qa-issue-104; player index: skills/README.md.
-
Scheduled sale startSaleAt (issue #114): TimeCurve moves from saleStart == 0 via onlyOwner startSaleAt(epoch) only; deadline = min(epoch + initialTimerSec, epoch + MAX_SALE_ELAPSED_SEC + 1) (GitLab #124 hard cap); SaleStarted carries epoch. DeployDev schedules safely ahead with YIELDOMEGA_DEV_SALE_START_DELAY_SEC and start-local-anvil-stack.sh advances local Anvil to that epoch before QA flows; AnvilSameBlockDrill keeps its immediate same-block drill semantics. buy / WarBow CL8Y / flag claim revert sale not live until block.timestamp >= saleStart. Views use elapsed = 0 until live (_elapsedForCharmPricing, then capped at MAX_SALE_ELAPSED_SEC per #124). Map: invariants §114, invariants §124, docs/frontend/timecurve-views.md.
-
Pre-open hero countdown (issue #115): In saleStartPending, Simple + Arena hero digits count down to saleStart (indexer sale_start_sec + hero chainNow preferred; #48); live phases use deadline. Timer-cap / extension preview stays on live deadline seconds only. Copy: “TimeCurve Opens In”. Map: invariants §115, timecurve-views §115; play index: skills/README.md.
-
RabbitTreasury Burrow curve param bounds (issue #119): setCStarWad / setAlphaWad / setBetaWad / setLamWad / setDeltaMaxFracWad + initialize enforce WAD envelopes aligned with contracts/PARAMETERS.md § Rabbit Treasury (Burrow); alphaWad < WAD prevents BurrowMath: inner<=0 from governance-alone misconfiguration; finalizeEpoch epochId remains rolling accounting windows, not "next product epoch" semantics — docs in rabbit-treasury.md, fee-routing-and-governance.md. Map: invariants §119; Foundry: RabbitTreasury.t.sol; play skill cross-link: skills/play-rabbit-treasury/SKILL.md.
-
AccessControl zero admin at deploy (issue #120): FeeRouter, PodiumPool, FeeSink derivatives, RabbitTreasury, Doubloon, LeprechaunNFT reject admin == address(0) before granting DEFAULT_ADMIN_ROLE (audit L-03). Map: INV-AC-ZERO-ADMIN-120 + derived INV-INDEXER-120-DEPLOY / INV-FRONTEND-120-DEPLOY (invariants §120); fee-routing — deployer boundary; indexer README; docs/indexer/design.md; wallet-connection §120; contributor manual QA #120; third-party deployers: skills/README.md.
-
ERC20 balance-delta ingress (GitLab #123, audit L‑05): TimeCurve buy / WarBow CL8Y pulls, RabbitTreasury deposit / receiveFee, ReferralRegistry registerCode, and TimeCurveBuyRouter PAY_STABLE pulls measure balanceOf deltas and revert when credited amounts disagree with declared pulls (TimeCurve: ERC20 parity, RT: ERC20 parity, ReferralRegistry: ERC20 parity, TimeCurveBuyRouter__StableIngressParity). FeeRouter.distributeFees amount must match measured ingress on callers (INV-ERC20-123). WarBow: warbowSteal / warbowRevenge / warbowActivateGuard require msg.sender != BURN_SINK (INV-WARBOW-123-BURN-CALLER) so #123 _pullAcceptedExact cannot net zero CL8Y to 0xdEaD while BP mutates. Map: invariants §123; Forge: NonStandardERC20.t.sol, TimeCurveBuyRouter.t.sol (TimeCurveBuyRouterStableIngress123Test), TimeCurveWarBowCl8yBurns.t.sol.
-
300-day wall-clock sale cap (issue #124, audit I‑01): MAX_SALE_ELAPSED_SEC caps pricing elapsed; buy / buyFor and WarBow CL8Y paths revert "TimeCurve: sale max elapsed exceeded" when block.timestamp > saleStart + MAX (inclusive <= at saleStart + MAX). Round countdown deadline() is also inclusive through the last second (block.timestamp <= deadline() for buys/WarBow mutations; endSale > deadline — issue #136, invariants §136). deadline clamps to saleStart + MAX + 1; LinearCharmPrice.initialize bounds daily increment for a 300-day plateau; TS charts use timeCurveMath.ts MAX_SALE_ELAPSED_SEC / capElapsedForSalePricing. Map: PARAMETERS.md § TimeCurve.
-
Leprechaun NFT admin-mutable baseURI / tokenURI (issue #125, audit I-02): DEFAULT_ADMIN_ROLE may setBaseURI; offchain JSON and media behind tokenURI can change while tokenTraits stay the gameplay authority. Do not document or UI-imply immutable offchain metadata without an onchain freeze. Map: INV-LEP-125, docs/product/leprechaun-nfts.md, LeprechaunNFT.sol, play skill collect-leprechaun-sets/SKILL.md.
-
Unredeemed DOUB sale allocation (issue #128): UNREDEEMED_LAUNCHED_TOKEN_GRACE_SEC (7 days); endSale sets saleEndedAt; onlyOwner sweepUnredeemedLaunchedToken after grace; setUnredeemedLaunchedTokenRecipient (governance sink, independent of podiumResidualRecipient / GitLab #116); repairSaleEndedAt for upgraded proxies; indexer idx_timecurve_unredeemed_launched_token_* — INV-INDEXER-128 ↔ decoder.rs. Migrations: 20260504180000_timecurve_unredeemed_launched_token has paired .down.sql for sqlx migrate revert (GitLab #152).
-
Doubloon allowance-only third-party burn (issue #132): mint stays MINTER_ROLE (typically RabbitTreasury); burns use ERC20Burnable — withdraw burnFrom requires holder approve(DOUB → RabbitTreasury). No BURNER_ROLE; MINTER_ROLE cannot wipe others without allowance. Map: INV-DOUB-132, fee-routing § mint vs burn, play-rabbit-treasury/SKILL.md.
-
WarBow per-(victim, stealer) revenge windows (issue #135): Onchain warbowRevenge(stealer) clears only [victim][stealer]; WarBowRevengeWindowOpened + idx_timecurve_warbow_revenge_window feed GET /v1/timecurve/warbow/pending-revenge. Arena + raw accordion list all open windows when VITE_INDEXER_URL is set. Foundry pins multi-stealer cardinality (test_warbow_revenge_seven_stealers_distinct_slots, testFuzz_warbow_revenge_distinct_stealer_slots_overlap — INV-WARBOW-135-CARD). Map: §135, timecurve-views, play-timecurve-warbow, indexer/design.md.
-
Foundry dev scripts — block.chainid allowlist (issue #141, parent #138): DeployDev, DeployKumbayaAnvilFixtures, AnvilSameBlockDrill, SimulateAnvilRichState* call DevOnlyChainGuard.assertDevScriptChain() first — 31337 / 6342 / 6343 only; not MegaETH mainnet 4326. Map: INV-DEVSCRIPT-141, docs/contracts/foundry-and-megaeth.md, contracts/README.md; tests: DevOnlyChainGuard.t.sol.
-
TimeCurve buy — wallet session continuity mid-flow (issue #144): submitBuy / Arena handleBuy and submitKumbayaSingleTxBuy latch getAccount(wagmi) after readFreshTimeCurveBuySizing and assertWalletBuySessionUnchanged after each await so account / chainId drift aborts with Wallet or network changed during purchase — please retry from the beginning. Complements wrong-chain preflight (#95). Map: INV-BUY-SESSION-144, walletBuySessionGuard.ts, wallet-connection.md; play UX hint: play-timecurve-doubloon/SKILL.md.
-
Indexer transactional block ingest (GitLab #140) + Forge regressions (umbrella #146): ingestion::run uses one SQL transaction per processed block (all persist_decoded_log_conn rows + indexed_blocks + chain_pointer). INV-INDEXER-140 ↔ docs/testing/invariants-and-business-logic.md; integration: integration_stage2.rs postgres_gitlab146_block_transaction_all_or_nothing_for_shared_tx_hash. #146 tracks related Forge tests: Doubloon, TimeCurve, DoubPresaleVesting (docs/testing/strategy.md). Naming hygiene (#148): tests/tools call persist_decoded_log_autocommit for intentional single-event commits — INV-INDEXER-148-AUTOCOMMIT; WarBow WARBOW_MAX_STEALS_PER_DAY documents the dual victim/attacker UTC threshold (INV-WARBOW-148-CONST).
-
Frontend ERC-20 approve sizing (GitLab #143, split from #138 Finding 5): Default exact approvals for Kumbaya maxIn, referral registrationBurnAmount, and CL8Y → TimeCurve (buy / WarBow pulls); opt-in unlimited CL8Y → TimeCurve via Cl8yTimeCurveUnlimitedApprovalFieldset + localStorage yieldomega.erc20.cl8yTimeCurveUnlimited.v1 with H-01 disclosure. Map: INV-ERC20-APPROVAL-143, wallet-connection.md §143, kumbaya.md, skills/README.md.
-
WarBow stack review hardening (GitLab #149; governance podium #172; protocol refresh-candidates UI paging guard — #174): refreshWarbowPodium removed — post-endSale, only finalizeWarbowPodium(first, second, third) sets warbowPodiumFinalized. Arena pending-revenge polling must not key off per-block ledgerSecInt churn; indexer WarBow SQL must compare lowercase-bound params to plain indexed address columns (no LOWER(column) index defeat); verify-timecurve-post-end-gates-anvil.sh validates ADDR_ALICE. GitLab #160 / #170 / #172: GET /v1/timecurve/warbow/refresh-candidates plus /timecurve/protocol assist operators with a reference wallet superset (unbounded DISTINCT from schema ≥ 1.18.0); post-end hint omission + sale_ended field; panel catch on indexer load. GitLab #174: if the UI exhausts its max page budget while next_offset stays set, TimeCurveProtocolWarbowRefreshSection surfaces a non-fatal warning (INV-FRONTEND-174-WARBOW-REFRESH-GUARD**). Map: INV-*-149-* / INV-WARBOW-172-* / §174, INV-INDEXER-160-WARBOW-REFRESH-CANDIDATES, INV-INDEXER-170-* / INV-FRONTEND-170-WARBOW-IDX-ERR, INV-WARBOW-172-GOV-FINALIZE; play index: skills/README.md, play-timecurve-warbow/SKILL.md.
Prefer small, reviewable diffs and avoid unrelated refactors.