Skip to content

EIP-6963

EIP-6963

Overview

EIP-6963 (Multi Injected Provider Discovery) is Ethereum's multi-wallet discovery standard, officially accepted in October 2023. This standard solves the discovery and selection problem when multiple wallet extensions coexist in the browser, using an event-based mechanism that allows DApps to discover and connect to all installed wallets.

Before EIP-6963, multiple wallets competed for window.ethereum, leading to a confusing user experience. EIP-6963 thoroughly solves this problem through event-driven bidirectional communication.

Background Problem

Race Condition with Window.ethereum In the traditional approach, wallet extensions all inject into window.ethereum:

// MetaMask injects
window.ethereum = metamaskProvider

// Trust Wallet loads later, overwriting MetaMask
window.ethereum = trustWalletProvider

// User has no choice, can only use the last loaded wallet

Existing Problems: 1. Non-deterministic loading order: Browser extension loading order is random 2. Last one wins: The last loaded wallet "wins" 3. No user choice: Users cannot select which wallet to use on the page 4. Developer dilemma: No way to know which wallets the user has installed 5. Inconsistent experience: Different wallets may connect on each page refresh

EIP-6963 Solution

Event-Driven Discovery Mechanism Instead of using a shared mutable object, bidirectional communication occurs through window events:

  1. DApp requests discovery: Dispatches an eip6963:requestProvider event
  2. Wallets respond: Each wallet dispatches an eip6963:announceProvider event
  3. DApp displays list: Collects all wallets and lets the user choose
  4. User selects: The user explicitly chooses which wallet to use

Core Interfaces

EIP6963ProviderInfo Information structure provided by wallets:

interface EIP6963ProviderInfo {
    uuid: string;        // Unique identifier for the wallet instance
    name: string;        // Wallet name, e.g., "MetaMask"
    icon: string;        // Wallet icon (Data URI)
    rdns: string;        // Reverse DNS name, e.g., "io.metamask"
}

EIP6963ProviderDetail Contains the complete provider information:

interface EIP6963ProviderDetail {
    info: EIP6963ProviderInfo;
    provider: EIP1193Provider;  // The actual provider object
}

EIP6963AnnounceProviderEvent Wallet announcement event:

interface EIP6963AnnounceProviderEvent extends CustomEvent {
    type: "eip6963:announceProvider";
    detail: EIP6963ProviderDetail;
}

EIP6963RequestProviderEvent DApp request event:

interface EIP6963RequestProviderEvent extends Event {
    type: "eip6963:requestProvider";
}

Implementation Examples

Wallet Extension Implementation

// Wallet extension code
function announceProvider() {
    const info = {
        uuid: "350670db-19fa-4704-a166-e52e178b59d2",
        name: "Example Wallet",
        icon: "data:image/svg+xml,...",  // Base64 encoded SVG
        rdns: "com.example.wallet"
    };

    const detail = {
        info,
        provider: window.myWalletProvider  // EIP-1193 provider
    };

    // Dispatch announcement event
    window.dispatchEvent(
        new CustomEvent("eip6963:announceProvider", {
            detail: Object.freeze(detail)
        })
    );
}

// Listen for DApp requests
window.addEventListener("eip6963:requestProvider", () => {
    announceProvider();
});

// Proactively announce on page load
announceProvider();

DApp Integration Implementation

// DApp code
const providers = new Map();

// Listen for wallet announcements
window.addEventListener("eip6963:announceProvider", (event) => {
    const { info, provider } = event.detail;

    // Store discovered providers
    providers.set(info.uuid, { info, provider });

    // Update UI, display available wallet list
    renderWalletList(Array.from(providers.values()));
});

// Request wallet announcements
function discoverWallets() {
    window.dispatchEvent(new Event("eip6963:requestProvider"));
}

// Discover wallets on page load
window.addEventListener("load", () => {
    discoverWallets();
});

Complete Wallet Selector Component

class WalletSelector {
    constructor() {
        this.providers = new Map();
        this.selectedProvider = null;

        this.listenForProviders();
        this.requestProviders();
    }

    listenForProviders() {
        window.addEventListener(
            "eip6963:announceProvider",
            (event) => {
                const { info, provider } = event.detail;
                this.providers.set(info.uuid, { info, provider });
                this.render();
            }
        );
    }

    requestProviders() {
        window.dispatchEvent(new Event("eip6963:requestProvider"));
    }

    async connect(uuid) {
        const providerDetail = this.providers.get(uuid);
        if (!providerDetail) return;

        const { provider } = providerDetail;

        try {
            // Use EIP-1193 to request connection
            const accounts = await provider.request({
                method: "eth_requestAccounts"
            });

            this.selectedProvider = providerDetail;
            console.log("Connected:", accounts[0]);
            return accounts[0];
        } catch (error) {
            console.error("Connection failed:", error);
        }
    }

    render() {
        const container = document.getElementById("wallets");
        container.innerHTML = "";

        this.providers.forEach(({ info, provider }, uuid) => {
            const button = document.createElement("button");

            // Display wallet icon
            const icon = document.createElement("img");
            icon.src = info.icon;
            icon.width = 32;
            icon.height = 32;
            button.appendChild(icon);

            // Display wallet name
            const name = document.createElement("span");
            name.textContent = info.name;
            button.appendChild(name);

            // Click to connect
            button.onclick = () => this.connect(uuid);

            container.appendChild(button);
        });
    }
}

// Usage
const walletSelector = new WalletSelector();

React Hook Example

import { useEffect, useState } from 'react';

interface Wallet {
    uuid: string;
    name: string;
    icon: string;
    provider: any;
}

