Signatures fail because the wallet is on the wrong chain
“Users sign successfully, but the server rejects the signature or the transaction goes to a different network than the app expects.”
ERROR CODES YOU MAY SEE
A code is a clue. Use its meaning and surrounding evidence to narrow the cause.
4902MetaMask wallet RPC errors · platformwallet_switchEthereumChain was called with a chain ID the wallet does not know.
Add the chain with wallet_addEthereumChain (EIP-3085), then switch. Other wallets may use different codes, so also handle unknown errors from the switch call.
Official reference for 4902 (opens in new tab)Code definition reviewed4901EIP-1193 provider errors · standardThe provider is not connected to the requested chain.
Read eth_chainId from the provider and listen for chainChanged before signing; ask the wallet to switch instead of assuming the active chain.
Official reference for 4901 (opens in new tab)Code definition reviewed4001EIP-1193 provider errors · standardThe user rejected the request.
Treat it as a user decision, not a failure to retry. If it follows a chain switch or signature prompt, check that the prompt showed the expected chain and payload.
Official reference for 4001 (opens in new tab)Code definition reviewed
THE PRINCIPLE
A signature is only valid for the domain it was made in. If the client chooses the chain ID, the client chooses what your server accepts.
FIRST MOVES
- Read eth_chainId and subscribe to chainChanged; never assume the wallet stayed where you left it.
- Call wallet_switchEthereumChain before signing; on 4902 (MetaMask), add the chain via wallet_addEthereumChain and retry.
- Build the EIP-712 domain on the server, including chainId and verifyingContract; EIP-712 says wallets should refuse a chainId that does not match the active chain.
- For plain personal_sign messages, put the chain ID in the message (SIWE does) and check it server-side, plus a nonce and expiry against replay.
- Verify with a client for the expected chain: smart accounts validate via ERC-1271 on that chain, and undeployed ones need ERC-6492 handling.
- Transactions carry an EIP-155 chain ID, so a wrong-chain transaction is a real transaction on that other chain, not a failed one.
TOOLS: THEN → NOW
PATTERN SNAPSHOT
// Client: switch first; add the chain if the wallet does not know it.
await walletClient.switchChain({ id: base.id }).catch(async (err) => {
if (err.code !== SwitchChainError.code) throw err; // 4902
await walletClient.addChain({ chain: base });
});
// Server: domain comes from config, not the request body.
const ok = await basePublicClient.verifyTypedData({
address: claimedSigner, // EOA or smart account (ERC-1271 / ERC-6492)
domain: { name: "Orders", version: "1", chainId: base.id, verifyingContract },
types, primaryType: "Order", message, signature,
});
if (!ok) throw new Error("invalid signature for expected chain");CLOSE THE AI. EXPLAIN THIS.
Why can a smart-account signature verify on one chain and fail on another, even with identical bytes?HOW IT WORKS UNDERNEATH
RELATED SYMPTOMS
SOURCES
Guide reviewed