bridge_ui: updated warnings & confirm dialog
Change-Id: I6b5e471ddf2d4188932b72647f92fcf4adfa7555
This commit is contained in:
parent
c824a99636
commit
4b510d8aa1
|
@ -1,3 +1,4 @@
|
|||
import { isEVMChain } from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
|
@ -7,26 +8,87 @@ 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();
|
||||
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 = (
|
||||
<>
|
||||
<DialogTitle>Are you sure?</DialogTitle>
|
||||
<DialogContent>
|
||||
{targetAsset ? (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<div style={{ textAlign: "center", marginBottom: 16 }}>
|
||||
<Typography variant="subtitle1" style={{ marginBottom: 8 }}>
|
||||
You are about to perform this transfer:
|
||||
</Typography>
|
||||
<SmartAddress
|
||||
variant="h6"
|
||||
chainId={sourceChain}
|
||||
|
@ -55,14 +117,33 @@ function SendConfirmationContent() {
|
|||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<Alert severity="warning" variant="outlined" style={{ marginTop: 8 }}>
|
||||
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.
|
||||
</Alert>
|
||||
<TokenWarning
|
||||
sourceAsset={sourceParsedTokenAccount?.mintKey}
|
||||
sourceChain={sourceChain}
|
||||
originChain={originChain}
|
||||
targetAsset={targetAsset ?? undefined}
|
||||
targetChain={targetChain}
|
||||
symbol={symbol}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button variant="outlined" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={onClick}
|
||||
size={"medium"}
|
||||
disabled={!!countdown}
|
||||
>
|
||||
{!!countdown ? countdown.toString() : "Confirm"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
|
||||
return sendConfirmationContent;
|
||||
}
|
||||
|
||||
export default function SendConfirmationDialog({
|
||||
|
@ -76,18 +157,11 @@ export default function SendConfirmationDialog({
|
|||
}) {
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose}>
|
||||
<DialogTitle>Are you sure?</DialogTitle>
|
||||
<DialogContent>
|
||||
<SendConfirmationContent />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button variant="outlined" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="contained" color="primary" onClick={onClick}>
|
||||
Confirm
|
||||
</Button>
|
||||
</DialogActions>
|
||||
<SendConfirmationContent
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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() {
|
|||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<TokenWarning
|
||||
sourceChain={sourceChain}
|
||||
tokenAddress={parsedTokenAccount?.mintKey}
|
||||
symbol={parsedTokenAccount?.symbol}
|
||||
/>
|
||||
<LowBalanceWarning chainId={sourceChain} />
|
||||
{hasParsedTokenAccount ? (
|
||||
<NumberTextField
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
} from "../../store/selectors";
|
||||
import { CHAINS_BY_ID } from "../../utils/consts";
|
||||
import SmartAddress from "../SmartAddress";
|
||||
import TokenWarning from "./TokenWarning";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
description: {
|
||||
|
@ -54,11 +53,6 @@ export default function SourcePreview() {
|
|||
>
|
||||
{explainerContent}
|
||||
</Typography>
|
||||
<TokenWarning
|
||||
sourceChain={sourceChain}
|
||||
tokenAddress={sourceParsedTokenAccount?.mintKey}
|
||||
symbol={sourceParsedTokenAccount?.symbol}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<Alert severity="info" variant="outlined" className={classes.alert}>
|
||||
<Typography component="div" className={classes.line}>
|
||||
The tokens you will receive are{" "}
|
||||
<Box fontWeight={900} display="inline">
|
||||
Wormhole Wrapped Tokens
|
||||
</Box>{" "}
|
||||
and will need to be exchanged for native assets.
|
||||
</Typography>
|
||||
<Typography component="div">
|
||||
<Link
|
||||
href={AVAILABLE_MARKETS_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Click here to see available markets for wrapped tokens.
|
||||
</Link>
|
||||
</Typography>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<Alert severity="warning" variant="outlined" className={classes.alert}>
|
||||
<Typography
|
||||
variant="h6"
|
||||
className={classes.line}
|
||||
>{`You will not receive native ${symbol} on ${CHAINS_BY_ID[targetChain].name}`}</Typography>
|
||||
<Typography
|
||||
className={classes.line}
|
||||
>{`To receive native ${symbol}, you will have to perform a swap with the wrapped tokens once you are done bridging.`}</Typography>
|
||||
<Typography>
|
||||
<Link
|
||||
href={AVAILABLE_MARKETS_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Click here to see available markets for wrapped tokens.
|
||||
</Link>
|
||||
</Typography>
|
||||
</Alert>
|
||||
);
|
||||
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 ? (
|
||||
<Alert severity="warning" variant="outlined">
|
||||
{tokenConflictingNativeWarning}
|
||||
</Alert>
|
||||
) : marketsWarning ? (
|
||||
<Alert severity="warning" variant="outlined">
|
||||
{marketsWarning}
|
||||
</Alert>
|
||||
) : sourceChain === CHAIN_ID_ETH &&
|
||||
tokenAddress &&
|
||||
getAddress(tokenAddress) ===
|
||||
getAddress("0xae7ab96520de3a18e5e111b5eaab095312d7fe84") ? ( // stETH (Lido)
|
||||
<Alert severity="warning" variant="outlined">
|
||||
function RewardsWarning() {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<Alert severity="warning" variant="outlined" className={classes.alert}>
|
||||
Lido stETH rewards can only be received on Ethereum. Use the value
|
||||
accruing wrapper token wstETH instead.
|
||||
</Alert>
|
||||
) : sourceChain === CHAIN_ID_ETH &&
|
||||
tokenAddress &&
|
||||
ETH_TOKENS_THAT_CAN_BE_SWAPPED_ON_SOLANA.includes(
|
||||
getAddress(tokenAddress)
|
||||
) ? (
|
||||
//TODO: will this be accurate with Terra support?
|
||||
<Alert severity="info" variant="outlined">
|
||||
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.
|
||||
</Alert>
|
||||
) : null;
|
||||
|
||||
return content ? <div className={classes.container}>{content}</div> : 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 ? (
|
||||
<MultichainWarning
|
||||
symbol={symbol || "tokens"}
|
||||
targetChain={targetChain}
|
||||
/>
|
||||
) : null}
|
||||
{showWrappedWarning ? <WormholeWrappedWarning /> : null}
|
||||
{showRewardsWarning ? <RewardsWarning /> : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
Loading…
Reference in New Issue