diff --git a/package-lock.json b/package-lock.json index b08a80d2d..833ef6836 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "dependencies": { "@coingecko/cryptoformat": "^0.5.4", "@loadable/component": "^5.15.2", + "@metamask/detect-provider": "^2.0.0", "@oceanprotocol/art": "^3.2.0", "@oceanprotocol/lib": "^2.6.0", "@oceanprotocol/typographies": "^0.1.0", @@ -4267,6 +4268,14 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/@metamask/detect-provider": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@metamask/detect-provider/-/detect-provider-2.0.0.tgz", + "integrity": "sha512-sFpN+TX13E9fdBDh9lvQeZdJn4qYoRb/6QF2oZZK/Pn559IhCFacPMU1rMuqyXoFQF3JSJfii2l98B87QDPeCQ==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@motionone/animation": { "version": "10.15.1", "resolved": "https://registry.npmjs.org/@motionone/animation/-/animation-10.15.1.tgz", @@ -49988,6 +49997,11 @@ "integrity": "sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==", "dev": true }, + "@metamask/detect-provider": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@metamask/detect-provider/-/detect-provider-2.0.0.tgz", + "integrity": "sha512-sFpN+TX13E9fdBDh9lvQeZdJn4qYoRb/6QF2oZZK/Pn559IhCFacPMU1rMuqyXoFQF3JSJfii2l98B87QDPeCQ==" + }, "@motionone/animation": { "version": "10.15.1", "resolved": "https://registry.npmjs.org/@motionone/animation/-/animation-10.15.1.tgz", diff --git a/package.json b/package.json index ab258aa54..5c777d8ee 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "dependencies": { "@coingecko/cryptoformat": "^0.5.4", "@loadable/component": "^5.15.2", + "@metamask/detect-provider": "^2.0.0", "@oceanprotocol/art": "^3.2.0", "@oceanprotocol/lib": "^2.6.0", "@oceanprotocol/typographies": "^0.1.0", diff --git a/src/@context/Web3.tsx b/src/@context/Web3.tsx index 1e51758f8..4ee4d0de0 100644 --- a/src/@context/Web3.tsx +++ b/src/@context/Web3.tsx @@ -9,10 +9,9 @@ import React, { } from 'react' import Web3 from 'web3' import { Web3Modal } from '@web3modal/standalone' -import { infuraProjectId as infuraId } from '../../app.config' import { LoggerInstance } from '@oceanprotocol/lib' -import { isBrowser } from '@utils/index' import SignClient from '@walletconnect/sign-client' +import detectEthereumProvider from '@metamask/detect-provider' import { getEnsProfile } from '@utils/ens' import useNetworkMetadata, { getNetworkDataById, @@ -28,7 +27,6 @@ interface Web3ProviderValue { web3: Web3 // eslint-disable-next-line @typescript-eslint/no-explicit-any web3Provider: any - web3Modal: Web3Modal // web3ProviderInfo: IProviderInfo accountId: string accountEns: string @@ -43,18 +41,11 @@ interface Web3ProviderValue { web3Loading: boolean isSupportedOceanNetwork: boolean approvedBaseTokens: TokenInfo[] - connect: () => Promise + connectWeb3Modal: () => Promise + connectMetaMask: () => Promise logout: () => Promise } -// const web3ModalTheme = { -// background: 'var(--background-body)', -// main: 'var(--font-color-heading)', -// secondary: 'var(--brand-grey-light)', -// border: 'var(--border-color)', -// hover: 'var(--background-highlight)' -// } - // const providerOptions = isBrowser // ? { // walletconnect: { @@ -81,8 +72,6 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement { const [web3, setWeb3] = useState() // eslint-disable-next-line @typescript-eslint/no-explicit-any const [web3Provider, setWeb3Provider] = useState() - const [signClient, setSignClient] = useState() - const [web3Modal, setWeb3Modal] = useState() // const [web3ProviderInfo, setWeb3ProviderInfo] = useState() const [networkId, setNetworkId] = useState() @@ -102,20 +91,42 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement { const [approvedBaseTokens, setApprovedBaseTokens] = useState() // ----------------------------------- - // Helper: connect to web3 + // Helper: get web3 provider from MetaMask // ----------------------------------- - const connect = useCallback(async () => { - if (!web3Modal || !signClient) { - setWeb3Loading(false) - return - } - + const connectMetaMask = useCallback(async () => { try { setWeb3Loading(true) LoggerInstance.log('[web3] Connecting Web3...') + const provider = await detectEthereumProvider() + setWeb3Provider(provider) + } catch (error) { + LoggerInstance.error('[web3] Error: ', error.message) + } finally { + setWeb3Loading(false) + } + }, []) + + // ----------------------------------- + // Helper: get web3 provider from Web3Modal + // ----------------------------------- + const connectWeb3Modal = useCallback(async () => { + try { + setWeb3Loading(true) + LoggerInstance.log('[web3] Connecting Web3...') + + const web3Modal = new Web3Modal({ + projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID, + standaloneChains: ['eip155:1'], + walletConnectVersion: 2 + // theme: web3ModalTheme + }) + const signClient = await SignClient.init({ + projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID + }) + if (signClient) { - const namespaces = { + const requiredNamespaces = { eip155: { methods: ['eth_sign'], chains: ['eip155:1'], @@ -123,42 +134,24 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement { } } const { uri, approval } = await signClient.connect({ - requiredNamespaces: namespaces + requiredNamespaces }) if (uri) { const provider = await web3Modal.openModal({ uri, - standaloneChains: namespaces.eip155.chains + standaloneChains: requiredNamespaces.eip155.chains }) await approval() setWeb3Provider(provider) web3Modal.closeModal() } } - - if (!web3Provider) return - - const web3 = new Web3(web3Provider) - setWeb3(web3) - LoggerInstance.log('[web3] Web3 created.', web3) - - const networkId = await web3.eth.net.getId() - setNetworkId(networkId) - LoggerInstance.log('[web3] network id ', networkId) - - const chainId = await web3.eth.getChainId() - setChainId(chainId) - LoggerInstance.log('[web3] chain id ', chainId) - - const accountId = (await web3.eth.getAccounts())[0] - setAccountId(accountId) - LoggerInstance.log('[web3] account id', accountId) } catch (error) { LoggerInstance.error('[web3] Error: ', error.message) } finally { setWeb3Loading(false) } - }, [web3Modal, web3Provider, signClient]) + }, []) // ----------------------------------- // Helper: Get approved base tokens list @@ -244,30 +237,34 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement { }, [accountId]) // ----------------------------------- - // Create initial Web3Modal instance + // Init Web3 // ----------------------------------- useEffect(() => { - if (web3Modal) { - setWeb3Loading(false) - return - } + if (!web3Provider) return - async function init() { - const web3Modal = new Web3Modal({ - projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID, - standaloneChains: ['eip155:1'], - walletConnectVersion: 2 - // theme: web3ModalTheme - }) - const signClient = await SignClient.init({ - projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID - }) - setWeb3Modal(web3Modal) - setSignClient(signClient) - LoggerInstance.log('[web3] Web3Modal instance created.', web3Modal) + async function initWeb3() { + try { + const web3 = new Web3(web3Provider) + setWeb3(web3) + LoggerInstance.log('[web3] Web3 created.', web3) + + const networkId = await web3.eth.net.getId() + setNetworkId(networkId) + LoggerInstance.log('[web3] network id ', networkId) + + const chainId = await web3.eth.getChainId() + setChainId(chainId) + LoggerInstance.log('[web3] chain id ', chainId) + + const accountId = (await web3.eth.getAccounts())[0] + setAccountId(accountId) + LoggerInstance.log('[web3] account id', accountId) + } catch (error) { + LoggerInstance.error('[web3] Error: ', error.message) + } } - init() - }, [web3Modal]) + initWeb3() + }, [web3Provider]) // ----------------------------------- // Reconnect automatically for returning users @@ -431,7 +428,6 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement { value={{ web3, web3Provider, - web3Modal, accountId, accountEns, accountEnsAvatar, @@ -445,7 +441,8 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement { web3Loading, isSupportedOceanNetwork, approvedBaseTokens, - connect, + connectWeb3Modal, + connectMetaMask, logout }} > diff --git a/src/components/Header/Wallet/Account.tsx b/src/components/Header/Wallet/Account.tsx index d19a01f0a..fc9843955 100644 --- a/src/components/Header/Wallet/Account.tsx +++ b/src/components/Header/Wallet/Account.tsx @@ -1,30 +1,25 @@ -import React, { FormEvent } from 'react' +import React, { useState } from 'react' import Caret from '@images/caret.svg' import { accountTruncate } from '@utils/web3' -import Loader from '@shared/atoms/Loader' import styles from './Account.module.css' import { useWeb3 } from '@context/Web3' import Avatar from '@shared/atoms/Avatar' +import Modal from '@components/@shared/atoms/Modal' // Forward ref for Tippy.js // eslint-disable-next-line const Account = React.forwardRef((props, ref: any) => { - const { accountId, accountEns, accountEnsAvatar, web3Modal, connect } = - useWeb3() + const { + accountId, + accountEns, + accountEnsAvatar, + connectWeb3Modal, + connectMetaMask + } = useWeb3() - async function handleActivation(e: FormEvent) { - // prevent accidentially submitting a form the button might be in - e.preventDefault() + const [isModalOpen, setIsModalOpen] = useState(false) - await connect() - } - - return !accountId && web3Modal?.cachedProvider ? ( - // Improve user experience for cached provider when connecting takes some time - - ) : accountId ? ( + return accountId ? ( ) : ( - + <> + + setIsModalOpen(false)} + > + + + + ) })