Skip to content

switchChainAsync resolves inconsistently with MetaMask chain switch state #4600

Open
@matteocelani

Description

@matteocelani

Check existing issues

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:

  1. 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
  2. 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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions