Web3 DappにEIP-6963サポートを実装する方法

Web3 dappにEIP-6963を実装して、よりシームレスなウォレット接続体験を実現する方法を学びましょう

6分で読める
Web3 DappにEIP-6963サポートを実装する方法

2023年5月、Ethereumウォレット開発者のグループが集まり、dappとの接続に関する新しい業界標準を策定しました。それ以前は、ウォレットがウェブブラウザのwindow.ethereumグローバルオブジェクトを通じてEIP-1193プロバイダーを公開するのが一般的な慣行でした。この手法は、ユーザーが通常1つのウォレットを所有・接続していた初期の頃には十分に機能していました。

エコシステムが成長し、ユーザーが複数のウォレットを同時に利用するようになると、dapp開発者にとって、ユーザーがどのウォレットに接続しているかを適切に検出したり、多数のウォレットがインストールされている場合にどのウォレットを使用するかをユーザーが選択できるようにしたりするプロセスの管理が困難になりました。

2023年10月、EIP-6963(Multi-Wallet Injected Provider Discovery)として知られるこの標準が承認されました。それ以来、ウォレット開発者はEIP-6963をウォレットプロバイダーの検出におけるデフォルトの方法として導入し始めています。MetaMaskも例外ではなく、MetaMask Wallet APIおよびMetaMask SDKでこの標準がサポートされるよう対応しています。

また、window.ethereumに依存するdappについても、引き続きサポートが保証されています。ただし、推奨されるプロバイダー検出メカニズムとして、すべてのdappにEIP-6963サポートを実装することを推奨します。最も簡単な方法は、EIP-6963をサポートするサードパーティライブラリを統合することです。利用可能なライブラリの一覧(随時更新)については、MetaMaskドキュメントをご覧ください。

このチュートリアルでは、利用可能なサードパーティライブラリを使用しない場合に、dappにEIP-6963サポートを実装するための基本的な手順を説明します。ReactとTypeScriptを使用していますが、JavaScriptの開発者であれば、任意のフレームワークで実装できるはずです。

Web3ウォレット向けのサポートインターフェースと型の定義

dappでマルチウォレット検出をサポートするための最初のステップは、改善提案に概説されているすべてのサポートインターフェースと型を定義することです。これらのインターフェースと型は、Ethereumウォレットプロバイダーを扱うための構造化・標準化された方法を提供し、複数のEthereumウォレットの統合と検出を容易にします。

// EthereumProviderTypes.d.ts

// Interface for provider information following EIP-6963.
interface EIP6963ProviderInfo {
  walletId: string; // Unique identifier for the wallet e.g io.metamask, io.metamask.flask 
  uuid: string; // Globally unique ID to differentiate between provider sessions for the lifetime of the page
  name: string; // Human-readable name of the wallet
  icon: string; // URL to the wallet's icon
}

// Interface for Ethereum providers based on the EIP-1193 standard.
interface EIP1193Provider {
  isStatus?: boolean; // Optional: Indicates the status of the provider
  host?: string; // Optional: Host URL of the Ethereum node
  path?: string; // Optional: Path to a specific endpoint or service on the host
  sendAsync?: (request: { method: string, params?: Array<unknown> }, callback: (error: Error | null, response: unknown) => void) => void; // For sending asynchronous requests
  send?: (request: { method: string, params?: Array<unknown> }, callback: (error: Error | null, response: unknown) => void) => void; // For sending synchronous requests
  request: (request: { method: string, params?: Array<unknown> }) => Promise<unknown>; // Standard method for sending requests per EIP-1193
}

// Interface detailing the structure of provider information and its Ethereum provider.
interface EIP6963ProviderDetail {
  info: EIP6963ProviderInfo; // The provider's info
  provider: EIP1193Provider; // The EIP-1193 compatible provider
}

// Type representing the event structure for announcing a provider based on EIP-6963.
type EIP6963AnnounceProviderEvent = {
  detail: {
    info: EIP6963ProviderInfo; // The provider's info
    provider: EIP1193Provider; // The EIP-1193 compatible 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) {
      // Prevent adding a provider if it already exists in the list based on its uuid.
      if (providers.some(p => p.info.uuid === event.detail.info.uuid)) return;

      // Add the new provider to the list and call the provided callback function.
      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);
  }
}
コピー

