Metamask App Opens Unexpectedly When Calling Read-Only Method with Ethers Signer
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
Could it be the call to Viem’s useWalletClient?
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 Have you figured out the solution to that problem?
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?