ethers.js icon indicating copy to clipboard operation
ethers.js copied to clipboard

Metamask App Opens Unexpectedly When Calling Read-Only Method with Ethers Signer

Open Linch1 opened this issue 2 years ago • 4 comments

Ethers Version

6.7.1

Search Terms

contract, metamask, ios

Describe the Problem

Description:

I am experiencing an unexpected behavior when using ethers.js to interact with a contract on iOS (iPhone 12, iOS 16.6.1), whether using Chrome or Safari.

Steps to Reproduce:

I'm connected to my dapp with Metamask via RainbowKit, which uses WAGMI that now uses VEIM. Converted the VEIM client/provider to an ethers signer/provider successfully. Created an ethers contract to interact with an ERC20 token (specifically USDT on the BSC testnet). When initializing the contract with the provider:

contract = new ethers.Contract(this.address, this.abi, this.provider);

Calling a readable method like balanceOf returns the expected response without any issues.

However, when initializing the contract with the signer:

contract = new ethers.Contract(this.address, this.abi, this.signer);

Calling the same readable method prompts the Metamask app to open, as if a transaction needs approval, even though it's merely a call to the balanceOf method.

Expected Behavior: The Metamask app shouldn't open for read-only calls even when using the signer.

Actual Behavior: The Metamask app opens unexpectedly when the contract is initialized with the signer and a read-only method is called.

Does anyone know how to prevent this behavior when initializing the contract with the signer?

The conversion from viem to ethers is done as following

Provider

import * as React from 'react'
import { ethers } from 'ethers'
import { usePublicClient } from 'wagmi'
export function publicClientToProvider(publicClient) {
    if(!publicClient) return;
    const { chain, transport } = publicClient
    const network = {
        chainId: chain.id,
        name: chain.name,
        ensAddress: chain.contracts?.ensRegistry?.address,
    }
    if (transport.type === 'fallback')
        return new ethers.FallbackProvider(
        (transport.transports).map(
            ({ value }) => new ethers.JsonRpcProvider(value?.url, network),
        ),
        )
    return new ethers.JsonRpcProvider(transport.url, network)
}

/** Hook to convert a viem Public Client to an ethers.js Provider. */
export function useEthersProvider() {
  const publicClient = usePublicClient()
  return React.useMemo(() => publicClientToProvider(publicClient), [publicClient])
}

SIgner

import * as React from 'react'
import { useWalletClient } from 'wagmi'
import { ethers } from 'ethers'

export async function walletClientToSigner(walletClient) {
    if(!walletClient) return
    const { account, chain, transport } = walletClient
    const network = {
        chainId: chain.id,
        name: chain.name,
        ensAddress: chain.contracts?.ensRegistry?.address,
    }
    const provider = new ethers.BrowserProvider(transport, network)
    const signer = await provider.getSigner(account.address)
    
    return signer
}

/** Hook to convert a viem Wallet Client to an ethers.js Signer. */
export function useEthersSigner( setSigner ) {
  const { data: walletClient } = useWalletClient()
  return React.useMemo(
    () => {
      ( async () => {
        walletClient ? setSigner( await walletClientToSigner(walletClient) ) : undefined
      })();
    },
    [walletClient],
  )
}

Code Snippet

balanceOf(account ){
        
        let result = await this.contract.balanceOf(account)
        .catch( err => { this.setError( err, err.code, err.message ); return null });

        if( result == null || result == undefined ) return null;
        this.setSuccess( result );
        return result;
    }

Contract ABI

No response

Errors

No response

Environment

No response

Environment (Other)

No response

Linch1 avatar Oct 19 '23 23:10 Linch1

Could it be the call to Viem’s useWalletClient?

ricmoo avatar Oct 20 '23 00:10 ricmoo

Unfotunately i don't think that useWalletClient causes that behaviour I made many different tried, but the only thing that triggered the request for open metamask app is the intialization of the contract with the signer instead than the provider

the useWalletClient is called once when connected, instead i place the balanceOf request in a setInterval that is called every 5 seconds, and every 5 seconds the request to open metamaks appears if the contract was initialized with the signer

Linch1 avatar Oct 20 '23 09:10 Linch1

@Linch1 Have you figured out the solution to that problem?

milaabl avatar Feb 13 '24 13:02 milaabl

There shouldn’t be anything specific ethers does that causes this. It seems like something must be triggering something like (await getSigner()).provider) instead of using a normal new BrowserProvider(window.ethereum)` or the equivalent using the new EIP for browser providers.

Ethers never reaches out to any global property and only works on things that are passed into it… Are you able to trace the stack? Or make adding provider.on("debug", console.log) can help isolate what is happening?

ricmoo avatar Feb 13 '24 16:02 ricmoo