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:
- DApp requests discovery: Dispatches an
eip6963:requestProviderevent - Wallets respond: Each wallet dispatches an
eip6963:announceProviderevent - DApp displays list: Collects all wallets and lets the user choose
- 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:
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:
UUID Generation Generate a unique UUID for each wallet instance:
Icon Format Use SVG in Data URI format:
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
Related Links¶
- EIP-6963 Official Specification
- EIP-6963 Official Website
- MetaMask EIP-6963 Implementation Guide
- WalletConnect EIP-6963 Example