diff --git a/src/components/Alert/index.module.css b/src/components/Alert/index.module.css index 917f217..ca9a100 100644 --- a/src/components/Alert/index.module.css +++ b/src/components/Alert/index.module.css @@ -1,11 +1,12 @@ .alert { - padding: .25rem; + padding: .5rem .25rem; background-color: white; font-size: 14px; line-height: 120%; border-radius: 10px; margin-top: 1rem; text-align: center; + word-wrap: break-word; } .alertDanger { diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx index acb6601..719b06c 100644 --- a/src/components/Layout/index.tsx +++ b/src/components/Layout/index.tsx @@ -8,7 +8,7 @@ export const Layout: FC = () => { const { pathname } = useLocation() const navigate = useNavigate() - // TODO: Unable to use hooks inside loader in react-router-dom + // TODO: Unable to use hooks inside loader in react-router-dom, needs refactoring useEffect(() => { // Route guard // Ignore for tx diff --git a/src/pages/ConnectWallet/index.tsx b/src/pages/ConnectWallet/index.tsx index 52ab25c..92629d3 100644 --- a/src/pages/ConnectWallet/index.tsx +++ b/src/pages/ConnectWallet/index.tsx @@ -2,31 +2,64 @@ import { FC, useState } from 'react' import classes from './index.module.css' import { useWeb3 } from '../../providers/Web3Provider' import { Button } from '../../components/Button' +import { UnknownNetworkError } from '../../utils/errors' +import { Alert } from '../../components/Alert' export const ConnectWallet: FC = () => { - const { connectWallet } = useWeb3() + const { connectWallet, switchNetwork } = useWeb3() const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState('') + const [isUnknownNetwork, setIsUnknownNetwork] = useState(false) const handleConnectWallet = async () => { setIsLoading(true) try { await connectWallet() } catch (ex) { - console.error(ex) + if (ex instanceof UnknownNetworkError) { + setIsUnknownNetwork(true) + } else { + setError(ex?.message || JSON.stringify(ex)) + } + } finally { + setIsLoading(false) + } + } + + const handleSwitchNetwork = async () => { + setIsLoading(true) + try { + await switchNetwork() + setIsUnknownNetwork(false) + } catch (ex) { + setError(ex?.message || JSON.stringify(ex)) } finally { setIsLoading(false) } } return ( -
-

- Quickly wrap your ROSE into wROSE and vice versa with the (un)wrap ROSE tool. -
- Please connect your wallet to get started. -

+ <> + {!isUnknownNetwork && (
+

+ Quickly wrap your ROSE into wROSE and vice versa with the (un)wrap ROSE tool. +
+ Please connect your wallet to get started. +

+ + + {error && {error}} +
)} + {isUnknownNetwork && (
+

+ Quickly wrap your ROSE into wROSE and vice versa with the (un)wrap ROSE tool. +
+ Please switch to another network to get started. +

- -
+ + {error && {error}} +
)} + ) } diff --git a/src/providers/Web3Provider.tsx b/src/providers/Web3Provider.tsx index 6241154..7fcde6b 100644 --- a/src/providers/Web3Provider.tsx +++ b/src/providers/Web3Provider.tsx @@ -5,6 +5,7 @@ import { WROSE_CONTRACT_BY_NETWORK } from '../constants/config' // https://repo.sourcify.dev/contracts/full_match/23295/0xB759a0fbc1dA517aF257D5Cf039aB4D86dFB3b94/ import WrappedRoseMetadata from '../contracts/WrappedROSE.json' import { TransactionResponse } from '@ethersproject/abstract-provider' +import { UnknownNetworkError } from '../utils/errors' const MAX_GAS_PRICE = utils.parseUnits('100', 'gwei').toNumber() const MAX_GAS_LIMIT = 100000 @@ -28,6 +29,7 @@ interface Web3ProviderContext { wrap: (amount: string) => Promise unwrap: (amount: string) => Promise connectWallet: () => Promise + switchNetwork: () => Promise getBalance: () => Promise getBalanceOfWROSE: () => Promise getTransaction: (txHash: string) => Promise @@ -56,8 +58,7 @@ export const Web3ContextProvider: FC = ({ children }) => { const network = await sapphireEthProvider.getNetwork() if (!(network.chainId in WROSE_CONTRACT_BY_NETWORK)) { - // TODO: Propagate unsupported network error - throw new Error('[Web3Context] Unsupported network!') + return Promise.reject(new UnknownNetworkError('Unknown network!')) } const contractAddress = WROSE_CONTRACT_BY_NETWORK[network.chainId] @@ -117,6 +118,53 @@ export const Web3ContextProvider: FC = ({ children }) => { await _init(account) } + const _addNetwork = async (chainId: number) => { + if (chainId === 0x5afe) { + + // Default to Sapphire Mainnet + await window.ethereum.request?.({ + method: 'wallet_addEthereumChain', + params: [ + { + chainId: '0x5afe', + chainName: 'Sapphire Mainnet', + 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 (toNetworkChainId = 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 === toNetworkChainId) return + try { + await window.ethereum.request?.({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: utils.hexlify(toNetworkChainId) }], + }) + } catch (e) { + // Chain is not available in the Metamask + if (e?.code !== 4902) { + throw e + } else { + _addNetwork(toNetworkChainId) + } + } + } + const wrap = async (amount) => { if (!amount) { throw new Error('[amount] is required!') @@ -164,11 +212,12 @@ export const Web3ContextProvider: FC = ({ children }) => { const providerState: Web3ProviderContext = { state, connectWallet, + switchNetwork, wrap, unwrap, getBalance, getBalanceOfWROSE, - getTransaction + getTransaction, } return {children} diff --git a/src/providers/WrapFormProvider.tsx b/src/providers/WrapFormProvider.tsx index b1d9a32..e0ab24e 100644 --- a/src/providers/WrapFormProvider.tsx +++ b/src/providers/WrapFormProvider.tsx @@ -80,6 +80,7 @@ export const WrapFormContextProvider: FC = ({ children }) => amount: amountBN, })) } catch (ex) { + // Ignore if invalid number console.error(ex) } } @@ -94,16 +95,24 @@ export const WrapFormContextProvider: FC = ({ children }) => const submit = async (amount: BigNumber) => { _setIsLoading(true) - const { formType, wRoseBalance, balance } = state + const { formType } = state let receipt: TransactionResponse | null = null if (formType === WrapFormType.WRAP) { - - receipt = await wrap(amount.toString()) + try { + receipt = await wrap(amount.toString()) + } catch (ex) { + _setIsLoading(false) + throw ex + } } else if (formType === WrapFormType.UNWRAP) { - - receipt = await unwrap(amount.toString()) + try { + receipt = await unwrap(amount.toString()) + } catch (ex) { + _setIsLoading(false) + throw ex + } } else { _setIsLoading(false) return Promise.reject(new Error('[formType] Invalid form type')) diff --git a/src/utils/errors.ts b/src/utils/errors.ts new file mode 100644 index 0000000..8990603 --- /dev/null +++ b/src/utils/errors.ts @@ -0,0 +1,5 @@ +export class UnknownNetworkError extends Error { + constructor(message: string) { + super(message); + } +}