bridge_ui: QoL improvements
Change-Id: I4221ff5d7757e099b8dad55de6ca2765137e5e38
This commit is contained in:
parent
03a373676b
commit
26b6ee22bb
|
@ -63,7 +63,7 @@ export default function ButtonWithLoader({
|
|||
) : null}
|
||||
</div>
|
||||
{error ? (
|
||||
<Typography color="error" className={classes.error}>
|
||||
<Typography variant="body2" color="error" className={classes.error}>
|
||||
{error}
|
||||
</Typography>
|
||||
) : null}
|
||||
|
|
|
@ -5,7 +5,6 @@ import {
|
|||
CHAIN_ID_SOLANA,
|
||||
getEmitterAddressEth,
|
||||
getEmitterAddressSolana,
|
||||
getSignedVAA,
|
||||
parseSequenceFromLogEth,
|
||||
parseSequenceFromLogSolana,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
|
@ -31,11 +30,11 @@ import { BigNumber, ethers } from "ethers";
|
|||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useEthereumProvider } from "../../contexts/EthereumProviderContext";
|
||||
import { setSignedVAAHex, setStep, setTargetChain } from "../../store/nftSlice";
|
||||
import {
|
||||
selectNFTSignedVAAHex,
|
||||
selectNFTSourceChain,
|
||||
} from "../../store/selectors";
|
||||
import { setSignedVAAHex, setStep, setTargetChain } from "../../store/nftSlice";
|
||||
import {
|
||||
hexToNativeString,
|
||||
hexToUint8Array,
|
||||
|
@ -49,9 +48,9 @@ import {
|
|||
SOL_NFT_BRIDGE_ADDRESS,
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
} from "../../utils/consts";
|
||||
import KeyAndBalance from "../KeyAndBalance";
|
||||
import { getSignedVAAWithRetry } from "../../utils/getSignedVAAWithRetry";
|
||||
import { METADATA_REPLACE } from "../../utils/metaplex";
|
||||
import { getNextRpcHost } from "../../utils/getSignedVAAWithRetry";
|
||||
import KeyAndBalance from "../KeyAndBalance";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
fab: {
|
||||
|
@ -66,11 +65,11 @@ async function eth(provider: ethers.providers.Web3Provider, tx: string) {
|
|||
const receipt = await provider.getTransactionReceipt(tx);
|
||||
const sequence = parseSequenceFromLogEth(receipt, ETH_BRIDGE_ADDRESS);
|
||||
const emitterAddress = getEmitterAddressEth(ETH_NFT_BRIDGE_ADDRESS);
|
||||
const { vaaBytes } = await getSignedVAA(
|
||||
WORMHOLE_RPC_HOSTS[getNextRpcHost()],
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_ETH,
|
||||
emitterAddress,
|
||||
sequence.toString()
|
||||
sequence.toString(),
|
||||
WORMHOLE_RPC_HOSTS.length
|
||||
);
|
||||
return uint8ArrayToHex(vaaBytes);
|
||||
} catch (e) {
|
||||
|
@ -90,11 +89,11 @@ async function solana(tx: string) {
|
|||
const emitterAddress = await getEmitterAddressSolana(
|
||||
SOL_NFT_BRIDGE_ADDRESS
|
||||
);
|
||||
const { vaaBytes } = await getSignedVAA(
|
||||
WORMHOLE_RPC_HOSTS[getNextRpcHost()],
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_SOLANA,
|
||||
emitterAddress,
|
||||
sequence.toString()
|
||||
sequence.toString(),
|
||||
WORMHOLE_RPC_HOSTS.length
|
||||
);
|
||||
return uint8ArrayToHex(vaaBytes);
|
||||
} catch (e) {
|
||||
|
@ -199,10 +198,10 @@ function RecoveryDialogContent({
|
|||
setRecoverySourceChain(event.target.value);
|
||||
}, []);
|
||||
const handleSourceTxChange = useCallback((event) => {
|
||||
setRecoverySourceTx(event.target.value);
|
||||
setRecoverySourceTx(event.target.value.trim());
|
||||
}, []);
|
||||
const handleSignedVAAChange = useCallback((event) => {
|
||||
setRecoverySignedVAA(event.target.value);
|
||||
setRecoverySignedVAA(event.target.value.trim());
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
|
|
@ -40,7 +40,7 @@ function Send() {
|
|||
Transfer the NFT to the Wormhole Token Bridge.
|
||||
</StepDescription>
|
||||
<KeyAndBalance chainId={sourceChain} />
|
||||
<Alert severity="warning">
|
||||
<Alert severity="info">
|
||||
This will initiate the transfer on {CHAINS_BY_ID[sourceChain].name} and
|
||||
wait for finalization. If you navigate away from this page before
|
||||
completing Step 4, you will have to perform the recovery workflow to
|
||||
|
|
|
@ -25,7 +25,10 @@ export default function ShowTx({
|
|||
tx: Transaction;
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
const showExplorerLink = CLUSTER === "testnet" || CLUSTER === "mainnet";
|
||||
const showExplorerLink =
|
||||
CLUSTER === "testnet" ||
|
||||
CLUSTER === "mainnet" ||
|
||||
(CLUSTER === "devnet" && chainId === CHAIN_ID_SOLANA);
|
||||
const explorerAddress =
|
||||
chainId === CHAIN_ID_ETH
|
||||
? `https://${CLUSTER === "testnet" ? "goerli." : ""}etherscan.io/tx/${
|
||||
|
@ -33,7 +36,11 @@ export default function ShowTx({
|
|||
}`
|
||||
: chainId === CHAIN_ID_SOLANA
|
||||
? `https://explorer.solana.com/tx/${tx?.id}${
|
||||
CLUSTER === "testnet" ? "?cluster=testnet" : ""
|
||||
CLUSTER === "testnet"
|
||||
? "?cluster=testnet"
|
||||
: CLUSTER === "devnet"
|
||||
? "?cluster=custom&customUrl=http%3A%2F%2Flocalhost%3A8899"
|
||||
: ""
|
||||
}`
|
||||
: undefined;
|
||||
const explorerName = chainId === CHAIN_ID_ETH ? "Etherscan" : "Explorer";
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import { CHAIN_ID_ETH } from "@certusone/wormhole-sdk";
|
||||
import { Button, makeStyles } from "@material-ui/core";
|
||||
import detectEthereumProvider from "@metamask/detect-provider";
|
||||
import { useCallback } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useEthereumProvider } from "../../contexts/EthereumProviderContext";
|
||||
import {
|
||||
selectTransferTargetAsset,
|
||||
selectTransferTargetChain,
|
||||
} from "../../store/selectors";
|
||||
import {
|
||||
ethTokenToParsedTokenAccount,
|
||||
getEthereumToken,
|
||||
} from "../../utils/ethereum";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
addButton: {
|
||||
display: "block",
|
||||
margin: `${theme.spacing(1)}px auto 0px`,
|
||||
},
|
||||
}));
|
||||
|
||||
export default function AddToMetamask() {
|
||||
const classes = useStyles();
|
||||
const targetChain = useSelector(selectTransferTargetChain);
|
||||
const targetAsset = useSelector(selectTransferTargetAsset);
|
||||
const { provider, signerAddress } = useEthereumProvider();
|
||||
const handleClick = useCallback(() => {
|
||||
if (provider && targetAsset && signerAddress) {
|
||||
(async () => {
|
||||
try {
|
||||
const token = await getEthereumToken(targetAsset, provider);
|
||||
const { symbol, decimals } = await ethTokenToParsedTokenAccount(
|
||||
token,
|
||||
signerAddress
|
||||
);
|
||||
const ethereum = (await detectEthereumProvider()) as any;
|
||||
ethereum.request({
|
||||
method: "wallet_watchAsset",
|
||||
params: {
|
||||
type: "ERC20", // In the future, other standards will be supported
|
||||
options: {
|
||||
address: targetAsset, // The address of the token contract
|
||||
symbol, // A ticker symbol or shorthand, up to 5 characters
|
||||
decimals, // The number of token decimals
|
||||
// image: string; // A string url of the token logo
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
})();
|
||||
}
|
||||
}, [provider, targetAsset, signerAddress]);
|
||||
return provider &&
|
||||
signerAddress &&
|
||||
targetAsset &&
|
||||
targetChain === CHAIN_ID_ETH ? (
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
className={classes.addButton}
|
||||
>
|
||||
Add to Metamask
|
||||
</Button>
|
||||
) : null;
|
||||
}
|
|
@ -7,7 +7,6 @@ import {
|
|||
getEmitterAddressEth,
|
||||
getEmitterAddressSolana,
|
||||
getEmitterAddressTerra,
|
||||
getSignedVAA,
|
||||
parseSequenceFromLogEth,
|
||||
parseSequenceFromLogSolana,
|
||||
parseSequenceFromLogTerra,
|
||||
|
@ -15,6 +14,7 @@ import {
|
|||
import {
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
|
@ -32,6 +32,7 @@ import { Alert } from "@material-ui/lab";
|
|||
import { Connection } from "@solana/web3.js";
|
||||
import { LCDClient } from "@terra-money/terra.js";
|
||||
import { BigNumber, ethers } from "ethers";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useEthereumProvider } from "../../contexts/EthereumProviderContext";
|
||||
|
@ -59,7 +60,8 @@ import {
|
|||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
} from "../../utils/consts";
|
||||
import { getNextRpcHost } from "../../utils/getSignedVAAWithRetry";
|
||||
import { getSignedVAAWithRetry } from "../../utils/getSignedVAAWithRetry";
|
||||
import parseError from "../../utils/parseError";
|
||||
import KeyAndBalance from "../KeyAndBalance";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
@ -70,25 +72,30 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
async function eth(provider: ethers.providers.Web3Provider, tx: string) {
|
||||
async function eth(
|
||||
provider: ethers.providers.Web3Provider,
|
||||
tx: string,
|
||||
enqueueSnackbar: any
|
||||
) {
|
||||
try {
|
||||
const receipt = await provider.getTransactionReceipt(tx);
|
||||
const sequence = parseSequenceFromLogEth(receipt, ETH_BRIDGE_ADDRESS);
|
||||
const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
||||
const { vaaBytes } = await getSignedVAA(
|
||||
WORMHOLE_RPC_HOSTS[getNextRpcHost()],
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_ETH,
|
||||
emitterAddress,
|
||||
sequence.toString()
|
||||
sequence.toString(),
|
||||
WORMHOLE_RPC_HOSTS.length
|
||||
);
|
||||
return uint8ArrayToHex(vaaBytes);
|
||||
return { vaa: uint8ArrayToHex(vaaBytes), error: null };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
return { vaa: null, error: parseError(e) };
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
async function solana(tx: string) {
|
||||
async function solana(tx: string, enqueueSnackbar: any) {
|
||||
try {
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const info = await connection.getTransaction(tx);
|
||||
|
@ -99,20 +106,21 @@ async function solana(tx: string) {
|
|||
const emitterAddress = await getEmitterAddressSolana(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS
|
||||
);
|
||||
const { vaaBytes } = await getSignedVAA(
|
||||
WORMHOLE_RPC_HOSTS[getNextRpcHost()],
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_SOLANA,
|
||||
emitterAddress,
|
||||
sequence.toString()
|
||||
sequence.toString(),
|
||||
WORMHOLE_RPC_HOSTS.length
|
||||
);
|
||||
return uint8ArrayToHex(vaaBytes);
|
||||
return { vaa: uint8ArrayToHex(vaaBytes), error: null };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
return { vaa: null, error: parseError(e) };
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
async function terra(tx: string) {
|
||||
async function terra(tx: string, enqueueSnackbar: any) {
|
||||
try {
|
||||
const lcd = new LCDClient(TERRA_HOST);
|
||||
const info = await lcd.tx.txInfo(tx);
|
||||
|
@ -123,17 +131,18 @@ async function terra(tx: string) {
|
|||
const emitterAddress = await getEmitterAddressTerra(
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS
|
||||
);
|
||||
const { vaaBytes } = await getSignedVAA(
|
||||
WORMHOLE_RPC_HOSTS[getNextRpcHost()],
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_TERRA,
|
||||
emitterAddress,
|
||||
sequence
|
||||
sequence,
|
||||
WORMHOLE_RPC_HOSTS.length
|
||||
);
|
||||
return uint8ArrayToHex(vaaBytes);
|
||||
return { vaa: uint8ArrayToHex(vaaBytes), error: null };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
return { vaa: null, error: parseError(e) };
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// 0 u256 amount
|
||||
|
@ -159,12 +168,16 @@ function RecoveryDialogContent({
|
|||
onClose: () => void;
|
||||
disabled: boolean;
|
||||
}) {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const dispatch = useDispatch();
|
||||
const { provider } = useEthereumProvider();
|
||||
const currentSourceChain = useSelector(selectTransferSourceChain);
|
||||
const [recoverySourceChain, setRecoverySourceChain] =
|
||||
useState(currentSourceChain);
|
||||
const [recoverySourceTx, setRecoverySourceTx] = useState("");
|
||||
const [recoverySourceTxIsLoading, setRecoverySourceTxIsLoading] =
|
||||
useState(false);
|
||||
const [recoverySourceTxError, setRecoverySourceTxError] = useState("");
|
||||
const currentSignedVAA = useSelector(selectTransferSignedVAAHex);
|
||||
const [recoverySignedVAA, setRecoverySignedVAA] = useState(currentSignedVAA);
|
||||
const [recoveryParsedVAA, setRecoveryParsedVAA] = useState<any>(null);
|
||||
|
@ -178,32 +191,63 @@ function RecoveryDialogContent({
|
|||
if (recoverySourceTx) {
|
||||
let cancelled = false;
|
||||
if (recoverySourceChain === CHAIN_ID_ETH && provider) {
|
||||
setRecoverySourceTxError("");
|
||||
setRecoverySourceTxIsLoading(true);
|
||||
(async () => {
|
||||
const vaa = await eth(provider, recoverySourceTx);
|
||||
const { vaa, error } = await eth(
|
||||
provider,
|
||||
recoverySourceTx,
|
||||
enqueueSnackbar
|
||||
);
|
||||
if (!cancelled) {
|
||||
setRecoverySourceTxIsLoading(false);
|
||||
if (vaa) {
|
||||
setRecoverySignedVAA(vaa);
|
||||
}
|
||||
if (error) {
|
||||
setRecoverySourceTxError(error);
|
||||
}
|
||||
}
|
||||
})();
|
||||
} else if (recoverySourceChain === CHAIN_ID_SOLANA) {
|
||||
setRecoverySourceTxError("");
|
||||
setRecoverySourceTxIsLoading(true);
|
||||
(async () => {
|
||||
const vaa = await solana(recoverySourceTx);
|
||||
const { vaa, error } = await solana(
|
||||
recoverySourceTx,
|
||||
enqueueSnackbar
|
||||
);
|
||||
if (!cancelled) {
|
||||
setRecoverySourceTxIsLoading(false);
|
||||
if (vaa) {
|
||||
setRecoverySignedVAA(vaa);
|
||||
}
|
||||
if (error) {
|
||||
setRecoverySourceTxError(error);
|
||||
}
|
||||
}
|
||||
})();
|
||||
} else if (recoverySourceChain === CHAIN_ID_TERRA) {
|
||||
setRecoverySourceTxError("");
|
||||
setRecoverySourceTxIsLoading(true);
|
||||
(async () => {
|
||||
const vaa = await terra(recoverySourceTx);
|
||||
const { vaa, error } = await terra(recoverySourceTx, enqueueSnackbar);
|
||||
if (!cancelled) {
|
||||
setRecoverySourceTxIsLoading(false);
|
||||
if (vaa) {
|
||||
setRecoverySignedVAA(vaa);
|
||||
}
|
||||
if (error) {
|
||||
setRecoverySourceTxError(error);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}
|
||||
}, [recoverySourceChain, recoverySourceTx, provider]);
|
||||
}, [recoverySourceChain, recoverySourceTx, provider, enqueueSnackbar]);
|
||||
useEffect(() => {
|
||||
setRecoverySignedVAA(currentSignedVAA);
|
||||
}, [currentSignedVAA]);
|
||||
|
@ -212,10 +256,10 @@ function RecoveryDialogContent({
|
|||
setRecoverySourceChain(event.target.value);
|
||||
}, []);
|
||||
const handleSourceTxChange = useCallback((event) => {
|
||||
setRecoverySourceTx(event.target.value);
|
||||
setRecoverySourceTx(event.target.value.trim());
|
||||
}, []);
|
||||
const handleSignedVAAChange = useCallback((event) => {
|
||||
setRecoverySignedVAA(event.target.value);
|
||||
setRecoverySignedVAA(event.target.value.trim());
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
@ -292,23 +336,45 @@ function RecoveryDialogContent({
|
|||
<KeyAndBalance chainId={recoverySourceChain} />
|
||||
) : null}
|
||||
<TextField
|
||||
label="Source Tx"
|
||||
disabled={!!recoverySignedVAA}
|
||||
label="Source Tx (paste here)"
|
||||
disabled={!!recoverySignedVAA || recoverySourceTxIsLoading}
|
||||
value={recoverySourceTx}
|
||||
onChange={handleSourceTxChange}
|
||||
error={!!recoverySourceTxError}
|
||||
helperText={recoverySourceTxError}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<Box position="relative">
|
||||
<Box mt={4}>
|
||||
<Typography>or</Typography>
|
||||
</Box>
|
||||
<TextField
|
||||
label="Signed VAA (Hex)"
|
||||
disabled={recoverySourceTxIsLoading}
|
||||
value={recoverySignedVAA || ""}
|
||||
onChange={handleSignedVAAChange}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
{recoverySourceTxIsLoading ? (
|
||||
<Box
|
||||
position="absolute"
|
||||
style={{
|
||||
top: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: "rgba(0,0,0,0.5)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : null}
|
||||
</Box>
|
||||
<Box my={4}>
|
||||
<Divider />
|
||||
</Box>
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
import { reset } from "../../store/transferSlice";
|
||||
import ButtonWithLoader from "../ButtonWithLoader";
|
||||
import ShowTx from "../ShowTx";
|
||||
import AddToMetamask from "./AddToMetamask";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
description: {
|
||||
|
@ -37,6 +38,7 @@ export default function RedeemPreview() {
|
|||
{explainerString}
|
||||
</Typography>
|
||||
{redeemTx ? <ShowTx chainId={targetChain} tx={redeemTx} /> : null}
|
||||
<AddToMetamask />
|
||||
<ButtonWithLoader onClick={handleResetClick}>
|
||||
Transfer More Tokens!
|
||||
</ButtonWithLoader>
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
import { CHAINS_BY_ID } from "../../utils/consts";
|
||||
import ButtonWithLoader from "../ButtonWithLoader";
|
||||
import KeyAndBalance from "../KeyAndBalance";
|
||||
import ShowTx from "../ShowTx";
|
||||
import StepDescription from "../StepDescription";
|
||||
import TransactionProgress from "../TransactionProgress";
|
||||
import WaitingForWalletMessage from "./WaitingForWalletMessage";
|
||||
|
@ -111,7 +112,7 @@ function Send() {
|
|||
Transfer the tokens to the Wormhole Token Bridge.
|
||||
</StepDescription>
|
||||
<KeyAndBalance chainId={sourceChain} />
|
||||
<Alert severity="warning">
|
||||
<Alert severity="info">
|
||||
This will initiate the transfer on {CHAINS_BY_ID[sourceChain].name} and
|
||||
wait for finalization. If you navigate away from this page before
|
||||
completing Step 4, you will have to perform the recovery workflow to
|
||||
|
@ -153,6 +154,7 @@ function Send() {
|
|||
</ButtonWithLoader>
|
||||
)}
|
||||
<WaitingForWalletMessage />
|
||||
{transferTx ? <ShowTx chainId={sourceChain} tx={transferTx} /> : null}
|
||||
<TransactionProgress
|
||||
chainId={sourceChain}
|
||||
tx={transferTx}
|
||||
|
|
|
@ -5,7 +5,6 @@ import { useCallback } from "react";
|
|||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useHistory } from "react-router";
|
||||
import useIsWalletReady from "../../hooks/useIsWalletReady";
|
||||
import useTokenBlacklistWarning from "../../hooks/useTokenBlacklistWarning";
|
||||
import {
|
||||
selectTransferAmount,
|
||||
selectTransferIsSourceComplete,
|
||||
|
@ -25,6 +24,7 @@ import ButtonWithLoader from "../ButtonWithLoader";
|
|||
import KeyAndBalance from "../KeyAndBalance";
|
||||
import StepDescription from "../StepDescription";
|
||||
import { TokenSelector } from "../TokenSelectors/SourceTokenSelector";
|
||||
import TokenBlacklistWarning from "./TokenBlacklistWarning";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
transferField: {
|
||||
|
@ -55,10 +55,6 @@ function Source({
|
|||
const isSourceComplete = useSelector(selectTransferIsSourceComplete);
|
||||
const shouldLockFields = useSelector(selectTransferShouldLockFields);
|
||||
const { isReady, statusMessage } = useIsWalletReady(sourceChain);
|
||||
const tokenBlacklistWarning = useTokenBlacklistWarning(
|
||||
sourceChain,
|
||||
parsedTokenAccount?.mintKey
|
||||
);
|
||||
const handleMigrationClick = useCallback(() => {
|
||||
parsedTokenAccount?.mintKey &&
|
||||
history.push("/migrate/" + parsedTokenAccount.mintKey);
|
||||
|
@ -124,6 +120,11 @@ function Source({
|
|||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<TokenBlacklistWarning
|
||||
sourceChain={sourceChain}
|
||||
tokenAddress={parsedTokenAccount?.mintKey}
|
||||
symbol={parsedTokenAccount?.symbol}
|
||||
/>
|
||||
{hasParsedTokenAccount ? (
|
||||
<TextField
|
||||
label="Amount"
|
||||
|
@ -139,7 +140,7 @@ function Source({
|
|||
disabled={!isSourceComplete}
|
||||
onClick={handleNextClick}
|
||||
showLoader={false}
|
||||
error={statusMessage || error || tokenBlacklistWarning}
|
||||
error={statusMessage || error}
|
||||
>
|
||||
Next
|
||||
</ButtonWithLoader>
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
} from "../../store/selectors";
|
||||
import { CHAINS_BY_ID } from "../../utils/consts";
|
||||
import { shortenAddress } from "../../utils/solana";
|
||||
import TokenBlacklistWarning from "./TokenBlacklistWarning";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
description: {
|
||||
|
@ -35,6 +36,7 @@ export default function SourcePreview() {
|
|||
: "Step complete.";
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography
|
||||
component="div"
|
||||
variant="subtitle2"
|
||||
|
@ -42,5 +44,11 @@ export default function SourcePreview() {
|
|||
>
|
||||
{explainerString}
|
||||
</Typography>
|
||||
<TokenBlacklistWarning
|
||||
sourceChain={sourceChain}
|
||||
tokenAddress={sourceParsedTokenAccount?.mintKey}
|
||||
symbol={sourceParsedTokenAccount?.symbol}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { ChainId } from "@certusone/wormhole-sdk";
|
||||
import { Alert } from "@material-ui/lab";
|
||||
import useTokenBlacklistWarning from "../../hooks/useTokenBlacklistWarning";
|
||||
|
||||
export default function TokenBlacklistWarning({
|
||||
sourceChain,
|
||||
tokenAddress,
|
||||
symbol,
|
||||
}: {
|
||||
sourceChain: ChainId;
|
||||
tokenAddress: string | undefined;
|
||||
symbol: string | undefined;
|
||||
}) {
|
||||
const tokenBlacklistWarning = useTokenBlacklistWarning(
|
||||
sourceChain,
|
||||
tokenAddress,
|
||||
symbol
|
||||
);
|
||||
return tokenBlacklistWarning ? (
|
||||
<Alert severity="warning">{tokenBlacklistWarning}</Alert>
|
||||
) : null;
|
||||
}
|
|
@ -21,7 +21,7 @@ import {
|
|||
import { useSnackbar } from "notistack";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Signer } from "../../../sdk/js/node_modules/ethers/lib";
|
||||
import { Signer } from "ethers";
|
||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
|
||||
import {
|
||||
|
|
|
@ -11,7 +11,8 @@ import {
|
|||
|
||||
export default function useTokenBlacklistWarning(
|
||||
chainId: ChainId,
|
||||
tokenAddress: string | undefined
|
||||
tokenAddress: string | undefined,
|
||||
symbol: string | undefined
|
||||
) {
|
||||
return useMemo(
|
||||
() =>
|
||||
|
@ -20,8 +21,12 @@ export default function useTokenBlacklistWarning(
|
|||
SOLANA_TOKENS_THAT_EXIST_ELSEWHERE.includes(tokenAddress)) ||
|
||||
(chainId === CHAIN_ID_ETH &&
|
||||
ETH_TOKENS_THAT_EXIST_ELSEWHERE.includes(tokenAddress)))
|
||||
? "This token exists on multiple chains! Bridging the token via Wormhole will produce a wrapped version which might have no liquidity on the target chain."
|
||||
? `Bridging ${
|
||||
symbol ? symbol : "the token"
|
||||
} via Wormhole will not produce native ${
|
||||
symbol ? symbol : "assets"
|
||||
}. It will produce a wrapped version which might have no liquidity or utility on the target chain.`
|
||||
: undefined,
|
||||
[chainId, tokenAddress]
|
||||
[chainId, tokenAddress, symbol]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -197,6 +197,7 @@ export const SOLANA_TOKENS_THAT_EXIST_ELSEWHERE = [
|
|||
"ArUkYE2XDKzqy77PRRGjo4wREWwqk6RXTfM9NeqzPvjU", // renDOGE
|
||||
"E99CQ2gFMmbiyK2bwiaFNWUUmwz4r8k2CVEFxwuvQ7ue", // renZEC
|
||||
"De2bU64vsXKU9jq4bCjeDxNRGPn8nr3euaTK8jBYmD3J", // renFIL
|
||||
"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", // USDT
|
||||
];
|
||||
export const ETH_TOKENS_THAT_EXIST_ELSEWHERE = [
|
||||
getAddress("0x476c5E26a75bd202a9683ffD34359C0CC15be0fF"), // SRM
|
||||
|
@ -209,6 +210,7 @@ export const ETH_TOKENS_THAT_EXIST_ELSEWHERE = [
|
|||
getAddress("0x3832d2F059E55934220881F831bE501D180671A7"), // renDOGE
|
||||
getAddress("0x1c5db575e2ff833e46a2e9864c22f4b22e0b37c2"), // renZEC
|
||||
getAddress("0xD5147bc8e386d91Cc5DBE72099DAC6C9b99276F5"), // renFIL
|
||||
getAddress("0xdac17f958d2ee523a2206206994597c13d831ec7"), // USDT
|
||||
];
|
||||
|
||||
export const MIGRATION_PROGRAM_ADDRESS =
|
||||
|
|
|
@ -9,10 +9,13 @@ export const getNextRpcHost = () =>
|
|||
export async function getSignedVAAWithRetry(
|
||||
emitterChain: ChainId,
|
||||
emitterAddress: string,
|
||||
sequence: string
|
||||
sequence: string,
|
||||
retryAttempts?: number
|
||||
) {
|
||||
let result;
|
||||
let attempts = 0;
|
||||
while (!result) {
|
||||
attempts++;
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
try {
|
||||
result = await getSignedVAA(
|
||||
|
@ -22,7 +25,10 @@ export async function getSignedVAAWithRetry(
|
|||
sequence
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.log(`Attempt ${attempts}: `, e);
|
||||
if (retryAttempts !== undefined && attempts > retryAttempts) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
Loading…
Reference in New Issue