diff --git a/bridge_ui/src/components/Transfer/SendConfirmationDialog.tsx b/bridge_ui/src/components/Transfer/SendConfirmationDialog.tsx
index a8870e0d..cedcf9c7 100644
--- a/bridge_ui/src/components/Transfer/SendConfirmationDialog.tsx
+++ b/bridge_ui/src/components/Transfer/SendConfirmationDialog.tsx
@@ -1,3 +1,4 @@
+import { isEVMChain } from "@certusone/wormhole-sdk";
import {
Button,
Dialog,
@@ -7,62 +8,142 @@ import {
Typography,
} from "@material-ui/core";
import { ArrowDownward } from "@material-ui/icons";
-import { Alert } from "@material-ui/lab";
+import { useEffect, useMemo, useState } from "react";
import { useSelector } from "react-redux";
import {
+ selectTransferOriginChain,
selectTransferSourceChain,
selectTransferSourceParsedTokenAccount,
} from "../../store/selectors";
-import { CHAINS_BY_ID } from "../../utils/consts";
+import { CHAINS_BY_ID, MULTI_CHAIN_TOKENS } from "../../utils/consts";
import SmartAddress from "../SmartAddress";
import { useTargetInfo } from "./Target";
+import TokenWarning from "./TokenWarning";
-function SendConfirmationContent() {
+function SendConfirmationContent({
+ open,
+ onClose,
+ onClick,
+}: {
+ open: boolean;
+ onClose: () => void;
+ onClick: () => void;
+}) {
const sourceChain = useSelector(selectTransferSourceChain);
const sourceParsedTokenAccount = useSelector(
selectTransferSourceParsedTokenAccount
);
const { targetChain, targetAsset, symbol, tokenName, logo } = useTargetInfo();
- return (
+ const originChain = useSelector(selectTransferOriginChain);
+
+ //TODO this check is essentially duplicated.
+ const deservesTimeout = useMemo(() => {
+ if (originChain && sourceParsedTokenAccount?.mintKey) {
+ const searchableAddress = isEVMChain(originChain)
+ ? sourceParsedTokenAccount.mintKey.toLowerCase()
+ : sourceParsedTokenAccount.mintKey;
+ return (
+ originChain !== targetChain &&
+ !!MULTI_CHAIN_TOKENS[sourceChain]?.[searchableAddress]
+ );
+ } else {
+ return false;
+ }
+ }, [originChain, targetChain, sourceChain, sourceParsedTokenAccount]);
+ const timeoutDuration = 5;
+
+ const [countdown, setCountdown] = useState(
+ deservesTimeout ? timeoutDuration : 0
+ );
+
+ useEffect(() => {
+ if (!deservesTimeout || countdown === 0) {
+ return;
+ }
+ let cancelled = false;
+
+ setInterval(() => {
+ if (!cancelled) {
+ setCountdown((state) => state - 1);
+ }
+ }, 1000);
+
+ return () => {
+ cancelled = true;
+ };
+ }, [deservesTimeout, countdown]);
+
+ useEffect(() => {
+ if (open && deservesTimeout) {
+ //Countdown starts on mount, but we actually want it to start on open
+ setCountdown(timeoutDuration);
+ }
+ }, [open, deservesTimeout]);
+
+ const sendConfirmationContent = (
<>
- {targetAsset ? (
-
-
-
-
- {CHAINS_BY_ID[sourceChain].name}
+ Are you sure?
+
+ {targetAsset ? (
+
+
+ You are about to perform this transfer:
+
+
+
+ {CHAINS_BY_ID[sourceChain].name}
+
+
+
+
+
+
+ {CHAINS_BY_ID[targetChain].name}
+
+
-
-
-
-
- {CHAINS_BY_ID[targetChain].name}
-
-
-
- ) : null}
-
- Once the transfer transaction is submitted, the transfer must be
- completed by redeeming the tokens on the target chain. Please ensure
- that the token listed above is the desired token and confirm that
- markets exist on the target chain.
-
+ ) : null}
+
+
+
+
+
+
>
);
+
+ return sendConfirmationContent;
}
export default function SendConfirmationDialog({
@@ -76,18 +157,11 @@ export default function SendConfirmationDialog({
}) {
return (
);
}
diff --git a/bridge_ui/src/components/Transfer/Source.tsx b/bridge_ui/src/components/Transfer/Source.tsx
index 40be6913..7cccecb7 100644
--- a/bridge_ui/src/components/Transfer/Source.tsx
+++ b/bridge_ui/src/components/Transfer/Source.tsx
@@ -36,7 +36,6 @@ import LowBalanceWarning from "../LowBalanceWarning";
import NumberTextField from "../NumberTextField";
import StepDescription from "../StepDescription";
import { TokenSelector } from "../TokenSelectors/SourceTokenSelector";
-import TokenWarning from "./TokenWarning";
const useStyles = makeStyles((theme) => ({
transferField: {
@@ -135,11 +134,6 @@ function Source() {
) : (
<>
-
{hasParsedTokenAccount ? (
({
description: {
@@ -54,11 +53,6 @@ export default function SourcePreview() {
>
{explainerContent}
-
>
);
}
diff --git a/bridge_ui/src/components/Transfer/TokenWarning.tsx b/bridge_ui/src/components/Transfer/TokenWarning.tsx
index 3f9f6082..5453512e 100644
--- a/bridge_ui/src/components/Transfer/TokenWarning.tsx
+++ b/bridge_ui/src/components/Transfer/TokenWarning.tsx
@@ -1,19 +1,10 @@
-import {
- ChainId,
- CHAIN_ID_BSC,
- CHAIN_ID_ETH,
- CHAIN_ID_SOLANA,
- WSOL_ADDRESS,
-} from "@certusone/wormhole-sdk";
-import { getAddress } from "@ethersproject/address";
-import { makeStyles } from "@material-ui/core";
+import { ChainId, CHAIN_ID_ETH, isEVMChain } from "@certusone/wormhole-sdk";
+import { Box, Link, makeStyles, Typography } from "@material-ui/core";
import { Alert } from "@material-ui/lab";
-import { useMemo } from "react";
import {
- BSC_MARKET_WARNINGS,
- ETH_TOKENS_THAT_CAN_BE_SWAPPED_ON_SOLANA,
- ETH_TOKENS_THAT_EXIST_ELSEWHERE,
- SOLANA_TOKENS_THAT_EXIST_ELSEWHERE,
+ AVAILABLE_MARKETS_URL,
+ CHAINS_BY_ID,
+ MULTI_CHAIN_TOKENS,
} from "../../utils/consts";
const useStyles = makeStyles((theme) => ({
@@ -21,81 +12,122 @@ const useStyles = makeStyles((theme) => ({
marginTop: theme.spacing(2),
marginBottom: theme.spacing(2),
},
+ alert: {
+ textAlign: "center",
+ },
+ line: {
+ marginBottom: theme.spacing(2),
+ },
}));
-export default function TokenWarning({
- sourceChain,
- tokenAddress,
+function WormholeWrappedWarning() {
+ const classes = useStyles();
+ return (
+
+
+ The tokens you will receive are{" "}
+
+ Wormhole Wrapped Tokens
+ {" "}
+ and will need to be exchanged for native assets.
+
+
+
+ Click here to see available markets for wrapped tokens.
+
+
+
+ );
+}
+
+function MultichainWarning({
symbol,
+ targetChain,
}: {
- sourceChain: ChainId;
- tokenAddress: string | undefined;
- symbol: string | undefined;
+ symbol: string;
+ targetChain: ChainId;
}) {
const classes = useStyles();
- const tokenConflictingNativeWarning = useMemo(
- () =>
- tokenAddress &&
- ((sourceChain === CHAIN_ID_SOLANA &&
- SOLANA_TOKENS_THAT_EXIST_ELSEWHERE.includes(tokenAddress)) ||
- (sourceChain === CHAIN_ID_ETH &&
- ETH_TOKENS_THAT_EXIST_ELSEWHERE.includes(getAddress(tokenAddress))))
- ? `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,
- [sourceChain, tokenAddress, symbol]
+ return (
+
+ {`You will not receive native ${symbol} on ${CHAINS_BY_ID[targetChain].name}`}
+ {`To receive native ${symbol}, you will have to perform a swap with the wrapped tokens once you are done bridging.`}
+
+
+ Click here to see available markets for wrapped tokens.
+
+
+
);
- const marketsWarning = useMemo(() => {
- let show = false;
- if (sourceChain === CHAIN_ID_SOLANA && tokenAddress === WSOL_ADDRESS) {
- show = true;
- } else if (
- sourceChain === CHAIN_ID_BSC &&
- tokenAddress &&
- BSC_MARKET_WARNINGS.includes(getAddress(tokenAddress))
- ) {
- show = true;
- }
- if (show) {
- return `As of 10/13/2021, markets have not been established for ${
- symbol ? "Wormhole-wrapped " + symbol : "this token"
- }. Please verify this token will be useful on the target chain.`;
- } else {
- return null;
- }
- }, [sourceChain, tokenAddress, symbol]);
+}
- const content = tokenConflictingNativeWarning ? (
-
- {tokenConflictingNativeWarning}
-
- ) : marketsWarning ? (
-
- {marketsWarning}
-
- ) : sourceChain === CHAIN_ID_ETH &&
- tokenAddress &&
- getAddress(tokenAddress) ===
- getAddress("0xae7ab96520de3a18e5e111b5eaab095312d7fe84") ? ( // stETH (Lido)
-
+function RewardsWarning() {
+ const classes = useStyles();
+ return (
+
Lido stETH rewards can only be received on Ethereum. Use the value
accruing wrapper token wstETH instead.
- ) : sourceChain === CHAIN_ID_ETH &&
- tokenAddress &&
- ETH_TOKENS_THAT_CAN_BE_SWAPPED_ON_SOLANA.includes(
- getAddress(tokenAddress)
- ) ? (
- //TODO: will this be accurate with Terra support?
-
- Bridging {symbol ? symbol : "the token"} via Wormhole will not produce
- native {symbol ? symbol : "assets"}. It will produce a wrapped version
- which can be swapped using a stable swap protocol.
-
- ) : null;
-
- return content ? {content}
: null;
+ );
+}
+
+export default function TokenWarning({
+ sourceChain,
+ sourceAsset,
+ originChain,
+ targetChain,
+ targetAsset,
+ symbol,
+}: {
+ sourceChain?: ChainId;
+ sourceAsset?: string;
+ originChain?: ChainId;
+ targetChain?: ChainId;
+ targetAsset?: string;
+ symbol?: string;
+}) {
+ if (
+ !(originChain && targetChain && targetAsset && sourceChain && sourceAsset)
+ ) {
+ return null;
+ }
+
+ const searchableAddress = isEVMChain(sourceChain)
+ ? sourceAsset.toLowerCase()
+ : sourceAsset;
+ const isWormholeWrapped = originChain !== targetChain;
+ const isMultiChain = !!MULTI_CHAIN_TOKENS[sourceChain]?.[searchableAddress];
+ const isRewardsToken =
+ searchableAddress === "0xae7ab96520de3a18e5e111b5eaab095312d7fe84" &&
+ sourceChain === CHAIN_ID_ETH;
+
+ const showMultiChainWarning = isMultiChain && isWormholeWrapped;
+ const showWrappedWarning = !isMultiChain && isWormholeWrapped; //Multichain warning is more important
+ const showRewardsWarning = isRewardsToken;
+
+ return (
+ <>
+ {showMultiChainWarning ? (
+
+ ) : null}
+ {showWrappedWarning ? : null}
+ {showRewardsWarning ? : null}
+ >
+ );
}
diff --git a/bridge_ui/src/utils/consts.ts b/bridge_ui/src/utils/consts.ts
index 7d6ad57f..bbe4787b 100644
--- a/bridge_ui/src/utils/consts.ts
+++ b/bridge_ui/src/utils/consts.ts
@@ -629,3 +629,36 @@ export const VAA_EMITTER_ADDRESSES = [
];
export const WORMHOLE_EXPLORER_BASE = "https://wormholenetwork.com/en/explorer";
+
+export const MULTI_CHAIN_TOKENS: {
+ [x: number]: { [address: string]: string };
+} =
+ //EVM chains should format the addresses to all lowercase
+ CLUSTER === "mainnet"
+ ? {
+ [CHAIN_ID_SOLANA]: {
+ EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v: "USDC",
+ Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB: "USDT",
+ },
+ [CHAIN_ID_ETH]: {
+ "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "USDC",
+ "0xdac17f958d2ee523a2206206994597c13d831ec7": "USDT",
+ },
+ [CHAIN_ID_TERRA]: {},
+ [CHAIN_ID_BSC]: {
+ "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d": "USDC",
+ "0x55d398326f99059ff775485246999027b3197955": "USDT",
+ },
+ [CHAIN_ID_POLYGON]: {
+ "0x2791bca1f2de4661ed88a30c99a7a9449aa84174": "USDC",
+ "0xc2132d05d31c914a87c6611c10748aeb04b58e8f": "USDT",
+ },
+ }
+ : {
+ [CHAIN_ID_SOLANA]: {
+ "2WDq7wSs9zYrpx2kbHDA4RUTRch2CCTP6ZWaH4GNfnQQ": "SOLT",
+ },
+ };
+
+export const AVAILABLE_MARKETS_URL =
+ "https://docs.wormholenetwork.com/wormhole/overview-liquid-markets";