bridge_ui: improve wallet checks
https: //github.com/certusone/wormhole/issues/360 Change-Id: I7ce3696aa0e038faea0da504aa9d8f4c69d7c6a6
This commit is contained in:
parent
e7a1dd600b
commit
e11e59095f
|
@ -1,16 +1,26 @@
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
import { useHandleCreateWrapped } from "../../hooks/useHandleCreateWrapped";
|
import { useHandleCreateWrapped } from "../../hooks/useHandleCreateWrapped";
|
||||||
|
import useIsWalletReady from "../../hooks/useIsWalletReady";
|
||||||
|
import { selectAttestTargetChain } from "../../store/selectors";
|
||||||
import ButtonWithLoader from "../ButtonWithLoader";
|
import ButtonWithLoader from "../ButtonWithLoader";
|
||||||
|
import KeyAndBalance from "../KeyAndBalance";
|
||||||
|
|
||||||
function Create() {
|
function Create() {
|
||||||
const { handleClick, disabled, showLoader } = useHandleCreateWrapped();
|
const { handleClick, disabled, showLoader } = useHandleCreateWrapped();
|
||||||
|
const targetChain = useSelector(selectAttestTargetChain);
|
||||||
|
const { isReady, statusMessage } = useIsWalletReady(targetChain);
|
||||||
return (
|
return (
|
||||||
<ButtonWithLoader
|
<>
|
||||||
disabled={disabled}
|
<KeyAndBalance chainId={targetChain} />
|
||||||
onClick={handleClick}
|
<ButtonWithLoader
|
||||||
showLoader={showLoader}
|
disabled={!isReady || disabled}
|
||||||
>
|
onClick={handleClick}
|
||||||
Create
|
showLoader={showLoader}
|
||||||
</ButtonWithLoader>
|
error={statusMessage}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</ButtonWithLoader>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,27 @@
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
import { useHandleAttest } from "../../hooks/useHandleAttest";
|
import { useHandleAttest } from "../../hooks/useHandleAttest";
|
||||||
|
import useIsWalletReady from "../../hooks/useIsWalletReady";
|
||||||
|
import { selectAttestSourceChain } from "../../store/selectors";
|
||||||
import ButtonWithLoader from "../ButtonWithLoader";
|
import ButtonWithLoader from "../ButtonWithLoader";
|
||||||
|
import KeyAndBalance from "../KeyAndBalance";
|
||||||
|
|
||||||
function Send() {
|
function Send() {
|
||||||
const { handleClick, disabled, showLoader } = useHandleAttest();
|
const { handleClick, disabled, showLoader } = useHandleAttest();
|
||||||
|
const sourceChain = useSelector(selectAttestSourceChain);
|
||||||
|
const { isReady, statusMessage } = useIsWalletReady(sourceChain);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonWithLoader
|
<>
|
||||||
disabled={disabled}
|
<KeyAndBalance chainId={sourceChain} />
|
||||||
onClick={handleClick}
|
<ButtonWithLoader
|
||||||
showLoader={showLoader}
|
disabled={!isReady || disabled}
|
||||||
>
|
onClick={handleClick}
|
||||||
Attest
|
showLoader={showLoader}
|
||||||
</ButtonWithLoader>
|
error={statusMessage}
|
||||||
|
>
|
||||||
|
Attest
|
||||||
|
</ButtonWithLoader>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { useHandleRedeem } from "../../hooks/useHandleRedeem";
|
import { useHandleRedeem } from "../../hooks/useHandleRedeem";
|
||||||
|
import useIsWalletReady from "../../hooks/useIsWalletReady";
|
||||||
import { selectTransferTargetChain } from "../../store/selectors";
|
import { selectTransferTargetChain } from "../../store/selectors";
|
||||||
import ButtonWithLoader from "../ButtonWithLoader";
|
import ButtonWithLoader from "../ButtonWithLoader";
|
||||||
import KeyAndBalance from "../KeyAndBalance";
|
import KeyAndBalance from "../KeyAndBalance";
|
||||||
|
@ -7,13 +8,15 @@ import KeyAndBalance from "../KeyAndBalance";
|
||||||
function Redeem() {
|
function Redeem() {
|
||||||
const { handleClick, disabled, showLoader } = useHandleRedeem();
|
const { handleClick, disabled, showLoader } = useHandleRedeem();
|
||||||
const targetChain = useSelector(selectTransferTargetChain);
|
const targetChain = useSelector(selectTransferTargetChain);
|
||||||
|
const { isReady, statusMessage } = useIsWalletReady(targetChain);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<KeyAndBalance chainId={targetChain} />
|
<KeyAndBalance chainId={targetChain} />
|
||||||
<ButtonWithLoader
|
<ButtonWithLoader
|
||||||
disabled={disabled}
|
disabled={!isReady || disabled}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
showLoader={showLoader}
|
showLoader={showLoader}
|
||||||
|
error={statusMessage}
|
||||||
>
|
>
|
||||||
Redeem
|
Redeem
|
||||||
</ButtonWithLoader>
|
</ButtonWithLoader>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { useHandleTransfer } from "../../hooks/useHandleTransfer";
|
import { useHandleTransfer } from "../../hooks/useHandleTransfer";
|
||||||
|
import useIsWalletReady from "../../hooks/useIsWalletReady";
|
||||||
import {
|
import {
|
||||||
selectTransferSourceChain,
|
selectTransferSourceChain,
|
||||||
selectTransferTargetError,
|
selectTransferTargetError,
|
||||||
|
@ -11,14 +12,15 @@ function Send() {
|
||||||
const { handleClick, disabled, showLoader } = useHandleTransfer();
|
const { handleClick, disabled, showLoader } = useHandleTransfer();
|
||||||
const sourceChain = useSelector(selectTransferSourceChain);
|
const sourceChain = useSelector(selectTransferSourceChain);
|
||||||
const error = useSelector(selectTransferTargetError);
|
const error = useSelector(selectTransferTargetError);
|
||||||
|
const { isReady, statusMessage } = useIsWalletReady(sourceChain);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<KeyAndBalance chainId={sourceChain} />
|
<KeyAndBalance chainId={sourceChain} />
|
||||||
<ButtonWithLoader
|
<ButtonWithLoader
|
||||||
disabled={disabled}
|
disabled={!isReady || disabled}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
showLoader={showLoader}
|
showLoader={showLoader}
|
||||||
error={error}
|
error={statusMessage || error}
|
||||||
>
|
>
|
||||||
Transfer
|
Transfer
|
||||||
</ButtonWithLoader>
|
</ButtonWithLoader>
|
||||||
|
|
|
@ -35,7 +35,7 @@ function Source() {
|
||||||
const error = useSelector(selectTransferSourceError);
|
const error = useSelector(selectTransferSourceError);
|
||||||
const isSourceComplete = useSelector(selectTransferIsSourceComplete);
|
const isSourceComplete = useSelector(selectTransferIsSourceComplete);
|
||||||
const shouldLockFields = useSelector(selectTransferShouldLockFields);
|
const shouldLockFields = useSelector(selectTransferShouldLockFields);
|
||||||
const isWalletReady = useIsWalletReady(sourceChain);
|
const { isReady, statusMessage } = useIsWalletReady(sourceChain);
|
||||||
const handleSourceChange = useCallback(
|
const handleSourceChange = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
dispatch(setSourceChain(event.target.value));
|
dispatch(setSourceChain(event.target.value));
|
||||||
|
@ -67,7 +67,7 @@ function Source() {
|
||||||
))}
|
))}
|
||||||
</TextField>
|
</TextField>
|
||||||
<KeyAndBalance chainId={sourceChain} balance={uiAmountString} />
|
<KeyAndBalance chainId={sourceChain} balance={uiAmountString} />
|
||||||
{isWalletReady || uiAmountString ? (
|
{isReady || uiAmountString ? (
|
||||||
<div className={classes.transferField}>
|
<div className={classes.transferField}>
|
||||||
<TokenSelector disabled={shouldLockFields} />
|
<TokenSelector disabled={shouldLockFields} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -86,7 +86,7 @@ function Source() {
|
||||||
disabled={!isSourceComplete}
|
disabled={!isSourceComplete}
|
||||||
onClick={handleNextClick}
|
onClick={handleNextClick}
|
||||||
showLoader={false}
|
showLoader={false}
|
||||||
error={error}
|
error={statusMessage || error}
|
||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
</ButtonWithLoader>
|
</ButtonWithLoader>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
|
||||||
import { makeStyles, MenuItem, TextField } from "@material-ui/core";
|
import { makeStyles, MenuItem, TextField } from "@material-ui/core";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import useIsWalletReady from "../../hooks/useIsWalletReady";
|
||||||
import useSyncTargetAddress from "../../hooks/useSyncTargetAddress";
|
import useSyncTargetAddress from "../../hooks/useSyncTargetAddress";
|
||||||
import {
|
import {
|
||||||
selectTransferIsTargetComplete,
|
selectTransferIsTargetComplete,
|
||||||
|
@ -43,6 +44,7 @@ function Target() {
|
||||||
const error = useSelector(selectTransferTargetError);
|
const error = useSelector(selectTransferTargetError);
|
||||||
const isTargetComplete = useSelector(selectTransferIsTargetComplete);
|
const isTargetComplete = useSelector(selectTransferIsTargetComplete);
|
||||||
const shouldLockFields = useSelector(selectTransferShouldLockFields);
|
const shouldLockFields = useSelector(selectTransferShouldLockFields);
|
||||||
|
const { statusMessage } = useIsWalletReady(targetChain);
|
||||||
useSyncTargetAddress(!shouldLockFields);
|
useSyncTargetAddress(!shouldLockFields);
|
||||||
const handleTargetChange = useCallback(
|
const handleTargetChange = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
|
@ -93,7 +95,7 @@ function Target() {
|
||||||
disabled={!isTargetComplete}
|
disabled={!isTargetComplete}
|
||||||
onClick={handleNextClick}
|
onClick={handleNextClick}
|
||||||
showLoader={false}
|
showLoader={false}
|
||||||
error={error}
|
error={statusMessage || error}
|
||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
</ButtonWithLoader>
|
</ButtonWithLoader>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import detectEthereumProvider from "@metamask/detect-provider";
|
import detectEthereumProvider from "@metamask/detect-provider";
|
||||||
import { ethers } from "ethers";
|
import { BigNumber, ethers } from "ethers";
|
||||||
import React, {
|
import React, {
|
||||||
ReactChildren,
|
ReactChildren,
|
||||||
useCallback,
|
useCallback,
|
||||||
|
@ -10,13 +10,12 @@ import React, {
|
||||||
|
|
||||||
type Provider = ethers.providers.Web3Provider | undefined;
|
type Provider = ethers.providers.Web3Provider | undefined;
|
||||||
type Signer = ethers.Signer | undefined;
|
type Signer = ethers.Signer | undefined;
|
||||||
type Network = ethers.providers.Network | undefined;
|
|
||||||
|
|
||||||
interface IEthereumProviderContext {
|
interface IEthereumProviderContext {
|
||||||
connect(): void;
|
connect(): void;
|
||||||
disconnect(): void;
|
disconnect(): void;
|
||||||
provider: Provider;
|
provider: Provider;
|
||||||
network: Network;
|
chainId: number | undefined;
|
||||||
signer: Signer;
|
signer: Signer;
|
||||||
signerAddress: string | undefined;
|
signerAddress: string | undefined;
|
||||||
providerError: string | null;
|
providerError: string | null;
|
||||||
|
@ -26,7 +25,7 @@ const EthereumProviderContext = React.createContext<IEthereumProviderContext>({
|
||||||
connect: () => {},
|
connect: () => {},
|
||||||
disconnect: () => {},
|
disconnect: () => {},
|
||||||
provider: undefined,
|
provider: undefined,
|
||||||
network: undefined,
|
chainId: undefined,
|
||||||
signer: undefined,
|
signer: undefined,
|
||||||
signerAddress: undefined,
|
signerAddress: undefined,
|
||||||
providerError: null,
|
providerError: null,
|
||||||
|
@ -38,7 +37,7 @@ export const EthereumProviderProvider = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [providerError, setProviderError] = useState<string | null>(null);
|
const [providerError, setProviderError] = useState<string | null>(null);
|
||||||
const [provider, setProvider] = useState<Provider>(undefined);
|
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 [signer, setSigner] = useState<Signer>(undefined);
|
||||||
const [signerAddress, setSignerAddress] = useState<string | undefined>(
|
const [signerAddress, setSignerAddress] = useState<string | undefined>(
|
||||||
undefined
|
undefined
|
||||||
|
@ -51,7 +50,7 @@ export const EthereumProviderProvider = ({
|
||||||
const provider = new ethers.providers.Web3Provider(
|
const provider = new ethers.providers.Web3Provider(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
detectedProvider,
|
detectedProvider,
|
||||||
"any" //TODO: should we only allow homestead? env perhaps?
|
"any"
|
||||||
);
|
);
|
||||||
provider
|
provider
|
||||||
.send("eth_requestAccounts", [])
|
.send("eth_requestAccounts", [])
|
||||||
|
@ -61,7 +60,7 @@ export const EthereumProviderProvider = ({
|
||||||
provider
|
provider
|
||||||
.getNetwork()
|
.getNetwork()
|
||||||
.then((network) => {
|
.then((network) => {
|
||||||
setNetwork(network);
|
setChainId(network.chainId);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setProviderError(
|
setProviderError(
|
||||||
|
@ -80,6 +79,16 @@ export const EthereumProviderProvider = ({
|
||||||
"An error occurred while getting the signer address"
|
"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(() => {
|
.catch(() => {
|
||||||
setProviderError(
|
setProviderError(
|
||||||
|
@ -97,7 +106,7 @@ export const EthereumProviderProvider = ({
|
||||||
const disconnect = useCallback(() => {
|
const disconnect = useCallback(() => {
|
||||||
setProviderError(null);
|
setProviderError(null);
|
||||||
setProvider(undefined);
|
setProvider(undefined);
|
||||||
setNetwork(undefined);
|
setChainId(undefined);
|
||||||
setSigner(undefined);
|
setSigner(undefined);
|
||||||
setSignerAddress(undefined);
|
setSignerAddress(undefined);
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -107,7 +116,7 @@ export const EthereumProviderProvider = ({
|
||||||
connect,
|
connect,
|
||||||
disconnect,
|
disconnect,
|
||||||
provider,
|
provider,
|
||||||
network,
|
chainId,
|
||||||
signer,
|
signer,
|
||||||
signerAddress,
|
signerAddress,
|
||||||
providerError,
|
providerError,
|
||||||
|
@ -116,7 +125,7 @@ export const EthereumProviderProvider = ({
|
||||||
connect,
|
connect,
|
||||||
disconnect,
|
disconnect,
|
||||||
provider,
|
provider,
|
||||||
network,
|
chainId,
|
||||||
signer,
|
signer,
|
||||||
signerAddress,
|
signerAddress,
|
||||||
providerError,
|
providerError,
|
||||||
|
|
|
@ -5,26 +5,53 @@ import {
|
||||||
CHAIN_ID_TERRA,
|
CHAIN_ID_TERRA,
|
||||||
} from "@certusone/wormhole-sdk";
|
} from "@certusone/wormhole-sdk";
|
||||||
import { useConnectedWallet } from "@terra-money/wallet-provider";
|
import { useConnectedWallet } from "@terra-money/wallet-provider";
|
||||||
|
import { useMemo } from "react";
|
||||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||||
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
|
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 solanaWallet = useSolanaWallet();
|
||||||
const solPK = solanaWallet?.publicKey;
|
const solPK = solanaWallet?.publicKey;
|
||||||
const terraWallet = useConnectedWallet();
|
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;
|
return useMemo(() => {
|
||||||
if (chainId === CHAIN_ID_TERRA && terraWallet) {
|
if (chainId === CHAIN_ID_TERRA && hasTerraWallet) {
|
||||||
output = true;
|
// TODO: terraWallet does not update on wallet changes
|
||||||
} else if (chainId === CHAIN_ID_SOLANA && solPK) {
|
return createWalletStatus(true);
|
||||||
output = true;
|
}
|
||||||
} else if (chainId === CHAIN_ID_ETH && provider && signerAddress) {
|
if (chainId === CHAIN_ID_SOLANA && solPK) {
|
||||||
output = true;
|
return createWalletStatus(true);
|
||||||
}
|
}
|
||||||
//TODO bsc
|
if (chainId === CHAIN_ID_ETH && hasEthInfo) {
|
||||||
|
if (hasCorrectEthNetwork) {
|
||||||
return output;
|
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;
|
export default useIsWalletReady;
|
||||||
|
|
|
@ -51,6 +51,12 @@ export const WORMHOLE_RPC_HOST =
|
||||||
process.env.REACT_APP_CLUSTER === "testnet"
|
process.env.REACT_APP_CLUSTER === "testnet"
|
||||||
? "https://wormhole-v2-testnet-api.certus.one"
|
? "https://wormhole-v2-testnet-api.certus.one"
|
||||||
: "http://localhost:8080";
|
: "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 =
|
export const SOLANA_HOST =
|
||||||
process.env.REACT_APP_CLUSTER === "testnet"
|
process.env.REACT_APP_CLUSTER === "testnet"
|
||||||
? clusterApiUrl("testnet")
|
? clusterApiUrl("testnet")
|
||||||
|
@ -92,7 +98,8 @@ export const TERRA_TEST_TOKEN_ADDRESS =
|
||||||
export const TERRA_BRIDGE_ADDRESS =
|
export const TERRA_BRIDGE_ADDRESS =
|
||||||
"terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5";
|
"terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5";
|
||||||
export const TERRA_TOKEN_BRIDGE_ADDRESS =
|
export const TERRA_TOKEN_BRIDGE_ADDRESS =
|
||||||
"terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4";
|
"terra174kgn5rtw4kf6f938wm7kwh70h2v4vcfd26jlc";
|
||||||
|
// "terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4";
|
||||||
|
|
||||||
export const COVALENT_API_KEY = process.env.REACT_APP_COVALENT_API_KEY
|
export const COVALENT_API_KEY = process.env.REACT_APP_COVALENT_API_KEY
|
||||||
? process.env.REACT_APP_COVALENT_API_KEY
|
? process.env.REACT_APP_COVALENT_API_KEY
|
||||||
|
|
Loading…
Reference in New Issue