Wrong-chain submit: BUY CHARM not gated on chainId match — frontend ships calldata to whatever chain MetaMask is on (caught by Blockaid only)
## Summary When MetaMask is on a chain other than `VITE_CHAIN_ID` (31337 in QA, MegaETH chain id at TGE), the **BUY CHARM** primary action remains **clickable** on `/timecurve`. The frontend builds the buy calldata using the Anvil TimeCurve proxy address (`0x59b670...`) and ships it to MetaMask on whatever chain is currently active. The "Wrong Network" pill in the header is **informational only** — it does not gate the buy action. ## Repro 1. Stack `282eb62` up (SKIP+KUMBAYA+NO_COOLDOWN), Vite on 5173 2. Connect MetaMask wallet (deployer or any) on Chain 31337, sale live 3. Switch MetaMask network from Chain 31337 to **BNB Chain** (or any non-target chain — Ethereum, Polygon, Base all work the same) 4. Header pill flips to "Wrong Network" alongside "NETWORK CHAIN 31337" 5. Click **BUY CHARM** 6. MetaMask transaction popup opens ## Observed MetaMask popup shows: - Sending: `0 BNB` - To: ⚠ **Malicious address** (Blockaid externally flagged) - Network: BNB Chain (the wrong network) - Request from: `HTTP 127.0.0.1:5173` - Estimated changes: No changes - "Review alerts" button greyed out (Blockaid blocking confirmation) In other words, the frontend built calldata against the local Anvil TimeCurve proxy address `0x59b670e9fA9D0A427751Af201D676719a970857b` and shipped it to MetaMask on **BSC mainnet**. Blockaid recognized that address slot as malicious / empty on BSC and saved the user. ## Why this is a real issue Two things prevented worst-case here: 1. MetaMask's external Blockaid integration flagged the BSC destination. Not all users have Blockaid, and not all chain mismatches will trigger a flag. 2. The same address slot on BSC happens to be empty / scam-flagged. On a different chain the address might map to a legitimate unrelated contract (or attacker-deployed honeypot at the same deterministic address), and the tx would not be flagged. The actual safety gap is: **the Buy CHARM CTA is not gated on chainId match**. The "Wrong Network" pill is a passive label, not a gate. A user with non-Blockaid wallet, or on a different wrong chain where the address slot resolves to something unflagged, could submit a buy interaction to the wrong chain and: - waste gas on a revert (best case), - or trigger an unintended interaction at the same deterministic address slot on that other chain (worst case), - or — if the user previously granted token approvals on that address slot on the wrong chain — have funds pulled. The MegaETH TGE risk surface includes wrong-chain attempts because users will be juggling MegaETH + BSC + ETH mainnet in the same wallet to bridge in. A wrong-chain BUY CHARM click is a plausible everyday user mistake at TGE, not an edge case. ## Suggested resolution options **Option A — hard-disable the BUY CHARM button when chainId mismatches.** Standard pattern. Replace the button's onClick handler with a chain-mismatch state that shows "Switch to Chain X" copy and either: - A passive "switch in your wallet" prompt, OR - An active "Switch network" CTA that calls `wallet_switchEthereumChain` with the configured `VITE_CHAIN_ID`. **Option B — wrap the submit handler with a chainId pre-flight.** Click works on wrong chain, but submit logic does `if (chainId !== expectedChainId) { promptSwitch(); return; }` before building calldata. **Option C — render a chain-mismatch overlay on the entire `/timecurve` Simple panel + Arena Buy panel + any other write-action surface** (referrals register, kumbaya buy, sir buy, vesting claim) that blocks interaction until chains match. Option A is the strongest user signal. Option B is the cheapest to ship. Option C is the most defensive across all surfaces. Combination of A + the same gating applied to every write surface would be the safest. The same gate should apply to: - `/timecurve` BUY CHARM (Simple) - `/timecurve/arena` Buy CHARM (Arena) - `/referrals` Register & burn CL8Y - `/vesting` Claim DOUB (per #92) - `/kumbaya` outbound submit (if/when wired) - `/sir` outbound submit (if/when wired) Anywhere the user signs a tx, chainId match should be a precondition. ## Severity **MEDIUM-HIGH.** Not a launch-blocker because Blockaid catches the BSC case + the address slot pattern is mostly empty on common L1s, but a real safety / trust-surface gap. Materially more severe than #93 because this affects safety, not just polish. ## Reference Caught during F-13/F-14 walkthrough on `/timecurve` Simple. Reproducible deterministically from steps above. cc @PlasticDigits
issue