F5刷新页面时ethers.js调用listAccounts返回空数组如何修复
问题根因
同时安装MetaMask和Coinbase钱包时,两个插件都会向页面注入window.ethereum对象,且会互相修改、覆盖该对象的属性。直接使用默认的window.ethereum初始化ethers provider时,页面刷新后拿到的provider实例不一定是之前完成连接授权的MetaMask实例,大概率指向了未给当前DApp授权的Coinbase钱包实例。此时调用listAccounts()只会返回当前provider对应钱包下已授权的账户,自然会拿到空数组。
修复方案
- 显式筛选目标钱包的provider实例初始化
多钱包共存时,所有注入的钱包实例都会存放在window.ethereum.providers数组中,每个实例会带专属标识:MetaMask实例带isMetaMask: true标记,Coinbase实例同时带isCoinbaseWallet: true和isMetaMask: true标记。初始化时手动筛选出MetaMask实例即可避免实例错位,参考代码:// 兼容单钱包/多钱包注入场景 const allInjectedProviders = window.ethereum?.providers ?? [window.ethereum]; // 过滤出纯MetaMask实例,排除Coinbase带的伪装MetaMask标记实例 const metaMaskProvider = allInjectedProviders.find( provider => provider.isMetaMask && !provider.isCoinbaseWallet ); // 优先用MetaMask实例初始化provider,兜底到默认注入对象 const provider = new ethers.providers.Web3Provider(metaMaskProvider || window.ethereum); - 替换账户获取方式,主动做授权校验
不要在页面初始化时直接调用listAccounts()拉取账户,该方法是只读方法,不会触发钱包侧的权限校验,多钱包场景下兼容问题较多。替换为eth_requestAccounts方法即可,用户已经完成授权的情况下,该方法不会重复弹出授权窗口,会直接返回已授权的账户列表,参考代码:// 初始化provider后主动拉取已授权账户 const accounts = await provider.send("eth_requestAccounts", []); // 绑定账户变更监听,用户切换账户、断开连接时实时同步状态 provider.provider.on("accountsChanged", (currentAccounts) => { // 此处写账户状态更新的业务逻辑,比如重连、刷新用户地址信息 }); - 增加本地钱包选择缓存
如果DApp需要支持多钱包连接,可在用户首次连接时弹出钱包选择面板,让用户手动选择要连接的钱包类型,将选择结果存在localStorage中。页面刷新时直接读取本地存储的钱包类型,筛选对应实例初始化,从根源上避免多钱包抢占注入对象导致的实例错乱问题。
注意:部分旧版本钱包在页面刚加载完成时存在注入延迟,可给初始化逻辑加100-300ms的延迟,或者监听
window的load事件后再初始化provider,进一步降低拿到空实例的概率。
内容的提问来源于stack exchange,提问作者user18017718
相关产品推荐
相关产品推荐

