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
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:
- The
value
function allows for retrieving the current state of the external store, i.e., the list of detected wallet providers. - 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.
- The
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,
anduserAccount
, 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'srequest
method with theeth_requestAccounts
method to prompt the user for account access. If access is granted, the first account address is stored inuserAccount
, and the provider's details are stored inselectedWallet
. - 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.
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.
Keep reading our latest stories
Developers, security news, and more