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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
import {
CHAIN_ID_BSC,
CHAIN_ID_ETH,
ChainId,
CHAIN_ID_SOLANA,
getEmitterAddressEth,
getEmitterAddressSolana,
@ -41,13 +40,14 @@ import {
selectNFTSourceChain,
} from "../../store/selectors";
import {
CHAINS,
ETH_BRIDGE_ADDRESS,
ETH_NFT_BRIDGE_ADDRESS,
CHAINS_WITH_NFT_SUPPORT,
getBridgeAddressForChain,
getNFTBridgeAddressForChain,
SOLANA_HOST,
SOL_NFT_BRIDGE_ADDRESS,
WORMHOLE_RPC_HOSTS,
} from "../../utils/consts";
import { isEVMChain } from "../../utils/ethereum";
import { getSignedVAAWithRetry } from "../../utils/getSignedVAAWithRetry";
import parseError from "../../utils/parseError";
import KeyAndBalance from "../KeyAndBalance";
@ -60,17 +60,23 @@ const useStyles = makeStyles((theme) => ({
},
}));
async function eth(
async function evm(
provider: ethers.providers.Web3Provider,
tx: string,
enqueueSnackbar: any
enqueueSnackbar: any,
chainId: ChainId
) {
try {
const receipt = await provider.getTransactionReceipt(tx);
const sequence = parseSequenceFromLogEth(receipt, ETH_BRIDGE_ADDRESS);
const emitterAddress = getEmitterAddressEth(ETH_NFT_BRIDGE_ADDRESS);
const sequence = parseSequenceFromLogEth(
receipt,
getBridgeAddressForChain(chainId)
);
const emitterAddress = getEmitterAddressEth(
getNFTBridgeAddressForChain(chainId)
);
const { vaaBytes } = await getSignedVAAWithRetry(
CHAIN_ID_ETH,
chainId,
emitterAddress,
sequence.toString(),
WORMHOLE_RPC_HOSTS.length
@ -137,14 +143,15 @@ function RecoveryDialogContent({
useEffect(() => {
if (recoverySourceTx) {
let cancelled = false;
if (recoverySourceChain === CHAIN_ID_ETH && provider) {
if (isEVMChain(recoverySourceChain) && provider) {
setRecoverySourceTxError("");
setRecoverySourceTxIsLoading(true);
(async () => {
const { vaa, error } = await eth(
const { vaa, error } = await evm(
provider,
recoverySourceTx,
enqueueSnackbar
enqueueSnackbar,
recoverySourceChain
);
if (!cancelled) {
setRecoverySourceTxIsLoading(false);
@ -259,16 +266,13 @@ function RecoveryDialogContent({
fullWidth
margin="normal"
>
{CHAINS.filter(
({ id }) => id === CHAIN_ID_ETH || id === CHAIN_ID_SOLANA
).map(({ id, name }) => (
{CHAINS_WITH_NFT_SUPPORT.map(({ id, name }) => (
<MenuItem key={id} value={id}>
{name}
</MenuItem>
))}
</TextField>
{recoverySourceChain === CHAIN_ID_ETH ||
recoverySourceChain === CHAIN_ID_BSC ? (
{isEVMChain(recoverySourceChain) ? (
<KeyAndBalance chainId={recoverySourceChain} />
) : null}
<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 { Restore, VerifiedUser } from "@material-ui/icons";
import { Alert } from "@material-ui/lab";
import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Link } from "react-router-dom";
import useIsWalletReady from "../../hooks/useIsWalletReady";
import { incrementStep, setSourceChain } from "../../store/nftSlice";
import {
selectNFTIsSourceComplete,
selectNFTShouldLockFields,
@ -11,15 +13,13 @@ import {
selectNFTSourceChain,
selectNFTSourceError,
} from "../../store/selectors";
import { incrementStep, setSourceChain } from "../../store/nftSlice";
import { CHAINS } from "../../utils/consts";
import { CHAINS_WITH_NFT_SUPPORT } from "../../utils/consts";
import { isEVMChain } from "../../utils/ethereum";
import ButtonWithLoader from "../ButtonWithLoader";
import KeyAndBalance from "../KeyAndBalance";
import LowBalanceWarning from "../LowBalanceWarning";
import StepDescription from "../StepDescription";
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) => ({
transferField: {
@ -94,15 +94,13 @@ function Source({
onChange={handleSourceChange}
disabled={shouldLockFields}
>
{CHAINS.filter(
({ id }) => id === CHAIN_ID_ETH || id === CHAIN_ID_SOLANA
).map(({ id, name }) => (
{CHAINS_WITH_NFT_SUPPORT.map(({ id, name }) => (
<MenuItem key={id} value={id}>
{name}
</MenuItem>
))}
</TextField>
{sourceChain === CHAIN_ID_ETH ? (
{isEVMChain(sourceChain) ? (
<Alert severity="info">
Only NFTs which implement ERC-721 are supported.
</Alert>

View File

@ -1,5 +1,4 @@
import {
CHAIN_ID_ETH,
CHAIN_ID_SOLANA,
hexToNativeString,
hexToUint8Array,
@ -27,7 +26,8 @@ import {
selectNFTTargetChain,
selectNFTTargetError,
} 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 KeyAndBalance from "../KeyAndBalance";
import LowBalanceWarning from "../LowBalanceWarning";
@ -48,7 +48,7 @@ function Target() {
const dispatch = useDispatch();
const sourceChain = useSelector(selectNFTSourceChain);
const chains = useMemo(
() => CHAINS.filter((c) => c.id !== sourceChain),
() => CHAINS_WITH_NFT_SUPPORT.filter((c) => c.id !== sourceChain),
[sourceChain]
);
const targetChain = useSelector(selectNFTTargetChain);
@ -93,11 +93,8 @@ function Target() {
fullWidth
value={targetChain}
onChange={handleTargetChange}
disabled={true}
>
{chains
.filter(({ id }) => id === CHAIN_ID_ETH || id === CHAIN_ID_SOLANA)
.map(({ id, name }) => (
{chains.map(({ id, name }) => (
<MenuItem key={id} value={id}>
{name}
</MenuItem>
@ -120,7 +117,7 @@ function Target() {
value={targetAsset || ""}
disabled={true}
/>
{targetChain === CHAIN_ID_ETH ? (
{isEVMChain(targetChain) ? (
<TextField
label="TokenId"
fullWidth
@ -136,8 +133,8 @@ function Target() {
You will have to pay transaction fees on{" "}
{CHAINS_BY_ID[targetChain].name} to redeem your NFT.
</Typography>
{targetChain === CHAIN_ID_ETH && (
<EthGasEstimateSummary methodType="nft" />
{isEVMChain(targetChain) && (
<EthGasEstimateSummary methodType="nft" chainId={targetChain} />
)}
</Alert>
<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 { useSelector } from "react-redux";
import {
@ -9,6 +9,7 @@ import {
selectNFTTargetChain,
selectNFTTransferTx,
} from "../../store/selectors";
import { isEVMChain } from "../../utils/ethereum";
import { WAITING_FOR_WALLET_AND_CONF } from "../Transfer/WaitingForWalletMessage";
const useStyles = makeStyles((theme) => ({
@ -33,7 +34,7 @@ export default function WaitingForWalletMessage() {
{WAITING_FOR_WALLET_AND_CONF}{" "}
{targetChain === CHAIN_ID_SOLANA && isRedeeming
? "Note: there will be several transactions"
: sourceChain === CHAIN_ID_ETH && isSending
: isEVMChain(sourceChain) && isSending
? "Note: there will be two transactions"
: null}
</Typography>

View File

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

View File

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

View File

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

View File

@ -30,7 +30,7 @@ import { NFTParsedTokenAccount } from "../../store/nftSlice";
import NFTViewer from "./NFTViewer";
import { useDebounce } from "use-debounce/lib";
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";
const useStyles = makeStyles((theme) =>
@ -83,7 +83,10 @@ const getLogo = (account: ParsedTokenAccount | null) => {
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(
WORMHOLE_V1_ETH_ADDRESS,
provider
@ -102,6 +105,7 @@ type EthereumSourceTokenSelectorProps = {
tokenAccounts: DataWrapper<ParsedTokenAccount[]> | undefined;
disabled: boolean;
resetAccounts: (() => void) | undefined;
chainId: ChainId;
nft?: boolean;
};
@ -186,6 +190,7 @@ export default function EthereumSourceTokenSelector(
tokenAccounts,
disabled,
resetAccounts,
chainId,
nft,
} = props;
const classes = useStyles();
@ -258,7 +263,7 @@ export default function EthereumSourceTokenSelector(
onChange(autocompleteHolder);
return;
}
isWormholev1(provider, autocompleteHolder.mintKey).then(
isWormholev1(provider, autocompleteHolder.mintKey, chainId).then(
(result) => {
if (!cancelled) {
result
@ -282,7 +287,7 @@ export default function EthereumSourceTokenSelector(
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
// it on the state.
@ -353,7 +358,8 @@ export default function EthereumSourceTokenSelector(
//Validate that the token is not a wormhole v1 asset
const isWormholePromise = isWormholev1(
provider,
advancedModeHolderString
advancedModeHolderString,
chainId
).then(
(result) => {
if (result && !cancelled) {
@ -430,6 +436,7 @@ export default function EthereumSourceTokenSelector(
onChange,
nft,
advancedModeHolderTokenId,
chainId,
]);
const handleClick = useCallback(() => {
@ -563,7 +570,7 @@ export default function EthereumSourceTokenSelector(
const content = value ? (
<>
{nft ? (
<NFTViewer value={value} chainId={CHAIN_ID_ETH} />
<NFTViewer value={value} chainId={chainId} />
) : (
<RefreshButtonWrapper callback={resetAccountWrapper}>
<Typography>

View File

@ -13,10 +13,12 @@ import { NFTParsedTokenAccount } from "../../store/nftSlice";
import clsx from "clsx";
import {
ChainId,
CHAIN_ID_BSC,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA,
} from "@certusone/wormhole-sdk";
import SmartAddress from "../SmartAddress";
import bscIcon from "../../icons/bsc.svg";
import ethIcon from "../../icons/eth.svg";
import solanaIcon from "../../icons/solana.svg";
import useCopyToClipboard from "../../hooks/useCopyToClipboard";
@ -53,6 +55,18 @@ const LogoIcon = ({ chainId }: { chainId: ChainId }) =>
src={ethIcon}
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;
const useStyles = makeStyles((theme) => ({
@ -127,6 +141,12 @@ const useStyles = makeStyles((theme) => ({
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%)",
},
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: {
// colors from https://solana.com/branding/new/exchange/exchange-sq-black.svg
backgroundColor: "rgb(153,69,255)",
@ -211,6 +231,7 @@ export default function NFTViewer({
<div
className={clsx(classes.cardInset, {
[classes.eth]: chainId === CHAIN_ID_ETH,
[classes.bsc]: chainId === CHAIN_ID_BSC,
[classes.solana]: chainId === CHAIN_ID_SOLANA,
})}
>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,12 @@
import {
ChainId,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA,
CHAIN_ID_TERRA,
getOriginalAssetEth,
getOriginalAssetSol,
getOriginalAssetTerra,
WormholeWrappedInfo,
uint8ArrayToHex,
WormholeWrappedInfo,
} from "@certusone/wormhole-sdk";
import {
getOriginalAssetEth as getOriginalAssetEthNFT,
@ -18,6 +17,7 @@ import { LCDClient } from "@terra-money/terra.js";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
import { setSourceWormholeWrappedInfo as setNFTSourceWormholeWrappedInfo } from "../store/nftSlice";
import {
selectNFTSourceAsset,
selectNFTSourceChain,
@ -25,16 +25,16 @@ import {
selectTransferSourceAsset,
selectTransferSourceChain,
} from "../store/selectors";
import { setSourceWormholeWrappedInfo as setNFTSourceWormholeWrappedInfo } from "../store/nftSlice";
import { setSourceWormholeWrappedInfo as setTransferSourceWormholeWrappedInfo } from "../store/transferSlice";
import {
ETH_NFT_BRIDGE_ADDRESS,
ETH_TOKEN_BRIDGE_ADDRESS,
getNFTBridgeAddressForChain,
getTokenBridgeAddressForChain,
SOLANA_HOST,
SOL_NFT_BRIDGE_ADDRESS,
SOL_TOKEN_BRIDGE_ADDRESS,
TERRA_HOST,
} from "../utils/consts";
import { isEVMChain } from "../utils/ethereum";
export interface StateSafeWormholeWrappedInfo {
isWrapped: boolean;
@ -74,19 +74,21 @@ function useCheckIfWormholeWrapped(nft?: boolean) {
dispatch(setSourceWormholeWrappedInfo(undefined));
let cancelled = false;
(async () => {
if (sourceChain === CHAIN_ID_ETH && provider && sourceAsset) {
if (isEVMChain(sourceChain) && provider && sourceAsset) {
const wrappedInfo = makeStateSafe(
await (nft
? getOriginalAssetEthNFT(
ETH_NFT_BRIDGE_ADDRESS,
getNFTBridgeAddressForChain(sourceChain),
provider,
sourceAsset,
tokenId
tokenId,
sourceChain
)
: getOriginalAssetEth(
ETH_TOKEN_BRIDGE_ADDRESS,
getTokenBridgeAddressForChain(sourceChain),
provider,
sourceAsset
sourceAsset,
sourceChain
))
);
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 { useEffect, useMemo, useState } from "react";
import {
@ -6,9 +6,10 @@ import {
useEthereumProvider,
} from "../contexts/EthereumProviderContext";
import { DataWrapper } from "../store/helpers";
import { isEVMChain } from "../utils/ethereum";
import useIsWalletReady from "./useIsWalletReady";
export type EthMetadata = {
export type EvmMetadata = {
symbol?: string;
logo?: string;
tokenName?: string;
@ -28,7 +29,7 @@ const handleError = () => {
const fetchSingleMetadata = async (
address: string,
provider: Provider
): Promise<EthMetadata> => {
): Promise<EvmMetadata> => {
const contract = new ethers.Contract(address, ERC20_BASIC_ABI, provider);
const [name, symbol, decimals] = await Promise.all([
contract.name().catch(handleError),
@ -39,12 +40,12 @@ const fetchSingleMetadata = async (
};
const fetchEthMetadata = async (addresses: string[], provider: Provider) => {
const promises: Promise<EthMetadata>[] = [];
const promises: Promise<EvmMetadata>[] = [];
addresses.forEach((address) => {
promises.push(fetchSingleMetadata(address, provider));
});
const resultsArray = await Promise.all(promises);
const output = new Map<string, EthMetadata>();
const output = new Map<string, EvmMetadata>();
addresses.forEach((address, index) => {
output.set(address, resultsArray[index]);
});
@ -52,19 +53,20 @@ const fetchEthMetadata = async (addresses: string[], provider: Provider) => {
return output;
};
function useEthMetadata(
addresses: string[]
): DataWrapper<Map<string, EthMetadata>> {
const { isReady } = useIsWalletReady(CHAIN_ID_ETH);
function useEvmMetadata(
addresses: string[],
chainId: ChainId
): DataWrapper<Map<string, EvmMetadata>> {
const { isReady } = useIsWalletReady(chainId);
const { provider } = useEthereumProvider();
const [isFetching, setIsFetching] = useState(false);
const [error, setError] = useState("");
const [data, setData] = useState<Map<string, EthMetadata> | null>(null);
const [data, setData] = useState<Map<string, EvmMetadata> | null>(null);
useEffect(() => {
let cancelled = false;
if (addresses.length && provider && isReady) {
if (addresses.length && provider && isReady && isEVMChain(chainId)) {
setIsFetching(true);
setError("");
setData(null);
@ -86,7 +88,7 @@ function useEthMetadata(
return () => {
cancelled = true;
};
}, [addresses, provider, isReady]);
}, [addresses, provider, isReady, chainId]);
return useMemo(
() => ({
@ -99,4 +101,4 @@ function useEthMetadata(
);
}
export default useEthMetadata;
export default useEvmMetadata;

View File

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

View File

@ -1,12 +1,11 @@
import {
CHAIN_ID_ETH,
CHAIN_ID_SOLANA,
CHAIN_ID_TERRA,
TokenImplementation__factory,
} from "@certusone/wormhole-sdk";
import { Connection, PublicKey } from "@solana/web3.js";
import { useConnectedWallet } from "@terra-money/wallet-provider";
import { LCDClient } from "@terra-money/terra.js";
import { useConnectedWallet } from "@terra-money/wallet-provider";
import { formatUnits } from "ethers/lib/utils";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
@ -22,9 +21,12 @@ import {
setSourceParsedTokenAccount,
setTargetParsedTokenAccount,
} 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";
// TODO: we only ever use this for target, could clean up and rename
/**
* 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!
@ -48,7 +50,12 @@ function useGetBalanceEffect(sourceOrTarget: "source" | "target") {
const solanaWallet = useSolanaWallet();
const solPK = solanaWallet?.publicKey;
const terraWallet = useConnectedWallet();
const { provider, signerAddress } = useEthereumProvider();
const {
provider,
signerAddress,
chainId: evmChainId,
} = useEthereumProvider();
const hasCorrectEvmNetwork = evmChainId === getEvmChainId(lookupChain);
useEffect(() => {
// source is now handled by getsourceparsedtokenaccounts
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);
token
.decimals()
@ -170,6 +182,7 @@ function useGetBalanceEffect(sourceOrTarget: "source" | "target") {
solPK,
sourceOrTarget,
terraWallet,
hasCorrectEvmNetwork,
]);
}

View File

@ -1,4 +1,6 @@
import {
ChainId,
CHAIN_ID_BSC,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA,
CHAIN_ID_TERRA,
@ -53,9 +55,12 @@ import {
import {
COVALENT_GET_TOKENS_URL,
SOLANA_HOST,
WBNB_ADDRESS,
WBNB_DECIMALS,
WETH_ADDRESS,
WETH_DECIMALS,
} from "../utils/consts";
import { isEVMChain } from "../utils/ethereum";
import {
ExtractedMintInfo,
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 = (
walletAddress: string,
covalent: CovalentData,
@ -266,9 +294,10 @@ export type CovalentNFTData = {
const getEthereumAccountsCovalent = async (
walletAddress: string,
nft?: boolean
nft: boolean,
chainId: ChainId
): Promise<CovalentData[]> => {
const url = COVALENT_GET_TOKENS_URL(CHAIN_ID_ETH, walletAddress, nft);
const url = COVALENT_GET_TOKENS_URL(chainId, walletAddress, nft);
try {
const output = [] as CovalentData[];
@ -387,8 +416,7 @@ function useGetAvailableTokens(nft: boolean = false) {
const selectedSourceWalletAddress = useSelector(
nft ? selectNFTSourceWalletAddress : selectSourceWalletAddress
);
const currentSourceWalletAddress: string | undefined =
lookupChain === CHAIN_ID_ETH
const currentSourceWalletAddress: string | undefined = isEVMChain(lookupChain)
? signerAddress
: lookupChain === CHAIN_ID_SOLANA
? solPK?.toString()
@ -539,15 +567,50 @@ function useGetAvailableTokens(nft: boolean = false) {
};
}, [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
useEffect(() => {
//const testWallet = "0xf60c2ea62edbfe808163751dd0d8693dcb30019c";
// const nftTestWallet1 = "0x3f304c6721f35ff9af00fd32650c8e0a982180ab";
// const nftTestWallet2 = "0x98ed231428088eb440e8edb5cc8d66dcf913b86e";
// const nftTestWallet3 = "0xb1fadf677a7e9b90e9d4f31c8ffb3dc18c138c6f";
// const nftBscTestWallet1 = "0x5f464a652bd1991df0be37979b93b3306d64a909";
let cancelled = false;
const walletAddress = signerAddress;
if (walletAddress && lookupChain === CHAIN_ID_ETH && !covalent) {
if (walletAddress && isEVMChain(lookupChain) && !covalent) {
//TODO less cancel
!cancelled && setCovalentLoading(true);
!cancelled &&
@ -556,7 +619,7 @@ function useGetAvailableTokens(nft: boolean = false) {
? fetchSourceParsedTokenAccountsNFT()
: fetchSourceParsedTokenAccounts()
);
getEthereumAccountsCovalent(walletAddress, nft).then(
getEthereumAccountsCovalent(walletAddress, nft, lookupChain).then(
(accounts) => {
!cancelled && setCovalentLoading(false);
!cancelled && setCovalentError(undefined);
@ -639,7 +702,7 @@ function useGetAvailableTokens(nft: boolean = false) {
},
resetAccounts: resetSourceAccounts,
}
: lookupChain === CHAIN_ID_ETH
: isEVMChain(lookupChain)
? {
tokenAccounts: ethAccounts,
covalent: {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
import {
ChainId,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA,
CHAIN_ID_TERRA,
} from "@certusone/wormhole-sdk";
@ -9,7 +8,8 @@ import { useConnectedWallet } from "@terra-money/wallet-provider";
import { useMemo } from "react";
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
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 = (
isReady: boolean,
@ -33,10 +33,11 @@ function useIsWalletReady(chainId: ChainId): {
const {
provider,
signerAddress,
chainId: ethChainId,
chainId: evmChainId,
} = useEthereumProvider();
const hasEthInfo = !!provider && !!signerAddress;
const hasCorrectEthNetwork = ethChainId === ETH_NETWORK_CHAIN_ID;
const correctEvmNetwork = getEvmChainId(chainId);
const hasCorrectEvmNetwork = evmChainId === correctEvmNetwork;
return useMemo(() => {
if (
@ -50,20 +51,20 @@ function useIsWalletReady(chainId: ChainId): {
if (chainId === CHAIN_ID_SOLANA && solPK) {
return createWalletStatus(true, undefined, solPK.toString());
}
if (chainId === CHAIN_ID_ETH && hasEthInfo && signerAddress) {
if (hasCorrectEthNetwork) {
if (isEVMChain(chainId) && hasEthInfo && signerAddress) {
if (hasCorrectEvmNetwork) {
return createWalletStatus(true, undefined, signerAddress);
} else {
if (provider) {
if (provider && correctEvmNetwork) {
try {
provider.send("wallet_switchEthereumChain", [
{ chainId: hexStripZeros(hexlify(ETH_NETWORK_CHAIN_ID)) },
{ chainId: hexStripZeros(hexlify(correctEvmNetwork)) },
]);
} catch (e) {}
}
return createWalletStatus(
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
);
}
@ -75,7 +76,8 @@ function useIsWalletReady(chainId: ChainId): {
hasTerraWallet,
solPK,
hasEthInfo,
hasCorrectEthNetwork,
correctEvmNetwork,
hasCorrectEvmNetwork,
provider,
signerAddress,
terraWallet,

View File

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

View File

@ -1,8 +1,7 @@
import {
CHAIN_ID_ETH,
canonicalAddress,
CHAIN_ID_SOLANA,
CHAIN_ID_TERRA,
canonicalAddress,
uint8ArrayToHex,
} from "@certusone/wormhole-sdk";
import { arrayify, zeroPad } from "@ethersproject/bytes";
@ -17,6 +16,7 @@ import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
import { setTargetAddressHex as setNFTTargetAddressHex } from "../store/nftSlice";
import {
selectNFTTargetAsset,
selectNFTTargetChain,
@ -24,8 +24,8 @@ import {
selectTransferTargetChain,
selectTransferTargetParsedTokenAccount,
} from "../store/selectors";
import { setTargetAddressHex as setNFTTargetAddressHex } from "../store/nftSlice";
import { setTargetAddressHex as setTransferTargetAddressHex } from "../store/transferSlice";
import { isEVMChain } from "../utils/ethereum";
function useSyncTargetAddress(shouldFire: boolean, nft?: boolean) {
const dispatch = useDispatch();
@ -49,7 +49,7 @@ function useSyncTargetAddress(shouldFire: boolean, nft?: boolean) {
useEffect(() => {
if (shouldFire) {
let cancelled = false;
if (targetChain === CHAIN_ID_ETH && signerAddress) {
if (isEVMChain(targetChain) && signerAddress) {
dispatch(
setTargetAddressHex(
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 { useCallback, useEffect, useMemo, useState } from "react";
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
import { SOLANA_HOST } from "../utils/consts";
import { getDefaultNativeCurrencySymbol, SOLANA_HOST } from "../utils/consts";
import { getMultipleAccountsRPC } from "../utils/solana";
import useIsWalletReady from "./useIsWalletReady";
@ -142,9 +142,9 @@ export default function useTransactionFees(chainId: ChainId) {
return results;
}
export function useEthereumGasPrice(contract: MethodType) {
export function useEthereumGasPrice(contract: MethodType, chainId: ChainId) {
const { provider } = useEthereumProvider();
const { isReady } = useIsWalletReady(CHAIN_ID_ETH);
const { isReady } = useIsWalletReady(chainId);
const [estimateResults, setEstimateResults] = useState<GasEstimate | null>(
null
);
@ -168,10 +168,12 @@ export function useEthereumGasPrice(contract: MethodType) {
export function EthGasEstimateSummary({
methodType,
chainId,
}: {
methodType: MethodType;
chainId: ChainId;
}) {
const estimate = useEthereumGasPrice(methodType);
const estimate = useEthereumGasPrice(methodType, chainId);
if (!estimate) {
return null;
}
@ -192,7 +194,8 @@ export function EthGasEstimateSummary({
</div>
<div>&nbsp;&nbsp;&nbsp;</div>
<div>
Est. Fees: {estimate.lowEstimate} - {estimate.highEstimate} ETH
Est. Fees: {estimate.lowEstimate} - {estimate.highEstimate}{" "}
{getDefaultNativeCurrencySymbol(chainId)}
</div>
</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 { parseUnits } from "ethers/lib/utils";
import { RootState } from ".";
import { isEVMChain } from "../utils/ethereum";
/*
* Attest
@ -259,7 +260,7 @@ export const selectTransferTargetError = (state: RootState) => {
return UNREGISTERED_ERROR_MESSAGE;
}
if (
state.transfer.targetChain === CHAIN_ID_ETH &&
isEVMChain(state.transfer.targetChain) &&
state.transfer.targetAsset === ethers.constants.AddressZero
) {
return UNREGISTERED_ERROR_MESSAGE;

View File

@ -64,11 +64,33 @@ export const CHAINS =
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 const CHAINS_BY_ID: ChainsById = CHAINS.reduce((obj, chain) => {
obj[chain.id] = chain;
return obj;
}, {} 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 =
CLUSTER === "mainnet"
? [
@ -81,11 +103,19 @@ export const WORMHOLE_RPC_HOSTS =
? [
"https://wormhole-v2-testnet-api.certus.one",
"https://wormhole-v2-testnet-api.mcf.rocks",
"https://wormhole-v2-testnet-api.chainlayer.network"
"https://wormhole-v2-testnet-api.chainlayer.network",
]
: ["http://localhost:7071"];
export const ETH_NETWORK_CHAIN_ID =
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
? process.env.REACT_APP_SOLANA_API_URL
: CLUSTER === "mainnet"
@ -127,6 +157,27 @@ export const ETH_TOKEN_BRIDGE_ADDRESS = getAddress(
? "0xa6CDAddA6e4B6704705b065E01E52e2486c0FBf6"
: "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 =
CLUSTER === "mainnet"
? "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth"
@ -151,24 +202,64 @@ export const SOL_CUSTODY_ADDRESS =
export const TERRA_TEST_TOKEN_ADDRESS =
"terra13nkgqrfymug724h8pprpexqj9h629sa3ncw7sh";
export const TERRA_BRIDGE_ADDRESS =
"terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5";
CLUSTER === "mainnet"
? "terra1dq03ugtd40zu9hcgdzrsq6z2z4hwhc9tqk2uy5"
: CLUSTER === "testnet"
? "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5"
: "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5";
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
? process.env.REACT_APP_COVALENT_API_KEY
: "";
export const COVALENT_ETHEREUM_MAINNET = "1";
export const COVALENT_BSC_MAINNET = "56";
export const COVALENT_GET_TOKENS_URL = (
chainId: ChainId,
walletAddress: string,
nft?: boolean
) => {
let chainNum = "";
if (chainId === CHAIN_ID_ETH) {
chainNum = COVALENT_ETHEREUM_MAINNET;
}
const chainNum =
chainId === CHAIN_ID_ETH
? COVALENT_ETHEREUM_MAINNET
: chainId === CHAIN_ID_BSC
? COVALENT_BSC_MAINNET
: "";
// 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}${
nft ? "&nft=true" : ""
@ -183,6 +274,14 @@ export const WETH_ADDRESS =
: "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E";
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 =
CLUSTER === "mainnet"
? "0xf92cD566Ea4864356C5491c177A430C222d7e678"

View File

@ -1,4 +1,7 @@
import {
ChainId,
CHAIN_ID_BSC,
CHAIN_ID_ETH,
NFTImplementation,
NFTImplementation__factory,
TokenImplementation,
@ -11,6 +14,9 @@ import {
createParsedTokenAccount,
} 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.
export async function getEthereumToken(
tokenAddress: string,

View File

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

View File

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

View File

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

View File

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