import React, { useContext, useState, useEffect, createContext, ReactElement } 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' interface Balance { eth: string | undefined ocean: string | undefined } interface OceanProviderValue { web3: Web3 | undefined web3Provider: any web3Modal: Web3Modal ocean: Ocean config: Config account: Account accountId: string balance: Balance chainId: number | undefined status: ProviderStatus connect: (config?: Config) => Promise logout: () => Promise refreshBalance: () => Promise } const OceanContext = createContext({} as OceanProviderValue) function OceanProvider({ initialConfig, web3ModalOpts, children }: { initialConfig: Config web3ModalOpts?: Partial children: any }): ReactElement { const [web3, setWeb3] = useState() const [web3Provider, setWeb3Provider] = useState() const [ocean, setOcean] = useState() const [web3Modal, setWeb3Modal] = useState() const [chainId, setChainId] = useState() const [account, setAccount] = useState() const [accountId, setAccountId] = useState() const [config, setConfig] = useState(initialConfig) const [balance, setBalance] = useState({ eth: undefined, ocean: undefined }) const [status, setStatus] = useState( ProviderStatus.NOT_AVAILABLE ) async function init() { Logger.log('Ocean Provider init') window && window.ethereum && (window.ethereum.autoRefreshOnNetworkChange = false) Logger.log('Web3Modal init.') if (web3ModalOpts === undefined) { web3ModalOpts = await getDefaultProviders() } const web3ModalInstance = new Web3Modal(web3ModalOpts) setWeb3Modal(web3ModalInstance) Logger.log('Web3Modal instance created.', web3ModalInstance) } async function connect(newConfig?: Config) { 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 chainId = web3 && (await web3.eth.getChainId()) setChainId(chainId) Logger.log('chain id ', chainId) 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) } } // On mount setup Web3Modal instance useEffect(() => { init() }, []) // Connect automatically to cached provider if present useEffect(() => { if (!web3Modal) return web3Modal.cachedProvider && connect() }, [web3Modal]) 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() } const handleAccountsChanged = async (accounts: string[]) => { Logger.debug("Handling 'accountsChanged' event with payload", accounts) connect() } // TODO: #68 Refetch balance periodically, or figure out some event to subscribe to useEffect(() => { // 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]) return ( {children} ) } // Helper hook to access the provider values const useOcean = (): OceanProviderValue => useContext(OceanContext) export { OceanProvider, useOcean, OceanProviderValue, Balance } export default OceanProvider