diff --git a/src/@context/MarketMetadata/_types.ts b/src/@context/MarketMetadata/_types.ts index 6bd9f746c..974736147 100644 --- a/src/@context/MarketMetadata/_types.ts +++ b/src/@context/MarketMetadata/_types.ts @@ -49,4 +49,5 @@ export interface MarketMetadataProviderValue { siteContent: SiteContent appConfig: AppConfig getOpcFeeForToken: (tokenAddress: string, chainId: number) => string + approvedBaseTokens: TokenInfo[] } diff --git a/src/@context/MarketMetadata/index.tsx b/src/@context/MarketMetadata/index.tsx index 3c83d82ec..2f8fe981a 100644 --- a/src/@context/MarketMetadata/index.tsx +++ b/src/@context/MarketMetadata/index.tsx @@ -13,8 +13,13 @@ import { opcQuery } from './_queries' import { MarketMetadataProviderValue, OpcFee } from './_types' import siteContent from '../../../content/site.json' import appConfig from '../../../app.config' -import { fetchData, getQueryContext } from '@utils/subgraph' +import { + fetchData, + getQueryContext, + getOpcsApprovedTokens +} from '@utils/subgraph' import { LoggerInstance } from '@oceanprotocol/lib' +import { useNetwork, useConnect } from 'wagmi' const MarketMetadataContext = createContext({} as MarketMetadataProviderValue) @@ -23,7 +28,11 @@ function MarketMetadataProvider({ }: { children: ReactNode }): ReactElement { + const { isLoading } = useConnect() + const { chain } = useNetwork() + const [opcFees, setOpcFees] = useState() + const [approvedBaseTokens, setApprovedBaseTokens] = useState() useEffect(() => { async function getOpcData() { @@ -64,6 +73,25 @@ function MarketMetadataProvider({ }, [opcFees] ) + + // ----------------------------------- + // Get and set approved base tokens list + // ----------------------------------- + const getApprovedBaseTokens = useCallback(async (chainId: number) => { + try { + const approvedTokensList = await getOpcsApprovedTokens(chainId) + setApprovedBaseTokens(approvedTokensList) + LoggerInstance.log('[web3] Approved baseTokens', approvedTokensList) + } catch (error) { + LoggerInstance.error('[web3] Error: ', error.message) + } + }, []) + + useEffect(() => { + if (isLoading) return + getApprovedBaseTokens(chain?.id || 1) + }, [chain?.id, getApprovedBaseTokens, isLoading]) + return ( Promise - // logout: () => Promise } // const web3ModalTheme = { @@ -55,117 +26,29 @@ interface Web3ProviderValue { // hover: 'var(--background-highlight)' // } -// const providerOptions = isBrowser -// ? { -// walletconnect: { -// package: WalletConnectProvider, -// options: { -// infuraId, -// rpc: { -// 137: 'https://polygon-rpc.com', -// 80001: 'https://rpc-mumbai.matic.today' -// } -// } -// } -// } -// : {} - -// export const web3ModalOpts = { -// cacheProvider: true, -// providerOptions, -// theme: web3ModalTheme -// } - const refreshInterval = 20000 // 20 sec. const Web3Context = createContext({} as Web3ProviderValue) function Web3Provider({ children }: { children: ReactNode }): ReactElement { - const { networksList } = useNetworkMetadata() - const { appConfig } = useMarketMetadata() + const { approvedBaseTokens } = useMarketMetadata() + const { networkData } = useNetworkMetadata() + const { chain } = useNetwork() + const { address } = useAccount() + const provider = useProvider() - const [web3, setWeb3] = useState() - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const [web3Provider, setWeb3Provider] = useState() - - // const [web3Modal, setWeb3Modal] = useState() - // const [web3ProviderInfo, setWeb3ProviderInfo] = useState() - const [networkId, setNetworkId] = useState() - const [chainId, setChainId] = useState() - const [networkDisplayName, setNetworkDisplayName] = useState() - const [networkData, setNetworkData] = useState() - const [block, setBlock] = useState() - const [isTestnet, setIsTestnet] = useState() - const [accountId, setAccountId] = useState() - const [accountEns, setAccountEns] = useState() - const [accountEnsAvatar, setAccountEnsAvatar] = useState() - const [web3Loading, setWeb3Loading] = useState(true) const [balance, setBalance] = useState({ eth: '0' }) - const [isSupportedOceanNetwork, setIsSupportedOceanNetwork] = useState(true) - const [approvedBaseTokens, setApprovedBaseTokens] = useState() - - // ----------------------------------- - // Helper: connect to web3 - // ----------------------------------- - // const connect = useCallback(async () => { - // if (!web3Modal) { - // setWeb3Loading(false) - // return - // } - // try { - // setWeb3Loading(true) - // LoggerInstance.log('[web3] Connecting Web3...') - - // const provider = await web3Modal?.connect() - // setWeb3Provider(provider) - - // const web3 = new Web3(provider) - // 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]) - - // ----------------------------------- - // Helper: Get approved base tokens list - // ----------------------------------- - const getApprovedBaseTokens = useCallback(async (chainId: number) => { - try { - const approvedTokensList = await getOpcsApprovedTokens(chainId) - setApprovedBaseTokens(approvedTokensList) - LoggerInstance.log('[web3] Approved baseTokens', approvedTokensList) - } catch (error) { - LoggerInstance.error('[web3] Error: ', error.message) - } - }, []) // ----------------------------------- // Helper: Get user balance // ----------------------------------- const getUserBalance = useCallback(async () => { - if (!accountId || !networkId || !web3 || !networkData) return + if (!address || !chain?.id || !provider) return try { - const userBalance = web3.utils.fromWei( - await web3.eth.getBalance(accountId, 'latest') - ) + const userBalance = utils.formatEther(await provider.getBalance('latest')) const key = networkData.nativeCurrency.symbol.toLowerCase() const balance: UserBalance = { [key]: userBalance } @@ -174,10 +57,10 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement { approvedBaseTokens.map(async (token) => { const { address, decimals, symbol } = token const tokenBalance = await getTokenBalance( - accountId, + address, decimals, address, - web3 + provider ) balance[symbol.toLocaleLowerCase()] = tokenBalance }) @@ -189,87 +72,7 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement { } catch (error) { LoggerInstance.error('[web3] Error: ', error.message) } - }, [accountId, approvedBaseTokens, networkId, web3, networkData]) - - // ----------------------------------- - // Helper: Get user ENS info - // ----------------------------------- - const getUserEns = useCallback(async () => { - if (!accountId) return - - try { - const profile = await getEnsProfile(accountId) - - if (!profile) { - setAccountEns(null) - setAccountEnsAvatar(null) - return - } - - setAccountEns(profile.name) - LoggerInstance.log( - `[web3] ENS name found for ${accountId}:`, - profile.name - ) - - if (profile.avatar) { - setAccountEnsAvatar(profile.avatar) - LoggerInstance.log( - `[web3] ENS avatar found for ${accountId}:`, - profile.avatar - ) - } else { - setAccountEnsAvatar(null) - } - } catch (error) { - LoggerInstance.error('[web3] Error: ', error.message) - } - }, [accountId]) - - // ----------------------------------- - // Create initial Web3Modal instance - // ----------------------------------- - // useEffect(() => { - // if (web3Modal) { - // setWeb3Loading(false) - // return - // } - - // async function init() { - // // note: needs artificial await here so the log message is reached and output - // const web3ModalInstance = await new Web3Modal(web3ModalOpts) - // setWeb3Modal(web3ModalInstance) - // LoggerInstance.log( - // '[web3] Web3Modal instance created.', - // web3ModalInstance - // ) - // } - // init() - // }, [connect, web3Modal]) - - // ----------------------------------- - // Reconnect automatically for returning users - // ----------------------------------- - // useEffect(() => { - // if (!web3Modal?.cachedProvider) return - - // async function connectCached() { - // LoggerInstance.log( - // '[web3] Connecting to cached provider: ', - // web3Modal.cachedProvider - // ) - // await connect() - // } - // connectCached() - // }, [connect, web3Modal]) - - // ----------------------------------- - // Get and set approved base tokens list - // ----------------------------------- - useEffect(() => { - if (web3Loading) return - getApprovedBaseTokens(chainId || 1) - }, [chainId, getApprovedBaseTokens, web3Loading]) + }, [address, approvedBaseTokens, chain?.id, provider]) // ----------------------------------- // Get and set user balance @@ -285,151 +88,8 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement { } }, [getUserBalance]) - // ----------------------------------- - // Get and set user ENS info - // ----------------------------------- - useEffect(() => { - getUserEns() - }, [getUserEns]) - - // ----------------------------------- - // Get and set network metadata - // ----------------------------------- - useEffect(() => { - if (!networkId) return - const networkData = getNetworkDataById(networksList, networkId) - setNetworkData(networkData) - LoggerInstance.log( - networkData - ? `[web3] Network metadata found.` - : `[web3] No network metadata found.`, - networkData - ) - - // Construct network display name - const networkDisplayName = getNetworkDisplayName(networkData) - setNetworkDisplayName(networkDisplayName) - - setIsTestnet(getNetworkType(networkData) !== NetworkType.Mainnet) - - LoggerInstance.log( - `[web3] Network display name set to: ${networkDisplayName}` - ) - }, [networkId, networksList]) - - // ----------------------------------- - // Get and set latest head block - // ----------------------------------- - useEffect(() => { - if (!web3) return - - async function getBlock() { - const block = await web3.eth.getBlockNumber() - setBlock(block) - LoggerInstance.log('[web3] Head block: ', block) - } - getBlock() - }, [web3, networkId]) - - // ----------------------------------- - // Get and set web3 provider info - // ----------------------------------- - // Workaround cause getInjectedProviderName() always returns `MetaMask` - // https://github.com/oceanprotocol/market/issues/332 - // useEffect(() => { - // if (!web3Provider) return - - // const providerInfo = getProviderInfo(web3Provider) - // setWeb3ProviderInfo(providerInfo) - // }, [web3Provider]) - - // ----------------------------------- - // Logout helper - // ----------------------------------- - // async function logout() { - // /* eslint-disable @typescript-eslint/no-explicit-any */ - // if ((web3?.currentProvider as any)?.close) { - // await (web3.currentProvider as any).close() - // } - // /* eslint-enable @typescript-eslint/no-explicit-any */ - - // await web3Modal.clearCachedProvider() - // } - // ----------------------------------- - // Get valid Networks and set isSupportedOceanNetwork - // ----------------------------------- - - useEffect(() => { - if (appConfig.chainIdsSupported.includes(networkId)) { - setIsSupportedOceanNetwork(true) - } else { - setIsSupportedOceanNetwork(false) - } - }, [networkId, appConfig.chainIdsSupported]) - - // ----------------------------------- - // Handle change events - // ----------------------------------- - async function handleChainChanged(chainId: string) { - LoggerInstance.log('[web3] Chain changed', chainId) - const networkId = await web3.eth.net.getId() - setChainId(Number(chainId)) - setNetworkId(Number(networkId)) - } - - async function handleNetworkChanged(networkId: string) { - LoggerInstance.log('[web3] Network changed', networkId) - const chainId = await web3.eth.getChainId() - setNetworkId(Number(networkId)) - setChainId(Number(chainId)) - } - - async function handleAccountsChanged(accounts: string[]) { - LoggerInstance.log('[web3] Account changed', accounts[0]) - setAccountId(accounts[0]) - } - - useEffect(() => { - if (!web3Provider || !web3) return - - web3Provider.on('chainChanged', handleChainChanged) - web3Provider.on('networkChanged', handleNetworkChanged) - web3Provider.on('accountsChanged', handleAccountsChanged) - - return () => { - web3Provider.removeListener('chainChanged', handleChainChanged) - web3Provider.removeListener('networkChanged', handleNetworkChanged) - web3Provider.removeListener('accountsChanged', handleAccountsChanged) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [web3Provider, web3]) - return ( - - {children} - + {children} ) } diff --git a/src/@hooks/useNetworkMetadata/index.ts b/src/@hooks/useNetworkMetadata/index.ts index 9686bd62a..0a7577ede 100644 --- a/src/@hooks/useNetworkMetadata/index.ts +++ b/src/@hooks/useNetworkMetadata/index.ts @@ -1,9 +1,57 @@ import { UseNetworkMetadata } from './types' import networkdata from '../../../content/networks-metadata.json' +import { useEffect, useState } from 'react' +import { + getNetworkDataById, + getNetworkDisplayName, + getNetworkType, + NetworkType +} from './utils' +import { useMarketMetadata } from '@context/MarketMetadata' +import { useNetwork } from 'wagmi' export default function useNetworkMetadata(): UseNetworkMetadata { + const { appConfig } = useMarketMetadata() + const { chain } = useNetwork() + + const [networkDisplayName, setNetworkDisplayName] = useState() + const [networkData, setNetworkData] = useState() + const [isTestnet, setIsTestnet] = useState() + const [isSupportedOceanNetwork, setIsSupportedOceanNetwork] = useState(true) + const networksList: EthereumListsChain[] = networkdata - return { networksList } + + // ----------------------------------- + // Get and set network metadata + // ----------------------------------- + useEffect(() => { + if (!chain?.id) return + + const networkData = getNetworkDataById(networksList, chain.id) + setNetworkData(networkData) + + // Construct network display name + const networkDisplayName = getNetworkDisplayName(networkData) + setNetworkDisplayName(networkDisplayName) + + // Check if network is supported by Ocean Protocol + if (appConfig.chainIdsSupported.includes(chain.id)) { + setIsSupportedOceanNetwork(true) + } else { + setIsSupportedOceanNetwork(false) + } + + // Check if network is testnet + setIsTestnet(getNetworkType(networkData) !== NetworkType.Mainnet) + }, [chain?.id, networksList, appConfig.chainIdsSupported]) + + return { + networksList, + networkDisplayName, + networkData, + isTestnet, + isSupportedOceanNetwork + } } export * from './utils' diff --git a/src/@hooks/useNetworkMetadata/types.ts b/src/@hooks/useNetworkMetadata/types.ts index 58e416b75..4b91e92c7 100644 --- a/src/@hooks/useNetworkMetadata/types.ts +++ b/src/@hooks/useNetworkMetadata/types.ts @@ -1,3 +1,7 @@ export interface UseNetworkMetadata { + networkDisplayName: string + networkData: EthereumListsChain + isTestnet: boolean + isSupportedOceanNetwork: boolean networksList: EthereumListsChain[] } diff --git a/src/@utils/web3.ts b/src/@utils/web3.ts index a42542a73..518e53dd8 100644 --- a/src/@utils/web3.ts +++ b/src/@utils/web3.ts @@ -1,17 +1,15 @@ import { getNetworkDisplayName } from '@hooks/useNetworkMetadata' import { LoggerInstance } from '@oceanprotocol/lib' -import Web3 from 'web3' import { getOceanConfig } from './ocean' -import { AbiItem } from 'web3-utils/types' - import { EthereumClient, modalConnectors, walletConnectProvider } from '@web3modal/ethereum' -import { configureChains, createClient } from 'wagmi' +import { configureChains, createClient, erc20ABI } from 'wagmi' import { mainnet, polygon, bsc, goerli, polygonMumbai } from 'wagmi/chains' +import { ethers, utils } from 'ethers' export const chains = [mainnet, polygon, bsc, goerli, polygonMumbai] @@ -42,10 +40,10 @@ export function accountTruncate(account: string): string { * @param chainId * @returns Web3 instance */ -export async function getDummyWeb3(chainId: number): Promise { - const config = getOceanConfig(chainId) - return new Web3(config.nodeUri) -} +// export async function getDummyWeb3(chainId: number): Promise { +// const config = getOceanConfig(chainId) +// return new Web3(config.nodeUri) +// } export async function addCustomNetwork( web3Provider: any, @@ -143,37 +141,13 @@ export async function getTokenBalance( accountId: string, decimals: number, tokenAddress: string, - web3: Web3 + provider: any ): Promise { - const minABI = [ - { - constant: true, - inputs: [ - { - name: '_owner', - type: 'address' - } - ], - name: 'balanceOf', - outputs: [ - { - name: 'balance', - type: 'uint256' - } - ], - payable: false, - stateMutability: 'view', - type: 'function' - } - ] as AbiItem[] - try { - const token = new web3.eth.Contract(minABI, tokenAddress, { - from: accountId - }) + const token = new ethers.Contract(tokenAddress, erc20ABI, provider) const balance = await token.methods.balanceOf(accountId).call() const adjustedDecimalsBalance = `${balance}${'0'.repeat(18 - decimals)}` - return web3.utils.fromWei(adjustedDecimalsBalance) + return utils.formatEther(adjustedDecimalsBalance) } catch (e) { LoggerInstance.error(`ERROR: Failed to get the balance: ${e.message}`) } diff --git a/src/components/Header/Wallet/Account.tsx b/src/components/Header/Wallet/Account.tsx index d19a01f0a..207568ba6 100644 --- a/src/components/Header/Wallet/Account.tsx +++ b/src/components/Header/Wallet/Account.tsx @@ -1,39 +1,45 @@ import React, { FormEvent } from 'react' import Caret from '@images/caret.svg' import { accountTruncate } from '@utils/web3' -import Loader from '@shared/atoms/Loader' +// import Loader from '@shared/atoms/Loader' import styles from './Account.module.css' -import { useWeb3 } from '@context/Web3' import Avatar from '@shared/atoms/Avatar' +import { useAccount, useProvider, useEnsName, useEnsAvatar } from 'wagmi' +import { useWeb3Modal } from '@web3modal/react' // Forward ref for Tippy.js // eslint-disable-next-line const Account = React.forwardRef((props, ref: any) => { - const { accountId, accountEns, accountEnsAvatar, web3Modal, connect } = - useWeb3() + // const provider = useProvider() + const { address } = useAccount() + const { data: accountEns } = useEnsName({ address, chainId: 1 }) + const { data: accountEnsAvatar } = useEnsAvatar({ address, chainId: 1 }) + const { open } = useWeb3Modal() async function handleActivation(e: FormEvent) { // prevent accidentially submitting a form the button might be in e.preventDefault() - await connect() + await open() } - return !accountId && web3Modal?.cachedProvider ? ( - // Improve user experience for cached provider when connecting takes some time - - ) : accountId ? ( + // return + // !address && provider ? ( + // // Improve user experience for cached provider when connecting takes some time + // + // ) : + return address ? ( diff --git a/src/components/Header/Wallet/Details.tsx b/src/components/Header/Wallet/Details.tsx index 5f0355a36..5a39348f6 100644 --- a/src/components/Header/Wallet/Details.tsx +++ b/src/components/Header/Wallet/Details.tsx @@ -4,20 +4,21 @@ import { useUserPreferences } from '@context/UserPreferences' import Button from '@shared/atoms/Button' import AddToken from '@shared/AddToken' import Conversion from '@shared/Price/Conversion' -import { useWeb3 } from '@context/Web3' import { getOceanConfig } from '@utils/ocean' +import { useNetwork, useProvider, useDisconnect, useAccount } from 'wagmi' import styles from './Details.module.css' +import { useWeb3 } from '@context/Web3' +import { useWeb3Modal } from '@web3modal/react' +import useNetworkMetadata from '@hooks/useNetworkMetadata' export default function Details(): ReactElement { - const { - web3ProviderInfo, - web3Modal, - connect, - logout, - networkData, - networkId, - balance - } = useWeb3() + const { chain } = useNetwork() + const { connector: activeConnector, isConnected } = useAccount() + const { open: openWeb3Modal } = useWeb3Modal() + const { disconnect } = useDisconnect() + const provider = useProvider() + const { balance } = useWeb3() + const { networkData } = useNetworkMetadata() const { locale } = useUserPreferences() const [mainCurrency, setMainCurrency] = useState() @@ -27,19 +28,19 @@ export default function Details(): ReactElement { }>() useEffect(() => { - if (!networkId) return + if (!chain?.id) return const symbol = networkData?.nativeCurrency.symbol setMainCurrency(symbol) - const oceanConfig = getOceanConfig(networkId) + const oceanConfig = getOceanConfig(chain.id) oceanConfig && setOceanTokenMetadata({ address: oceanConfig.oceanTokenAddress, symbol: oceanConfig.oceanTokenSymbol }) - }, [networkData, networkId]) + }, [networkData, chain?.id]) return (
@@ -65,10 +66,10 @@ export default function Details(): ReactElement {
  • - - {web3ProviderInfo?.name} + {/* */} + {activeConnector?.name} - {web3ProviderInfo?.name === 'MetaMask' && ( + {activeConnector?.name === 'MetaMask' && (

    -