2021-03-17 11:44:26 +01:00
|
|
|
import React, {
|
|
|
|
useContext,
|
|
|
|
useState,
|
|
|
|
useEffect,
|
|
|
|
createContext,
|
|
|
|
ReactElement,
|
|
|
|
ReactNode,
|
|
|
|
useCallback
|
|
|
|
} from 'react'
|
|
|
|
import Web3 from 'web3'
|
2021-05-17 16:12:22 +02:00
|
|
|
import Web3Modal, { getProviderInfo, IProviderInfo } from 'web3modal'
|
2021-03-17 11:44:26 +01:00
|
|
|
import { infuraProjectId as infuraId, portisId } from '../../app.config'
|
|
|
|
import WalletConnectProvider from '@walletconnect/web3-provider'
|
|
|
|
import { Logger } from '@oceanprotocol/lib'
|
|
|
|
import { isBrowser } from '../utils'
|
|
|
|
import {
|
|
|
|
EthereumListsChain,
|
|
|
|
getNetworkData,
|
|
|
|
getNetworkDisplayName
|
|
|
|
} from '../utils/web3'
|
|
|
|
import { graphql, useStaticQuery } from 'gatsby'
|
2021-05-31 14:27:04 +02:00
|
|
|
import { UserBalance } from '../@types/TokenBalance'
|
|
|
|
import { getOceanBalance } from '../utils/ocean'
|
2021-03-17 11:44:26 +01:00
|
|
|
|
|
|
|
interface Web3ProviderValue {
|
|
|
|
web3: Web3
|
|
|
|
web3Provider: any
|
|
|
|
web3Modal: Web3Modal
|
2021-05-17 16:12:22 +02:00
|
|
|
web3ProviderInfo: IProviderInfo
|
2021-03-17 11:44:26 +01:00
|
|
|
accountId: string
|
2021-05-31 14:27:04 +02:00
|
|
|
balance: UserBalance
|
2021-03-17 11:44:26 +01:00
|
|
|
networkId: number
|
|
|
|
networkDisplayName: string
|
|
|
|
networkData: EthereumListsChain
|
2021-04-13 10:57:59 +02:00
|
|
|
block: number
|
2021-03-17 11:44:26 +01:00
|
|
|
isTestnet: boolean
|
2021-04-13 10:57:59 +02:00
|
|
|
web3Loading: boolean
|
2021-03-17 11:44:26 +01:00
|
|
|
connect: () => Promise<void>
|
|
|
|
logout: () => Promise<void>
|
|
|
|
}
|
|
|
|
|
|
|
|
const web3ModalTheme = {
|
|
|
|
background: 'var(--background-body)',
|
|
|
|
main: 'var(--font-color-heading)',
|
|
|
|
secondary: 'var(--brand-grey-light)',
|
|
|
|
border: 'var(--border-color)',
|
|
|
|
hover: 'var(--background-highlight)'
|
|
|
|
}
|
|
|
|
|
|
|
|
// HEADS UP! We inline-require some packages so the Gatsby SSR build does not break.
|
|
|
|
// We only need them client-side.
|
|
|
|
const providerOptions = isBrowser
|
|
|
|
? {
|
|
|
|
walletconnect: {
|
|
|
|
package: WalletConnectProvider,
|
|
|
|
options: { infuraId }
|
|
|
|
},
|
|
|
|
portis: {
|
|
|
|
package: require('@portis/web3'),
|
|
|
|
options: {
|
|
|
|
id: portisId
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// torus: {
|
|
|
|
// package: require('@toruslabs/torus-embed')
|
|
|
|
// // options: {
|
|
|
|
// // networkParams: {
|
|
|
|
// // host: oceanConfig.url, // optional
|
|
|
|
// // chainId: 1337, // optional
|
|
|
|
// // networkId: 1337 // optional
|
|
|
|
// // }
|
|
|
|
// // }
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
: {}
|
|
|
|
|
|
|
|
export const web3ModalOpts = {
|
|
|
|
cacheProvider: true,
|
|
|
|
providerOptions,
|
|
|
|
theme: web3ModalTheme
|
|
|
|
}
|
|
|
|
|
2021-05-31 14:27:04 +02:00
|
|
|
const refreshInterval = 20000 // 20 sec.
|
|
|
|
|
2021-03-17 11:44:26 +01:00
|
|
|
const networksQuery = graphql`
|
|
|
|
query {
|
|
|
|
allNetworksMetadataJson {
|
|
|
|
edges {
|
|
|
|
node {
|
|
|
|
chain
|
|
|
|
network
|
|
|
|
networkId
|
|
|
|
chainId
|
|
|
|
nativeCurrency {
|
|
|
|
name
|
|
|
|
symbol
|
|
|
|
decimals
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`
|
|
|
|
|
|
|
|
const Web3Context = createContext({} as Web3ProviderValue)
|
|
|
|
|
|
|
|
function Web3Provider({ children }: { children: ReactNode }): ReactElement {
|
|
|
|
const data = useStaticQuery(networksQuery)
|
|
|
|
const networksList: { node: EthereumListsChain }[] =
|
|
|
|
data.allNetworksMetadataJson.edges
|
|
|
|
|
|
|
|
const [web3, setWeb3] = useState<Web3>()
|
|
|
|
const [web3Provider, setWeb3Provider] = useState<any>()
|
|
|
|
const [web3Modal, setWeb3Modal] = useState<Web3Modal>()
|
2021-05-17 16:12:22 +02:00
|
|
|
const [web3ProviderInfo, setWeb3ProviderInfo] = useState<IProviderInfo>()
|
2021-03-17 11:44:26 +01:00
|
|
|
const [networkId, setNetworkId] = useState<number>()
|
|
|
|
const [networkDisplayName, setNetworkDisplayName] = useState<string>()
|
|
|
|
const [networkData, setNetworkData] = useState<EthereumListsChain>()
|
2021-04-13 10:57:59 +02:00
|
|
|
const [block, setBlock] = useState<number>()
|
2021-03-17 11:44:26 +01:00
|
|
|
const [isTestnet, setIsTestnet] = useState<boolean>()
|
|
|
|
const [accountId, setAccountId] = useState<string>()
|
2021-04-15 12:43:12 +02:00
|
|
|
const [web3Loading, setWeb3Loading] = useState<boolean>(true)
|
2021-05-31 14:27:04 +02:00
|
|
|
const [balance, setBalance] = useState<UserBalance>({
|
|
|
|
eth: '0',
|
|
|
|
ocean: '0'
|
|
|
|
})
|
2021-03-17 11:44:26 +01:00
|
|
|
|
2021-05-31 14:27:04 +02:00
|
|
|
// -----------------------------------
|
|
|
|
// Helper: connect to web3
|
|
|
|
// -----------------------------------
|
2021-03-17 11:44:26 +01:00
|
|
|
const connect = useCallback(async () => {
|
2021-04-15 12:43:12 +02:00
|
|
|
if (!web3Modal) {
|
|
|
|
setWeb3Loading(false)
|
|
|
|
return
|
|
|
|
}
|
2021-03-17 11:44:26 +01:00
|
|
|
try {
|
2021-04-13 10:57:59 +02:00
|
|
|
setWeb3Loading(true)
|
2021-03-17 11:44:26 +01:00
|
|
|
Logger.log('[web3] Connecting Web3...')
|
|
|
|
|
|
|
|
const provider = await web3Modal?.connect()
|
|
|
|
setWeb3Provider(provider)
|
|
|
|
|
|
|
|
const web3 = new Web3(provider)
|
|
|
|
setWeb3(web3)
|
|
|
|
Logger.log('[web3] Web3 created.', web3)
|
|
|
|
|
|
|
|
const networkId = await web3.eth.net.getId()
|
|
|
|
setNetworkId(networkId)
|
|
|
|
Logger.log('[web3] network id ', networkId)
|
|
|
|
|
|
|
|
const accountId = (await web3.eth.getAccounts())[0]
|
|
|
|
setAccountId(accountId)
|
|
|
|
Logger.log('[web3] account id', accountId)
|
|
|
|
} catch (error) {
|
|
|
|
Logger.error('[web3] Error: ', error.message)
|
2021-04-13 10:57:59 +02:00
|
|
|
} finally {
|
|
|
|
setWeb3Loading(false)
|
2021-03-17 11:44:26 +01:00
|
|
|
}
|
|
|
|
}, [web3Modal])
|
|
|
|
|
2021-05-31 14:27:04 +02:00
|
|
|
// -----------------------------------
|
|
|
|
// Helper: Get user balance
|
|
|
|
// -----------------------------------
|
|
|
|
const getUserBalance = useCallback(async () => {
|
|
|
|
if (!accountId || !networkId || !web3) return
|
|
|
|
|
|
|
|
try {
|
|
|
|
const balance = {
|
|
|
|
eth: web3.utils.fromWei(await web3.eth.getBalance(accountId, 'latest')),
|
|
|
|
ocean: await getOceanBalance(accountId, networkId, web3)
|
|
|
|
}
|
|
|
|
setBalance(balance)
|
|
|
|
Logger.log('[web3] Balance: ', balance)
|
|
|
|
} catch (error) {
|
|
|
|
Logger.error('[web3] Error: ', error.message)
|
|
|
|
}
|
|
|
|
}, [accountId, networkId, web3])
|
|
|
|
|
2021-03-17 11:44:26 +01:00
|
|
|
// -----------------------------------
|
|
|
|
// Create initial Web3Modal instance
|
|
|
|
// -----------------------------------
|
|
|
|
useEffect(() => {
|
2021-05-19 10:31:23 +02:00
|
|
|
if (web3Modal) {
|
|
|
|
setWeb3Loading(false)
|
|
|
|
return
|
|
|
|
}
|
2021-03-17 11:44:26 +01:00
|
|
|
|
|
|
|
async function init() {
|
|
|
|
// note: needs artificial await here so the log message is reached and output
|
|
|
|
const web3ModalInstance = await new Web3Modal(web3ModalOpts)
|
|
|
|
setWeb3Modal(web3ModalInstance)
|
|
|
|
Logger.log('[web3] Web3Modal instance created.', web3ModalInstance)
|
|
|
|
}
|
|
|
|
init()
|
|
|
|
}, [connect, web3Modal])
|
|
|
|
|
|
|
|
// -----------------------------------
|
|
|
|
// Reconnect automatically for returning users
|
|
|
|
// -----------------------------------
|
|
|
|
useEffect(() => {
|
|
|
|
if (!web3Modal?.cachedProvider) return
|
|
|
|
|
|
|
|
async function connectCached() {
|
|
|
|
Logger.log(
|
|
|
|
'[web3] Connecting to cached provider: ',
|
|
|
|
web3Modal.cachedProvider
|
|
|
|
)
|
|
|
|
await connect()
|
|
|
|
}
|
|
|
|
connectCached()
|
|
|
|
}, [connect, web3Modal])
|
|
|
|
|
2021-05-31 14:27:04 +02:00
|
|
|
// -----------------------------------
|
|
|
|
// Get and set user balance
|
|
|
|
// -----------------------------------
|
|
|
|
useEffect(() => {
|
|
|
|
getUserBalance()
|
|
|
|
|
|
|
|
// init periodic refresh of wallet balance
|
|
|
|
const balanceInterval = setInterval(() => getUserBalance(), refreshInterval)
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
clearInterval(balanceInterval)
|
|
|
|
}
|
|
|
|
}, [getUserBalance])
|
|
|
|
|
2021-03-17 11:44:26 +01:00
|
|
|
// -----------------------------------
|
|
|
|
// Get and set network metadata
|
|
|
|
// -----------------------------------
|
|
|
|
useEffect(() => {
|
|
|
|
if (!networkId) return
|
|
|
|
|
|
|
|
const networkData = getNetworkData(networksList, networkId)
|
|
|
|
setNetworkData(networkData)
|
|
|
|
Logger.log('[web3] Network metadata found.', networkData)
|
|
|
|
|
|
|
|
// Construct network display name
|
|
|
|
const networkDisplayName = getNetworkDisplayName(networkData, networkId)
|
|
|
|
setNetworkDisplayName(networkDisplayName)
|
|
|
|
|
|
|
|
// Figure out if we're on a chain's testnet, or not
|
|
|
|
setIsTestnet(networkData.network !== 'mainnet')
|
|
|
|
|
|
|
|
Logger.log(`[web3] Network display name set to: ${networkDisplayName}`)
|
|
|
|
}, [networkId, networksList])
|
|
|
|
|
2021-04-13 10:57:59 +02:00
|
|
|
// -----------------------------------
|
|
|
|
// Get and set latest head block
|
|
|
|
// -----------------------------------
|
|
|
|
useEffect(() => {
|
|
|
|
if (!web3) return
|
|
|
|
|
|
|
|
async function getBlock() {
|
|
|
|
const block = await web3.eth.getBlockNumber()
|
|
|
|
setBlock(block)
|
|
|
|
Logger.log('[web3] Head block: ', block)
|
|
|
|
}
|
|
|
|
getBlock()
|
|
|
|
}, [web3, networkId])
|
|
|
|
|
2021-05-17 16:12:22 +02:00
|
|
|
// -----------------------------------
|
|
|
|
// 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])
|
|
|
|
|
2021-03-17 11:44:26 +01:00
|
|
|
// -----------------------------------
|
|
|
|
// Logout helper
|
|
|
|
// -----------------------------------
|
|
|
|
async function logout() {
|
|
|
|
web3Modal?.clearCachedProvider()
|
|
|
|
}
|
|
|
|
|
|
|
|
// -----------------------------------
|
|
|
|
// Handle change events
|
|
|
|
// -----------------------------------
|
|
|
|
async function handleNetworkChanged(networkId: string) {
|
|
|
|
Logger.log('[web3] Network changed', networkId)
|
|
|
|
// const networkId = Number(chainId.replace('0x', ''))
|
|
|
|
setNetworkId(Number(networkId))
|
|
|
|
}
|
|
|
|
|
|
|
|
async function handleAccountsChanged(accounts: string[]) {
|
|
|
|
Logger.log('[web3] Account changed', accounts[0])
|
|
|
|
setAccountId(accounts[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (!web3Provider || !web3) return
|
|
|
|
|
|
|
|
//
|
|
|
|
// HEADS UP! We should rather listen to `chainChanged` exposing the `chainId`
|
|
|
|
// but for whatever reason the exposed `chainId` is wildly different from
|
|
|
|
// what is shown on https://chainid.network, in turn breaking our network/config
|
|
|
|
// mapping. The networkChanged is deprecated but works as expected for our case.
|
|
|
|
// See: https://eips.ethereum.org/EIPS/eip-1193#chainchanged
|
|
|
|
//
|
|
|
|
web3Provider.on('networkChanged', handleNetworkChanged)
|
|
|
|
web3Provider.on('accountsChanged', handleAccountsChanged)
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
web3Provider.removeListener('networkChanged')
|
|
|
|
web3Provider.removeListener('accountsChanged')
|
|
|
|
}
|
|
|
|
}, [web3Provider, web3])
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Web3Context.Provider
|
|
|
|
value={{
|
|
|
|
web3,
|
|
|
|
web3Provider,
|
|
|
|
web3Modal,
|
2021-05-17 16:12:22 +02:00
|
|
|
web3ProviderInfo,
|
2021-03-17 11:44:26 +01:00
|
|
|
accountId,
|
2021-05-31 14:27:04 +02:00
|
|
|
balance,
|
2021-03-17 11:44:26 +01:00
|
|
|
networkId,
|
|
|
|
networkDisplayName,
|
|
|
|
networkData,
|
2021-04-13 10:57:59 +02:00
|
|
|
block,
|
2021-03-17 11:44:26 +01:00
|
|
|
isTestnet,
|
2021-04-13 10:57:59 +02:00
|
|
|
web3Loading,
|
2021-03-17 11:44:26 +01:00
|
|
|
connect,
|
|
|
|
logout
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{children}
|
|
|
|
</Web3Context.Provider>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper hook to access the provider values
|
|
|
|
const useWeb3 = (): Web3ProviderValue => useContext(Web3Context)
|
|
|
|
|
|
|
|
export { Web3Provider, useWeb3, Web3ProviderValue, Web3Context }
|
|
|
|
export default Web3Provider
|
2021-05-31 14:27:04 +02:00
|
|
|
function getTokenBalance() {
|
|
|
|
throw new Error('Function not implemented.')
|
|
|
|
}
|