如何在你的 Web3 Dapp 中实现 EIP-6963 支持

  • metamask
  • 发布于 2024-03-06 22:55
  • 阅读 14

本文介绍了如何在 Web3 DApp 中实现 EIP-6963 标准,以提供更无缝的多钱包连接体验。EIP-6963 旨在解决用户同时使用多个钱包时,DApp 检测和管理钱包连接的难题。文章详细讲解了如何定义支持的接口和类型,订阅事件以检测新连接,并动态显示每个检测到的 Web3 钱包供应商的按钮,并提供了 React 和 TypeScript 代码示例。

学习如何在你的 web3 dapp 中实现 EIP-6963,以获得更无缝的连接钱包体验

如何在你的Web3 Dapp中实现EIP-6963支持

2023年5月,一群以太坊钱包开发者齐聚一堂,为与dapp连接建立新的行业标准。在此之前,常见的做法是钱包通过 web 浏览器中的 window.ethereum 全局对象公开 EIP-1193 provider。这种技术在早期用户通常只拥有并连接到一个钱包时效果很好。

随着生态系统的发展,用户开始一次使用多个钱包,dapp 开发者很难正确管理检测用户连接到哪个钱包的过程,或者在安装了多个钱包时让用户选择使用哪个钱包。

2023年10月,这个标准被接受,现在被称为 EIP-6963 或多钱包注入 Provider 发现。从那时起,钱包开发者已经开始引入 EIP-6963 作为发现钱包 provider 的默认方式。MetaMask 也不例外,因为我们已经确保 MetaMask 钱包 API 和 MetaMask SDK 支持这个标准。

此外,依赖 window.ethereum 的 dapp 保证会继续得到支持。但是,我们鼓励将 EIP-6963 支持集成到所有 dapp 中,作为首选的 provider 发现机制。最简单的方法是与支持 EIP-6963 的第三方库集成。访问 MetaMask 文档以获取不断增长的可用库列表。

本教程将引导你完成在你不想使用可用的第三方库时,在你的 dapp 中实现 EIP-6963 支持的基本步骤。它使用 React 和 TypeScript,但是,任何 JavaScript 开发者都应该能够遵循并在他们选择的框架中实现它。

定义 Web3 钱包支持的接口和类型

在你的 dapp 中支持多钱包发现的第一步是定义改进提案中概述的所有支持的接口和类型。这些接口和类型提供了一种结构化和标准化的方式来处理以太坊钱包 provider,从而促进多个以太坊钱包的集成和发现。

// EthereumProviderTypes.d.ts

// 遵循 EIP-6963 的 provider 信息接口。
interface EIP6963ProviderInfo {
  walletId: string; // 钱包的唯一标识符,例如 io.metamask, io.metamask.flask
  uuid: string; // 全局唯一ID,用于区分页面生命周期内的provider会话
  name: string; // 钱包的人性化名称
  icon: string; // 钱包图标的 URL
}

// 基于 EIP-1193 标准的以太坊 provider 接口。
interface EIP1193Provider {
  isStatus?: boolean; // 可选:指示 provider 的状态
  host?: string; // 可选:以太坊节点的 Host URL
  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>; // 根据 EIP-1193 发送请求的标准方法
}

// 详细说明 provider 信息及其以太坊 provider 结构的接口。
interface EIP6963ProviderDetail {
  info: EIP6963ProviderInfo; // provider 的信息
  provider: EIP1193Provider; // 与 EIP-1193 兼容的 provider
}

// 表示用于根据 EIP-6963 公布 provider 的事件结构的类型。
type EIP6963AnnounceProviderEvent = {
  detail: {
    info: EIP6963ProviderInfo; // provider 的信息
    provider: EIP1193Provider; // 与 EIP-1193 兼容的 provider
  }
}

我添加了注释来帮助你理解每个接口和类型的含义。有关更详细的说明,请参阅改进提案。

订阅事件并获取新连接的通知

下一步是建立一种在检测到新的钱包连接时提醒你的 dapp 的方法。请记住,EIP-6963 的目的是实现多个钱包之间的无缝连接和切换。在 React 中,我们可以使用下面的示例代码来实现这一点。

// store.tsx

declare global {
  interface WindowEventMap {
    "eip6963:announceProvider": CustomEvent<EIP6963AnnounceProviderEvent>;
  }
}

let providers: EIP6963ProviderDetail[] = [];

export const store = {
  value: () => providers,

  subscribe: (callback: () => void) => {
    function onAnnouncement(event: EIP6963AnnounceProviderEvent) {
      // 如果列表中已存在具有相同 uuid 的 provider,则阻止添加该 provider。
      if (providers.some(p => p.info.uuid === event.detail.info.uuid)) return;

      // 将新的 provider 添加到列表并调用提供的回调函数。
      providers = [...providers, event.detail];
      callback();
    }

    window.addEventListener("eip6963:announceProvider", onAnnouncement as EventListener);
    window.dispatchEvent(new Event("eip6963:requestProvider"));

    return () => window.removeEventListener("eip6963:announceProvider", onAnnouncement as EventListener);
  }
}

