How to Implement EIP-6963 Support in your Web3 Dapp

Learn how to implement EIP-6963 in your web3 dapp for a more seamless connected wallet experience

by Eric Bishard, Kingsley OkonkwoMarch 5, 2024
How to Implement EIP-6963 Support in your Web3 Dapp Feature image

In May 2023, a group of Ethereum wallet developers came together to establish a new industry standard for connecting with dapps. Before that, the common practice was for wallets to expose an EIP-1193 provider via the window.ethereum global object in web browsers. This technique worked well in the early days when users typically owned and connected to one wallet.

As the ecosystem grew and users started utilizing multiple wallets at a time, it became difficult for dapp developers to properly manage the process of detecting what wallet a user was connected to or give users the option of selecting which wallet to use when many were installed.

In October 2023, this standard, now known as EIP-6963 or Multi-Wallet Injected Provider Discovery, was accepted. Since then, wallet developers have started introducing EIP-6963 as the default way of discovering wallet providers. MetaMask is no exception, as we’ve ensured that this standard is supported by the MetaMask wallet API and MetaMask SDK.

Additionally, dapps that rely on window.ethereum are assured continued support. However, we encourage implementing EIP-6963 support into all dapps as the preferred provider discovery mechanism. The easiest way to do this is by integrating with third-party libraries supporting EIP-6963. Visit the MetaMask documentation for a growing list of available libraries.

This tutorial will walk you through the basic steps to implementing EIP-6963 support in your dapp if you decide not to use the available third-party libraries. It uses React and TypeScript however, any JavaScript developer should be able to follow through and implement this in their framework of choice.

Define the Supported Interfaces and Types for Web3 Wallets


The first step to supporting multi-wallet discovery in your dapp is to define all the supported interfaces and types as outlined in the improvement proposal. These interfaces and types provide a structured and standardized way to handle Ethereum wallet providers, facilitating the integration and discovery of multiple Ethereum wallets.

// 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
  }
}

I’ve added comments to help you understand what each interface and type represents. Please refer to the improvement proposal for a more detailed explanation.

Subscribe to Events and Get Notified of New Connections


The next step is to establish a way to alert your dapp when new wallet connections are detected. Remember, the purpose of EIP-6963 is to enable seamless connection and switching between multiple wallets. In React, we can achieve this with the sample code below.

// 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);
  }
}

Here’s a breakdown of the above code sample:

  • We extend the WindowEventMap with a custom event named "eip6963:announceProvider," thereby establishing a global event type for announcing the availability of new Ethereum wallet providers.
  • We initialize an external store, represented by an array of named providers, which is used to keep track of all detected wallet providers. This array stores objects conforming to the EIP6963ProviderDetail interface, which includes information about the wallet provider and its corresponding Ethereum provider.
  • We introduce a subscription mechanism through the subscribe function within the store object. This function listens for the "eip6963:announceProvider" event and updates the external store with the details of any newly announced providers. It ensures that providers are only added if not in the store, avoiding duplicate entries.
  • The store object provides two key functionalities:
    1. The value function allows for retrieving the current state of the external store, i.e., the list of detected wallet providers.
    2. The subscribe function enables components or other parts of the application to subscribe to changes in the store. When a new provider is announced and added to the store, subscribed entities are notified via a callback mechanism, allowing them to react to the updated list of providers.

Next, let’s define a useSyncExternalStore hook to synchronize the local state with the external store defined in store.tsx above:

// useSyncProviders.tsx

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

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

Dynamically Display Buttons for each Detected Web3 Wallet Provider


Let’s define a React component that uses the useSyncProviders hook to render a button for each detected wallet provider dynamically:

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>
      )}
    </>
  );
};

Here’s a breakdown of the above React component:

  • We use two state hooks, selectedWallet, and userAccount, to keep track of the currently selected Ethereum wallet provider and the user's account address, respectively.
  • We use the useSyncProviders hook to detect each available Ethereum wallet provider dynamically. This hook returns an array of providers, each conforming to the EIP6963ProviderDetail interface that includes the provider's information and Ethereum provider object.
  • We define the handleConnect function that is invoked when a wallet provider button is clicked. This function uses the provider's request method with the eth_requestAccounts method to prompt the user for account access. If access is granted, the first account address is stored in userAccount, and the provider's details are stored in selectedWallet.
  • Finally, we render a list of buttons for each detected wallet provider, displaying the provider's name and icon.

With our component defined, the last step is to render this component.

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

function App() {

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

export default App

With these few steps, we’ve successfully implemented EIP-6963 support in our dapp on a fundamental level. See below for a visual representation of the functionality we just built. The complete code for the dapp captured in the visual can be found on GitHub.

How to Implement EIP-6963 Support in your Web3 Dapp GIF

EIP-6963 and the Road Ahead for Web3 Wallets


As we continue to see wider adoption of this standard by wallet developers and third-party connection libraries, we're excited about the various use cases it opens up for the Ethereum community, especially in improving the UX for onboarding new users.

We at MetaMask are eager to collaborate with other wallet builders, dapp developers, and third-party connection libraries to ensure even greater adoption of EIP-6963. For more information on our work to support this standard, please refer to the MetaMask documentation.

Receive our Newsletter