Open
Description
Check existing issues
- I checked there isn't already an issue for the bug I encountered.
Describe the bug
There is an inconsistency in the behavior of switchChainAsync
when used with MetaMask. The function resolves before the actual chain switch is completed, leading to a mismatch between the Promise resolution and the actual chain state.
Key observations:
-
Timing:
switchChainAsync
resolves immediately after sending the request to MetaMask- The actual chain switch in MetaMask happens several seconds later
- This creates a gap between Promise resolution and chain switch completion
-
State Management:
- UI updates show the new chain immediately
- Hook values (
useChainId
,useAccount
) maintain previous chain values - Console logs demonstrate the state mismatch during the switch process
Link to Minimal Reproducible Example
https://stackblitz.com/edit/new-wagmi-hrpyucft?file=src%2FApp.tsx
Steps To Reproduce
import { useState, useEffect } from 'react';
import { useChainId, useSwitchChain, useAccount } from 'wagmi';
import { arbitrum, base } from 'wagmi/chains';
export default function App() {
const chainId = useChainId();
const { chain: accountChain } = useAccount();
const { switchChainAsync } = useSwitchChain();
const [isLoading, setIsLoading] = useState(false);
// Monitor chain changes
useEffect(() => {
console.log('Chain ID from useChainId:', chainId);
console.log('Chain ID from useAccount:', accountChain?.id);
}, [chainId, accountChain]);
const testAsyncBehavior = async (targetChainId: number) => {
console.log('=== Test Async Behavior Start ===');
console.log('T0 - Initial state:');
console.log('- ChainId from useChainId:', chainId);
console.log('- ChainId from useAccount:', accountChain?.id);
const switchPromise = switchChainAsync({ chainId: targetChainId });
console.log('T1 - Switch request sent to wallet');
const result = await switchPromise;
console.log('T3 - switchChainAsync resolved with:', result);
console.log('T4 - State after await:');
console.log('- ChainId from useChainId:', chainId);
console.log('- ChainId from useAccount:', accountChain?.id);
return result;
};
const handleSwitchChain = async () => {
try {
setIsLoading(true);
if (!chainId) return;
const targetChainId = chainId === base.id ? arbitrum.id : base.id;
await testAsyncBehavior(targetChainId);
} catch (error) {
console.error('Chain switch error:', error);
} finally {
setTimeout(() => {
if (isLoading) setIsLoading(false);
}, 5000);
}
};
return (
<div>
<button onClick={handleSwitchChain} disabled={isLoading}>
{isLoading
? 'Switching...'
: `Switch to ${chainId === base.id ? 'Arbitrum' : 'Base'}`}
</button>
<div>
<p>Current Chain (useChainId): {chainId === base.id ? 'Base' : 'Arbitrum'}</p>
<p>Current Chain (useAccount): {accountChain?.id === base.id ? 'Base' : 'Arbitrum'}</p>
</div>
</div>
);
}
Console output showing the issue:
=== Test Async Behavior Start ===
T0 - Initial state:
- ChainId from useChainId: 8453
- ChainId from useAccount: 8453
T1 - Switch request sent to wallet
T3 - switchChainAsync resolved with: {id: 42161, name: 'Arbitrum One', ...}
T4 - State after await:
- ChainId from useChainId: 8453 // Still showing old chain
- ChainId from useAccount: 8453 // Still showing old chain
What Wagmi package(s) are you using?
wagmi
Wagmi Package(s) Version(s)
wagmi@2.14.15
Viem Version
2.24.1
TypeScript Version
5.7.3
Anything else?
Using:
- MetaMask as wallet
- Testing performed with Base (8453) and Arbitrum (42161) networks
The issue affects the reliability of chain-switching logic in dApps, as developers cannot depend on the Promise resolution to accurately reflect the actual chain state.
Metadata
Metadata
Assignees
Labels
No labels