diff --git a/bridge_ui/src/components/TokenSelectors/SolanaSourceTokenSelector.tsx b/bridge_ui/src/components/TokenSelectors/SolanaSourceTokenSelector.tsx index 57ba40d1..662e40ff 100644 --- a/bridge_ui/src/components/TokenSelectors/SolanaSourceTokenSelector.tsx +++ b/bridge_ui/src/components/TokenSelectors/SolanaSourceTokenSelector.tsx @@ -1,6 +1,6 @@ import { CircularProgress, TextField, Typography } from "@material-ui/core"; import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"; -import { Autocomplete } from "@material-ui/lab"; +import { Alert, Autocomplete } from "@material-ui/lab"; import { createFilterOptions } from "@material-ui/lab/Autocomplete"; import { TokenInfo } from "@solana/spl-token-registry"; import React, { useCallback, useMemo } from "react"; @@ -8,7 +8,10 @@ import useMetaplexData from "../../hooks/useMetaplexData"; import useSolanaTokenMap from "../../hooks/useSolanaTokenMap"; import { DataWrapper } from "../../store/helpers"; import { ParsedTokenAccount } from "../../store/transferSlice"; -import { WORMHOLE_V1_MINT_AUTHORITY } from "../../utils/consts"; +import { + MIGRATION_ASSET_MAP, + WORMHOLE_V1_MINT_AUTHORITY, +} from "../../utils/consts"; import { shortenAddress } from "../../utils/solana"; import NFTViewer from "./NFTViewer"; import RefreshButtonWrapper from "./RefreshButtonWrapper"; @@ -146,6 +149,10 @@ export default function SolanaSourceTokenSelector( [props.mintAccounts] ); + const isMigrationEligible = useCallback((address: string) => { + return !!MIGRATION_ASSET_MAP.get(address); + }, []); + const renderAccount = useCallback( (account: ParsedTokenAccount) => { const mintPrettyString = shortenAddress(account.mintKey); @@ -189,9 +196,24 @@ export default function SolanaSourceTokenSelector( ); - return isWormholev1(account.mintKey) ? v1Warning : content; + const migrationRender = ( +
+ + + This is a legacy asset eligible for migration. + +
{content}
+
+
+ ); + + return isMigrationEligible(account.mintKey) + ? migrationRender + : isWormholev1(account.mintKey) + ? v1Warning + : content; }, - [getLogo, getSymbol, getName, classes, isWormholev1] + [getLogo, getSymbol, getName, classes, isWormholev1, isMigrationEligible] ); //The autocomplete doesn't rerender the option label unless the value changes. @@ -218,8 +240,10 @@ export default function SolanaSourceTokenSelector( }, [metaplex.data, nft, props.accounts]); const isOptionDisabled = useMemo(() => { - return (value: ParsedTokenAccount) => isWormholev1(value.mintKey); - }, [isWormholev1]); + return (value: ParsedTokenAccount) => { + return isWormholev1(value.mintKey) && !isMigrationEligible(value.mintKey); + }; + }, [isWormholev1, isMigrationEligible]); const onAutocompleteChange = useCallback( (event, newValue) => { diff --git a/bridge_ui/src/components/Transfer/Source.tsx b/bridge_ui/src/components/Transfer/Source.tsx index 3d25f854..f847464d 100644 --- a/bridge_ui/src/components/Transfer/Source.tsx +++ b/bridge_ui/src/components/Transfer/Source.tsx @@ -1,7 +1,9 @@ +import { 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 useIsWalletReady from "../../hooks/useIsWalletReady"; import useTokenBlacklistWarning from "../../hooks/useTokenBlacklistWarning"; import { @@ -18,7 +20,7 @@ import { setAmount, setSourceChain, } from "../../store/transferSlice"; -import { CHAINS } from "../../utils/consts"; +import { CHAINS, MIGRATION_ASSET_MAP } from "../../utils/consts"; import ButtonWithLoader from "../ButtonWithLoader"; import KeyAndBalance from "../KeyAndBalance"; import StepDescription from "../StepDescription"; @@ -37,11 +39,16 @@ function Source({ }) { const classes = useStyles(); const dispatch = useDispatch(); + const history = useHistory(); const sourceChain = useSelector(selectTransferSourceChain); const parsedTokenAccount = useSelector( selectTransferSourceParsedTokenAccount ); const hasParsedTokenAccount = !!parsedTokenAccount; + const isMigrationAsset = + sourceChain === CHAIN_ID_SOLANA && + !!parsedTokenAccount && + !!MIGRATION_ASSET_MAP.get(parsedTokenAccount.mintKey); const uiAmountString = useSelector(selectTransferSourceBalanceString); const amount = useSelector(selectTransferAmount); const error = useSelector(selectTransferSourceError); @@ -52,6 +59,10 @@ function Source({ sourceChain, parsedTokenAccount?.mintKey ); + const handleMigrationClick = useCallback(() => { + parsedTokenAccount?.mintKey && + history.push("/migrate/" + parsedTokenAccount.mintKey); + }, [history, parsedTokenAccount]); const handleSourceChange = useCallback( (event) => { dispatch(setSourceChain(event.target.value)); @@ -102,25 +113,38 @@ function Source({ ) : null} - {hasParsedTokenAccount ? ( - - ) : null} - - Next - + onClick={handleMigrationClick} + > + Go to Migration Page + + ) : ( + <> + {hasParsedTokenAccount ? ( + + ) : null} + + Next + + + )} ); } diff --git a/bridge_ui/src/hooks/useGetSourceParsedTokenAccounts.ts b/bridge_ui/src/hooks/useGetSourceParsedTokenAccounts.ts index e76e6012..b000ef02 100644 --- a/bridge_ui/src/hooks/useGetSourceParsedTokenAccounts.ts +++ b/bridge_ui/src/hooks/useGetSourceParsedTokenAccounts.ts @@ -371,7 +371,9 @@ function useGetAvailableTokens(nft: boolean = false) { setSolanaMintAccountsError(undefined); const mintAddresses = tokenAccounts.data.map((x) => x.mintKey); //This is a known wormhole v1 token on testnet - //mintAddresses.push("4QixXecTZ4zdZGa39KH8gVND5NZ2xcaB12wiBhE4S7rn"); + // mintAddresses.push("4QixXecTZ4zdZGa39KH8gVND5NZ2xcaB12wiBhE4S7rn"); + //SOLT devnet token + // mintAddresses.push("2WDq7wSs9zYrpx2kbHDA4RUTRch2CCTP6ZWaH4GNfnQQ"); const connection = new Connection(SOLANA_HOST, "finalized"); getMultipleAccountsRPC(