335 lines
9.7 KiB
TypeScript
335 lines
9.7 KiB
TypeScript
import {
|
|
ChainId,
|
|
CHAIN_ID_ETH,
|
|
CHAIN_ID_SOLANA,
|
|
CHAIN_ID_TERRA,
|
|
getEmitterAddressEth,
|
|
getEmitterAddressSolana,
|
|
getEmitterAddressTerra,
|
|
parseSequenceFromLogEth,
|
|
parseSequenceFromLogSolana,
|
|
parseSequenceFromLogTerra,
|
|
transferFromEth,
|
|
transferFromSolana,
|
|
transferFromTerra,
|
|
} from "@certusone/wormhole-sdk";
|
|
import { WalletContextState } from "@solana/wallet-adapter-react";
|
|
import { Connection } from "@solana/web3.js";
|
|
import {
|
|
ConnectedWallet,
|
|
useConnectedWallet,
|
|
} from "@terra-money/wallet-provider";
|
|
import { Signer } from "ethers";
|
|
import { parseUnits, zeroPad } from "ethers/lib/utils";
|
|
import { useSnackbar } from "notistack";
|
|
import { useCallback, useMemo } from "react";
|
|
import { useDispatch, useSelector } from "react-redux";
|
|
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
|
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
|
|
import {
|
|
selectTransferAmount,
|
|
selectTransferIsSendComplete,
|
|
selectTransferIsSending,
|
|
selectTransferIsTargetComplete,
|
|
selectTransferOriginAsset,
|
|
selectTransferOriginChain,
|
|
selectTransferSourceAsset,
|
|
selectTransferSourceChain,
|
|
selectTransferSourceParsedTokenAccount,
|
|
selectTransferTargetChain,
|
|
} from "../store/selectors";
|
|
import {
|
|
setIsSending,
|
|
setSignedVAAHex,
|
|
setTransferTx,
|
|
} from "../store/transferSlice";
|
|
import { hexToUint8Array, uint8ArrayToHex } from "../utils/array";
|
|
import {
|
|
ETH_BRIDGE_ADDRESS,
|
|
ETH_TOKEN_BRIDGE_ADDRESS,
|
|
SOLANA_HOST,
|
|
SOL_BRIDGE_ADDRESS,
|
|
SOL_TOKEN_BRIDGE_ADDRESS,
|
|
TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
} from "../utils/consts";
|
|
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(
|
|
dispatch: any,
|
|
enqueueSnackbar: any,
|
|
signer: Signer,
|
|
tokenAddress: string,
|
|
decimals: number,
|
|
amount: string,
|
|
recipientChain: ChainId,
|
|
recipientAddress: Uint8Array
|
|
) {
|
|
dispatch(setIsSending(true));
|
|
try {
|
|
const amountParsed = parseUnits(amount, decimals);
|
|
const receipt = await transferFromEth(
|
|
ETH_TOKEN_BRIDGE_ADDRESS,
|
|
signer,
|
|
tokenAddress,
|
|
amountParsed,
|
|
recipientChain,
|
|
recipientAddress
|
|
);
|
|
dispatch(
|
|
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);
|
|
enqueueSnackbar("Fetching VAA", { variant: "info" });
|
|
const { vaaBytes } = await getSignedVAAWithRetry(
|
|
CHAIN_ID_ETH,
|
|
emitterAddress,
|
|
sequence.toString()
|
|
);
|
|
dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes)));
|
|
enqueueSnackbar("Fetched Signed VAA", { variant: "success" });
|
|
} catch (e) {
|
|
console.error(e);
|
|
enqueueSnackbar(parseError(e), { variant: "error" });
|
|
dispatch(setIsSending(false));
|
|
}
|
|
}
|
|
|
|
async function solana(
|
|
dispatch: any,
|
|
enqueueSnackbar: any,
|
|
wallet: WalletContextState,
|
|
payerAddress: string, //TODO: we may not need this since we have wallet
|
|
fromAddress: string,
|
|
mintAddress: string,
|
|
amount: string,
|
|
decimals: number,
|
|
targetChain: ChainId,
|
|
targetAddress: Uint8Array,
|
|
originAddressStr?: string,
|
|
originChain?: ChainId
|
|
) {
|
|
dispatch(setIsSending(true));
|
|
try {
|
|
//TODO: check if token attestation exists on the target chain
|
|
// TODO: share connection in context?
|
|
const connection = new Connection(SOLANA_HOST, "confirmed");
|
|
const amountParsed = parseUnits(amount, decimals).toBigInt();
|
|
const originAddress = originAddressStr
|
|
? zeroPad(hexToUint8Array(originAddressStr), 32)
|
|
: undefined;
|
|
const transaction = await transferFromSolana(
|
|
connection,
|
|
SOL_BRIDGE_ADDRESS,
|
|
SOL_TOKEN_BRIDGE_ADDRESS,
|
|
payerAddress,
|
|
fromAddress,
|
|
mintAddress,
|
|
amountParsed,
|
|
targetAddress,
|
|
targetChain,
|
|
originAddress,
|
|
originChain
|
|
);
|
|
const txid = await signSendAndConfirm(wallet, connection, transaction);
|
|
enqueueSnackbar("Transaction confirmed", { variant: "success" });
|
|
const info = await connection.getTransaction(txid);
|
|
if (!info) {
|
|
throw new Error("An error occurred while fetching the transaction info");
|
|
}
|
|
dispatch(setTransferTx({ id: txid, block: info.slot }));
|
|
enqueueSnackbar("Transaction confirmed", { variant: "success" });
|
|
const sequence = parseSequenceFromLogSolana(info);
|
|
const emitterAddress = await getEmitterAddressSolana(
|
|
SOL_TOKEN_BRIDGE_ADDRESS
|
|
);
|
|
enqueueSnackbar("Fetching VAA", { variant: "info" });
|
|
const { vaaBytes } = await getSignedVAAWithRetry(
|
|
CHAIN_ID_SOLANA,
|
|
emitterAddress,
|
|
sequence
|
|
);
|
|
|
|
dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes)));
|
|
enqueueSnackbar("Fetched Signed VAA", { variant: "success" });
|
|
} catch (e) {
|
|
console.error(e);
|
|
enqueueSnackbar(parseError(e), { variant: "error" });
|
|
dispatch(setIsSending(false));
|
|
}
|
|
}
|
|
|
|
async function terra(
|
|
dispatch: any,
|
|
enqueueSnackbar: any,
|
|
wallet: ConnectedWallet,
|
|
asset: string,
|
|
amount: string,
|
|
decimals: number,
|
|
targetChain: ChainId,
|
|
targetAddress: Uint8Array
|
|
) {
|
|
dispatch(setIsSending(true));
|
|
try {
|
|
const amountParsed = parseUnits(amount, decimals).toString();
|
|
const msgs = await transferFromTerra(
|
|
wallet.terraAddress,
|
|
TERRA_TOKEN_BRIDGE_ADDRESS,
|
|
asset,
|
|
amountParsed,
|
|
targetChain,
|
|
targetAddress
|
|
);
|
|
const result = await wallet.post({
|
|
msgs: [...msgs],
|
|
memo: "Wormhole - Initiate Transfer",
|
|
});
|
|
console.log(result);
|
|
const info = await waitForTerraExecution(result);
|
|
console.log(info);
|
|
dispatch(setTransferTx({ id: info.txhash, block: info.height }));
|
|
enqueueSnackbar("Transaction confirmed", { variant: "success" });
|
|
const sequence = parseSequenceFromLogTerra(info);
|
|
console.log(sequence);
|
|
if (!sequence) {
|
|
throw new Error("Sequence not found");
|
|
}
|
|
const emitterAddress = await getEmitterAddressTerra(
|
|
TERRA_TOKEN_BRIDGE_ADDRESS
|
|
);
|
|
console.log(emitterAddress);
|
|
enqueueSnackbar("Fetching VAA", { variant: "info" });
|
|
const { vaaBytes } = await getSignedVAAWithRetry(
|
|
CHAIN_ID_TERRA,
|
|
emitterAddress,
|
|
sequence
|
|
);
|
|
enqueueSnackbar("Fetched Signed VAA", { variant: "success" });
|
|
dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes)));
|
|
} catch (e) {
|
|
console.error(e);
|
|
enqueueSnackbar(parseError(e), { variant: "error" });
|
|
dispatch(setIsSending(false));
|
|
}
|
|
}
|
|
|
|
export function useHandleTransfer() {
|
|
const dispatch = useDispatch();
|
|
const { enqueueSnackbar } = useSnackbar();
|
|
const sourceChain = useSelector(selectTransferSourceChain);
|
|
const sourceAsset = useSelector(selectTransferSourceAsset);
|
|
const originChain = useSelector(selectTransferOriginChain);
|
|
const originAsset = useSelector(selectTransferOriginAsset);
|
|
const amount = useSelector(selectTransferAmount);
|
|
const targetChain = useSelector(selectTransferTargetChain);
|
|
const targetAddress = useTransferTargetAddressHex();
|
|
const isTargetComplete = useSelector(selectTransferIsTargetComplete);
|
|
const isSending = useSelector(selectTransferIsSending);
|
|
const isSendComplete = useSelector(selectTransferIsSendComplete);
|
|
const { signer } = useEthereumProvider();
|
|
const solanaWallet = useSolanaWallet();
|
|
const solPK = solanaWallet?.publicKey;
|
|
const terraWallet = useConnectedWallet();
|
|
const sourceParsedTokenAccount = useSelector(
|
|
selectTransferSourceParsedTokenAccount
|
|
);
|
|
const sourceTokenPublicKey = sourceParsedTokenAccount?.publicKey;
|
|
const decimals = sourceParsedTokenAccount?.decimals;
|
|
const disabled = !isTargetComplete || isSending || isSendComplete;
|
|
const handleTransferClick = useCallback(() => {
|
|
// TODO: we should separate state for transaction vs fetching vaa
|
|
// TODO: more generic way of calling these
|
|
if (
|
|
sourceChain === CHAIN_ID_ETH &&
|
|
!!signer &&
|
|
!!sourceAsset &&
|
|
decimals !== undefined &&
|
|
!!targetAddress
|
|
) {
|
|
eth(
|
|
dispatch,
|
|
enqueueSnackbar,
|
|
signer,
|
|
sourceAsset,
|
|
decimals,
|
|
amount,
|
|
targetChain,
|
|
targetAddress
|
|
);
|
|
} else if (
|
|
sourceChain === CHAIN_ID_SOLANA &&
|
|
!!solanaWallet &&
|
|
!!solPK &&
|
|
!!sourceAsset &&
|
|
!!sourceTokenPublicKey &&
|
|
!!targetAddress &&
|
|
decimals !== undefined
|
|
) {
|
|
solana(
|
|
dispatch,
|
|
enqueueSnackbar,
|
|
solanaWallet,
|
|
solPK.toString(),
|
|
sourceTokenPublicKey,
|
|
sourceAsset,
|
|
amount,
|
|
decimals,
|
|
targetChain,
|
|
targetAddress,
|
|
originAsset,
|
|
originChain
|
|
);
|
|
} else if (
|
|
sourceChain === CHAIN_ID_TERRA &&
|
|
!!terraWallet &&
|
|
!!sourceAsset &&
|
|
decimals !== undefined &&
|
|
!!targetAddress
|
|
) {
|
|
terra(
|
|
dispatch,
|
|
enqueueSnackbar,
|
|
terraWallet,
|
|
sourceAsset,
|
|
amount,
|
|
decimals,
|
|
targetChain,
|
|
targetAddress
|
|
);
|
|
} else {
|
|
// enqueueSnackbar("Transfers from this chain are not yet supported", {
|
|
// variant: "error",
|
|
// });
|
|
}
|
|
}, [
|
|
dispatch,
|
|
enqueueSnackbar,
|
|
sourceChain,
|
|
signer,
|
|
solanaWallet,
|
|
solPK,
|
|
terraWallet,
|
|
sourceTokenPublicKey,
|
|
sourceAsset,
|
|
amount,
|
|
decimals,
|
|
targetChain,
|
|
targetAddress,
|
|
originAsset,
|
|
originChain,
|
|
]);
|
|
return useMemo(
|
|
() => ({
|
|
handleClick: handleTransferClick,
|
|
disabled,
|
|
showLoader: isSending,
|
|
}),
|
|
[handleTransferClick, disabled, isSending]
|
|
);
|
|
}
|