diff --git a/src.ts/providers/provider-browser.ts b/src.ts/providers/provider-browser.ts index 8b45957625..8f07efe84e 100644 --- a/src.ts/providers/provider-browser.ts +++ b/src.ts/providers/provider-browser.ts @@ -42,7 +42,24 @@ export type DebugEventBrowserProvider = { * currently do. */ export class BrowserProvider extends JsonRpcApiPollingProvider { - #request: (method: string, params: Array | Record) => Promise; + protected ethereum: Eip1193Provider; + + #request = async (method: string, params: Array | Record) => { + const payload = { method, params }; + this.emit("debug", { action: "sendEip1193Request", payload }); + try { + const result = await this.ethereum.request(payload); + this.emit("debug", { action: "receiveEip1193Result", result }); + return result; + } catch (e: any) { + const error = new Error(e.message); + (error).code = e.code; + (error).data = e.data; + (error).payload = payload; + this.emit("debug", { action: "receiveEip1193Error", error }); + throw error; + } + }; /** * Connnect to the %%ethereum%% provider, optionally forcing the @@ -50,25 +67,7 @@ export class BrowserProvider extends JsonRpcApiPollingProvider { */ constructor(ethereum: Eip1193Provider, network?: Networkish) { assertArgument(ethereum && ethereum.request, "invalid EIP-1193 provider", "ethereum", ethereum); - super(network, { batchMaxCount: 1 }); - - this.#request = async (method: string, params: Array | Record) => { - const payload = { method, params }; - this.emit("debug", { action: "sendEip1193Request", payload }); - try { - const result = await ethereum.request(payload); - this.emit("debug", { action: "receiveEip1193Result", result }); - return result; - } catch (e: any) { - const error = new Error(e.message); - (error).code = e.code; - (error).data = e.data; - (error).payload = payload; - this.emit("debug", { action: "receiveEip1193Error", error }); - throw error; - } - }; } async send(method: string, params: Array | Record): Promise { diff --git a/src.ts/providers/provider-multi-injected-browser.ts b/src.ts/providers/provider-multi-injected-browser.ts new file mode 100644 index 0000000000..a1fa98cf78 --- /dev/null +++ b/src.ts/providers/provider-multi-injected-browser.ts @@ -0,0 +1,120 @@ +import { Networkish } from './network'; +import { BrowserProvider, Eip1193Provider } from './provider-browser'; + +declare global { + interface WindowEventMap { + 'eip6963:announceProvider': Eip6963AnnounceProviderEvent; + } +} + +export class Eip6963RequestProviderEvent extends Event { + constructor() { + super('eip6963:requestProvider'); + } +} + +export interface Eip6963AnnounceProviderEvent extends Event { + type: 'eip6963:announceProvider'; + detail: Eip6963ProviderDetail; +} + +export interface Eip6963ProviderDetail { + info: Eip6963ProviderInfo; + provider: Eip1193Provider; +} + +export interface Eip6963ProviderInfo { + /** + * Unique identifier of the wallet extension announcement, keep in mind it + * changes on every request-announcement cycle + */ + uuid: string; + /** + * Name of the wallet extension + */ + name: string; + /** + * Icon for the wallet extension + */ + icon: string; + /** + * Reverse DNS name of the wallet extension + */ + rdns: string; +} + +export interface Eip6963ProviderFilter { + rdns?: string; +} + +/** + * MultiInjectedBrowserProvider is a provider that wrapper around `BrowserProvider`. + * It supports EIP6963(https://eips.ethereum.org/EIPS/eip-6963) compliant + * provider protocols and automatically select one to use. + */ +export class MultiInjectedBrowserProvider extends BrowserProvider { + #options?: Eip6963ProviderFilter; + + // This will hold the details of the providers received + #providers: Eip6963ProviderDetail[]; + + constructor(options?: Eip6963ProviderFilter, network?: Networkish) { + const notAvailableProvider: Eip1193Provider = { + request: async () => { + throw new Error('No provider available'); + }, + }; + super(notAvailableProvider, network); + + this.#options = options; + + const handleEip6963Event = (event: Eip6963AnnounceProviderEvent) => { + const providerDetail = event.detail; + const isExist = this.#providers.some( + ({ info }) => info.uuid === providerDetail.info.uuid + ); + if (!isExist) { + this.#providers = [...this.#providers, providerDetail]; + } + + this.switchEip6963Provider(this.#options); + }; + + // subscribe to the announceProvider event + window.addEventListener('eip6963:announceProvider', handleEip6963Event); + + // request for the provider details + window.dispatchEvent(new Eip6963RequestProviderEvent()); + } + + /** + * Switch the EIP-6963 provider detail. + * + * @param filter.rdns filter the provider by reverse DNS name + * @returns EIP-6963 provider details or null + */ + switchEip6963Provider( + filter?: Eip6963ProviderFilter + ): Eip6963ProviderDetail | null { + const provider = + this.#providers.find( + (providerDetail) => providerDetail.info.rdns === filter?.rdns + ) || + this.#providers[0] || + null; + + if (provider?.provider && provider.provider !== this.ethereum) { + this.ethereum = provider.provider; + } + return provider; + } + + /** + * Get the list of EIP-6963 providers details. + * + * @returns EIP-6963 providers details list + */ + getEip6963Providers(): Eip6963ProviderDetail[] { + return this.#providers; + } +}