fix(eip1193): keep the active wallet's address when another wallet changes network (wagmi mode unaffected)
Scope: EIP-1193 mode only. Wagmi mode is not affected.
This bug and this fix are only in the EIP-1193 / EIP-6963 autocapture path (no wagmi option). In wagmi mode (options.wagmi = { config, queryClient }), the SDK reads the connected account and chain from wagmi state and never attaches the chainChanged listener this PR changes (trackEIP1193Provider() is not called in wagmi mode). Apps on wagmi mode keep the correct address on every event before and after this change, and need no action.
Problem
In EIP-1193 mode, a chainChanged from a wallet that is not the active one went through handleProviderMismatch(). That took the active slot and cleared the active wallet's address and chain. A guard existed, but only when autocapture.chain is false, and chain autocapture is on by default.
With two extensions installed (for example Rabby and MetaMask), one network switch fires chainChanged on both. After that, every track() went out with no address until the next connect or identify. Autocaptured transaction events were not affected (they take the address from the request), so the gap only shows on app events.
Fix
In EvmEventTracker.onChainChanged, a chain event from a provider other than the active one is now observation only. Its chain is still remembered, so a later signature through that wallet is labelled correctly. Nothing else changes. handleProviderMismatch() has no callers left and is removed.
A real wallet switch still arrives through accountsChanged from the other wallet, which moves the slot.
Not changed: while no provider is active yet (a wallet set by the cookie, identify() or syncWalletState()), a chain event still claims the slot when chain autocapture is on, as on main. That is how such a wallet associates and moves central state off an excluded chain. I tried making that path observation only as well; it broke "retries detection when EIP-1193 leaves an excluded chain" (events stay suppressed), so it stays as it was and is documented in the code.
Behaviour change: if a user switches wallets and the new wallet sends only chainChanged (no account signal), events keep the previous address until the new wallet sends accountsChanged. A chain change does not say who the user is.
Tests
Unit (pnpm test: 1541 passing, 0 failing, 0 pending):
chainIdResolution: with default options, another wallet's chain change keeps the active provider, address and chain, emits nochainevent, andtrack()keeps the address. Fails on main. The active wallet's own chain change is still reported.duplicateConnect: the same through real provider listeners andtrack()(fails on main); a real switch throughaccountsChangedstill works after another wallet's chain change (fails on main, because there the chain change already moved the slot). The existing "does not let a background EVM chain change steal the slot from a live Solana wallet" still passes and covers the removed Solana special case.- "drops a displaced provider's record on a chain-driven switch" asserted the old behaviour and is replaced. Two race tests used another wallet's chain change only to make a third wallet active; they now use
connect(), which takes its own observation, so the stale-switch guard is still exercised. (KeepingproviderC.emit("connect")alone times out: aconnectfrom a non-active wallet is not adopted while another wallet holds the slot.)
E2E (pnpm test:e2e: 16 of 16):
- New
twowalletsscenario where the active wallet answerseip6963:requestProviderlike a real extension.track()after the other wallet's chain change:track(Swap Completed)@-/0x5137on this branch,@-/-on published 1.42.0.
Browser (pnpm test:browser with anvil): all checks pass, including switch to B.
Example app (examples/with-react, EIP-1193 mode, real browser, two mock EIP-6963 extensions that answer requestProvider): connect Rabby, MetaMask switches network, then track("Swap Completed").
- published 1.42.0:
track | Swap Completed | - | -(no address) - this branch:
track | Swap Completed | - | 0x51377eā¦(correct)
Not in this PR
A wallet seen only through window.ethereum (not in the EIP-6963 announcement list) loses its active slot when another wallet announces later (cleanupUnavailableProviders() ā untrackProvider() ā clearProvider()). The address is kept, but the next chain event from the other wallet can take the now empty slot and relabel the chain. Separate and older; follow-up.
š¤ Generated with Claude Code