上記のコードサンプルの詳細は以下のとおりです:

  • WindowEventMapを「eip6963:announceProvider」というカスタムイベントで拡張し、新しいEthereumウォレットプロバイダーの利用可能性を通知するグローバルイベント型を確立します。

  • 検出されたすべてのウォレットプロバイダーを追跡するために使用される、名前付きプロバイダーの配列として表される外部ストアを初期化します。この配列は、ウォレットプロバイダーとそれに対応するEthereumプロバイダーに関する情報を含むEIP6963ProviderDetailインターフェースに準拠したオブジェクトを格納します。

  • ストアオブジェクト内のsubscribe関数を通じてサブスクリプションメカニズムを導入します。この関数は「eip6963:announceProvider」イベントをリッスンし、新たに通知されたプロバイダーの詳細で外部ストアを更新します。また、プロバイダーがストアに存在しない場合にのみ追加されるようにし、重複エントリーを防ぎます。

  • ストアオブジェクトは2つの主要な機能を提供します:1. value関数は外部ストアの現在の状態、つまり検出されたウォレットプロバイダーのリストを取得するために使用されます。

  1. subscribe関数は、コンポーネントやアプリケーションの他の部分がストアの変更をサブスクライブできるようにします。新しいプロバイダーが通知されてストアに追加されると、サブスクライブしているエンティティはコールバックメカニズムを通じて通知を受け、更新されたプロバイダーリストに対応できます。

次に、上記のstore.tsxで定義された外部ストアとローカル状態を同期するためのuseSyncExternalStoreフックを定義しましょう:

// useSyncProviders.tsx

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

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

検出された各Web3ウォレットプロバイダーのボタンを動的に表示する

useSyncProvidersフックを使用して、検出された各ウォレットプロバイダーのボタンを動的にレンダリングするReactコンポーネントを定義しましょう:

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>Wallets Detected:</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>There are no announced providers.</div>
        )}
      </div>
      <hr />
      <h2>{userAccount ? 'Wallet Selected' : 'No Wallet Selected'}</h2>
      {userAccount && (
        <div>
          <img src={selectedWallet!.info.icon} alt={selectedWallet!.info.name} />
          <div>{selectedWallet!.info.name}</div>
          <div>({formatAddress(userAccount)})</div>
        </div>
      )}
    </>
  );
};
コピー

上記のReactコンポーネントの詳細は以下のとおりです:

  • selectedWalletとuserAccountの2つのステートフックを使用して、現在選択されているEthereumウォレットプロバイダーとユーザーのアカウントアドレスをそれぞれ追跡します。

  • useSyncProvidersフックを使用して、利用可能な各EthereumウォレットプロバイダーをEIP6963ProviderDetailインターフェースに準拠した形で動的に検出します。このフックはプロバイダーの配列を返し、各プロバイダーにはプロバイダーの情報とEthereumプロバイダーオブジェクトが含まれます。

  • ウォレットプロバイダーボタンがクリックされたときに呼び出されるhandleConnect関数を定義します。この関数は、プロバイダーのrequestメソッドをeth_requestAccountsメソッドとともに使用して、ユーザーにアカウントへのアクセスを求めます。アクセスが許可された場合、最初のアカウントアドレスがuserAccountに格納され、プロバイダーの詳細がselectedWalletに格納されます。

  • 最後に、検出された各ウォレットプロバイダーのボタンのリストをレンダリングし、プロバイダーの名前とアイコンを表示します。

コンポーネントが定義されたら、最後のステップはこのコンポーネントをレンダリングすることです。

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

function App() {

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

export default App
コピー

これらの少ないステップで、dappにEIP-6963サポートを基本レベルで実装することができました。以下に、構築した機能のビジュアル表現を示します。ビジュアルに示されているdappの完全なコードはGitHubで確認できます。

EIP-6963とWeb3ウォレットの今後の展望

この標準がウォレット開発者やサードパーティの接続ライブラリによってより広く採用されていく中で、Ethereumコミュニティにとって開かれるさまざまなユースケース、特に新規ユーザーのオンボーディングにおけるUXの向上に期待しています。

MetaMaskは、EIP-6963のさらなる普及を確実にするために、他のウォレット開発者、dapp開発者、およびサードパーティの接続ライブラリと積極的に協力していきたいと考えています。この標準をサポートするための取り組みの詳細については、MetaMaskドキュメントを参照してください。

AIによる翻訳です。エラーが含まれている可能性があります。常に情報を確認してください。

翻訳を評価する
  • Kingsley Okonkwo
    Kingsley Okonkwo

    Kingsleyは、認定Ethereumブロックチェーン開発者であり、Consensysでテクニカルライターとして勤務し、開発者ツールを専門としています。Consensys入社以前は、GigsterやBraintrustなど複数のプラットフォームでフリーランスのバックエンド開発者として活躍していました。5年にわたるソフトウェア開発経験を持ち、JavaScript(Node.js & React)、GoLang、SQL、Dockerなどのツールに精通しています。Kingsleyは、最新のweb3技術を活用したステップバイステップのチュートリアルやハウツーガイドの作成を得意とし、他の開発者がより優れたdappを構築できるよう支援しています。現在はドバイを拠点とし、余暇にはバスケットボールやサッカーを楽しんでいます。

    すべての記事を読む