bridge_ui: support multiple evm bridges

Change-Id: I3c416955e3e01707eec29404a483b1c223bffef4
This commit is contained in:
Evan Gray 2021-10-05 18:47:14 -04:00
parent 659b7b2547
commit 4bdb714594
43 changed files with 600 additions and 316 deletions

View File

@ -1,4 +1,3 @@
import { CHAIN_ID_ETH } from "@certusone/wormhole-sdk";
import { makeStyles, MenuItem, TextField, Typography } from "@material-ui/core"; import { makeStyles, MenuItem, TextField, Typography } from "@material-ui/core";
import { Alert } from "@material-ui/lab"; import { Alert } from "@material-ui/lab";
import { useCallback, useMemo } from "react"; import { useCallback, useMemo } from "react";
@ -12,6 +11,7 @@ import {
selectAttestTargetChain, selectAttestTargetChain,
} from "../../store/selectors"; } from "../../store/selectors";
import { CHAINS, CHAINS_BY_ID } from "../../utils/consts"; import { CHAINS, CHAINS_BY_ID } from "../../utils/consts";
import { isEVMChain } from "../../utils/ethereum";
import ButtonWithLoader from "../ButtonWithLoader"; import ButtonWithLoader from "../ButtonWithLoader";
import KeyAndBalance from "../KeyAndBalance"; import KeyAndBalance from "../KeyAndBalance";
import LowBalanceWarning from "../LowBalanceWarning"; import LowBalanceWarning from "../LowBalanceWarning";
@ -64,8 +64,11 @@ function Target() {
You will have to pay transaction fees on{" "} You will have to pay transaction fees on{" "}
{CHAINS_BY_ID[targetChain].name} to attest this token.{" "} {CHAINS_BY_ID[targetChain].name} to attest this token.{" "}
</Typography> </Typography>
{targetChain === CHAIN_ID_ETH && ( {isEVMChain(targetChain) && (
<EthGasEstimateSummary methodType="createWrapped" /> <EthGasEstimateSummary
methodType="createWrapped"
chainId={targetChain}
/>
)} )}
</Alert> </Alert>
<LowBalanceWarning chainId={targetChain} /> <LowBalanceWarning chainId={targetChain} />

View File

@ -1,10 +1,10 @@
import { import {
ChainId, ChainId,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
CHAIN_ID_TERRA, CHAIN_ID_TERRA,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import { Typography } from "@material-ui/core"; import { Typography } from "@material-ui/core";
import { isEVMChain } from "../utils/ethereum";
import EthereumSignerKey from "./EthereumSignerKey"; import EthereumSignerKey from "./EthereumSignerKey";
import SolanaWalletKey from "./SolanaWalletKey"; import SolanaWalletKey from "./SolanaWalletKey";
import TerraWalletKey from "./TerraWalletKey"; import TerraWalletKey from "./TerraWalletKey";
@ -17,7 +17,7 @@ function KeyAndBalance({
balance?: string; balance?: string;
}) { }) {
const balanceString = balance ? "Balance: " + balance : balance; const balanceString = balance ? "Balance: " + balance : balance;
if (chainId === CHAIN_ID_ETH) { if (isEVMChain(chainId)) {
return ( return (
<> <>
<EthereumSignerKey /> <EthereumSignerKey />

View File

@ -1,13 +1,9 @@
import { import { ChainId } from "@certusone/wormhole-sdk";
ChainId, import { makeStyles, Typography } from "@material-ui/core";
CHAIN_ID_ETH,
CHAIN_ID_SOLANA,
} from "@certusone/wormhole-sdk";
import { Typography } from "@material-ui/core";
import { Alert } from "@material-ui/lab"; import { Alert } from "@material-ui/lab";
import { makeStyles } from "@material-ui/core";
import useTransactionFees from "../hooks/useTransactionFees";
import useIsWalletReady from "../hooks/useIsWalletReady"; import useIsWalletReady from "../hooks/useIsWalletReady";
import useTransactionFees from "../hooks/useTransactionFees";
import { getDefaultNativeCurrencySymbol } from "../utils/consts";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
alert: { alert: {
@ -24,9 +20,9 @@ function LowBalanceWarning({ chainId }: { chainId: ChainId }) {
isReady && isReady &&
transactionFeeWarning.balanceString && transactionFeeWarning.balanceString &&
transactionFeeWarning.isSufficientBalance === false; transactionFeeWarning.isSufficientBalance === false;
const warningMessage = `This wallet has a very low ${ const warningMessage = `This wallet has a very low ${getDefaultNativeCurrencySymbol(
chainId === CHAIN_ID_SOLANA ? "SOL" : chainId === CHAIN_ID_ETH ? "ETH" : "" chainId
} balance and may not be able to pay for the upcoming transaction fees.`; )} balance and may not be able to pay for the upcoming transaction fees.`;
const content = ( const content = (
<Alert severity="warning" className={classes.alert}> <Alert severity="warning" className={classes.alert}>

View File

@ -1,6 +1,5 @@
import { import {
CHAIN_ID_BSC, ChainId,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
getEmitterAddressEth, getEmitterAddressEth,
getEmitterAddressSolana, getEmitterAddressSolana,
@ -41,13 +40,14 @@ import {
selectNFTSourceChain, selectNFTSourceChain,
} from "../../store/selectors"; } from "../../store/selectors";
import { import {
CHAINS, CHAINS_WITH_NFT_SUPPORT,
ETH_BRIDGE_ADDRESS, getBridgeAddressForChain,
ETH_NFT_BRIDGE_ADDRESS, getNFTBridgeAddressForChain,
SOLANA_HOST, SOLANA_HOST,
SOL_NFT_BRIDGE_ADDRESS, SOL_NFT_BRIDGE_ADDRESS,
WORMHOLE_RPC_HOSTS, WORMHOLE_RPC_HOSTS,
} from "../../utils/consts"; } from "../../utils/consts";
import { isEVMChain } from "../../utils/ethereum";
import { getSignedVAAWithRetry } from "../../utils/getSignedVAAWithRetry"; import { getSignedVAAWithRetry } from "../../utils/getSignedVAAWithRetry";
import parseError from "../../utils/parseError"; import parseError from "../../utils/parseError";
import KeyAndBalance from "../KeyAndBalance"; import KeyAndBalance from "../KeyAndBalance";
@ -60,17 +60,23 @@ const useStyles = makeStyles((theme) => ({
}, },
})); }));
async function eth( async function evm(
provider: ethers.providers.Web3Provider, provider: ethers.providers.Web3Provider,
tx: string, tx: string,
enqueueSnackbar: any enqueueSnackbar: any,
chainId: ChainId
) { ) {
try { try {
const receipt = await provider.getTransactionReceipt(tx); const receipt = await provider.getTransactionReceipt(tx);
const sequence = parseSequenceFromLogEth(receipt, ETH_BRIDGE_ADDRESS); const sequence = parseSequenceFromLogEth(
const emitterAddress = getEmitterAddressEth(ETH_NFT_BRIDGE_ADDRESS); receipt,
getBridgeAddressForChain(chainId)
);
const emitterAddress = getEmitterAddressEth(
getNFTBridgeAddressForChain(chainId)
);
const { vaaBytes } = await getSignedVAAWithRetry( const { vaaBytes } = await getSignedVAAWithRetry(
CHAIN_ID_ETH, chainId,
emitterAddress, emitterAddress,
sequence.toString(), sequence.toString(),
WORMHOLE_RPC_HOSTS.length WORMHOLE_RPC_HOSTS.length
@ -137,14 +143,15 @@ function RecoveryDialogContent({
useEffect(() => { useEffect(() => {
if (recoverySourceTx) { if (recoverySourceTx) {
let cancelled = false; let cancelled = false;
if (recoverySourceChain === CHAIN_ID_ETH && provider) { if (isEVMChain(recoverySourceChain) && provider) {
setRecoverySourceTxError(""); setRecoverySourceTxError("");
setRecoverySourceTxIsLoading(true); setRecoverySourceTxIsLoading(true);
(async () => { (async () => {
const { vaa, error } = await eth( const { vaa, error } = await evm(
provider, provider,
recoverySourceTx, recoverySourceTx,
enqueueSnackbar enqueueSnackbar,
recoverySourceChain
); );
if (!cancelled) { if (!cancelled) {
setRecoverySourceTxIsLoading(false); setRecoverySourceTxIsLoading(false);
@ -259,16 +266,13 @@ function RecoveryDialogContent({
fullWidth fullWidth
margin="normal" margin="normal"
> >
{CHAINS.filter( {CHAINS_WITH_NFT_SUPPORT.map(({ id, name }) => (
({ id }) => id === CHAIN_ID_ETH || id === CHAIN_ID_SOLANA
).map(({ id, name }) => (
<MenuItem key={id} value={id}> <MenuItem key={id} value={id}>
{name} {name}
</MenuItem> </MenuItem>
))} ))}
</TextField> </TextField>
{recoverySourceChain === CHAIN_ID_ETH || {isEVMChain(recoverySourceChain) ? (
recoverySourceChain === CHAIN_ID_BSC ? (
<KeyAndBalance chainId={recoverySourceChain} /> <KeyAndBalance chainId={recoverySourceChain} />
) : null} ) : null}
<TextField <TextField

View File

@ -1,9 +1,11 @@
import { 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 { Restore, VerifiedUser } from "@material-ui/icons"; import { Restore, VerifiedUser } from "@material-ui/icons";
import { Alert } from "@material-ui/lab";
import { useCallback } from "react"; import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { Link } from "react-router-dom";
import useIsWalletReady from "../../hooks/useIsWalletReady"; import useIsWalletReady from "../../hooks/useIsWalletReady";
import { incrementStep, setSourceChain } from "../../store/nftSlice";
import { import {
selectNFTIsSourceComplete, selectNFTIsSourceComplete,
selectNFTShouldLockFields, selectNFTShouldLockFields,
@ -11,15 +13,13 @@ import {
selectNFTSourceChain, selectNFTSourceChain,
selectNFTSourceError, selectNFTSourceError,
} from "../../store/selectors"; } from "../../store/selectors";
import { incrementStep, setSourceChain } from "../../store/nftSlice"; import { CHAINS_WITH_NFT_SUPPORT } from "../../utils/consts";
import { CHAINS } from "../../utils/consts"; import { isEVMChain } from "../../utils/ethereum";
import ButtonWithLoader from "../ButtonWithLoader"; import ButtonWithLoader from "../ButtonWithLoader";
import KeyAndBalance from "../KeyAndBalance"; import KeyAndBalance from "../KeyAndBalance";
import LowBalanceWarning from "../LowBalanceWarning";
import StepDescription from "../StepDescription"; import StepDescription from "../StepDescription";
import { TokenSelector } from "../TokenSelectors/SourceTokenSelector"; import { TokenSelector } from "../TokenSelectors/SourceTokenSelector";
import { Alert } from "@material-ui/lab";
import LowBalanceWarning from "../LowBalanceWarning";
import { Link } from "react-router-dom";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
transferField: { transferField: {
@ -94,15 +94,13 @@ function Source({
onChange={handleSourceChange} onChange={handleSourceChange}
disabled={shouldLockFields} disabled={shouldLockFields}
> >
{CHAINS.filter( {CHAINS_WITH_NFT_SUPPORT.map(({ id, name }) => (
({ id }) => id === CHAIN_ID_ETH || id === CHAIN_ID_SOLANA
).map(({ id, name }) => (
<MenuItem key={id} value={id}> <MenuItem key={id} value={id}>
{name} {name}
</MenuItem> </MenuItem>
))} ))}
</TextField> </TextField>
{sourceChain === CHAIN_ID_ETH ? ( {isEVMChain(sourceChain) ? (
<Alert severity="info"> <Alert severity="info">
Only NFTs which implement ERC-721 are supported. Only NFTs which implement ERC-721 are supported.
</Alert> </Alert>

View File

@ -1,5 +1,4 @@
import { import {
CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
hexToNativeString, hexToNativeString,
hexToUint8Array, hexToUint8Array,
@ -27,7 +26,8 @@ import {
selectNFTTargetChain, selectNFTTargetChain,
selectNFTTargetError, selectNFTTargetError,
} from "../../store/selectors"; } from "../../store/selectors";
import { CHAINS, CHAINS_BY_ID } from "../../utils/consts"; import { CHAINS_BY_ID, CHAINS_WITH_NFT_SUPPORT } from "../../utils/consts";
import { isEVMChain } from "../../utils/ethereum";
import ButtonWithLoader from "../ButtonWithLoader"; import ButtonWithLoader from "../ButtonWithLoader";
import KeyAndBalance from "../KeyAndBalance"; import KeyAndBalance from "../KeyAndBalance";
import LowBalanceWarning from "../LowBalanceWarning"; import LowBalanceWarning from "../LowBalanceWarning";
@ -48,7 +48,7 @@ function Target() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const sourceChain = useSelector(selectNFTSourceChain); const sourceChain = useSelector(selectNFTSourceChain);
const chains = useMemo( const chains = useMemo(
() => CHAINS.filter((c) => c.id !== sourceChain), () => CHAINS_WITH_NFT_SUPPORT.filter((c) => c.id !== sourceChain),
[sourceChain] [sourceChain]
); );
const targetChain = useSelector(selectNFTTargetChain); const targetChain = useSelector(selectNFTTargetChain);
@ -93,15 +93,12 @@ function Target() {
fullWidth fullWidth
value={targetChain} value={targetChain}
onChange={handleTargetChange} onChange={handleTargetChange}
disabled={true}
> >
{chains {chains.map(({ id, name }) => (
.filter(({ id }) => id === CHAIN_ID_ETH || id === CHAIN_ID_SOLANA) <MenuItem key={id} value={id}>
.map(({ id, name }) => ( {name}
<MenuItem key={id} value={id}> </MenuItem>
{name} ))}
</MenuItem>
))}
</TextField> </TextField>
<KeyAndBalance chainId={targetChain} balance={uiAmountString} /> <KeyAndBalance chainId={targetChain} balance={uiAmountString} />
<TextField <TextField
@ -120,7 +117,7 @@ function Target() {
value={targetAsset || ""} value={targetAsset || ""}
disabled={true} disabled={true}
/> />
{targetChain === CHAIN_ID_ETH ? ( {isEVMChain(targetChain) ? (
<TextField <TextField
label="TokenId" label="TokenId"
fullWidth fullWidth
@ -136,8 +133,8 @@ function Target() {
You will have to pay transaction fees on{" "} You will have to pay transaction fees on{" "}
{CHAINS_BY_ID[targetChain].name} to redeem your NFT. {CHAINS_BY_ID[targetChain].name} to redeem your NFT.
</Typography> </Typography>
{targetChain === CHAIN_ID_ETH && ( {isEVMChain(targetChain) && (
<EthGasEstimateSummary methodType="nft" /> <EthGasEstimateSummary methodType="nft" chainId={targetChain} />
)} )}
</Alert> </Alert>
<LowBalanceWarning chainId={targetChain} /> <LowBalanceWarning chainId={targetChain} />

View File

@ -1,4 +1,4 @@
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk"; import { CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
import { makeStyles, Typography } from "@material-ui/core"; import { makeStyles, Typography } from "@material-ui/core";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { import {
@ -9,6 +9,7 @@ import {
selectNFTTargetChain, selectNFTTargetChain,
selectNFTTransferTx, selectNFTTransferTx,
} from "../../store/selectors"; } from "../../store/selectors";
import { isEVMChain } from "../../utils/ethereum";
import { WAITING_FOR_WALLET_AND_CONF } from "../Transfer/WaitingForWalletMessage"; import { WAITING_FOR_WALLET_AND_CONF } from "../Transfer/WaitingForWalletMessage";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
@ -33,7 +34,7 @@ export default function WaitingForWalletMessage() {
{WAITING_FOR_WALLET_AND_CONF}{" "} {WAITING_FOR_WALLET_AND_CONF}{" "}
{targetChain === CHAIN_ID_SOLANA && isRedeeming {targetChain === CHAIN_ID_SOLANA && isRedeeming
? "Note: there will be several transactions" ? "Note: there will be several transactions"
: sourceChain === CHAIN_ID_ETH && isSending : isEVMChain(sourceChain) && isSending
? "Note: there will be two transactions" ? "Note: there will be two transactions"
: null} : null}
</Typography> </Typography>

View File

@ -30,15 +30,16 @@ import { getMetaplexData } from "../hooks/useMetaplexData";
import { COLORS } from "../muiTheme"; import { COLORS } from "../muiTheme";
import { NFTParsedTokenAccount } from "../store/nftSlice"; import { NFTParsedTokenAccount } from "../store/nftSlice";
import { import {
CHAINS,
CHAINS_BY_ID, CHAINS_BY_ID,
ETH_NFT_BRIDGE_ADDRESS, CHAINS_WITH_NFT_SUPPORT,
getNFTBridgeAddressForChain,
SOLANA_HOST, SOLANA_HOST,
SOL_NFT_BRIDGE_ADDRESS, SOL_NFT_BRIDGE_ADDRESS,
} from "../utils/consts"; } from "../utils/consts";
import { import {
ethNFTToNFTParsedTokenAccount, ethNFTToNFTParsedTokenAccount,
getEthereumNFT, getEthereumNFT,
isEVMChain,
isNFT, isNFT,
isValidEthereumAddress, isValidEthereumAddress,
} from "../utils/ethereum"; } from "../utils/ethereum";
@ -119,7 +120,7 @@ export default function NFTOriginVerifier() {
isReady && isReady &&
provider && provider &&
signerAddress && signerAddress &&
lookupChain === CHAIN_ID_ETH && isEVMChain(lookupChain) &&
lookupAsset && lookupAsset &&
lookupTokenId lookupTokenId
) { ) {
@ -136,10 +137,11 @@ export default function NFTOriginVerifier() {
signerAddress signerAddress
); );
const info = await getOriginalAssetEth( const info = await getOriginalAssetEth(
ETH_NFT_BRIDGE_ADDRESS, getNFTBridgeAddressForChain(lookupChain),
provider, provider,
lookupAsset, lookupAsset,
lookupTokenId lookupTokenId,
lookupChain
); );
if (!cancelled) { if (!cancelled) {
setIsLoading(false); setIsLoading(false);
@ -225,7 +227,7 @@ export default function NFTOriginVerifier() {
originInfo.chainId originInfo.chainId
); );
const displayError = const displayError =
(lookupChain === CHAIN_ID_ETH && statusMessage) || lookupError; (isEVMChain(lookupChain) && statusMessage) || lookupError;
return ( return (
<div> <div>
<Container maxWidth="md"> <Container maxWidth="md">
@ -249,15 +251,13 @@ export default function NFTOriginVerifier() {
fullWidth fullWidth
margin="normal" margin="normal"
> >
{CHAINS.filter( {CHAINS_WITH_NFT_SUPPORT.map(({ id, name }) => (
({ id }) => id === CHAIN_ID_ETH || id === CHAIN_ID_SOLANA
).map(({ id, name }) => (
<MenuItem key={id} value={id}> <MenuItem key={id} value={id}>
{name} {name}
</MenuItem> </MenuItem>
))} ))}
</TextField> </TextField>
{lookupChain === CHAIN_ID_ETH || lookupChain === CHAIN_ID_BSC ? ( {isEVMChain(lookupChain) ? (
<KeyAndBalance chainId={lookupChain} /> <KeyAndBalance chainId={lookupChain} />
) : null} ) : null}
<TextField <TextField
@ -267,7 +267,7 @@ export default function NFTOriginVerifier() {
value={lookupAsset} value={lookupAsset}
onChange={handleAssetChange} onChange={handleAssetChange}
/> />
{lookupChain === CHAIN_ID_ETH ? ( {isEVMChain(lookupChain) ? (
<TextField <TextField
fullWidth fullWidth
margin="normal" margin="normal"
@ -318,6 +318,16 @@ export default function NFTOriginVerifier() {
> >
View on Solscan View on Solscan
</Button> </Button>
) : originInfo.chainId === CHAIN_ID_BSC ? (
<Button
href={`https://bscscan.com/token/${readableAddress}?a=${originInfo.tokenId}`}
target="_blank"
endIcon={<Launch />}
className={classes.viewButton}
variant="outlined"
>
View on BscScan
</Button>
) : ( ) : (
<Button <Button
href={`https://opensea.io/assets/${readableAddress}/${originInfo.tokenId}`} href={`https://opensea.io/assets/${readableAddress}/${originInfo.tokenId}`}

View File

@ -1,12 +1,13 @@
import { import {
ChainId, ChainId,
CHAIN_ID_BSC,
CHAIN_ID_ETH, CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
CHAIN_ID_TERRA, CHAIN_ID_TERRA,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import { Button, makeStyles, Typography } from "@material-ui/core"; import { Button, makeStyles, Typography } from "@material-ui/core";
import { Transaction } from "../store/transferSlice"; import { Transaction } from "../store/transferSlice";
import { CLUSTER } from "../utils/consts"; import { CLUSTER, getExplorerName } from "../utils/consts";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
tx: { tx: {
@ -36,6 +37,8 @@ export default function ShowTx({
? `https://${CLUSTER === "testnet" ? "goerli." : ""}etherscan.io/tx/${ ? `https://${CLUSTER === "testnet" ? "goerli." : ""}etherscan.io/tx/${
tx?.id tx?.id
}` }`
: chainId === CHAIN_ID_BSC
? `https://bscscan.com/tx/${tx?.id}`
: chainId === CHAIN_ID_SOLANA : chainId === CHAIN_ID_SOLANA
? `https://explorer.solana.com/tx/${tx?.id}${ ? `https://explorer.solana.com/tx/${tx?.id}${
CLUSTER === "testnet" CLUSTER === "testnet"
@ -53,12 +56,7 @@ export default function ShowTx({
: "columbus-5" : "columbus-5"
}/tx/${tx?.id}` }/tx/${tx?.id}`
: undefined; : undefined;
const explorerName = const explorerName = getExplorerName(chainId);
chainId === CHAIN_ID_ETH
? "Etherscan"
: chainId === CHAIN_ID_TERRA
? "Finder"
: "Explorer";
return ( return (
<div className={classes.tx}> <div className={classes.tx}>

View File

@ -1,5 +1,6 @@
import { import {
ChainId, ChainId,
CHAIN_ID_BSC,
CHAIN_ID_ETH, CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
CHAIN_ID_TERRA, CHAIN_ID_TERRA,
@ -10,7 +11,7 @@ import { withStyles } from "@material-ui/styles";
import clsx from "clsx"; import clsx from "clsx";
import useCopyToClipboard from "../hooks/useCopyToClipboard"; import useCopyToClipboard from "../hooks/useCopyToClipboard";
import { ParsedTokenAccount } from "../store/transferSlice"; import { ParsedTokenAccount } from "../store/transferSlice";
import { CLUSTER } from "../utils/consts"; import { CLUSTER, getExplorerName } from "../utils/consts";
import { shortenAddress } from "../utils/solana"; import { shortenAddress } from "../utils/solana";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
@ -86,6 +87,8 @@ export default function SmartAddress({
? `https://${ ? `https://${
CLUSTER === "testnet" ? "goerli." : "" CLUSTER === "testnet" ? "goerli." : ""
}etherscan.io/address/${useableAddress}` }etherscan.io/address/${useableAddress}`
: chainId === CHAIN_ID_BSC
? `https://bscscan.com/address/${useableAddress}`
: chainId === CHAIN_ID_SOLANA : chainId === CHAIN_ID_SOLANA
? `https://explorer.solana.com/address/${useableAddress}${ ? `https://explorer.solana.com/address/${useableAddress}${
CLUSTER === "testnet" CLUSTER === "testnet"
@ -103,12 +106,7 @@ export default function SmartAddress({
: "columbus-5" : "columbus-5"
}/address/${useableAddress}` }/address/${useableAddress}`
: undefined; : undefined;
const explorerName = const explorerName = getExplorerName(chainId);
chainId === CHAIN_ID_ETH
? "Etherscan"
: chainId === CHAIN_ID_TERRA
? "Finder"
: "Explorer";
const copyToClipboard = useCopyToClipboard(useableAddress); const copyToClipboard = useCopyToClipboard(useableAddress);

View File

@ -30,7 +30,7 @@ import { NFTParsedTokenAccount } from "../../store/nftSlice";
import NFTViewer from "./NFTViewer"; import NFTViewer from "./NFTViewer";
import { useDebounce } from "use-debounce/lib"; import { useDebounce } from "use-debounce/lib";
import RefreshButtonWrapper from "./RefreshButtonWrapper"; import RefreshButtonWrapper from "./RefreshButtonWrapper";
import { CHAIN_ID_ETH } from "@certusone/wormhole-sdk"; import { ChainId, CHAIN_ID_ETH } from "@certusone/wormhole-sdk";
import { sortParsedTokenAccounts } from "../../utils/sort"; import { sortParsedTokenAccounts } from "../../utils/sort";
const useStyles = makeStyles((theme) => const useStyles = makeStyles((theme) =>
@ -83,7 +83,10 @@ const getLogo = (account: ParsedTokenAccount | null) => {
return account.logo; return account.logo;
}; };
const isWormholev1 = (provider: any, address: string) => { const isWormholev1 = (provider: any, address: string, chainId: ChainId) => {
if (chainId !== CHAIN_ID_ETH) {
return Promise.resolve(false);
}
const connection = WormholeAbi__factory.connect( const connection = WormholeAbi__factory.connect(
WORMHOLE_V1_ETH_ADDRESS, WORMHOLE_V1_ETH_ADDRESS,
provider provider
@ -102,6 +105,7 @@ type EthereumSourceTokenSelectorProps = {
tokenAccounts: DataWrapper<ParsedTokenAccount[]> | undefined; tokenAccounts: DataWrapper<ParsedTokenAccount[]> | undefined;
disabled: boolean; disabled: boolean;
resetAccounts: (() => void) | undefined; resetAccounts: (() => void) | undefined;
chainId: ChainId;
nft?: boolean; nft?: boolean;
}; };
@ -186,6 +190,7 @@ export default function EthereumSourceTokenSelector(
tokenAccounts, tokenAccounts,
disabled, disabled,
resetAccounts, resetAccounts,
chainId,
nft, nft,
} = props; } = props;
const classes = useStyles(); const classes = useStyles();
@ -258,7 +263,7 @@ export default function EthereumSourceTokenSelector(
onChange(autocompleteHolder); onChange(autocompleteHolder);
return; return;
} }
isWormholev1(provider, autocompleteHolder.mintKey).then( isWormholev1(provider, autocompleteHolder.mintKey, chainId).then(
(result) => { (result) => {
if (!cancelled) { if (!cancelled) {
result result
@ -282,7 +287,7 @@ export default function EthereumSourceTokenSelector(
cancelled = true; cancelled = true;
}; };
} }
}, [autocompleteHolder, provider, advancedMode, onChange, nft]); }, [autocompleteHolder, provider, advancedMode, onChange, nft, chainId]);
//This effect watches the advancedModeString, and checks that the selected asset is valid before putting //This effect watches the advancedModeString, and checks that the selected asset is valid before putting
// it on the state. // it on the state.
@ -353,7 +358,8 @@ export default function EthereumSourceTokenSelector(
//Validate that the token is not a wormhole v1 asset //Validate that the token is not a wormhole v1 asset
const isWormholePromise = isWormholev1( const isWormholePromise = isWormholev1(
provider, provider,
advancedModeHolderString advancedModeHolderString,
chainId
).then( ).then(
(result) => { (result) => {
if (result && !cancelled) { if (result && !cancelled) {
@ -430,6 +436,7 @@ export default function EthereumSourceTokenSelector(
onChange, onChange,
nft, nft,
advancedModeHolderTokenId, advancedModeHolderTokenId,
chainId,
]); ]);
const handleClick = useCallback(() => { const handleClick = useCallback(() => {
@ -563,7 +570,7 @@ export default function EthereumSourceTokenSelector(
const content = value ? ( const content = value ? (
<> <>
{nft ? ( {nft ? (
<NFTViewer value={value} chainId={CHAIN_ID_ETH} /> <NFTViewer value={value} chainId={chainId} />
) : ( ) : (
<RefreshButtonWrapper callback={resetAccountWrapper}> <RefreshButtonWrapper callback={resetAccountWrapper}>
<Typography> <Typography>

View File

@ -13,10 +13,12 @@ import { NFTParsedTokenAccount } from "../../store/nftSlice";
import clsx from "clsx"; import clsx from "clsx";
import { import {
ChainId, ChainId,
CHAIN_ID_BSC,
CHAIN_ID_ETH, CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import SmartAddress from "../SmartAddress"; import SmartAddress from "../SmartAddress";
import bscIcon from "../../icons/bsc.svg";
import ethIcon from "../../icons/eth.svg"; import ethIcon from "../../icons/eth.svg";
import solanaIcon from "../../icons/solana.svg"; import solanaIcon from "../../icons/solana.svg";
import useCopyToClipboard from "../../hooks/useCopyToClipboard"; import useCopyToClipboard from "../../hooks/useCopyToClipboard";
@ -53,6 +55,18 @@ const LogoIcon = ({ chainId }: { chainId: ChainId }) =>
src={ethIcon} src={ethIcon}
alt="Ethereum" alt="Ethereum"
/> />
) : chainId === CHAIN_ID_BSC ? (
<Avatar
style={{
backgroundColor: "rgb(20, 21, 26)",
height: "1em",
width: "1em",
marginLeft: "4px",
padding: "2px",
}}
src={bscIcon}
alt="Binance Smart Chain"
/>
) : null; ) : null;
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
@ -127,6 +141,12 @@ const useStyles = makeStyles((theme) => ({
background: 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(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%)",
}, },
bsc: {
// color from binance background rgb(20, 21, 26), 2 and 1 tint lighter
backgroundColor: "#F0B90B",
background:
"linear-gradient(160deg, rgb(20, 21, 26) 0%, #4A4D57 33%, rgb(20, 21, 26) 66%, #2C2F3B 100%)",
},
solana: { solana: {
// colors from https://solana.com/branding/new/exchange/exchange-sq-black.svg // colors from https://solana.com/branding/new/exchange/exchange-sq-black.svg
backgroundColor: "rgb(153,69,255)", backgroundColor: "rgb(153,69,255)",
@ -211,6 +231,7 @@ export default function NFTViewer({
<div <div
className={clsx(classes.cardInset, { className={clsx(classes.cardInset, {
[classes.eth]: chainId === CHAIN_ID_ETH, [classes.eth]: chainId === CHAIN_ID_ETH,
[classes.bsc]: chainId === CHAIN_ID_BSC,
[classes.solana]: chainId === CHAIN_ID_SOLANA, [classes.solana]: chainId === CHAIN_ID_SOLANA,
})} })}
> >

View File

@ -1,14 +1,14 @@
//import Autocomplete from '@material-ui/lab/Autocomplete'; //import Autocomplete from '@material-ui/lab/Autocomplete';
import { import { CHAIN_ID_SOLANA, CHAIN_ID_TERRA } from "@certusone/wormhole-sdk";
CHAIN_ID_ETH,
CHAIN_ID_SOLANA,
CHAIN_ID_TERRA,
} from "@certusone/wormhole-sdk";
import { TextField, Typography } from "@material-ui/core"; import { TextField, Typography } from "@material-ui/core";
import { useCallback } from "react"; import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import useGetSourceParsedTokens from "../../hooks/useGetSourceParsedTokenAccounts"; import useGetSourceParsedTokens from "../../hooks/useGetSourceParsedTokenAccounts";
import useIsWalletReady from "../../hooks/useIsWalletReady"; import useIsWalletReady from "../../hooks/useIsWalletReady";
import {
setSourceParsedTokenAccount as setNFTSourceParsedTokenAccount,
setSourceWalletAddress as setNFTSourceWalletAddress,
} from "../../store/nftSlice";
import { import {
selectNFTSourceChain, selectNFTSourceChain,
selectNFTSourceParsedTokenAccount, selectNFTSourceParsedTokenAccount,
@ -20,14 +20,11 @@ import {
setSourceParsedTokenAccount as setTransferSourceParsedTokenAccount, setSourceParsedTokenAccount as setTransferSourceParsedTokenAccount,
setSourceWalletAddress as setTransferSourceWalletAddress, setSourceWalletAddress as setTransferSourceWalletAddress,
} from "../../store/transferSlice"; } from "../../store/transferSlice";
import { import { isEVMChain } from "../../utils/ethereum";
setSourceParsedTokenAccount as setNFTSourceParsedTokenAccount,
setSourceWalletAddress as setNFTSourceWalletAddress,
} from "../../store/nftSlice";
import EthereumSourceTokenSelector from "./EthereumSourceTokenSelector"; import EthereumSourceTokenSelector from "./EthereumSourceTokenSelector";
import RefreshButtonWrapper from "./RefreshButtonWrapper";
import SolanaSourceTokenSelector from "./SolanaSourceTokenSelector"; import SolanaSourceTokenSelector from "./SolanaSourceTokenSelector";
import TerraSourceTokenSelector from "./TerraSourceTokenSelector"; import TerraSourceTokenSelector from "./TerraSourceTokenSelector";
import RefreshButtonWrapper from "./RefreshButtonWrapper";
type TokenSelectorProps = { type TokenSelectorProps = {
disabled: boolean; disabled: boolean;
@ -78,7 +75,7 @@ export const TokenSelector = (props: TokenSelectorProps) => {
//This is only for errors so bad that we shouldn't even mount the component //This is only for errors so bad that we shouldn't even mount the component
const fatalError = const fatalError =
lookupChain !== CHAIN_ID_ETH && isEVMChain(lookupChain) &&
lookupChain !== CHAIN_ID_TERRA && lookupChain !== CHAIN_ID_TERRA &&
maps?.tokenAccounts?.error; //Terra & ETH can proceed because it has advanced mode maps?.tokenAccounts?.error; //Terra & ETH can proceed because it has advanced mode
@ -96,7 +93,7 @@ export const TokenSelector = (props: TokenSelectorProps) => {
resetAccounts={maps?.resetAccounts} resetAccounts={maps?.resetAccounts}
nft={nft} nft={nft}
/> />
) : lookupChain === CHAIN_ID_ETH ? ( ) : isEVMChain(lookupChain) ? (
<EthereumSourceTokenSelector <EthereumSourceTokenSelector
value={sourceParsedTokenAccount || null} value={sourceParsedTokenAccount || null}
disabled={disabled} disabled={disabled}
@ -104,6 +101,7 @@ export const TokenSelector = (props: TokenSelectorProps) => {
covalent={maps?.covalent || undefined} covalent={maps?.covalent || undefined}
tokenAccounts={maps?.tokenAccounts} tokenAccounts={maps?.tokenAccounts}
resetAccounts={maps?.resetAccounts} resetAccounts={maps?.resetAccounts}
chainId={lookupChain}
nft={nft} nft={nft}
/> />
) : lookupChain === CHAIN_ID_TERRA ? ( ) : lookupChain === CHAIN_ID_TERRA ? (

View File

@ -9,6 +9,7 @@ import { useEffect, useState } from "react";
import { useEthereumProvider } from "../contexts/EthereumProviderContext"; import { useEthereumProvider } from "../contexts/EthereumProviderContext";
import { Transaction } from "../store/transferSlice"; import { Transaction } from "../store/transferSlice";
import { CHAINS_BY_ID, SOLANA_HOST } from "../utils/consts"; import { CHAINS_BY_ID, SOLANA_HOST } from "../utils/consts";
import { isEVMChain } from "../utils/ethereum";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
root: { root: {
@ -34,7 +35,7 @@ export default function TransactionProgress({
const [currentBlock, setCurrentBlock] = useState(0); const [currentBlock, setCurrentBlock] = useState(0);
useEffect(() => { useEffect(() => {
if (isSendComplete || !tx) return; if (isSendComplete || !tx) return;
if (chainId === CHAIN_ID_ETH && provider) { if (isEVMChain(chainId) && provider) {
let cancelled = false; let cancelled = false;
(async () => { (async () => {
while (!cancelled) { while (!cancelled) {
@ -73,7 +74,7 @@ export default function TransactionProgress({
chainId === CHAIN_ID_SOLANA ? 32 : chainId === CHAIN_ID_ETH ? 15 : 1; chainId === CHAIN_ID_SOLANA ? 32 : chainId === CHAIN_ID_ETH ? 15 : 1;
if ( if (
!isSendComplete && !isSendComplete &&
(chainId === CHAIN_ID_SOLANA || chainId === CHAIN_ID_ETH) && (chainId === CHAIN_ID_SOLANA || isEVMChain(chainId)) &&
blockDiff !== undefined blockDiff !== undefined
) { ) {
return ( return (

View File

@ -1,4 +1,3 @@
import { CHAIN_ID_ETH } from "@certusone/wormhole-sdk";
import { Button, makeStyles } from "@material-ui/core"; import { Button, makeStyles } from "@material-ui/core";
import detectEthereumProvider from "@metamask/detect-provider"; import detectEthereumProvider from "@metamask/detect-provider";
import { useCallback } from "react"; import { useCallback } from "react";
@ -8,9 +7,11 @@ import {
selectTransferTargetAsset, selectTransferTargetAsset,
selectTransferTargetChain, selectTransferTargetChain,
} from "../../store/selectors"; } from "../../store/selectors";
import { getEvmChainId } from "../../utils/consts";
import { import {
ethTokenToParsedTokenAccount, ethTokenToParsedTokenAccount,
getEthereumToken, getEthereumToken,
isEVMChain,
} from "../../utils/ethereum"; } from "../../utils/ethereum";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
@ -24,9 +25,14 @@ export default function AddToMetamask() {
const classes = useStyles(); const classes = useStyles();
const targetChain = useSelector(selectTransferTargetChain); const targetChain = useSelector(selectTransferTargetChain);
const targetAsset = useSelector(selectTransferTargetAsset); const targetAsset = useSelector(selectTransferTargetAsset);
const { provider, signerAddress } = useEthereumProvider(); const {
provider,
signerAddress,
chainId: evmChainId,
} = useEthereumProvider();
const hasCorrectEvmNetwork = evmChainId === getEvmChainId(targetChain);
const handleClick = useCallback(() => { const handleClick = useCallback(() => {
if (provider && targetAsset && signerAddress) { if (provider && targetAsset && signerAddress && hasCorrectEvmNetwork) {
(async () => { (async () => {
try { try {
const token = await getEthereumToken(targetAsset, provider); const token = await getEthereumToken(targetAsset, provider);
@ -52,11 +58,12 @@ export default function AddToMetamask() {
} }
})(); })();
} }
}, [provider, targetAsset, signerAddress]); }, [provider, targetAsset, signerAddress, hasCorrectEvmNetwork]);
return provider && return provider &&
signerAddress && signerAddress &&
targetAsset && targetAsset &&
targetChain === CHAIN_ID_ETH ? ( isEVMChain(targetChain) &&
hasCorrectEvmNetwork ? (
<Button <Button
onClick={handleClick} onClick={handleClick}
size="small" size="small"

View File

@ -1,6 +1,5 @@
import { import {
CHAIN_ID_BSC, ChainId,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
CHAIN_ID_TERRA, CHAIN_ID_TERRA,
getEmitterAddressEth, getEmitterAddressEth,
@ -50,14 +49,15 @@ import {
} from "../../store/transferSlice"; } from "../../store/transferSlice";
import { import {
CHAINS, CHAINS,
ETH_BRIDGE_ADDRESS, getBridgeAddressForChain,
ETH_TOKEN_BRIDGE_ADDRESS, getTokenBridgeAddressForChain,
SOLANA_HOST, SOLANA_HOST,
SOL_TOKEN_BRIDGE_ADDRESS, SOL_TOKEN_BRIDGE_ADDRESS,
TERRA_HOST, TERRA_HOST,
TERRA_TOKEN_BRIDGE_ADDRESS, TERRA_TOKEN_BRIDGE_ADDRESS,
WORMHOLE_RPC_HOSTS, WORMHOLE_RPC_HOSTS,
} from "../../utils/consts"; } from "../../utils/consts";
import { isEVMChain } from "../../utils/ethereum";
import { getSignedVAAWithRetry } from "../../utils/getSignedVAAWithRetry"; import { getSignedVAAWithRetry } from "../../utils/getSignedVAAWithRetry";
import parseError from "../../utils/parseError"; import parseError from "../../utils/parseError";
import KeyAndBalance from "../KeyAndBalance"; import KeyAndBalance from "../KeyAndBalance";
@ -70,17 +70,23 @@ const useStyles = makeStyles((theme) => ({
}, },
})); }));
async function eth( async function evm(
provider: ethers.providers.Web3Provider, provider: ethers.providers.Web3Provider,
tx: string, tx: string,
enqueueSnackbar: any enqueueSnackbar: any,
chainId: ChainId
) { ) {
try { try {
const receipt = await provider.getTransactionReceipt(tx); const receipt = await provider.getTransactionReceipt(tx);
const sequence = parseSequenceFromLogEth(receipt, ETH_BRIDGE_ADDRESS); const sequence = parseSequenceFromLogEth(
const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS); receipt,
getBridgeAddressForChain(chainId)
);
const emitterAddress = getEmitterAddressEth(
getTokenBridgeAddressForChain(chainId)
);
const { vaaBytes } = await getSignedVAAWithRetry( const { vaaBytes } = await getSignedVAAWithRetry(
CHAIN_ID_ETH, chainId,
emitterAddress, emitterAddress,
sequence.toString(), sequence.toString(),
WORMHOLE_RPC_HOSTS.length WORMHOLE_RPC_HOSTS.length
@ -172,14 +178,15 @@ function RecoveryDialogContent({
useEffect(() => { useEffect(() => {
if (recoverySourceTx) { if (recoverySourceTx) {
let cancelled = false; let cancelled = false;
if (recoverySourceChain === CHAIN_ID_ETH && provider) { if (isEVMChain(recoverySourceChain) && provider) {
setRecoverySourceTxError(""); setRecoverySourceTxError("");
setRecoverySourceTxIsLoading(true); setRecoverySourceTxIsLoading(true);
(async () => { (async () => {
const { vaa, error } = await eth( const { vaa, error } = await evm(
provider, provider,
recoverySourceTx, recoverySourceTx,
enqueueSnackbar enqueueSnackbar,
recoverySourceChain
); );
if (!cancelled) { if (!cancelled) {
setRecoverySourceTxIsLoading(false); setRecoverySourceTxIsLoading(false);
@ -315,8 +322,7 @@ function RecoveryDialogContent({
</MenuItem> </MenuItem>
))} ))}
</TextField> </TextField>
{recoverySourceChain === CHAIN_ID_ETH || {isEVMChain(recoverySourceChain) ? (
recoverySourceChain === CHAIN_ID_BSC ? (
<KeyAndBalance chainId={recoverySourceChain} /> <KeyAndBalance chainId={recoverySourceChain} />
) : null} ) : null}
<TextField <TextField

View File

@ -1,4 +1,5 @@
import { import {
CHAIN_ID_BSC,
CHAIN_ID_ETH, CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
WSOL_ADDRESS, WSOL_ADDRESS,
@ -12,7 +13,7 @@ import {
selectTransferTargetAsset, selectTransferTargetAsset,
selectTransferTargetChain, selectTransferTargetChain,
} from "../../store/selectors"; } from "../../store/selectors";
import { WETH_ADDRESS } from "../../utils/consts"; import { WBNB_ADDRESS, WETH_ADDRESS } from "../../utils/consts";
import ButtonWithLoader from "../ButtonWithLoader"; import ButtonWithLoader from "../ButtonWithLoader";
import KeyAndBalance from "../KeyAndBalance"; import KeyAndBalance from "../KeyAndBalance";
import StepDescription from "../StepDescription"; import StepDescription from "../StepDescription";
@ -29,11 +30,15 @@ function Redeem() {
targetChain === CHAIN_ID_ETH && targetChain === CHAIN_ID_ETH &&
targetAsset && targetAsset &&
targetAsset.toLowerCase() === WETH_ADDRESS.toLowerCase(); targetAsset.toLowerCase() === WETH_ADDRESS.toLowerCase();
const isBscNative =
targetChain === CHAIN_ID_BSC &&
targetAsset &&
targetAsset.toLowerCase() === WBNB_ADDRESS.toLowerCase();
const isSolNative = const isSolNative =
targetChain === CHAIN_ID_SOLANA && targetChain === CHAIN_ID_SOLANA &&
targetAsset && targetAsset &&
targetAsset === WSOL_ADDRESS; targetAsset === WSOL_ADDRESS;
const isNativeEligible = isEthNative || isSolNative; const isNativeEligible = isEthNative || isBscNative || isSolNative;
const [useNativeRedeem, setUseNativeRedeem] = useState(true); const [useNativeRedeem, setUseNativeRedeem] = useState(true);
const toggleNativeRedeem = useCallback(() => { const toggleNativeRedeem = useCallback(() => {
setUseNativeRedeem(!useNativeRedeem); setUseNativeRedeem(!useNativeRedeem);

View File

@ -1,4 +1,3 @@
import { CHAIN_ID_ETH } from "@certusone/wormhole-sdk";
import { Checkbox, FormControlLabel } from "@material-ui/core"; import { Checkbox, FormControlLabel } from "@material-ui/core";
import { Alert } from "@material-ui/lab"; import { Alert } from "@material-ui/lab";
import { ethers } from "ethers"; import { ethers } from "ethers";
@ -19,6 +18,7 @@ import {
selectTransferTransferTx, selectTransferTransferTx,
} from "../../store/selectors"; } from "../../store/selectors";
import { CHAINS_BY_ID } from "../../utils/consts"; import { CHAINS_BY_ID } from "../../utils/consts";
import { isEVMChain } from "../../utils/ethereum";
import ButtonWithLoader from "../ButtonWithLoader"; import ButtonWithLoader from "../ButtonWithLoader";
import KeyAndBalance from "../KeyAndBalance"; import KeyAndBalance from "../KeyAndBalance";
import ShowTx from "../ShowTx"; import ShowTx from "../ShowTx";
@ -70,8 +70,7 @@ function Send() {
approveAmount, approveAmount,
} = useAllowance(sourceChain, sourceAsset, sourceAmountParsed || undefined); } = useAllowance(sourceChain, sourceAsset, sourceAmountParsed || undefined);
const approveButtonNeeded = const approveButtonNeeded = isEVMChain(sourceChain) && !sufficientAllowance;
sourceChain === CHAIN_ID_ETH && !sufficientAllowance;
const notOne = shouldApproveUnlimited || sourceAmountParsed !== oneParsed; const notOne = shouldApproveUnlimited || sourceAmountParsed !== oneParsed;
const isDisabled = const isDisabled =
!isReady || !isReady ||

View File

@ -1,8 +1,4 @@
import { import { CHAIN_ID_SOLANA, hexToNativeString } from "@certusone/wormhole-sdk";
CHAIN_ID_SOLANA,
CHAIN_ID_ETH,
hexToNativeString,
} from "@certusone/wormhole-sdk";
import { makeStyles, MenuItem, TextField, Typography } from "@material-ui/core"; import { makeStyles, MenuItem, TextField, Typography } from "@material-ui/core";
import { Alert } from "@material-ui/lab"; import { Alert } from "@material-ui/lab";
import { useCallback, useMemo } from "react"; import { useCallback, useMemo } from "react";
@ -12,6 +8,7 @@ import useMetadata from "../../hooks/useMetadata";
import useSyncTargetAddress from "../../hooks/useSyncTargetAddress"; import useSyncTargetAddress from "../../hooks/useSyncTargetAddress";
import { EthGasEstimateSummary } from "../../hooks/useTransactionFees"; import { EthGasEstimateSummary } from "../../hooks/useTransactionFees";
import { import {
selectTransferAmount,
selectTransferIsTargetComplete, selectTransferIsTargetComplete,
selectTransferShouldLockFields, selectTransferShouldLockFields,
selectTransferSourceChain, selectTransferSourceChain,
@ -21,10 +18,10 @@ import {
selectTransferTargetChain, selectTransferTargetChain,
selectTransferTargetError, selectTransferTargetError,
UNREGISTERED_ERROR_MESSAGE, UNREGISTERED_ERROR_MESSAGE,
selectTransferAmount,
} from "../../store/selectors"; } from "../../store/selectors";
import { incrementStep, setTargetChain } from "../../store/transferSlice"; import { incrementStep, setTargetChain } from "../../store/transferSlice";
import { CHAINS, CHAINS_BY_ID } from "../../utils/consts"; import { CHAINS, CHAINS_BY_ID } from "../../utils/consts";
import { isEVMChain } from "../../utils/ethereum";
import ButtonWithLoader from "../ButtonWithLoader"; import ButtonWithLoader from "../ButtonWithLoader";
import KeyAndBalance from "../KeyAndBalance"; import KeyAndBalance from "../KeyAndBalance";
import LowBalanceWarning from "../LowBalanceWarning"; import LowBalanceWarning from "../LowBalanceWarning";
@ -152,8 +149,8 @@ function Target() {
You will have to pay transaction fees on{" "} You will have to pay transaction fees on{" "}
{CHAINS_BY_ID[targetChain].name} to redeem your tokens. {CHAINS_BY_ID[targetChain].name} to redeem your tokens.
</Typography> </Typography>
{targetChain === CHAIN_ID_ETH && ( {isEVMChain(targetChain) && (
<EthGasEstimateSummary methodType="transfer" /> <EthGasEstimateSummary methodType="transfer" chainId={targetChain} />
)} )}
</Alert> </Alert>
<LowBalanceWarning chainId={targetChain} /> <LowBalanceWarning chainId={targetChain} />

View File

@ -1,16 +1,12 @@
import { import { approveEth, ChainId, getAllowanceEth } from "@certusone/wormhole-sdk";
approveEth,
ChainId,
CHAIN_ID_ETH,
getAllowanceEth,
} from "@certusone/wormhole-sdk";
import { BigNumber } from "ethers"; import { BigNumber } from "ethers";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { useEthereumProvider } from "../contexts/EthereumProviderContext"; import { useEthereumProvider } from "../contexts/EthereumProviderContext";
import { selectTransferIsApproving } from "../store/selectors"; import { selectTransferIsApproving } from "../store/selectors";
import { setIsApproving } from "../store/transferSlice"; import { setIsApproving } from "../store/transferSlice";
import { ETH_TOKEN_BRIDGE_ADDRESS } from "../utils/consts"; import { getTokenBridgeAddressForChain } from "../utils/consts";
import { isEVMChain } from "../utils/ethereum";
export default function useAllowance( export default function useAllowance(
chainId: ChainId, chainId: ChainId,
@ -23,19 +19,18 @@ export default function useAllowance(
const isApproveProcessing = useSelector(selectTransferIsApproving); const isApproveProcessing = useSelector(selectTransferIsApproving);
const { signer } = useEthereumProvider(); const { signer } = useEthereumProvider();
const sufficientAllowance = const sufficientAllowance =
chainId !== CHAIN_ID_ETH || !isEVMChain(chainId) ||
(allowance && transferAmount && allowance >= transferAmount); (allowance && transferAmount && allowance >= transferAmount);
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
if ( if (isEVMChain(chainId) && tokenAddress && signer && !isApproveProcessing) {
chainId === CHAIN_ID_ETH &&
tokenAddress &&
signer &&
!isApproveProcessing
) {
setIsAllowanceFetching(true); setIsAllowanceFetching(true);
getAllowanceEth(ETH_TOKEN_BRIDGE_ADDRESS, tokenAddress, signer).then( getAllowanceEth(
getTokenBridgeAddressForChain(chainId),
tokenAddress,
signer
).then(
(result) => { (result) => {
if (!cancelled) { if (!cancelled) {
setIsAllowanceFetching(false); setIsAllowanceFetching(false);
@ -57,14 +52,14 @@ export default function useAllowance(
}, [chainId, tokenAddress, signer, isApproveProcessing]); }, [chainId, tokenAddress, signer, isApproveProcessing]);
const approveAmount: (amount: BigInt) => Promise<any> = useMemo(() => { const approveAmount: (amount: BigInt) => Promise<any> = useMemo(() => {
return chainId !== CHAIN_ID_ETH || !tokenAddress || !signer return !isEVMChain(chainId) || !tokenAddress || !signer
? (amount: BigInt) => { ? (amount: BigInt) => {
return Promise.resolve(); return Promise.resolve();
} }
: (amount: BigInt) => { : (amount: BigInt) => {
dispatch(setIsApproving(true)); dispatch(setIsApproving(true));
return approveEth( return approveEth(
ETH_TOKEN_BRIDGE_ADDRESS, getTokenBridgeAddressForChain(chainId),
tokenAddress, tokenAddress,
signer, signer,
BigNumber.from(amount) BigNumber.from(amount)

View File

@ -1,13 +1,12 @@
import { import {
ChainId, ChainId,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
CHAIN_ID_TERRA, CHAIN_ID_TERRA,
getOriginalAssetEth, getOriginalAssetEth,
getOriginalAssetSol, getOriginalAssetSol,
getOriginalAssetTerra, getOriginalAssetTerra,
WormholeWrappedInfo,
uint8ArrayToHex, uint8ArrayToHex,
WormholeWrappedInfo,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import { import {
getOriginalAssetEth as getOriginalAssetEthNFT, getOriginalAssetEth as getOriginalAssetEthNFT,
@ -18,6 +17,7 @@ import { LCDClient } from "@terra-money/terra.js";
import { useEffect } from "react"; import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { useEthereumProvider } from "../contexts/EthereumProviderContext"; import { useEthereumProvider } from "../contexts/EthereumProviderContext";
import { setSourceWormholeWrappedInfo as setNFTSourceWormholeWrappedInfo } from "../store/nftSlice";
import { import {
selectNFTSourceAsset, selectNFTSourceAsset,
selectNFTSourceChain, selectNFTSourceChain,
@ -25,16 +25,16 @@ import {
selectTransferSourceAsset, selectTransferSourceAsset,
selectTransferSourceChain, selectTransferSourceChain,
} from "../store/selectors"; } from "../store/selectors";
import { setSourceWormholeWrappedInfo as setNFTSourceWormholeWrappedInfo } from "../store/nftSlice";
import { setSourceWormholeWrappedInfo as setTransferSourceWormholeWrappedInfo } from "../store/transferSlice"; import { setSourceWormholeWrappedInfo as setTransferSourceWormholeWrappedInfo } from "../store/transferSlice";
import { import {
ETH_NFT_BRIDGE_ADDRESS, getNFTBridgeAddressForChain,
ETH_TOKEN_BRIDGE_ADDRESS, getTokenBridgeAddressForChain,
SOLANA_HOST, SOLANA_HOST,
SOL_NFT_BRIDGE_ADDRESS, SOL_NFT_BRIDGE_ADDRESS,
SOL_TOKEN_BRIDGE_ADDRESS, SOL_TOKEN_BRIDGE_ADDRESS,
TERRA_HOST, TERRA_HOST,
} from "../utils/consts"; } from "../utils/consts";
import { isEVMChain } from "../utils/ethereum";
export interface StateSafeWormholeWrappedInfo { export interface StateSafeWormholeWrappedInfo {
isWrapped: boolean; isWrapped: boolean;
@ -74,19 +74,21 @@ function useCheckIfWormholeWrapped(nft?: boolean) {
dispatch(setSourceWormholeWrappedInfo(undefined)); dispatch(setSourceWormholeWrappedInfo(undefined));
let cancelled = false; let cancelled = false;
(async () => { (async () => {
if (sourceChain === CHAIN_ID_ETH && provider && sourceAsset) { if (isEVMChain(sourceChain) && provider && sourceAsset) {
const wrappedInfo = makeStateSafe( const wrappedInfo = makeStateSafe(
await (nft await (nft
? getOriginalAssetEthNFT( ? getOriginalAssetEthNFT(
ETH_NFT_BRIDGE_ADDRESS, getNFTBridgeAddressForChain(sourceChain),
provider, provider,
sourceAsset, sourceAsset,
tokenId tokenId,
sourceChain
) )
: getOriginalAssetEth( : getOriginalAssetEth(
ETH_TOKEN_BRIDGE_ADDRESS, getTokenBridgeAddressForChain(sourceChain),
provider, provider,
sourceAsset sourceAsset,
sourceChain
)) ))
); );
if (!cancelled) { if (!cancelled) {

View File

@ -1,4 +1,4 @@
import { CHAIN_ID_ETH } from "@certusone/wormhole-sdk"; import { ChainId } from "@certusone/wormhole-sdk";
import { ethers } from "@certusone/wormhole-sdk/node_modules/ethers"; import { ethers } from "@certusone/wormhole-sdk/node_modules/ethers";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { import {
@ -6,9 +6,10 @@ import {
useEthereumProvider, useEthereumProvider,
} from "../contexts/EthereumProviderContext"; } from "../contexts/EthereumProviderContext";
import { DataWrapper } from "../store/helpers"; import { DataWrapper } from "../store/helpers";
import { isEVMChain } from "../utils/ethereum";
import useIsWalletReady from "./useIsWalletReady"; import useIsWalletReady from "./useIsWalletReady";
export type EthMetadata = { export type EvmMetadata = {
symbol?: string; symbol?: string;
logo?: string; logo?: string;
tokenName?: string; tokenName?: string;
@ -28,7 +29,7 @@ const handleError = () => {
const fetchSingleMetadata = async ( const fetchSingleMetadata = async (
address: string, address: string,
provider: Provider provider: Provider
): Promise<EthMetadata> => { ): Promise<EvmMetadata> => {
const contract = new ethers.Contract(address, ERC20_BASIC_ABI, provider); const contract = new ethers.Contract(address, ERC20_BASIC_ABI, provider);
const [name, symbol, decimals] = await Promise.all([ const [name, symbol, decimals] = await Promise.all([
contract.name().catch(handleError), contract.name().catch(handleError),
@ -39,12 +40,12 @@ const fetchSingleMetadata = async (
}; };
const fetchEthMetadata = async (addresses: string[], provider: Provider) => { const fetchEthMetadata = async (addresses: string[], provider: Provider) => {
const promises: Promise<EthMetadata>[] = []; const promises: Promise<EvmMetadata>[] = [];
addresses.forEach((address) => { addresses.forEach((address) => {
promises.push(fetchSingleMetadata(address, provider)); promises.push(fetchSingleMetadata(address, provider));
}); });
const resultsArray = await Promise.all(promises); const resultsArray = await Promise.all(promises);
const output = new Map<string, EthMetadata>(); const output = new Map<string, EvmMetadata>();
addresses.forEach((address, index) => { addresses.forEach((address, index) => {
output.set(address, resultsArray[index]); output.set(address, resultsArray[index]);
}); });
@ -52,19 +53,20 @@ const fetchEthMetadata = async (addresses: string[], provider: Provider) => {
return output; return output;
}; };
function useEthMetadata( function useEvmMetadata(
addresses: string[] addresses: string[],
): DataWrapper<Map<string, EthMetadata>> { chainId: ChainId
const { isReady } = useIsWalletReady(CHAIN_ID_ETH); ): DataWrapper<Map<string, EvmMetadata>> {
const { isReady } = useIsWalletReady(chainId);
const { provider } = useEthereumProvider(); const { provider } = useEthereumProvider();
const [isFetching, setIsFetching] = useState(false); const [isFetching, setIsFetching] = useState(false);
const [error, setError] = useState(""); const [error, setError] = useState("");
const [data, setData] = useState<Map<string, EthMetadata> | null>(null); const [data, setData] = useState<Map<string, EvmMetadata> | null>(null);
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
if (addresses.length && provider && isReady) { if (addresses.length && provider && isReady && isEVMChain(chainId)) {
setIsFetching(true); setIsFetching(true);
setError(""); setError("");
setData(null); setData(null);
@ -86,7 +88,7 @@ function useEthMetadata(
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [addresses, provider, isReady]); }, [addresses, provider, isReady, chainId]);
return useMemo( return useMemo(
() => ({ () => ({
@ -99,4 +101,4 @@ function useEthMetadata(
); );
} }
export default useEthMetadata; export default useEvmMetadata;

View File

@ -1,5 +1,4 @@
import { import {
CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
CHAIN_ID_TERRA, CHAIN_ID_TERRA,
getForeignAssetEth, getForeignAssetEth,
@ -33,14 +32,16 @@ import {
} from "../store/selectors"; } from "../store/selectors";
import { setTargetAsset as setTransferTargetAsset } from "../store/transferSlice"; import { setTargetAsset as setTransferTargetAsset } from "../store/transferSlice";
import { import {
ETH_NFT_BRIDGE_ADDRESS, getEvmChainId,
ETH_TOKEN_BRIDGE_ADDRESS, getNFTBridgeAddressForChain,
getTokenBridgeAddressForChain,
SOLANA_HOST, SOLANA_HOST,
SOL_NFT_BRIDGE_ADDRESS, SOL_NFT_BRIDGE_ADDRESS,
SOL_TOKEN_BRIDGE_ADDRESS, SOL_TOKEN_BRIDGE_ADDRESS,
TERRA_HOST, TERRA_HOST,
TERRA_TOKEN_BRIDGE_ADDRESS, TERRA_TOKEN_BRIDGE_ADDRESS,
} from "../utils/consts"; } from "../utils/consts";
import { isEVMChain } from "../utils/ethereum";
function useFetchTargetAsset(nft?: boolean) { function useFetchTargetAsset(nft?: boolean) {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -61,32 +62,35 @@ function useFetchTargetAsset(nft?: boolean) {
nft ? selectNFTTargetChain : selectTransferTargetChain nft ? selectNFTTargetChain : selectTransferTargetChain
); );
const setTargetAsset = nft ? setNFTTargetAsset : setTransferTargetAsset; const setTargetAsset = nft ? setNFTTargetAsset : setTransferTargetAsset;
const { provider } = useEthereumProvider(); const { provider, chainId: evmChainId } = useEthereumProvider();
const correctEvmNetwork = getEvmChainId(targetChain);
const hasCorrectEvmNetwork = evmChainId === correctEvmNetwork;
useEffect(() => { useEffect(() => {
if (isSourceAssetWormholeWrapped && originChain === targetChain) { if (isSourceAssetWormholeWrapped && originChain === targetChain) {
dispatch(setTargetAsset(hexToNativeString(originAsset, originChain))); dispatch(setTargetAsset(hexToNativeString(originAsset, originChain)));
return; return;
} }
// TODO: loading state, error state // TODO: loading state, error state
dispatch(setTargetAsset(undefined));
let cancelled = false; let cancelled = false;
(async () => { (async () => {
if ( if (
targetChain === CHAIN_ID_ETH && isEVMChain(targetChain) &&
provider && provider &&
hasCorrectEvmNetwork &&
originChain && originChain &&
originAsset originAsset
) { ) {
dispatch(setTargetAsset(undefined));
try { try {
const asset = await (nft const asset = await (nft
? getForeignAssetEthNFT( ? getForeignAssetEthNFT(
ETH_NFT_BRIDGE_ADDRESS, getNFTBridgeAddressForChain(targetChain),
provider, provider,
originChain, originChain,
hexToUint8Array(originAsset) hexToUint8Array(originAsset)
) )
: getForeignAssetEth( : getForeignAssetEth(
ETH_TOKEN_BRIDGE_ADDRESS, getTokenBridgeAddressForChain(targetChain),
provider, provider,
originChain, originChain,
hexToUint8Array(originAsset) hexToUint8Array(originAsset)
@ -102,6 +106,7 @@ function useFetchTargetAsset(nft?: boolean) {
} }
} }
if (targetChain === CHAIN_ID_SOLANA && originChain && originAsset) { if (targetChain === CHAIN_ID_SOLANA && originChain && originAsset) {
dispatch(setTargetAsset(undefined));
try { try {
const connection = new Connection(SOLANA_HOST, "confirmed"); const connection = new Connection(SOLANA_HOST, "confirmed");
const asset = await (nft const asset = await (nft
@ -128,6 +133,7 @@ function useFetchTargetAsset(nft?: boolean) {
} }
} }
if (targetChain === CHAIN_ID_TERRA && originChain && originAsset) { if (targetChain === CHAIN_ID_TERRA && originChain && originAsset) {
dispatch(setTargetAsset(undefined));
try { try {
const lcd = new LCDClient(TERRA_HOST); const lcd = new LCDClient(TERRA_HOST);
const asset = await getForeignAssetTerra( const asset = await getForeignAssetTerra(
@ -160,6 +166,7 @@ function useFetchTargetAsset(nft?: boolean) {
nft, nft,
setTargetAsset, setTargetAsset,
tokenId, tokenId,
hasCorrectEvmNetwork,
]); ]);
} }

View File

@ -1,12 +1,11 @@
import { import {
CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
CHAIN_ID_TERRA, CHAIN_ID_TERRA,
TokenImplementation__factory, TokenImplementation__factory,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import { Connection, PublicKey } from "@solana/web3.js"; import { Connection, PublicKey } from "@solana/web3.js";
import { useConnectedWallet } from "@terra-money/wallet-provider";
import { LCDClient } from "@terra-money/terra.js"; import { LCDClient } from "@terra-money/terra.js";
import { useConnectedWallet } from "@terra-money/wallet-provider";
import { formatUnits } from "ethers/lib/utils"; import { formatUnits } from "ethers/lib/utils";
import { useEffect } from "react"; import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
@ -22,9 +21,12 @@ import {
setSourceParsedTokenAccount, setSourceParsedTokenAccount,
setTargetParsedTokenAccount, setTargetParsedTokenAccount,
} from "../store/transferSlice"; } from "../store/transferSlice";
import { SOLANA_HOST, TERRA_HOST } from "../utils/consts"; import { getEvmChainId, SOLANA_HOST, TERRA_HOST } from "../utils/consts";
import { isEVMChain } from "../utils/ethereum";
import { createParsedTokenAccount } from "./useGetSourceParsedTokenAccounts"; import { createParsedTokenAccount } from "./useGetSourceParsedTokenAccounts";
// TODO: we only ever use this for target, could clean up and rename
/** /**
* Fetches the balance of an asset for the connected wallet * Fetches the balance of an asset for the connected wallet
* @param sourceOrTarget determines whether this will fetch balance for the source or target account. Not intended to be switched on the same hook! * @param sourceOrTarget determines whether this will fetch balance for the source or target account. Not intended to be switched on the same hook!
@ -48,7 +50,12 @@ function useGetBalanceEffect(sourceOrTarget: "source" | "target") {
const solanaWallet = useSolanaWallet(); const solanaWallet = useSolanaWallet();
const solPK = solanaWallet?.publicKey; const solPK = solanaWallet?.publicKey;
const terraWallet = useConnectedWallet(); const terraWallet = useConnectedWallet();
const { provider, signerAddress } = useEthereumProvider(); const {
provider,
signerAddress,
chainId: evmChainId,
} = useEthereumProvider();
const hasCorrectEvmNetwork = evmChainId === getEvmChainId(lookupChain);
useEffect(() => { useEffect(() => {
// source is now handled by getsourceparsedtokenaccounts // source is now handled by getsourceparsedtokenaccounts
if (sourceOrTarget === "source") return; if (sourceOrTarget === "source") return;
@ -127,7 +134,12 @@ function useGetBalanceEffect(sourceOrTarget: "source" | "target") {
} }
}); });
} }
if (lookupChain === CHAIN_ID_ETH && provider && signerAddress) { if (
isEVMChain(lookupChain) &&
provider &&
signerAddress &&
hasCorrectEvmNetwork
) {
const token = TokenImplementation__factory.connect(lookupAsset, provider); const token = TokenImplementation__factory.connect(lookupAsset, provider);
token token
.decimals() .decimals()
@ -170,6 +182,7 @@ function useGetBalanceEffect(sourceOrTarget: "source" | "target") {
solPK, solPK,
sourceOrTarget, sourceOrTarget,
terraWallet, terraWallet,
hasCorrectEvmNetwork,
]); ]);
} }

View File

@ -1,4 +1,6 @@
import { import {
ChainId,
CHAIN_ID_BSC,
CHAIN_ID_ETH, CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
CHAIN_ID_TERRA, CHAIN_ID_TERRA,
@ -53,9 +55,12 @@ import {
import { import {
COVALENT_GET_TOKENS_URL, COVALENT_GET_TOKENS_URL,
SOLANA_HOST, SOLANA_HOST,
WBNB_ADDRESS,
WBNB_DECIMALS,
WETH_ADDRESS, WETH_ADDRESS,
WETH_DECIMALS, WETH_DECIMALS,
} from "../utils/consts"; } from "../utils/consts";
import { isEVMChain } from "../utils/ethereum";
import { import {
ExtractedMintInfo, ExtractedMintInfo,
extractMintInfo, extractMintInfo,
@ -205,6 +210,29 @@ const createNativeEthParsedTokenAccount = (
}); });
}; };
const createNativeBscParsedTokenAccount = (
provider: Provider,
signerAddress: string | undefined
) => {
return !(provider && signerAddress)
? Promise.reject()
: provider.getBalance(signerAddress).then((balanceInWei) => {
const balanceInEth = ethers.utils.formatEther(balanceInWei);
return createParsedTokenAccount(
signerAddress, //public key
WBNB_ADDRESS, //Mint key, On the other side this will be WBNB, so this is hopefully a white lie.
balanceInWei.toString(), //amount, in wei
WBNB_DECIMALS, //Luckily both BNB and WBNB have 18 decimals, so this should not be an issue.
parseFloat(balanceInEth), //This loses precision, but is a limitation of the current datamodel. This field is essentially deprecated
balanceInEth.toString(), //This is the actual display field, which has full precision.
"BNB", //A white lie for display purposes
"Binance Coin", //A white lie for display purposes
undefined, //TODO logo
true //isNativeAsset
);
});
};
const createNFTParsedTokenAccountFromCovalent = ( const createNFTParsedTokenAccountFromCovalent = (
walletAddress: string, walletAddress: string,
covalent: CovalentData, covalent: CovalentData,
@ -266,9 +294,10 @@ export type CovalentNFTData = {
const getEthereumAccountsCovalent = async ( const getEthereumAccountsCovalent = async (
walletAddress: string, walletAddress: string,
nft?: boolean nft: boolean,
chainId: ChainId
): Promise<CovalentData[]> => { ): Promise<CovalentData[]> => {
const url = COVALENT_GET_TOKENS_URL(CHAIN_ID_ETH, walletAddress, nft); const url = COVALENT_GET_TOKENS_URL(chainId, walletAddress, nft);
try { try {
const output = [] as CovalentData[]; const output = [] as CovalentData[];
@ -387,12 +416,11 @@ function useGetAvailableTokens(nft: boolean = false) {
const selectedSourceWalletAddress = useSelector( const selectedSourceWalletAddress = useSelector(
nft ? selectNFTSourceWalletAddress : selectSourceWalletAddress nft ? selectNFTSourceWalletAddress : selectSourceWalletAddress
); );
const currentSourceWalletAddress: string | undefined = const currentSourceWalletAddress: string | undefined = isEVMChain(lookupChain)
lookupChain === CHAIN_ID_ETH ? signerAddress
? signerAddress : lookupChain === CHAIN_ID_SOLANA
: lookupChain === CHAIN_ID_SOLANA ? solPK?.toString()
? solPK?.toString() : undefined;
: undefined;
const resetSourceAccounts = useCallback(() => { const resetSourceAccounts = useCallback(() => {
dispatch( dispatch(
@ -539,15 +567,50 @@ function useGetAvailableTokens(nft: boolean = false) {
}; };
}, [lookupChain, provider, signerAddress, nft, ethNativeAccount]); }, [lookupChain, provider, signerAddress, nft, ethNativeAccount]);
//Binance Smart Chain native asset load
useEffect(() => {
let cancelled = false;
if (
signerAddress &&
lookupChain === CHAIN_ID_BSC &&
!ethNativeAccount &&
!nft
) {
setEthNativeAccountLoading(true);
createNativeBscParsedTokenAccount(provider, signerAddress).then(
(result) => {
console.log("create native account returned with value", result);
if (!cancelled) {
setEthNativeAccount(result);
setEthNativeAccountLoading(false);
setEthNativeAccountError("");
}
},
(error) => {
if (!cancelled) {
setEthNativeAccount(undefined);
setEthNativeAccountLoading(false);
setEthNativeAccountError("Unable to retrieve your BSC balance.");
}
}
);
}
return () => {
cancelled = true;
};
}, [lookupChain, provider, signerAddress, nft, ethNativeAccount]);
//Ethereum covalent accounts load //Ethereum covalent accounts load
useEffect(() => { useEffect(() => {
//const testWallet = "0xf60c2ea62edbfe808163751dd0d8693dcb30019c"; //const testWallet = "0xf60c2ea62edbfe808163751dd0d8693dcb30019c";
// const nftTestWallet1 = "0x3f304c6721f35ff9af00fd32650c8e0a982180ab"; // const nftTestWallet1 = "0x3f304c6721f35ff9af00fd32650c8e0a982180ab";
// const nftTestWallet2 = "0x98ed231428088eb440e8edb5cc8d66dcf913b86e"; // const nftTestWallet2 = "0x98ed231428088eb440e8edb5cc8d66dcf913b86e";
// const nftTestWallet3 = "0xb1fadf677a7e9b90e9d4f31c8ffb3dc18c138c6f"; // const nftTestWallet3 = "0xb1fadf677a7e9b90e9d4f31c8ffb3dc18c138c6f";
// const nftBscTestWallet1 = "0x5f464a652bd1991df0be37979b93b3306d64a909";
let cancelled = false; let cancelled = false;
const walletAddress = signerAddress; const walletAddress = signerAddress;
if (walletAddress && lookupChain === CHAIN_ID_ETH && !covalent) { if (walletAddress && isEVMChain(lookupChain) && !covalent) {
//TODO less cancel //TODO less cancel
!cancelled && setCovalentLoading(true); !cancelled && setCovalentLoading(true);
!cancelled && !cancelled &&
@ -556,7 +619,7 @@ function useGetAvailableTokens(nft: boolean = false) {
? fetchSourceParsedTokenAccountsNFT() ? fetchSourceParsedTokenAccountsNFT()
: fetchSourceParsedTokenAccounts() : fetchSourceParsedTokenAccounts()
); );
getEthereumAccountsCovalent(walletAddress, nft).then( getEthereumAccountsCovalent(walletAddress, nft, lookupChain).then(
(accounts) => { (accounts) => {
!cancelled && setCovalentLoading(false); !cancelled && setCovalentLoading(false);
!cancelled && setCovalentError(undefined); !cancelled && setCovalentError(undefined);
@ -639,7 +702,7 @@ function useGetAvailableTokens(nft: boolean = false) {
}, },
resetAccounts: resetSourceAccounts, resetAccounts: resetSourceAccounts,
} }
: lookupChain === CHAIN_ID_ETH : isEVMChain(lookupChain)
? { ? {
tokenAccounts: ethAccounts, tokenAccounts: ethAccounts,
covalent: { covalent: {

View File

@ -2,7 +2,7 @@ import {
attestFromEth, attestFromEth,
attestFromSolana, attestFromSolana,
attestFromTerra, attestFromTerra,
CHAIN_ID_ETH, ChainId,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
CHAIN_ID_TERRA, CHAIN_ID_TERRA,
getEmitterAddressEth, getEmitterAddressEth,
@ -19,10 +19,10 @@ import {
ConnectedWallet, ConnectedWallet,
useConnectedWallet, useConnectedWallet,
} from "@terra-money/wallet-provider"; } from "@terra-money/wallet-provider";
import { Signer } from "ethers";
import { useSnackbar } from "notistack"; import { useSnackbar } from "notistack";
import { useCallback, useMemo } from "react"; import { useCallback, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { Signer } from "ethers";
import { useEthereumProvider } from "../contexts/EthereumProviderContext"; import { useEthereumProvider } from "../contexts/EthereumProviderContext";
import { useSolanaWallet } from "../contexts/SolanaWalletContext"; import { useSolanaWallet } from "../contexts/SolanaWalletContext";
import { import {
@ -38,28 +38,30 @@ import {
selectAttestSourceChain, selectAttestSourceChain,
} from "../store/selectors"; } from "../store/selectors";
import { import {
ETH_BRIDGE_ADDRESS, getBridgeAddressForChain,
ETH_TOKEN_BRIDGE_ADDRESS, getTokenBridgeAddressForChain,
SOLANA_HOST, SOLANA_HOST,
SOL_BRIDGE_ADDRESS, SOL_BRIDGE_ADDRESS,
SOL_TOKEN_BRIDGE_ADDRESS, SOL_TOKEN_BRIDGE_ADDRESS,
TERRA_TOKEN_BRIDGE_ADDRESS, TERRA_TOKEN_BRIDGE_ADDRESS,
} from "../utils/consts"; } from "../utils/consts";
import { isEVMChain } from "../utils/ethereum";
import { getSignedVAAWithRetry } from "../utils/getSignedVAAWithRetry"; import { getSignedVAAWithRetry } from "../utils/getSignedVAAWithRetry";
import parseError from "../utils/parseError"; import parseError from "../utils/parseError";
import { signSendAndConfirm } from "../utils/solana"; import { signSendAndConfirm } from "../utils/solana";
import { waitForTerraExecution } from "../utils/terra"; import { waitForTerraExecution } from "../utils/terra";
async function eth( async function evm(
dispatch: any, dispatch: any,
enqueueSnackbar: any, enqueueSnackbar: any,
signer: Signer, signer: Signer,
sourceAsset: string sourceAsset: string,
chainId: ChainId
) { ) {
dispatch(setIsSending(true)); dispatch(setIsSending(true));
try { try {
const receipt = await attestFromEth( const receipt = await attestFromEth(
ETH_TOKEN_BRIDGE_ADDRESS, getTokenBridgeAddressForChain(chainId),
signer, signer,
sourceAsset sourceAsset
); );
@ -67,11 +69,16 @@ async function eth(
setAttestTx({ id: receipt.transactionHash, block: receipt.blockNumber }) setAttestTx({ id: receipt.transactionHash, block: receipt.blockNumber })
); );
enqueueSnackbar("Transaction confirmed", { variant: "success" }); enqueueSnackbar("Transaction confirmed", { variant: "success" });
const sequence = parseSequenceFromLogEth(receipt, ETH_BRIDGE_ADDRESS); const sequence = parseSequenceFromLogEth(
const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS); receipt,
getBridgeAddressForChain(chainId)
);
const emitterAddress = getEmitterAddressEth(
getTokenBridgeAddressForChain(chainId)
);
enqueueSnackbar("Fetching VAA", { variant: "info" }); enqueueSnackbar("Fetching VAA", { variant: "info" });
const { vaaBytes } = await getSignedVAAWithRetry( const { vaaBytes } = await getSignedVAAWithRetry(
CHAIN_ID_ETH, chainId,
emitterAddress, emitterAddress,
sequence sequence
); );
@ -180,8 +187,8 @@ export function useHandleAttest() {
const terraWallet = useConnectedWallet(); const terraWallet = useConnectedWallet();
const disabled = !isTargetComplete || isSending || isSendComplete; const disabled = !isTargetComplete || isSending || isSendComplete;
const handleAttestClick = useCallback(() => { const handleAttestClick = useCallback(() => {
if (sourceChain === CHAIN_ID_ETH && !!signer) { if (isEVMChain(sourceChain) && !!signer) {
eth(dispatch, enqueueSnackbar, signer, sourceAsset); evm(dispatch, enqueueSnackbar, signer, sourceAsset, sourceChain);
} else if (sourceChain === CHAIN_ID_SOLANA && !!solanaWallet && !!solPK) { } else if (sourceChain === CHAIN_ID_SOLANA && !!solanaWallet && !!solPK) {
solana(dispatch, enqueueSnackbar, solPK, sourceAsset, solanaWallet); solana(dispatch, enqueueSnackbar, solPK, sourceAsset, solanaWallet);
} else if (sourceChain === CHAIN_ID_TERRA && !!terraWallet) { } else if (sourceChain === CHAIN_ID_TERRA && !!terraWallet) {

View File

@ -1,5 +1,5 @@
import { import {
CHAIN_ID_ETH, ChainId,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
CHAIN_ID_TERRA, CHAIN_ID_TERRA,
createWrappedOnEth, createWrappedOnEth,
@ -26,25 +26,27 @@ import {
selectAttestTargetChain, selectAttestTargetChain,
} from "../store/selectors"; } from "../store/selectors";
import { import {
ETH_TOKEN_BRIDGE_ADDRESS, getTokenBridgeAddressForChain,
SOLANA_HOST, SOLANA_HOST,
SOL_BRIDGE_ADDRESS, SOL_BRIDGE_ADDRESS,
SOL_TOKEN_BRIDGE_ADDRESS, SOL_TOKEN_BRIDGE_ADDRESS,
TERRA_TOKEN_BRIDGE_ADDRESS, TERRA_TOKEN_BRIDGE_ADDRESS,
} from "../utils/consts"; } from "../utils/consts";
import { isEVMChain } from "../utils/ethereum";
import parseError from "../utils/parseError"; import parseError from "../utils/parseError";
import { signSendAndConfirm } from "../utils/solana"; import { signSendAndConfirm } from "../utils/solana";
async function eth( async function evm(
dispatch: any, dispatch: any,
enqueueSnackbar: any, enqueueSnackbar: any,
signer: Signer, signer: Signer,
signedVAA: Uint8Array signedVAA: Uint8Array,
chainId: ChainId
) { ) {
dispatch(setIsCreating(true)); dispatch(setIsCreating(true));
try { try {
const receipt = await createWrappedOnEth( const receipt = await createWrappedOnEth(
ETH_TOKEN_BRIDGE_ADDRESS, getTokenBridgeAddressForChain(chainId),
signer, signer,
signedVAA signedVAA
); );
@ -133,8 +135,8 @@ export function useHandleCreateWrapped() {
const { signer } = useEthereumProvider(); const { signer } = useEthereumProvider();
const terraWallet = useConnectedWallet(); const terraWallet = useConnectedWallet();
const handleCreateClick = useCallback(() => { const handleCreateClick = useCallback(() => {
if (targetChain === CHAIN_ID_ETH && !!signer && !!signedVAA) { if (isEVMChain(targetChain) && !!signer && !!signedVAA) {
eth(dispatch, enqueueSnackbar, signer, signedVAA); evm(dispatch, enqueueSnackbar, signer, signedVAA, targetChain);
} else if ( } else if (
targetChain === CHAIN_ID_SOLANA && targetChain === CHAIN_ID_SOLANA &&
!!solanaWallet && !!solanaWallet &&

View File

@ -1,10 +1,10 @@
import { import {
CHAIN_ID_ETH, ChainId,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
getClaimAddressSolana, getClaimAddressSolana,
postVaaSolana,
parseNFTPayload,
hexToUint8Array, hexToUint8Array,
parseNFTPayload,
postVaaSolana,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import { import {
createMetaOnSolana, createMetaOnSolana,
@ -25,26 +25,28 @@ import { useSolanaWallet } from "../contexts/SolanaWalletContext";
import { setIsRedeeming, setRedeemTx } from "../store/nftSlice"; import { setIsRedeeming, setRedeemTx } from "../store/nftSlice";
import { selectNFTIsRedeeming, selectNFTTargetChain } from "../store/selectors"; import { selectNFTIsRedeeming, selectNFTTargetChain } from "../store/selectors";
import { import {
ETH_NFT_BRIDGE_ADDRESS, getNFTBridgeAddressForChain,
SOLANA_HOST, SOLANA_HOST,
SOL_BRIDGE_ADDRESS, SOL_BRIDGE_ADDRESS,
SOL_NFT_BRIDGE_ADDRESS, SOL_NFT_BRIDGE_ADDRESS,
} from "../utils/consts"; } from "../utils/consts";
import { isEVMChain } from "../utils/ethereum";
import { getMetadataAddress } from "../utils/metaplex"; import { getMetadataAddress } from "../utils/metaplex";
import parseError from "../utils/parseError"; import parseError from "../utils/parseError";
import { signSendAndConfirm } from "../utils/solana"; import { signSendAndConfirm } from "../utils/solana";
import useNFTSignedVAA from "./useNFTSignedVAA"; import useNFTSignedVAA from "./useNFTSignedVAA";
async function eth( async function evm(
dispatch: any, dispatch: any,
enqueueSnackbar: any, enqueueSnackbar: any,
signer: Signer, signer: Signer,
signedVAA: Uint8Array signedVAA: Uint8Array,
chainId: ChainId
) { ) {
dispatch(setIsRedeeming(true)); dispatch(setIsRedeeming(true));
try { try {
const receipt = await redeemOnEth( const receipt = await redeemOnEth(
ETH_NFT_BRIDGE_ADDRESS, getNFTBridgeAddressForChain(chainId),
signer, signer,
signedVAA signedVAA
); );
@ -142,8 +144,8 @@ export function useHandleNFTRedeem() {
const signedVAA = useNFTSignedVAA(); const signedVAA = useNFTSignedVAA();
const isRedeeming = useSelector(selectNFTIsRedeeming); const isRedeeming = useSelector(selectNFTIsRedeeming);
const handleRedeemClick = useCallback(() => { const handleRedeemClick = useCallback(() => {
if (targetChain === CHAIN_ID_ETH && !!signer && signedVAA) { if (isEVMChain(targetChain) && !!signer && signedVAA) {
eth(dispatch, enqueueSnackbar, signer, signedVAA); evm(dispatch, enqueueSnackbar, signer, signedVAA, targetChain);
} else if ( } else if (
targetChain === CHAIN_ID_SOLANA && targetChain === CHAIN_ID_SOLANA &&
!!solanaWallet && !!solanaWallet &&

View File

@ -1,12 +1,11 @@
import { import {
ChainId, ChainId,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
getEmitterAddressEth, getEmitterAddressEth,
getEmitterAddressSolana, getEmitterAddressSolana,
hexToUint8Array,
parseSequenceFromLogEth, parseSequenceFromLogEth,
parseSequenceFromLogSolana, parseSequenceFromLogSolana,
hexToUint8Array,
uint8ArrayToHex, uint8ArrayToHex,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import { import {
@ -40,30 +39,32 @@ import {
selectNFTTargetChain, selectNFTTargetChain,
} from "../store/selectors"; } from "../store/selectors";
import { import {
ETH_BRIDGE_ADDRESS, getBridgeAddressForChain,
ETH_NFT_BRIDGE_ADDRESS, getNFTBridgeAddressForChain,
SOLANA_HOST, SOLANA_HOST,
SOL_BRIDGE_ADDRESS, SOL_BRIDGE_ADDRESS,
SOL_NFT_BRIDGE_ADDRESS, SOL_NFT_BRIDGE_ADDRESS,
} from "../utils/consts"; } from "../utils/consts";
import { isEVMChain } from "../utils/ethereum";
import { getSignedVAAWithRetry } from "../utils/getSignedVAAWithRetry"; import { getSignedVAAWithRetry } from "../utils/getSignedVAAWithRetry";
import parseError from "../utils/parseError"; import parseError from "../utils/parseError";
import { signSendAndConfirm } from "../utils/solana"; import { signSendAndConfirm } from "../utils/solana";
import useNFTTargetAddressHex from "./useNFTTargetAddress"; import useNFTTargetAddressHex from "./useNFTTargetAddress";
async function eth( async function evm(
dispatch: any, dispatch: any,
enqueueSnackbar: any, enqueueSnackbar: any,
signer: Signer, signer: Signer,
tokenAddress: string, tokenAddress: string,
tokenId: string, tokenId: string,
recipientChain: ChainId, recipientChain: ChainId,
recipientAddress: Uint8Array recipientAddress: Uint8Array,
chainId: ChainId
) { ) {
dispatch(setIsSending(true)); dispatch(setIsSending(true));
try { try {
const receipt = await transferFromEth( const receipt = await transferFromEth(
ETH_NFT_BRIDGE_ADDRESS, getNFTBridgeAddressForChain(chainId),
signer, signer,
tokenAddress, tokenAddress,
tokenId, tokenId,
@ -74,11 +75,16 @@ async function eth(
setTransferTx({ id: receipt.transactionHash, block: receipt.blockNumber }) setTransferTx({ id: receipt.transactionHash, block: receipt.blockNumber })
); );
enqueueSnackbar("Transaction confirmed", { variant: "success" }); enqueueSnackbar("Transaction confirmed", { variant: "success" });
const sequence = parseSequenceFromLogEth(receipt, ETH_BRIDGE_ADDRESS); const sequence = parseSequenceFromLogEth(
const emitterAddress = getEmitterAddressEth(ETH_NFT_BRIDGE_ADDRESS); receipt,
getBridgeAddressForChain(chainId)
);
const emitterAddress = getEmitterAddressEth(
getNFTBridgeAddressForChain(chainId)
);
enqueueSnackbar("Fetching VAA", { variant: "info" }); enqueueSnackbar("Fetching VAA", { variant: "info" });
const { vaaBytes } = await getSignedVAAWithRetry( const { vaaBytes } = await getSignedVAAWithRetry(
CHAIN_ID_ETH, chainId,
emitterAddress, emitterAddress,
sequence.toString() sequence.toString()
); );
@ -178,20 +184,21 @@ export function useHandleNFTTransfer() {
const handleTransferClick = useCallback(() => { const handleTransferClick = useCallback(() => {
// TODO: we should separate state for transaction vs fetching vaa // TODO: we should separate state for transaction vs fetching vaa
if ( if (
sourceChain === CHAIN_ID_ETH && isEVMChain(sourceChain) &&
!!signer && !!signer &&
!!sourceAsset && !!sourceAsset &&
!!sourceTokenId && !!sourceTokenId &&
!!targetAddress !!targetAddress
) { ) {
eth( evm(
dispatch, dispatch,
enqueueSnackbar, enqueueSnackbar,
signer, signer,
sourceAsset, sourceAsset,
sourceTokenId, sourceTokenId,
targetChain, targetChain,
targetAddress targetAddress,
sourceChain
); );
} else if ( } else if (
sourceChain === CHAIN_ID_SOLANA && sourceChain === CHAIN_ID_SOLANA &&

View File

@ -1,5 +1,5 @@
import { import {
CHAIN_ID_ETH, ChainId,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
CHAIN_ID_TERRA, CHAIN_ID_TERRA,
postVaaSolana, postVaaSolana,
@ -28,27 +28,37 @@ import {
} from "../store/selectors"; } from "../store/selectors";
import { setIsRedeeming, setRedeemTx } from "../store/transferSlice"; import { setIsRedeeming, setRedeemTx } from "../store/transferSlice";
import { import {
ETH_TOKEN_BRIDGE_ADDRESS, getTokenBridgeAddressForChain,
SOLANA_HOST, SOLANA_HOST,
SOL_BRIDGE_ADDRESS, SOL_BRIDGE_ADDRESS,
SOL_TOKEN_BRIDGE_ADDRESS, SOL_TOKEN_BRIDGE_ADDRESS,
TERRA_TOKEN_BRIDGE_ADDRESS, TERRA_TOKEN_BRIDGE_ADDRESS,
} from "../utils/consts"; } from "../utils/consts";
import { isEVMChain } from "../utils/ethereum";
import parseError from "../utils/parseError"; import parseError from "../utils/parseError";
import { signSendAndConfirm } from "../utils/solana"; import { signSendAndConfirm } from "../utils/solana";
async function eth( async function evm(
dispatch: any, dispatch: any,
enqueueSnackbar: any, enqueueSnackbar: any,
signer: Signer, signer: Signer,
signedVAA: Uint8Array, signedVAA: Uint8Array,
isNative: boolean isNative: boolean,
chainId: ChainId
) { ) {
dispatch(setIsRedeeming(true)); dispatch(setIsRedeeming(true));
try { try {
const receipt = isNative const receipt = isNative
? await redeemOnEthNative(ETH_TOKEN_BRIDGE_ADDRESS, signer, signedVAA) ? await redeemOnEthNative(
: await redeemOnEth(ETH_TOKEN_BRIDGE_ADDRESS, signer, signedVAA); getTokenBridgeAddressForChain(chainId),
signer,
signedVAA
)
: await redeemOnEth(
getTokenBridgeAddressForChain(chainId),
signer,
signedVAA
);
dispatch( dispatch(
setRedeemTx({ id: receipt.transactionHash, block: receipt.blockNumber }) setRedeemTx({ id: receipt.transactionHash, block: receipt.blockNumber })
); );
@ -144,8 +154,8 @@ export function useHandleRedeem() {
const signedVAA = useTransferSignedVAA(); const signedVAA = useTransferSignedVAA();
const isRedeeming = useSelector(selectTransferIsRedeeming); const isRedeeming = useSelector(selectTransferIsRedeeming);
const handleRedeemClick = useCallback(() => { const handleRedeemClick = useCallback(() => {
if (targetChain === CHAIN_ID_ETH && !!signer && signedVAA) { if (isEVMChain(targetChain) && !!signer && signedVAA) {
eth(dispatch, enqueueSnackbar, signer, signedVAA, false); evm(dispatch, enqueueSnackbar, signer, signedVAA, false, targetChain);
} else if ( } else if (
targetChain === CHAIN_ID_SOLANA && targetChain === CHAIN_ID_SOLANA &&
!!solanaWallet && !!solanaWallet &&
@ -179,8 +189,8 @@ export function useHandleRedeem() {
]); ]);
const handleRedeemNativeClick = useCallback(() => { const handleRedeemNativeClick = useCallback(() => {
if (targetChain === CHAIN_ID_ETH && !!signer && signedVAA) { if (isEVMChain(targetChain) && !!signer && signedVAA) {
eth(dispatch, enqueueSnackbar, signer, signedVAA, true); evm(dispatch, enqueueSnackbar, signer, signedVAA, true, targetChain);
} else if ( } else if (
targetChain === CHAIN_ID_SOLANA && targetChain === CHAIN_ID_SOLANA &&
!!solanaWallet && !!solanaWallet &&

View File

@ -1,20 +1,19 @@
import { import {
ChainId, ChainId,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
CHAIN_ID_TERRA, CHAIN_ID_TERRA,
getEmitterAddressEth, getEmitterAddressEth,
getEmitterAddressSolana, getEmitterAddressSolana,
getEmitterAddressTerra, getEmitterAddressTerra,
hexToUint8Array,
parseSequenceFromLogEth, parseSequenceFromLogEth,
parseSequenceFromLogSolana, parseSequenceFromLogSolana,
parseSequenceFromLogTerra, parseSequenceFromLogTerra,
transferFromEth, transferFromEth,
transferFromEthNative, transferFromEthNative,
transferFromSolana, transferFromSolana,
transferNativeSol,
transferFromTerra, transferFromTerra,
hexToUint8Array, transferNativeSol,
uint8ArrayToHex, uint8ArrayToHex,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import { WalletContextState } from "@solana/wallet-adapter-react"; import { WalletContextState } from "@solana/wallet-adapter-react";
@ -48,20 +47,21 @@ import {
setTransferTx, setTransferTx,
} from "../store/transferSlice"; } from "../store/transferSlice";
import { import {
ETH_BRIDGE_ADDRESS, getBridgeAddressForChain,
ETH_TOKEN_BRIDGE_ADDRESS, getTokenBridgeAddressForChain,
SOLANA_HOST, SOLANA_HOST,
SOL_BRIDGE_ADDRESS, SOL_BRIDGE_ADDRESS,
SOL_TOKEN_BRIDGE_ADDRESS, SOL_TOKEN_BRIDGE_ADDRESS,
TERRA_TOKEN_BRIDGE_ADDRESS, TERRA_TOKEN_BRIDGE_ADDRESS,
} from "../utils/consts"; } from "../utils/consts";
import { isEVMChain } from "../utils/ethereum";
import { getSignedVAAWithRetry } from "../utils/getSignedVAAWithRetry"; import { getSignedVAAWithRetry } from "../utils/getSignedVAAWithRetry";
import parseError from "../utils/parseError"; import parseError from "../utils/parseError";
import { signSendAndConfirm } from "../utils/solana"; import { signSendAndConfirm } from "../utils/solana";
import { waitForTerraExecution } from "../utils/terra"; import { waitForTerraExecution } from "../utils/terra";
import useTransferTargetAddressHex from "./useTransferTargetAddress"; import useTransferTargetAddressHex from "./useTransferTargetAddress";
async function eth( async function evm(
dispatch: any, dispatch: any,
enqueueSnackbar: any, enqueueSnackbar: any,
signer: Signer, signer: Signer,
@ -70,21 +70,22 @@ async function eth(
amount: string, amount: string,
recipientChain: ChainId, recipientChain: ChainId,
recipientAddress: Uint8Array, recipientAddress: Uint8Array,
isNative: boolean isNative: boolean,
chainId: ChainId
) { ) {
dispatch(setIsSending(true)); dispatch(setIsSending(true));
try { try {
const amountParsed = parseUnits(amount, decimals); const amountParsed = parseUnits(amount, decimals);
const receipt = isNative const receipt = isNative
? await transferFromEthNative( ? await transferFromEthNative(
ETH_TOKEN_BRIDGE_ADDRESS, getTokenBridgeAddressForChain(chainId),
signer, signer,
amountParsed, amountParsed,
recipientChain, recipientChain,
recipientAddress recipientAddress
) )
: await transferFromEth( : await transferFromEth(
ETH_TOKEN_BRIDGE_ADDRESS, getTokenBridgeAddressForChain(chainId),
signer, signer,
tokenAddress, tokenAddress,
amountParsed, amountParsed,
@ -95,11 +96,16 @@ async function eth(
setTransferTx({ id: receipt.transactionHash, block: receipt.blockNumber }) setTransferTx({ id: receipt.transactionHash, block: receipt.blockNumber })
); );
enqueueSnackbar("Transaction confirmed", { variant: "success" }); enqueueSnackbar("Transaction confirmed", { variant: "success" });
const sequence = parseSequenceFromLogEth(receipt, ETH_BRIDGE_ADDRESS); const sequence = parseSequenceFromLogEth(
const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS); receipt,
getBridgeAddressForChain(chainId)
);
const emitterAddress = getEmitterAddressEth(
getTokenBridgeAddressForChain(chainId)
);
enqueueSnackbar("Fetching VAA", { variant: "info" }); enqueueSnackbar("Fetching VAA", { variant: "info" });
const { vaaBytes } = await getSignedVAAWithRetry( const { vaaBytes } = await getSignedVAAWithRetry(
CHAIN_ID_ETH, chainId,
emitterAddress, emitterAddress,
sequence.toString() sequence.toString()
); );
@ -262,13 +268,13 @@ export function useHandleTransfer() {
const handleTransferClick = useCallback(() => { const handleTransferClick = useCallback(() => {
// TODO: we should separate state for transaction vs fetching vaa // TODO: we should separate state for transaction vs fetching vaa
if ( if (
sourceChain === CHAIN_ID_ETH && isEVMChain(sourceChain) &&
!!signer && !!signer &&
!!sourceAsset && !!sourceAsset &&
decimals !== undefined && decimals !== undefined &&
!!targetAddress !!targetAddress
) { ) {
eth( evm(
dispatch, dispatch,
enqueueSnackbar, enqueueSnackbar,
signer, signer,
@ -277,7 +283,8 @@ export function useHandleTransfer() {
amount, amount,
targetChain, targetChain,
targetAddress, targetAddress,
isNative isNative,
sourceChain
); );
} else if ( } else if (
sourceChain === CHAIN_ID_SOLANA && sourceChain === CHAIN_ID_SOLANA &&

View File

@ -1,6 +1,5 @@
import { import {
ChainId, ChainId,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
CHAIN_ID_TERRA, CHAIN_ID_TERRA,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
@ -9,7 +8,8 @@ import { useConnectedWallet } from "@terra-money/wallet-provider";
import { useMemo } from "react"; import { useMemo } from "react";
import { useEthereumProvider } from "../contexts/EthereumProviderContext"; import { useEthereumProvider } from "../contexts/EthereumProviderContext";
import { useSolanaWallet } from "../contexts/SolanaWalletContext"; import { useSolanaWallet } from "../contexts/SolanaWalletContext";
import { CLUSTER, ETH_NETWORK_CHAIN_ID } from "../utils/consts"; import { CLUSTER, getEvmChainId } from "../utils/consts";
import { isEVMChain } from "../utils/ethereum";
const createWalletStatus = ( const createWalletStatus = (
isReady: boolean, isReady: boolean,
@ -33,10 +33,11 @@ function useIsWalletReady(chainId: ChainId): {
const { const {
provider, provider,
signerAddress, signerAddress,
chainId: ethChainId, chainId: evmChainId,
} = useEthereumProvider(); } = useEthereumProvider();
const hasEthInfo = !!provider && !!signerAddress; const hasEthInfo = !!provider && !!signerAddress;
const hasCorrectEthNetwork = ethChainId === ETH_NETWORK_CHAIN_ID; const correctEvmNetwork = getEvmChainId(chainId);
const hasCorrectEvmNetwork = evmChainId === correctEvmNetwork;
return useMemo(() => { return useMemo(() => {
if ( if (
@ -50,20 +51,20 @@ function useIsWalletReady(chainId: ChainId): {
if (chainId === CHAIN_ID_SOLANA && solPK) { if (chainId === CHAIN_ID_SOLANA && solPK) {
return createWalletStatus(true, undefined, solPK.toString()); return createWalletStatus(true, undefined, solPK.toString());
} }
if (chainId === CHAIN_ID_ETH && hasEthInfo && signerAddress) { if (isEVMChain(chainId) && hasEthInfo && signerAddress) {
if (hasCorrectEthNetwork) { if (hasCorrectEvmNetwork) {
return createWalletStatus(true, undefined, signerAddress); return createWalletStatus(true, undefined, signerAddress);
} else { } else {
if (provider) { if (provider && correctEvmNetwork) {
try { try {
provider.send("wallet_switchEthereumChain", [ provider.send("wallet_switchEthereumChain", [
{ chainId: hexStripZeros(hexlify(ETH_NETWORK_CHAIN_ID)) }, { chainId: hexStripZeros(hexlify(correctEvmNetwork)) },
]); ]);
} catch (e) {} } catch (e) {}
} }
return createWalletStatus( return createWalletStatus(
false, false,
`Wallet is not connected to ${CLUSTER}. Expected Chain ID: ${ETH_NETWORK_CHAIN_ID}`, `Wallet is not connected to ${CLUSTER}. Expected Chain ID: ${correctEvmNetwork}`,
undefined undefined
); );
} }
@ -75,7 +76,8 @@ function useIsWalletReady(chainId: ChainId): {
hasTerraWallet, hasTerraWallet,
solPK, solPK,
hasEthInfo, hasEthInfo,
hasCorrectEthNetwork, correctEvmNetwork,
hasCorrectEvmNetwork,
provider, provider,
signerAddress, signerAddress,
terraWallet, terraWallet,

View File

@ -1,14 +1,14 @@
import { import {
ChainId, ChainId,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
CHAIN_ID_TERRA, CHAIN_ID_TERRA,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import { TokenInfo } from "@solana/spl-token-registry"; import { TokenInfo } from "@solana/spl-token-registry";
import { useMemo } from "react"; import { useMemo } from "react";
import { DataWrapper, getEmptyDataWrapper } from "../store/helpers"; import { DataWrapper, getEmptyDataWrapper } from "../store/helpers";
import { isEVMChain } from "../utils/ethereum";
import { Metadata } from "../utils/metaplex"; import { Metadata } from "../utils/metaplex";
import useEthMetadata, { EthMetadata } from "./useEthMetadata"; import useEvmMetadata, { EvmMetadata } from "./useEvmMetadata";
import useMetaplexData from "./useMetaplexData"; import useMetaplexData from "./useMetaplexData";
import useSolanaTokenMap from "./useSolanaTokenMap"; import useSolanaTokenMap from "./useSolanaTokenMap";
import useTerraTokenMap, { TerraTokenMap } from "./useTerraTokenMap"; import useTerraTokenMap, { TerraTokenMap } from "./useTerraTokenMap";
@ -80,7 +80,7 @@ const constructTerraMetadata = (
const constructEthMetadata = ( const constructEthMetadata = (
addresses: string[], addresses: string[],
metadataMap: DataWrapper<Map<string, EthMetadata> | null> metadataMap: DataWrapper<Map<string, EvmMetadata> | null>
) => { ) => {
const isFetching = metadataMap.isFetching; const isFetching = metadataMap.isFetching;
const error = metadataMap.error; const error = metadataMap.error;
@ -119,17 +119,17 @@ export default function useMetadata(
return chainId === CHAIN_ID_TERRA ? addresses : []; return chainId === CHAIN_ID_TERRA ? addresses : [];
}, [chainId, addresses]); }, [chainId, addresses]);
const ethereumAddresses = useMemo(() => { const ethereumAddresses = useMemo(() => {
return chainId === CHAIN_ID_ETH ? addresses : []; return isEVMChain(chainId) ? addresses : [];
}, [chainId, addresses]); }, [chainId, addresses]);
const metaplexData = useMetaplexData(solanaAddresses); const metaplexData = useMetaplexData(solanaAddresses);
const ethMetadata = useEthMetadata(ethereumAddresses); const ethMetadata = useEvmMetadata(ethereumAddresses, chainId);
const output: DataWrapper<Map<string, GenericMetadata>> = useMemo( const output: DataWrapper<Map<string, GenericMetadata>> = useMemo(
() => () =>
chainId === CHAIN_ID_SOLANA chainId === CHAIN_ID_SOLANA
? constructSolanaMetadata(solanaAddresses, solanaTokenMap, metaplexData) ? constructSolanaMetadata(solanaAddresses, solanaTokenMap, metaplexData)
: chainId === CHAIN_ID_ETH : isEVMChain(chainId)
? constructEthMetadata(ethereumAddresses, ethMetadata) ? constructEthMetadata(ethereumAddresses, ethMetadata)
: chainId === CHAIN_ID_TERRA : chainId === CHAIN_ID_TERRA
? constructTerraMetadata(terraAddresses, terraTokenMap) ? constructTerraMetadata(terraAddresses, terraTokenMap)

View File

@ -1,8 +1,7 @@
import { import {
CHAIN_ID_ETH, canonicalAddress,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
CHAIN_ID_TERRA, CHAIN_ID_TERRA,
canonicalAddress,
uint8ArrayToHex, uint8ArrayToHex,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import { arrayify, zeroPad } from "@ethersproject/bytes"; import { arrayify, zeroPad } from "@ethersproject/bytes";
@ -17,6 +16,7 @@ import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { useEthereumProvider } from "../contexts/EthereumProviderContext"; import { useEthereumProvider } from "../contexts/EthereumProviderContext";
import { useSolanaWallet } from "../contexts/SolanaWalletContext"; import { useSolanaWallet } from "../contexts/SolanaWalletContext";
import { setTargetAddressHex as setNFTTargetAddressHex } from "../store/nftSlice";
import { import {
selectNFTTargetAsset, selectNFTTargetAsset,
selectNFTTargetChain, selectNFTTargetChain,
@ -24,8 +24,8 @@ import {
selectTransferTargetChain, selectTransferTargetChain,
selectTransferTargetParsedTokenAccount, selectTransferTargetParsedTokenAccount,
} from "../store/selectors"; } from "../store/selectors";
import { setTargetAddressHex as setNFTTargetAddressHex } from "../store/nftSlice";
import { setTargetAddressHex as setTransferTargetAddressHex } from "../store/transferSlice"; import { setTargetAddressHex as setTransferTargetAddressHex } from "../store/transferSlice";
import { isEVMChain } from "../utils/ethereum";
function useSyncTargetAddress(shouldFire: boolean, nft?: boolean) { function useSyncTargetAddress(shouldFire: boolean, nft?: boolean) {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -49,7 +49,7 @@ function useSyncTargetAddress(shouldFire: boolean, nft?: boolean) {
useEffect(() => { useEffect(() => {
if (shouldFire) { if (shouldFire) {
let cancelled = false; let cancelled = false;
if (targetChain === CHAIN_ID_ETH && signerAddress) { if (isEVMChain(targetChain) && signerAddress) {
dispatch( dispatch(
setTargetAddressHex( setTargetAddressHex(
uint8ArrayToHex(zeroPad(arrayify(signerAddress), 32)) uint8ArrayToHex(zeroPad(arrayify(signerAddress), 32))

View File

@ -11,7 +11,7 @@ import { LocalGasStation } from "@material-ui/icons";
import { Connection, PublicKey } from "@solana/web3.js"; import { Connection, PublicKey } from "@solana/web3.js";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { useEthereumProvider } from "../contexts/EthereumProviderContext"; import { useEthereumProvider } from "../contexts/EthereumProviderContext";
import { SOLANA_HOST } from "../utils/consts"; import { getDefaultNativeCurrencySymbol, SOLANA_HOST } from "../utils/consts";
import { getMultipleAccountsRPC } from "../utils/solana"; import { getMultipleAccountsRPC } from "../utils/solana";
import useIsWalletReady from "./useIsWalletReady"; import useIsWalletReady from "./useIsWalletReady";
@ -142,9 +142,9 @@ export default function useTransactionFees(chainId: ChainId) {
return results; return results;
} }
export function useEthereumGasPrice(contract: MethodType) { export function useEthereumGasPrice(contract: MethodType, chainId: ChainId) {
const { provider } = useEthereumProvider(); const { provider } = useEthereumProvider();
const { isReady } = useIsWalletReady(CHAIN_ID_ETH); const { isReady } = useIsWalletReady(chainId);
const [estimateResults, setEstimateResults] = useState<GasEstimate | null>( const [estimateResults, setEstimateResults] = useState<GasEstimate | null>(
null null
); );
@ -168,10 +168,12 @@ export function useEthereumGasPrice(contract: MethodType) {
export function EthGasEstimateSummary({ export function EthGasEstimateSummary({
methodType, methodType,
chainId,
}: { }: {
methodType: MethodType; methodType: MethodType;
chainId: ChainId;
}) { }) {
const estimate = useEthereumGasPrice(methodType); const estimate = useEthereumGasPrice(methodType, chainId);
if (!estimate) { if (!estimate) {
return null; return null;
} }
@ -192,7 +194,8 @@ export function EthGasEstimateSummary({
</div> </div>
<div>&nbsp;&nbsp;&nbsp;</div> <div>&nbsp;&nbsp;&nbsp;</div>
<div> <div>
Est. Fees: {estimate.lowEstimate} - {estimate.highEstimate} ETH Est. Fees: {estimate.lowEstimate} - {estimate.highEstimate}{" "}
{getDefaultNativeCurrencySymbol(chainId)}
</div> </div>
</Typography> </Typography>
); );

View File

@ -0,0 +1,12 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.3025 0L9.67897 6.12683L13.5847 8.39024L20.3025 4.52683L27.0204 8.39024L30.9261 6.12683L20.3025 0Z" fill="#F0B90B"/>
<path d="M27.0204 11.5902L30.9261 13.8537V18.3805L24.2083 22.2439V29.9707L20.3025 32.2341L16.3968 29.9707V22.2439L9.67897 18.3805V13.8537L13.5847 11.5902L20.3025 15.4537L27.0204 11.5902Z" fill="#F0B90B"/>
<path d="M30.9261 21.5805V26.1073L27.0204 28.3707V23.8439L30.9261 21.5805Z" fill="#F0B90B"/>
<path d="M26.9814 31.5707L33.6992 27.7073V19.9805L37.605 17.7171V29.9707L26.9814 36.0976V31.5707Z" fill="#F0B90B"/>
<path d="M33.6992 12.2537L29.7935 9.99025L33.6992 7.72683L37.605 9.99025V14.5171L33.6992 16.7805V12.2537Z" fill="#F0B90B"/>
<path d="M16.3968 37.7366V33.2098L20.3025 35.4732L24.2083 33.2098V37.7366L20.3025 40L16.3968 37.7366Z" fill="#F0B90B"/>
<path d="M13.5847 28.3707L9.67897 26.1073V21.5805L13.5847 23.8439V28.3707Z" fill="#F0B90B"/>
<path d="M20.3025 12.2537L16.3968 9.99025L20.3025 7.72683L24.2083 9.99025L20.3025 12.2537Z" fill="#F0B90B"/>
<path d="M10.8116 9.99025L6.90586 12.2537V16.7805L3.00012 14.5171V9.99025L6.90586 7.72683L10.8116 9.99025Z" fill="#F0B90B"/>
<path d="M3.00012 17.7171L6.90586 19.9805V27.7073L13.6237 31.5707V36.0976L3.00012 29.9707V17.7171Z" fill="#F0B90B"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,7 +1,8 @@
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk"; import { CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
import { ethers } from "ethers"; import { ethers } from "ethers";
import { parseUnits } from "ethers/lib/utils"; import { parseUnits } from "ethers/lib/utils";
import { RootState } from "."; import { RootState } from ".";
import { isEVMChain } from "../utils/ethereum";
/* /*
* Attest * Attest
@ -259,7 +260,7 @@ export const selectTransferTargetError = (state: RootState) => {
return UNREGISTERED_ERROR_MESSAGE; return UNREGISTERED_ERROR_MESSAGE;
} }
if ( if (
state.transfer.targetChain === CHAIN_ID_ETH && isEVMChain(state.transfer.targetChain) &&
state.transfer.targetAsset === ethers.constants.AddressZero state.transfer.targetAsset === ethers.constants.AddressZero
) { ) {
return UNREGISTERED_ERROR_MESSAGE; return UNREGISTERED_ERROR_MESSAGE;

View File

@ -64,11 +64,33 @@ export const CHAINS =
name: "Terra", name: "Terra",
}, },
]; ];
export const CHAINS_WITH_NFT_SUPPORT = CHAINS.filter(
({ id }) =>
id === CHAIN_ID_ETH || id === CHAIN_ID_BSC || id === CHAIN_ID_SOLANA
);
export type ChainsById = { [key in ChainId]: ChainInfo }; export type ChainsById = { [key in ChainId]: ChainInfo };
export const CHAINS_BY_ID: ChainsById = CHAINS.reduce((obj, chain) => { export const CHAINS_BY_ID: ChainsById = CHAINS.reduce((obj, chain) => {
obj[chain.id] = chain; obj[chain.id] = chain;
return obj; return obj;
}, {} as ChainsById); }, {} as ChainsById);
export const getDefaultNativeCurrencySymbol = (chainId: ChainId) =>
chainId === CHAIN_ID_SOLANA
? "SOL"
: chainId === CHAIN_ID_ETH
? "ETH"
: chainId === CHAIN_ID_BSC
? "BNB"
: chainId === CHAIN_ID_TERRA
? "LUNA"
: "";
export const getExplorerName = (chainId: ChainId) =>
chainId === CHAIN_ID_ETH
? "Etherscan"
: chainId === CHAIN_ID_BSC
? "BscScan"
: chainId === CHAIN_ID_TERRA
? "Finder"
: "Explorer";
export const WORMHOLE_RPC_HOSTS = export const WORMHOLE_RPC_HOSTS =
CLUSTER === "mainnet" CLUSTER === "mainnet"
? [ ? [
@ -81,11 +103,19 @@ export const WORMHOLE_RPC_HOSTS =
? [ ? [
"https://wormhole-v2-testnet-api.certus.one", "https://wormhole-v2-testnet-api.certus.one",
"https://wormhole-v2-testnet-api.mcf.rocks", "https://wormhole-v2-testnet-api.mcf.rocks",
"https://wormhole-v2-testnet-api.chainlayer.network" "https://wormhole-v2-testnet-api.chainlayer.network",
] ]
: ["http://localhost:7071"]; : ["http://localhost:7071"];
export const ETH_NETWORK_CHAIN_ID = export const ETH_NETWORK_CHAIN_ID =
CLUSTER === "mainnet" ? 1 : CLUSTER === "testnet" ? 5 : 1337; CLUSTER === "mainnet" ? 1 : CLUSTER === "testnet" ? 5 : 1337;
export const BSC_NETWORK_CHAIN_ID =
CLUSTER === "mainnet" ? 56 : CLUSTER === "testnet" ? 97 : 1397;
export const getEvmChainId = (chainId: ChainId) =>
chainId === CHAIN_ID_ETH
? ETH_NETWORK_CHAIN_ID
: chainId === CHAIN_ID_BSC
? BSC_NETWORK_CHAIN_ID
: undefined;
export const SOLANA_HOST = process.env.REACT_APP_SOLANA_API_URL export const SOLANA_HOST = process.env.REACT_APP_SOLANA_API_URL
? process.env.REACT_APP_SOLANA_API_URL ? process.env.REACT_APP_SOLANA_API_URL
: CLUSTER === "mainnet" : CLUSTER === "mainnet"
@ -127,6 +157,27 @@ export const ETH_TOKEN_BRIDGE_ADDRESS = getAddress(
? "0xa6CDAddA6e4B6704705b065E01E52e2486c0FBf6" ? "0xa6CDAddA6e4B6704705b065E01E52e2486c0FBf6"
: "0x0290FB167208Af455bB137780163b7B7a9a10C16" : "0x0290FB167208Af455bB137780163b7B7a9a10C16"
); );
export const BSC_BRIDGE_ADDRESS = getAddress(
CLUSTER === "mainnet"
? "0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B"
: CLUSTER === "testnet"
? "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550" // TODO: test address
: "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550"
);
export const BSC_NFT_BRIDGE_ADDRESS = getAddress(
CLUSTER === "mainnet"
? "0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE"
: CLUSTER === "testnet"
? "0x26b4afb60d6c903165150c6f0aa14f8016be4aec" // TODO: test address
: "0x26b4afb60d6c903165150c6f0aa14f8016be4aec"
);
export const BSC_TOKEN_BRIDGE_ADDRESS = getAddress(
CLUSTER === "mainnet"
? "0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7"
: CLUSTER === "testnet"
? "0x0290FB167208Af455bB137780163b7B7a9a10C16" // TODO: test address
: "0x0290FB167208Af455bB137780163b7B7a9a10C16"
);
export const SOL_BRIDGE_ADDRESS = export const SOL_BRIDGE_ADDRESS =
CLUSTER === "mainnet" CLUSTER === "mainnet"
? "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth" ? "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth"
@ -151,24 +202,64 @@ export const SOL_CUSTODY_ADDRESS =
export const TERRA_TEST_TOKEN_ADDRESS = export const TERRA_TEST_TOKEN_ADDRESS =
"terra13nkgqrfymug724h8pprpexqj9h629sa3ncw7sh"; "terra13nkgqrfymug724h8pprpexqj9h629sa3ncw7sh";
export const TERRA_BRIDGE_ADDRESS = export const TERRA_BRIDGE_ADDRESS =
"terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5"; CLUSTER === "mainnet"
? "terra1dq03ugtd40zu9hcgdzrsq6z2z4hwhc9tqk2uy5"
: CLUSTER === "testnet"
? "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5"
: "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5";
export const TERRA_TOKEN_BRIDGE_ADDRESS = export const TERRA_TOKEN_BRIDGE_ADDRESS =
"terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4"; CLUSTER === "mainnet"
? "terra10nmmwe8r3g99a9newtqa7a75xfgs2e8z87r2sf"
: CLUSTER === "testnet"
? "terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4"
: "terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4";
export const getBridgeAddressForChain = (chainId: ChainId) =>
chainId === CHAIN_ID_SOLANA
? SOL_BRIDGE_ADDRESS
: chainId === CHAIN_ID_ETH
? ETH_BRIDGE_ADDRESS
: chainId === CHAIN_ID_BSC
? BSC_BRIDGE_ADDRESS
: chainId === CHAIN_ID_TERRA
? TERRA_BRIDGE_ADDRESS
: "";
export const getNFTBridgeAddressForChain = (chainId: ChainId) =>
chainId === CHAIN_ID_SOLANA
? SOL_NFT_BRIDGE_ADDRESS
: chainId === CHAIN_ID_ETH
? ETH_NFT_BRIDGE_ADDRESS
: chainId === CHAIN_ID_BSC
? BSC_NFT_BRIDGE_ADDRESS
: "";
export const getTokenBridgeAddressForChain = (chainId: ChainId) =>
chainId === CHAIN_ID_SOLANA
? SOL_TOKEN_BRIDGE_ADDRESS
: chainId === CHAIN_ID_ETH
? ETH_TOKEN_BRIDGE_ADDRESS
: chainId === CHAIN_ID_BSC
? BSC_TOKEN_BRIDGE_ADDRESS
: chainId === CHAIN_ID_TERRA
? TERRA_TOKEN_BRIDGE_ADDRESS
: "";
export const COVALENT_API_KEY = process.env.REACT_APP_COVALENT_API_KEY export const COVALENT_API_KEY = process.env.REACT_APP_COVALENT_API_KEY
? process.env.REACT_APP_COVALENT_API_KEY ? process.env.REACT_APP_COVALENT_API_KEY
: ""; : "";
export const COVALENT_ETHEREUM_MAINNET = "1"; export const COVALENT_ETHEREUM_MAINNET = "1";
export const COVALENT_BSC_MAINNET = "56";
export const COVALENT_GET_TOKENS_URL = ( export const COVALENT_GET_TOKENS_URL = (
chainId: ChainId, chainId: ChainId,
walletAddress: string, walletAddress: string,
nft?: boolean nft?: boolean
) => { ) => {
let chainNum = ""; const chainNum =
if (chainId === CHAIN_ID_ETH) { chainId === CHAIN_ID_ETH
chainNum = COVALENT_ETHEREUM_MAINNET; ? COVALENT_ETHEREUM_MAINNET
} : chainId === CHAIN_ID_BSC
? COVALENT_BSC_MAINNET
: "";
// https://www.covalenthq.com/docs/api/#get-/v1/{chain_id}/address/{address}/balances_v2/ // https://www.covalenthq.com/docs/api/#get-/v1/{chain_id}/address/{address}/balances_v2/
return `https://api.covalenthq.com/v1/${chainNum}/address/${walletAddress}/balances_v2/?key=${COVALENT_API_KEY}${ return `https://api.covalenthq.com/v1/${chainNum}/address/${walletAddress}/balances_v2/?key=${COVALENT_API_KEY}${
nft ? "&nft=true" : "" nft ? "&nft=true" : ""
@ -183,6 +274,14 @@ export const WETH_ADDRESS =
: "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E"; : "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E";
export const WETH_DECIMALS = 18; export const WETH_DECIMALS = 18;
export const WBNB_ADDRESS =
CLUSTER === "mainnet"
? "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"
: CLUSTER === "testnet"
? ""
: "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E";
export const WBNB_DECIMALS = 18;
export const WORMHOLE_V1_ETH_ADDRESS = export const WORMHOLE_V1_ETH_ADDRESS =
CLUSTER === "mainnet" CLUSTER === "mainnet"
? "0xf92cD566Ea4864356C5491c177A430C222d7e678" ? "0xf92cD566Ea4864356C5491c177A430C222d7e678"

View File

@ -1,4 +1,7 @@
import { import {
ChainId,
CHAIN_ID_BSC,
CHAIN_ID_ETH,
NFTImplementation, NFTImplementation,
NFTImplementation__factory, NFTImplementation__factory,
TokenImplementation, TokenImplementation,
@ -11,6 +14,9 @@ import {
createParsedTokenAccount, createParsedTokenAccount,
} from "../hooks/useGetSourceParsedTokenAccounts"; } from "../hooks/useGetSourceParsedTokenAccounts";
export const isEVMChain = (chainId: ChainId) =>
chainId === CHAIN_ID_ETH || chainId === CHAIN_ID_BSC;
//This is a valuable intermediate step to the parsed token account, as the token has metadata information on it. //This is a valuable intermediate step to the parsed token account, as the token has metadata information on it.
export async function getEthereumToken( export async function getEthereumToken(
tokenAddress: string, tokenAddress: string,

View File

@ -106,7 +106,7 @@ spec:
- --deterministic - --deterministic
- --time="1970-01-01T00:00:00+00:00" - --time="1970-01-01T00:00:00+00:00"
- --host=0.0.0.0 - --host=0.0.0.0
- -i 1337 - --chainId=1397
ports: ports:
- containerPort: 8545 - containerPort: 8545
name: rpc name: rpc

View File

@ -2,7 +2,7 @@ import { Connection, PublicKey } from "@solana/web3.js";
import { BigNumber, ethers } from "ethers"; import { BigNumber, ethers } from "ethers";
import { arrayify, zeroPad } from "ethers/lib/utils"; import { arrayify, zeroPad } from "ethers/lib/utils";
import { TokenImplementation__factory } from "../ethers-contracts"; import { TokenImplementation__factory } from "../ethers-contracts";
import { ChainId, CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../utils"; import { ChainId, CHAIN_ID_SOLANA } from "../utils";
import { getIsWrappedAssetEth } from "./getIsWrappedAsset"; import { getIsWrappedAssetEth } from "./getIsWrappedAsset";
export interface WormholeWrappedNFTInfo { export interface WormholeWrappedNFTInfo {
@ -23,7 +23,8 @@ export async function getOriginalAssetEth(
tokenBridgeAddress: string, tokenBridgeAddress: string,
provider: ethers.providers.Web3Provider, provider: ethers.providers.Web3Provider,
wrappedAddress: string, wrappedAddress: string,
tokenId: string tokenId: string,
lookupChainId: ChainId
): Promise<WormholeWrappedNFTInfo> { ): Promise<WormholeWrappedNFTInfo> {
const isWrapped = await getIsWrappedAssetEth( const isWrapped = await getIsWrappedAssetEth(
tokenBridgeAddress, tokenBridgeAddress,
@ -49,7 +50,7 @@ export async function getOriginalAssetEth(
} }
return { return {
isWrapped: false, isWrapped: false,
chainId: CHAIN_ID_ETH, chainId: lookupChainId,
assetAddress: zeroPad(arrayify(wrappedAddress), 32), assetAddress: zeroPad(arrayify(wrappedAddress), 32),
tokenId, tokenId,
}; };

View File

@ -1,16 +1,11 @@
import { Connection, PublicKey } from "@solana/web3.js"; import { Connection, PublicKey } from "@solana/web3.js";
import { LCDClient } from "@terra-money/terra.js";
import { ethers } from "ethers"; import { ethers } from "ethers";
import { arrayify, zeroPad } from "ethers/lib/utils"; import { arrayify, zeroPad } from "ethers/lib/utils";
import { TokenImplementation__factory } from "../ethers-contracts"; import { TokenImplementation__factory } from "../ethers-contracts";
import {
ChainId,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA,
CHAIN_ID_TERRA,
} from "../utils";
import { getIsWrappedAssetEth } from "./getIsWrappedAsset";
import { LCDClient } from "@terra-money/terra.js";
import { buildNativeId, canonicalAddress, isNativeDenom } from "../terra"; import { buildNativeId, canonicalAddress, isNativeDenom } from "../terra";
import { ChainId, CHAIN_ID_SOLANA, CHAIN_ID_TERRA } from "../utils";
import { getIsWrappedAssetEth } from "./getIsWrappedAsset";
export interface WormholeWrappedInfo { export interface WormholeWrappedInfo {
isWrapped: boolean; isWrapped: boolean;
@ -28,7 +23,8 @@ export interface WormholeWrappedInfo {
export async function getOriginalAssetEth( export async function getOriginalAssetEth(
tokenBridgeAddress: string, tokenBridgeAddress: string,
provider: ethers.providers.Web3Provider, provider: ethers.providers.Web3Provider,
wrappedAddress: string wrappedAddress: string,
lookupChainId: ChainId
): Promise<WormholeWrappedInfo> { ): Promise<WormholeWrappedInfo> {
const isWrapped = await getIsWrappedAssetEth( const isWrapped = await getIsWrappedAssetEth(
tokenBridgeAddress, tokenBridgeAddress,
@ -50,7 +46,7 @@ export async function getOriginalAssetEth(
} }
return { return {
isWrapped: false, isWrapped: false,
chainId: CHAIN_ID_ETH, chainId: lookupChainId,
assetAddress: zeroPad(arrayify(wrappedAddress), 32), assetAddress: zeroPad(arrayify(wrappedAddress), 32),
}; };
} }

View File

@ -1,5 +1,6 @@
import { import {
ChainId, ChainId,
CHAIN_ID_BSC,
CHAIN_ID_ETH, CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
CHAIN_ID_TERRA, CHAIN_ID_TERRA,
@ -21,7 +22,7 @@ export const hexToNativeString = (h: string | undefined, c: ChainId) => {
? undefined ? undefined
: c === CHAIN_ID_SOLANA : c === CHAIN_ID_SOLANA
? new PublicKey(hexToUint8Array(h)).toString() ? new PublicKey(hexToUint8Array(h)).toString()
: c === CHAIN_ID_ETH : c === CHAIN_ID_ETH || c === CHAIN_ID_BSC
? hexZeroPad(hexValue(hexToUint8Array(h)), 20) ? hexZeroPad(hexValue(hexToUint8Array(h)), 20)
: c === CHAIN_ID_TERRA : c === CHAIN_ID_TERRA
? isHexNativeTerra(h) ? isHexNativeTerra(h)