bridge_ui: improve wallet checks

https: //github.com/certusone/wormhole/issues/360
Change-Id: I7ce3696aa0e038faea0da504aa9d8f4c69d7c6a6
This commit is contained in:
Evan Gray 2021-09-01 00:46:07 -04:00
parent e7a1dd600b
commit e11e59095f
9 changed files with 116 additions and 45 deletions

View File

@ -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 (
<ButtonWithLoader
disabled={disabled}
onClick={handleClick}
showLoader={showLoader}
>
Create
</ButtonWithLoader>
<>
<KeyAndBalance chainId={targetChain} />
<ButtonWithLoader
disabled={!isReady || disabled}
onClick={handleClick}
showLoader={showLoader}
error={statusMessage}
>
Create
</ButtonWithLoader>
</>
);
}

View File

@ -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 (
<ButtonWithLoader
disabled={disabled}
onClick={handleClick}
showLoader={showLoader}
>
Attest
</ButtonWithLoader>
<>
<KeyAndBalance chainId={sourceChain} />
<ButtonWithLoader
disabled={!isReady || disabled}
onClick={handleClick}
showLoader={showLoader}
error={statusMessage}
>
Attest
</ButtonWithLoader>
</>
);
}

View File

@ -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 (
<>
<KeyAndBalance chainId={targetChain} />
<ButtonWithLoader
disabled={disabled}
disabled={!isReady || disabled}
onClick={handleClick}
showLoader={showLoader}
error={statusMessage}
>
Redeem
</ButtonWithLoader>

View File

@ -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 (
<>
<KeyAndBalance chainId={sourceChain} />
<ButtonWithLoader
disabled={disabled}
disabled={!isReady || disabled}
onClick={handleClick}
showLoader={showLoader}
error={error}
error={statusMessage || error}
>
Transfer
</ButtonWithLoader>

View File

@ -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() {
))}
</TextField>
<KeyAndBalance chainId={sourceChain} balance={uiAmountString} />
{isWalletReady || uiAmountString ? (
{isReady || uiAmountString ? (
<div className={classes.transferField}>
<TokenSelector disabled={shouldLockFields} />
</div>
@ -86,7 +86,7 @@ function Source() {
disabled={!isSourceComplete}
onClick={handleNextClick}
showLoader={false}
error={error}
error={statusMessage || error}
>
Next
</ButtonWithLoader>

View File

@ -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
</ButtonWithLoader>

View File

@ -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<IEthereumProviderContext>({
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<string | null>(null);
const [provider, setProvider] = useState<Provider>(undefined);
const [network, setNetwork] = useState<Network>(undefined);
const [chainId, setChainId] = useState<number | undefined>(undefined);
const [signer, setSigner] = useState<Signer>(undefined);
const [signerAddress, setSignerAddress] = useState<string | undefined>(
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,

View File

@ -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;

View File

@ -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