Web3新手系列:点击MetaMask误唤起其他钱包?钱包冲突解决方案现状
连接钱包是进入Web3世界的关键一步,Web3用户经常需要在一些DApp网站上连接钱包。但是,仅仅是这个简单的动作,也可能对用户造成严重的不便。
连接钱包想象一下这样一个场景:一位新入门的Web3用户(出于好奇,他安装了许多个插件钱包),访问了某个DApp网站,并且想要使用自己的浏览器插件钱包来连接它,但是当他们点击网站提供的“ConnectWallet”按钮,并选择某个钱包以便想使用它来连接DApp时,可能会发现弹出的钱包并不是自己选择的。这很可能会让他感到慌乱和窒息,以为是自己的电脑中了病毒,所以才执行了自己意料之外的操作。
区块链钱包是连接区块链的重要入口,而为了占据这个入口,各个钱包使用了它们能想到的各种方式。其中最让DApp开发者以及DApp用户头疼的要数各钱包对全局变量的篡改。
在当前的浏览器钱包实现逻辑中,都有通过向浏览器注入全局变量来暴露钱包提供的功能(例如以太坊平台的钱包会将其提供的功能注入到“window.ethereum”上),以便DApp可以调用钱包提供的方法来与之交互。
只是,由于很多钱包都会将自己注入到同一个window.ethereum变量上,就导致在后面注册的钱包会覆盖之前注册的钱包,以至于只能通过这种方式只能唤起最后注册的那个。
有时候DApp用户为了可以正常使用自己想要用的钱包,不得不临时将其他钱包插件禁用,或者直接只安装某一个钱包。这样一来,反而与钱包开发者最初的想法大相径庭了。并且新钱包哪怕做的更出色,也很难吸引到已经使用其他钱包的用户。
有朋友可能奇怪,为什么一定要注入到同一个变量中呢?假设有两个钱包:A和B,其实只要A将自己注入到“window.a”,B将自己注入到“window.b”,想要唤起哪个钱包,就调用其对应的对象中提供的方法,就不会发生上述想要调用A却反而将B唤起的问题。这样确实可以解决竞争问题,但是,问题在于,如此以来,假如DApp将要支持多个钱包连接,就必须将开发者想要适配的所有钱包名称全部预先定义在代码中,并且在用户选中某个钱包时,调用该钱包的相关方法。导致相关代码维护起来相当麻烦。而统一将钱包注入到同一个对象上,则可以免于这个麻烦。
解决方案为了脱离上述的两难困境,社区中有两个相似的标准。
以太坊的解决方案:EIP-6963以太坊社区在2023年5月份提出了EIP-6963提案。
其中的基本逻辑很简单,就是舍弃全局变量,转而使用约定的事件,来解决钱包注册与发现的问题。
具体来说,插件钱包加载成功后,触发统一的“eip6963:announceProvider”事件,通知DApp有新的钱包可用。而DApp则通过监听此事件,来得知自己目前可用的钱包有哪些。
这样,通过一套抽象的事件监听逻辑,避免了直接使用全局变量所造成的问题,且能够自动发现目前用户环境中可用的钱包。如此一来,两难自解。
社区标准:WalletStandardEIP-6963是以太坊生态标准,但是不止以太坊,其他链平台也会有类似的问题。例如Solana链的钱包,普遍会将自己注入到“window.solana”变量上,同样会造成竞争情况。
那么可否让Solana生态也实现这个标准呢?虽然EIP-6963只是为了解决以太坊生态中的钱包发现问题,但是其中所蕴含的思路其实可以套用在所有链平台。那么,我们能否再进一步,提供一套通用的标准,让所有区块链平台的钱包和DApp实现,让所有链平台的开发者和用户都能享受到EIP-6963所提供的便利?理论上是完全没有问题的,而且已经有开发者在这么做了,也就是WalletStandard。
WalletStandard所做的核心工作,在于提供了两个函数:“registerWallet”和“getWallets”,前者用于钱包,后者用于DApp。
钱包调用“registerWallet”,传入一个钱包对象,这个对象上封装了钱包提供的功能(例如Connect方法,用于连接钱包)。函数内部会先触发一个RegisterWalletEvent事件,事件的参数其实是一个回调函数,用于让DApp监听到RegisterWalletEvent事件时调用,而这个回调函数实际上会将wallet对象传入,于是DApp就可以拿到钱包对象引用,也就可以与钱包进行交互了。
DApp开发者没有必要自己来写监听、接收钱包对象的代码,这部分也已经被WalletStandard内置到“getWallets”当中。但是,getWallets只是监听了事件,具体要怎样处理事件,还是需要开发者考虑。例如获取到的Wallets放到哪里?有些钱包在DApp加载前就已经加载,而另一些钱包可能在之后才加载,这些钱包的状态如何维护?WalletStandard针对以上细节问题,同时提供了“@wallet-standard/react”包,开发者直接使用它提供的ReactHooks就可以获取到想要的数据,包括钱包列表、当前连接的钱包、钱包提供的方法等。
WalletStandardFeatures除了最基本的获取Wallet对象外,WalletStandard也定义了一些Features格式。
实际上,钱包都具有一些最基本的功能,例如连接、监听钱包事件等。WalletStandard提供了“standard:connect”、“standard:events”等features,钱包供应商实现这些特性后,DApp可以直接根据这些值来判断钱包是否支持某些操作。
上面提到的"standard:*"是它内置定义的特性,实际上它们的值并没有特别强硬的要求,所以可以随意扩展。不同的链平台也会有其独特的特性,例如Solana,直接约定"solana:*"即可。Solana平台常见的features包括“solana:signTransaction”,“solana:signMessage”等。
WalletStandard现状目前实现了WalletStandard标准的项目实际上并不多,值得一提的有Solana和Sui。
在AntDesignWeb3的Solana适配器中,也支持适配了WalletStandard的钱包的自动检测,开发者基本只需要通过一个“autoAddRegisteredWallets”开启即可,不需要配置一大堆的钱包元数据,开发体验和用户使用体验直线上升。
ZAN.TOP连接钱包的逻辑在早期同样遇到相同的问题,不过现在,得益于AntDesignWeb3提供的配置,很轻松就适配了EIP-6963标准。大家在https://zan.top/personal/account?chInfo=ch_wxdyh绑定地址时应该已经体验到这一点了。
各区块链生态的实现目前各个区块链平台对WalletStandard(或EIP-6963)标准的态度并不相同,这里举几个例子:
Bitcoin比特币目前为止似乎没有类似的标准,有一个实现了WalletStandard标准的项目,但是并没有引起太多关注,现在也很久没有提交新的代码。
目前开发者只能手动维护状态,或者使用一些开发包来辅助工作。例如在AntDesignWeb3中的Bitcoin适配器实现中,针对不同的钱包,会从不同的全局变量上获取,并存到统一的状态中。这其实是等于库开发者将繁琐的状态维护工作接手了。而且,这仅仅解决了钱包冲突问题,无法自动感知可用钱包的问题依然存在。
Ethereum以太坊平台已经有了EIP-6963标准,相关库和钱包也大多提供了支持。
Solana如上文,官方提供了实现:https://github.com/solana-labs/wallet-standard
SuiSui目前已经对WalletStandard提供了实现,在官方文档上可以找到使用方法:https://docs.sui.io/standards/wallet-standard
DApps开发库的支持wagmiwagmi通过mipd(https://github.com/wevm/mipd)库对EIP-6963提供了支持,具体方式可以查看wagmi的文档。
RainbowKitRainbowKit(https://www.rainbowkit.com/)内部逻辑基于wagmi,所以也已经对EIP-6963提供了内置支持。
AntDesignWeb3AntDesignWeb3(https://web3.ant.design/)的Ethereum和Solana适配器对这两个标准都进行了非常好的支持,并且开发者开启起来非常便携。
对以太坊DApp开发者而言,只需要添加eip6963配置即可,注意其中与EIP-6963相关的在23-25行:
而如果你是Solana生态的DApp开发者,方式也是类似的。它提供了autoAddRegisteredWallets属性:
总结EIP-6963和WalletStandard可以极大改善用户连接钱包的体验,降低新钱包供应商的准入门槛。希望以后能有更多链平台以及钱包、DApp开发者可以提供或实现相关标准,这有利于Web3向着更好的方向发展。
本文由ZANTeam(X官方账号@zan_team)的gin-lsl撰写。