'Ethers.js returns the same wallet address even if I switch accounts

I'm using Ethers.js to allow users to connect their Metamask wallets to my app. Here's the code that I have:

import { ethers } from "ethers"

async function connect() {
    const provider = new ethers.providers.Web3Provider(window.ethereum, "any")
    await provider.send("eth_requestAccounts", [])

    const signer = provider.getSigner()

    const address = await signer.getAddress()

    // Always prints the address that I first connected with
    console.log(address)
}

The issue is that once I have connected one of my Metamask accounts, then I always get its wallet address even if I switch to another Metamask account and try to connect it as well.

Why is that and how should I fix this?



Solution 1:[1]

Correct answer:

To get the current account, get the 0 address of eth_requestAccounts:

let accounts = await provider.send("eth_requestAccounts", []);
let account = accounts[0];

To update it automatically, listen to the accountsChanged event:

provider.on('accountsChanged', function (accounts) {
    account = accounts[0];
});

Code:

import { ethers } from "ethers"

async function connect() {
    const provider = new ethers.providers.Web3Provider(window.ethereum, "any");
    let accounts = await provider.send("eth_requestAccounts", []);
    let account = accounts[0];
    provider.on('accountsChanged', function (accounts) {
        account = accounts[0];
        console.log(address); // Print new address
    });

    const signer = provider.getSigner();

    const address = await signer.getAddress();

    console.log(address);
}

Old (incorrect) answer:

I believe that the issue is that you need to re-instantiate both the signer and the provider when the account is switched.

In addition, if I read the docs correctly, it's better practice to instantiate the signer after eth_requestAccounts is successfully called.

Solution 2:[2]

async function connect() {

    try{
         // detecting provider and requesting metamask usually handled in separate function but I m displaying on your implementation
         const provider = new ethers.providers.Web3Provider(window.ethereum, "any")
         if (provider){
              window.ethereum?.request({ method: "eth_requestAccounts" });
         const signer = provider.getSigner()
         const address = await signer.getAddress()
         
          }        
    
        // Always prints the the address that I first connected with
        console.log(address)
    }catch(){
       // handle the error
    }}

The problem is you are not detecting account change. Assuming you are storing window.ethereum as useState, you could write this useEffect

useEffect(() => {
      ethereum?.on("accountsChanged", handleAccountChange);
      return () => {
        ethereum?.removeListener("accountsChanged", handleAccountChange);
      };
    });

then:

const handleAccountChange = (...args) => {
      // you can console to see the args
      const accounts = args[0] ;
      // if no accounts that means we are not connected
      if (accounts.length === 0) {
        console.log("Please connect to metamask");
        // our old data is not current connected account
        // currentAccount account that you already fetched and assume you stored it in useState
      } else if (accounts[0] !== currentAccount) {
        // if account changed you should update the currentAccount so you return the updated the data
        // assuming you have [currentAccount,setCurrentAccount]=useState
        // however you are tracking the state currentAccount, you have to update it. in case of redux you have to dispatch update action etc
        setCurrentAccount(accounts[0)
      }
    };

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Yilmaz