95 lines
2.7 KiB
TypeScript
95 lines
2.7 KiB
TypeScript
/**
|
|
* 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<any>;
|
|
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<string, EIP6963ProviderDetail>();
|
|
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 {}
|
|
}
|
|
}
|
|
}
|