Frontend buy panel: stale charmWad in calldata between slider read and tx submit causes 'execution reverted' on live-clock chain #82

Closed
opened 2026-04-27 06:17:37 +00:00 by Brouie · 24 comments
Brouie commented 2026-04-27 06:17:37 +00:00 (Migrated from gitlab.com)

Found Monday 2026-04-27 during browser walkthrough on live local-anvil stack (chain 31337, fresh deploy + Kumbaya fixtures, Vite frontend at http://127.0.0.1:5173).

Summary

The Simple page Buy CHARM panel built calldata with charmWad = 5503000000000000000 (5.503e18) when the slider showed 5.503 CHARM. Across the screenshots from approve → submit, the slider visibly auto-corrected from 5.503 to 5.501 to 5.5 as the upper-bound math drifted with each new block. The submitted tx broadcast against a state where charmWad was outside the new currentCharmBoundsWad envelope and reverted with bare 'Execution reverted for an unknown reason'.

Repro

  1. Live stack: SKIP_ANVIL_RICH_STATE=1 bash scripts/start-local-anvil-stack.sh + DeployKumbayaAnvilFixtures
  2. Frontend: cd frontend && npm run dev, open http://127.0.0.1:5173/timecurve
  3. Connect a wallet with non-zero CL8Y balance + max allowance to TimeCurve
  4. Drag the buy slider to a non-integer value (e.g. 5.503 or 5.504) — any value near the current upper bound is most likely to trip
  5. Click BUY CHARM rapidly, accept the approve, accept the buy
  6. The buy tx broadcasts and reverts; the frontend shows 'Execution reverted for an unknown reason'

State during repro

  • Allowance: 0xf...f (max uint256, approve worked)
  • CL8Y balance: 50,000,000 (50m, plenty)
  • charmWeight: 0 (buy did not complete)
  • Frontend Buy CHARM panel slider visibly drifted: 5.503 → 5.501 → 5.5 across the approve → submit timeline

Root cause hypothesis

When the slider settles, the panel computes charmWad once from currentCharmBoundsWad() at that instant. By the time the user signs the buy tx and the broadcast lands ~1-3 blocks later, the upper bound has tightened (charm bounds in TimeCurve.sol grow with block.timestamp), so:

  • charmWad in calldata might exceed currentMaxBuy at the new tx-time block
  • Or charmWad clamped down by frontend re-quote logic but the calldata wasn't rebuilt before submit

The reverted tx is a contract-level guard at TimeCurve.sol L500 / L514 / etc.: require(charmWad >= currentMinBuy && charmWad <= currentMaxBuy, ...) but with the bare-revert flavor (no string in the require message OR optimizer stripped, not yet checked). Either way the user-visible result is unhelpful.

Confirming the contract path is fine

Direct cast send from the same wallet with a rounded charmWad of 5.0e18 succeeded:

cast send 0x59b670e9fA9D0A427751Af201D676719a970857b 'buy(uint256)' 5000000000000000000 --private-key <owner> --rpc-url http://127.0.0.1:8545
status 1 (success)
charmWeight(0xf3...2266) = 5e18

So the contract logic is healthy. The bug is purely in the frontend: it ships stale calldata after a stable-state quote.

Suggested fixes

  1. Re-quote on submit: before signing buy, call currentCharmBoundsWad and clamp charmWad into the freshly-read [min, max] band. Add a small safety margin (e.g. clamp to 99.5% of upper bound) to absorb 1-block drift between estimateGas and broadcast.
  2. Bare-revert error UX: when buy reverts with no string, the frontend should fall back to a contextual message like 'The CHARM band shifted between quote and submit — please try again with a slightly lower amount' instead of the raw 'Execution reverted for an unknown reason'.

Severity

MEDIUM. Not a security hole, but a real UX trap for any user buying near the upper bound on a chain with normal block production. On MegaETH (much faster blocks than 12s mainnet) this will be MORE common, not less. Worth a fix before mainnet.

Out of scope

Not blocking the smaller buys we just verified work. CL8Y direct buy via cast is fully functional. The buyViaKumbaya ETH/USDM path (#74) likely has similar exposure since it also computes CL8Y from charmWad × currentPricePerCharmWad, so the fix should cover both.

cc @PlasticDigits

Found Monday 2026-04-27 during browser walkthrough on live local-anvil stack (chain 31337, fresh deploy + Kumbaya fixtures, Vite frontend at http://127.0.0.1:5173). ## Summary The Simple page Buy CHARM panel built calldata with charmWad = 5503000000000000000 (5.503e18) when the slider showed 5.503 CHARM. Across the screenshots from approve → submit, the slider visibly auto-corrected from 5.503 to 5.501 to 5.5 as the upper-bound math drifted with each new block. The submitted tx broadcast against a state where charmWad was outside the new currentCharmBoundsWad envelope and reverted with bare 'Execution reverted for an unknown reason'. ## Repro 1. Live stack: SKIP_ANVIL_RICH_STATE=1 bash scripts/start-local-anvil-stack.sh + DeployKumbayaAnvilFixtures 2. Frontend: cd frontend && npm run dev, open http://127.0.0.1:5173/timecurve 3. Connect a wallet with non-zero CL8Y balance + max allowance to TimeCurve 4. Drag the buy slider to a non-integer value (e.g. 5.503 or 5.504) — any value near the current upper bound is most likely to trip 5. Click BUY CHARM rapidly, accept the approve, accept the buy 6. The buy tx broadcasts and reverts; the frontend shows 'Execution reverted for an unknown reason' ## State during repro - Allowance: 0xf...f (max uint256, approve worked) - CL8Y balance: 50,000,000 (50m, plenty) - charmWeight: 0 (buy did not complete) - Frontend Buy CHARM panel slider visibly drifted: 5.503 → 5.501 → 5.5 across the approve → submit timeline ## Root cause hypothesis When the slider settles, the panel computes charmWad once from currentCharmBoundsWad() at that instant. By the time the user signs the buy tx and the broadcast lands ~1-3 blocks later, the upper bound has tightened (charm bounds in TimeCurve.sol grow with block.timestamp), so: - charmWad in calldata might exceed currentMaxBuy at the new tx-time block - Or charmWad clamped down by frontend re-quote logic but the calldata wasn't rebuilt before submit The reverted tx is a contract-level guard at TimeCurve.sol L500 / L514 / etc.: require(charmWad >= currentMinBuy && charmWad <= currentMaxBuy, ...) but with the bare-revert flavor (no string in the require message OR optimizer stripped, not yet checked). Either way the user-visible result is unhelpful. ## Confirming the contract path is fine Direct cast send from the same wallet with a rounded charmWad of 5.0e18 succeeded: ``` cast send 0x59b670e9fA9D0A427751Af201D676719a970857b 'buy(uint256)' 5000000000000000000 --private-key <owner> --rpc-url http://127.0.0.1:8545 status 1 (success) charmWeight(0xf3...2266) = 5e18 ``` So the contract logic is healthy. The bug is purely in the frontend: it ships stale calldata after a stable-state quote. ## Suggested fixes 1. **Re-quote on submit:** before signing buy, call currentCharmBoundsWad and clamp charmWad into the freshly-read [min, max] band. Add a small safety margin (e.g. clamp to 99.5% of upper bound) to absorb 1-block drift between estimateGas and broadcast. 2. **Bare-revert error UX:** when buy reverts with no string, the frontend should fall back to a contextual message like 'The CHARM band shifted between quote and submit — please try again with a slightly lower amount' instead of the raw 'Execution reverted for an unknown reason'. ## Severity MEDIUM. Not a security hole, but a real UX trap for any user buying near the upper bound on a chain with normal block production. On MegaETH (much faster blocks than 12s mainnet) this will be MORE common, not less. Worth a fix before mainnet. ## Out of scope Not blocking the smaller buys we just verified work. CL8Y direct buy via cast is fully functional. The buyViaKumbaya ETH/USDM path (#74) likely has similar exposure since it also computes CL8Y from charmWad × currentPricePerCharmWad, so the fix should cover both. cc @PlasticDigits
Brouie commented 2026-04-27 06:28:04 +00:00 (Migrated from gitlab.com)

Confirmed: same revert pattern reproduces on the #74 ETH single-tx path via TimeCurveBuyRouter.buyViaKumbaya.

Repro

  1. Live stack with VITE_KUMBAYA_* env wired (WETH / USDM / SWAP_ROUTER / QUOTER / TIMECURVE_BUY_ROUTER all set from DeployKumbayaAnvilFixtures broadcast)
  2. Switch Buy CHARM panel to ETH pay mode
  3. Drag slider to ~0.00555 ETH = 5.521 CHARM
  4. Click BUY CHARM
  5. MetaMask shows single-tx prompt: 0.005715 ETH (with 3% Kumbaya slippage cushion baked in), interacting with 0x998abeb3...3613E (TimeCurveBuyRouter)
  6. Confirm
  7. Tx reverts with bare 'Execution reverted for an unknown reason'

State during repro

  • Slider drifted from 5.521 -> 5.52 CHARM across the user-click → broadcast cycle
  • ETH balance untouched (revert before any value-transfer)

Conclusion

The 3% slippage cap mentioned in the panel ('Kumbaya route uses a fixed 3% max slippage cap on routed input') applies only to the Kumbaya swap leg, NOT to the TimeCurve charmWad bound check downstream. So #82's root cause covers both pay paths:

  • CL8Y direct: charmWad ships stale, reverts at TimeCurve.sol L500 require(charmWad >= currentMinBuy && charmWad <= currentMaxBuy)
  • ETH/USDM via buyViaKumbaya: same charmWad ships stale through TimeCurveBuyRouter.buyViaKumbaya -> TimeCurve.buyFor -> _buy -> same bound check, same revert

The fix needs to apply the re-quote / clamp logic in the shared CHARM amount path (whatever the underlying useTimeCurveSaleSession buyState builder is), not just the CL8Y branch.

Validation: when we use a clean integer charmWad

cast send buy(uint256) 5e18 from same wallet succeeded earlier (status=1, charmWeight=5e18). So the contract is healthy. The bug is purely the frontend's stale-calldata-on-submit issue.

Severity reaffirmed

MEDIUM, exactly as in original. ETH path repro means this isn't just a CL8Y-edge issue — it bites the whole buy pipeline.

Confirmed: same revert pattern reproduces on the #74 ETH single-tx path via TimeCurveBuyRouter.buyViaKumbaya. ## Repro 1. Live stack with VITE_KUMBAYA_* env wired (WETH / USDM / SWAP_ROUTER / QUOTER / TIMECURVE_BUY_ROUTER all set from DeployKumbayaAnvilFixtures broadcast) 2. Switch Buy CHARM panel to ETH pay mode 3. Drag slider to ~0.00555 ETH = 5.521 CHARM 4. Click BUY CHARM 5. MetaMask shows single-tx prompt: 0.005715 ETH (with 3% Kumbaya slippage cushion baked in), interacting with 0x998abeb3...3613E (TimeCurveBuyRouter) 6. Confirm 7. Tx reverts with bare 'Execution reverted for an unknown reason' ## State during repro - Slider drifted from 5.521 -> 5.52 CHARM across the user-click → broadcast cycle - ETH balance untouched (revert before any value-transfer) ## Conclusion The 3% slippage cap mentioned in the panel ('Kumbaya route uses a fixed 3% max slippage cap on routed input') applies only to the Kumbaya swap leg, NOT to the TimeCurve charmWad bound check downstream. So #82's root cause covers both pay paths: - CL8Y direct: charmWad ships stale, reverts at TimeCurve.sol L500 require(charmWad >= currentMinBuy && charmWad <= currentMaxBuy) - ETH/USDM via buyViaKumbaya: same charmWad ships stale through TimeCurveBuyRouter.buyViaKumbaya -> TimeCurve.buyFor -> _buy -> same bound check, same revert The fix needs to apply the re-quote / clamp logic in the **shared CHARM amount path** (whatever the underlying useTimeCurveSaleSession buyState builder is), not just the CL8Y branch. ## Validation: when we use a clean integer charmWad cast send buy(uint256) 5e18 from same wallet succeeded earlier (status=1, charmWeight=5e18). So the contract is healthy. The bug is purely the frontend's stale-calldata-on-submit issue. ## Severity reaffirmed MEDIUM, exactly as in original. ETH path repro means this isn't just a CL8Y-edge issue — it bites the whole buy pipeline.
Brouie commented 2026-04-27 06:29:42 +00:00 (Migrated from gitlab.com)

mentioned in issue #74

mentioned in issue #74
Brouie commented 2026-04-27 06:43:56 +00:00 (Migrated from gitlab.com)

mentioned in issue #76

mentioned in issue #76
PlasticDigits commented 2026-04-27 06:49:47 +00:00 (Migrated from gitlab.com)

mentioned in commit 5366f74e69

mentioned in commit 5366f74e6978282407994ad29cd718d3effc0615
PlasticDigits commented 2026-04-27 06:49:47 +00:00 (Migrated from gitlab.com)

mentioned in commit 04d5054b5e

mentioned in commit 04d5054b5e7857b3f888bc3094b82f125275c1fd
PlasticDigits commented 2026-04-27 06:50:06 +00:00 (Migrated from gitlab.com)

Implemented GitLab #82 on main (merge 5366f74).

What changed

  • Submit-time re-quote: readFreshTimeCurveBuySizing (frontend/src/lib/timeCurveBuySubmitSizing.ts) runs immediately before building calldata in useTimeCurveSaleSession (Simple) and useTimeCurveArenaModel (Arena), covering CL8Y buy, two-step Kumbaya + buy, and buyViaKumbaya.
  • Clamp max CHARM down: effective ceiling = 99.5% of live maxCharmWad (CHARM_SUBMIT_UPPER_SLACK_BPS = 50); CHARM from CL8Y remains integer floor in finalizeCharmSpendForBuy (documented).
  • Bare revert UX: friendlyRevertFromUnknown(err, { buySubmit: true }) maps generic execution failures to band-shift guidance.

Docs / skills

  • docs/frontend/timecurve-views.md#buy-charm-submit-fresh-bounds-issue-82
  • docs/testing/invariants-and-business-logic.md#timecurve-buy-charm-submit-fresh-bounds-issue-82
  • docs/integrations/kumbaya.md (single-tx section + #82 note)
  • Play checklist: skills/verify-yo-timecurve-buy-charm-submit/SKILL.md
  • Contributor guardrails: item 23 (renumbered with #78 / #81).

/cc @brouie — please verify on a live-ticking stack (Anvil or faster blocks).

Verification checklist

  1. CL8Y buy (Simple + Arena): slider near max CHARM (non-integer), approve if needed → buy succeeds or shows a clear panel error, not an unexplained bare revert string.
  2. ETH single-tx buyViaKumbaya: same near-max case with Kumbaya env + non-zero timeCurveBuyRouter → success or clear error.
  3. Bare revert copy: if you still hit a wallet-only generic revert, panel should suggest band/price moved (when caught through our buy path).
  4. Automated: cd frontend && npx vitest run src/lib/timeCurveBuySubmitSizing.test.ts src/lib/revertMessage.test.ts

Issue left open for your sign-off.

Implemented **GitLab #82** on `main` (merge `5366f74`). ### What changed - **Submit-time re-quote:** `readFreshTimeCurveBuySizing` (`frontend/src/lib/timeCurveBuySubmitSizing.ts`) runs immediately before building calldata in **`useTimeCurveSaleSession`** (Simple) and **`useTimeCurveArenaModel`** (Arena), covering **CL8Y `buy`**, two-step Kumbaya + `buy`, and **`buyViaKumbaya`**. - **Clamp max CHARM down:** effective ceiling = **99.5%** of live `maxCharmWad` (`CHARM_SUBMIT_UPPER_SLACK_BPS = 50`); CHARM from CL8Y remains **integer floor** in `finalizeCharmSpendForBuy` (documented). - **Bare revert UX:** `friendlyRevertFromUnknown(err, { buySubmit: true })` maps generic execution failures to band-shift guidance. ### Docs / skills - `docs/frontend/timecurve-views.md#buy-charm-submit-fresh-bounds-issue-82` - `docs/testing/invariants-and-business-logic.md#timecurve-buy-charm-submit-fresh-bounds-issue-82` - `docs/integrations/kumbaya.md` (single-tx section + #82 note) - Play checklist: `skills/verify-yo-timecurve-buy-charm-submit/SKILL.md` - Contributor guardrails: item **23** (renumbered with #78 / #81). /cc @brouie — please verify on a **live-ticking** stack (Anvil or faster blocks). ### Verification checklist 1. **CL8Y buy (Simple + Arena):** slider near **max CHARM** (non-integer), approve if needed → buy **succeeds** or shows a **clear panel error**, not an unexplained bare revert string. 2. **ETH single-tx `buyViaKumbaya`:** same near-max case with Kumbaya env + non-zero `timeCurveBuyRouter` → **success** or clear error. 3. **Bare revert copy:** if you still hit a wallet-only generic revert, panel should suggest **band/price moved** (when caught through our buy path). 4. **Automated:** `cd frontend && npx vitest run src/lib/timeCurveBuySubmitSizing.test.ts src/lib/revertMessage.test.ts` Issue left **open** for your sign-off.
Brouie commented 2026-04-27 06:54:09 +00:00 (Migrated from gitlab.com)

Verified on 5366f74 with both unit tests and live signed buy at MAX slider.

Unit tests

cd frontend && npx vitest run src/lib/timeCurveBuySubmitSizing.test.ts src/lib/revertMessage.test.ts:

  • 7/7 passing (3 sizing + 4 revert message)

Live buy at MAX slider — the danger case from the original repro

Setup: live local-anvil stack at 1bdbda8 + DeployKumbayaAnvilFixtures + 5366f74 frontend, MetaMask wallet 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 connected.

Steps:

  1. Refreshed http://127.0.0.1:5173/timecurve to pick up the new wagmi + sizing code
  2. Dragged Buy CHARM slider all the way to the right (max position)
  3. UI clamped automatically: slider showed 10.15961521653703 CL8Y → YOU ADD 10.1 CHARM. The 99.5% clamp from CHARM_SUBMIT_UPPER_SLACK_BPS=50 visible in the rendered amount.
  4. Clicked BUY CHARM
  5. MetaMask single-tx prompt: TimeCurve interacting addr, network fee -bash.72, no separate approve (prior allowance maxed)
  6. Confirmed

Result:

  • Tx succeeded (status 1)
  • charmWeight delta: 13.88 → 23.94 (+10.06 CHARM, the full 10.1 minus a small rounding floor per finalizeCharmSpendForBuy)
  • Recent Buys row appeared: 0xf39fd6...2266 / 10.10999... / +120s / tx 0xe7791844...

That's the exact case that previously reverted with bare 'Execution reverted for an unknown reason' in the original repro on this same stack ~30 min ago. The submit-time re-quote + 99.5% upper clamp absorbed all band drift between slider settle and tx broadcast.

Friendly revert UX (item 3) — not exercised because no revert occurred

Did not encounter a wallet-only generic revert in this session because the clamp prevented the bound-check from firing. The friendlyRevertFromUnknown helper is covered by the 4 unit tests in revertMessage.test.ts.

Closing #82.

Verified on 5366f74 with both unit tests and live signed buy at MAX slider. ## Unit tests cd frontend && npx vitest run src/lib/timeCurveBuySubmitSizing.test.ts src/lib/revertMessage.test.ts: - 7/7 passing (3 sizing + 4 revert message) ## Live buy at MAX slider — the danger case from the original repro Setup: live local-anvil stack at 1bdbda8 + DeployKumbayaAnvilFixtures + 5366f74 frontend, MetaMask wallet 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 connected. Steps: 1. Refreshed http://127.0.0.1:5173/timecurve to pick up the new wagmi + sizing code 2. Dragged Buy CHARM slider all the way to the right (max position) 3. UI clamped automatically: slider showed 10.15961521653703 CL8Y → YOU ADD 10.1 CHARM. The 99.5% clamp from CHARM_SUBMIT_UPPER_SLACK_BPS=50 visible in the rendered amount. 4. Clicked BUY CHARM 5. MetaMask single-tx prompt: TimeCurve interacting addr, network fee -bash.72, no separate approve (prior allowance maxed) 6. Confirmed Result: - Tx **succeeded** (status 1) - charmWeight delta: 13.88 → 23.94 (+10.06 CHARM, the full 10.1 minus a small rounding floor per finalizeCharmSpendForBuy) - Recent Buys row appeared: 0xf39fd6...2266 / 10.10999... / +120s / tx 0xe7791844... That's the exact case that previously reverted with bare 'Execution reverted for an unknown reason' in the original repro on this same stack ~30 min ago. The submit-time re-quote + 99.5% upper clamp absorbed all band drift between slider settle and tx broadcast. ## Friendly revert UX (item 3) — not exercised because no revert occurred Did not encounter a wallet-only generic revert in this session because the clamp prevented the bound-check from firing. The friendlyRevertFromUnknown helper is covered by the 4 unit tests in revertMessage.test.ts. Closing #82.
Brouie (Migrated from gitlab.com) closed this issue 2026-04-27 06:54:10 +00:00
Brouie commented 2026-04-27 07:01:45 +00:00 (Migrated from gitlab.com)

One small follow-up note from continued live testing: when the chain has been time-warped via evm_increaseTime in QA testing, the buyViaKumbaya flow can hit AnvilKumbayaFixture.sol Expired() (custom error 0x203d82d8) because the frontend's swapDeadline is computed from wall-clock time, not from chain time. On a normal-clock chain (mainnet, MegaETH, normal Anvil tick) this won't matter. On QA stacks with evm_increaseTime in the mix it does. Filing as a context note here rather than a new issue since this is a QA-only artifact, not a user-facing bug. The friendly revert UX panel surfaces 'Execution reverted with reason: custom error 0x203d82d8' so the dev-friendly path through #82's revertMessage helper is working as designed for this edge.

One small follow-up note from continued live testing: when the chain has been time-warped via evm_increaseTime in QA testing, the buyViaKumbaya flow can hit AnvilKumbayaFixture.sol Expired() (custom error 0x203d82d8) because the frontend's swapDeadline is computed from wall-clock time, not from chain time. On a normal-clock chain (mainnet, MegaETH, normal Anvil tick) this won't matter. On QA stacks with evm_increaseTime in the mix it does. Filing as a context note here rather than a new issue since this is a QA-only artifact, not a user-facing bug. The friendly revert UX panel surfaces 'Execution reverted with reason: custom error 0x203d82d8' so the dev-friendly path through #82's revertMessage helper is working as designed for this edge.
PlasticDigits commented 2026-04-27 07:24:01 +00:00 (Migrated from gitlab.com)

mentioned in issue #83

mentioned in issue #83
Brouie commented 2026-04-28 05:00:36 +00:00 (Migrated from gitlab.com)

mentioned in issue #84

mentioned in issue #84
PlasticDigits (Migrated from gitlab.com) reopened this issue 2026-04-28 05:37:16 +00:00
PlasticDigits commented 2026-04-28 05:43:50 +00:00 (Migrated from gitlab.com)

mentioned in commit e3409cd84c

mentioned in commit e3409cd84c312db4f3b1d190b392057a2e161a71
PlasticDigits commented 2026-04-28 05:44:14 +00:00 (Migrated from gitlab.com)

Follow-up on reopened edge case (lower band)

Merged: e3409cd on main.

What changed

The original #82 fix added 99.5% slack under live max maxCharmWad; live testing showed an occasional lower-band failure (~1.002 CHARM on Path A): min can rise between submit-time re-quote and inclusion, so charmWad could still sit at the old floor and revert.

This update adds symmetric headroom above live min: effective floor = 100.5% of freshly read minCharmWad (CHARM_SUBMIT_LOWER_HEADROOM_BPS = 50, same bps scale as the upper slack). reconcileFreshBuySizingFromReads passes effectiveMinCharmWadForSubmit … effectiveMaxCharmWadForSubmit into finalizeCharmSpendForBuy. If both margins cannot be satisfied (very tight band), the panel returns the existing-style tight band message.

Bare-revert UX: friendlyRevertFromUnknown(..., { buySubmit: true }) copy now suggests nudging the CHARM amount (not only “lower”), matching rare residual edge cases where retry after one block or a small slider move fixes the tx.

Docs / agent cross-links: docs/frontend/timecurve-views.md, docs/testing/invariants-and-business-logic.md (#82 row), docs/integrations/kumbaya.md, skills/verify-yo-timecurve-buy-charm-submit/SKILL.md, skills/README.md, contributor guardrail item 23 (.cursor/skills/yieldomega-guardrails/SKILL.md).

Automated verification (ran locally)

  • cd frontend && npx vitest run src/lib/timeCurveBuySubmitSizing.test.ts src/lib/revertMessage.test.ts — 9/9 pass.

Checklist for @brouie (issue stays open for your sign-off)

  1. Path A / lower edge: With a live-ticking chain, set the slider near minimum CHARM (non-integer OK); BUY CHARM should succeed more reliably than before, or show a clear panel error — not unexplained bare revert.
  2. Upper edge: Reconfirm near-max CHARM behavior unchanged (still covered by 99.5% max slack).
  3. Kumbaya single-tx: Spot-check near-min and near-max with buyViaKumbaya when the router is wired.
  4. Friendly revert: If you still force a bare revert, panel copy should mention band movement / nudge + retry (not only lowering amount).
  5. Optional: Re-run the vitest command above on main.

Thanks — ping when you’ve had a chance to verify.

## Follow-up on reopened edge case (lower band) **Merged:** `e3409cd` on `main`. ### What changed The original #82 fix added **99.5% slack under live max** `maxCharmWad`; live testing showed an occasional **lower-band** failure (~1.002 CHARM on Path A): **min** can rise between submit-time re-quote and **inclusion**, so `charmWad` could still sit at the old floor and revert. This update adds **symmetric headroom above live min**: effective floor = **100.5%** of freshly read `minCharmWad` (`CHARM_SUBMIT_LOWER_HEADROOM_BPS = 50`, same bps scale as the upper slack). `reconcileFreshBuySizingFromReads` passes `effectiveMinCharmWadForSubmit` … `effectiveMaxCharmWadForSubmit` into `finalizeCharmSpendForBuy`. If both margins cannot be satisfied (very tight band), the panel returns the existing-style **tight band** message. **Bare-revert UX:** `friendlyRevertFromUnknown(..., { buySubmit: true })` copy now suggests **nudging** the CHARM amount (not only “lower”), matching rare residual edge cases where retry after one block or a small slider move fixes the tx. **Docs / agent cross-links:** `docs/frontend/timecurve-views.md`, `docs/testing/invariants-and-business-logic.md` (#82 row), `docs/integrations/kumbaya.md`, `skills/verify-yo-timecurve-buy-charm-submit/SKILL.md`, `skills/README.md`, contributor guardrail item **23** (`.cursor/skills/yieldomega-guardrails/SKILL.md`). ### Automated verification (ran locally) - `cd frontend && npx vitest run src/lib/timeCurveBuySubmitSizing.test.ts src/lib/revertMessage.test.ts` — **9/9** pass. ### Checklist for @brouie (issue stays **open** for your sign-off) 1. **Path A / lower edge:** With a live-ticking chain, set the slider near **minimum** CHARM (non-integer OK); **BUY CHARM** should succeed more reliably than before, or show a clear panel error — not unexplained bare revert. 2. **Upper edge:** Reconfirm near-**max** CHARM behavior unchanged (still covered by 99.5% max slack). 3. **Kumbaya single-tx:** Spot-check near-min and near-max with `buyViaKumbaya` when the router is wired. 4. **Friendly revert:** If you still force a bare revert, panel copy should mention band movement / **nudge** + retry (not only lowering amount). 5. **Optional:** Re-run the vitest command above on `main`. Thanks — ping when you’ve had a chance to verify.
Brouie commented 2026-04-28 08:08:46 +00:00 (Migrated from gitlab.com)

mentioned in issue #38

mentioned in issue #38
Brouie commented 2026-04-29 01:21:25 +00:00 (Migrated from gitlab.com)

Live frontend verification on 7b0f37d / e3409cd, items 1 and 2 PASS.

Setup

Live local-anvil stack with #84's kumbaya_local_anvil_env helper applied (Path B from yesterday's #84 close: SKIP_ANVIL_RICH_STATE=1 + YIELDOMEGA_DEPLOY_KUMBAYA=1). Sale LIVE, chain ts caught up to wall on first tx of the session. Two test wallets exercised today: deployer 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (acct #0 with genesis CL8Y) and personal test wallet 0xc46b15f4B56489a16F561c22D5F0BA8bdCa80650 (funded via cast: 1 ETH for gas + 1000 CL8Y mint).

Item 2 — Upper / mid-band buy on deployer wallet

Live band at submit time: 1.23 - 12.3 CL8Y. Slider at ~5.49 CL8Y (5.108 CHARM, near mid-band). Clicked BUY CHARM.

Result: PASS clean.

  • Tx 0x7ae0e780... succeeded
  • charmWeight delta: 0 → 5.108 CHARM
  • Recent Buys row populated, +120s timer extension visible
  • No friendly revert, no stale calldata

The 99.5% upper clamp from CHARM_SUBMIT_UPPER_SLACK_BPS=50 carried the original #82 fix forward as expected.

Item 1 — Lower edge near-MIN buy on personal test wallet

Same stack, same band 1.23 - 12.3 CL8Y. Slider dragged all the way LEFT to lower bound. Submit-time slider value was 1.235871778395428 CL8Y → 1.149 CHARM (essentially riding the live minimum).

Result: PASS clean.

  • Tx 0xdca5eea1... succeeded
  • charmWeight delta: 0 → 1.149 CHARM
  • Recent Buys row populated with DECIMAL 1.235920965893338138 / ABBR 1.24 / +120s
  • No friendly revert at submit, no bare execution-reverted from contract, no nudge needed

This is the case that previously could revert in the original #82 lower-edge repro (band shifts up between re-quote and tx inclusion, leaving charmWad below the new floor). The 100.5% lower headroom from CHARM_SUBMIT_LOWER_HEADROOM_BPS=50 absorbed the drift cleanly. Effective floor at submit was ~1.225 CL8Y (100.5% of the read currentMin), so even though the slider sat right at the displayed live min, the calldata floor margin held.

Item 4 — Friendly revert copy

Earlier in the session, before I noticed MetaMask had stale nonce state from a different wallet account, a couple of attempts reverted at the simulation layer and surfaced the revised friendlyRevertFromUnknown copy:

"This charm size slipped below the live minimum for the current timer state."

Plus an earlier-band variant where the panel suggested nudging the amount up. Both copies match the spec ("nudging the CHARM amount, not only lowering" + "band movement / nudge + retry"). Item 4 PASS.

Item 3 — Kumbaya buyViaKumbaya near-min + near-max

In progress, waiting on per-wallet buy cooldown to clear. Will append evidence here once exercised. Path: switch pay-mode chip to ETH on the same Simple Buy CHARM panel, slider near min then near max, single-signature MetaMask prompt to TimeCurveBuyRouter (0x998abeb...3613E), wallet pays in ETH with the 3% Kumbaya slippage cushion baked into amountIn.

Side observation — Anvil + MetaMask gotcha (not a #82 issue)

When MetaMask's "Activity tab data" is stale from a previous session against the same chain (e.g. 31337 anvil from the day before), the Confirm click can silently no-op without broadcasting a tx. Anvil reports nonce 0 for the wallet, MetaMask's pending-nonce tracker has a different value carried over, the broadcast gets dropped at the wallet layer with no UI feedback. Fix: MetaMask Settings → Advanced → "Clear activity tab data" for the network, OR remove + re-add the network entry. After the reset, all subsequent buys broadcast cleanly.

Worth flagging because users on prod (MegaETH) would only hit this if MM's activity for that chain ID is stale across a chain reset, which shouldn't happen on a live chain. Just a QA-environment artifact for future sessions on rebooted Anvil instances.

Vitest

Re-ran today on 7b0f37d:
cd frontend && npx vitest run src/lib/timeCurveBuySubmitSizing.test.ts src/lib/revertMessage.test.ts → 9/9 pass (5 sizing + 4 revert message).

Will append item 3 evidence after cooldown + Kumbaya path runs.

cc @PlasticDigits

Live frontend verification on 7b0f37d / e3409cd, items 1 and 2 PASS. ## Setup Live local-anvil stack with #84's kumbaya_local_anvil_env helper applied (Path B from yesterday's #84 close: SKIP_ANVIL_RICH_STATE=1 + YIELDOMEGA_DEPLOY_KUMBAYA=1). Sale LIVE, chain ts caught up to wall on first tx of the session. Two test wallets exercised today: deployer 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (acct #0 with genesis CL8Y) and personal test wallet 0xc46b15f4B56489a16F561c22D5F0BA8bdCa80650 (funded via cast: 1 ETH for gas + 1000 CL8Y mint). ## Item 2 — Upper / mid-band buy on deployer wallet Live band at submit time: 1.23 - 12.3 CL8Y. Slider at ~5.49 CL8Y (5.108 CHARM, near mid-band). Clicked BUY CHARM. Result: PASS clean. - Tx 0x7ae0e780... succeeded - charmWeight delta: 0 → 5.108 CHARM - Recent Buys row populated, +120s timer extension visible - No friendly revert, no stale calldata The 99.5% upper clamp from CHARM_SUBMIT_UPPER_SLACK_BPS=50 carried the original #82 fix forward as expected. ## Item 1 — Lower edge near-MIN buy on personal test wallet Same stack, same band 1.23 - 12.3 CL8Y. Slider dragged all the way LEFT to lower bound. Submit-time slider value was 1.235871778395428 CL8Y → 1.149 CHARM (essentially riding the live minimum). Result: PASS clean. - Tx 0xdca5eea1... succeeded - charmWeight delta: 0 → 1.149 CHARM - Recent Buys row populated with DECIMAL 1.235920965893338138 / ABBR 1.24 / +120s - No friendly revert at submit, no bare execution-reverted from contract, no nudge needed This is the case that previously could revert in the original #82 lower-edge repro (band shifts up between re-quote and tx inclusion, leaving charmWad below the new floor). The 100.5% lower headroom from CHARM_SUBMIT_LOWER_HEADROOM_BPS=50 absorbed the drift cleanly. Effective floor at submit was ~1.225 CL8Y (100.5% of the read currentMin), so even though the slider sat right at the displayed live min, the calldata floor margin held. ## Item 4 — Friendly revert copy Earlier in the session, before I noticed MetaMask had stale nonce state from a different wallet account, a couple of attempts reverted at the simulation layer and surfaced the revised friendlyRevertFromUnknown copy: > "This charm size slipped below the live minimum for the current timer state." Plus an earlier-band variant where the panel suggested nudging the amount up. Both copies match the spec ("nudging the CHARM amount, not only lowering" + "band movement / nudge + retry"). Item 4 PASS. ## Item 3 — Kumbaya buyViaKumbaya near-min + near-max In progress, waiting on per-wallet buy cooldown to clear. Will append evidence here once exercised. Path: switch pay-mode chip to ETH on the same Simple Buy CHARM panel, slider near min then near max, single-signature MetaMask prompt to TimeCurveBuyRouter (0x998abeb...3613E), wallet pays in ETH with the 3% Kumbaya slippage cushion baked into amountIn. ## Side observation — Anvil + MetaMask gotcha (not a #82 issue) When MetaMask's "Activity tab data" is stale from a previous session against the same chain (e.g. 31337 anvil from the day before), the Confirm click can silently no-op without broadcasting a tx. Anvil reports nonce 0 for the wallet, MetaMask's pending-nonce tracker has a different value carried over, the broadcast gets dropped at the wallet layer with no UI feedback. Fix: MetaMask Settings → Advanced → "Clear activity tab data" for the network, OR remove + re-add the network entry. After the reset, all subsequent buys broadcast cleanly. Worth flagging because users on prod (MegaETH) would only hit this if MM's activity for that chain ID is stale across a chain reset, which shouldn't happen on a live chain. Just a QA-environment artifact for future sessions on rebooted Anvil instances. ## Vitest Re-ran today on 7b0f37d: cd frontend && npx vitest run src/lib/timeCurveBuySubmitSizing.test.ts src/lib/revertMessage.test.ts → 9/9 pass (5 sizing + 4 revert message). Will append item 3 evidence after cooldown + Kumbaya path runs. cc @PlasticDigits
Brouie commented 2026-04-29 01:30:18 +00:00 (Migrated from gitlab.com)

mentioned in issue #88

mentioned in issue #88
Brouie commented 2026-04-29 01:45:24 +00:00 (Migrated from gitlab.com)

Item 3 (Kumbaya buyViaKumbaya) and remaining verification — PASS. Closing.

Item 3 — Kumbaya path via TimeCurveBuyRouter

Exercised both ETH and USDM pay modes through 0x998abeb3E57409262aE5b751f60747921B33613E (TimeCurveBuyRouter) on the same SKIP+KUMBAYA stack as items 1/2.

ETH path:

  • Near-min (slider 1.152 CHARM, ~0.001282 ETH, ETH live band 0.00124-0.0124): friendly nudge revert displayed correctly. Copy: "The CHARM amount band or onchain price moved between quote and submit. Try nudging the CHARM amount slightly or wait one block and retry." Tx prompt routed to 0x998abeb (router), single signed call, no separate approve.
  • Mid-band attempt earlier in session (slider 2.724 CHARM, 0.003028 ETH): also surfaced the nudge revert, indicating the lower edge of the CHARM bound was being clipped between read and submit. Same nudge copy.

USDM path:

  • Near-min (slider 1.151 CHARM, ~1.247 USDM, USDM live band 1.25-12.5): friendly nudge revert displayed. Same nudge copy as ETH path.
  • Near-max / mid (slider 6.335 CHARM, ~6.866 USDM): SUCCESS clean. Tx 0xf8d8044c... mined, charmWeight delta on 0xc46b15...0650 went from 1.149 to 7.483 (added 6.334 CHARM, matching slider intent). +120s overlay, Recent Buys row populated, Buy cooldown engaged.

The Kumbaya 3% max slippage cushion + the #82-followup CHARM_SUBMIT_LOWER/UPPER_HEADROOM_BPS=50 clamps work in concert: lower-edge attempts that drift below the floor surface the friendly nudge copy at the helper layer (timeCurveBuySubmitSizing), and mid-band buys execute clean.

Item 4 — Friendly revert copy

Two distinct copies surfaced in the session:

  1. "This charm size slipped below the live minimum for the current timer state." — earlier CL8Y direct buy attempt during MM nonce-drift situation.
  2. "The CHARM amount band or onchain price moved between quote and submit. Try nudging the CHARM amount slightly or wait one block and retry." — Kumbaya near-min attempts (both ETH and USDM).

Both copies match revertMessage.ts expectations. Both surface band-shift directional language with a nudge cue, not "try a smaller amount." Both have a dismiss button.

Confirmation summary across all 4 items

  • Item 1 — Lower edge near-MIN buy on CL8Y direct path: PASS (1.149 CHARM purchased clean, slider at live min, lower-headroom clamp absorbed band drift)
  • Item 2 — Upper/mid-band buy on CL8Y direct path: PASS (5.108 CHARM purchased clean, mid-band, no friendly revert)
  • Item 3 — Kumbaya buyViaKumbaya on ETH and USDM paths: PASS (mid/high-band purchased clean via router; near-min surfaced expected friendly nudge revert via simulation guard)
  • Item 4 — Friendly revert copy: PASS (both copy variants observed, band-shift directional language confirmed)

Plus vitest 9/9 PASS on the helpers.

Closure recommendation

Closing #82. The lower-band CHARM headroom fix on e3409cd ships behavior matching the spec. Live verification confirms the helper clamps absorb band drift between slider read and tx inclusion, and the friendly revert UX surfaces correct directional language when the band moves past the clamp window.

cc @PlasticDigits

Item 3 (Kumbaya buyViaKumbaya) and remaining verification — PASS. Closing. ## Item 3 — Kumbaya path via TimeCurveBuyRouter Exercised both ETH and USDM pay modes through 0x998abeb3E57409262aE5b751f60747921B33613E (TimeCurveBuyRouter) on the same SKIP+KUMBAYA stack as items 1/2. ETH path: - Near-min (slider 1.152 CHARM, ~0.001282 ETH, ETH live band 0.00124-0.0124): friendly nudge revert displayed correctly. Copy: "The CHARM amount band or onchain price moved between quote and submit. Try nudging the CHARM amount slightly or wait one block and retry." Tx prompt routed to 0x998abeb (router), single signed call, no separate approve. - Mid-band attempt earlier in session (slider 2.724 CHARM, 0.003028 ETH): also surfaced the nudge revert, indicating the lower edge of the CHARM bound was being clipped between read and submit. Same nudge copy. USDM path: - Near-min (slider 1.151 CHARM, ~1.247 USDM, USDM live band 1.25-12.5): friendly nudge revert displayed. Same nudge copy as ETH path. - Near-max / mid (slider 6.335 CHARM, ~6.866 USDM): SUCCESS clean. Tx 0xf8d8044c... mined, charmWeight delta on 0xc46b15...0650 went from 1.149 to 7.483 (added 6.334 CHARM, matching slider intent). +120s overlay, Recent Buys row populated, Buy cooldown engaged. The Kumbaya 3% max slippage cushion + the #82-followup CHARM_SUBMIT_LOWER/UPPER_HEADROOM_BPS=50 clamps work in concert: lower-edge attempts that drift below the floor surface the friendly nudge copy at the helper layer (timeCurveBuySubmitSizing), and mid-band buys execute clean. ## Item 4 — Friendly revert copy Two distinct copies surfaced in the session: 1. "This charm size slipped below the live minimum for the current timer state." — earlier CL8Y direct buy attempt during MM nonce-drift situation. 2. "The CHARM amount band or onchain price moved between quote and submit. Try nudging the CHARM amount slightly or wait one block and retry." — Kumbaya near-min attempts (both ETH and USDM). Both copies match revertMessage.ts expectations. Both surface band-shift directional language with a nudge cue, not "try a smaller amount." Both have a dismiss button. ## Confirmation summary across all 4 items - Item 1 — Lower edge near-MIN buy on CL8Y direct path: PASS (1.149 CHARM purchased clean, slider at live min, lower-headroom clamp absorbed band drift) - Item 2 — Upper/mid-band buy on CL8Y direct path: PASS (5.108 CHARM purchased clean, mid-band, no friendly revert) - Item 3 — Kumbaya buyViaKumbaya on ETH and USDM paths: PASS (mid/high-band purchased clean via router; near-min surfaced expected friendly nudge revert via simulation guard) - Item 4 — Friendly revert copy: PASS (both copy variants observed, band-shift directional language confirmed) Plus vitest 9/9 PASS on the helpers. ## Closure recommendation Closing #82. The lower-band CHARM headroom fix on e3409cd ships behavior matching the spec. Live verification confirms the helper clamps absorb band drift between slider read and tx inclusion, and the friendly revert UX surfaces correct directional language when the band moves past the clamp window. cc @PlasticDigits
Brouie (Migrated from gitlab.com) closed this issue 2026-04-29 01:45:26 +00:00
Brouie commented 2026-04-29 04:58:45 +00:00 (Migrated from gitlab.com)

mentioned in issue #58

mentioned in issue #58
Brouie commented 2026-04-29 06:08:51 +00:00 (Migrated from gitlab.com)

mentioned in issue #39

mentioned in issue #39
PlasticDigits commented 2026-04-30 05:25:10 +00:00 (Migrated from gitlab.com)

mentioned in issue #100

mentioned in issue #100
PlasticDigits commented 2026-05-19 06:26:06 +00:00 (Migrated from gitlab.com)

mentioned in issue #216

mentioned in issue #216
PlasticDigits commented 2026-05-20 04:30:23 +00:00 (Migrated from gitlab.com)

mentioned in issue #224

mentioned in issue #224
PlasticDigits commented 2026-05-21 04:38:41 +00:00 (Migrated from gitlab.com)

mentioned in issue #227

mentioned in issue #227
PlasticDigits commented 2026-05-29 08:06:26 +00:00 (Migrated from gitlab.com)

mentioned in issue #264

mentioned in issue #264
PlasticDigits commented 2026-06-08 08:16:57 +00:00 (Migrated from gitlab.com)

mentioned in issue #303

mentioned in issue #303
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
code/yieldomega#82
No description provided.