diff --git a/env.d.ts b/env.d.ts index 017a0e3b84..6d2987cd7c 100644 --- a/env.d.ts +++ b/env.d.ts @@ -25,6 +25,7 @@ type BaseWalletProvider = { identityFlag?: string checkIdentity?: (provider: WalletProvider) => boolean } + emit: (eventName: string | symbol, ...args: unknown[]) => boolean on: ( eventName: string | symbol, listener: (...args: unknown[]) => void @@ -60,6 +61,7 @@ interface Window { shouldReload?: boolean ) => void routeToNewNonTahoDefault: (request: unknown) => Promise + reemitTahoEvent: (event: string | symbol, ...args: unknown[]) => boolean getProviderInfo: ( provider: WalletProvider ) => WalletProvider["providerInfo"] diff --git a/src/window-provider.ts b/src/window-provider.ts index 87a99ad284..ecc718d5b9 100644 --- a/src/window-provider.ts +++ b/src/window-provider.ts @@ -37,6 +37,7 @@ const tahoRoutedProperties = new Set( // function below. const metaMaskMock: WalletProvider = { isMetaMask: true, + emit: (_: string | symbol, ...__: unknown[]) => false, on: () => {}, removeListener: () => {}, _state: { @@ -49,12 +50,29 @@ const metaMaskMock: WalletProvider = { } // A tracking list of MetaMask wrappers that allow us to avoid double-wrapping. -const metaMaskWrappers = new Set() +// The map key is the provider that *was wrapped*, i.e. the original, and the +// map value is the wrapping provider, i.e. the proxy that makes sure default +// settings are respected. +const metaMaskWrapperByWrappedProvider = new Map< + WalletProvider, + WalletProvider +>() let metaMaskMockWrapper: WalletProvider | undefined -function wrapMetaMaskProvider(provider: WalletProvider): WalletProvider { - if (metaMaskWrappers.has(provider)) { - return provider +function wrapMetaMaskProvider(provider: WalletProvider): { + provider: WalletProvider + wasMetaMaskLike: boolean +} { + if (metaMaskWrapperByWrappedProvider.has(provider)) { + return { + // `get` is guarded by the `has` check above. + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + provider: metaMaskWrapperByWrappedProvider.get(provider)!, + wasMetaMaskLike: true, + } + } + if (new Set(metaMaskWrapperByWrappedProvider.values()).has(provider)) { + return { provider, wasMetaMaskLike: true } } if ( @@ -89,16 +107,19 @@ function wrapMetaMaskProvider(provider: WalletProvider): WalletProvider { }, }) - metaMaskWrappers.add(wrapper) + metaMaskWrapperByWrappedProvider.set(provider, wrapper) if (provider === metaMaskMock) { metaMaskMockWrapper = wrapper } - return wrapper + return { + provider: wrapper, + wasMetaMaskLike: true, + } } - return provider + return { provider, wasMetaMaskLike: false } } /** @@ -132,20 +153,21 @@ function metaMaskWrappedProviders( return { defaultManagedProviders, metaMaskDetected } } - const defaultManaged = wrapMetaMaskProvider(provider) + const { provider: defaultManaged, wasMetaMaskLike } = + wrapMetaMaskProvider(provider) return { defaultManagedProviders: [...defaultManagedProviders, defaultManaged], - // MetaMask is the only one whose return value will differ from the - // underlying object. - metaMaskDetected: metaMaskDetected || defaultManaged !== provider, + metaMaskDetected: metaMaskDetected || wasMetaMaskLike, } }, { defaultManagedProviders: [], metaMaskDetected: false } ) if (!metaMaskDetected && tahoIsDefault) { - return [wrapMetaMaskProvider(metaMaskMock), ...defaultManagedProviders] + const { provider: metaMaskMockProvider } = + wrapMetaMaskProvider(metaMaskMock) + return [metaMaskMockProvider, ...defaultManagedProviders] } return defaultManagedProviders @@ -173,10 +195,15 @@ if (!window.walletRouter) { ]), ].filter((item) => item !== undefined) + const wrappedLastInjectedProvider: WalletProvider | undefined = + window.ethereum === undefined + ? undefined + : wrapMetaMaskProvider(window.ethereum).provider + Object.defineProperty(window, "walletRouter", { value: { currentProvider: window.tally, - lastInjectedProvider: window.ethereum, + lastInjectedProvider: wrappedLastInjectedProvider, tallyProvider: window.tally, providers: dedupedProviders, shouldSetTallyForCurrentProvider( @@ -238,13 +265,28 @@ if (!window.walletRouter) { } ) }, + reemitTahoEvent(event: string | symbol, ...args: unknown[]): boolean { + if ( + this.currentProvider !== this.tallyProvider || + this.lastInjectedProvider === undefined || + this.currentProvider === this.lastInjectedProvider + ) { + return false + } + + return this.lastInjectedProvider.emit(event, ...args) + }, setSelectedProvider() {}, addProvider(newProvider: WalletProvider) { - if (!this.providers.includes(newProvider)) { - this.providers.push(wrapMetaMaskProvider(newProvider)) + const wrappedProvider = wrapMetaMaskProvider(newProvider).provider + if ( + !this.providers.includes(newProvider) && + !this.providers.includes(wrappedProvider) + ) { + this.providers.push(wrappedProvider) } - this.lastInjectedProvider = newProvider + this.lastInjectedProvider = wrappedProvider }, }, writable: false, diff --git a/window-provider/index.ts b/window-provider/index.ts index f885f876f8..f27e107324 100644 --- a/window-provider/index.ts +++ b/window-provider/index.ts @@ -367,6 +367,17 @@ export default class TahoWindowProvider extends EventEmitter { }) } + override emit(event: string | symbol, ...args: unknown[]): boolean { + const hadAdditionalListeners = window.walletRouter?.reemitTahoEvent( + event, + ...args + ) + + const hadDirectListeners = super.emit(event, ...args) + + return hadAdditionalListeners || hadDirectListeners + } + handleChainIdChange(chainId: string): void { this.chainId = chainId this.emit("chainChanged", chainId)