Quellcode durchsuchen

bridge_ui: QoL improvements

Change-Id: I4221ff5d7757e099b8dad55de6ca2765137e5e38
Evan Gray vor 4 Jahren
Ursprung
Commit
26b6ee22bb

+ 1 - 1
bridge_ui/src/components/ButtonWithLoader.tsx

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

+ 11 - 12
bridge_ui/src/components/NFT/Recovery.tsx

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

+ 1 - 1
bridge_ui/src/components/NFT/Send.tsx

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

+ 9 - 2
bridge_ui/src/components/ShowTx.tsx

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

+ 69 - 0
bridge_ui/src/components/Transfer/AddToMetamask.tsx

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

+ 106 - 40
bridge_ui/src/components/Transfer/Recovery.tsx

@@ -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,24 +191,55 @@ 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) {
-            setRecoverySignedVAA(vaa);
+            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) {
-            setRecoverySignedVAA(vaa);
+            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) {
-            setRecoverySignedVAA(vaa);
+            setRecoverySourceTxIsLoading(false);
+            if (vaa) {
+              setRecoverySignedVAA(vaa);
+            }
+            if (error) {
+              setRecoverySourceTxError(error);
+            }
           }
         })();
       }
@@ -203,7 +247,7 @@ function RecoveryDialogContent({
         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 mt={4}>
-          <Typography>or</Typography>
+        <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>
-        <TextField
-          label="Signed VAA (Hex)"
-          value={recoverySignedVAA || ""}
-          onChange={handleSignedVAAChange}
-          fullWidth
-          margin="normal"
-        />
         <Box my={4}>
           <Divider />
         </Box>

+ 2 - 0
bridge_ui/src/components/Transfer/RedeemPreview.tsx

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

+ 3 - 1
bridge_ui/src/components/Transfer/Send.tsx

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

+ 7 - 6
bridge_ui/src/components/Transfer/Source.tsx

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

+ 15 - 7
bridge_ui/src/components/Transfer/SourcePreview.tsx

@@ -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,12 +36,19 @@ export default function SourcePreview() {
     : "Step complete.";
 
   return (
-    <Typography
-      component="div"
-      variant="subtitle2"
-      className={classes.description}
-    >
-      {explainerString}
-    </Typography>
+    <>
+      <Typography
+        component="div"
+        variant="subtitle2"
+        className={classes.description}
+      >
+        {explainerString}
+      </Typography>
+      <TokenBlacklistWarning
+        sourceChain={sourceChain}
+        tokenAddress={sourceParsedTokenAccount?.mintKey}
+        symbol={sourceParsedTokenAccount?.symbol}
+      />
+    </>
   );
 }

+ 22 - 0
bridge_ui/src/components/Transfer/TokenBlacklistWarning.tsx

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

+ 1 - 1
bridge_ui/src/hooks/useHandleAttest.ts

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

+ 8 - 3
bridge_ui/src/hooks/useTokenBlacklistWarning.ts

@@ -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]
   );
 }

+ 5 - 3
bridge_ui/src/utils/consts.ts

@@ -197,11 +197,12 @@ 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
-  getAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"), //  USDC
-  getAddress("0x818fc6c2ec5986bc6e2cbf00939d90556ab12ce5"), //  KIN
+  getAddress("0x476c5E26a75bd202a9683ffD34359C0CC15be0fF"), // SRM
+  getAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"), // USDC
+  getAddress("0x818fc6c2ec5986bc6e2cbf00939d90556ab12ce5"), // KIN
   getAddress("0xeb4c2781e4eba804ce9a9803c67d0893436bb27d"), // renBTC
   getAddress("0x52d87F22192131636F93c5AB18d0127Ea52CB641"), // renLUNA
   getAddress("0x459086f2376525bdceba5bdda135e4e9d3fef5bf"), // renBCH
@@ -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 =

+ 8 - 2
bridge_ui/src/utils/getSignedVAAWithRetry.ts

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