import React, { useContext, useState, useEffect, createContext, ReactElement, useCallback, ReactNode } from 'react' import Web3 from 'web3' import ProviderStatus from './ProviderStatus' import { Ocean, Logger, Account, Config } from '@oceanprotocol/lib' import Web3Modal, { ICoreOptions } from 'web3modal' import { getDefaultProviders } from './getDefaultProviders' import { getAccountId, getBalance } from 'utils' import { ConfigHelperConfig } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper' import { AccountPurgatoryData, getAccountPurgatoryData } from 'utils/getPurgatoryData' interface Balance { eth: string | undefined ocean: string | undefined } interface OceanProviderValue { web3: Web3 | undefined web3Provider: any web3Modal: Web3Modal ocean: Ocean config: Config | ConfigHelperConfig account: Account isInPurgatory: boolean purgatoryData: AccountPurgatoryData accountId: string balance: Balance networkId: number | undefined status: ProviderStatus connect: (config?: Config) => Promise logout: () => Promise refreshBalance: () => Promise } const OceanContext = createContext({} as OceanProviderValue) function OceanProvider({ initialConfig, web3ModalOpts, children }: { initialConfig: Config | ConfigHelperConfig web3ModalOpts?: Partial children: ReactNode }): ReactElement { const [web3, setWeb3] = useState() const [web3Provider, setWeb3Provider] = useState() const [ocean, setOcean] = useState() const [web3Modal, setWeb3Modal] = useState() const [networkId, setNetworkId] = useState() const [account, setAccount] = useState() const [accountId, setAccountId] = useState() const [isInPurgatory, setIsInPurgatory] = useState(false) const [purgatoryData, setPurgatoryData] = useState() const [config, setConfig] = useState(initialConfig) const [balance, setBalance] = useState({ eth: undefined, ocean: undefined }) const [status, setStatus] = useState( ProviderStatus.NOT_AVAILABLE ) const setPurgatory = useCallback(async (address: string): Promise => { if (!address) return try { const result = await getAccountPurgatoryData(address) if (result?.address !== undefined) { setIsInPurgatory(true) setPurgatoryData(result) } else { setIsInPurgatory(false) } setPurgatoryData(result) } catch (error) { Logger.error(error) } }, []) const init = useCallback(async () => { Logger.log('Ocean Provider init') window && window.ethereum && (window.ethereum.autoRefreshOnNetworkChange = false) Logger.log('Web3Modal init.') const web3ModalInstance = new Web3Modal( web3ModalOpts || (await getDefaultProviders()) ) setWeb3Modal(web3ModalInstance) Logger.log('Web3Modal instance created.', web3ModalInstance) }, [web3ModalOpts]) const connect = useCallback( async (newConfig?: Config | ConfigHelperConfig) => { try { Logger.log('Connecting ...', newConfig) newConfig && setConfig(newConfig) const provider = await web3Modal?.connect() setWeb3Provider(provider) const web3 = new Web3(provider) setWeb3(web3) Logger.log('Web3 created.', web3) const networkId = web3 && (await web3.eth.net.getId()) setNetworkId(networkId) Logger.log('network id ', networkId) config.web3Provider = web3 const ocean = await Ocean.getInstance(config) setOcean(ocean) Logger.log('Ocean instance created.', ocean) setStatus(ProviderStatus.CONNECTED) const account = (await ocean.accounts.list())[0] setAccount(account) Logger.log('Account ', account) const accountId = await getAccountId(web3) setAccountId(accountId) Logger.log('account id', accountId) const balance = await getBalance(account) setBalance(balance) Logger.log('balance', JSON.stringify(balance)) } catch (error) { Logger.error(error) } }, [config, web3Modal] ) // On mount setup Web3Modal instance useEffect(() => { init() }, [init]) // Connect automatically to cached provider if present useEffect(() => { if (!web3Modal) return web3Modal.cachedProvider && connect() }, [web3Modal, connect]) async function refreshBalance() { const balance = account && (await getBalance(account)) setBalance(balance) } async function logout() { // TODO: #67 check how is the proper way to logout web3Modal?.clearCachedProvider() } // TODO: #68 Refetch balance periodically, or figure out some event to subscribe to useEffect(() => { if (!accountId) return console.log('balanc ref', accountId) setPurgatory(accountId) }, [accountId]) useEffect(() => { const handleAccountsChanged = async (accounts: string[]) => { Logger.debug("Handling 'accountsChanged' event with payload", accounts) connect() } // web3Modal && web3Modal.on('connect', handleConnect) if (web3Provider !== undefined && web3Provider !== null) { web3Provider.on('accountsChanged', handleAccountsChanged) // web3Provider.on('chainChanged', handleNetworkChanged) return () => { web3Provider.removeListener('accountsChanged', handleAccountsChanged) // web3Provider.removeListener('chainChanged', handleNetworkChanged) } } }, [web3Modal, web3Provider, connect]) return ( {children} ) } // Helper hook to access the provider values const useOcean = (): OceanProviderValue => useContext(OceanContext) export { OceanProvider, useOcean, OceanProviderValue, Balance, OceanContext } export default OceanProvider