bridge_ui: QoL improvements

Change-Id: I4221ff5d7757e099b8dad55de6ca2765137e5e38
This commit is contained in:
Evan Gray 2021-09-18 13:04:43 -04:00
parent 03a373676b
commit 26b6ee22bb
15 changed files with 268 additions and 79 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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