diff --git a/bridge_ui/src/components/Attest/Create.tsx b/bridge_ui/src/components/Attest/Create.tsx index 1e75f6ea4..70b66d4a7 100644 --- a/bridge_ui/src/components/Attest/Create.tsx +++ b/bridge_ui/src/components/Attest/Create.tsx @@ -1,16 +1,26 @@ +import { useSelector } from "react-redux"; import { useHandleCreateWrapped } from "../../hooks/useHandleCreateWrapped"; +import useIsWalletReady from "../../hooks/useIsWalletReady"; +import { selectAttestTargetChain } from "../../store/selectors"; import ButtonWithLoader from "../ButtonWithLoader"; +import KeyAndBalance from "../KeyAndBalance"; function Create() { const { handleClick, disabled, showLoader } = useHandleCreateWrapped(); + const targetChain = useSelector(selectAttestTargetChain); + const { isReady, statusMessage } = useIsWalletReady(targetChain); return ( - - Create - + <> + + + Create + + ); } diff --git a/bridge_ui/src/components/Attest/Send.tsx b/bridge_ui/src/components/Attest/Send.tsx index ff676c9c4..270285aae 100644 --- a/bridge_ui/src/components/Attest/Send.tsx +++ b/bridge_ui/src/components/Attest/Send.tsx @@ -1,16 +1,27 @@ +import { useSelector } from "react-redux"; import { useHandleAttest } from "../../hooks/useHandleAttest"; +import useIsWalletReady from "../../hooks/useIsWalletReady"; +import { selectAttestSourceChain } from "../../store/selectors"; import ButtonWithLoader from "../ButtonWithLoader"; +import KeyAndBalance from "../KeyAndBalance"; function Send() { const { handleClick, disabled, showLoader } = useHandleAttest(); + const sourceChain = useSelector(selectAttestSourceChain); + const { isReady, statusMessage } = useIsWalletReady(sourceChain); + return ( - - Attest - + <> + + + Attest + + ); } diff --git a/bridge_ui/src/components/Transfer/Redeem.tsx b/bridge_ui/src/components/Transfer/Redeem.tsx index c70929d28..fd815d8ac 100644 --- a/bridge_ui/src/components/Transfer/Redeem.tsx +++ b/bridge_ui/src/components/Transfer/Redeem.tsx @@ -1,5 +1,6 @@ import { useSelector } from "react-redux"; import { useHandleRedeem } from "../../hooks/useHandleRedeem"; +import useIsWalletReady from "../../hooks/useIsWalletReady"; import { selectTransferTargetChain } from "../../store/selectors"; import ButtonWithLoader from "../ButtonWithLoader"; import KeyAndBalance from "../KeyAndBalance"; @@ -7,13 +8,15 @@ import KeyAndBalance from "../KeyAndBalance"; function Redeem() { const { handleClick, disabled, showLoader } = useHandleRedeem(); const targetChain = useSelector(selectTransferTargetChain); + const { isReady, statusMessage } = useIsWalletReady(targetChain); return ( <> Redeem diff --git a/bridge_ui/src/components/Transfer/Send.tsx b/bridge_ui/src/components/Transfer/Send.tsx index 20dbe9ee4..50f08521b 100644 --- a/bridge_ui/src/components/Transfer/Send.tsx +++ b/bridge_ui/src/components/Transfer/Send.tsx @@ -1,5 +1,6 @@ import { useSelector } from "react-redux"; import { useHandleTransfer } from "../../hooks/useHandleTransfer"; +import useIsWalletReady from "../../hooks/useIsWalletReady"; import { selectTransferSourceChain, selectTransferTargetError, @@ -11,14 +12,15 @@ function Send() { const { handleClick, disabled, showLoader } = useHandleTransfer(); const sourceChain = useSelector(selectTransferSourceChain); const error = useSelector(selectTransferTargetError); + const { isReady, statusMessage } = useIsWalletReady(sourceChain); return ( <> Transfer diff --git a/bridge_ui/src/components/Transfer/Source.tsx b/bridge_ui/src/components/Transfer/Source.tsx index 9006685c1..c36075dc2 100644 --- a/bridge_ui/src/components/Transfer/Source.tsx +++ b/bridge_ui/src/components/Transfer/Source.tsx @@ -35,7 +35,7 @@ function Source() { const error = useSelector(selectTransferSourceError); const isSourceComplete = useSelector(selectTransferIsSourceComplete); const shouldLockFields = useSelector(selectTransferShouldLockFields); - const isWalletReady = useIsWalletReady(sourceChain); + const { isReady, statusMessage } = useIsWalletReady(sourceChain); const handleSourceChange = useCallback( (event) => { dispatch(setSourceChain(event.target.value)); @@ -67,7 +67,7 @@ function Source() { ))} - {isWalletReady || uiAmountString ? ( + {isReady || uiAmountString ? (
@@ -86,7 +86,7 @@ function Source() { disabled={!isSourceComplete} onClick={handleNextClick} showLoader={false} - error={error} + error={statusMessage || error} > Next diff --git a/bridge_ui/src/components/Transfer/Target.tsx b/bridge_ui/src/components/Transfer/Target.tsx index 3bc75b729..176445a91 100644 --- a/bridge_ui/src/components/Transfer/Target.tsx +++ b/bridge_ui/src/components/Transfer/Target.tsx @@ -2,6 +2,7 @@ import { CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk"; import { makeStyles, MenuItem, TextField } from "@material-ui/core"; import { useCallback, useMemo } from "react"; import { useDispatch, useSelector } from "react-redux"; +import useIsWalletReady from "../../hooks/useIsWalletReady"; import useSyncTargetAddress from "../../hooks/useSyncTargetAddress"; import { selectTransferIsTargetComplete, @@ -43,6 +44,7 @@ function Target() { const error = useSelector(selectTransferTargetError); const isTargetComplete = useSelector(selectTransferIsTargetComplete); const shouldLockFields = useSelector(selectTransferShouldLockFields); + const { statusMessage } = useIsWalletReady(targetChain); useSyncTargetAddress(!shouldLockFields); const handleTargetChange = useCallback( (event) => { @@ -93,7 +95,7 @@ function Target() { disabled={!isTargetComplete} onClick={handleNextClick} showLoader={false} - error={error} + error={statusMessage || error} > Next diff --git a/bridge_ui/src/contexts/EthereumProviderContext.tsx b/bridge_ui/src/contexts/EthereumProviderContext.tsx index 5904a479e..0d8fff522 100644 --- a/bridge_ui/src/contexts/EthereumProviderContext.tsx +++ b/bridge_ui/src/contexts/EthereumProviderContext.tsx @@ -1,5 +1,5 @@ import detectEthereumProvider from "@metamask/detect-provider"; -import { ethers } from "ethers"; +import { BigNumber, ethers } from "ethers"; import React, { ReactChildren, useCallback, @@ -10,13 +10,12 @@ import React, { type Provider = ethers.providers.Web3Provider | undefined; type Signer = ethers.Signer | undefined; -type Network = ethers.providers.Network | undefined; interface IEthereumProviderContext { connect(): void; disconnect(): void; provider: Provider; - network: Network; + chainId: number | undefined; signer: Signer; signerAddress: string | undefined; providerError: string | null; @@ -26,7 +25,7 @@ const EthereumProviderContext = React.createContext({ connect: () => {}, disconnect: () => {}, provider: undefined, - network: undefined, + chainId: undefined, signer: undefined, signerAddress: undefined, providerError: null, @@ -38,7 +37,7 @@ export const EthereumProviderProvider = ({ }) => { const [providerError, setProviderError] = useState(null); const [provider, setProvider] = useState(undefined); - const [network, setNetwork] = useState(undefined); + const [chainId, setChainId] = useState(undefined); const [signer, setSigner] = useState(undefined); const [signerAddress, setSignerAddress] = useState( undefined @@ -51,7 +50,7 @@ export const EthereumProviderProvider = ({ const provider = new ethers.providers.Web3Provider( // @ts-ignore detectedProvider, - "any" //TODO: should we only allow homestead? env perhaps? + "any" ); provider .send("eth_requestAccounts", []) @@ -61,7 +60,7 @@ export const EthereumProviderProvider = ({ provider .getNetwork() .then((network) => { - setNetwork(network); + setChainId(network.chainId); }) .catch(() => { setProviderError( @@ -80,6 +79,16 @@ export const EthereumProviderProvider = ({ "An error occurred while getting the signer address" ); }); + // TODO: try using ethers directly + // @ts-ignore + if (detectedProvider && detectedProvider.on) { + // @ts-ignore + detectedProvider.on("chainChanged", (chainId) => { + try { + setChainId(BigNumber.from(chainId).toNumber()); + } catch (e) {} + }); + } }) .catch(() => { setProviderError( @@ -97,7 +106,7 @@ export const EthereumProviderProvider = ({ const disconnect = useCallback(() => { setProviderError(null); setProvider(undefined); - setNetwork(undefined); + setChainId(undefined); setSigner(undefined); setSignerAddress(undefined); }, []); @@ -107,7 +116,7 @@ export const EthereumProviderProvider = ({ connect, disconnect, provider, - network, + chainId, signer, signerAddress, providerError, @@ -116,7 +125,7 @@ export const EthereumProviderProvider = ({ connect, disconnect, provider, - network, + chainId, signer, signerAddress, providerError, diff --git a/bridge_ui/src/hooks/useIsWalletReady.ts b/bridge_ui/src/hooks/useIsWalletReady.ts index 3ff78fef4..e2a28f131 100644 --- a/bridge_ui/src/hooks/useIsWalletReady.ts +++ b/bridge_ui/src/hooks/useIsWalletReady.ts @@ -5,26 +5,53 @@ import { CHAIN_ID_TERRA, } from "@certusone/wormhole-sdk"; import { useConnectedWallet } from "@terra-money/wallet-provider"; +import { useMemo } from "react"; import { useEthereumProvider } from "../contexts/EthereumProviderContext"; import { useSolanaWallet } from "../contexts/SolanaWalletContext"; +import { ETH_NETWORK_CHAIN_ID } from "../utils/consts"; -function useIsWalletReady(chainId: ChainId) { +const createWalletStatus = (isReady: boolean, statusMessage: string = "") => ({ + isReady, + statusMessage, +}); + +function useIsWalletReady(chainId: ChainId): { + isReady: boolean; + statusMessage: string; +} { const solanaWallet = useSolanaWallet(); const solPK = solanaWallet?.publicKey; const terraWallet = useConnectedWallet(); - const { provider, signerAddress } = useEthereumProvider(); + const hasTerraWallet = !!terraWallet; + const { + provider, + signerAddress, + chainId: ethChainId, + } = useEthereumProvider(); + const hasEthInfo = !!provider && !!signerAddress; + const hasCorrectEthNetwork = ethChainId === ETH_NETWORK_CHAIN_ID; - let output = false; - if (chainId === CHAIN_ID_TERRA && terraWallet) { - output = true; - } else if (chainId === CHAIN_ID_SOLANA && solPK) { - output = true; - } else if (chainId === CHAIN_ID_ETH && provider && signerAddress) { - output = true; - } - //TODO bsc - - return output; + return useMemo(() => { + if (chainId === CHAIN_ID_TERRA && hasTerraWallet) { + // TODO: terraWallet does not update on wallet changes + return createWalletStatus(true); + } + if (chainId === CHAIN_ID_SOLANA && solPK) { + return createWalletStatus(true); + } + if (chainId === CHAIN_ID_ETH && hasEthInfo) { + if (hasCorrectEthNetwork) { + return createWalletStatus(true); + } else { + return createWalletStatus( + false, + `Wallet is not connected to ${process.env.REACT_APP_CLUSTER}. Expected Chain ID: ${ETH_NETWORK_CHAIN_ID}` + ); + } + } + //TODO bsc + return createWalletStatus(false, "Wallet not connected"); + }, [chainId, hasTerraWallet, solPK, hasEthInfo, hasCorrectEthNetwork]); } export default useIsWalletReady; diff --git a/bridge_ui/src/utils/consts.ts b/bridge_ui/src/utils/consts.ts index c112169e2..4dd9cd3a5 100644 --- a/bridge_ui/src/utils/consts.ts +++ b/bridge_ui/src/utils/consts.ts @@ -51,6 +51,12 @@ export const WORMHOLE_RPC_HOST = process.env.REACT_APP_CLUSTER === "testnet" ? "https://wormhole-v2-testnet-api.certus.one" : "http://localhost:8080"; +export const ETH_NETWORK_CHAIN_ID = + process.env.REACT_APP_CLUSTER === "mainnet" + ? 1 + : process.env.REACT_APP_CLUSTER === "testnet" + ? 5 + : 1337; export const SOLANA_HOST = process.env.REACT_APP_CLUSTER === "testnet" ? clusterApiUrl("testnet") @@ -92,7 +98,8 @@ export const TERRA_TEST_TOKEN_ADDRESS = export const TERRA_BRIDGE_ADDRESS = "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5"; export const TERRA_TOKEN_BRIDGE_ADDRESS = - "terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4"; + "terra174kgn5rtw4kf6f938wm7kwh70h2v4vcfd26jlc"; +// "terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4"; export const COVALENT_API_KEY = process.env.REACT_APP_COVALENT_API_KEY ? process.env.REACT_APP_COVALENT_API_KEY