← Back to the library
Web3Applied · 8 min

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 · platform

wallet_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 reviewed
4901EIP-1193 provider errors · standard

The 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 reviewed
4001EIP-1193 provider errors · standard

The 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

  1. Read eth_chainId and subscribe to chainChanged; never assume the wallet stayed where you left it.
  2. Call wallet_switchEthereumChain before signing; on 4902 (MetaMask), add the chain via wallet_addEthereumChain and retry.
  3. 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.
  4. 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.
  5. Verify with a client for the expected chain: smart accounts validate via ERC-1271 on that chain, and undeployed ones need ERC-6492 handling.
  6. 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

ecrecover onlyVerifiers that handle EOAs, ERC-1271, and ERC-6492 (viem verifyTypedData / verifyMessage)
Asking users to add networks manuallywallet_switchEthereumChain with wallet_addEthereumChain fallback

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

SOURCES

Guide reviewed