bridge_ui: confidence enhancement
Change-Id: Ied0f0fe641a89bb99c9b6a5d0e1400e70b2c8d77
This commit is contained in:
parent
4bdb714594
commit
461e8f256e
|
@ -5,10 +5,7 @@
|
|||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Wormhole Token Bridge"
|
||||
/>
|
||||
<meta name="description" content="Wormhole Token Bridge" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
|
@ -24,9 +21,12 @@
|
|||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Sora:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Sora:wght@200;300;400;500;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>Wormhole Token Bridge</title>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -1,14 +1,22 @@
|
|||
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
AppBar,
|
||||
Button,
|
||||
Container,
|
||||
Hidden,
|
||||
IconButton,
|
||||
Link,
|
||||
makeStyles,
|
||||
Tab,
|
||||
Tabs,
|
||||
Toolbar,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { GitHub, Help, Publish, Send } from "@material-ui/icons";
|
||||
import { HelpOutline, Send } from "@material-ui/icons";
|
||||
import clsx from "clsx";
|
||||
import { useCallback } from "react";
|
||||
import { useHistory, useLocation, useRouteMatch } from "react-router";
|
||||
import {
|
||||
Link as RouterLink,
|
||||
NavLink,
|
||||
|
@ -19,17 +27,18 @@ import {
|
|||
import Attest from "./components/Attest";
|
||||
import Home from "./components/Home";
|
||||
import Migration from "./components/Migration";
|
||||
import EthereumQuickMigrate from "./components/Migration/EthereumQuickMigrate";
|
||||
import NFT from "./components/NFT";
|
||||
import NFTOriginVerifier from "./components/NFTOriginVerifier";
|
||||
import Recovery from "./components/Recovery";
|
||||
import Transfer from "./components/Transfer";
|
||||
import wormholeLogo from "./icons/wormhole.svg";
|
||||
import { useBetaContext } from "./contexts/BetaContext";
|
||||
import { COLORS } from "./muiTheme";
|
||||
import { CLUSTER } from "./utils/consts";
|
||||
import EthereumQuickMigrate from "./components/Migration/EthereumQuickMigrate";
|
||||
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
appBar: {
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
background: COLORS.nearBlackWithMinorTransparency,
|
||||
"& > .MuiToolbar-root": {
|
||||
margin: "auto",
|
||||
width: "100%",
|
||||
|
@ -40,13 +49,6 @@ const useStyles = makeStyles((theme) => ({
|
|||
flex: 1,
|
||||
width: "100vw",
|
||||
},
|
||||
logo: {
|
||||
verticalAlign: "middle",
|
||||
height: 52,
|
||||
[theme.breakpoints.down("xs")]: {
|
||||
height: 42,
|
||||
},
|
||||
},
|
||||
link: {
|
||||
...theme.typography.body1,
|
||||
color: theme.palette.text.primary,
|
||||
|
@ -58,9 +60,14 @@ const useStyles = makeStyles((theme) => ({
|
|||
marginLeft: theme.spacing(1),
|
||||
},
|
||||
"&.active": {
|
||||
color: theme.palette.secondary.light,
|
||||
color: theme.palette.primary.light,
|
||||
},
|
||||
},
|
||||
bg: {
|
||||
minHeight: "100vh",
|
||||
background:
|
||||
"linear-gradient(160deg, rgba(69,74,117,.1) 0%, rgba(138,146,178,.1) 33%, rgba(69,74,117,.1) 66%, rgba(98,104,143,.1) 100%), linear-gradient(45deg, rgba(153,69,255,.1) 0%, rgba(121,98,231,.1) 20%, rgba(0,209,140,.1) 100%)",
|
||||
},
|
||||
content: {
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
margin: theme.spacing(2, 0),
|
||||
|
@ -69,70 +76,93 @@ const useStyles = makeStyles((theme) => ({
|
|||
margin: theme.spacing(4, 0),
|
||||
},
|
||||
},
|
||||
brandText: {
|
||||
...theme.typography.h5,
|
||||
[theme.breakpoints.down("xs")]: {
|
||||
fontSize: 22,
|
||||
},
|
||||
fontWeight: "500",
|
||||
background: `linear-gradient(160deg, rgba(255,255,255,1) 0%, rgba(255,255,255,0.5) 100%);`,
|
||||
WebkitBackgroundClip: "text",
|
||||
backgroundClip: "text",
|
||||
WebkitTextFillColor: "transparent",
|
||||
MozBackgroundClip: "text",
|
||||
MozTextFillColor: "transparent",
|
||||
letterSpacing: "3px",
|
||||
},
|
||||
gradientButton: {
|
||||
backgroundImage: `linear-gradient(45deg, ${COLORS.blue} 0%, ${COLORS.nearBlack}20 50%, ${COLORS.blue}30 62%, ${COLORS.nearBlack}50 120%)`,
|
||||
transition: "0.75s",
|
||||
backgroundSize: "200% auto",
|
||||
boxShadow: "0 0 20px #222",
|
||||
"&:hover": {
|
||||
backgroundPosition:
|
||||
"right center" /* change the direction of the change here */,
|
||||
},
|
||||
},
|
||||
betaBanner: {
|
||||
background: `linear-gradient(to left, ${COLORS.blue}40, ${COLORS.green}40);`,
|
||||
padding: theme.spacing(1, 0),
|
||||
},
|
||||
}));
|
||||
|
||||
function App() {
|
||||
const classes = useStyles();
|
||||
const isBeta = useBetaContext();
|
||||
const isHomepage = useRouteMatch({ path: "/", exact: true });
|
||||
const isOriginVerifier = useRouteMatch({
|
||||
path: "/nft-origin-verifier",
|
||||
exact: true,
|
||||
});
|
||||
const { push } = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
const handleTabChange = useCallback(
|
||||
(event, value) => {
|
||||
push(value);
|
||||
},
|
||||
[push]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div className={classes.bg}>
|
||||
<AppBar position="static" color="inherit" className={classes.appBar}>
|
||||
<Toolbar>
|
||||
<RouterLink to="/">
|
||||
<img
|
||||
src={wormholeLogo}
|
||||
alt="Wormhole Logo"
|
||||
className={classes.logo}
|
||||
/>
|
||||
</RouterLink>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to="/"
|
||||
className={clsx(classes.link, classes.brandText)}
|
||||
>
|
||||
wormhole
|
||||
</Link>
|
||||
<div className={classes.spacer} />
|
||||
<Hidden implementation="css" xsDown>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<Tooltip title="Transfer NFTs to another blockchain">
|
||||
<Link component={NavLink} to="/nft" className={classes.link}>
|
||||
NFTs
|
||||
</Link>
|
||||
</Tooltip>
|
||||
<Tooltip title="Transfer tokens to another blockchain">
|
||||
<Link
|
||||
component={NavLink}
|
||||
{isHomepage ? (
|
||||
<Button
|
||||
component={RouterLink}
|
||||
to="/transfer"
|
||||
className={classes.link}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="large"
|
||||
className={classes.gradientButton}
|
||||
>
|
||||
Transfer
|
||||
</Link>
|
||||
</Tooltip>
|
||||
<Tooltip title="Register a new wrapped token">
|
||||
<Link
|
||||
component={NavLink}
|
||||
to="/register"
|
||||
className={classes.link}
|
||||
>
|
||||
Register
|
||||
</Link>
|
||||
</Tooltip>
|
||||
<Tooltip title="View the source code">
|
||||
<IconButton
|
||||
href="https://github.com/certusone/wormhole"
|
||||
target="_blank"
|
||||
size="small"
|
||||
className={classes.link}
|
||||
>
|
||||
<GitHub />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
Transfer Tokens
|
||||
</Button>
|
||||
) : (
|
||||
<Tooltip title="View the FAQ">
|
||||
<IconButton
|
||||
<Button
|
||||
href="https://docs.wormholenetwork.com/wormhole/faqs"
|
||||
target="_blank"
|
||||
size="small"
|
||||
className={classes.link}
|
||||
variant="outlined"
|
||||
endIcon={<HelpOutline />}
|
||||
>
|
||||
<Help />
|
||||
</IconButton>
|
||||
FAQ
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</Hidden>
|
||||
<Hidden implementation="css" smUp>
|
||||
{isHomepage ? (
|
||||
<Tooltip title="Transfer tokens to another blockchain">
|
||||
<IconButton
|
||||
component={NavLink}
|
||||
|
@ -143,16 +173,7 @@ function App() {
|
|||
<Send />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Register a new wrapped token">
|
||||
<IconButton
|
||||
component={NavLink}
|
||||
to="/register"
|
||||
size="small"
|
||||
className={classes.link}
|
||||
>
|
||||
<Publish />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip title="View the FAQ">
|
||||
<IconButton
|
||||
href="https://docs.wormholenetwork.com/wormhole/faqs"
|
||||
|
@ -160,20 +181,47 @@ function App() {
|
|||
size="small"
|
||||
className={classes.link}
|
||||
>
|
||||
<Help />
|
||||
<HelpOutline />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Hidden>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
{CLUSTER === "mainnet" ? null : (
|
||||
<AppBar position="static" color="secondary">
|
||||
<AppBar position="static" className={classes.betaBanner}>
|
||||
<Typography style={{ textAlign: "center" }}>
|
||||
Caution! You are using the {CLUSTER} build of this app.
|
||||
</Typography>
|
||||
</AppBar>
|
||||
)}
|
||||
{isBeta ? (
|
||||
<AppBar position="static" className={classes.betaBanner}>
|
||||
<Typography style={{ textAlign: "center" }}>
|
||||
Caution! You have enabled the beta. Enter the secret code again to
|
||||
disable.
|
||||
</Typography>
|
||||
</AppBar>
|
||||
) : null}
|
||||
<div className={classes.content}>
|
||||
{isHomepage || isOriginVerifier ? null : (
|
||||
<Container maxWidth="md" style={{ paddingBottom: 24 }}>
|
||||
<Tabs
|
||||
value={
|
||||
["/transfer", "/nft", "/redeem"].includes(pathname)
|
||||
? pathname
|
||||
: "/transfer"
|
||||
}
|
||||
variant="fullWidth"
|
||||
onChange={handleTabChange}
|
||||
indicatorColor="primary"
|
||||
>
|
||||
<Tab label="Tokens" value="/transfer" />
|
||||
<Tab label="NFTs" value="/nft" />
|
||||
<Tab label="Redeem" value="/redeem" to="/redeem" />
|
||||
</Tabs>
|
||||
</Container>
|
||||
)}
|
||||
<Switch>
|
||||
<Route exact path="/nft">
|
||||
<NFT />
|
||||
|
@ -184,6 +232,9 @@ function App() {
|
|||
<Route exact path="/transfer">
|
||||
<Transfer />
|
||||
</Route>
|
||||
<Route exact path="/redeem">
|
||||
<Recovery />
|
||||
</Route>
|
||||
<Route exact path="/register">
|
||||
<Attest />
|
||||
</Route>
|
||||
|
@ -204,7 +255,7 @@ function App() {
|
|||
</Route>
|
||||
</Switch>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ export default function CreatePreview() {
|
|||
}, [dispatch, push]);
|
||||
|
||||
const explainerString =
|
||||
"Success! The redeem transaction was submitted. The tokens will become available once the transaction confirms.";
|
||||
"Success! The create wrapped transaction was submitted.";
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { makeStyles, MenuItem, TextField } from "@material-ui/core";
|
||||
import { useCallback } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useBetaContext } from "../../contexts/BetaContext";
|
||||
import {
|
||||
incrementStep,
|
||||
setSourceAsset,
|
||||
|
@ -12,7 +13,7 @@ import {
|
|||
selectAttestSourceAsset,
|
||||
selectAttestSourceChain,
|
||||
} from "../../store/selectors";
|
||||
import { CHAINS } from "../../utils/consts";
|
||||
import { BETA_CHAINS, CHAINS } from "../../utils/consts";
|
||||
import ButtonWithLoader from "../ButtonWithLoader";
|
||||
import KeyAndBalance from "../KeyAndBalance";
|
||||
import LowBalanceWarning from "../LowBalanceWarning";
|
||||
|
@ -26,6 +27,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
function Source() {
|
||||
const classes = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const isBeta = useBetaContext();
|
||||
const sourceChain = useSelector(selectAttestSourceChain);
|
||||
const sourceAsset = useSelector(selectAttestSourceAsset);
|
||||
const isSourceComplete = useSelector(selectAttestIsSourceComplete);
|
||||
|
@ -49,12 +51,15 @@ function Source() {
|
|||
<>
|
||||
<TextField
|
||||
select
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={sourceChain}
|
||||
onChange={handleSourceChange}
|
||||
disabled={shouldLockFields}
|
||||
>
|
||||
{CHAINS.map(({ id, name }) => (
|
||||
{CHAINS.filter(({ id }) =>
|
||||
isBeta ? true : !BETA_CHAINS.includes(id)
|
||||
).map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
|
@ -63,6 +68,7 @@ function Source() {
|
|||
<KeyAndBalance chainId={sourceChain} />
|
||||
<TextField
|
||||
label="Asset"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
className={classes.transferField}
|
||||
value={sourceAsset}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { makeStyles, MenuItem, TextField, Typography } from "@material-ui/core";
|
|||
import { Alert } from "@material-ui/lab";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useBetaContext } from "../../contexts/BetaContext";
|
||||
import { EthGasEstimateSummary } from "../../hooks/useTransactionFees";
|
||||
import { incrementStep, setTargetChain } from "../../store/attestSlice";
|
||||
import {
|
||||
|
@ -10,7 +11,7 @@ import {
|
|||
selectAttestSourceChain,
|
||||
selectAttestTargetChain,
|
||||
} from "../../store/selectors";
|
||||
import { CHAINS, CHAINS_BY_ID } from "../../utils/consts";
|
||||
import { BETA_CHAINS, CHAINS, CHAINS_BY_ID } from "../../utils/consts";
|
||||
import { isEVMChain } from "../../utils/ethereum";
|
||||
import ButtonWithLoader from "../ButtonWithLoader";
|
||||
import KeyAndBalance from "../KeyAndBalance";
|
||||
|
@ -26,6 +27,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
function Target() {
|
||||
const classes = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const isBeta = useBetaContext();
|
||||
const sourceChain = useSelector(selectAttestSourceChain);
|
||||
const chains = useMemo(
|
||||
() => CHAINS.filter((c) => c.id !== sourceChain),
|
||||
|
@ -47,12 +49,15 @@ function Target() {
|
|||
<>
|
||||
<TextField
|
||||
select
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={targetChain}
|
||||
onChange={handleTargetChange}
|
||||
disabled={shouldLockFields}
|
||||
>
|
||||
{chains.map(({ id, name }) => (
|
||||
{chains
|
||||
.filter(({ id }) => (isBeta ? true : !BETA_CHAINS.includes(id)))
|
||||
.map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import {
|
||||
Container,
|
||||
makeStyles,
|
||||
Step,
|
||||
StepButton,
|
||||
StepContent,
|
||||
|
@ -9,7 +8,6 @@ import {
|
|||
import { Alert } from "@material-ui/lab";
|
||||
import { useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { COLORS } from "../../muiTheme";
|
||||
import { setStep } from "../../store/attestSlice";
|
||||
import {
|
||||
selectAttestActiveStep,
|
||||
|
@ -27,14 +25,7 @@ import SourcePreview from "./SourcePreview";
|
|||
import Target from "./Target";
|
||||
import TargetPreview from "./TargetPreview";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
rootContainer: {
|
||||
backgroundColor: COLORS.nearBlackWithMinorTransparency,
|
||||
},
|
||||
}));
|
||||
|
||||
function Attest() {
|
||||
const classes = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const activeStep = useSelector(selectAttestActiveStep);
|
||||
const isSending = useSelector(selectAttestIsSending);
|
||||
|
@ -57,11 +48,7 @@ function Attest() {
|
|||
This form allows you to register a token on a new foreign chain. Tokens
|
||||
must be registered before they can be transferred.
|
||||
</Alert>
|
||||
<Stepper
|
||||
activeStep={activeStep}
|
||||
orientation="vertical"
|
||||
className={classes.rootContainer}
|
||||
>
|
||||
<Stepper activeStep={activeStep} orientation="vertical">
|
||||
<Step
|
||||
expanded={activeStep >= 0}
|
||||
disabled={preventNavigation || isCreateComplete}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { makeStyles } from "@material-ui/core";
|
||||
import { useRouteMatch } from "react-router";
|
||||
import holev2 from "../images/holev2.svg";
|
||||
// import { useRouteMatch } from "react-router";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
holeOuterContainer: {
|
||||
|
@ -31,19 +30,11 @@ const useStyles = makeStyles((theme) => ({
|
|||
|
||||
const BackgroundImage = () => {
|
||||
const classes = useStyles();
|
||||
const isHomepage = useRouteMatch({ path: "/", exact: true });
|
||||
// const isHomepage = useRouteMatch({ path: "/", exact: true });
|
||||
|
||||
return (
|
||||
<div className={classes.holeOuterContainer}>
|
||||
<div className={classes.holeInnerContainer}>
|
||||
<img
|
||||
src={holev2}
|
||||
alt=""
|
||||
className={
|
||||
classes.holeImage + (isHomepage ? "" : " " + classes.blurred)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.holeInnerContainer}></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {
|
||||
Button,
|
||||
Card,
|
||||
Container,
|
||||
Link,
|
||||
|
@ -7,7 +6,6 @@ import {
|
|||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import overview from "../../images/overview2.svg";
|
||||
import { COLORS } from "../../muiTheme";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
@ -29,7 +27,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
WebkitTextFillColor: "transparent",
|
||||
MozBackgroundClip: "text",
|
||||
MozTextFillColor: "transparent",
|
||||
filter: `drop-shadow( 0px 0px 8px ${COLORS.nearBlack}) drop-shadow( 0px 0px 14px ${COLORS.nearBlack}) drop-shadow( 0px 0px 24px ${COLORS.nearBlack})`,
|
||||
// filter: `drop-shadow( 0px 0px 8px ${COLORS.nearBlack}) drop-shadow( 0px 0px 14px ${COLORS.nearBlack}) drop-shadow( 0px 0px 24px ${COLORS.nearBlack})`,
|
||||
},
|
||||
description: {
|
||||
marginBottom: theme.spacing(2),
|
||||
|
@ -45,8 +43,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
maxWidth: "100%",
|
||||
},
|
||||
mainCard: {
|
||||
padding: theme.spacing(1),
|
||||
borderRadius: "5px",
|
||||
padding: theme.spacing(8),
|
||||
backgroundColor: COLORS.nearBlackWithMinorTransparency,
|
||||
},
|
||||
spacer: {
|
||||
|
@ -60,12 +57,12 @@ function Home() {
|
|||
<div>
|
||||
<Container maxWidth="md">
|
||||
<div className={classes.centeredContainer}>
|
||||
<Typography variant="h2" component="h1" className={classes.header}>
|
||||
<Typography variant="h1" className={classes.header}>
|
||||
<span className={classes.linearGradient}>The Portal is Open</span>
|
||||
</Typography>
|
||||
</div>
|
||||
</Container>
|
||||
<Container maxWidth="sm">
|
||||
<Container maxWidth="md">
|
||||
<Card className={classes.mainCard}>
|
||||
<Typography variant="h4" className={classes.description}>
|
||||
Wormhole v2 is here!
|
||||
|
@ -74,26 +71,11 @@ function Home() {
|
|||
The Wormhole Token Bridge allows you to seamlessly transfer
|
||||
tokenized assets across Solana and Ethereum.
|
||||
</Typography>
|
||||
<Button
|
||||
component={RouterLink}
|
||||
to="/transfer"
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
size="large"
|
||||
className={classes.button}
|
||||
>
|
||||
Transfer Tokens
|
||||
</Button>
|
||||
<div className={classes.spacer} />
|
||||
<Typography variant="subtitle1" className={classes.description}>
|
||||
If you transferred assets using the previous version of Wormhole,
|
||||
most assets can be migrated to v2 on the{" "}
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to="/transfer"
|
||||
color="secondary"
|
||||
noWrap
|
||||
>
|
||||
<Link component={RouterLink} to="/transfer" noWrap>
|
||||
transfer page
|
||||
</Link>
|
||||
.
|
||||
|
@ -101,23 +83,18 @@ function Home() {
|
|||
<Typography variant="subtitle1" className={classes.description}>
|
||||
For assets that don't support the migration, the v1 UI can be found
|
||||
at{" "}
|
||||
<Link href="https://v1.wormholebridge.com" color="secondary">
|
||||
<Link href="https://v1.wormholebridge.com">
|
||||
v1.wormholebridge.com
|
||||
</Link>
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" className={classes.description}>
|
||||
To learn more about the Wormhole Protocol that powers it, visit{" "}
|
||||
<Link href="https://wormholenetwork.com/en/" color="secondary">
|
||||
<Link href="https://wormholenetwork.com/en/">
|
||||
wormholenetwork.com
|
||||
</Link>
|
||||
</Typography>
|
||||
</Card>
|
||||
</Container>
|
||||
<Container maxWidth="md">
|
||||
<div className={classes.centeredContainer}>
|
||||
<img src={overview} alt="overview" className={classes.overview} />
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -182,6 +182,7 @@ export default function EthereumWorkflow({
|
|||
{explainerContent}
|
||||
<div className={classes.spacer} />
|
||||
<TextField
|
||||
variant="outlined"
|
||||
value={migrationAmount}
|
||||
type="number"
|
||||
onChange={handleAmountChange}
|
||||
|
|
|
@ -460,6 +460,7 @@ export default function Workflow({
|
|||
) : null}
|
||||
<div className={classes.spacer} />
|
||||
<TextField
|
||||
variant="outlined"
|
||||
value={migrationAmount}
|
||||
type="number"
|
||||
onChange={handleAmountChange}
|
||||
|
|
|
@ -1,462 +0,0 @@
|
|||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_SOLANA,
|
||||
getEmitterAddressEth,
|
||||
getEmitterAddressSolana,
|
||||
hexToNativeString,
|
||||
hexToUint8Array,
|
||||
parseNFTPayload,
|
||||
parseSequenceFromLogEth,
|
||||
parseSequenceFromLogSolana,
|
||||
uint8ArrayToHex,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Divider,
|
||||
Fab,
|
||||
makeStyles,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { Restore } from "@material-ui/icons";
|
||||
import { Alert } from "@material-ui/lab";
|
||||
import { Connection } from "@solana/web3.js";
|
||||
import { 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";
|
||||
import { setSignedVAAHex, setStep, setTargetChain } from "../../store/nftSlice";
|
||||
import {
|
||||
selectNFTSignedVAAHex,
|
||||
selectNFTSourceChain,
|
||||
} from "../../store/selectors";
|
||||
import {
|
||||
CHAINS_WITH_NFT_SUPPORT,
|
||||
getBridgeAddressForChain,
|
||||
getNFTBridgeAddressForChain,
|
||||
SOLANA_HOST,
|
||||
SOL_NFT_BRIDGE_ADDRESS,
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
} from "../../utils/consts";
|
||||
import { isEVMChain } from "../../utils/ethereum";
|
||||
import { getSignedVAAWithRetry } from "../../utils/getSignedVAAWithRetry";
|
||||
import parseError from "../../utils/parseError";
|
||||
import KeyAndBalance from "../KeyAndBalance";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
fab: {
|
||||
position: "fixed",
|
||||
bottom: theme.spacing(2),
|
||||
right: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
|
||||
async function evm(
|
||||
provider: ethers.providers.Web3Provider,
|
||||
tx: string,
|
||||
enqueueSnackbar: any,
|
||||
chainId: ChainId
|
||||
) {
|
||||
try {
|
||||
const receipt = await provider.getTransactionReceipt(tx);
|
||||
const sequence = parseSequenceFromLogEth(
|
||||
receipt,
|
||||
getBridgeAddressForChain(chainId)
|
||||
);
|
||||
const emitterAddress = getEmitterAddressEth(
|
||||
getNFTBridgeAddressForChain(chainId)
|
||||
);
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
chainId,
|
||||
emitterAddress,
|
||||
sequence.toString(),
|
||||
WORMHOLE_RPC_HOSTS.length
|
||||
);
|
||||
return { vaa: uint8ArrayToHex(vaaBytes), error: null };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
return { vaa: null, error: parseError(e) };
|
||||
}
|
||||
}
|
||||
|
||||
async function solana(tx: string, enqueueSnackbar: any) {
|
||||
try {
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const info = await connection.getTransaction(tx);
|
||||
if (!info) {
|
||||
throw new Error("An error occurred while fetching the transaction info");
|
||||
}
|
||||
const sequence = parseSequenceFromLogSolana(info);
|
||||
const emitterAddress = await getEmitterAddressSolana(
|
||||
SOL_NFT_BRIDGE_ADDRESS
|
||||
);
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_SOLANA,
|
||||
emitterAddress,
|
||||
sequence.toString(),
|
||||
WORMHOLE_RPC_HOSTS.length
|
||||
);
|
||||
return { vaa: uint8ArrayToHex(vaaBytes), error: null };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
return { vaa: null, error: parseError(e) };
|
||||
}
|
||||
}
|
||||
|
||||
function RecoveryDialogContent({
|
||||
onClose,
|
||||
disabled,
|
||||
}: {
|
||||
onClose: () => void;
|
||||
disabled: boolean;
|
||||
}) {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const dispatch = useDispatch();
|
||||
const { provider } = useEthereumProvider();
|
||||
const currentSourceChain = useSelector(selectNFTSourceChain);
|
||||
const [recoverySourceChain, setRecoverySourceChain] =
|
||||
useState(currentSourceChain);
|
||||
const [recoverySourceTx, setRecoverySourceTx] = useState("");
|
||||
const [recoverySourceTxIsLoading, setRecoverySourceTxIsLoading] =
|
||||
useState(false);
|
||||
const [recoverySourceTxError, setRecoverySourceTxError] = useState("");
|
||||
const currentSignedVAA = useSelector(selectNFTSignedVAAHex);
|
||||
const [recoverySignedVAA, setRecoverySignedVAA] = useState(currentSignedVAA);
|
||||
const [recoveryParsedVAA, setRecoveryParsedVAA] = useState<any>(null);
|
||||
useEffect(() => {
|
||||
if (!recoverySignedVAA) {
|
||||
setRecoverySourceTx("");
|
||||
setRecoverySourceChain(currentSourceChain);
|
||||
}
|
||||
}, [recoverySignedVAA, currentSourceChain]);
|
||||
useEffect(() => {
|
||||
if (recoverySourceTx) {
|
||||
let cancelled = false;
|
||||
if (isEVMChain(recoverySourceChain) && provider) {
|
||||
setRecoverySourceTxError("");
|
||||
setRecoverySourceTxIsLoading(true);
|
||||
(async () => {
|
||||
const { vaa, error } = await evm(
|
||||
provider,
|
||||
recoverySourceTx,
|
||||
enqueueSnackbar,
|
||||
recoverySourceChain
|
||||
);
|
||||
if (!cancelled) {
|
||||
setRecoverySourceTxIsLoading(false);
|
||||
if (vaa) {
|
||||
setRecoverySignedVAA(vaa);
|
||||
}
|
||||
if (error) {
|
||||
setRecoverySourceTxError(error);
|
||||
}
|
||||
}
|
||||
})();
|
||||
} else if (recoverySourceChain === CHAIN_ID_SOLANA) {
|
||||
setRecoverySourceTxError("");
|
||||
setRecoverySourceTxIsLoading(true);
|
||||
(async () => {
|
||||
const { vaa, error } = await solana(
|
||||
recoverySourceTx,
|
||||
enqueueSnackbar
|
||||
);
|
||||
if (!cancelled) {
|
||||
setRecoverySourceTxIsLoading(false);
|
||||
if (vaa) {
|
||||
setRecoverySignedVAA(vaa);
|
||||
}
|
||||
if (error) {
|
||||
setRecoverySourceTxError(error);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}
|
||||
}, [recoverySourceChain, recoverySourceTx, provider, enqueueSnackbar]);
|
||||
useEffect(() => {
|
||||
setRecoverySignedVAA(currentSignedVAA);
|
||||
}, [currentSignedVAA]);
|
||||
const handleSourceChainChange = useCallback((event) => {
|
||||
setRecoverySourceTx("");
|
||||
setRecoverySourceChain(event.target.value);
|
||||
}, []);
|
||||
const handleSourceTxChange = useCallback((event) => {
|
||||
setRecoverySourceTx(event.target.value.trim());
|
||||
}, []);
|
||||
const handleSignedVAAChange = useCallback((event) => {
|
||||
setRecoverySignedVAA(event.target.value.trim());
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
if (recoverySignedVAA) {
|
||||
(async () => {
|
||||
try {
|
||||
const { parse_vaa } = await import(
|
||||
"@certusone/wormhole-sdk/lib/solana/core/bridge"
|
||||
);
|
||||
const parsedVAA = parse_vaa(hexToUint8Array(recoverySignedVAA));
|
||||
if (!cancelled) {
|
||||
setRecoveryParsedVAA(parsedVAA);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if (!cancelled) {
|
||||
setRecoveryParsedVAA(null);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [recoverySignedVAA]);
|
||||
const parsedPayload = useMemo(
|
||||
() =>
|
||||
recoveryParsedVAA?.payload
|
||||
? parseNFTPayload(
|
||||
Buffer.from(new Uint8Array(recoveryParsedVAA.payload))
|
||||
)
|
||||
: null,
|
||||
[recoveryParsedVAA]
|
||||
);
|
||||
const parsedPayloadTargetChain = parsedPayload?.targetChain;
|
||||
const enableRecovery = recoverySignedVAA && parsedPayloadTargetChain;
|
||||
const handleRecoverClick = useCallback(() => {
|
||||
if (enableRecovery && recoverySignedVAA && parsedPayloadTargetChain) {
|
||||
// TODO: make recovery reducer
|
||||
dispatch(setSignedVAAHex(recoverySignedVAA));
|
||||
dispatch(setTargetChain(parsedPayloadTargetChain));
|
||||
dispatch(setStep(3));
|
||||
onClose();
|
||||
}
|
||||
}, [
|
||||
dispatch,
|
||||
enableRecovery,
|
||||
recoverySignedVAA,
|
||||
parsedPayloadTargetChain,
|
||||
onClose,
|
||||
]);
|
||||
return (
|
||||
<>
|
||||
<DialogContent>
|
||||
<Alert severity="info">
|
||||
If you have sent your tokens but have not redeemed them, you may paste
|
||||
in the Source Transaction ID (from Step 3) to resume your transfer.
|
||||
</Alert>
|
||||
<TextField
|
||||
select
|
||||
label="Source Chain"
|
||||
disabled={!!recoverySignedVAA}
|
||||
value={recoverySourceChain}
|
||||
onChange={handleSourceChainChange}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
>
|
||||
{CHAINS_WITH_NFT_SUPPORT.map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
{isEVMChain(recoverySourceChain) ? (
|
||||
<KeyAndBalance chainId={recoverySourceChain} />
|
||||
) : null}
|
||||
<TextField
|
||||
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>
|
||||
<TextField
|
||||
label="Emitter Chain"
|
||||
disabled
|
||||
value={recoveryParsedVAA?.emitter_chain || ""}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
label="Emitter Address"
|
||||
disabled
|
||||
value={
|
||||
(recoveryParsedVAA &&
|
||||
hexToNativeString(
|
||||
recoveryParsedVAA.emitter_address,
|
||||
recoveryParsedVAA.emitter_chain
|
||||
)) ||
|
||||
""
|
||||
}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
label="Sequence"
|
||||
disabled
|
||||
value={recoveryParsedVAA?.sequence || ""}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
label="Timestamp"
|
||||
disabled
|
||||
value={
|
||||
(recoveryParsedVAA &&
|
||||
new Date(recoveryParsedVAA.timestamp * 1000).toLocaleString()) ||
|
||||
""
|
||||
}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<Box my={4}>
|
||||
<Divider />
|
||||
</Box>
|
||||
<TextField
|
||||
label="Origin Chain"
|
||||
disabled
|
||||
value={parsedPayload?.originChain.toString() || ""}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
label="Origin Token Address"
|
||||
disabled
|
||||
value={
|
||||
(parsedPayload &&
|
||||
hexToNativeString(
|
||||
parsedPayload.originAddress,
|
||||
parsedPayload.originChain
|
||||
)) ||
|
||||
""
|
||||
}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
label="Origin Token ID"
|
||||
disabled
|
||||
value={parsedPayload?.tokenId || ""}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
label="Target Chain"
|
||||
disabled
|
||||
value={parsedPayload?.targetChain.toString() || ""}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
label="Target Address"
|
||||
disabled
|
||||
value={
|
||||
(parsedPayload &&
|
||||
hexToNativeString(
|
||||
parsedPayload.targetAddress,
|
||||
parsedPayload.targetChain
|
||||
)) ||
|
||||
""
|
||||
}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<Box my={4}>
|
||||
<Divider />
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} variant="outlined" color="default">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleRecoverClick}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={!enableRecovery || disabled}
|
||||
>
|
||||
Recover
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Recovery({
|
||||
open,
|
||||
setOpen,
|
||||
disabled,
|
||||
}: {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
disabled: boolean;
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
const handleOpenClick = useCallback(() => {
|
||||
setOpen(true);
|
||||
}, [setOpen]);
|
||||
const handleCloseClick = useCallback(() => {
|
||||
setOpen(false);
|
||||
}, [setOpen]);
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="Open Recovery Dialog">
|
||||
<Fab className={classes.fab} onClick={handleOpenClick}>
|
||||
<Restore />
|
||||
</Fab>
|
||||
</Tooltip>
|
||||
<Dialog open={open} onClose={handleCloseClick} maxWidth="md" fullWidth>
|
||||
<DialogTitle>Recovery</DialogTitle>
|
||||
<RecoveryDialogContent onClose={handleCloseClick} disabled={disabled} />
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
import { Button, makeStyles, MenuItem, TextField } from "@material-ui/core";
|
||||
import { Restore, VerifiedUser } from "@material-ui/icons";
|
||||
import { VerifiedUser } from "@material-ui/icons";
|
||||
import { Alert } from "@material-ui/lab";
|
||||
import { useCallback } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useBetaContext } from "../../contexts/BetaContext";
|
||||
import useIsWalletReady from "../../hooks/useIsWalletReady";
|
||||
import { incrementStep, setSourceChain } from "../../store/nftSlice";
|
||||
import {
|
||||
|
@ -13,7 +14,7 @@ import {
|
|||
selectNFTSourceChain,
|
||||
selectNFTSourceError,
|
||||
} from "../../store/selectors";
|
||||
import { CHAINS_WITH_NFT_SUPPORT } from "../../utils/consts";
|
||||
import { BETA_CHAINS, CHAINS_WITH_NFT_SUPPORT } from "../../utils/consts";
|
||||
import { isEVMChain } from "../../utils/ethereum";
|
||||
import ButtonWithLoader from "../ButtonWithLoader";
|
||||
import KeyAndBalance from "../KeyAndBalance";
|
||||
|
@ -25,21 +26,12 @@ const useStyles = makeStyles((theme) => ({
|
|||
transferField: {
|
||||
marginTop: theme.spacing(5),
|
||||
},
|
||||
buttonWrapper: {
|
||||
textAlign: "right",
|
||||
},
|
||||
nftOriginVerifierButton: {
|
||||
marginTop: theme.spacing(0.5),
|
||||
},
|
||||
}));
|
||||
|
||||
function Source({
|
||||
setIsRecoveryOpen,
|
||||
}: {
|
||||
setIsRecoveryOpen: (open: boolean) => void;
|
||||
}) {
|
||||
function Source() {
|
||||
const classes = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const isBeta = useBetaContext();
|
||||
const sourceChain = useSelector(selectNFTSourceChain);
|
||||
const uiAmountString = useSelector(selectNFTSourceBalanceString);
|
||||
const error = useSelector(selectNFTSourceError);
|
||||
|
@ -62,39 +54,29 @@ function Source({
|
|||
Select an NFT to send through the Wormhole NFT Bridge.
|
||||
<div style={{ flexGrow: 1 }} />
|
||||
<div>
|
||||
<div className={classes.buttonWrapper}>
|
||||
<Button
|
||||
onClick={() => setIsRecoveryOpen(true)}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
endIcon={<Restore />}
|
||||
>
|
||||
Perform Recovery
|
||||
</Button>
|
||||
</div>
|
||||
<div className={classes.buttonWrapper}>
|
||||
<Button
|
||||
component={Link}
|
||||
to="/nft-origin-verifier"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
endIcon={<VerifiedUser />}
|
||||
className={classes.nftOriginVerifierButton}
|
||||
>
|
||||
NFT Origin Verifier
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</StepDescription>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
select
|
||||
fullWidth
|
||||
value={sourceChain}
|
||||
onChange={handleSourceChange}
|
||||
disabled={shouldLockFields}
|
||||
>
|
||||
{CHAINS_WITH_NFT_SUPPORT.map(({ id, name }) => (
|
||||
{CHAINS_WITH_NFT_SUPPORT.filter(({ id }) =>
|
||||
isBeta ? true : !BETA_CHAINS.includes(id)
|
||||
).map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
|
|
|
@ -9,6 +9,7 @@ import { PublicKey } from "@solana/web3.js";
|
|||
import { BigNumber, ethers } from "ethers";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useBetaContext } from "../../contexts/BetaContext";
|
||||
import useIsWalletReady from "../../hooks/useIsWalletReady";
|
||||
import useSyncTargetAddress from "../../hooks/useSyncTargetAddress";
|
||||
import { EthGasEstimateSummary } from "../../hooks/useTransactionFees";
|
||||
|
@ -26,7 +27,11 @@ import {
|
|||
selectNFTTargetChain,
|
||||
selectNFTTargetError,
|
||||
} from "../../store/selectors";
|
||||
import { CHAINS_BY_ID, CHAINS_WITH_NFT_SUPPORT } from "../../utils/consts";
|
||||
import {
|
||||
BETA_CHAINS,
|
||||
CHAINS_BY_ID,
|
||||
CHAINS_WITH_NFT_SUPPORT,
|
||||
} from "../../utils/consts";
|
||||
import { isEVMChain } from "../../utils/ethereum";
|
||||
import ButtonWithLoader from "../ButtonWithLoader";
|
||||
import KeyAndBalance from "../KeyAndBalance";
|
||||
|
@ -46,6 +51,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
function Target() {
|
||||
const classes = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const isBeta = useBetaContext();
|
||||
const sourceChain = useSelector(selectNFTSourceChain);
|
||||
const chains = useMemo(
|
||||
() => CHAINS_WITH_NFT_SUPPORT.filter((c) => c.id !== sourceChain),
|
||||
|
@ -91,10 +97,13 @@ function Target() {
|
|||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={targetChain}
|
||||
onChange={handleTargetChange}
|
||||
>
|
||||
{chains.map(({ id, name }) => (
|
||||
{chains
|
||||
.filter(({ id }) => (isBeta ? true : !BETA_CHAINS.includes(id)))
|
||||
.map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
|
@ -104,6 +113,7 @@ function Target() {
|
|||
<TextField
|
||||
label="Recipient Address"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
className={classes.transferField}
|
||||
value={readableTargetAddress}
|
||||
disabled={true}
|
||||
|
@ -113,12 +123,14 @@ function Target() {
|
|||
<TextField
|
||||
label="Token Address"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
className={classes.transferField}
|
||||
value={targetAsset || ""}
|
||||
disabled={true}
|
||||
/>
|
||||
{isEVMChain(targetChain) ? (
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="TokenId"
|
||||
fullWidth
|
||||
className={classes.transferField}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import {
|
||||
Container,
|
||||
makeStyles,
|
||||
Step,
|
||||
StepButton,
|
||||
StepContent,
|
||||
Stepper,
|
||||
} from "@material-ui/core";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import useCheckIfWormholeWrapped from "../../hooks/useCheckIfWormholeWrapped";
|
||||
import useFetchTargetAsset from "../../hooks/useFetchTargetAsset";
|
||||
import { setStep } from "../../store/nftSlice";
|
||||
import {
|
||||
selectNFTActiveStep,
|
||||
selectNFTIsRedeemComplete,
|
||||
|
@ -17,8 +17,6 @@ import {
|
|||
selectNFTIsSendComplete,
|
||||
selectNFTIsSending,
|
||||
} from "../../store/selectors";
|
||||
import { setStep } from "../../store/nftSlice";
|
||||
import Recovery from "./Recovery";
|
||||
import Redeem from "./Redeem";
|
||||
import RedeemPreview from "./RedeemPreview";
|
||||
import Send from "./Send";
|
||||
|
@ -27,19 +25,10 @@ import Source from "./Source";
|
|||
import SourcePreview from "./SourcePreview";
|
||||
import Target from "./Target";
|
||||
import TargetPreview from "./TargetPreview";
|
||||
import { COLORS } from "../../muiTheme";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
rootContainer: {
|
||||
backgroundColor: COLORS.nearBlackWithMinorTransparency,
|
||||
},
|
||||
}));
|
||||
|
||||
function NFT() {
|
||||
const classes = useStyles();
|
||||
useCheckIfWormholeWrapped(true);
|
||||
useFetchTargetAsset(true);
|
||||
const [isRecoveryOpen, setIsRecoveryOpen] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
const activeStep = useSelector(selectNFTActiveStep);
|
||||
const isSending = useSelector(selectNFTIsSending);
|
||||
|
@ -58,22 +47,14 @@ function NFT() {
|
|||
}, [preventNavigation]);
|
||||
return (
|
||||
<Container maxWidth="md">
|
||||
<Stepper
|
||||
activeStep={activeStep}
|
||||
orientation="vertical"
|
||||
className={classes.rootContainer}
|
||||
>
|
||||
<Stepper activeStep={activeStep} orientation="vertical">
|
||||
<Step
|
||||
expanded={activeStep >= 0}
|
||||
disabled={preventNavigation || isRedeemComplete}
|
||||
>
|
||||
<StepButton onClick={() => dispatch(setStep(0))}>Source</StepButton>
|
||||
<StepContent>
|
||||
{activeStep === 0 ? (
|
||||
<Source setIsRecoveryOpen={setIsRecoveryOpen} />
|
||||
) : (
|
||||
<SourcePreview />
|
||||
)}
|
||||
{activeStep === 0 ? <Source /> : <SourcePreview />}
|
||||
</StepContent>
|
||||
</Step>
|
||||
<Step
|
||||
|
@ -103,11 +84,6 @@ function NFT() {
|
|||
</StepContent>
|
||||
</Step>
|
||||
</Stepper>
|
||||
<Recovery
|
||||
open={isRecoveryOpen}
|
||||
setOpen={setIsRecoveryOpen}
|
||||
disabled={preventNavigation}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -24,12 +24,14 @@ import { Launch } from "@material-ui/icons";
|
|||
import { Alert } from "@material-ui/lab";
|
||||
import { Connection } from "@solana/web3.js";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useBetaContext } from "../contexts/BetaContext";
|
||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||
import useIsWalletReady from "../hooks/useIsWalletReady";
|
||||
import { getMetaplexData } from "../hooks/useMetaplexData";
|
||||
import { COLORS } from "../muiTheme";
|
||||
import { NFTParsedTokenAccount } from "../store/nftSlice";
|
||||
import {
|
||||
BETA_CHAINS,
|
||||
CHAINS_BY_ID,
|
||||
CHAINS_WITH_NFT_SUPPORT,
|
||||
getNFTBridgeAddressForChain,
|
||||
|
@ -65,11 +67,10 @@ const useStyles = makeStyles((theme) => ({
|
|||
WebkitTextFillColor: "transparent",
|
||||
MozBackgroundClip: "text",
|
||||
MozTextFillColor: "transparent",
|
||||
filter: `drop-shadow( 0px 0px 8px ${COLORS.nearBlack}) drop-shadow( 0px 0px 14px ${COLORS.nearBlack}) drop-shadow( 0px 0px 24px ${COLORS.nearBlack})`,
|
||||
// filter: `drop-shadow( 0px 0px 8px ${COLORS.nearBlack}) drop-shadow( 0px 0px 14px ${COLORS.nearBlack}) drop-shadow( 0px 0px 24px ${COLORS.nearBlack})`,
|
||||
},
|
||||
mainCard: {
|
||||
padding: theme.spacing(1),
|
||||
borderRadius: "5px",
|
||||
backgroundColor: COLORS.nearBlackWithMinorTransparency,
|
||||
},
|
||||
originHeader: {
|
||||
|
@ -89,6 +90,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
|
||||
export default function NFTOriginVerifier() {
|
||||
const classes = useStyles();
|
||||
const isBeta = useBetaContext();
|
||||
const { provider, signerAddress } = useEthereumProvider();
|
||||
const [lookupChain, setLookupChain] = useState(CHAIN_ID_ETH);
|
||||
const { isReady, statusMessage } = useIsWalletReady(lookupChain);
|
||||
|
@ -232,7 +234,7 @@ export default function NFTOriginVerifier() {
|
|||
<div>
|
||||
<Container maxWidth="md">
|
||||
<div className={classes.centeredContainer}>
|
||||
<Typography variant="h2" component="h1" className={classes.header}>
|
||||
<Typography variant="h1" className={classes.header}>
|
||||
<span className={classes.linearGradient}>NFT Origin Verifier</span>
|
||||
</Typography>
|
||||
</div>
|
||||
|
@ -245,13 +247,16 @@ export default function NFTOriginVerifier() {
|
|||
</Alert>
|
||||
<TextField
|
||||
select
|
||||
variant="outlined"
|
||||
label="Chain"
|
||||
value={lookupChain}
|
||||
onChange={handleChainChange}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
>
|
||||
{CHAINS_WITH_NFT_SUPPORT.map(({ id, name }) => (
|
||||
{CHAINS_WITH_NFT_SUPPORT.filter(({ id }) =>
|
||||
isBeta ? true : !BETA_CHAINS.includes(id)
|
||||
).map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
|
@ -262,6 +267,7 @@ export default function NFTOriginVerifier() {
|
|||
) : null}
|
||||
<TextField
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
label="Paste an address"
|
||||
value={lookupAsset}
|
||||
|
@ -270,6 +276,7 @@ export default function NFTOriginVerifier() {
|
|||
{isEVMChain(lookupChain) ? (
|
||||
<TextField
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
label="Paste a tokenId"
|
||||
value={lookupTokenId}
|
||||
|
|
|
@ -0,0 +1,557 @@
|
|||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_SOLANA,
|
||||
CHAIN_ID_TERRA,
|
||||
getEmitterAddressEth,
|
||||
getEmitterAddressSolana,
|
||||
getEmitterAddressTerra,
|
||||
hexToNativeString,
|
||||
hexToUint8Array,
|
||||
parseNFTPayload,
|
||||
parseSequenceFromLogEth,
|
||||
parseSequenceFromLogSolana,
|
||||
parseSequenceFromLogTerra,
|
||||
parseTransferPayload,
|
||||
uint8ArrayToHex,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Box,
|
||||
Card,
|
||||
CircularProgress,
|
||||
Container,
|
||||
Divider,
|
||||
makeStyles,
|
||||
MenuItem,
|
||||
TextField,
|
||||
} from "@material-ui/core";
|
||||
import { ExpandMore } from "@material-ui/icons";
|
||||
import { Alert } from "@material-ui/lab";
|
||||
import { Connection } from "@solana/web3.js";
|
||||
import { LCDClient } from "@terra-money/terra.js";
|
||||
import { ethers } from "ethers";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useHistory } from "react-router";
|
||||
import { useBetaContext } from "../contexts/BetaContext";
|
||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||
import { COLORS } from "../muiTheme";
|
||||
import {
|
||||
setSignedVAAHex as setNFTSignedVAAHex,
|
||||
setStep as setNFTStep,
|
||||
setTargetChain as setNFTTargetChain,
|
||||
} from "../store/nftSlice";
|
||||
import {
|
||||
setSignedVAAHex,
|
||||
setStep,
|
||||
setTargetChain,
|
||||
} from "../store/transferSlice";
|
||||
import {
|
||||
BETA_CHAINS,
|
||||
CHAINS,
|
||||
CHAINS_WITH_NFT_SUPPORT,
|
||||
getBridgeAddressForChain,
|
||||
getNFTBridgeAddressForChain,
|
||||
getTokenBridgeAddressForChain,
|
||||
SOLANA_HOST,
|
||||
SOL_NFT_BRIDGE_ADDRESS,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
TERRA_HOST,
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
} from "../utils/consts";
|
||||
import { isEVMChain } from "../utils/ethereum";
|
||||
import { getSignedVAAWithRetry } from "../utils/getSignedVAAWithRetry";
|
||||
import parseError from "../utils/parseError";
|
||||
import ButtonWithLoader from "./ButtonWithLoader";
|
||||
import KeyAndBalance from "./KeyAndBalance";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
mainCard: {
|
||||
padding: theme.spacing(2),
|
||||
backgroundColor: COLORS.nearBlackWithMinorTransparency,
|
||||
},
|
||||
advancedContainer: {
|
||||
padding: theme.spacing(2, 0),
|
||||
},
|
||||
}));
|
||||
|
||||
async function evm(
|
||||
provider: ethers.providers.Web3Provider,
|
||||
tx: string,
|
||||
enqueueSnackbar: any,
|
||||
chainId: ChainId,
|
||||
nft: boolean
|
||||
) {
|
||||
try {
|
||||
const receipt = await provider.getTransactionReceipt(tx);
|
||||
const sequence = parseSequenceFromLogEth(
|
||||
receipt,
|
||||
getBridgeAddressForChain(chainId)
|
||||
);
|
||||
const emitterAddress = getEmitterAddressEth(
|
||||
nft
|
||||
? getNFTBridgeAddressForChain(chainId)
|
||||
: getTokenBridgeAddressForChain(chainId)
|
||||
);
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
chainId,
|
||||
emitterAddress,
|
||||
sequence.toString(),
|
||||
WORMHOLE_RPC_HOSTS.length
|
||||
);
|
||||
return { vaa: uint8ArrayToHex(vaaBytes), error: null };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
return { vaa: null, error: parseError(e) };
|
||||
}
|
||||
}
|
||||
|
||||
async function solana(tx: string, enqueueSnackbar: any, nft: boolean) {
|
||||
try {
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const info = await connection.getTransaction(tx);
|
||||
if (!info) {
|
||||
throw new Error("An error occurred while fetching the transaction info");
|
||||
}
|
||||
const sequence = parseSequenceFromLogSolana(info);
|
||||
const emitterAddress = await getEmitterAddressSolana(
|
||||
nft ? SOL_NFT_BRIDGE_ADDRESS : SOL_TOKEN_BRIDGE_ADDRESS
|
||||
);
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_SOLANA,
|
||||
emitterAddress,
|
||||
sequence.toString(),
|
||||
WORMHOLE_RPC_HOSTS.length
|
||||
);
|
||||
return { vaa: uint8ArrayToHex(vaaBytes), error: null };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
return { vaa: null, error: parseError(e) };
|
||||
}
|
||||
}
|
||||
|
||||
async function terra(tx: string, enqueueSnackbar: any) {
|
||||
try {
|
||||
const lcd = new LCDClient(TERRA_HOST);
|
||||
const info = await lcd.tx.txInfo(tx);
|
||||
const sequence = parseSequenceFromLogTerra(info);
|
||||
if (!sequence) {
|
||||
throw new Error("Sequence not found");
|
||||
}
|
||||
const emitterAddress = await getEmitterAddressTerra(
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS
|
||||
);
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_TERRA,
|
||||
emitterAddress,
|
||||
sequence,
|
||||
WORMHOLE_RPC_HOSTS.length
|
||||
);
|
||||
return { vaa: uint8ArrayToHex(vaaBytes), error: null };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
return { vaa: null, error: parseError(e) };
|
||||
}
|
||||
}
|
||||
|
||||
export default function Recovery() {
|
||||
const classes = useStyles();
|
||||
const isBeta = useBetaContext();
|
||||
const { push } = useHistory();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const dispatch = useDispatch();
|
||||
const { provider } = useEthereumProvider();
|
||||
const [type, setType] = useState("Token");
|
||||
const isNFT = type === "NFT";
|
||||
const [recoverySourceChain, setRecoverySourceChain] =
|
||||
useState(CHAIN_ID_SOLANA);
|
||||
const [recoverySourceTx, setRecoverySourceTx] = useState("");
|
||||
const [recoverySourceTxIsLoading, setRecoverySourceTxIsLoading] =
|
||||
useState(false);
|
||||
const [recoverySourceTxError, setRecoverySourceTxError] = useState("");
|
||||
const [recoverySignedVAA, setRecoverySignedVAA] = useState("");
|
||||
const [recoveryParsedVAA, setRecoveryParsedVAA] = useState<any>(null);
|
||||
const parsedPayload = useMemo(() => {
|
||||
try {
|
||||
return recoveryParsedVAA?.payload
|
||||
? isNFT
|
||||
? parseNFTPayload(
|
||||
Buffer.from(new Uint8Array(recoveryParsedVAA.payload))
|
||||
)
|
||||
: parseTransferPayload(
|
||||
Buffer.from(new Uint8Array(recoveryParsedVAA.payload))
|
||||
)
|
||||
: null;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return null;
|
||||
}
|
||||
}, [recoveryParsedVAA, isNFT]);
|
||||
useEffect(() => {
|
||||
if (recoverySourceTx) {
|
||||
let cancelled = false;
|
||||
if (isEVMChain(recoverySourceChain) && provider) {
|
||||
setRecoverySourceTxError("");
|
||||
setRecoverySourceTxIsLoading(true);
|
||||
(async () => {
|
||||
const { vaa, error } = await evm(
|
||||
provider,
|
||||
recoverySourceTx,
|
||||
enqueueSnackbar,
|
||||
recoverySourceChain,
|
||||
isNFT
|
||||
);
|
||||
if (!cancelled) {
|
||||
setRecoverySourceTxIsLoading(false);
|
||||
if (vaa) {
|
||||
setRecoverySignedVAA(vaa);
|
||||
}
|
||||
if (error) {
|
||||
setRecoverySourceTxError(error);
|
||||
}
|
||||
}
|
||||
})();
|
||||
} else if (recoverySourceChain === CHAIN_ID_SOLANA) {
|
||||
setRecoverySourceTxError("");
|
||||
setRecoverySourceTxIsLoading(true);
|
||||
(async () => {
|
||||
const { vaa, error } = await solana(
|
||||
recoverySourceTx,
|
||||
enqueueSnackbar,
|
||||
isNFT
|
||||
);
|
||||
if (!cancelled) {
|
||||
setRecoverySourceTxIsLoading(false);
|
||||
if (vaa) {
|
||||
setRecoverySignedVAA(vaa);
|
||||
}
|
||||
if (error) {
|
||||
setRecoverySourceTxError(error);
|
||||
}
|
||||
}
|
||||
})();
|
||||
} else if (recoverySourceChain === CHAIN_ID_TERRA) {
|
||||
setRecoverySourceTxError("");
|
||||
setRecoverySourceTxIsLoading(true);
|
||||
(async () => {
|
||||
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, enqueueSnackbar, isNFT]);
|
||||
const handleTypeChange = useCallback((event) => {
|
||||
setRecoverySourceChain((prevChain) =>
|
||||
event.target.value === "NFT" &&
|
||||
!CHAINS_WITH_NFT_SUPPORT.find((chain) => chain.id === prevChain)
|
||||
? CHAIN_ID_SOLANA
|
||||
: prevChain
|
||||
);
|
||||
setType(event.target.value);
|
||||
}, []);
|
||||
const handleSourceChainChange = useCallback((event) => {
|
||||
setRecoverySourceTx("");
|
||||
setRecoverySourceChain(event.target.value);
|
||||
}, []);
|
||||
const handleSourceTxChange = useCallback((event) => {
|
||||
setRecoverySourceTx(event.target.value.trim());
|
||||
}, []);
|
||||
const handleSignedVAAChange = useCallback((event) => {
|
||||
setRecoverySignedVAA(event.target.value.trim());
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
if (recoverySignedVAA) {
|
||||
(async () => {
|
||||
try {
|
||||
const { parse_vaa } = await import(
|
||||
"@certusone/wormhole-sdk/lib/solana/core/bridge"
|
||||
);
|
||||
const parsedVAA = parse_vaa(hexToUint8Array(recoverySignedVAA));
|
||||
if (!cancelled) {
|
||||
setRecoveryParsedVAA(parsedVAA);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if (!cancelled) {
|
||||
setRecoveryParsedVAA(null);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [recoverySignedVAA]);
|
||||
const parsedPayloadTargetChain = parsedPayload?.targetChain;
|
||||
const enableRecovery = recoverySignedVAA && parsedPayloadTargetChain;
|
||||
const handleRecoverClick = useCallback(() => {
|
||||
if (enableRecovery && recoverySignedVAA && parsedPayloadTargetChain) {
|
||||
// TODO: make recovery reducer
|
||||
if (isNFT) {
|
||||
dispatch(setNFTSignedVAAHex(recoverySignedVAA));
|
||||
dispatch(setNFTTargetChain(parsedPayloadTargetChain));
|
||||
dispatch(setNFTStep(3));
|
||||
push("/nft");
|
||||
} else {
|
||||
dispatch(setSignedVAAHex(recoverySignedVAA));
|
||||
dispatch(setTargetChain(parsedPayloadTargetChain));
|
||||
dispatch(setStep(3));
|
||||
push("/transfer");
|
||||
}
|
||||
}
|
||||
}, [
|
||||
dispatch,
|
||||
enableRecovery,
|
||||
recoverySignedVAA,
|
||||
parsedPayloadTargetChain,
|
||||
isNFT,
|
||||
push,
|
||||
]);
|
||||
return (
|
||||
<Container maxWidth="md">
|
||||
<Card className={classes.mainCard}>
|
||||
<Alert severity="info">
|
||||
If you have sent your tokens but have not redeemed them, you may paste
|
||||
in the Source Transaction ID (from Step 3) to resume your transfer.
|
||||
</Alert>
|
||||
<TextField
|
||||
select
|
||||
variant="outlined"
|
||||
label="Type"
|
||||
disabled={!!recoverySignedVAA}
|
||||
value={type}
|
||||
onChange={handleTypeChange}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
>
|
||||
<MenuItem value="Token">Token</MenuItem>
|
||||
<MenuItem value="NFT">NFT</MenuItem>
|
||||
</TextField>
|
||||
<TextField
|
||||
select
|
||||
variant="outlined"
|
||||
label="Source Chain"
|
||||
disabled={!!recoverySignedVAA}
|
||||
value={recoverySourceChain}
|
||||
onChange={handleSourceChainChange}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
>
|
||||
{(isNFT ? CHAINS_WITH_NFT_SUPPORT : CHAINS)
|
||||
.filter(({ id }) => (isBeta ? true : !BETA_CHAINS.includes(id)))
|
||||
.map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
{isEVMChain(recoverySourceChain) ? (
|
||||
<KeyAndBalance chainId={recoverySourceChain} />
|
||||
) : null}
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Source Tx (paste here)"
|
||||
disabled={!!recoverySignedVAA || recoverySourceTxIsLoading}
|
||||
value={recoverySourceTx}
|
||||
onChange={handleSourceTxChange}
|
||||
error={!!recoverySourceTxError}
|
||||
helperText={recoverySourceTxError}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<ButtonWithLoader
|
||||
onClick={handleRecoverClick}
|
||||
disabled={!enableRecovery}
|
||||
showLoader={recoverySourceTxIsLoading}
|
||||
>
|
||||
Recover
|
||||
</ButtonWithLoader>
|
||||
<div className={classes.advancedContainer}>
|
||||
<Accordion>
|
||||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||||
Advanced
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<div>
|
||||
<Box position="relative">
|
||||
<TextField
|
||||
variant="outlined"
|
||||
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>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Emitter Chain"
|
||||
disabled
|
||||
value={recoveryParsedVAA?.emitter_chain || ""}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Emitter Address"
|
||||
disabled
|
||||
value={
|
||||
(recoveryParsedVAA &&
|
||||
hexToNativeString(
|
||||
recoveryParsedVAA.emitter_address,
|
||||
recoveryParsedVAA.emitter_chain
|
||||
)) ||
|
||||
""
|
||||
}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Sequence"
|
||||
disabled
|
||||
value={recoveryParsedVAA?.sequence || ""}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Timestamp"
|
||||
disabled
|
||||
value={
|
||||
(recoveryParsedVAA &&
|
||||
new Date(
|
||||
recoveryParsedVAA.timestamp * 1000
|
||||
).toLocaleString()) ||
|
||||
""
|
||||
}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<Box my={4}>
|
||||
<Divider />
|
||||
</Box>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Origin Chain"
|
||||
disabled
|
||||
value={parsedPayload?.originChain.toString() || ""}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Origin Token Address"
|
||||
disabled
|
||||
value={
|
||||
(parsedPayload &&
|
||||
hexToNativeString(
|
||||
parsedPayload.originAddress,
|
||||
parsedPayload.originChain
|
||||
)) ||
|
||||
""
|
||||
}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
{isNFT ? (
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Origin Token ID"
|
||||
disabled
|
||||
// @ts-ignore
|
||||
value={parsedPayload?.tokenId || ""}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
) : null}
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Target Chain"
|
||||
disabled
|
||||
value={parsedPayload?.targetChain.toString() || ""}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Target Chain"
|
||||
disabled
|
||||
value={parsedPayload?.targetChain.toString() || ""}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Target Address"
|
||||
disabled
|
||||
value={
|
||||
(parsedPayload &&
|
||||
hexToNativeString(
|
||||
parsedPayload.targetAddress,
|
||||
parsedPayload.targetChain
|
||||
)) ||
|
||||
""
|
||||
}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
{isNFT ? null : (
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Amount"
|
||||
disabled
|
||||
// @ts-ignore
|
||||
value={parsedPayload?.amount.toString() || ""}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</div>
|
||||
</Card>
|
||||
</Container>
|
||||
);
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
import { Button, makeStyles, Tooltip } from "@material-ui/core";
|
||||
import { LinkOff } from "@material-ui/icons";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
button: {
|
||||
display: "block",
|
||||
display: "flex",
|
||||
margin: `${theme.spacing(1)}px auto`,
|
||||
width: "100%",
|
||||
maxWidth: 400,
|
||||
|
@ -25,11 +26,12 @@ const ToggleConnectedButton = ({
|
|||
return connected ? (
|
||||
<Tooltip title={pk}>
|
||||
<Button
|
||||
color="secondary"
|
||||
color="primary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={disconnect}
|
||||
className={classes.button}
|
||||
startIcon={<LinkOff />}
|
||||
>
|
||||
Disconnect {pk.substring(0, is0x ? 6 : 3)}...
|
||||
{pk.substr(pk.length - (is0x ? 4 : 3))}
|
||||
|
|
|
@ -584,6 +584,7 @@ export default function EthereumSourceTokenSelector(
|
|||
) : advancedMode ? (
|
||||
<>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
label="Enter an asset address"
|
||||
value={advancedModeHolderString}
|
||||
|
@ -602,6 +603,7 @@ export default function EthereumSourceTokenSelector(
|
|||
/>
|
||||
{nft ? (
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
label="Enter a tokenId"
|
||||
value={advancedModeHolderTokenIdRaw}
|
||||
|
|
|
@ -113,6 +113,7 @@ export const TokenSelector = (props: TokenSelectorProps) => {
|
|||
/>
|
||||
) : (
|
||||
<TextField
|
||||
variant="outlined"
|
||||
placeholder="Asset"
|
||||
fullWidth
|
||||
value={"Not Implemented"}
|
||||
|
|
|
@ -296,6 +296,7 @@ export default function TerraSourceTokenSelector(
|
|||
<>
|
||||
<TextField
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
label="Enter an asset address"
|
||||
value={advancedModeHolderString}
|
||||
onChange={handleOnChange}
|
||||
|
|
|
@ -1,512 +0,0 @@
|
|||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_SOLANA,
|
||||
CHAIN_ID_TERRA,
|
||||
getEmitterAddressEth,
|
||||
getEmitterAddressSolana,
|
||||
getEmitterAddressTerra,
|
||||
hexToNativeString,
|
||||
hexToUint8Array,
|
||||
parseSequenceFromLogEth,
|
||||
parseSequenceFromLogSolana,
|
||||
parseSequenceFromLogTerra,
|
||||
parseTransferPayload,
|
||||
uint8ArrayToHex,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Divider,
|
||||
Fab,
|
||||
makeStyles,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { Restore } from "@material-ui/icons";
|
||||
import { Alert } from "@material-ui/lab";
|
||||
import { Connection } from "@solana/web3.js";
|
||||
import { LCDClient } from "@terra-money/terra.js";
|
||||
import { 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";
|
||||
import {
|
||||
selectTransferSignedVAAHex,
|
||||
selectTransferSourceChain,
|
||||
} from "../../store/selectors";
|
||||
import {
|
||||
setSignedVAAHex,
|
||||
setStep,
|
||||
setTargetChain,
|
||||
} from "../../store/transferSlice";
|
||||
import {
|
||||
CHAINS,
|
||||
getBridgeAddressForChain,
|
||||
getTokenBridgeAddressForChain,
|
||||
SOLANA_HOST,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
TERRA_HOST,
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
} from "../../utils/consts";
|
||||
import { isEVMChain } from "../../utils/ethereum";
|
||||
import { getSignedVAAWithRetry } from "../../utils/getSignedVAAWithRetry";
|
||||
import parseError from "../../utils/parseError";
|
||||
import KeyAndBalance from "../KeyAndBalance";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
fab: {
|
||||
position: "fixed",
|
||||
bottom: theme.spacing(2),
|
||||
right: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
|
||||
async function evm(
|
||||
provider: ethers.providers.Web3Provider,
|
||||
tx: string,
|
||||
enqueueSnackbar: any,
|
||||
chainId: ChainId
|
||||
) {
|
||||
try {
|
||||
const receipt = await provider.getTransactionReceipt(tx);
|
||||
const sequence = parseSequenceFromLogEth(
|
||||
receipt,
|
||||
getBridgeAddressForChain(chainId)
|
||||
);
|
||||
const emitterAddress = getEmitterAddressEth(
|
||||
getTokenBridgeAddressForChain(chainId)
|
||||
);
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
chainId,
|
||||
emitterAddress,
|
||||
sequence.toString(),
|
||||
WORMHOLE_RPC_HOSTS.length
|
||||
);
|
||||
return { vaa: uint8ArrayToHex(vaaBytes), error: null };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
return { vaa: null, error: parseError(e) };
|
||||
}
|
||||
}
|
||||
|
||||
async function solana(tx: string, enqueueSnackbar: any) {
|
||||
try {
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const info = await connection.getTransaction(tx);
|
||||
if (!info) {
|
||||
throw new Error("An error occurred while fetching the transaction info");
|
||||
}
|
||||
const sequence = parseSequenceFromLogSolana(info);
|
||||
const emitterAddress = await getEmitterAddressSolana(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS
|
||||
);
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_SOLANA,
|
||||
emitterAddress,
|
||||
sequence.toString(),
|
||||
WORMHOLE_RPC_HOSTS.length
|
||||
);
|
||||
return { vaa: uint8ArrayToHex(vaaBytes), error: null };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
return { vaa: null, error: parseError(e) };
|
||||
}
|
||||
}
|
||||
|
||||
async function terra(tx: string, enqueueSnackbar: any) {
|
||||
try {
|
||||
const lcd = new LCDClient(TERRA_HOST);
|
||||
const info = await lcd.tx.txInfo(tx);
|
||||
const sequence = parseSequenceFromLogTerra(info);
|
||||
if (!sequence) {
|
||||
throw new Error("Sequence not found");
|
||||
}
|
||||
const emitterAddress = await getEmitterAddressTerra(
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS
|
||||
);
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_TERRA,
|
||||
emitterAddress,
|
||||
sequence,
|
||||
WORMHOLE_RPC_HOSTS.length
|
||||
);
|
||||
return { vaa: uint8ArrayToHex(vaaBytes), error: null };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
return { vaa: null, error: parseError(e) };
|
||||
}
|
||||
}
|
||||
|
||||
function RecoveryDialogContent({
|
||||
onClose,
|
||||
disabled,
|
||||
}: {
|
||||
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);
|
||||
useEffect(() => {
|
||||
if (!recoverySignedVAA) {
|
||||
setRecoverySourceTx("");
|
||||
setRecoverySourceChain(currentSourceChain);
|
||||
}
|
||||
}, [recoverySignedVAA, currentSourceChain]);
|
||||
useEffect(() => {
|
||||
if (recoverySourceTx) {
|
||||
let cancelled = false;
|
||||
if (isEVMChain(recoverySourceChain) && provider) {
|
||||
setRecoverySourceTxError("");
|
||||
setRecoverySourceTxIsLoading(true);
|
||||
(async () => {
|
||||
const { vaa, error } = await evm(
|
||||
provider,
|
||||
recoverySourceTx,
|
||||
enqueueSnackbar,
|
||||
recoverySourceChain
|
||||
);
|
||||
if (!cancelled) {
|
||||
setRecoverySourceTxIsLoading(false);
|
||||
if (vaa) {
|
||||
setRecoverySignedVAA(vaa);
|
||||
}
|
||||
if (error) {
|
||||
setRecoverySourceTxError(error);
|
||||
}
|
||||
}
|
||||
})();
|
||||
} else if (recoverySourceChain === CHAIN_ID_SOLANA) {
|
||||
setRecoverySourceTxError("");
|
||||
setRecoverySourceTxIsLoading(true);
|
||||
(async () => {
|
||||
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, error } = await terra(recoverySourceTx, enqueueSnackbar);
|
||||
if (!cancelled) {
|
||||
setRecoverySourceTxIsLoading(false);
|
||||
if (vaa) {
|
||||
setRecoverySignedVAA(vaa);
|
||||
}
|
||||
if (error) {
|
||||
setRecoverySourceTxError(error);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}
|
||||
}, [recoverySourceChain, recoverySourceTx, provider, enqueueSnackbar]);
|
||||
useEffect(() => {
|
||||
setRecoverySignedVAA(currentSignedVAA);
|
||||
}, [currentSignedVAA]);
|
||||
const handleSourceChainChange = useCallback((event) => {
|
||||
setRecoverySourceTx("");
|
||||
setRecoverySourceChain(event.target.value);
|
||||
}, []);
|
||||
const handleSourceTxChange = useCallback((event) => {
|
||||
setRecoverySourceTx(event.target.value.trim());
|
||||
}, []);
|
||||
const handleSignedVAAChange = useCallback((event) => {
|
||||
setRecoverySignedVAA(event.target.value.trim());
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
if (recoverySignedVAA) {
|
||||
(async () => {
|
||||
try {
|
||||
const { parse_vaa } = await import(
|
||||
"@certusone/wormhole-sdk/lib/solana/core/bridge"
|
||||
);
|
||||
const parsedVAA = parse_vaa(hexToUint8Array(recoverySignedVAA));
|
||||
if (!cancelled) {
|
||||
setRecoveryParsedVAA(parsedVAA);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if (!cancelled) {
|
||||
setRecoveryParsedVAA(null);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [recoverySignedVAA]);
|
||||
const parsedPayload = useMemo(
|
||||
() =>
|
||||
recoveryParsedVAA?.payload
|
||||
? parseTransferPayload(
|
||||
Buffer.from(new Uint8Array(recoveryParsedVAA.payload))
|
||||
)
|
||||
: null,
|
||||
[recoveryParsedVAA]
|
||||
);
|
||||
const parsedPayloadTargetChain = parsedPayload?.targetChain;
|
||||
const enableRecovery = recoverySignedVAA && parsedPayloadTargetChain;
|
||||
const handleRecoverClick = useCallback(() => {
|
||||
if (enableRecovery && recoverySignedVAA && parsedPayloadTargetChain) {
|
||||
// TODO: make recovery reducer
|
||||
dispatch(setSignedVAAHex(recoverySignedVAA));
|
||||
dispatch(setTargetChain(parsedPayloadTargetChain));
|
||||
dispatch(setStep(3));
|
||||
onClose();
|
||||
}
|
||||
}, [
|
||||
dispatch,
|
||||
enableRecovery,
|
||||
recoverySignedVAA,
|
||||
parsedPayloadTargetChain,
|
||||
onClose,
|
||||
]);
|
||||
return (
|
||||
<>
|
||||
<DialogContent>
|
||||
<Alert severity="info">
|
||||
If you have sent your tokens but have not redeemed them, you may paste
|
||||
in the Source Transaction ID (from Step 3) to resume your transfer.
|
||||
</Alert>
|
||||
<TextField
|
||||
select
|
||||
label="Source Chain"
|
||||
disabled={!!recoverySignedVAA}
|
||||
value={recoverySourceChain}
|
||||
onChange={handleSourceChainChange}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
>
|
||||
{CHAINS.map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
{isEVMChain(recoverySourceChain) ? (
|
||||
<KeyAndBalance chainId={recoverySourceChain} />
|
||||
) : null}
|
||||
<TextField
|
||||
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>
|
||||
<TextField
|
||||
label="Emitter Chain"
|
||||
disabled
|
||||
value={recoveryParsedVAA?.emitter_chain || ""}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
label="Emitter Address"
|
||||
disabled
|
||||
value={
|
||||
(recoveryParsedVAA &&
|
||||
hexToNativeString(
|
||||
recoveryParsedVAA.emitter_address,
|
||||
recoveryParsedVAA.emitter_chain
|
||||
)) ||
|
||||
""
|
||||
}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
label="Sequence"
|
||||
disabled
|
||||
value={recoveryParsedVAA?.sequence || ""}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
label="Timestamp"
|
||||
disabled
|
||||
value={
|
||||
(recoveryParsedVAA &&
|
||||
new Date(recoveryParsedVAA.timestamp * 1000).toLocaleString()) ||
|
||||
""
|
||||
}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<Box my={4}>
|
||||
<Divider />
|
||||
</Box>
|
||||
<TextField
|
||||
label="Origin Chain"
|
||||
disabled
|
||||
value={parsedPayload?.originChain.toString() || ""}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
label="Origin Token Address"
|
||||
disabled
|
||||
value={
|
||||
(parsedPayload &&
|
||||
hexToNativeString(
|
||||
parsedPayload.originAddress,
|
||||
parsedPayload.originChain
|
||||
)) ||
|
||||
""
|
||||
}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
label="Target Chain"
|
||||
disabled
|
||||
value={parsedPayload?.targetChain.toString() || ""}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
label="Target Address"
|
||||
disabled
|
||||
value={
|
||||
(parsedPayload &&
|
||||
hexToNativeString(
|
||||
parsedPayload.targetAddress,
|
||||
parsedPayload.targetChain
|
||||
)) ||
|
||||
""
|
||||
}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
label="Amount"
|
||||
disabled
|
||||
value={parsedPayload?.amount.toString() || ""}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<Box my={4}>
|
||||
<Divider />
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} variant="outlined" color="default">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleRecoverClick}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={!enableRecovery || disabled}
|
||||
>
|
||||
Recover
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Recovery({
|
||||
open,
|
||||
setOpen,
|
||||
disabled,
|
||||
}: {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
disabled: boolean;
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
const handleOpenClick = useCallback(() => {
|
||||
setOpen(true);
|
||||
}, [setOpen]);
|
||||
const handleCloseClick = useCallback(() => {
|
||||
setOpen(false);
|
||||
}, [setOpen]);
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="Open Recovery Dialog">
|
||||
<Fab className={classes.fab} onClick={handleOpenClick}>
|
||||
<Restore />
|
||||
</Fab>
|
||||
</Tooltip>
|
||||
<Dialog open={open} onClose={handleCloseClick} maxWidth="md" fullWidth>
|
||||
<DialogTitle>Recovery</DialogTitle>
|
||||
<RecoveryDialogContent onClose={handleCloseClick} disabled={disabled} />
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
|
||||
import { Button, makeStyles, MenuItem, TextField } from "@material-ui/core";
|
||||
import { Restore } from "@material-ui/icons";
|
||||
import { useCallback } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useHistory } from "react-router";
|
||||
import { useBetaContext } from "../../contexts/BetaContext";
|
||||
import useIsWalletReady from "../../hooks/useIsWalletReady";
|
||||
import {
|
||||
selectTransferAmount,
|
||||
|
@ -20,6 +20,7 @@ import {
|
|||
setSourceChain,
|
||||
} from "../../store/transferSlice";
|
||||
import {
|
||||
BETA_CHAINS,
|
||||
CHAINS,
|
||||
ETH_MIGRATION_ASSET_MAP,
|
||||
MIGRATION_ASSET_MAP,
|
||||
|
@ -37,13 +38,10 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
function Source({
|
||||
setIsRecoveryOpen,
|
||||
}: {
|
||||
setIsRecoveryOpen: (open: boolean) => void;
|
||||
}) {
|
||||
function Source() {
|
||||
const classes = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const isBeta = useBetaContext();
|
||||
const history = useHistory();
|
||||
const sourceChain = useSelector(selectTransferSourceChain);
|
||||
const parsedTokenAccount = useSelector(
|
||||
|
@ -92,27 +90,19 @@ function Source({
|
|||
return (
|
||||
<>
|
||||
<StepDescription>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
Select tokens to send through the Wormhole Token Bridge.
|
||||
<div style={{ flexGrow: 1 }} />
|
||||
<Button
|
||||
onClick={() => setIsRecoveryOpen(true)}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
endIcon={<Restore />}
|
||||
>
|
||||
Perform Recovery
|
||||
</Button>
|
||||
</div>
|
||||
</StepDescription>
|
||||
<TextField
|
||||
select
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={sourceChain}
|
||||
onChange={handleSourceChange}
|
||||
disabled={shouldLockFields}
|
||||
>
|
||||
{CHAINS.map(({ id, name }) => (
|
||||
{CHAINS.filter(({ id }) =>
|
||||
isBeta ? true : !BETA_CHAINS.includes(id)
|
||||
).map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
|
@ -143,6 +133,7 @@ function Source({
|
|||
<LowBalanceWarning chainId={sourceChain} />
|
||||
{hasParsedTokenAccount ? (
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Amount"
|
||||
type="number"
|
||||
fullWidth
|
||||
|
|
|
@ -3,6 +3,7 @@ import { makeStyles, MenuItem, TextField, Typography } from "@material-ui/core";
|
|||
import { Alert } from "@material-ui/lab";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useBetaContext } from "../../contexts/BetaContext";
|
||||
import useIsWalletReady from "../../hooks/useIsWalletReady";
|
||||
import useMetadata from "../../hooks/useMetadata";
|
||||
import useSyncTargetAddress from "../../hooks/useSyncTargetAddress";
|
||||
|
@ -20,7 +21,7 @@ import {
|
|||
UNREGISTERED_ERROR_MESSAGE,
|
||||
} from "../../store/selectors";
|
||||
import { incrementStep, setTargetChain } from "../../store/transferSlice";
|
||||
import { CHAINS, CHAINS_BY_ID } from "../../utils/consts";
|
||||
import { BETA_CHAINS, CHAINS, CHAINS_BY_ID } from "../../utils/consts";
|
||||
import { isEVMChain } from "../../utils/ethereum";
|
||||
import ButtonWithLoader from "../ButtonWithLoader";
|
||||
import KeyAndBalance from "../KeyAndBalance";
|
||||
|
@ -45,6 +46,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
function Target() {
|
||||
const classes = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const isBeta = useBetaContext();
|
||||
const sourceChain = useSelector(selectTransferSourceChain);
|
||||
const chains = useMemo(
|
||||
() => CHAINS.filter((c) => c.id !== sourceChain),
|
||||
|
@ -92,13 +94,16 @@ function Target() {
|
|||
<>
|
||||
<StepDescription>Select a recipient chain and address.</StepDescription>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
select
|
||||
fullWidth
|
||||
value={targetChain}
|
||||
onChange={handleTargetChange}
|
||||
disabled={shouldLockFields}
|
||||
>
|
||||
{chains.map(({ id, name }) => (
|
||||
{chains
|
||||
.filter(({ id }) => (isBeta ? true : !BETA_CHAINS.includes(id)))
|
||||
.map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
ETH_TOKENS_THAT_CAN_BE_SWAPPED_ON_SOLANA,
|
||||
ETH_TOKENS_THAT_EXIST_ELSEWHERE,
|
||||
SOLANA_TOKENS_THAT_EXIST_ELSEWHERE,
|
||||
WETH_ADDRESS,
|
||||
} from "../../utils/consts";
|
||||
|
||||
export default function TokenWarning({
|
||||
|
@ -39,10 +38,13 @@ export default function TokenWarning({
|
|||
);
|
||||
return tokenConflictingNativeWarning ? (
|
||||
<Alert severity="warning">{tokenConflictingNativeWarning}</Alert>
|
||||
) : sourceChain === CHAIN_ID_ETH && tokenAddress === WETH_ADDRESS ? (
|
||||
) : sourceChain === CHAIN_ID_ETH &&
|
||||
tokenAddress &&
|
||||
getAddress(tokenAddress) ===
|
||||
getAddress("0xae7ab96520de3a18e5e111b5eaab095312d7fe84") ? ( // stETH (Lido)
|
||||
<Alert severity="warning">
|
||||
As of 2021-09-30, markets for Wormhole v2 wrapped WETH have not yet been
|
||||
created.
|
||||
Lido stETH rewards can only be received on Ethereum. Use the value
|
||||
accruing wrapper token wstETH instead.
|
||||
</Alert>
|
||||
) : sourceChain === CHAIN_ID_ETH &&
|
||||
tokenAddress &&
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
import {
|
||||
Container,
|
||||
makeStyles,
|
||||
Step,
|
||||
StepButton,
|
||||
StepContent,
|
||||
Stepper,
|
||||
} from "@material-ui/core";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import useCheckIfWormholeWrapped from "../../hooks/useCheckIfWormholeWrapped";
|
||||
import useFetchTargetAsset from "../../hooks/useFetchTargetAsset";
|
||||
import useGetBalanceEffect from "../../hooks/useGetBalanceEffect";
|
||||
import { COLORS } from "../../muiTheme";
|
||||
import {
|
||||
selectTransferActiveStep,
|
||||
selectTransferIsRedeemComplete,
|
||||
|
@ -20,7 +18,6 @@ import {
|
|||
selectTransferIsSending,
|
||||
} from "../../store/selectors";
|
||||
import { setStep } from "../../store/transferSlice";
|
||||
import Recovery from "./Recovery";
|
||||
import Redeem from "./Redeem";
|
||||
import RedeemPreview from "./RedeemPreview";
|
||||
import Send from "./Send";
|
||||
|
@ -30,18 +27,10 @@ import SourcePreview from "./SourcePreview";
|
|||
import Target from "./Target";
|
||||
import TargetPreview from "./TargetPreview";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
rootContainer: {
|
||||
backgroundColor: COLORS.nearBlackWithMinorTransparency,
|
||||
},
|
||||
}));
|
||||
|
||||
function Transfer() {
|
||||
const classes = useStyles();
|
||||
useCheckIfWormholeWrapped();
|
||||
useFetchTargetAsset();
|
||||
useGetBalanceEffect("target");
|
||||
const [isRecoveryOpen, setIsRecoveryOpen] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
const activeStep = useSelector(selectTransferActiveStep);
|
||||
const isSending = useSelector(selectTransferIsSending);
|
||||
|
@ -60,22 +49,14 @@ function Transfer() {
|
|||
}, [preventNavigation]);
|
||||
return (
|
||||
<Container maxWidth="md">
|
||||
<Stepper
|
||||
activeStep={activeStep}
|
||||
orientation="vertical"
|
||||
className={classes.rootContainer}
|
||||
>
|
||||
<Stepper activeStep={activeStep} orientation="vertical">
|
||||
<Step
|
||||
expanded={activeStep >= 0}
|
||||
disabled={preventNavigation || isRedeemComplete}
|
||||
>
|
||||
<StepButton onClick={() => dispatch(setStep(0))}>Source</StepButton>
|
||||
<StepContent>
|
||||
{activeStep === 0 ? (
|
||||
<Source setIsRecoveryOpen={setIsRecoveryOpen} />
|
||||
) : (
|
||||
<SourcePreview />
|
||||
)}
|
||||
{activeStep === 0 ? <Source /> : <SourcePreview />}
|
||||
</StepContent>
|
||||
</Step>
|
||||
<Step
|
||||
|
@ -107,11 +88,6 @@ function Transfer() {
|
|||
</StepContent>
|
||||
</Step>
|
||||
</Stepper>
|
||||
<Recovery
|
||||
open={isRecoveryOpen}
|
||||
setOpen={setIsRecoveryOpen}
|
||||
disabled={preventNavigation}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import React, { ReactChildren, useContext, useEffect, useState } from "react";
|
||||
|
||||
const BetaContext = React.createContext<boolean>(false);
|
||||
|
||||
export const BetaContextProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactChildren;
|
||||
}) => {
|
||||
const [isBetaEnabled, setIsBetaEnabled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let userEntered = [];
|
||||
const secretSequence = [
|
||||
"38",
|
||||
"38",
|
||||
"40",
|
||||
"40",
|
||||
"37",
|
||||
"39",
|
||||
"37",
|
||||
"39",
|
||||
"66",
|
||||
"65",
|
||||
];
|
||||
const secretListener = (event: KeyboardEvent) => {
|
||||
const k = event.keyCode.toString();
|
||||
if (k === secretSequence[userEntered.length]) {
|
||||
userEntered.push(k);
|
||||
if (userEntered.length === secretSequence.length) {
|
||||
userEntered = [];
|
||||
setIsBetaEnabled((prev) => !prev);
|
||||
}
|
||||
} else {
|
||||
userEntered = [];
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", secretListener);
|
||||
return () => {
|
||||
window.removeEventListener("keydown", secretListener);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<BetaContext.Provider value={isBetaEnabled}>
|
||||
{children}
|
||||
</BetaContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useBetaContext = () => {
|
||||
return useContext(BetaContext);
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 288.9 274" style="enable-background:new 0 0 288.9 274;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#2849A9;}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#5795ED;}
|
||||
</style>
|
||||
<path class="st0" d="M151.1,0.3c33.7,0,64.9,12.1,88.7,32.9c31.8,24.5,22.6,113.9-9.6,90.3c-70.8-0.3-202.4-38.2-163.2-90.3
|
||||
c4-5.3,9-9.6,14.5-13.7h-0.3c0.9-0.5,1.9-1,2.8-1.6c0.9-0.5,1.9-1.1,2.8-1.6h0c2.8-1.6,5.6-3.1,8.7-4.3
|
||||
C112.5,4.6,131.3,0.3,151.1,0.3z M174.9,272.8c-14.2,0.9-42.6-21.4-50.7-50.9c-15.1-55.9,107.2-84.4,118.7-85.4
|
||||
c31.2,0.9,38.9,38.2,16.1,76.7C229.3,262.6,175.5,272.8,174.9,272.8z"/>
|
||||
<path class="st1" d="M14.8,77.9c9.9,2.8,70.5-16.5,88.4-43.8c0.3-0.3,14.2-21.7-12.7-22c-3.1,0-11.7,0.3-20.1,5.3
|
||||
c-4,2.5-7.7,5-11.4,7.8c-5.8,4.3-11.3,9.5-16.5,14.4h0l-0.2,0.2c-5.3,5-10.2,10.9-14.5,16.8c-4.3,5.9-8.3,12.4-11.7,18.9
|
||||
c-0.2,0.5-0.4,0.9-0.6,1.2C15.2,77,15,77.4,14.8,77.9z M86.5,272.8c1.9-2.8,3.1-36.6,1.9-45.3c-1.2-8.7-4-26.4-20.7-55.6
|
||||
c-2.8-4.7-16.1-26.4-26-39.7c-5.6-7.8-11.7-15-17.8-22.3h0c-5.1-6-10.2-12.1-15-18.4c-0.3,0.8-0.5,1.5-0.8,2.2s-0.5,1.4-0.8,2.2
|
||||
c-2.5,7.1-4.3,14.6-5.6,22.4S0,133.8,0,141.8c0,8.1,0.6,15.8,1.9,23.6s3.4,15.2,5.6,22.4c2.2,7.1,5.3,14.3,8.7,20.8
|
||||
s7.4,13,11.7,18.9c4.3,5.9,9.3,11.5,14.5,16.8c4.9,5.3,10.8,10.2,16.7,14.6h0h0c4.6,3.1,9.3,6.2,13.9,9c8.5,5,11.7,5,13.4,5
|
||||
C86.4,272.8,86.4,272.8,86.5,272.8z M288.9,141.8c0,18.9-3.7,36.9-10.2,53.4c-15.7,17-115.3-20.7-130.8-26.6c-1.2-0.5-2-0.7-2-0.8
|
||||
c-15.8-6.8-63.3-27.9-67.7-60.8c-6.2-47.5,89.6-80.7,131.9-82c4.9,0,20.4,0.3,29.4,7.5C269.8,59.2,288.9,98.4,288.9,141.8z
|
||||
M188.8,260.1c-3.7,12.1,10.2,16.5,22.6,10.6c24.7-13,45.1-33.2,59-57.1c0.9-1.2,0-2.5-1.5-2.2C255.6,212.6,195.6,236.5,188.8,260.1
|
||||
z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 1.0 MiB |
|
@ -6,6 +6,7 @@ import { Provider } from "react-redux";
|
|||
import { HashRouter } from "react-router-dom";
|
||||
import App from "./App";
|
||||
import BackgroundImage from "./components/BackgroundImage";
|
||||
import { BetaContextProvider } from "./contexts/BetaContext";
|
||||
import { EthereumProviderProvider } from "./contexts/EthereumProviderContext";
|
||||
import { SolanaWalletProvider } from "./contexts/SolanaWalletContext.tsx";
|
||||
import { TerraWalletProvider } from "./contexts/TerraWalletContext.tsx";
|
||||
|
@ -20,6 +21,7 @@ ReactDOM.render(
|
|||
<CssBaseline />
|
||||
<ErrorBoundary>
|
||||
<SnackbarProvider maxSnack={3}>
|
||||
<BetaContextProvider>
|
||||
<SolanaWalletProvider>
|
||||
<EthereumProviderProvider>
|
||||
<TerraWalletProvider>
|
||||
|
@ -30,6 +32,7 @@ ReactDOM.render(
|
|||
</TerraWalletProvider>
|
||||
</EthereumProviderProvider>
|
||||
</SolanaWalletProvider>
|
||||
</BetaContextProvider>
|
||||
</SnackbarProvider>
|
||||
</ErrorBoundary>
|
||||
</ThemeProvider>
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import { createTheme, responsiveFontSizes } from "@material-ui/core";
|
||||
|
||||
export const COLORS = {
|
||||
blue: "#1975e6",
|
||||
blueWithTransparency: "rgba(25, 117, 230, 0.8)",
|
||||
green: "#0ac2af",
|
||||
greenWithTransparency: "rgba(10, 194, 175, 0.8)",
|
||||
lightGreen: "rgba(51, 242, 223, 1)",
|
||||
green: "#00EFD8",
|
||||
blue: "#0074FF",
|
||||
blueWithTransparency: "rgba(0, 116, 255, 0.8)",
|
||||
greenWithTransparency: "rgba(0,239,216,0.8)",
|
||||
nearBlack: "#010114",
|
||||
nearBlackWithMinorTransparency: "rgba(0,0,0,.97)",
|
||||
lightBlue: "#83b9fc",
|
||||
nearBlack: "#000008",
|
||||
nearBlackWithMinorTransparency: "rgba(0,0,0,.25)",
|
||||
red: "#aa0818",
|
||||
darkRed: "#810612",
|
||||
};
|
||||
|
||||
export const theme = responsiveFontSizes(
|
||||
|
@ -24,31 +27,106 @@ export const theme = responsiveFontSizes(
|
|||
},
|
||||
primary: {
|
||||
main: COLORS.blueWithTransparency, // #0074FF
|
||||
light: COLORS.lightBlue,
|
||||
},
|
||||
secondary: {
|
||||
main: COLORS.greenWithTransparency, // #00EFD8
|
||||
light: COLORS.lightGreen,
|
||||
},
|
||||
error: {
|
||||
main: "#FD3503",
|
||||
main: COLORS.red,
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
fontFamily: "'Sora', sans-serif",
|
||||
h1: {
|
||||
fontWeight: "200",
|
||||
},
|
||||
h2: {
|
||||
fontWeight: "700",
|
||||
fontWeight: "300",
|
||||
},
|
||||
h4: {
|
||||
fontWeight: "500",
|
||||
},
|
||||
},
|
||||
overrides: {
|
||||
MuiAccordion: {
|
||||
root: {
|
||||
backgroundColor: COLORS.nearBlackWithMinorTransparency,
|
||||
"&:before": {
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
rounded: {
|
||||
"&:first-child": {
|
||||
borderTopLeftRadius: "16px",
|
||||
borderTopRightRadius: "16px",
|
||||
},
|
||||
"&:last-child": {
|
||||
borderBottomLeftRadius: "16px",
|
||||
borderBottomRightRadius: "16px",
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiButton: {
|
||||
root: {
|
||||
borderRadius: "5px",
|
||||
textTransform: "none",
|
||||
},
|
||||
},
|
||||
MuiLink: {
|
||||
root: {
|
||||
color: COLORS.lightBlue,
|
||||
},
|
||||
},
|
||||
MuiPaper: {
|
||||
rounded: {
|
||||
borderRadius: "16px",
|
||||
},
|
||||
},
|
||||
MuiStepper: {
|
||||
root: {
|
||||
backgroundColor: "transparent",
|
||||
padding: 0,
|
||||
},
|
||||
},
|
||||
MuiStep: {
|
||||
root: {
|
||||
backgroundColor: COLORS.nearBlackWithMinorTransparency,
|
||||
borderRadius: "16px",
|
||||
padding: 16,
|
||||
},
|
||||
},
|
||||
MuiStepConnector: {
|
||||
lineVertical: {
|
||||
borderLeftWidth: 0,
|
||||
},
|
||||
},
|
||||
MuiStepContent: {
|
||||
root: {
|
||||
borderLeftWidth: 0,
|
||||
},
|
||||
},
|
||||
MuiStepLabel: {
|
||||
label: {
|
||||
fontSize: 16,
|
||||
fontWeight: "300",
|
||||
"&.MuiStepLabel-active": {
|
||||
fontWeight: "300",
|
||||
},
|
||||
"&.MuiStepLabel-completed": {
|
||||
fontWeight: "300",
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTab: {
|
||||
root: {
|
||||
fontSize: 18,
|
||||
fontWeight: "300",
|
||||
padding: 12,
|
||||
textTransform: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
|
|
@ -22,6 +22,10 @@ export interface ChainInfo {
|
|||
export const CHAINS =
|
||||
CLUSTER === "mainnet"
|
||||
? [
|
||||
// {
|
||||
// id: CHAIN_ID_BSC,
|
||||
// name: "Binance Smart Chain",
|
||||
// },
|
||||
{
|
||||
id: CHAIN_ID_ETH,
|
||||
name: "Ethereum",
|
||||
|
@ -30,6 +34,10 @@ export const CHAINS =
|
|||
id: CHAIN_ID_SOLANA,
|
||||
name: "Solana",
|
||||
},
|
||||
// {
|
||||
// id: CHAIN_ID_TERRA,
|
||||
// name: "Terra",
|
||||
// },
|
||||
]
|
||||
: CLUSTER === "testnet"
|
||||
? [
|
||||
|
@ -64,6 +72,8 @@ export const CHAINS =
|
|||
name: "Terra",
|
||||
},
|
||||
];
|
||||
export const BETA_CHAINS =
|
||||
CLUSTER === "mainnet" ? [CHAIN_ID_BSC, CHAIN_ID_TERRA] : [];
|
||||
export const CHAINS_WITH_NFT_SUPPORT = CHAINS.filter(
|
||||
({ id }) =>
|
||||
id === CHAIN_ID_ETH || id === CHAIN_ID_BSC || id === CHAIN_ID_SOLANA
|
||||
|
@ -202,13 +212,13 @@ export const SOL_CUSTODY_ADDRESS =
|
|||
export const TERRA_TEST_TOKEN_ADDRESS =
|
||||
"terra13nkgqrfymug724h8pprpexqj9h629sa3ncw7sh";
|
||||
export const TERRA_BRIDGE_ADDRESS =
|
||||
CLUSTER === "mainnet"
|
||||
CLUSTER === "mainnet"
|
||||
? "terra1dq03ugtd40zu9hcgdzrsq6z2z4hwhc9tqk2uy5"
|
||||
: CLUSTER === "testnet"
|
||||
? "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5"
|
||||
: "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5";
|
||||
export const TERRA_TOKEN_BRIDGE_ADDRESS =
|
||||
CLUSTER === "mainnet"
|
||||
CLUSTER === "mainnet"
|
||||
? "terra10nmmwe8r3g99a9newtqa7a75xfgs2e8z87r2sf"
|
||||
: CLUSTER === "testnet"
|
||||
? "terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4"
|
||||
|
|
Loading…
Reference in New Issue