bridge_ui: migration view
Change-Id: I1c9f564dbdb77ae71e63934b55f395f7410869f6
This commit is contained in:
parent
0dc9f28bfd
commit
c8aee80b1d
|
@ -20,6 +20,7 @@ import Attest from "./components/Attest";
|
|||
import Home from "./components/Home";
|
||||
import NFT from "./components/NFT";
|
||||
import Transfer from "./components/Transfer";
|
||||
import Migration from "./components/Migration";
|
||||
import wormholeLogo from "./icons/wormhole.svg";
|
||||
import { ENABLE_NFT } from "./utils/consts";
|
||||
|
||||
|
@ -160,6 +161,9 @@ function App() {
|
|||
<Route exact path="/register">
|
||||
<Attest />
|
||||
</Route>
|
||||
<Route exact path="/migrate/:legacyAsset">
|
||||
<Migration />
|
||||
</Route>
|
||||
<Route exact path="/">
|
||||
<Home />
|
||||
</Route>
|
||||
|
|
|
@ -0,0 +1,472 @@
|
|||
import { CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
|
||||
import migrateTokensTx from "@certusone/wormhole-sdk/lib/migration/migrateTokens";
|
||||
import getPoolAddress from "@certusone/wormhole-sdk/lib/migration/poolAddress";
|
||||
import getToCustodyAddress from "@certusone/wormhole-sdk/lib/migration/toCustodyAddress";
|
||||
import {
|
||||
Container,
|
||||
Divider,
|
||||
makeStyles,
|
||||
Paper,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import {
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
Token,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
import { Connection, PublicKey } from "@solana/web3.js";
|
||||
import { parseUnits } from "ethers/lib/utils";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useSolanaWallet } from "../../contexts/SolanaWalletContext";
|
||||
import useIsWalletReady from "../../hooks/useIsWalletReady";
|
||||
import useMetaplexData from "../../hooks/useMetaplexData";
|
||||
import useSolanaTokenMap from "../../hooks/useSolanaTokenMap";
|
||||
import { MIGRATION_PROGRAM_ADDRESS, SOLANA_HOST } from "../../utils/consts";
|
||||
import {
|
||||
getMultipleAccounts,
|
||||
shortenAddress,
|
||||
signSendAndConfirm,
|
||||
} from "../../utils/solana";
|
||||
import ButtonWithLoader from "../ButtonWithLoader";
|
||||
import ShowTx from "../ShowTx";
|
||||
import SolanaCreateAssociatedAddress, {
|
||||
useAssociatedAccountExistsState,
|
||||
} from "../SolanaCreateAssociatedAddress";
|
||||
import SolanaWalletKey from "../SolanaWalletKey";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
mainPaper: {
|
||||
textAlign: "center",
|
||||
padding: "2rem",
|
||||
"& > h, p ": {
|
||||
margin: "1rem",
|
||||
},
|
||||
},
|
||||
divider: {
|
||||
margin: "2rem 0rem 2rem 0rem",
|
||||
},
|
||||
spacer: {
|
||||
height: "2rem",
|
||||
},
|
||||
}));
|
||||
|
||||
//TODO move to utils/solana
|
||||
const getDecimals = async (
|
||||
connection: Connection,
|
||||
mint: string,
|
||||
setter: (decimals: number | undefined) => void
|
||||
) => {
|
||||
setter(undefined);
|
||||
if (mint) {
|
||||
try {
|
||||
const pk = new PublicKey(mint);
|
||||
const info = await connection.getParsedAccountInfo(pk);
|
||||
// @ts-ignore
|
||||
const decimals = info.value?.data.parsed.info.decimals;
|
||||
setter(decimals);
|
||||
} catch (e) {
|
||||
console.log(`Unable to determine decimals of ${mint}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//TODO move to utils/solana
|
||||
const getBalance = async (
|
||||
connection: Connection,
|
||||
address: string | undefined,
|
||||
setter: (balance: string | undefined) => void
|
||||
) => {
|
||||
setter(undefined);
|
||||
if (address) {
|
||||
try {
|
||||
const pk = new PublicKey(address);
|
||||
const info = await connection.getParsedAccountInfo(pk);
|
||||
// @ts-ignore
|
||||
const balance = info.value?.data.parsed.info.tokenAmount.uiAmountString;
|
||||
console.log(`${address} has a balance of ${balance}`);
|
||||
setter(balance);
|
||||
} catch (e) {
|
||||
console.log(`Unable to determine balance of ${address}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default function Workflow({
|
||||
fromMint,
|
||||
toMint,
|
||||
}: {
|
||||
fromMint: string;
|
||||
toMint: string;
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
const connection = useMemo(
|
||||
() => new Connection(SOLANA_HOST, "confirmed"),
|
||||
[]
|
||||
); //TODO confirmed or finalized?
|
||||
const wallet = useSolanaWallet();
|
||||
const { isReady } = useIsWalletReady(CHAIN_ID_SOLANA);
|
||||
const solanaTokenMap = useSolanaTokenMap();
|
||||
const metaplexArray = useMemo(() => [fromMint, toMint], [fromMint, toMint]);
|
||||
const metaplexData = useMetaplexData(metaplexArray);
|
||||
|
||||
const [poolAddress, setPoolAddress] = useState("");
|
||||
const [poolExists, setPoolExists] = useState<boolean | undefined>(undefined);
|
||||
const [fromTokenAccount, setFromTokenAccount] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [fromTokenAccountBalance, setFromTokenAccountBalance] = useState<
|
||||
string | undefined
|
||||
>(undefined);
|
||||
const [toTokenAccount, setToTokenAccount] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [toTokenAccountBalance, setToTokenAccountBalance] = useState<
|
||||
string | undefined
|
||||
>(undefined);
|
||||
const [fromMintDecimals, setFromMintDecimals] = useState<number | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const {
|
||||
associatedAccountExists: fromTokenAccountExists,
|
||||
//setAssociatedAccountExists: setFromTokenAccountExists,
|
||||
} = useAssociatedAccountExistsState(
|
||||
CHAIN_ID_SOLANA,
|
||||
fromMint,
|
||||
fromTokenAccount
|
||||
);
|
||||
const {
|
||||
associatedAccountExists: toTokenAccountExists,
|
||||
setAssociatedAccountExists: setToTokenAccountExists,
|
||||
} = useAssociatedAccountExistsState(CHAIN_ID_SOLANA, toMint, toTokenAccount);
|
||||
|
||||
const [toCustodyAddress, setToCustodyAddress] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [toCustodyBalance, setToCustodyBalance] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const [migrationAmount, setMigrationAmount] = useState("");
|
||||
const [migrationIsProcessing, setMigrationIsProcessing] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [transaction, setTransaction] = useState<string | null>(null);
|
||||
|
||||
/* Effects
|
||||
*/
|
||||
useEffect(() => {
|
||||
getDecimals(connection, fromMint, setFromMintDecimals);
|
||||
}, [connection, fromMint]);
|
||||
|
||||
//Retrieve user balance when fromTokenAccount changes
|
||||
useEffect(() => {
|
||||
// TODO: cancellable
|
||||
if (fromTokenAccount && fromTokenAccountExists) {
|
||||
getBalance(connection, fromTokenAccount, setFromTokenAccountBalance);
|
||||
} else {
|
||||
setFromTokenAccountBalance(undefined);
|
||||
}
|
||||
}, [
|
||||
connection,
|
||||
fromTokenAccountExists,
|
||||
fromTokenAccount,
|
||||
setFromTokenAccountBalance,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: cancellable
|
||||
if (toTokenAccount && toTokenAccountExists) {
|
||||
getBalance(connection, toTokenAccount, setToTokenAccountBalance);
|
||||
} else {
|
||||
setToTokenAccountBalance(undefined);
|
||||
}
|
||||
}, [
|
||||
connection,
|
||||
toTokenAccountExists,
|
||||
toTokenAccount,
|
||||
setFromTokenAccountBalance,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: cancellable
|
||||
if (toCustodyAddress) {
|
||||
getBalance(connection, toCustodyAddress, setToCustodyBalance);
|
||||
} else {
|
||||
setToCustodyAddress(undefined);
|
||||
}
|
||||
}, [connection, toCustodyAddress, setToCustodyBalance]);
|
||||
|
||||
//Retrieve pool address on selectedTokens change
|
||||
useEffect(() => {
|
||||
if (toMint && fromMint) {
|
||||
setPoolAddress("");
|
||||
setPoolExists(undefined);
|
||||
getPoolAddress(MIGRATION_PROGRAM_ADDRESS, fromMint, toMint).then(
|
||||
(result) => {
|
||||
const key = new PublicKey(result).toString();
|
||||
setPoolAddress(key);
|
||||
},
|
||||
(error) => console.log("Could not calculate pool address.")
|
||||
);
|
||||
}
|
||||
}, [toMint, fromMint, setPoolAddress]);
|
||||
|
||||
//Retrieve the poolAccount every time the pool address changes.
|
||||
useEffect(() => {
|
||||
if (poolAddress) {
|
||||
setPoolExists(undefined);
|
||||
try {
|
||||
getMultipleAccounts(
|
||||
connection,
|
||||
[new PublicKey(poolAddress)],
|
||||
"confirmed"
|
||||
).then((result) => {
|
||||
if (result.length && result[0] !== null) {
|
||||
setPoolExists(true);
|
||||
} else if (result.length && result[0] === null) {
|
||||
setPoolExists(false);
|
||||
setError("There is no swap pool for this token.");
|
||||
} else {
|
||||
setError(
|
||||
"unexpected error in fetching pool address. Please reload and try again"
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
setError("Could not fetch pool address");
|
||||
}
|
||||
}
|
||||
}, [connection, poolAddress]);
|
||||
|
||||
//Set relevant information derived from poolAddress
|
||||
useEffect(() => {
|
||||
getToCustodyAddress(MIGRATION_PROGRAM_ADDRESS, poolAddress).then(
|
||||
(result: any) => setToCustodyAddress(new PublicKey(result).toString())
|
||||
);
|
||||
}, [poolAddress]);
|
||||
|
||||
//Set the associated token accounts when the designated mint changes
|
||||
useEffect(() => {
|
||||
if (wallet?.publicKey && fromMint) {
|
||||
Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
new PublicKey(fromMint),
|
||||
wallet?.publicKey || new PublicKey([])
|
||||
).then(
|
||||
(result) => {
|
||||
setFromTokenAccount(result.toString());
|
||||
},
|
||||
(error) => {}
|
||||
);
|
||||
}
|
||||
}, [fromMint, wallet?.publicKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (wallet?.publicKey && toMint) {
|
||||
Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
new PublicKey(toMint),
|
||||
wallet?.publicKey || new PublicKey([])
|
||||
).then(
|
||||
(result) => {
|
||||
setToTokenAccount(result.toString());
|
||||
},
|
||||
(error) => {}
|
||||
);
|
||||
}
|
||||
}, [toMint, wallet?.publicKey]);
|
||||
/*
|
||||
End effects
|
||||
*/
|
||||
|
||||
const migrateTokens = useCallback(async () => {
|
||||
try {
|
||||
setError("");
|
||||
const instruction = await migrateTokensTx(
|
||||
connection,
|
||||
wallet?.publicKey?.toString() || "",
|
||||
MIGRATION_PROGRAM_ADDRESS,
|
||||
fromMint,
|
||||
toMint,
|
||||
fromTokenAccount || "",
|
||||
toTokenAccount || "",
|
||||
parseUnits(migrationAmount, fromMintDecimals).toBigInt()
|
||||
);
|
||||
setMigrationIsProcessing(true);
|
||||
signSendAndConfirm(wallet, connection, instruction).then(
|
||||
(transaction: any) => {
|
||||
setMigrationIsProcessing(false);
|
||||
setTransaction(transaction);
|
||||
},
|
||||
(error) => {
|
||||
console.log(error);
|
||||
setError("Could not complete the migrateTokens transaction.");
|
||||
setMigrationIsProcessing(false);
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
setError("Could not complete the migrateTokens transaction.");
|
||||
setMigrationIsProcessing(false);
|
||||
}
|
||||
}, [
|
||||
connection,
|
||||
fromMint,
|
||||
fromTokenAccount,
|
||||
migrationAmount,
|
||||
toMint,
|
||||
toTokenAccount,
|
||||
wallet,
|
||||
fromMintDecimals,
|
||||
]);
|
||||
|
||||
const fromParse = (amount: string) => {
|
||||
return parseUnits(amount, fromMintDecimals).toBigInt();
|
||||
};
|
||||
|
||||
const hasRequisiteData = fromMint && toMint && poolAddress && poolExists;
|
||||
const accountsReady =
|
||||
fromTokenAccountExists && toTokenAccountExists && poolExists;
|
||||
const sufficientBalances =
|
||||
toCustodyBalance &&
|
||||
fromTokenAccountBalance &&
|
||||
migrationAmount &&
|
||||
fromParse(migrationAmount) <= fromParse(fromTokenAccountBalance) &&
|
||||
parseFloat(migrationAmount) <= parseFloat(toCustodyBalance);
|
||||
|
||||
console.log("rendered");
|
||||
|
||||
const isReadyToTransfer =
|
||||
isReady && sufficientBalances && accountsReady && hasRequisiteData;
|
||||
|
||||
const getNotReadyCause = () => {
|
||||
if (!fromMint || !toMint || !poolAddress || !poolExists) {
|
||||
return "This asset is not supported.";
|
||||
} else if (!isReady) {
|
||||
return "Wallet is not connected.";
|
||||
} else if (!toTokenAccountExists || !fromTokenAccountExists) {
|
||||
return "You have not created the necessary token accounts.";
|
||||
} else if (!migrationAmount) {
|
||||
return "Enter an amount to transfer.";
|
||||
} else if (!sufficientBalances) {
|
||||
return "There are not sufficient funds for this transfer.";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const handleAmountChange = useCallback(
|
||||
(event) => setMigrationAmount(event.target.value),
|
||||
[setMigrationAmount]
|
||||
);
|
||||
|
||||
const getMetadata = (address: string) => {
|
||||
const tokenMapItem = solanaTokenMap.data?.find(
|
||||
(x) => x.address === address
|
||||
);
|
||||
const metaplexItem = metaplexData.data?.get(address);
|
||||
|
||||
return {
|
||||
symbol: tokenMapItem?.symbol || metaplexItem?.data?.symbol || undefined,
|
||||
name: tokenMapItem?.name || metaplexItem?.data?.name || undefined,
|
||||
logo: tokenMapItem?.logoURI || metaplexItem?.data?.uri || undefined,
|
||||
};
|
||||
};
|
||||
|
||||
const toMetadata = getMetadata(toMint);
|
||||
const fromMetadata = getMetadata(fromMint);
|
||||
|
||||
const toMintPrettyString = toMetadata.symbol
|
||||
? toMetadata.symbol + " (" + shortenAddress(toMint) + ")"
|
||||
: shortenAddress(toMint);
|
||||
const fromMintPrettyString = fromMetadata.symbol
|
||||
? fromMetadata.symbol + " (" + shortenAddress(fromMint) + ")"
|
||||
: shortenAddress(fromMint);
|
||||
|
||||
return (
|
||||
<Container maxWidth="md">
|
||||
<Paper className={classes.mainPaper}>
|
||||
<Typography variant="h5">Migrate Legacy Assets</Typography>
|
||||
<Typography variant="subtitle2">
|
||||
Convert assets from legacy bridges to Wormhole V2 tokens
|
||||
</Typography>
|
||||
<Divider className={classes.divider} />
|
||||
|
||||
<SolanaWalletKey />
|
||||
{fromTokenAccount && toTokenAccount && fromTokenAccountBalance ? (
|
||||
<>
|
||||
<Typography variant="body2">
|
||||
This will migrate {fromMintPrettyString} tokens in this account:
|
||||
</Typography>
|
||||
<Typography variant="h5">
|
||||
{shortenAddress(fromTokenAccount) +
|
||||
` (Balance: ${fromTokenAccountBalance}${
|
||||
fromMetadata.symbol && " " + fromMetadata.symbol
|
||||
})`}
|
||||
</Typography>
|
||||
<div className={classes.spacer} />
|
||||
<Typography variant="body2">
|
||||
into {toMintPrettyString} tokens in this account:
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h5"
|
||||
color={toTokenAccountExists ? "textPrimary" : "textSecondary"}
|
||||
>
|
||||
{shortenAddress(toTokenAccount) +
|
||||
(toTokenAccountExists
|
||||
? ` (Balance: ${toTokenAccountBalance}${
|
||||
(toMetadata.symbol && " " + toMetadata.symbol) || ""
|
||||
})`
|
||||
: " (Not created yet)")}
|
||||
</Typography>
|
||||
<SolanaCreateAssociatedAddress
|
||||
mintAddress={toMint}
|
||||
readableTargetAddress={toTokenAccount}
|
||||
associatedAccountExists={toTokenAccountExists}
|
||||
setAssociatedAccountExists={setToTokenAccountExists}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
<div className={classes.spacer} />
|
||||
<TextField
|
||||
value={migrationAmount}
|
||||
type="number"
|
||||
onChange={handleAmountChange}
|
||||
label={"Amount"}
|
||||
disabled={!!migrationIsProcessing || !!transaction}
|
||||
></TextField>
|
||||
|
||||
{!transaction && (
|
||||
<ButtonWithLoader
|
||||
disabled={!isReadyToTransfer || migrationIsProcessing}
|
||||
showLoader={migrationIsProcessing}
|
||||
onClick={migrateTokens}
|
||||
>
|
||||
{migrationAmount && isReadyToTransfer
|
||||
? "Migrate " + migrationAmount + " Tokens"
|
||||
: "Migrate"}
|
||||
</ButtonWithLoader>
|
||||
)}
|
||||
{(error || !isReadyToTransfer) && (
|
||||
<Typography color="error">{error || getNotReadyCause()}</Typography>
|
||||
)}
|
||||
{transaction ? (
|
||||
<>
|
||||
<Typography>
|
||||
Successfully migrated your tokens! They will be available once
|
||||
this transaction confirms.
|
||||
</Typography>
|
||||
<ShowTx
|
||||
tx={{ id: transaction, block: 1 }}
|
||||
chainId={CHAIN_ID_SOLANA}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</Paper>
|
||||
</Container>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { Typography } from "@material-ui/core";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import { MIGRATION_ASSET_MAP } from "../../utils/consts";
|
||||
import Workflow from "./Workflow";
|
||||
import { withRouter } from "react-router";
|
||||
|
||||
interface RouteParams {
|
||||
legacyAsset: string;
|
||||
}
|
||||
|
||||
interface Migration extends RouteComponentProps<RouteParams> {}
|
||||
|
||||
const MigrationRoot: React.FC<Migration> = (props) => {
|
||||
const legacyAsset: string = props.match.params.legacyAsset;
|
||||
const targetAsset: string | undefined = MIGRATION_ASSET_MAP.get(legacyAsset);
|
||||
|
||||
let fromMint: string | undefined = "";
|
||||
let toMint: string | undefined = "";
|
||||
try {
|
||||
fromMint = legacyAsset && new PublicKey(legacyAsset).toString();
|
||||
toMint = targetAsset && new PublicKey(targetAsset).toString();
|
||||
} catch (e) {}
|
||||
|
||||
if (fromMint && toMint) {
|
||||
return <Workflow fromMint={fromMint} toMint={toMint} />;
|
||||
} else {
|
||||
return <Typography>This asset is not eligible for migration.</Typography>;
|
||||
}
|
||||
};
|
||||
|
||||
export default withRouter(MigrationRoot);
|
|
@ -40,7 +40,7 @@ export default function ShowTx({
|
|||
|
||||
return (
|
||||
<div className={classes.tx}>
|
||||
<Typography component="div" variant="body2">
|
||||
<Typography noWrap component="div" variant="body2">
|
||||
{tx.id}
|
||||
</Typography>
|
||||
{showExplorerLink && explorerAddress ? (
|
||||
|
|
|
@ -15,7 +15,7 @@ import ButtonWithLoader from "./ButtonWithLoader";
|
|||
export function useAssociatedAccountExistsState(
|
||||
targetChain: ChainId,
|
||||
mintAddress: string | null | undefined,
|
||||
readableTargetAddress: string
|
||||
readableTargetAddress: string | undefined
|
||||
) {
|
||||
const [associatedAccountExists, setAssociatedAccountExists] = useState(true); // for now, assume it exists until we confirm it doesn't
|
||||
const solanaWallet = useSolanaWallet();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Connection } from "@solana/web3.js";
|
||||
import { useLayoutEffect, useState } from "react";
|
||||
import { useLayoutEffect, useMemo, useState } from "react";
|
||||
import { DataWrapper } from "../store/helpers";
|
||||
import { SOLANA_HOST } from "../utils/consts";
|
||||
import {
|
||||
|
@ -97,12 +97,16 @@ const useMetaplexData = (
|
|||
};
|
||||
}, [addresses, setResults, setIsLoading, setError]);
|
||||
|
||||
return {
|
||||
data: results,
|
||||
isFetching: isLoading,
|
||||
error,
|
||||
receivedAt,
|
||||
};
|
||||
const output = useMemo(
|
||||
() => ({
|
||||
data: results,
|
||||
isFetching: isLoading,
|
||||
error,
|
||||
receivedAt,
|
||||
}),
|
||||
[results, isLoading, error, receivedAt]
|
||||
);
|
||||
return output;
|
||||
};
|
||||
|
||||
export default useMetaplexData;
|
||||
|
|
|
@ -210,3 +210,17 @@ export const ETH_TOKENS_THAT_EXIST_ELSEWHERE = [
|
|||
getAddress("0x1c5db575e2ff833e46a2e9864c22f4b22e0b37c2"), // renZEC
|
||||
getAddress("0xD5147bc8e386d91Cc5DBE72099DAC6C9b99276F5"), // renFIL
|
||||
];
|
||||
|
||||
export const MIGRATION_PROGRAM_ADDRESS =
|
||||
process.env.REACT_APP_CLUSTER === "mainnet"
|
||||
? "whmRZnmyxdr2TkHXcZoFdtvNYRLQ5Jtbkf6ZbGkJjdk"
|
||||
: process.env.REACT_APP_CLUSTER === "testnet"
|
||||
? ""
|
||||
: "Ex9bCdVMSfx7EzB3pgSi2R4UHwJAXvTw18rBQm5YQ8gK";
|
||||
|
||||
export const MIGRATION_ASSET_MAP = new Map<string, string>([
|
||||
[
|
||||
"2WDq7wSs9zYrpx2kbHDA4RUTRch2CCTP6ZWaH4GNfnQQ",
|
||||
"ApgUoB1467PXXofoLWFELH2Kz9DKB8WXdU2szGSsFKhX",
|
||||
],
|
||||
]);
|
||||
|
|
|
@ -2,7 +2,7 @@ import { clusterApiUrl } from "@solana/web3.js";
|
|||
|
||||
export const MIGRATION_PROGRAM_ADDRESS =
|
||||
process.env.REACT_APP_CLUSTER === "mainnet"
|
||||
? ""
|
||||
? "whmRZnmyxdr2TkHXcZoFdtvNYRLQ5Jtbkf6ZbGkJjdk"
|
||||
: process.env.REACT_APP_CLUSTER === "testnet"
|
||||
? ""
|
||||
: "Ex9bCdVMSfx7EzB3pgSi2R4UHwJAXvTw18rBQm5YQ8gK";
|
||||
|
|
Loading…
Reference in New Issue