以下是上述代码示例的细分:

  • 我们使用名为“eip6963:announceProvider”的自定义事件扩展 WindowEventMap,从而建立用于公布新的以太坊钱包 provider 可用性的全局事件类型。

  • 我们初始化一个外部存储,由一个命名的 provider 数组表示,用于跟踪所有检测到的钱包 provider。此数组存储符合 EIP6963ProviderDetail 接口的对象,其中包括有关钱包 provider 及其相应以太坊 provider 的信息。

  • 我们通过 store 对象中的 subscribe 函数引入了一种订阅机制。此函数侦听“eip6963:announceProvider”事件,并使用任何新公布的 provider 的详细信息更新外部存储。它确保仅在存储中不存在 provider 时才添加 provider,从而避免重复条目。

  • store 对象提供两个关键功能:

  1. value 函数允许检索外部存储的当前状态,即检测到的钱包 provider 的列表。

  2. subscribe 函数使组件或应用程序的其他部分能够订阅存储中的更改。当新的 provider 被公布并添加到存储时,订阅的实体会通过回调机制收到通知,从而使它们能够对更新的 provider 列表做出反应。

接下来,让我们定义一个 useSyncExternalStore hook,以将本地状态与上面 store.tsx 中定义的外部存储同步:

// useSyncProviders.tsx

import { useSyncExternalStore } from "react";
import { store } from "./store";

export const useSyncProviders = ()=> useSyncExternalStore(store.subscribe, store.value, store.value)

动态显示每个检测到的 Web3 钱包 Provider 的按钮

让我们定义一个 React 组件,该组件使用 useSyncProviders hook 为每个检测到的钱包 provider 动态渲染一个按钮:

import { useState } from 'react';
import { useSyncProviders } from '../hooks/useSyncProviders';
import { formatAddress } from '~/utils';

export const DiscoverWalletProviders = () => {
  const [selectedWallet, setSelectedWallet] = useState<EIP6963ProviderDetail | undefined>();
  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 && accounts[0]) {
      setSelectedWallet(providerWithInfo);
      setUserAccount(accounts[0]);
    }
  };

  return (
    <>
      <h2>检测到的钱包:</h2>
      <div>
        {providers.length > 0 ? (
          providers.map((provider) => (
            <button key={provider.info.uuid} onClick={() => handleConnect(provider)}>
              <img src={provider.info.icon} alt={provider.info.name} />
              <div>{provider.info.name}</div>
            </button>
          ))
        ) : (
          <div>没有已公布的 provider。</div>
        )}
      </div>
      <hr />
      <h2>{userAccount ? '已选择钱包' : '未选择钱包'}</h2>
      {userAccount && (
        <div>
          <img src={selectedWallet!.info.icon} alt={selectedWallet!.info.name} />
          <div>{selectedWallet!.info.name}</div>
          <div>({formatAddress(userAccount)})</div>
        </div>
      )}
    </>
  );
};

以下是上述 React 组件的细分:

  • 我们使用两个状态 hook,selectedWallet 和 userAccount,分别跟踪当前选择的以太坊钱包 provider 和用户的帐户地址。

  • 我们使用 useSyncProviders hook 动态检测每个可用的以太坊钱包 provider。此 hook 返回一个 provider 数组,每个 provider 都符合 EIP6963ProviderDetail 接口,该接口包括 provider 的信息和以太坊 provider 对象。

  • 我们定义了 handleConnect 函数,该函数在单击钱包 provider 按钮时调用。此函数使用 provider 的 request 方法和 eth_requestAccounts 方法来提示用户访问帐户。如果授予访问权限,则第一个帐户地址将存储在 userAccount 中,provider 的详细信息将存储在 selectedWallet 中。

  • 最后,我们为每个检测到的钱包 provider 渲染一个按钮列表,显示 provider 的名称和图标。

定义了我们的组件后,最后一步是渲染此组件。

import './App.css'
import { DiscoverWalletProviders } from './components/DiscoverWalletProviders'

function App() {

  return (
    <>
      <DiscoverWalletProviders/>
    </>
  )
}

export default App

通过这几个步骤,我们已经在基础层面上成功地在我们的 dapp 中实现了 EIP-6963 支持。有关我们刚刚构建的功能的可视化表示,请参见下文。可以在 GitHub 上找到视觉效果中捕获的 dapp 的完整代码。

EIP-6963 和 Web3 钱包的未来之路

随着我们继续看到钱包开发者和第三方连接库更广泛地采用此标准,我们对它为以太坊社区开辟的各种用例感到兴奋,尤其是在改善新用户入门的 UX 方面。

我们在 MetaMask 渴望与其他钱包构建者、dapp 开发者和第三方连接库合作,以确保更广泛地采用 EIP-6963。有关我们支持此标准的工作的更多信息,请参阅 MetaMask 文档。

  • 原文链接: metamask.io/news/how-to-...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
metamask
metamask
江湖只有他的大名,没有他的介绍。