Chain Security 7 min read

How Chain-Mismatch Drains Happen — and Why Your Wallet Doesn't Stop Them

The anatomy of a chain-mismatch drain: stale RPC returns wrong chain ID, approval goes to the wrong network, funds gone before the next block. And why every wallet you've used has already failed this check.

You're moving funds from Base to Arbitrum. Your wallet is connected to an RPC you added months ago — one that's been running without incident. You initiate the transfer, your hardware wallet shows the destination address, you confirm. The transaction goes through. The tokens arrive somewhere, but not on Arbitrum. They settled on Ethereum mainnet, against your intent, because the RPC your wallet trusted was silently reporting the wrong chain ID.

This is not hypothetical. Chain-mismatch drains follow a pattern that repeats every few months across different traders, different chains, different amounts. The mechanism is always the same: a stale or adversarial RPC endpoint returns a chain ID that doesn't match where you think you're transacting, and the wallet never checks.

How chain ID gets into your transaction

EIP-155, merged in 2016, added chain ID into the transaction signing hash to prevent replay attacks between networks. Before EIP-155, a signed transaction on Ethereum could be replayed on Ethereum Classic. The fix was to include chainId as a parameter in the signing input, making the signature network-specific.

When you sign a transaction today, your wallet constructs an RLP-encoded structure that includes chainId alongside nonce, gas price, gas limit, to, value, and data. The signature covers all of it. A transaction signed with chain ID 8453 (Base) is cryptographically invalid on chain ID 42161 (Arbitrum One) — at the protocol level, replays don't work.

The problem is not replay. The problem is initial signing on the wrong chain entirely. If your wallet asks your RPC "what chain are you on?" and the RPC returns 1 (Ethereum mainnet) instead of 8453, your wallet constructs a transaction bound to Ethereum mainnet. You sign it. It gets broadcast. It lands on mainnet, not Base. Your funds move — to the right address, on the wrong network.

What "stale RPC" actually means in practice

An RPC endpoint going stale is not always a security incident. Infrastructure misconfiguration is far more common than active poisoning. Consider a scenario like this: a trader running an active multi-chain flow across Base and Arbitrum configured their wallet with a private RPC node operated by a small provider. That node's sync lag grew during a period of high network activity, and for several hours it was responding to eth_chainId with a cached value from a different network context it had briefly connected to during an internal failover. The trader executed three transactions during that window. The wallet accepted the stale chain ID without verification.

The issue is that most wallets call eth_chainId once at connection time and cache it. They do not re-verify before each signing operation. If the endpoint is inconsistent — returning different values across calls, or stuck on a stale value — the wallet's cached version drifts from reality silently.

Adversarial poisoning is a harder scenario but the same mechanism. A malicious RPC endpoint simply returns whatever chainId produces the drain outcome the attacker wants. Since wallets don't verify that the returned chain ID matches the network's actual on-chain behavior, there's nothing to detect the mismatch at signing time.

The wallet_switchEthereumChain gap

EIP-3326 introduced wallet_switchEthereumChain as a way for dApps to request chain switches. The spec puts the burden of verification on the wallet, requiring it to validate the chain ID against a known registry or at minimum against the current connection before switching. In practice, most wallet implementations accept the switch if the user confirms, without verifying that the target RPC actually serves that chain.

This creates a gap that attackers can use. A malicious dApp can call wallet_switchEthereumChain with a fake entry point: correct chain ID in the metadata, but an RPC URL that actually serves a different chain. The wallet displays the switch request with the legitimate chain name and ID, user confirms, and the wallet is now connected to a network that does not correspond to its displayed identity.

We're not saying that wallets are being careless — the standard itself doesn't mandate RPC-side verification of chain ID consistency. The gap is a spec-level assumption that RPC providers are trusted and accurate, which is not an assumption that holds in a permissionless network where anyone can run an endpoint.

Two patterns where this has surfaced

The first pattern involves third-party RPC aggregators with automatic failover. When the primary endpoint is unhealthy, the aggregator routes to a fallback. If the fallback's eth_chainId response diverges from what the primary was serving — due to a configuration error, not malice — the wallet session continues with the wrong chain ID cached. Transactions signed in this window are bound to the wrong chain. The trader sees a successful transaction hash but the funds have landed on the wrong network.

The second pattern is simpler and more common: traders who use wallet configurations copied from community resources — Discord posts, GitHub gists, tutorial articles — without verifying that the RPC URLs in those configs are still accurate or trustworthy. RPC URLs posted publicly can be replaced with malicious alternatives. The original author's endpoint gets taken over, or the URL resolves to a new operator. The chain ID returned changes. Wallets that cached the original chain ID across sessions may sign consistently, but new sessions pick up the wrong value.

What verification at signing time looks like

The correct approach is to verify chain ID not at connection time but immediately before constructing the signing payload. This requires a live eth_chainId call to the configured RPC endpoint, compared against the expected chain ID for the current operation, within the same request-response window as the signing request. If the returned value doesn't match, the signing operation should not proceed.

Chain Chain ID (decimal) Chain ID (hex)
Ethereum Mainnet 1 0x1
Arbitrum One 42161 0xa4b1
Base 8453 0x2105
Arbitrum Nova 42170 0xa4ba

The RPC response to eth_chainId should be compared against this expected value before any signing operation proceeds — not cached from session start. This is the verification gap that most current wallet implementations leave open. If the comparison fails, the signing surface should surface a clear error, not a warning the user might dismiss.

The other mitigation layer is RPC health monitoring with chain ID consistency checks across multiple calls. A single eth_chainId call can return the correct value by chance even on a misconfigured endpoint. Checking across two or three independent calls in the same session, and flagging variance, catches the inconsistent endpoint behavior that causes the most common drain scenario.

None of this requires trusting the wallet provider's judgment about which RPCs are safe. It requires only that the chain ID reported by the configured endpoint matches the chain ID you expect, checked at the moment you sign — not earlier, not from cache.

Share this article