export function useWalletDiscovery() {
    const [wallets, setWallets] = useState<Wallet[]>([]);

    useEffect(() => {
        const handleAnnouncement = (event: any) => {
            const { info, provider } = event.detail;

            setWallets(prev => {
                // Avoid duplicates
                if (prev.some(w => w.uuid === info.uuid)) {
                    return prev;
                }

                return [...prev, {
                    uuid: info.uuid,
                    name: info.name,
                    icon: info.icon,
                    provider
                }];
            });
        };

        window.addEventListener(
            'eip6963:announceProvider',
            handleAnnouncement
        );

        // Request wallet announcements
        window.dispatchEvent(new Event('eip6963:requestProvider'));

        return () => {
            window.removeEventListener(
                'eip6963:announceProvider',
                handleAnnouncement
            );
        };
    }, []);

    const connectWallet = async (uuid: string) => {
        const wallet = wallets.find(w => w.uuid === uuid);
        if (!wallet) return;

        try {
            const accounts = await wallet.provider.request({
                method: 'eth_requestAccounts'
            });
            return accounts[0];
        } catch (error) {
            console.error('Failed to connect:', error);
        }
    };

    return { wallets, connectWallet };
}

// Usage in a component
function WalletConnector() {
    const { wallets, connectWallet } = useWalletDiscovery();

    return (
        <div>
            <h2>Select a Wallet</h2>
            {wallets.map(wallet => (
                <button
                    key={wallet.uuid}
                    onClick={() => connectWallet(wallet.uuid)}
                >
                    <img src={wallet.icon} alt={wallet.name} width={32} />
                    <span>{wallet.name}</span>
                </button>
            ))}
        </div>
    );
}

Key Advantages

Eliminates Race Conditions - No longer relies on window.ethereum - Avoids wallet overwrites - Loading order is irrelevant

User Control - Users clearly see all installed wallets - Explicitly choose which wallet to use - Can switch wallets at any time

Developer Friendly - Automatically discovers all compatible wallets - Unified connection flow - No wallet-specific code needed

Better User Experience - Consistent wallet selection interface - Clear wallet branding display - Avoids confusion and errors

Relationship with Existing Standards

Works with EIP-1193 EIP-6963 discovers wallets; EIP-1193 defines interactions:

// EIP-6963: discover wallets
window.dispatchEvent(new Event("eip6963:requestProvider"));

// After obtaining the provider, interact using EIP-1193
const accounts = await provider.request({
    method: "eth_requestAccounts"
});

Backward Compatible Wallets can still inject window.ethereum: - Supports legacy DApps - New DApps use EIP-6963 - Smooth transition

Wallet Adoption

Supported Wallets - MetaMask - Coinbase Wallet - Trust Wallet - Rainbow - Zerion - Rabby - Phantom (planned)

Implementation Guide Wallet developers need to: 1. Implement the EIP-6963 event mechanism 2. Provide wallet metadata (name, icon, rdns) 3. Maintain EIP-1193 compatibility 4. Test coexistence with other wallets

Best Practices

RDNS Naming Use reverse domain names:

com.example.wallet
io.metamask
com.coinbase.wallet

UUID Generation Generate a unique UUID for each wallet instance:

import { v4 as uuidv4 } from 'uuid';

const uuid = uuidv4(); // Different for each instance

Icon Format Use SVG in Data URI format:

const icon = "data:image/svg+xml;base64," + btoa(svgString);

Defensive Programming

// Check event support
if (typeof window !== 'undefined' && window.dispatchEvent) {
    window.dispatchEvent(new Event("eip6963:requestProvider"));
}

// Handle duplicate announcements
const seen = new Set();
window.addEventListener("eip6963:announceProvider", (event) => {
    const { uuid } = event.detail.info;
    if (seen.has(uuid)) return;
    seen.add(uuid);
    // Handle new wallet
});

Migration Guide

Migrating from the Old Approach

// Old approach
if (window.ethereum) {
    await window.ethereum.request({ method: 'eth_requestAccounts' });
}

// New approach
const walletSelector = new WalletSelector();
// Let the user choose a wallet

Supporting Both Approaches

// Prefer EIP-6963
const providers = await discoverProviders();

if (providers.length > 0) {
    // Use wallets discovered via EIP-6963
    await connectWithProvider(providers[0]);
} else if (window.ethereum) {
    // Fall back to the traditional approach
    await window.ethereum.request({ method: 'eth_requestAccounts' });
} else {
    // Prompt user to install a wallet
    alert('Please install a wallet');
}

Tools and Libraries

@metamask/detect-provider

import detectProvider from '@metamask/detect-provider';

const provider = await detectProvider({
    // Automatically uses EIP-6963
    mustBeMetaMask: true,
    silent: false
});

RainbowKit EIP-6963 support is already integrated:

import { connectorsForWallets } from '@rainbow-me/rainbowkit';

const connectors = connectorsForWallets([
    // Automatically discovers all EIP-6963 wallets
]);

Web3Modal The latest version supports EIP-6963:

import { Web3Modal } from '@web3modal/ethereum';

const modal = new Web3Modal({
    // Automatically discovers all wallets
});

Future Outlook

Ecosystem Adoption EIP-6963 is rapidly becoming the standard: - Major wallets are progressively adding support - New wallets implement it by default - DApp frameworks are integrating it

Improvement Directions - Wallet capability negotiation (supported chains, features) - Richer metadata - Mobile adaptation

Integration with EIP-5792 EIP-5792 defines wallet capabilities: - Discover wallets (EIP-6963) - Query wallet capabilities (EIP-5792) - Complete wallet ecosystem