Skip to content

Commit

Permalink
refactor subbridge ethereum
Browse files Browse the repository at this point in the history
  • Loading branch information
kingsleydon committed Sep 1, 2024
1 parent f24bf8d commit 0fe8f2a
Show file tree
Hide file tree
Showing 7 changed files with 46 additions and 109 deletions.
4 changes: 2 additions & 2 deletions apps/app/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ const nextConfig = {
},
experimental: {
swcPlugins: [
// ['@swc-jotai/debug-label', {}],
// ['@swc-jotai/react-refresh', {}],
['@swc-jotai/debug-label', {}],
['@swc-jotai/react-refresh', {}],
],
},
}
Expand Down
7 changes: 4 additions & 3 deletions apps/subbridge/components/ConnectWalletButton.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {useEthersWeb3Provider} from '@/hooks/useEthersProvider'
import {fromChainAtom} from '@/store/bridge'
import {ethereumProviderAtom, evmAccountAtom} from '@/store/ethers'
import {polkadotWalletModalOpenAtom} from '@/store/polkadotWalletModal'
Expand All @@ -15,17 +16,17 @@ import {type FC, useState} from 'react'

const ConnectEvmWalletButton: FC<ButtonProps> = (props) => {
const [open, setOpen] = useState(false)
const [ethereum] = useAtom(ethereumProviderAtom)
const ethersWeb3Provider = useEthersWeb3Provider()
const [, setEvmAccount] = useAtom(evmAccountAtom)
const [loading, setLoading] = useState(false)

const handleClick = (): void => {
if (ethereum == null) {
if (ethersWeb3Provider == null) {
setOpen(true)
return
}
setLoading(true)
void ethereum
void ethersWeb3Provider.provider
.request?.({method: 'eth_requestAccounts'})
.then((accounts) => {
if (Array.isArray(accounts) && accounts.length > 0) {
Expand Down
71 changes: 19 additions & 52 deletions apps/subbridge/hooks/useEthereumProviderInitialization.ts
Original file line number Diff line number Diff line change
@@ -1,75 +1,42 @@
import {
ethereumProviderAtom,
ethersWeb3ProviderAtom,
evmAccountAtom,
evmChainIdAtom,
isEvmWalletAuthorizedAtom,
} from '@/store/ethers'
import type {ethers} from 'ethers'
import detectEthereumProvider from '@metamask/detect-provider'
import {ethers} from 'ethers'
import {useAtom} from 'jotai'
import {useEffect} from 'react'
import {useEffect, useRef} from 'react'

export const useEthereumProviderInitialization = (): void => {
const [evmAccount] = useAtom(evmAccountAtom)
const [ethereumProvider, setEthereumProvider] = useAtom(ethereumProviderAtom)
const [, setEthersWeb3Provider] = useAtom(ethersWeb3ProviderAtom)
const [, setEvmAccount] = useAtom(evmAccountAtom)
const [, setEvmChainId] = useAtom(evmChainIdAtom)
const [isEvmWalletAuthorized, setIsEvmWalletAuthorized] = useAtom(
isEvmWalletAuthorizedAtom,
)
const runRef = useRef(false)

useEffect(() => {
if (ethereumProvider != null) return
if (runRef.current) return
runRef.current = true
const init = async (): Promise<void> => {
const {default: detectEthereumProvider} = await import(
'@metamask/detect-provider'
)
const provider = await detectEthereumProvider({silent: true})
if (provider == null) return
const ethereum = provider as ethers.providers.ExternalProvider
setEthereumProvider(ethereum)
const ethereum = await detectEthereumProvider({silent: true})
if (ethereum == null) return
const provider = new ethers.providers.Web3Provider(ethereum)
setEthersWeb3Provider(provider)
const updateAccounts = (accounts: unknown): void => {
const account = (accounts as string[])[0]
setEvmAccount(account ?? null)
}
const updateChainId = (chainId: unknown): void => {
setEvmChainId(Number.parseInt(chainId as string, 16))
}
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
;(ethereum as any).on('accountsChanged', updateAccounts)
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
;(ethereum as any).on('chainChanged', updateChainId)
void ethereum.request?.({method: 'eth_chainId'}).then(updateChainId)
if (isEvmWalletAuthorized) {
ethereum
.request?.({method: 'eth_requestAccounts'})
.then((accounts) => {
const account = (accounts as string[])[0]
setEvmAccount(account ?? null)
})
.catch((error) => {
// User rejected
if (error.code === 4001) {
setIsEvmWalletAuthorized(false)
} else {
throw error
}
})
}
await provider.listAccounts().then(updateAccounts)
ethereum.on('accountsChanged', updateAccounts)
ethereum.on('chainChanged', updateChainId)
await provider.provider
.request?.({method: 'eth_chainId'})
.then(updateChainId)
}

void init()
}, [
ethereumProvider,
isEvmWalletAuthorized,
setEthereumProvider,
setIsEvmWalletAuthorized,
setEvmAccount,
setEvmChainId,
])

useEffect(() => {
if (evmAccount != null) {
setIsEvmWalletAuthorized(true)
}
}, [evmAccount, setIsEvmWalletAuthorized])
}, [setEthersWeb3Provider, setEvmAccount, setEvmChainId])
}
48 changes: 11 additions & 37 deletions apps/subbridge/hooks/useEthersProvider.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,16 @@
import {fromChainAtom} from '@/store/bridge'
import {ethereumProviderAtom, evmChainIdAtom} from '@/store/ethers'
import {ethers} from 'ethers'
import {ethersWeb3ProviderAtom} from '@/store/ethers'
import type {ethers} from 'ethers'
import {useAtomValue} from 'jotai'
import useSWRImmutable from 'swr/immutable'

const ethersWeb3ProviderFetcher = async ([ethereumProvider, _]: [
ethers.providers.ExternalProvider,
number,
]): Promise<ethers.providers.Web3Provider> => {
const provider = new ethers.providers.Web3Provider(ethereumProvider)
return provider
}
export const useEthersWeb3Provider =
(): ethers.providers.Web3Provider | null => {
const fromChain = useAtomValue(fromChainAtom)
const ethersWeb3Provider = useAtomValue(ethersWeb3ProviderAtom)

export const useEthersWeb3Provider = ():
| ethers.providers.Web3Provider
| undefined => {
const fromChain = useAtomValue(fromChainAtom)
const evmChainId = useAtomValue(evmChainIdAtom)
const ethereumProvider = useAtomValue(ethereumProviderAtom)
const {data} = useSWRImmutable(
fromChain.kind === 'evm' &&
fromChain.evmChainId === evmChainId &&
ethereumProvider != null && [ethereumProvider, evmChainId],
ethersWeb3ProviderFetcher,
)
if (fromChain.kind === 'evm') {
return ethersWeb3Provider
}

return data
}

const ethersJsonRpcProviderFetcher = async (
url: string,
): Promise<ethers.providers.StaticJsonRpcProvider> => {
return new ethers.providers.StaticJsonRpcProvider(url)
}

export const useEthersJsonRpcProvider = (
url?: string,
): ethers.providers.StaticJsonRpcProvider | undefined => {
const {data} = useSWRImmutable(url, ethersJsonRpcProviderFetcher)
return data
}
return null
}
12 changes: 6 additions & 6 deletions apps/subbridge/hooks/useSwitchNetwork.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import evmChainsData from '@/assets/evm_chains_data.json'
import {fromChainAtom} from '@/store/bridge'
import {ethereumProviderAtom} from '@/store/ethers'
import {useAtom} from 'jotai'
import {useCallback} from 'react'
import {useEthersWeb3Provider} from './useEthersProvider'

export const useSwitchNetwork = (): (() => Promise<void>) => {
const [fromChain] = useAtom(fromChainAtom)
const [ethereum] = useAtom(ethereumProviderAtom)
const ethersWeb3Provider = useEthersWeb3Provider()

const switchNetwork = useCallback(async () => {
if (ethereum == null || fromChain.kind !== 'evm') return
if (ethersWeb3Provider == null || fromChain.kind !== 'evm') return
try {
await ethereum.request?.({
await ethersWeb3Provider.provider.request?.({
method: 'wallet_switchEthereumChain',
params: [{chainId: `0x${fromChain.evmChainId.toString(16)}`}],
})
} catch (switchError) {
if ((switchError as {code: number}).code === 4902) {
await ethereum.request?.({
await ethersWeb3Provider.provider.request?.({
method: 'wallet_addEthereumChain',
params: [
(evmChainsData as Readonly<Record<number, unknown>>)[
Expand All @@ -27,7 +27,7 @@ export const useSwitchNetwork = (): (() => Promise<void>) => {
})
}
}
}, [ethereum, fromChain])
}, [ethersWeb3Provider, fromChain])

return switchNetwork
}
4 changes: 2 additions & 2 deletions apps/subbridge/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ const nextConfig = {
},
experimental: {
swcPlugins: [
// ['@swc-jotai/debug-label', {}],
// ['@swc-jotai/react-refresh', {}],
['@swc-jotai/debug-label', {}],
['@swc-jotai/react-refresh', {}],
],
},
}
Expand Down
9 changes: 2 additions & 7 deletions apps/subbridge/store/ethers.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import type {ethers} from 'ethers'
import {atom} from 'jotai'
import {atomWithStorage} from 'jotai/utils'
import {fromChainAtom} from './bridge'

export const isEvmWalletAuthorizedAtom = atomWithStorage(
'jotai:is_evm_wallet_authorized',
false,
)
export const ethereumProviderAtom =
atom<ethers.providers.ExternalProvider | null>(null)
export const ethersWeb3ProviderAtom =
atom<ethers.providers.Web3Provider | null>(null)
export const evmAccountAtom = atom<string | null>(null)
export const evmChainIdAtom = atom<number | null>(null)
export const isNetworkWrongAtom = atom<boolean>((get) => {
Expand Down

0 comments on commit 0fe8f2a

Please sign in to comment.