/** * EIP-6963 Multi-Provider Discovery * * Discovers all injected browser wallets (MetaMask, Rainbow, etc.) * via the standardized EIP-6963 event protocol. Zero dependencies. * * @see https://eips.ethereum.org/EIPS/eip-6963 */ // ============================================================================ // TYPES // ============================================================================ export interface EIP6963ProviderInfo { uuid: string; name: string; icon: string; rdns: string; } export interface EIP1193Provider { request(args: { method: string; params?: any[] }): Promise; on?(event: string, handler: (...args: any[]) => void): void; removeListener?(event: string, handler: (...args: any[]) => void): void; } export interface EIP6963ProviderDetail { info: EIP6963ProviderInfo; provider: EIP1193Provider; } interface EIP6963AnnounceProviderEvent extends Event { detail: EIP6963ProviderDetail; } // ============================================================================ // WALLET PROVIDER DISCOVERY // ============================================================================ export class WalletProviderDiscovery { private providers = new Map(); private listeners = new Set<(providers: EIP6963ProviderDetail[]) => void>(); private listening = false; private handler: ((e: Event) => void) | null = null; start(): void { if (this.listening) return; this.listening = true; this.handler = (e: Event) => { const detail = (e as EIP6963AnnounceProviderEvent).detail; if (!detail?.info?.uuid || !detail?.provider) return; this.providers.set(detail.info.uuid, detail); this.notify(); }; window.addEventListener('eip6963:announceProvider', this.handler); window.dispatchEvent(new Event('eip6963:requestProvider')); } stop(): void { if (!this.listening || !this.handler) return; window.removeEventListener('eip6963:announceProvider', this.handler); this.handler = null; this.listening = false; } getProviders(): EIP6963ProviderDetail[] { return [...this.providers.values()]; } getProvider(uuid: string): EIP6963ProviderDetail | undefined { return this.providers.get(uuid); } getProviderByRdns(rdns: string): EIP6963ProviderDetail | undefined { for (const detail of this.providers.values()) { if (detail.info.rdns === rdns) return detail; } return undefined; } onProvidersChanged(cb: (providers: EIP6963ProviderDetail[]) => void): () => void { this.listeners.add(cb); return () => this.listeners.delete(cb); } private notify(): void { const list = this.getProviders(); for (const cb of this.listeners) { try { cb(list); } catch {} } } }