From 8bc1e15eecaa0cfd9733648e105ae3443b3c2abc Mon Sep 17 00:00:00 2001 From: lubej Date: Tue, 13 Feb 2024 12:49:53 +0100 Subject: [PATCH 1/7] Refactor - split Web3Provider into EIP1193Provider and Web3Provider --- src/App.tsx | 9 +- src/hooks/useEIP1193.ts | 11 ++ src/pages/ConnectWallet/index.tsx | 12 +-- src/providers/EIP1193Context.ts | 10 ++ src/providers/EIP1193Provider.tsx | 115 ++++++++++++++++++++ src/providers/Web3Context.ts | 10 +- src/providers/Web3Provider.tsx | 168 +++++++++++++----------------- 7 files changed, 226 insertions(+), 109 deletions(-) create mode 100644 src/hooks/useEIP1193.ts create mode 100644 src/providers/EIP1193Context.ts create mode 100644 src/providers/EIP1193Provider.tsx diff --git a/src/App.tsx b/src/App.tsx index e04bb48..35ed69b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,7 @@ import { Web3ContextProvider } from './providers/Web3Provider' import { ConnectWallet } from './pages/ConnectWallet' import { WrapFormContextProvider } from './providers/WrapFormProvider' import { Transaction } from './pages/Transaction' +import { EIP1193ContextProvider } from './providers/EIP1193Provider.tsx' const router = createHashRouter([ { @@ -33,7 +34,9 @@ const router = createHashRouter([ ]) export const App: FC = () => ( - - - + + + + + ) diff --git a/src/hooks/useEIP1193.ts b/src/hooks/useEIP1193.ts new file mode 100644 index 0000000..ce0d2d5 --- /dev/null +++ b/src/hooks/useEIP1193.ts @@ -0,0 +1,11 @@ +import { useContext } from 'react' +import { EIP1193Context } from '../providers/EIP1193Context' + +export const useEIP1193 = () => { + const value = useContext(EIP1193Context) + if (value === undefined) { + throw new Error('[useEIP1193] Component not wrapped within a Provider') + } + + return value +} diff --git a/src/pages/ConnectWallet/index.tsx b/src/pages/ConnectWallet/index.tsx index 6f32bcc..bcfba7d 100644 --- a/src/pages/ConnectWallet/index.tsx +++ b/src/pages/ConnectWallet/index.tsx @@ -7,16 +7,16 @@ import { METAMASK_HOME_PAGE } from '../../constants/config' import { useWeb3 } from '../../hooks/useWeb3' export const ConnectWallet: FC = () => { - const { connectWallet, switchNetwork, isMetaMaskInstalled } = useWeb3() + const { connectWallet, switchNetwork, isProviderAvailable } = useWeb3() const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState('') - const [hasMetaMaskWallet, setHasMetaMaskWallet] = useState(true) + const [providerAvailable, setProviderAvailable] = useState(true) const [isUnknownNetwork, setIsUnknownNetwork] = useState(false) useEffect(() => { const init = async () => { setIsLoading(true) - setHasMetaMaskWallet(await isMetaMaskInstalled()) + setProviderAvailable(await isProviderAvailable()) setIsLoading(false) } @@ -53,7 +53,7 @@ export const ConnectWallet: FC = () => { return ( <> - {!hasMetaMaskWallet && ( + {!providerAvailable && (

Quickly wrap your ROSE into wROSE and vice versa with the (un)wrap ROSE tool. @@ -68,7 +68,7 @@ export const ConnectWallet: FC = () => {

)} - {hasMetaMaskWallet && ( + {providerAvailable && ( <> {!isUnknownNetwork && (
diff --git a/src/providers/EIP1193Context.ts b/src/providers/EIP1193Context.ts new file mode 100644 index 0000000..5b9e443 --- /dev/null +++ b/src/providers/EIP1193Context.ts @@ -0,0 +1,10 @@ +import { createContext } from 'react' + +export interface EIP1193ProviderContext { + isEIP1193ProviderAvailable: () => Promise + connectWallet: () => Promise + switchNetwork: (chainId: number) => void + addTokenToWallet: (wRoseContractAddress: string) => Promise +} + +export const EIP1193Context = createContext({} as EIP1193ProviderContext) diff --git a/src/providers/EIP1193Provider.tsx b/src/providers/EIP1193Provider.tsx new file mode 100644 index 0000000..ea2b131 --- /dev/null +++ b/src/providers/EIP1193Provider.tsx @@ -0,0 +1,115 @@ +import { FC, PropsWithChildren } from 'react' +import { ethers, utils } from 'ethers' +import * as sapphire from '@oasisprotocol/sapphire-paratime' +import { MetaMaskError } from '../utils/errors' +import detectEthereumProvider from '@metamask/detect-provider' +import { EIP1193Context, EIP1193ProviderContext } from './EIP1193Context.ts' + +declare global { + interface Window { + ethereum?: ethers.providers.ExternalProvider & ethers.providers.Web3Provider + } +} + +export const EIP1193ContextProvider: FC = ({ children }) => { + const isEIP1193ProviderAvailable = async () => { + const provider = await detectEthereumProvider({ + // Explicitly set, provider doesn't have to be MetaMask + mustBeMetaMask: false, + }) + + return !!window.ethereum && provider === window.ethereum + } + + const connectWallet = async (): Promise => { + const accounts: string[] = await (window.ethereum?.request?.({ method: 'eth_requestAccounts' }) || + Promise.resolve([])) + + if (!accounts || accounts?.length <= 0) { + throw new Error('[EIP1193Context] Request account failed!') + } + + return accounts[0] + } + + const _addNetwork = (chainId: number) => { + if (chainId === 0x5afe) { + // Default to Sapphire Mainnet + return window.ethereum?.request?.({ + method: 'wallet_addEthereumChain', + params: [ + { + chainId: '0x5afe', + chainName: 'Oasis Sapphire', + nativeCurrency: { + name: 'ROSE', + symbol: 'ROSE', + decimals: 18, + }, + rpcUrls: ['https://sapphire.oasis.io/', 'wss://sapphire.oasis.io/ws'], + blockExplorerUrls: ['https://explorer.oasis.io/mainnet/sapphire'], + }, + ], + }) + } + + throw new Error('Unable to automatically add the network, please do it manually!') + } + + const switchNetwork = async (chainId = 0x5afe) => { + const ethProvider = new ethers.providers.Web3Provider(window.ethereum!) + const sapphireEthProvider = sapphire.wrap(ethProvider) as ethers.providers.Web3Provider & + sapphire.SapphireAnnex + + const network = await sapphireEthProvider.getNetwork() + + if (network.chainId === chainId) return + try { + await window.ethereum!.request?.({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: utils.hexlify(chainId) }], + }) + } catch (e) { + const metaMaskError = e as MetaMaskError + // Metamask desktop - Throws e.code 4902 when chain is not available + // Metamask mobile - Throws generic -32603 (https://github.com/MetaMask/metamask-mobile/issues/3312) + + if (metaMaskError?.code !== 4902 && metaMaskError?.code !== -32603) { + throw metaMaskError + } else { + _addNetwork(chainId) + } + } + } + + const addTokenToWallet = async (wRoseContractAddress: string) => { + const symbol = 'WROSE' + + try { + await window.ethereum?.request?.({ + method: 'wallet_watchAsset', + params: { + type: 'ERC20', + options: { + address: wRoseContractAddress, + symbol, + decimals: 18, + }, + // Fails if Array + } as unknown as never, + }) + } catch (ex) { + // Silently fail + console.error(ex) + } + } + + const providerState: EIP1193ProviderContext = { + isEIP1193ProviderAvailable, + connectWallet, + switchNetwork, + addTokenToWallet, + } + + return {children} +} diff --git a/src/providers/Web3Context.ts b/src/providers/Web3Context.ts index 59a729e..9c3b5e0 100644 --- a/src/providers/Web3Context.ts +++ b/src/providers/Web3Context.ts @@ -3,6 +3,11 @@ import { BigNumber, ethers } from 'ethers' import * as sapphire from '@oasisprotocol/sapphire-paratime' import { TransactionResponse } from '@ethersproject/abstract-provider' +export enum ProviderType { + EIP1193, + EIP6963, +} + export interface Web3ProviderState { isConnected: boolean ethProvider: ethers.providers.Web3Provider | null @@ -12,20 +17,21 @@ export interface Web3ProviderState { account: string | null explorerBaseUrl: string | null networkName: string | null + providerType: ProviderType } export interface Web3ProviderContext { readonly state: Web3ProviderState wrap: (amount: string, gasPrice: BigNumber) => Promise unwrap: (amount: string, gasPrice: BigNumber) => Promise - isMetaMaskInstalled: () => Promise connectWallet: () => Promise - switchNetwork: () => Promise + switchNetwork: (chainId?: number) => Promise getBalance: () => Promise getBalanceOfWROSE: () => Promise getTransaction: (txHash: string) => Promise addTokenToWallet: () => Promise getGasPrice: () => Promise + isProviderAvailable: () => Promise } export const Web3Context = createContext({} as Web3ProviderContext) diff --git a/src/providers/Web3Provider.tsx b/src/providers/Web3Provider.tsx index 2ccd2cd..ba04abe 100644 --- a/src/providers/Web3Provider.tsx +++ b/src/providers/Web3Provider.tsx @@ -1,19 +1,13 @@ import { FC, PropsWithChildren, useCallback, useState } from 'react' -import { BigNumber, ethers, utils } from 'ethers' +import { BigNumber, ethers } from 'ethers' import * as sapphire from '@oasisprotocol/sapphire-paratime' import { MAX_GAS_LIMIT, NETWORKS } from '../constants/config' // https://repo.sourcify.dev/contracts/full_match/23295/0xB759a0fbc1dA517aF257D5Cf039aB4D86dFB3b94/ // https://repo.sourcify.dev/contracts/full_match/23294/0x8Bc2B030b299964eEfb5e1e0b36991352E56D2D3/ import WrappedRoseMetadata from '../contracts/WrappedROSE.json' -import { MetaMaskError, UnknownNetworkError } from '../utils/errors' -import detectEthereumProvider from '@metamask/detect-provider' -import { Web3ProviderContext, Web3ProviderState, Web3Context } from './Web3Context' - -declare global { - interface Window { - ethereum?: ethers.providers.ExternalProvider & ethers.providers.Web3Provider - } -} +import { UnknownNetworkError } from '../utils/errors' +import { ProviderType, Web3Context, Web3ProviderContext, Web3ProviderState } from './Web3Context' +import { useEIP1193 } from '../hooks/useEIP1193.ts' const web3ProviderInitialState: Web3ProviderState = { isConnected: false, @@ -24,9 +18,17 @@ const web3ProviderInitialState: Web3ProviderState = { account: null, explorerBaseUrl: null, networkName: null, + providerType: ProviderType.EIP1193, } export const Web3ContextProvider: FC = ({ children }) => { + const { + isEIP1193ProviderAvailable, + connectWallet: connectWalletEIP1193, + switchNetwork: switchNetworkEIP1193, + addTokenToWallet: addTokenToWalletEIP1193, + } = useEIP1193() + const [state, setState] = useState({ ...web3ProviderInitialState, }) @@ -88,33 +90,31 @@ export const Web3ContextProvider: FC = ({ children }) => { const _connect = useCallback(() => _connectionChanged(true), []) const _disconnect = useCallback(() => _connectionChanged(false), []) - const _addEventListeners = (ethProvider = window.ethereum!) => { - ethProvider.on('accountsChanged', _accountsChanged) - ethProvider.on('chainChanged', _chainChanged) - ethProvider.on('connect', _connect) - ethProvider.on('disconnect', _disconnect) - } + const _addEventListenersOnce = (() => { + let eventListenersInitialized = false + return (ethProvider: typeof window.ethereum) => { + if (eventListenersInitialized) { + return + } - const _removeEventListeners = (ethProvider = window.ethereum!) => { - ethProvider.off('accountsChanged', _accountsChanged) - ethProvider.off('chainChanged', _chainChanged) - ethProvider.off('connect', _connect) - ethProvider.off('disconnect', _disconnect) - } + ethProvider?.on?.('accountsChanged', _accountsChanged) + ethProvider?.on?.('chainChanged', _chainChanged) + ethProvider?.on?.('connect', _connect) + ethProvider?.on?.('disconnect', _disconnect) - const _init = async (account: string) => { - _removeEventListeners() + eventListenersInitialized = true + } + })() + const _init = async (account: string, provider: typeof window.ethereum) => { try { - const ethProvider = new ethers.providers.Web3Provider(window.ethereum!) + const ethProvider = new ethers.providers.Web3Provider(provider!) const sapphireEthProvider = sapphire.wrap(ethProvider) as ethers.providers.Web3Provider & sapphire.SapphireAnnex const network = await sapphireEthProvider.getNetwork() _setNetworkSpecificVars(network.chainId, sapphireEthProvider) - _addEventListeners() - setState(prevState => ({ ...prevState, isConnected: true, @@ -156,70 +156,52 @@ export const Web3ContextProvider: FC = ({ children }) => { return await wRoseContract.balanceOf(account) } - const isMetaMaskInstalled = async () => { - const provider = await detectEthereumProvider() + const isProviderAvailable = async () => { + const { providerType } = state - return !!window.ethereum && provider === window.ethereum + switch (providerType) { + case ProviderType.EIP1193: + return isEIP1193ProviderAvailable() + default: + return false + } } - const connectWallet = async () => { - const accounts: string[] = await (window.ethereum?.request?.({ method: 'eth_requestAccounts' }) || - Promise.resolve([])) + const _getConnectedAccount = () => { + const { providerType } = state - if (!accounts || accounts?.length <= 0) { - throw new Error('[Web3Context] Request account failed!') + switch (providerType) { + case ProviderType.EIP1193: + return connectWalletEIP1193() + default: + return } - - await _init(accounts[0]) } - const _addNetwork = async (chainId: number) => { - if (chainId === 0x5afe) { - // Default to Sapphire Mainnet - return await window.ethereum?.request?.({ - method: 'wallet_addEthereumChain', - params: [ - { - chainId: '0x5afe', - chainName: 'Oasis Sapphire', - nativeCurrency: { - name: 'ROSE', - symbol: 'ROSE', - decimals: 18, - }, - rpcUrls: ['https://sapphire.oasis.io/', 'wss://sapphire.oasis.io/ws'], - blockExplorerUrls: ['https://explorer.oasis.io/mainnet/sapphire'], - }, - ], - }) + const connectWallet = async () => { + const { providerType } = state + const account = await _getConnectedAccount() + + if (!account) { + throw new Error('[Web3Context] Request account failed!') } - throw new Error('Unable to automatically add the network, please do it manually!') + switch (providerType) { + case ProviderType.EIP1193: { + await _init(account, window.ethereum) + _addEventListenersOnce(window.ethereum) + } + } } - const switchNetwork = async (toNetworkChainId = 0x5afe) => { - const ethProvider = new ethers.providers.Web3Provider(window.ethereum!) - const sapphireEthProvider = sapphire.wrap(ethProvider) as ethers.providers.Web3Provider & - sapphire.SapphireAnnex + const switchNetwork = async (chainId = 0x5afe) => { + const { providerType } = state - const network = await sapphireEthProvider.getNetwork() - - if (network.chainId === toNetworkChainId) return - try { - await window.ethereum!.request?.({ - method: 'wallet_switchEthereumChain', - params: [{ chainId: utils.hexlify(toNetworkChainId) }], - }) - } catch (e) { - const metaMaskError = e as MetaMaskError - // Metamask desktop - Throws e.code 4902 when chain is not available - // Metamask mobile - Throws generic -32603 (https://github.com/MetaMask/metamask-mobile/issues/3312) - - if (metaMaskError?.code !== 4902 && metaMaskError?.code !== -32603) { - throw metaMaskError - } else { - _addNetwork(toNetworkChainId) - } + switch (providerType) { + case ProviderType.EIP1193: + return switchNetworkEIP1193(chainId) + default: + return } } @@ -280,31 +262,21 @@ export const Web3ContextProvider: FC = ({ children }) => { } const addTokenToWallet = async () => { - const { wRoseContractAddress: address } = state - const symbol = 'WROSE' + const { wRoseContractAddress, providerType } = state - try { - await window.ethereum?.request?.({ - method: 'wallet_watchAsset', - params: { - type: 'ERC20', - options: { - address, - symbol, - decimals: 18, - }, - // Fails if Array - } as unknown as never, - }) - } catch (ex) { - // Silently fail - console.error(ex) + if (!wRoseContractAddress) { + return + } + + switch (providerType) { + case ProviderType.EIP1193: + return addTokenToWalletEIP1193(wRoseContractAddress) } } const providerState: Web3ProviderContext = { state, - isMetaMaskInstalled, + isProviderAvailable, connectWallet, switchNetwork, wrap, From e598b88b53748caee41bf6260ef7c0ebbea4813d Mon Sep 17 00:00:00 2001 From: lubej Date: Thu, 15 Feb 2024 16:12:52 +0100 Subject: [PATCH 2/7] Add EIP-6963 manager and provider --- src/App.tsx | 16 ++++-- src/hooks/useEIP6963.ts | 11 ++++ src/hooks/useEIP6963Manager.ts | 11 ++++ src/providers/EIP1193Context.ts | 7 +-- src/providers/EIP1193Provider.tsx | 35 ++++++------ src/providers/EIP6963Context.ts | 17 ++++++ src/providers/EIP6963ManagerContext.ts | 18 +++++++ src/providers/EIP6963ManagerProvider.tsx | 69 ++++++++++++++++++++++++ src/providers/EIP6963Provider.tsx | 64 ++++++++++++++++++++++ src/utils/EIP6963Provider.class.ts | 64 ++++++++++++++++++++++ src/utils/eip.utils.ts | 17 ++++++ src/utils/errors.ts | 2 +- src/utils/types.ts | 27 ++++++++++ 13 files changed, 332 insertions(+), 26 deletions(-) create mode 100644 src/hooks/useEIP6963.ts create mode 100644 src/hooks/useEIP6963Manager.ts create mode 100644 src/providers/EIP6963Context.ts create mode 100644 src/providers/EIP6963ManagerContext.ts create mode 100644 src/providers/EIP6963ManagerProvider.tsx create mode 100644 src/providers/EIP6963Provider.tsx create mode 100644 src/utils/EIP6963Provider.class.ts create mode 100644 src/utils/eip.utils.ts diff --git a/src/App.tsx b/src/App.tsx index 35ed69b..bf033fb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,8 @@ import { ConnectWallet } from './pages/ConnectWallet' import { WrapFormContextProvider } from './providers/WrapFormProvider' import { Transaction } from './pages/Transaction' import { EIP1193ContextProvider } from './providers/EIP1193Provider.tsx' +import { EIP6963ManagerContextProvider } from './providers/EIP6963ManagerProvider.tsx' +import { EIP6963ContextProvider } from './providers/EIP6963Provider.tsx' const router = createHashRouter([ { @@ -34,9 +36,13 @@ const router = createHashRouter([ ]) export const App: FC = () => ( - - - - - + + + + + + + + + ) diff --git a/src/hooks/useEIP6963.ts b/src/hooks/useEIP6963.ts new file mode 100644 index 0000000..5568a40 --- /dev/null +++ b/src/hooks/useEIP6963.ts @@ -0,0 +1,11 @@ +import { useContext } from 'react' +import { EIP6963Context } from '../providers/EIP6963Context.ts' + +export const useEIP6963 = () => { + const value = useContext(EIP6963Context) + if (value === undefined) { + throw new Error('[useEIP6963] Component not wrapped within a Provider') + } + + return value +} diff --git a/src/hooks/useEIP6963Manager.ts b/src/hooks/useEIP6963Manager.ts new file mode 100644 index 0000000..acd0393 --- /dev/null +++ b/src/hooks/useEIP6963Manager.ts @@ -0,0 +1,11 @@ +import { useContext } from 'react' +import { EIP6963ManagerContext } from '../providers/EIP6963ManagerContext.ts' + +export const useEIP6963Manager = () => { + const value = useContext(EIP6963ManagerContext) + if (value === undefined) { + throw new Error('[useEIP6963Manager] Component not wrapped within a Provider') + } + + return value +} diff --git a/src/providers/EIP1193Context.ts b/src/providers/EIP1193Context.ts index 5b9e443..2598e66 100644 --- a/src/providers/EIP1193Context.ts +++ b/src/providers/EIP1193Context.ts @@ -1,10 +1,11 @@ import { createContext } from 'react' +import { EIP1193Provider } from '../utils/types.ts' export interface EIP1193ProviderContext { isEIP1193ProviderAvailable: () => Promise - connectWallet: () => Promise - switchNetwork: (chainId: number) => void - addTokenToWallet: (wRoseContractAddress: string) => Promise + connectWallet: (provider?: EIP1193Provider) => Promise + switchNetwork: (chainId: number, provider?: EIP1193Provider) => void + addTokenToWallet: (wRoseContractAddress: string, provider?: EIP1193Provider) => Promise } export const EIP1193Context = createContext({} as EIP1193ProviderContext) diff --git a/src/providers/EIP1193Provider.tsx b/src/providers/EIP1193Provider.tsx index ea2b131..8386c23 100644 --- a/src/providers/EIP1193Provider.tsx +++ b/src/providers/EIP1193Provider.tsx @@ -1,13 +1,14 @@ import { FC, PropsWithChildren } from 'react' import { ethers, utils } from 'ethers' import * as sapphire from '@oasisprotocol/sapphire-paratime' -import { MetaMaskError } from '../utils/errors' +import { EIP1193Error } from '../utils/errors' import detectEthereumProvider from '@metamask/detect-provider' import { EIP1193Context, EIP1193ProviderContext } from './EIP1193Context.ts' +import { EIP1193Provider } from '../utils/types.ts' declare global { interface Window { - ethereum?: ethers.providers.ExternalProvider & ethers.providers.Web3Provider + ethereum?: EIP1193Provider } } @@ -18,11 +19,11 @@ export const EIP1193ContextProvider: FC = ({ children }) => { mustBeMetaMask: false, }) - return !!window.ethereum && provider === window.ethereum + return !!provider } - const connectWallet = async (): Promise => { - const accounts: string[] = await (window.ethereum?.request?.({ method: 'eth_requestAccounts' }) || + const connectWallet = async (provider = window.ethereum): Promise => { + const accounts: string[] = await (provider?.request?.({ method: 'eth_requestAccounts' }) || Promise.resolve([])) if (!accounts || accounts?.length <= 0) { @@ -32,10 +33,10 @@ export const EIP1193ContextProvider: FC = ({ children }) => { return accounts[0] } - const _addNetwork = (chainId: number) => { + const _addNetwork = (chainId: number, provider = window.ethereum) => { if (chainId === 0x5afe) { // Default to Sapphire Mainnet - return window.ethereum?.request?.({ + return provider?.request?.({ method: 'wallet_addEthereumChain', params: [ { @@ -56,8 +57,8 @@ export const EIP1193ContextProvider: FC = ({ children }) => { throw new Error('Unable to automatically add the network, please do it manually!') } - const switchNetwork = async (chainId = 0x5afe) => { - const ethProvider = new ethers.providers.Web3Provider(window.ethereum!) + const switchNetwork = async (chainId = 0x5afe, provider = window.ethereum) => { + const ethProvider = new ethers.providers.Web3Provider(provider!) const sapphireEthProvider = sapphire.wrap(ethProvider) as ethers.providers.Web3Provider & sapphire.SapphireAnnex @@ -65,28 +66,28 @@ export const EIP1193ContextProvider: FC = ({ children }) => { if (network.chainId === chainId) return try { - await window.ethereum!.request?.({ + await provider!.request?.({ method: 'wallet_switchEthereumChain', params: [{ chainId: utils.hexlify(chainId) }], }) } catch (e) { - const metaMaskError = e as MetaMaskError - // Metamask desktop - Throws e.code 4902 when chain is not available - // Metamask mobile - Throws generic -32603 (https://github.com/MetaMask/metamask-mobile/issues/3312) + const error = e as EIP1193Error + // EIP1193 desktop - Throws e.code 4902 when chain is not available + // Metamask mobile(edge case) - Throws generic -32603 (https://github.com/MetaMask/metamask-mobile/issues/3312) - if (metaMaskError?.code !== 4902 && metaMaskError?.code !== -32603) { - throw metaMaskError + if (error?.code !== 4902 && error?.code !== -32603) { + throw error } else { _addNetwork(chainId) } } } - const addTokenToWallet = async (wRoseContractAddress: string) => { + const addTokenToWallet = async (wRoseContractAddress: string, provider = window.ethereum) => { const symbol = 'WROSE' try { - await window.ethereum?.request?.({ + await provider?.request?.({ method: 'wallet_watchAsset', params: { type: 'ERC20', diff --git a/src/providers/EIP6963Context.ts b/src/providers/EIP6963Context.ts new file mode 100644 index 0000000..97f2d90 --- /dev/null +++ b/src/providers/EIP6963Context.ts @@ -0,0 +1,17 @@ +import { createContext } from 'react' +import { EIP6963ClassProvider } from '../utils/EIP6963Provider.class.ts' + +export interface EIP6963ProviderState { + provider: EIP6963ClassProvider + isEIP6963ProviderAvailable: boolean +} + +export interface EIP6963ProviderContext { + readonly state: EIP6963ProviderState + isEIP6963ProviderAvailableSync: () => boolean + connectWallet: () => Promise + switchNetwork: (chainId: number) => void + addTokenToWallet: (wRoseContractAddress: string) => Promise +} + +export const EIP6963Context = createContext({} as EIP6963ProviderContext) diff --git a/src/providers/EIP6963ManagerContext.ts b/src/providers/EIP6963ManagerContext.ts new file mode 100644 index 0000000..a077dbe --- /dev/null +++ b/src/providers/EIP6963ManagerContext.ts @@ -0,0 +1,18 @@ +import { createContext } from 'react' +import { EIP6963ProviderDetail } from '../utils/types.ts' + +export interface EIP6963ManagerProviderState { + providerList: EIP6963ProviderDetail[] +} + +export interface EIP6963ManagerProviderContext { + readonly state: EIP6963ManagerProviderState +} + +type MutableInjectedProviderMap = Map + +export const EIP6963_MANAGER_INJECTED_PROVIDERS_MAP: MutableInjectedProviderMap = new Map() + +export const EIP6963ManagerContext = createContext( + {} as EIP6963ManagerProviderContext, +) diff --git a/src/providers/EIP6963ManagerProvider.tsx b/src/providers/EIP6963ManagerProvider.tsx new file mode 100644 index 0000000..ee5460f --- /dev/null +++ b/src/providers/EIP6963ManagerProvider.tsx @@ -0,0 +1,69 @@ +import { FC, PropsWithChildren, useEffect, useState } from 'react' +import { + EIP6963_MANAGER_INJECTED_PROVIDERS_MAP, + EIP6963ManagerContext, + EIP6963ManagerProviderContext, + EIP6963ManagerProviderState, +} from './EIP6963ManagerContext.ts' +import { EIP6963AnnounceProviderEvent, EIP6963Event } from '../utils/types.ts' +import { EIPUtils } from '../utils/eip.utils.ts' + +interface CustomEventMap { + [EIP6963Event.ANNOUNCE_PROVIDER]: CustomEvent +} + +declare global { + interface Document { + addEventListener( + type: K, + listener: (this: Document, ev: CustomEventMap[K]) => void, + ): void + + dispatchEvent(ev: CustomEventMap[K]): void + } +} + +const eip6963ManagerProviderInitialState: EIP6963ManagerProviderState = { + providerList: [], +} + +export const EIP6963ManagerContextProvider: FC = ({ children }) => { + const [state, setState] = useState({ + ...eip6963ManagerProviderInitialState, + }) + + const _onAnnounceProvider: EIP6963AnnounceProviderEvent = event => { + if (!EIPUtils.isEIP6963Provider(event.detail)) { + return + } + + const { detail } = event + + // Ignore duplicated providers with same rdns + const providerDetails = EIP6963_MANAGER_INJECTED_PROVIDERS_MAP.get(detail.info.rdns!) + if (providerDetails) { + if (providerDetails.provider !== detail.provider) { + console.warn(`Duplicate provider detected for wallet with rdns: ${detail.info.rdns}!`) + } + return + } + + EIP6963_MANAGER_INJECTED_PROVIDERS_MAP.set(detail.info.rdns!, detail) + + setState(prevState => ({ + ...prevState, + providerList: [...prevState.providerList, detail], + })) + } + + useEffect(() => { + window.addEventListener(EIP6963Event.ANNOUNCE_PROVIDER, _onAnnounceProvider as EventListener) + window.dispatchEvent(new Event(EIP6963Event.REQUEST_PROVIDER)) + }, []) + + const providerState: EIP6963ManagerProviderContext = { + state, + } + + return {children} +} diff --git a/src/providers/EIP6963Provider.tsx b/src/providers/EIP6963Provider.tsx new file mode 100644 index 0000000..549a6fd --- /dev/null +++ b/src/providers/EIP6963Provider.tsx @@ -0,0 +1,64 @@ +import { FC, PropsWithChildren, useEffect, useState } from 'react' +import { EIP6963Context, EIP6963ProviderContext, EIP6963ProviderState } from './EIP6963Context.ts' +import { EIP6963ClassProvider } from '../utils/EIP6963Provider.class.ts' +import { useEIP6963Manager } from '../hooks/useEIP6963Manager.ts' +import { useEIP1193 } from '../hooks/useEIP1193.ts' + +const eip6963ProviderInitialState: EIP6963ProviderState = { + provider: new EIP6963ClassProvider(), + isEIP6963ProviderAvailable: false, +} + +export const EIP6963ContextProvider: FC = ({ children }) => { + const { + state: { providerList }, + } = useEIP6963Manager() + const { + connectWallet: connectWalletEIP1193, + switchNetwork: switchNetworkEIP1193, + addTokenToWallet: addTokenToWalletEIP1193, + } = useEIP1193() + + const [state, setState] = useState({ + ...eip6963ProviderInitialState, + }) + + useEffect(() => { + setState(prevState => ({ + ...prevState, + isEIP6963ProviderAvailable: !!providerList.length, + })) + }, [providerList]) + + const isEIP6963ProviderAvailableSync = () => { + return providerList.length > 0 + } + + const connectWallet = async (): Promise => { + const { provider } = state + + return await connectWalletEIP1193(provider) + } + + const switchNetwork = async (chainId = 0x5afe) => { + const { provider } = state + + return switchNetworkEIP1193(chainId, provider) + } + + const addTokenToWallet = async (wRoseContractAddress: string) => { + const { provider } = state + + return addTokenToWalletEIP1193(wRoseContractAddress, provider) + } + + const providerState: EIP6963ProviderContext = { + state, + isEIP6963ProviderAvailableSync, + connectWallet, + switchNetwork, + addTokenToWallet, + } + + return {children} +} diff --git a/src/utils/EIP6963Provider.class.ts b/src/utils/EIP6963Provider.class.ts new file mode 100644 index 0000000..a049c90 --- /dev/null +++ b/src/utils/EIP6963Provider.class.ts @@ -0,0 +1,64 @@ +import { EIP1193Provider, EIP6963ProviderDetail } from './types.ts' +import { EIP6963_MANAGER_INJECTED_PROVIDERS_MAP } from '../providers/EIP6963ManagerContext.ts' + +interface Listener { + (...args: T[]): void +} + +export class EIP6963ClassProvider implements EIP1193Provider { + currentProviderDetail?: EIP6963ProviderDetail + + // Stores stable references to prevent memory leaks + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private readonly proxyListeners: { [eventName: string | symbol]: Listener[] } = {} + + request = async (args: { method: string; params?: T }): Promise => + this.currentProviderDetail && this.currentProviderDetail.provider + ? this.currentProviderDetail.provider.request(args) + : Promise.reject('Provider is undefined') + + on = (eventName: string, listener: Listener): this => { + if (!this.proxyListeners[eventName]) { + this.proxyListeners[eventName] = [] + } + this.proxyListeners[eventName].push(listener) + this.currentProviderDetail?.provider.on(eventName, listener) + + return this + } + + removeListener = (eventName: string | symbol, listener: Listener): this => { + this.currentProviderDetail?.provider.removeListener(eventName, listener) + + if (this.proxyListeners[eventName]) { + const index = this.proxyListeners[eventName]?.indexOf(listener) + if (index !== -1) { + // proxyListeners must be referentially stable + this.proxyListeners[eventName]?.splice(index, 1) + } + } + + return this + } + + private transferListeners( + eventName: string, + newProvider?: EIP6963ProviderDetail, + oldProvider?: EIP6963ProviderDetail, + ): void { + if (!eventName) return + for (const proxyListener of this.proxyListeners[eventName]) { + oldProvider?.provider.removeListener(eventName, proxyListener) + newProvider?.provider.on(eventName, proxyListener) + } + } + + setCurrentProvider(rdns: string) { + const oldProvider = this.currentProviderDetail + const newProvider = (this.currentProviderDetail = EIP6963_MANAGER_INJECTED_PROVIDERS_MAP.get(rdns)) + + for (const eventName in this.proxyListeners) { + this.transferListeners(eventName, newProvider, oldProvider) + } + } +} diff --git a/src/utils/eip.utils.ts b/src/utils/eip.utils.ts new file mode 100644 index 0000000..81b55f9 --- /dev/null +++ b/src/utils/eip.utils.ts @@ -0,0 +1,17 @@ +import { EIP1193Provider, EIP6963ProviderDetail } from './types.ts' + +export abstract class EIPUtils { + static isEIP1193Provider(value: Partial): value is EIP1193Provider { + return !!(value.request && value.on && value.removeListener) + } + + static isEIP6963Provider = (value: Partial): value is EIP6963ProviderDetail => { + const { provider, info } = value + const { name, uuid, rdns, icon } = info || {} + + const providerExist = !!(provider && EIPUtils.isEIP1193Provider(provider)) + const infoExist = !!(info && name && uuid && rdns && icon) + + return providerExist && infoExist + } +} diff --git a/src/utils/errors.ts b/src/utils/errors.ts index b75f8e4..f98be37 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -4,6 +4,6 @@ export class UnknownNetworkError extends Error { } } -export interface MetaMaskError extends Error { +export interface EIP1193Error extends Error { code: number } diff --git a/src/utils/types.ts b/src/utils/types.ts index b400fa0..7e64e97 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -2,3 +2,30 @@ export enum WrapFormType { WRAP = 'wrap', UNWRAP = 'unwrap', } + +export interface EIP1193Provider { + request: (payload: { method: string; params?: T }) => Promise + on: (eventName: string, callback: (...args: T[]) => void) => this + removeListener: (eventName: string | symbol, listener: (...args: T[]) => void) => this +} + +export enum EIP6963Event { + REQUEST_PROVIDER = 'eip6963:requestProvider', + ANNOUNCE_PROVIDER = 'eip6963:announceProvider', +} + +export interface EIP6963ProviderInfo { + uuid: string + name: string + icon: string + rdns?: string +} + +export interface EIP6963ProviderDetail { + info: EIP6963ProviderInfo + provider: EIP1193Provider +} + +export interface EIP6963AnnounceProviderEvent { + (evt: Event & { detail: EIP6963ProviderDetail }): void +} From 919fb8cc4bd2152f16934d97ca3fd9e3e8045add Mon Sep 17 00:00:00 2001 From: lubej Date: Fri, 23 Feb 2024 16:09:05 +0100 Subject: [PATCH 3/7] Add set/get on EIP-6963 Provider for current provider --- src/providers/EIP6963Context.ts | 3 +++ src/providers/EIP6963Provider.tsx | 30 ++++++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/providers/EIP6963Context.ts b/src/providers/EIP6963Context.ts index 97f2d90..453a156 100644 --- a/src/providers/EIP6963Context.ts +++ b/src/providers/EIP6963Context.ts @@ -1,5 +1,6 @@ import { createContext } from 'react' import { EIP6963ClassProvider } from '../utils/EIP6963Provider.class.ts' +import { EIP1193Provider } from '../utils/types.ts' export interface EIP6963ProviderState { provider: EIP6963ClassProvider @@ -12,6 +13,8 @@ export interface EIP6963ProviderContext { connectWallet: () => Promise switchNetwork: (chainId: number) => void addTokenToWallet: (wRoseContractAddress: string) => Promise + setCurrentProviderByRdns: (rdns: string) => void + getCurrentProvider: () => EIP1193Provider | undefined } export const EIP6963Context = createContext({} as EIP6963ProviderContext) diff --git a/src/providers/EIP6963Provider.tsx b/src/providers/EIP6963Provider.tsx index 549a6fd..3362d91 100644 --- a/src/providers/EIP6963Provider.tsx +++ b/src/providers/EIP6963Provider.tsx @@ -24,10 +24,18 @@ export const EIP6963ContextProvider: FC = ({ children }) => { }) useEffect(() => { - setState(prevState => ({ - ...prevState, - isEIP6963ProviderAvailable: !!providerList.length, - })) + const { isEIP6963ProviderAvailable } = state + + const isEIP6963ProviderAvailableNext = !!providerList.length + + if (isEIP6963ProviderAvailable !== isEIP6963ProviderAvailableNext) { + setState(prevState => ({ + ...prevState, + isEIP6963ProviderAvailable: isEIP6963ProviderAvailableNext, + })) + } + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [providerList]) const isEIP6963ProviderAvailableSync = () => { @@ -52,12 +60,26 @@ export const EIP6963ContextProvider: FC = ({ children }) => { return addTokenToWalletEIP1193(wRoseContractAddress, provider) } + const setCurrentProviderByRdns = (rdns: string) => { + const { provider } = state + + provider.setCurrentProvider(rdns) + } + + const getCurrentProvider = () => { + const { provider } = state + + return provider.currentProviderDetail?.provider + } + const providerState: EIP6963ProviderContext = { state, isEIP6963ProviderAvailableSync, connectWallet, switchNetwork, addTokenToWallet, + setCurrentProviderByRdns, + getCurrentProvider, } return {children} From 1ae9075130f3b06699fa05a0b6a2f3a25531bf34 Mon Sep 17 00:00:00 2001 From: lubej Date: Fri, 23 Feb 2024 16:10:42 +0100 Subject: [PATCH 4/7] Add support for EIP-6963 provider in Web3 provider --- src/providers/Web3Context.ts | 2 +- src/providers/Web3Provider.tsx | 54 ++++++++++++++++++++++------------ 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/providers/Web3Context.ts b/src/providers/Web3Context.ts index 9c3b5e0..4e71454 100644 --- a/src/providers/Web3Context.ts +++ b/src/providers/Web3Context.ts @@ -24,7 +24,7 @@ export interface Web3ProviderContext { readonly state: Web3ProviderState wrap: (amount: string, gasPrice: BigNumber) => Promise unwrap: (amount: string, gasPrice: BigNumber) => Promise - connectWallet: () => Promise + connectWallet: (providerType?: ProviderType) => Promise switchNetwork: (chainId?: number) => Promise getBalance: () => Promise getBalanceOfWROSE: () => Promise diff --git a/src/providers/Web3Provider.tsx b/src/providers/Web3Provider.tsx index ba04abe..8f61703 100644 --- a/src/providers/Web3Provider.tsx +++ b/src/providers/Web3Provider.tsx @@ -8,6 +8,7 @@ import WrappedRoseMetadata from '../contracts/WrappedROSE.json' import { UnknownNetworkError } from '../utils/errors' import { ProviderType, Web3Context, Web3ProviderContext, Web3ProviderState } from './Web3Context' import { useEIP1193 } from '../hooks/useEIP1193.ts' +import { useEIP6963 } from '../hooks/useEIP6963.ts' const web3ProviderInitialState: Web3ProviderState = { isConnected: false, @@ -18,7 +19,7 @@ const web3ProviderInitialState: Web3ProviderState = { account: null, explorerBaseUrl: null, networkName: null, - providerType: ProviderType.EIP1193, + providerType: ProviderType.EIP6963, } export const Web3ContextProvider: FC = ({ children }) => { @@ -28,6 +29,13 @@ export const Web3ContextProvider: FC = ({ children }) => { switchNetwork: switchNetworkEIP1193, addTokenToWallet: addTokenToWalletEIP1193, } = useEIP1193() + const { + isEIP6963ProviderAvailableSync, + connectWallet: connectWalletEIP6963, + switchNetwork: switchNetworkEIP6963, + addTokenToWallet: addTokenToWalletEIP6963, + getCurrentProvider: getEIP6963CurrentProvider, + } = useEIP6963() const [state, setState] = useState({ ...web3ProviderInitialState, @@ -157,41 +165,49 @@ export const Web3ContextProvider: FC = ({ children }) => { } const isProviderAvailable = async () => { - const { providerType } = state - - switch (providerType) { - case ProviderType.EIP1193: - return isEIP1193ProviderAvailable() - default: - return false - } + return (await isEIP1193ProviderAvailable()) || isEIP6963ProviderAvailableSync() } - const _getConnectedAccount = () => { - const { providerType } = state - + const _getConnectedAccount = (providerType?: ProviderType) => { switch (providerType) { case ProviderType.EIP1193: return connectWalletEIP1193() default: - return + return connectWalletEIP6963() } } - const connectWallet = async () => { - const { providerType } = state - const account = await _getConnectedAccount() + const connectWallet = async (providerType?: ProviderType) => { + const { providerType: globalProviderType } = state + const currentProviderType = providerType ?? globalProviderType + + const account = await _getConnectedAccount(currentProviderType) if (!account) { throw new Error('[Web3Context] Request account failed!') } - switch (providerType) { + switch (currentProviderType) { case ProviderType.EIP1193: { await _init(account, window.ethereum) _addEventListenersOnce(window.ethereum) + + break + } + default: { + const provider = getEIP6963CurrentProvider() + + await _init(account, provider) + _addEventListenersOnce(provider) } } + + if (currentProviderType !== globalProviderType) { + setState(prevState => ({ + ...prevState, + providerType: currentProviderType, + })) + } } const switchNetwork = async (chainId = 0x5afe) => { @@ -201,7 +217,7 @@ export const Web3ContextProvider: FC = ({ children }) => { case ProviderType.EIP1193: return switchNetworkEIP1193(chainId) default: - return + return switchNetworkEIP6963(chainId) } } @@ -271,6 +287,8 @@ export const Web3ContextProvider: FC = ({ children }) => { switch (providerType) { case ProviderType.EIP1193: return addTokenToWalletEIP1193(wRoseContractAddress) + default: + return addTokenToWalletEIP6963(wRoseContractAddress) } } From 032bfa564812efd417252497e08ae343f4266145 Mon Sep 17 00:00:00 2001 From: lubej Date: Fri, 23 Feb 2024 16:11:16 +0100 Subject: [PATCH 5/7] Add EIP-6963 Wallet connect modal --- src/components/WalletModal/index.module.css | 64 ++++++++++++++++++ src/components/WalletModal/index.tsx | 74 +++++++++++++++++++++ src/hooks/useEIP6963ProviderInfoList.ts | 10 +++ 3 files changed, 148 insertions(+) create mode 100644 src/components/WalletModal/index.module.css create mode 100644 src/components/WalletModal/index.tsx create mode 100644 src/hooks/useEIP6963ProviderInfoList.ts diff --git a/src/components/WalletModal/index.module.css b/src/components/WalletModal/index.module.css new file mode 100644 index 0000000..6ef2d30 --- /dev/null +++ b/src/components/WalletModal/index.module.css @@ -0,0 +1,64 @@ +.walletModalContent { + display: flex; + flex-direction: column; + padding-bottom: 2rem; + + h4 { + color: var(--gray-extra-dark); + margin-bottom: 2rem; + text-align: center; + } +} + +.walletModalLogo { + align-self: center; + margin-bottom: 3.125rem; +} + +.walletModalProviderList { + display: flex; + flex-direction: column; + gap: 0.5rem; + + .providerOption { + background-color: var(--gray-medium-light); + border-radius: 46px; + } +} + +.providerOptionBtn { + display: flex; + align-items: center; + padding: 0.5rem 2rem; + gap: 0.75rem; + background: none; + border: none; + font: inherit; + outline: inherit; + color: var(--gray-extra-dark); + cursor: pointer; + width: 100%; + border-radius: 46px; + + &:hover, + &:focus { + background-color: var(--gray-extra-dark); + color: var(--white); + } +} + +.providerOptionLogo { + object-fit: cover; + width: 45px; + height: 45px; +} + +@media screen and (max-width: 1000px) { + .walletModal { + max-width: unset; + } + + .walletModalLogo { + margin-bottom: 2rem; + } +} diff --git a/src/components/WalletModal/index.tsx b/src/components/WalletModal/index.tsx new file mode 100644 index 0000000..e90604d --- /dev/null +++ b/src/components/WalletModal/index.tsx @@ -0,0 +1,74 @@ +import { FC, useState } from 'react' +import { Modal, ModalProps } from '../Modal' +import { LogoIconRound } from '../icons/LogoIconRound.tsx' +import classes from './index.module.css' +import { useEIP6963ProviderInfoList } from '../../hooks/useEIP6963ProviderInfoList.ts' +import { EIP6963ProviderInfo } from '../../utils/types.ts' +import { useEIP6963 } from '../../hooks/useEIP6963.ts' + +interface WalletModalProps extends Pick { + next: () => void +} + +interface ProviderOptionProps extends Pick { + isLoading: boolean + onSelectProvider: (rdns: string) => void +} + +const ProviderOption: FC = ({ rdns, name, icon, onSelectProvider, isLoading }) => { + if (!rdns) { + return null + } + + return ( +
+ +
+ ) +} + +export const WalletModal: FC = ({ isOpen, closeModal, next }) => { + const { setCurrentProviderByRdns } = useEIP6963() + const providerInfoList = useEIP6963ProviderInfoList() + + const [isLoading, setIsLoading] = useState(false) + + const onSelectProvider = (rdns: string) => { + setIsLoading(true) + setCurrentProviderByRdns(rdns) + next() + setIsLoading(false) + } + + const providerInfoOptionsList = providerInfoList.map(({ uuid, rdns, name, icon }) => ( + + )) + + return ( + +
+
+ +
+ +

Connect a wallet

+ +
{providerInfoOptionsList}
+
+
+ ) +} diff --git a/src/hooks/useEIP6963ProviderInfoList.ts b/src/hooks/useEIP6963ProviderInfoList.ts new file mode 100644 index 0000000..4b449bf --- /dev/null +++ b/src/hooks/useEIP6963ProviderInfoList.ts @@ -0,0 +1,10 @@ +import { useEIP6963Manager } from './useEIP6963Manager.ts' +import { EIP6963ProviderInfo } from '../utils/types.ts' + +export const useEIP6963ProviderInfoList = (): EIP6963ProviderInfo[] => { + const { + state: { providerList }, + } = useEIP6963Manager() + + return providerList.map(({ info }) => info) +} From 08196a0b433b151c652ded06b90ce49fd6b551a6 Mon Sep 17 00:00:00 2001 From: lubej Date: Fri, 23 Feb 2024 16:11:45 +0100 Subject: [PATCH 6/7] Integrate EIP-6963 modal on ConnectWallet page --- src/pages/ConnectWallet/index.tsx | 50 +++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/src/pages/ConnectWallet/index.tsx b/src/pages/ConnectWallet/index.tsx index bcfba7d..8cd1ead 100644 --- a/src/pages/ConnectWallet/index.tsx +++ b/src/pages/ConnectWallet/index.tsx @@ -5,10 +5,24 @@ import { UnknownNetworkError } from '../../utils/errors' import { Alert } from '../../components/Alert' import { METAMASK_HOME_PAGE } from '../../constants/config' import { useWeb3 } from '../../hooks/useWeb3' +import { WalletModal } from '../../components/WalletModal' +import { ProviderType } from '../../providers/Web3Context.ts' +import { useEIP6963 } from '../../hooks/useEIP6963.ts' +import { useEIP1193 } from '../../hooks/useEIP1193.ts' export const ConnectWallet: FC = () => { - const { connectWallet, switchNetwork, isProviderAvailable } = useWeb3() + const { + state: { isEIP6963ProviderAvailable }, + } = useEIP6963() + const { isEIP1193ProviderAvailable } = useEIP1193() + const { + state: { providerType }, + connectWallet, + switchNetwork, + isProviderAvailable, + } = useWeb3() const [isLoading, setIsLoading] = useState(false) + const [isWalletModalOpen, setIsWalletModalOpen] = useState(false) const [error, setError] = useState('') const [providerAvailable, setProviderAvailable] = useState(true) const [isUnknownNetwork, setIsUnknownNetwork] = useState(false) @@ -22,12 +36,33 @@ export const ConnectWallet: FC = () => { init() // eslint-disable-next-line react-hooks/exhaustive-deps - }, [window.ethereum]) + }, []) + + useEffect(() => { + const setAvailable = async () => { + setProviderAvailable((await isEIP1193ProviderAvailable()) || isEIP6963ProviderAvailable) + } + + setAvailable() + }, [isEIP1193ProviderAvailable, isEIP6963ProviderAvailable]) + + const handleProviderConnectionType = () => { + // This is async version of EIP-6963 + if (isEIP6963ProviderAvailable) { + setIsWalletModalOpen(true) + + return + } + + handleConnectWallet(ProviderType.EIP1193) + } + + const handleConnectWallet = async (walletProviderType: ProviderType = providerType) => { + setIsWalletModalOpen(false) - const handleConnectWallet = async () => { setIsLoading(true) try { - await connectWallet() + await connectWallet(walletProviderType) } catch (ex) { if (ex instanceof UnknownNetworkError) { setIsUnknownNetwork(true) @@ -86,7 +121,7 @@ export const ConnectWallet: FC = () => { Please connect your wallet to get started.

- {error && {error}} @@ -108,6 +143,11 @@ export const ConnectWallet: FC = () => { )} )} + setIsWalletModalOpen(false)} + next={() => handleConnectWallet(ProviderType.EIP6963)} + /> ) } From f2aa2ae98da037b51c26d3c60a97f8d052fdd0a4 Mon Sep 17 00:00:00 2001 From: Matej Lubej Date: Fri, 19 Apr 2024 07:32:56 +0200 Subject: [PATCH 7/7] Fix ContextProviders default value --- src/hooks/useEIP1193.ts | 2 +- src/hooks/useEIP6963.ts | 2 +- src/hooks/useEIP6963Manager.ts | 2 +- src/hooks/useWeb3.ts | 2 +- src/hooks/useWrapForm.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hooks/useEIP1193.ts b/src/hooks/useEIP1193.ts index ce0d2d5..edbf41a 100644 --- a/src/hooks/useEIP1193.ts +++ b/src/hooks/useEIP1193.ts @@ -3,7 +3,7 @@ import { EIP1193Context } from '../providers/EIP1193Context' export const useEIP1193 = () => { const value = useContext(EIP1193Context) - if (value === undefined) { + if (Object.keys(value).length === 0) { throw new Error('[useEIP1193] Component not wrapped within a Provider') } diff --git a/src/hooks/useEIP6963.ts b/src/hooks/useEIP6963.ts index 5568a40..cc915eb 100644 --- a/src/hooks/useEIP6963.ts +++ b/src/hooks/useEIP6963.ts @@ -3,7 +3,7 @@ import { EIP6963Context } from '../providers/EIP6963Context.ts' export const useEIP6963 = () => { const value = useContext(EIP6963Context) - if (value === undefined) { + if (Object.keys(value).length === 0) { throw new Error('[useEIP6963] Component not wrapped within a Provider') } diff --git a/src/hooks/useEIP6963Manager.ts b/src/hooks/useEIP6963Manager.ts index acd0393..d3b3cbf 100644 --- a/src/hooks/useEIP6963Manager.ts +++ b/src/hooks/useEIP6963Manager.ts @@ -3,7 +3,7 @@ import { EIP6963ManagerContext } from '../providers/EIP6963ManagerContext.ts' export const useEIP6963Manager = () => { const value = useContext(EIP6963ManagerContext) - if (value === undefined) { + if (Object.keys(value).length === 0) { throw new Error('[useEIP6963Manager] Component not wrapped within a Provider') } diff --git a/src/hooks/useWeb3.ts b/src/hooks/useWeb3.ts index 1030fad..1ed9986 100644 --- a/src/hooks/useWeb3.ts +++ b/src/hooks/useWeb3.ts @@ -3,7 +3,7 @@ import { Web3Context } from '../providers/Web3Context' export const useWeb3 = () => { const value = useContext(Web3Context) - if (value === undefined) { + if (Object.keys(value).length === 0) { throw new Error('[useWeb3] Component not wrapped within a Provider') } diff --git a/src/hooks/useWrapForm.ts b/src/hooks/useWrapForm.ts index 990cd82..21733a9 100644 --- a/src/hooks/useWrapForm.ts +++ b/src/hooks/useWrapForm.ts @@ -3,7 +3,7 @@ import { WrapFormContext } from '../providers/WrapFormContext' export const useWrapForm = () => { const value = useContext(WrapFormContext) - if (value === undefined) { + if (Object.keys(value).length === 0) { throw new Error('[useWrapForm] Component not wrapped within a Provider') }