QA Anvil time-warp vs wall-clock swapDeadline breaks buyViaKumbaya (AnvilKumbayaFixture Expired)
## Summary Follow-up from [#75](https://gitlab.com/PlasticDigits/yieldomega/-/issues/75) (indexer `entry_pay_asset='eth'` evidence via live `buyViaKumbaya`) and the [#82](https://gitlab.com/PlasticDigits/yieldomega/-/issues/82) note: on a **QA Anvil stack** that had used **`evm_increaseTime` / `anvil_increaseTime`** to move chain time, the **last-mile signed `buyViaKumbaya` tx** reverted on **`AnvilKumbayaRouter` `Expired()`** even though **source, integration tests, and Postgres roundtrip already PASS** and the **frontend reached the swap router** with correct wiring. This is a **chain-time vs deadline encoding** mismatch in the **Kumbaya single-tx path** under **artificial time warping**, not a user-facing bug on **normal-clock** networks. ## Root cause (repo investigation) 1. **Swap deadline is wall-clock based** `swapDeadlineUnixSec` in `frontend/src/lib/timeCurveKumbayaSwap.ts` sets `Math.floor(Date.now() / 1000) + bufferSec` (default buffer **600s**). The same helper is used from `useTimeCurveSaleSession`, `useTimeCurveArenaModel`, and `timeCurveKumbayaSingleTx.ts` when building `buyViaKumbaya(..., swapDeadline, ...)`. 2. **The Anvil fixture enforces deadline against `block.timestamp`** In `contracts/src/fixtures/AnvilKumbayaFixture.sol`, `AnvilKumbayaRouter.exactOutput` does `if (block.timestamp > params.deadline) revert Expired();` before executing the swap. `TimeCurveBuyRouter.buyViaKumbaya` passes that `swapDeadline` through unchanged to the Kumbaya router (`TimeCurveBuyRouter.sol` → `kumbayaRouter.exactOutput`). 3. **QA stacks advance `block.timestamp` without moving the browser clock** e.g. `contracts/script/anvil_rich_state.sh` calls `cast rpc anvil_increaseTime` in `warp_to_at_least` / `warp_past_timcurve_deadline` to bring **chain time** to (or past) onchain `TimeCurve.deadline` and for post-end flows. After a large warp, **`block.timestamp` can be many minutes or hours *ahead* of `Date.now()`**, so a deadline computed as `Date.now() + 600` is **still in the past** relative to `block.timestamp` at tx execution → **`Expired()`**. 4. **Contrast: elsewhere the app already reasons about chain time** e.g. `useTimeCurveArenaModel` documents that display / phase logic should track **`block.timestamp`**, not wall-clock drift (`blockChainSec`, `effectiveLedgerSec`). The Kumbaya **swap deadline** is still the exception: it does **not** use head block time + buffer. ## Why mainnet / typical testnets are unaffected On live chains, **head `block.timestamp`** tracks **real time** closely; `Date.now() + 600` is usually **ahead** of inclusion-time `block.timestamp`, so the Uniswap-style deadline check passes. The failure mode is **specific to Anvil (or any environment) that jumps chain time far ahead of wall time**. ## Proposed solutions (pick or combine) ### A — **Align swap deadline with chain time (recommended for correctness)** - **Change:** When the wallet is connected to an RPC, compute `swapDeadline = latestHeadBlockTimestamp + bufferSec` (and optionally a small client-side margin), instead of `Date.now() + bufferSec`. - **Where:** Centralize in `timeCurveKumbayaSwap.ts` and/or the call sites in `useTimeCurveSaleSession`, `useTimeCurveArenaModel`, and `submitKumbayaSingleTxBuy` so **single-tx and UI-built txs** share one rule. - **Data source:** `useBlock({ watch: true })` in components, or `getBlock` / `getBlockNumber` in `submitKumbayaSingleTxBuy` via the wagmi `Config` (must run **immediately before** building calldata, like other submit-time invariants in [#82](https://gitlab.com/PlasticDigits/yieldomega/-/issues/82)). - **Tradeoff:** Slightly more RPC coupling; on very stale heads, deadline follows chain (still desirable for onchain `block.timestamp` checks). ### B — **QA / infra: ordering and expectations** - **Document** in the relevant playbooks (E2E / manual QA, not a code change in this task): for stacks where **`anvil_increaseTime`** is used, either: - run **`buyViaKumbaya` live evidence *before* any large time warp** in the session, **or** - use a **fresh Anvil** (or `anvil_reset` / equivalent) for the Kumbaya proof after warping, **or** - **avoid warping** for the slice that must produce the indexer row (aligns with “normal-clock chains unaffected”). - **Scripts:** Any automation that currently chains `anvil_rich_state.sh` (or similar) *before* browser Kumbaya flows should be reordered or split. ### C — **Fixture-only relaxation (low priority, risks prod parity)** - **Option:** Dev-only / test-only router behavior (e.g. mock “infinite” deadline) **weakens** parity with production routers that enforce deadlines — **not recommended** unless only used in isolated tests, not the shared AnvilKumbaya fixture that mirrors production interfaces. ## Concrete follow-ups (for the chosen approach) - **If A:** Implementation + a short regression test matrix: **wall-aligned chain** (today’s behavior) **and** a manual or scripted case where `anvil_increaseTime` is applied, then a **fresh** `getBlock` + `buyViaKumbaya` still succeeds. - **If B:** Update the **agent/QA** workflow for [#75](https://gitlab.com/PlasticDigits/yieldomega/-/issues/75) evidence and cross-link from [#82](https://gitlab.com/PlasticDigits/yieldomega/-/issues/82) / [#75](https://gitlab.com/PlasticDigits/yieldomega/-/issues/75) as needed. ## Cross-links - Indexer: `entry_pay_asset` for `BuyViaKumbaya` — [#67](https://gitlab.com/PlasticDigits/yieldomega/-/issues/67) - Submit-time sizing — [#82](https://gitlab.com/PlasticDigits/yieldomega/-/issues/82) - `TimeCurveBuyRouter` / Kumbaya — [#65](https://gitlab.com/PlasticDigits/yieldomega/-/issues/65), [#66](https://gitlab.com/PlasticDigits/yieldomega/-/issues/66), [#78](https://gitlab.com/PlasticDigits/yieldomega/-/issues/78)
issue