diff --git a/packages/wallet-ui/package.json b/packages/wallet-ui/package.json index c6349ca3..a5c2b668 100644 --- a/packages/wallet-ui/package.json +++ b/packages/wallet-ui/package.json @@ -13,7 +13,6 @@ "@fortawesome/free-solid-svg-icons": "^6.1.1", "@fortawesome/react-fontawesome": "^0.1.18", "@headlessui/react": "^1.6.4", - "@metamask/detect-provider": "^1.2.0", "@metamask/jazzicon": "github:metamask/jazzicon#d923914fda6a8795f74c2e66134f73cd72070667", "@mui/icons-material": "^5.6.2", "@mui/material": "^5.6.2", diff --git a/packages/wallet-ui/src/hooks/useHasMetamask.ts b/packages/wallet-ui/src/hooks/useHasMetamask.ts index 6e42962b..ae7a39ce 100644 --- a/packages/wallet-ui/src/hooks/useHasMetamask.ts +++ b/packages/wallet-ui/src/hooks/useHasMetamask.ts @@ -1,22 +1,96 @@ -import detectEthereumProvider from '@metamask/detect-provider'; import { useEffect, useState } from 'react'; import { useAppDispatch } from 'hooks/redux'; import { setProvider } from 'slices/walletSlice'; import { enableLoadingWithMessage, disableLoading } from 'slices/UISlice'; +interface MetaMaskProvider { + isMetaMask: boolean; + request(options: { method: string }): Promise; +} + +declare global { + interface Window { + ethereum?: MetaMaskProvider; + } +} + +function isMetaMaskProvider(obj: unknown): obj is MetaMaskProvider { + return obj !== null && typeof obj === 'object' && obj.hasOwnProperty('isMetaMask') && obj.hasOwnProperty('request'); +} + +function detectMetaMaskProvider( + windowObject: Window & typeof globalThis, + { timeout = 3000 } = {}, +): Promise { + let handled = false; + return new Promise((resolve) => { + const handleEIP6963Provider = (event: CustomEvent) => { + const { info, provider } = event.detail; + if (['io.metamask', 'io.metamask.flask'].includes(info.rdns) && isMetaMaskProvider(provider)) { + resolve(provider); + handled = true; + } + }; + + if (typeof windowObject.addEventListener === 'function') { + windowObject.addEventListener('eip6963:announceProvider', (event: Event) => { + handleEIP6963Provider(event as CustomEvent); + }); + } + + setTimeout(() => { + if (!handled) { + resolve(null); + } + }, timeout); + + // Notify event listeners and other parts of the dapp that a provider is requested. + if (typeof windowObject.dispatchEvent === 'function') { + windowObject.dispatchEvent(new Event('eip6963:requestProvider')); + } + }); +} + +async function waitForMetaMaskProvider( + windowObject: Window & typeof globalThis, + { timeout = 1000, retries = 0 } = {}, +): Promise { + return detectMetaMaskProvider(windowObject, { timeout }) + .catch(function () { + return null; + }) + .then(function (provider) { + if (provider || retries === 0) { + return provider; + } + return waitForMetaMaskProvider(windowObject, { + timeout, + retries: retries - 1, + }); + }); +} + +async function detectMetamaskSupport(windowObject: Window & typeof globalThis) { + const provider = await waitForMetaMaskProvider(windowObject, { retries: 3 }); + return provider; +} + export const useHasMetamask = () => { const dispatch = useAppDispatch(); const [hasMetamask, setHasMetamask] = useState(null); + useEffect(() => { const init = async () => { try { dispatch(enableLoadingWithMessage('Detecting Metamask...')); - //make sure mm has installed - if (await detectMetamask()) { - //metamask SDK is not support when multiple wallet installed, and each wallet may injected window.ethereum, some may override isMetamask - const _provider = await getProvider(); - dispatch(setProvider(_provider)); - setHasMetamask(_provider != null); + const provider = await detectMetamaskSupport(window); + // Use the new detection method + + if (provider && (await isSupportSnap(provider))) { + window.ethereum = provider; + dispatch(setProvider(provider)); + setHasMetamask(provider != null); + return window.ethereum; } else { dispatch(setProvider(null)); setHasMetamask(false); @@ -36,40 +110,6 @@ export const useHasMetamask = () => { }; }; -export const detectMetamask = async () => { - try { - const hasMetamask = await detectEthereumProvider({ mustBeMetaMask: true }); - if (hasMetamask) { - return true; - } - return false; - } catch (e) { - console.log('Error', e); - return false; - } -}; - -export const getProvider = async () => { - let { ethereum } = window as any; - let providers = [ethereum]; - - //ethereum.detected or ethereum.providers may exist when more than 1 wallet installed - if ('detected' in ethereum) { - providers = ethereum['detected']; - } else if ('providers' in ethereum) { - providers = ethereum['providers']; - } - - //delect provider by sending request - for (const provider of providers) { - if (provider && (await isSupportSnap(provider))) { - window.ethereum = provider; - return window.ethereum; - } - } - return null; -}; - const isSupportSnap = async (provider: any) => { try { await provider.request({ diff --git a/packages/wallet-ui/src/hooks/useHasMetamaskFlask.ts b/packages/wallet-ui/src/hooks/useHasMetamaskFlask.ts deleted file mode 100644 index 919bfdc5..00000000 --- a/packages/wallet-ui/src/hooks/useHasMetamaskFlask.ts +++ /dev/null @@ -1,39 +0,0 @@ -//TODO: remove when metamask is released with snap support -import detectEthereumProvider from '@metamask/detect-provider'; -import { useEffect, useState } from 'react'; - -export const useHasMetamaskFlask = () => { - const [hasMetamaskFlask, setHasMetamaskFlask] = useState(null); - - const detectMetamaskFlask = async () => { - try { - const provider = (await detectEthereumProvider({ - mustBeMetaMask: false, - silent: true, - })) as any | undefined; - const isFlask = (await provider?.request({ method: 'web3_clientVersion' }))?.includes('flask'); - if (provider && isFlask) { - return true; - } - return false; - } catch (e) { - console.log('Error', e); - return false; - } - }; - - useEffect(() => { - detectMetamaskFlask() - .then((result) => { - setHasMetamaskFlask(result); - }) - .catch((err) => { - console.error(err); - setHasMetamaskFlask(false); - }); - }, []); - - return { - hasMetamaskFlask, - }; -};