[#138] Finding 6: submitBuy — detect wallet/account switch mid multi-step flow #144
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:open_issues
agents
agent:security_audit
agent:verify
area
timecurve
arena
arena-v2
audit
block:feature_review
block:security
bug
contracts
design
devops
docs
documentation
enhancement
epic
feature
frontend
indexer
low priority
megaeth
operations
ops
performance
QA
qa checklist
referrals
removal
security
smart-contracts
tech-debt
testing
time-arena
timecurve
visual-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
code/yieldomega#144
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parent: Split from #138 — Pre-deploy review pass 2 (Finding 6).
Severity: Low–Medium
Problem
useTimeCurveSaleSession.tssubmitBuy(multi-step WETH/USDM wrap, allowances, Kumbaya swap, CL8Y allowance, buy) capturesaddressonce at invocation and reuses it across multiple asyncwriteContractAsync+waitForTransactionReceiptsteps.If the user switches accounts mid-flow (disconnect/reconnect or wallet account switch), later txs are signed by the new account while recipient / allowance reads may still reflect the old address — CL8Y can route to the wrong wallet, balances mismatch, reverts are opaque.
References
frontend/src/pages/timecurve/useTimeCurveSaleSession.ts(submitBuy~L861–1050)docs/frontend/wallet-connection.mdfrontend/src/lib/revertMessage.ts(friendlyRevertFromUnknown)docs/testing/invariants-and-business-logic.md§ wallet chain writes.cursor/skills/yieldomega-guardrails/SKILL.mdRecommended fix
awaitboundary, re-readuseAccount()address +chainId(pass getters into callback or subscribe to wagmi store).Acceptance criteria
submitBuynever silently completes with mixed wallets.Verification checklist
mentioned in issue #138
mentioned in commit
9c723e66abFix merged (main @
9c723e6)Implemented INV-BUY-SESSION-144:
useTimeCurveSaleSession.submitBuyand ArenahandleBuylatchgetAccount(wagmi)right afterreadFreshTimeCurveBuySizing, thenassertWalletBuySessionUnchangedafter each async step. SamesessionSnapshotis passed intosubmitKumbayaSingleTxBuyfor internal awaits. User copy: Wallet or network changed during purchase — please retry from the beginning.@brouie — please verify on a real wallet (Rabby/MetaMask).
Checklist for you
buyViaKumbaya: if USDM needs approve + router tx, switch account between the two → expect abort.cd frontend && npm test—walletBuySessionGuard.test.tsgreen.Docs: wallet-connection §144, invariants §144, manual QA #144, play-timecurve-doubloon skill, contributor index skills/README.md.
Issue left open for your sign-off.
mentioned in commit
0f7b9a4e34mentioned in issue #143
@PlasticDigits — verification PASS on
9c723e6. walked your checklist:6.
npx vitest run walletBuySessionGuardgreen: 7/7 PASS — covers connected-match, case-insensitive address, disconnected, reconnecting, account switch, chain switch, missing-address-with-connected-status.1-5. real-wallet smoke (Rabby/MetaMask account switch + Kumbaya multi-step + chain switch + happy path) skipped this round — no live wallet flow this session. source verified comprehensively:
useTimeCurveSaleSession.tsL915:captureWalletBuySession(wagmiConfig)taken BEFORE any async work, immediately after sizing read.guardBuySession()call sites between L961-L1091 — every wallet/RPC await in the multi-step flow guarded (allowance reads, ETH wrap, USDM swap, CL8Y approve, buy).timeCurveKumbayaSingleTx.ts6assertWalletBuySessionUnchangedcall sites between L88-L126 — internal Kumbaya path also guarded with the same snapshot passed in as parameter (single source of truth).walletBuySessionGuard.tsprovidesWalletBuySessionSnapshot {address, chainId},captureWalletBuySession(config),walletBuySessionDriftFromState,assertWalletBuySessionUnchangedthrowing stable copyWALLET_BUY_SESSION_DRIFT_MESSAGE.buySessionSnapshotsnapshot also drivesArena handleBuyper dev comment.account switch or chain switch between any pair of awaits will throw the stable drift message instead of completing with mixed wallets. abort surface is explicit, not generic 'execution reverted'.
docs §144 anchors in
wallet-connection.md,invariants-and-business-logic.md,manual-qa-checklists.mdall read cleanly.ready for close on your side.
@Brouie We do need to run real wallet tests, must add live wallet flow for this checklist
mentioned in issue #150
mentioned in merge request !374
@PlasticDigits — walked the live wallet flow on QA-server stack post-#150 merge. spent the last hour actually trying to trigger the abort scenarios with real wallets. here's what I hit:
happy path (scenario 5) — PASS
deployer
0xf39F...2266: bought 0.001 ETH through /timecurve Simple. reset MetaMask activity once for nonce sync after the stack restart. tx signed cleanly, CHARM credited. confirmed in YOUR STAKE AT LAUNCH panel — wallet now holds 30.51 CHARM / 36.67 CL8Y after a few buys.anvil#1
0x70997...79C8: switched account in MetaMask (between buys, not during one), connected, bought 0.001 ETH through Simple. tx signed cleanly, holds 3.571 CHARM. live ticker confirms both wallets' buys.so the buy flow itself is healthy on multiple accounts.
scenarios 1-4 abort attempts — could not reach the trigger
tried scenario 1 first (account switch mid-flow on Simple). starting position: deployer connected, on /timecurve.
attempt 1 (MetaMask):
attempt 2 (Rabby — switched wallet to see if the constraint was MetaMask-specific):
both wallets enforce the same UX: active account is locked during a pending signature popup. the user literally cannot perform the switch the spec describes between the BUY CHARM click and the signing event. they have to cancel.
attempt 3 (chain switch, scenario 4):
what I think this means
the source guard is correct (your 7/7 walletBuySessionGuard vitest coverage proves it would fire if the wagmi state changed mid-flow). but the trigger pattern in scenarios 1-4 — "switch account before next signature" on a single-popup buy — is structurally unreachable through normal wallet UI. the wallet itself enforces what the dApp guard would catch.
where the guard WOULD live-fire on real wallet UX is on multi-popup buy flows where there's a window between popups, or disconnect → reconnect mid-flow. the Simple single-popup path doesn't expose that window.
flagging as testable-via-multi-popup-only on real wallets. contract + unit test layer guarantees stand for the single-popup case since the wallet UX itself enforces what the guard would catch.
{{deleted - closing}}
mentioned in issue #155
mentioned in issue #224
mentioned in issue #263
mentioned in issue #264
mentioned in issue #284
mentioned in issue #325