bridge_ui: add bsc migration support

Change-Id: Ia11a78be9318406ab72d7c9a58c6e74699379cff
This commit is contained in:
Chase Moran 2021-10-15 12:30:49 -04:00
parent e8b51439fe
commit 350244e8e4
7 changed files with 108 additions and 50 deletions

View File

@ -1,4 +1,8 @@
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk"; import {
CHAIN_ID_BSC,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA,
} from "@certusone/wormhole-sdk";
import { import {
AppBar, AppBar,
Button, Button,
@ -28,7 +32,7 @@ import Attest from "./components/Attest";
import Footer from "./components/Footer"; import Footer from "./components/Footer";
import Home from "./components/Home"; import Home from "./components/Home";
import Migration from "./components/Migration"; import Migration from "./components/Migration";
import EthereumQuickMigrate from "./components/Migration/EthereumQuickMigrate"; import EvmQuickMigrate from "./components/Migration/EvmQuickMigrate";
import NFT from "./components/NFT"; import NFT from "./components/NFT";
import NFTOriginVerifier from "./components/NFTOriginVerifier"; import NFTOriginVerifier from "./components/NFTOriginVerifier";
import Recovery from "./components/Recovery"; import Recovery from "./components/Recovery";
@ -124,6 +128,7 @@ function App() {
const isBeta = useBetaContext(); const isBeta = useBetaContext();
const isHomepage = useRouteMatch({ path: "/", exact: true }); const isHomepage = useRouteMatch({ path: "/", exact: true });
const isStats = useRouteMatch({ path: "/stats", exact: true }); const isStats = useRouteMatch({ path: "/stats", exact: true });
const isMigrate = useRouteMatch({ path: "/migrate" });
const isOriginVerifier = useRouteMatch({ const isOriginVerifier = useRouteMatch({
path: "/nft-origin-verifier", path: "/nft-origin-verifier",
exact: true, exact: true,
@ -244,7 +249,7 @@ function App() {
</AppBar> </AppBar>
) : null} ) : null}
<div className={classes.content}> <div className={classes.content}>
{isHomepage || isOriginVerifier || isStats ? null : ( {isHomepage || isOriginVerifier || isStats || isMigrate ? null : (
<Container maxWidth="md" style={{ paddingBottom: 24 }}> <Container maxWidth="md" style={{ paddingBottom: 24 }}>
<Tabs <Tabs
value={ value={
@ -284,8 +289,14 @@ function App() {
<Route exact path="/migrate/Ethereum/:legacyAsset/"> <Route exact path="/migrate/Ethereum/:legacyAsset/">
<Migration chainId={CHAIN_ID_ETH} /> <Migration chainId={CHAIN_ID_ETH} />
</Route> </Route>
<Route exact path="/migrate/BinanceSmartChain/:legacyAsset/">
<Migration chainId={CHAIN_ID_BSC} />
</Route>
<Route exact path="/migrate/Ethereum/"> <Route exact path="/migrate/Ethereum/">
<EthereumQuickMigrate /> <EvmQuickMigrate chainId={CHAIN_ID_ETH} />
</Route>
<Route exact path="/migrate/BinanceSmartChain/">
<EvmQuickMigrate chainId={CHAIN_ID_BSC} />
</Route> </Route>
<Route exact path="/stats"> <Route exact path="/stats">
<Stats /> <Stats />

View File

@ -1,7 +1,4 @@
import { import { ChainId, TokenImplementation__factory } from "@certusone/wormhole-sdk";
CHAIN_ID_ETH,
TokenImplementation__factory,
} from "@certusone/wormhole-sdk";
import { Signer } from "@ethersproject/abstract-signer"; import { Signer } from "@ethersproject/abstract-signer";
import { BigNumber } from "@ethersproject/bignumber"; import { BigNumber } from "@ethersproject/bignumber";
import { import {
@ -20,7 +17,7 @@ import { useEthereumProvider } from "../../contexts/EthereumProviderContext";
import useEthereumMigratorInformation from "../../hooks/useEthereumMigratorInformation"; import useEthereumMigratorInformation from "../../hooks/useEthereumMigratorInformation";
import useIsWalletReady from "../../hooks/useIsWalletReady"; import useIsWalletReady from "../../hooks/useIsWalletReady";
import { COLORS } from "../../muiTheme"; import { COLORS } from "../../muiTheme";
import { ETH_MIGRATION_ASSET_MAP } from "../../utils/consts"; import { CHAINS, getMigrationAssetMap } from "../../utils/consts";
import ButtonWithLoader from "../ButtonWithLoader"; import ButtonWithLoader from "../ButtonWithLoader";
import EthereumSignerKey from "../EthereumSignerKey"; import EthereumSignerKey from "../EthereumSignerKey";
import ShowTx from "../ShowTx"; import ShowTx from "../ShowTx";
@ -89,10 +86,12 @@ export const compareWithDecimalOffset = (
} }
}; };
function EthereumMigrationLineItem({ function EvmMigrationLineItem({
chainId,
migratorAddress, migratorAddress,
onLoadComplete, onLoadComplete,
}: { }: {
chainId: ChainId;
migratorAddress: string; migratorAddress: string;
onLoadComplete: () => void; onLoadComplete: () => void;
}) { }) {
@ -175,7 +174,7 @@ function EthereumMigrationLineItem({
Successfully migrated your tokens. They will become available once Successfully migrated your tokens. They will become available once
this transaction confirms. this transaction confirms.
</Typography> </Typography>
<ShowTx chainId={CHAIN_ID_ETH} tx={{ id: transaction, block: 1 }} /> <ShowTx chainId={chainId} tx={{ id: transaction, block: 1 }} />
</div> </div>
</div> </div>
); );
@ -189,10 +188,7 @@ function EthereumMigrationLineItem({
<Typography className={classes.balance}> <Typography className={classes.balance}>
{poolInfo.data.fromWalletBalance} {poolInfo.data.fromWalletBalance}
</Typography> </Typography>
<SmartAddress <SmartAddress chainId={chainId} address={poolInfo.data.fromAddress} />
chainId={CHAIN_ID_ETH}
address={poolInfo.data.fromAddress}
/>
</div> </div>
<div> <div>
<Typography variant="body2" color="textSecondary"> <Typography variant="body2" color="textSecondary">
@ -207,10 +203,7 @@ function EthereumMigrationLineItem({
<Typography className={classes.balance}> <Typography className={classes.balance}>
{poolInfo.data.fromWalletBalance} {poolInfo.data.fromWalletBalance}
</Typography> </Typography>
<SmartAddress <SmartAddress chainId={chainId} address={poolInfo.data.toAddress} />
chainId={CHAIN_ID_ETH}
address={poolInfo.data.toAddress}
/>
</div> </div>
<div className={classes.convertButton}> <div className={classes.convertButton}>
<ButtonWithLoader <ButtonWithLoader
@ -261,13 +254,14 @@ const getAddressBalances = async (
} }
}; };
export default function EthereumQuickMigrate() { export default function EvmQuickMigrate({ chainId }: { chainId: ChainId }) {
const classes = useStyles(); const classes = useStyles();
const { signer, signerAddress } = useEthereumProvider(); const { signer, signerAddress } = useEthereumProvider();
const { isReady } = useIsWalletReady(CHAIN_ID_ETH); const { isReady } = useIsWalletReady(chainId);
const migrationMap = useMemo(() => getMigrationAssetMap(chainId), [chainId]);
const eligibleTokens = useMemo( const eligibleTokens = useMemo(
() => Array.from(ETH_MIGRATION_ASSET_MAP.keys()), () => Array.from(migrationMap.keys()),
[] [migrationMap]
); );
const [migrators, setMigrators] = useState<string[] | null>(null); const [migrators, setMigrators] = useState<string[] | null>(null);
const [migratorsError, setMigratorsError] = useState(""); const [migratorsError, setMigratorsError] = useState("");
@ -297,8 +291,7 @@ export default function EthereumQuickMigrate() {
const migratorAddresses = []; const migratorAddresses = [];
for (const tokenAddress of result.keys()) { for (const tokenAddress of result.keys()) {
if (result.get(tokenAddress) && result.get(tokenAddress)?.gt(0)) { if (result.get(tokenAddress) && result.get(tokenAddress)?.gt(0)) {
const migratorAddress = const migratorAddress = migrationMap.get(tokenAddress);
ETH_MIGRATION_ASSET_MAP.get(tokenAddress);
if (migratorAddress) { if (migratorAddress) {
migratorAddresses.push(migratorAddress); migratorAddresses.push(migratorAddress);
} }
@ -323,15 +316,18 @@ export default function EthereumQuickMigrate() {
cancelled = true; cancelled = true;
}; };
} }
}, [isReady, signer, signerAddress, eligibleTokens]); }, [isReady, signer, signerAddress, eligibleTokens, migrationMap]);
const hasEligibleAssets = migrators && migrators.length > 0; const hasEligibleAssets = migrators && migrators.length > 0;
const chainName = CHAINS[chainId]?.name;
const content = ( const content = (
<div className={classes.containerDiv}> <div className={classes.containerDiv}>
<Typography variant="h5"> <Typography variant="h5">
This page allows you to convert certain wrapped tokens on Ethereum into {`This page allows you to convert certain wrapped tokens ${
Wormhole V2 tokens. chainName ? "on " + chainName : ""
} into
Wormhole V2 tokens.`}
</Typography> </Typography>
<EthereumSignerKey /> <EthereumSignerKey />
{!isReady ? ( {!isReady ? (
@ -351,8 +347,9 @@ export default function EthereumQuickMigrate() {
<div className={classes.spacer} /> <div className={classes.spacer} />
{migrators?.map((address) => { {migrators?.map((address) => {
return ( return (
<EthereumMigrationLineItem <EvmMigrationLineItem
key={address} key={address}
chainId={chainId}
migratorAddress={address} migratorAddress={address}
onLoadComplete={reportLoadComplete} onLoadComplete={reportLoadComplete}
/> />

View File

@ -1,4 +1,4 @@
import { CHAIN_ID_ETH } from "@certusone/wormhole-sdk"; import { ChainId } from "@certusone/wormhole-sdk";
import { import {
CircularProgress, CircularProgress,
makeStyles, makeStyles,
@ -27,15 +27,17 @@ const useStyles = makeStyles((theme) => ({
}, },
})); }));
export default function EthereumWorkflow({ export default function EvmWorkflow({
chainId,
migratorAddress, migratorAddress,
}: { }: {
chainId: ChainId;
migratorAddress: string; migratorAddress: string;
}) { }) {
const classes = useStyles(); const classes = useStyles();
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const { signer, signerAddress } = useEthereumProvider(); const { signer, signerAddress } = useEthereumProvider();
const { isReady } = useIsWalletReady(CHAIN_ID_ETH); const { isReady } = useIsWalletReady(chainId);
const [toggleRefresh, setToggleRefresh] = useState(false); const [toggleRefresh, setToggleRefresh] = useState(false);
const forceRefresh = useCallback( const forceRefresh = useCallback(
() => setToggleRefresh((prevState) => !prevState), () => setToggleRefresh((prevState) => !prevState),
@ -144,20 +146,20 @@ export default function EthereumWorkflow({
//TODO tokenName //TODO tokenName
const toTokenPretty = ( const toTokenPretty = (
<SmartAddress <SmartAddress
chainId={CHAIN_ID_ETH} chainId={chainId}
address={poolInfo.data?.toAddress} address={poolInfo.data?.toAddress}
symbol={poolInfo.data?.toSymbol} symbol={poolInfo.data?.toSymbol}
/> />
); );
const fromTokenPretty = ( const fromTokenPretty = (
<SmartAddress <SmartAddress
chainId={CHAIN_ID_ETH} chainId={chainId}
address={poolInfo.data?.fromAddress} address={poolInfo.data?.fromAddress}
symbol={poolInfo.data?.fromSymbol} symbol={poolInfo.data?.fromSymbol}
/> />
); );
const poolPretty = ( const poolPretty = (
<SmartAddress chainId={CHAIN_ID_ETH} address={poolInfo.data?.poolAddress} /> <SmartAddress chainId={chainId} address={poolInfo.data?.poolAddress} />
); );
const fatalError = poolInfo.error const fatalError = poolInfo.error
@ -218,7 +220,7 @@ export default function EthereumWorkflow({
Successfully migrated your tokens! They will be available once this Successfully migrated your tokens! They will be available once this
transaction confirms. transaction confirms.
</Typography> </Typography>
<ShowTx tx={{ id: transaction, block: 1 }} chainId={CHAIN_ID_ETH} /> <ShowTx tx={{ id: transaction, block: 1 }} chainId={chainId} />
</> </>
) : null} ) : null}
</> </>

View File

@ -7,10 +7,7 @@ import {
} from "@material-ui/core"; } from "@material-ui/core";
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
import { RouteComponentProps } from "react-router-dom"; import { RouteComponentProps } from "react-router-dom";
import { import { getMigrationAssetMap, MIGRATION_ASSET_MAP } from "../../utils/consts";
ETH_MIGRATION_ASSET_MAP,
MIGRATION_ASSET_MAP,
} from "../../utils/consts";
import SolanaWorkflow from "./SolanaWorkflow"; import SolanaWorkflow from "./SolanaWorkflow";
import { withRouter } from "react-router"; import { withRouter } from "react-router";
import { COLORS } from "../../muiTheme"; import { COLORS } from "../../muiTheme";
@ -18,8 +15,9 @@ import {
ChainId, ChainId,
CHAIN_ID_ETH, CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
CHAIN_ID_BSC,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import EthereumWorkflow from "./EthereumWorkflow"; import EvmWorkflow from "./EvmWorkflow";
const useStyles = makeStyles(() => ({ const useStyles = makeStyles(() => ({
mainPaper: { mainPaper: {
@ -91,7 +89,8 @@ const SolanaRoot: React.FC<Migration> = (props) => {
const EthereumRoot: React.FC<Migration> = (props) => { const EthereumRoot: React.FC<Migration> = (props) => {
const legacyAsset: string = props.match.params.legacyAsset; const legacyAsset: string = props.match.params.legacyAsset;
const targetPool = ETH_MIGRATION_ASSET_MAP.get(legacyAsset); const assetMap = getMigrationAssetMap(props.chainId);
const targetPool = assetMap.get(legacyAsset);
let content = null; let content = null;
if (!legacyAsset || !targetPool) { if (!legacyAsset || !targetPool) {
@ -101,7 +100,9 @@ const EthereumRoot: React.FC<Migration> = (props) => {
</Typography> </Typography>
); );
} else { } else {
content = <EthereumWorkflow migratorAddress={targetPool} />; content = (
<EvmWorkflow migratorAddress={targetPool} chainId={props.chainId} />
);
} }
return content; return content;
@ -113,7 +114,7 @@ const MigrationRoot: React.FC<Migration> = (props) => {
if (props.chainId === CHAIN_ID_SOLANA) { if (props.chainId === CHAIN_ID_SOLANA) {
content = <SolanaRoot {...props} />; content = <SolanaRoot {...props} />;
} else if (props.chainId === CHAIN_ID_ETH) { } else if (props.chainId === CHAIN_ID_ETH || props.chainId === CHAIN_ID_BSC) {
content = <EthereumRoot {...props} />; content = <EthereumRoot {...props} />;
} }

View File

@ -13,7 +13,7 @@ import { CovalentData } from "../../hooks/useGetSourceParsedTokenAccounts";
import { DataWrapper } from "../../store/helpers"; import { DataWrapper } from "../../store/helpers";
import { ParsedTokenAccount } from "../../store/transferSlice"; import { ParsedTokenAccount } from "../../store/transferSlice";
import { import {
ETH_MIGRATION_ASSET_MAP, getMigrationAssetMap,
WORMHOLE_V1_ETH_ADDRESS, WORMHOLE_V1_ETH_ADDRESS,
} from "../../utils/consts"; } from "../../utils/consts";
import { import {
@ -94,8 +94,9 @@ const isWormholev1 = (provider: any, address: string, chainId: ChainId) => {
return connection.isWrappedAsset(address); return connection.isWrappedAsset(address);
}; };
const isMigrationEligible = (address: string) => { const isMigrationEligible = (chainId: ChainId, address: string) => {
return !!ETH_MIGRATION_ASSET_MAP.get(address); const assetMap = getMigrationAssetMap(chainId);
return !!assetMap.get(address);
}; };
type EthereumSourceTokenSelectorProps = { type EthereumSourceTokenSelectorProps = {
@ -110,6 +111,7 @@ type EthereumSourceTokenSelectorProps = {
}; };
const renderAccount = ( const renderAccount = (
chainId: ChainId,
account: ParsedTokenAccount, account: ParsedTokenAccount,
covalentData: CovalentData | undefined, covalentData: CovalentData | undefined,
classes: any classes: any
@ -150,7 +152,9 @@ const renderAccount = (
</div> </div>
); );
return isMigrationEligible(account.mintKey) ? migrationRender : content; return isMigrationEligible(chainId, account.mintKey)
? migrationRender
: content;
}; };
const renderNFTAccount = ( const renderNFTAccount = (
@ -534,6 +538,7 @@ export default function EthereumSourceTokenSelector(
classes classes
) )
: renderAccount( : renderAccount(
chainId,
option, option,
covalent?.data?.find( covalent?.data?.find(
(x) => x.contract_address === option.mintKey (x) => x.contract_address === option.mintKey

View File

@ -1,4 +1,8 @@
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk"; import {
CHAIN_ID_BSC,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA,
} from "@certusone/wormhole-sdk";
import { Button, makeStyles, MenuItem, TextField } from "@material-ui/core"; import { Button, makeStyles, MenuItem, TextField } from "@material-ui/core";
import { useCallback } from "react"; import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
@ -21,6 +25,7 @@ import {
} from "../../store/transferSlice"; } from "../../store/transferSlice";
import { import {
BETA_CHAINS, BETA_CHAINS,
BSC_MIGRATION_ASSET_MAP,
CHAINS, CHAINS,
ETH_MIGRATION_ASSET_MAP, ETH_MIGRATION_ASSET_MAP,
MIGRATION_ASSET_MAP, MIGRATION_ASSET_MAP,
@ -56,7 +61,12 @@ function Source() {
sourceChain === CHAIN_ID_ETH && sourceChain === CHAIN_ID_ETH &&
!!parsedTokenAccount && !!parsedTokenAccount &&
!!ETH_MIGRATION_ASSET_MAP.get(parsedTokenAccount.mintKey); !!ETH_MIGRATION_ASSET_MAP.get(parsedTokenAccount.mintKey);
const isMigrationAsset = isSolanaMigration || isEthereumMigration; const isBscMigration =
sourceChain === CHAIN_ID_BSC &&
!!parsedTokenAccount &&
!!BSC_MIGRATION_ASSET_MAP.get(parsedTokenAccount.mintKey);
const isMigrationAsset =
isSolanaMigration || isEthereumMigration || isBscMigration;
const uiAmountString = useSelector(selectTransferSourceBalanceString); const uiAmountString = useSelector(selectTransferSourceBalanceString);
const amount = useSelector(selectTransferAmount); const amount = useSelector(selectTransferAmount);
const error = useSelector(selectTransferSourceError); const error = useSelector(selectTransferSourceError);
@ -70,6 +80,8 @@ function Source() {
); );
} else if (sourceChain === CHAIN_ID_ETH) { } else if (sourceChain === CHAIN_ID_ETH) {
history.push(`/migrate/Ethereum/${parsedTokenAccount?.mintKey}`); history.push(`/migrate/Ethereum/${parsedTokenAccount?.mintKey}`);
} else if (sourceChain === CHAIN_ID_BSC) {
history.push(`/migrate/BinanceSmartChain/${parsedTokenAccount?.mintKey}`);
} }
}, [history, parsedTokenAccount, sourceChain]); }, [history, parsedTokenAccount, sourceChain]);
const handleSourceChange = useCallback( const handleSourceChange = useCallback(

View File

@ -5,6 +5,7 @@ import {
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
CHAIN_ID_TERRA, CHAIN_ID_TERRA,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import { ChainID } from "@certusone/wormhole-sdk/lib/proto/publicrpc/v1/publicrpc";
import { clusterApiUrl } from "@solana/web3.js"; import { clusterApiUrl } from "@solana/web3.js";
import { getAddress } from "ethers/lib/utils"; import { getAddress } from "ethers/lib/utils";
@ -464,4 +465,33 @@ export const ETH_MIGRATION_ASSET_MAP = new Map<string, string>(
] ]
); );
export const BSC_MIGRATION_ASSET_MAP = new Map<string, string>(
CLUSTER === "mainnet"
? []
: CLUSTER === "testnet"
? []
: [
// [
// "0x2D8BE6BF0baA74e0A907016679CaE9190e80dD0A",
// "0x4bf3A7dFB3b76b5B3E169ACE65f888A4b4FCa5Ee",
// ],
// [
// "0x68d1569d1a6968f194b4d93f8d0b416c123a599f",
// "0xFcCeD5E997E7fb1D0594518D3eD57245bB8ed17E",
// ],
]
);
export const getMigrationAssetMap = (chainId: ChainID) => {
if (chainId === CHAIN_ID_BSC) {
return BSC_MIGRATION_ASSET_MAP;
} else if (chainId === CHAIN_ID_ETH) {
return ETH_MIGRATION_ASSET_MAP;
} else if (chainId === CHAIN_ID_SOLANA) {
return MIGRATION_ASSET_MAP;
} else {
return new Map<string, string>();
}
};
export const SUPPORTED_TERRA_TOKENS = ["uluna", "uusd"]; export const SUPPORTED_TERRA_TOKENS = ["uluna", "uusd"];