EIP-6963 解决同时多个钱包提供者的烦恼,本文介绍如在在前端 React应用中集成 EIP-6963 。
- 原文链接:https://hackmd.io/@VydelHxmR0CbKxAe4TjhbQ/BJ9Bf4XD6
- 译者:AI翻译官,校对: 翻译小组
- 本文永久链接:learnblockchain.cn/article…
本文将快速帮助你理解 EIP-6963(多钱包注入提供者发现),并回答如何在你的 React dApp 中实现它的问题。
如果只想看代码? 这个 ViteJS(React + TypeScript)的示例Github 代码:vite-react-ts-eip-6963
这个示例代码实现了 EIP-6963,用来检测多个注入的提供者(浏览器安装的钱包(外部拥有账户))。
跟随我们一起探索 EIP 中概述的接口,了解如何监听 eip6963:announceProvider
事件,并存储和区分返回的各种提供者,以便在我们的 dApp 中使用。
如果你了解 EIP-6963 背景,可以直接跳到“开发者需要知道的内容”
我们将构建的内容预览:
EIP-6963 是一种替代检查 window.ethereum
的机制,它是 EIP-1193 定义的提供者,支持使用 JavaScript 的 window 事件在网页中发现多个注入的钱包提供者。
如果不了解 EIP-6963,你可以阅读 EIP-6963 的摘要, 动机 。
如果文档中有你不理解任何部分,可以通过像 Ethereum Magicians 这样的站点获取更多信息(EIP-6963 MIPD),在那里你可以从作者和社区获取大多数 EIP 的额外背景信息。熟悉 EIP-1193 也会有所帮助。
EIP-6963 摘要 中最重要的一句话是:
"一种替代 window.ethereum
的发现机制,其作为 EIP-1193 提供者"
这告诉我们,EIP-6963 的多注入提供者发现提案引入了一种不同的方法来发现和与 EIP-1193 提供者交互,与现有方法依赖 window.ethereum
对象形成对比。
作为一个在 dapps 中到处使用 window.ethereum
的开发者,以及旧方法带来的用户体验问题,他们引起了我的注意。
希望这些信息能帮助我们在阅读 EIP 并开始实现这种改进的用户钱包检测方法时,框定我们的思维。
在以太坊 dApps 中,钱包传统上使用一个称为“提供者”的 JavaScript 对象来暴露它们的 API。最初的 EIP 是为了标准化这个接口,即 EIP-1193,但不同钱包实现之间出现了冲突。虽然 EIP-1193 旨在建立一个通用约定,但由于钱包注入它们的提供者(覆盖)window
(以太坊对象)导致的竞争条件,用户体验受到了影响。这是对 Web3 用户体验及其在钱包发现、入门和连接方面缺陷的巨大批评。这些竞争条件导致在同一浏览器中启用多个钱包扩展时发生冲突,最后一个注入的提供者优先。
EIP-5749 尝试通过 window.evmprovider
解决这个问题,这是朝着正确方向迈出的一步,但最终没有被足够多的钱包采用来解决我们的用户体验问题。这就是我们最终得到 EIP 6963 的地方,它提出了一种进行多注入提供者发现的标准,钱包们正在注意并大多数已经实现了这个新标准。
作为开发者,熟悉 EIP-6963 的第一件事是了解最初的动机、基本描述,更重要的是实现这种新方法所需的类型、接口和逻辑。
每个钱包提供者将使用以下接口公告(announce)自己:
/**
* 表示显示钱包所需的资源
*/
interface EIP6963ProviderInfo {
uuid: string;
name: string;
icon: string;
rdns: string;
}
用作组合接口来公告钱包提供者及其相关元数据
interface EIP6963ProviderDetail {
info: EIP6963ProviderInfo;
provider: EIP1193Provider;
}
EIP6963AnnounceProviderEvent
接口必须是一个CustomEvent
对象,其type
属性包含一个字符串值eip6963:announceProvider
,并且其 detail 属性包含一个类型为EIP6963ProviderDetail
的对象值。
// 钱包派发的公告事件
interface EIP6963AnnounceProviderEvent extends CustomEvent {
type: "eip6963:announceProvider";
detail: EIP6963ProviderDetail;
}
EIP6963RequestProviderEvent
接口必须是一个 Event 对象,其 type 属性包含一个字符串值eip6963:requestProvider
。
// DApp 派发的请求事件
interface EIP6963RequestProviderEvent extends Event {
type: "eip6963:requestProvider";
}
理解了这些接口后,我们可以启动一个 ViteJS React + TypeScript 应用,并在 src/vite-env.d.ts
中更新这些接口,并添加一个 EIP1193Provider
:
/// <reference types="vite/client" />
interface EIP6963ProviderInfo {
walletId: string;
uuid: string;
name: string;
icon: string;
}
// Represents the structure of an Ethereum provider based on the EIP-1193 standard.
interface EIP1193Provider {
isStatus?: boolean;
host?: string;
path?: string;
sendAsync?: (request: { method: string, params?: Array<unknown> }, callback: (error: Error | null, response: unknown) => void) => void
send?: (request: { method: string, params?: Array<unknown> }, callback: (error: Error | null, response: unknown) => void) => void
request: (request: { method: string, params?: Array<unknown> }) => Promise<unknown>
}
interface EIP6963ProviderDetail {
info: EIP6963ProviderInfo;
provider: EIP1193Provider;
}
// This type represents the structure of an event dispatched by a wallet to announce its presence based on EIP-6963.
type EIP6963AnnounceProviderEvent = {
detail:{
info: EIP6963ProviderInfo,
provider: EIP1193Provider
}
}
这些接口和类型提供了一种结构化和标准化的方式来处理以太坊钱包提供者,促进了 EIP-6963 的集成。定义为钱包提供者提供了明确的规范,促进了一致性和互操作性。
然后我们可以创建一个 hooks
目录并添加以下两个文件:
declare global {
interface WindowEventMap {
"eip6963:announceProvider": CustomEvent
}
}
let providers: EIP6963ProviderDetail[] = []
export const store = {
value: ()=>providers,
subscribe: (callback: ()=>void)=>{
function onAnnouncement(event: EIP6963AnnounceProviderEvent){
if(providers.map(p => p.info.uuid).includes(event.detail.info.uuid)) return
providers = [...providers, event.detail]
callback()
}
window.addEventListener("eip6963:announceProvider", onAnnouncement);
window.dispatchEvent(new Event("eip6963:requestProvider"));
return ()=>window.removeEventListener("eip6963:announceProvider", onAnnouncement)
}
}
关于这个 store:
import { useSyncExternalStore } from "react";
import { store } from "./store";
export const useSyncProviders = ()=> useSyncExternalStore(store.subscribe, store.value, store.value)
利用 useSyncExternalStore
钩子将本地状态与在 store.tsx
中定义的外部 store 同步。
在下一个组件 DiscoverWalletProviders
中,调用 useSyncProviders
钩子以获取同步状态。providers
变量现在保存了外部 store store.value
的当前状态。DiscoverWalletProviders
组件使用此状态动态渲染每个检测到的钱包提供者的按钮。
接下来我们将在 src/components
目录中添加一个组件。
import { useState } from 'react'
import { useSyncProviders } from '../hooks/useSyncProviders'
import { formatAddress } from '~/utils'
export const DiscoverWalletProviders = () => {
const [selectedWallet, setSelectedWallet] = useState<EIP6963ProviderDetail>()
const [userAccount, setUserAccount] = useState<string>('')
const providers = useSyncProviders()
const handleConnect = async(providerWithInfo: EIP6963ProviderDetail)=> {
const accounts = await providerWithInfo.provider
.request({method:'eth_requestAccounts'})
.catch(console.error)
if(accounts?.[0]){
setSelectedWallet(providerWithInfo)
setUserAccount(accounts?.[0])
}
}
return (
<>
<h2>Wallets Detected:</h2>
<div>
{
providers.length > 0 ? providers?.map((provider: EIP6963ProviderDetail)=>(
<button key={provider.info.uuid} onClick={()=>handleConnect(provider)} >
<img src={provider.info.icon} alt={provider.info.name} />
<div>{provider.info.name}</div>
</button>
)) :
<div>
there are no Announced Providers
</div>
}
</div>
<hr />
<h2>{ userAccount ? "" : "No " }Wallet Selected</h2>
{ userAccount &&
<div>
<div>
<img src={selectedWallet.info.icon} alt={selectedWallet.info.name} />
<div>{selectedWallet.info.name}</div>
<div>({formatAddress(userAccount)})</div>
</div>
</div>
}
</>
)
}
在上面的代码中,一旦页面渲染,我们就会记录检测到的提供者。见第 11 行。我们可以遍历这些提供者并为每个提供者创建一个按钮,该按钮将用于调用 eth_requestAccounts
。
最后我们可以在 src/App.tsx
中渲染这个组件
import './App.css'
import { DiscoverWalletProviders } from './components/DiscoverWalletProviders'
function App() {
return (
<>
<DiscoverWalletProviders/>
</>
)
}
export default App
总之,这些文件在 ViteJS(React + TypeScript)应用程序中实现了 EIP-6963 的集成。定义了接口和类型,通过 useSyncProviders
钩子处理组件之间的同步,并且外部 store store.tsx
管理检测到的钱包提供者的状态。
通过这些简单的步骤,我们在使用 TypeScript 接口的 React 应用程序中实现了 EIP-6963。基本上就是这样。你可以在这里看到源代码:vite-react-ts-eip-6963
对于构建 Web3 的开发人员来说,开始使用 EIP-6963(多注入提供者发现)的最简单方法是利用已经支持它的第三方连接库(便利库)。在撰写本文时,以下是列表:
如果你正在构建钱包连接库,你会很高兴知道,集成 MetaMask 的 SDK 将确保连接到 MetaMask 支持 EIP-6963,以检测 MetaMask 扩展和 MetaMask Flask 钱包。你可以使用 Wagmi 的 MIPD Store 获取注入的提供者,包含 Vanilla JS 和 React 示例。
MetaMask SDK 不仅支持在 MetaMask 上检测 EIP-6963,还集成到 Web3 Onboard 和 WAGMI v2.0 中。
SDK 对 EIP-6963 的集成旨在高效发现和连接 MetaMask 扩展。这一增强对于简化用户体验和促进与以太坊区块链的无缝交互至关重要。
MetaMask JS SDK 现在会自动检查支持 EIP-6963 的 MetaMask 扩展的存在。这消除了手动配置或检测方法的需要,从而简化了开发人员和用户的初始设置过程。
冲突解决:通过遵循 EIP-6963 设定的标准,SDK 明确识别并连接到 MetaMask 扩展。这种方法有效解决了可能与其他钱包扩展产生的潜在冲突,确保用户更稳定和可靠的交互。
不支持 EIP-6963 的 Dapps 仍然可以使用 window.ethereum
提供者检测 MetaMask。
然而,我们建议添加支持以改善多钱包安装的用户体验。
阅读更多关于 EIP-6963 向后兼容性。
Wallet Connect 团队的这个应用的 NextJS 版本
EIP-6963 概述:解决多钱包冲突的可能方案
EIP-6963
EIP-6963 标准化你的浏览器钱包体验
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!