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