bridge_ui: notistack, hooks, cleanup
Change-Id: Ia553e514afee655c6cd8e26320e539fc59041e49
This commit is contained in:
parent
0fab2481ca
commit
5f5a2a56f5
|
@ -31,6 +31,7 @@
|
|||
"@terra-money/wallet-provider": "^1.4.0-alpha.1",
|
||||
"ethers": "^5.4.1",
|
||||
"js-base64": "^3.6.1",
|
||||
"notistack": "^1.0.10",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-redux": "^7.2.4",
|
||||
|
@ -26881,6 +26882,24 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/notistack": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/notistack/-/notistack-1.0.10.tgz",
|
||||
"integrity": "sha512-z0y4jJaVtOoH3kc3GtNUlhNTY+5LE04QDeLVujX3VPhhzg67zw055mZjrBF+nzpv3V9aiPNph1EgRU4+t8kQTQ==",
|
||||
"dependencies": {
|
||||
"clsx": "^1.1.0",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/notistack"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@material-ui/core": "^4.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-bundled": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz",
|
||||
|
@ -61189,6 +61208,15 @@
|
|||
"sort-keys": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"notistack": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/notistack/-/notistack-1.0.10.tgz",
|
||||
"integrity": "sha512-z0y4jJaVtOoH3kc3GtNUlhNTY+5LE04QDeLVujX3VPhhzg67zw055mZjrBF+nzpv3V9aiPNph1EgRU4+t8kQTQ==",
|
||||
"requires": {
|
||||
"clsx": "^1.1.0",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"npm-bundled": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz",
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
"@terra-money/wallet-provider": "^1.4.0-alpha.1",
|
||||
"ethers": "^5.4.1",
|
||||
"js-base64": "^3.6.1",
|
||||
"notistack": "^1.0.10",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-redux": "^7.2.4",
|
||||
|
|
|
@ -1,25 +1,5 @@
|
|||
import {
|
||||
CHAIN_ID_TERRA,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { Button, CircularProgress, makeStyles } from "@material-ui/core";
|
||||
import { useCallback } from "react";
|
||||
import { useConnectedWallet } from "@terra-money/wallet-provider";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useEthereumProvider } from "../../contexts/EthereumProviderContext";
|
||||
import { useSolanaWallet } from "../../contexts/SolanaWalletContext";
|
||||
import useAttestSignedVAA from "../../hooks/useAttestSignedVAA";
|
||||
import { reset, setIsCreating } from "../../store/attestSlice";
|
||||
import {
|
||||
selectAttestIsCreating,
|
||||
selectAttestTargetChain,
|
||||
} from "../../store/selectors";
|
||||
import {
|
||||
createWrappedOnEth,
|
||||
createWrappedOnSolana,
|
||||
createWrappedOnTerra,
|
||||
} from "../../utils/createWrappedOn";
|
||||
import { useHandleCreateWrapped } from "../../hooks/useHandleCreateWrapped";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
transferButton: {
|
||||
|
@ -30,49 +10,20 @@ const useStyles = makeStyles((theme) => ({
|
|||
}));
|
||||
|
||||
function Create() {
|
||||
const dispatch = useDispatch();
|
||||
const classes = useStyles();
|
||||
const targetChain = useSelector(selectAttestTargetChain);
|
||||
const solanaWallet = useSolanaWallet();
|
||||
const solPK = solanaWallet?.publicKey;
|
||||
const signedVAA = useAttestSignedVAA();
|
||||
const isCreating = useSelector(selectAttestIsCreating);
|
||||
const { signer } = useEthereumProvider();
|
||||
const terraWallet = useConnectedWallet();
|
||||
const handleCreateClick = useCallback(() => {
|
||||
if (targetChain === CHAIN_ID_SOLANA && signedVAA) {
|
||||
(async () => {
|
||||
dispatch(setIsCreating(true));
|
||||
await createWrappedOnSolana(solanaWallet, solPK?.toString(), signedVAA);
|
||||
dispatch(reset());
|
||||
})();
|
||||
}
|
||||
if (targetChain === CHAIN_ID_ETH && signedVAA) {
|
||||
(async () => {
|
||||
dispatch(setIsCreating(true));
|
||||
await createWrappedOnEth(signer, signedVAA);
|
||||
dispatch(reset());
|
||||
})();
|
||||
}
|
||||
if (targetChain === CHAIN_ID_TERRA && signedVAA) {
|
||||
(async () => {
|
||||
dispatch(setIsCreating(true));
|
||||
createWrappedOnTerra(terraWallet, signedVAA);
|
||||
})();
|
||||
}
|
||||
}, [dispatch, targetChain, solanaWallet, solPK, signedVAA, signer]);
|
||||
const { handleClick, disabled, showLoader } = useHandleCreateWrapped();
|
||||
return (
|
||||
<div style={{ position: "relative" }}>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
className={classes.transferButton}
|
||||
disabled={isCreating}
|
||||
onClick={handleCreateClick}
|
||||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
{isCreating ? (
|
||||
{showLoader ? (
|
||||
<CircularProgress
|
||||
size={24}
|
||||
color="inherit"
|
||||
|
|
|
@ -1,28 +1,5 @@
|
|||
import {
|
||||
CHAIN_ID_TERRA,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { Button, CircularProgress, makeStyles } from "@material-ui/core";
|
||||
import { useCallback } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useEthereumProvider } from "../../contexts/EthereumProviderContext";
|
||||
import { useSolanaWallet } from "../../contexts/SolanaWalletContext";
|
||||
import { useConnectedWallet } from "@terra-money/wallet-provider";
|
||||
import { setIsSending, setSignedVAAHex } from "../../store/attestSlice";
|
||||
import {
|
||||
selectAttestIsSendComplete,
|
||||
selectAttestIsSending,
|
||||
selectAttestIsTargetComplete,
|
||||
selectAttestSourceAsset,
|
||||
selectAttestSourceChain,
|
||||
} from "../../store/selectors";
|
||||
import { uint8ArrayToHex } from "../../utils/array";
|
||||
import {
|
||||
attestFromEth,
|
||||
attestFromSolana,
|
||||
attestFromTerra,
|
||||
} from "../../utils/attestFrom";
|
||||
import { useHandleAttest } from "../../hooks/useHandleAttest";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
transferButton: {
|
||||
|
@ -32,67 +9,9 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
// TODO: move attest to its own workflow
|
||||
|
||||
function Send() {
|
||||
const classes = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const sourceChain = useSelector(selectAttestSourceChain);
|
||||
const sourceAsset = useSelector(selectAttestSourceAsset);
|
||||
const isTargetComplete = useSelector(selectAttestIsTargetComplete);
|
||||
const isSending = useSelector(selectAttestIsSending);
|
||||
const isSendComplete = useSelector(selectAttestIsSendComplete);
|
||||
const { signer } = useEthereumProvider();
|
||||
const solanaWallet = useSolanaWallet();
|
||||
const terraWallet = useConnectedWallet();
|
||||
const solPK = solanaWallet?.publicKey;
|
||||
// TODO: dynamically get "to" wallet
|
||||
const handleAttestClick = useCallback(() => {
|
||||
if (sourceChain === CHAIN_ID_ETH) {
|
||||
//TODO: just for testing, this should eventually use the store to communicate between steps
|
||||
(async () => {
|
||||
dispatch(setIsSending(true));
|
||||
try {
|
||||
const vaaBytes = await attestFromEth(signer, sourceAsset);
|
||||
console.log("bytes in attest", vaaBytes);
|
||||
vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes)));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
dispatch(setIsSending(false));
|
||||
}
|
||||
})();
|
||||
} else if (sourceChain === CHAIN_ID_SOLANA) {
|
||||
//TODO: just for testing, this should eventually use the store to communicate between steps
|
||||
(async () => {
|
||||
dispatch(setIsSending(true));
|
||||
try {
|
||||
const vaaBytes = await attestFromSolana(
|
||||
solanaWallet,
|
||||
solPK?.toString(),
|
||||
sourceAsset
|
||||
);
|
||||
console.log("bytes in attest", vaaBytes);
|
||||
vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes)));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
dispatch(setIsSending(false));
|
||||
}
|
||||
})();
|
||||
} else if (sourceChain === CHAIN_ID_TERRA) {
|
||||
//TODO: just for testing, this should eventually use the store to communicate between steps
|
||||
(async () => {
|
||||
dispatch(setIsSending(true));
|
||||
try {
|
||||
const vaaBytes = await attestFromTerra(terraWallet, sourceAsset);
|
||||
console.log("bytes in attest", vaaBytes);
|
||||
vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes)));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
dispatch(setIsSending(false));
|
||||
}
|
||||
})();
|
||||
}
|
||||
}, [dispatch, sourceChain, signer, solanaWallet, solPK, sourceAsset]);
|
||||
const { handleClick, disabled, showLoader } = useHandleAttest();
|
||||
return (
|
||||
<>
|
||||
<div style={{ position: "relative" }}>
|
||||
|
@ -100,12 +19,12 @@ function Send() {
|
|||
color="primary"
|
||||
variant="contained"
|
||||
className={classes.transferButton}
|
||||
onClick={handleAttestClick}
|
||||
disabled={!isTargetComplete || isSending || isSendComplete}
|
||||
onClick={handleClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
Attest
|
||||
</Button>
|
||||
{isSending ? (
|
||||
{showLoader ? (
|
||||
<CircularProgress
|
||||
size={24}
|
||||
color="inherit"
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { Toolbar, Typography } from "@material-ui/core";
|
||||
import { Toolbar } from "@material-ui/core";
|
||||
import DisconnectIcon from "@material-ui/icons/LinkOff";
|
||||
import {
|
||||
WalletDisconnectButton,
|
||||
WalletMultiButton,
|
||||
} from "@solana/wallet-adapter-material-ui";
|
||||
import { useWallet } from "@solana/wallet-adapter-react";
|
||||
import React, { FC } from "react";
|
||||
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
|
||||
|
||||
const SolanaWalletKey = () => {
|
||||
|
|
|
@ -1,28 +1,5 @@
|
|||
import {
|
||||
CHAIN_ID_TERRA,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { Button, CircularProgress, makeStyles } from "@material-ui/core";
|
||||
import { useCallback } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useConnectedWallet } from "@terra-money/wallet-provider";
|
||||
import { useEthereumProvider } from "../../contexts/EthereumProviderContext";
|
||||
import { useSolanaWallet } from "../../contexts/SolanaWalletContext";
|
||||
import useTransferSignedVAA from "../../hooks/useTransferSignedVAA";
|
||||
import {
|
||||
selectTransferIsRedeeming,
|
||||
selectTransferIsSourceAssetWormholeWrapped,
|
||||
selectTransferOriginChain,
|
||||
selectTransferTargetAsset,
|
||||
selectTransferTargetChain,
|
||||
} from "../../store/selectors";
|
||||
import { reset, setIsRedeeming } from "../../store/transferSlice";
|
||||
import {
|
||||
redeemOnEth,
|
||||
redeemOnSolana,
|
||||
redeemOnTerra,
|
||||
} from "../../utils/redeemOn";
|
||||
import { useHandleRedeem } from "../../hooks/useHandleRedeem";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
transferButton: {
|
||||
|
@ -33,69 +10,20 @@ const useStyles = makeStyles((theme) => ({
|
|||
}));
|
||||
|
||||
function Redeem() {
|
||||
const dispatch = useDispatch();
|
||||
const classes = useStyles();
|
||||
const isSourceAssetWormholeWrapped = useSelector(
|
||||
selectTransferIsSourceAssetWormholeWrapped
|
||||
);
|
||||
const originChain = useSelector(selectTransferOriginChain);
|
||||
const targetChain = useSelector(selectTransferTargetChain);
|
||||
const targetAsset = useSelector(selectTransferTargetAsset);
|
||||
const solanaWallet = useSolanaWallet();
|
||||
const solPK = solanaWallet?.publicKey;
|
||||
const { signer } = useEthereumProvider();
|
||||
const terraWallet = useConnectedWallet();
|
||||
const signedVAA = useTransferSignedVAA();
|
||||
const isRedeeming = useSelector(selectTransferIsRedeeming);
|
||||
const handleRedeemClick = useCallback(() => {
|
||||
if (targetChain === CHAIN_ID_ETH && signedVAA) {
|
||||
(async () => {
|
||||
dispatch(setIsRedeeming(true));
|
||||
await redeemOnEth(signer, signedVAA);
|
||||
dispatch(reset());
|
||||
})();
|
||||
}
|
||||
if (targetChain === CHAIN_ID_SOLANA && signedVAA) {
|
||||
(async () => {
|
||||
dispatch(setIsRedeeming(true));
|
||||
await redeemOnSolana(
|
||||
solanaWallet,
|
||||
solPK?.toString(),
|
||||
signedVAA,
|
||||
!!isSourceAssetWormholeWrapped && originChain === CHAIN_ID_SOLANA,
|
||||
targetAsset || undefined
|
||||
);
|
||||
dispatch(reset());
|
||||
})();
|
||||
}
|
||||
if (targetChain === CHAIN_ID_TERRA && signedVAA) {
|
||||
dispatch(setIsRedeeming(true));
|
||||
redeemOnTerra(terraWallet, signedVAA);
|
||||
}
|
||||
}, [
|
||||
dispatch,
|
||||
terraWallet,
|
||||
targetChain,
|
||||
signer,
|
||||
signedVAA,
|
||||
solanaWallet,
|
||||
solPK,
|
||||
isSourceAssetWormholeWrapped,
|
||||
originChain,
|
||||
targetAsset,
|
||||
]);
|
||||
const { handleClick, disabled, showLoader } = useHandleRedeem();
|
||||
return (
|
||||
<div style={{ position: "relative" }}>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
className={classes.transferButton}
|
||||
disabled={isRedeeming}
|
||||
onClick={handleRedeemClick}
|
||||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
>
|
||||
Redeem
|
||||
</Button>
|
||||
{isRedeeming ? (
|
||||
{showLoader ? (
|
||||
<CircularProgress
|
||||
size={24}
|
||||
color="inherit"
|
||||
|
|
|
@ -1,42 +1,5 @@
|
|||
import {
|
||||
CHAIN_ID_TERRA,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { Button, CircularProgress, makeStyles } from "@material-ui/core";
|
||||
import {
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
Token,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { zeroPad } from "ethers/lib/utils";
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useEthereumProvider } from "../../contexts/EthereumProviderContext";
|
||||
import { useConnectedWallet } from "@terra-money/wallet-provider";
|
||||
import { useSolanaWallet } from "../../contexts/SolanaWalletContext";
|
||||
import {
|
||||
selectTransferAmount,
|
||||
selectTransferIsSendComplete,
|
||||
selectTransferIsSending,
|
||||
selectTransferIsTargetComplete,
|
||||
selectTransferOriginAsset,
|
||||
selectTransferOriginChain,
|
||||
selectTransferSourceAsset,
|
||||
selectTransferSourceChain,
|
||||
selectTransferSourceParsedTokenAccount,
|
||||
selectTransferTargetAsset,
|
||||
selectTransferTargetChain,
|
||||
selectTransferTargetParsedTokenAccount,
|
||||
} from "../../store/selectors";
|
||||
import { setIsSending, setSignedVAAHex } from "../../store/transferSlice";
|
||||
import { uint8ArrayToHex } from "../../utils/array";
|
||||
import {
|
||||
transferFromEth,
|
||||
transferFromSolana,
|
||||
transferFromTerra,
|
||||
} from "../../utils/transferFrom";
|
||||
import { useHandleTransfer } from "../../hooks/useHandleTransfer";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
transferButton: {
|
||||
|
@ -46,150 +9,9 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
// TODO: move attest to its own workflow
|
||||
|
||||
function Send() {
|
||||
const classes = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
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 targetAsset = useSelector(selectTransferTargetAsset);
|
||||
const isTargetComplete = useSelector(selectTransferIsTargetComplete);
|
||||
const isSending = useSelector(selectTransferIsSending);
|
||||
const isSendComplete = useSelector(selectTransferIsSendComplete);
|
||||
const { signer, signerAddress } = useEthereumProvider();
|
||||
const terraWallet = useConnectedWallet();
|
||||
const solanaWallet = useSolanaWallet();
|
||||
const solPK = solanaWallet?.publicKey;
|
||||
const sourceParsedTokenAccount = useSelector(
|
||||
selectTransferSourceParsedTokenAccount
|
||||
);
|
||||
const sourceTokenPublicKey = sourceParsedTokenAccount?.publicKey;
|
||||
const decimals = sourceParsedTokenAccount?.decimals;
|
||||
const targetParsedTokenAccount = useSelector(
|
||||
selectTransferTargetParsedTokenAccount
|
||||
);
|
||||
// TODO: we probably shouldn't get here if we don't have this public key
|
||||
// TODO: also this is just for solana... send help(ers)
|
||||
const targetTokenAccountPublicKey = targetParsedTokenAccount?.publicKey;
|
||||
console.log(
|
||||
"Sending to:",
|
||||
targetTokenAccountPublicKey,
|
||||
targetTokenAccountPublicKey &&
|
||||
new PublicKey(targetTokenAccountPublicKey).toBytes()
|
||||
);
|
||||
// TODO: AVOID THIS DANGEROUS CACOPHONY
|
||||
const tpkRef = useRef<undefined | Uint8Array>(undefined);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (targetChain === CHAIN_ID_SOLANA) {
|
||||
tpkRef.current = targetTokenAccountPublicKey
|
||||
? zeroPad(new PublicKey(targetTokenAccountPublicKey).toBytes(), 32) // use the target's TokenAccount if it exists
|
||||
: solPK && targetAsset // otherwise, use the associated token account (which we create in the case it doesn't exist)
|
||||
? zeroPad(
|
||||
(
|
||||
await Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
new PublicKey(targetAsset),
|
||||
solPK
|
||||
)
|
||||
).toBytes(),
|
||||
32
|
||||
)
|
||||
: undefined;
|
||||
} else tpkRef.current = undefined;
|
||||
})();
|
||||
}, [targetChain, solPK, targetAsset, targetTokenAccountPublicKey]);
|
||||
// TODO: dynamically get "to" wallet
|
||||
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 && decimals) {
|
||||
//TODO: just for testing, this should eventually use the store to communicate between steps
|
||||
(async () => {
|
||||
dispatch(setIsSending(true));
|
||||
try {
|
||||
console.log("actually sending", tpkRef.current);
|
||||
const vaaBytes = await transferFromEth(
|
||||
signer,
|
||||
sourceAsset,
|
||||
decimals,
|
||||
amount,
|
||||
targetChain,
|
||||
tpkRef.current
|
||||
);
|
||||
console.log("bytes in transfer", vaaBytes);
|
||||
vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes)));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
dispatch(setIsSending(false));
|
||||
}
|
||||
})();
|
||||
}
|
||||
if (sourceChain === CHAIN_ID_SOLANA && decimals) {
|
||||
//TODO: just for testing, this should eventually use the store to communicate between steps
|
||||
(async () => {
|
||||
dispatch(setIsSending(true));
|
||||
try {
|
||||
const vaaBytes = await transferFromSolana(
|
||||
solanaWallet,
|
||||
solPK?.toString(),
|
||||
sourceTokenPublicKey,
|
||||
sourceAsset,
|
||||
amount, //TODO: avoid decimals, pass in parsed amount
|
||||
decimals,
|
||||
signerAddress,
|
||||
targetChain,
|
||||
originAsset,
|
||||
originChain
|
||||
);
|
||||
console.log("bytes in transfer", vaaBytes);
|
||||
vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes)));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
dispatch(setIsSending(false));
|
||||
}
|
||||
})();
|
||||
}
|
||||
if (sourceChain === CHAIN_ID_TERRA && decimals) {
|
||||
(async () => {
|
||||
dispatch(setIsSending(true));
|
||||
try {
|
||||
const vaaBytes = await transferFromTerra(
|
||||
terraWallet,
|
||||
sourceAsset,
|
||||
amount,
|
||||
"",
|
||||
targetChain
|
||||
);
|
||||
console.log("bytes in transfer", vaaBytes);
|
||||
vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes)));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
dispatch(setIsSending(false));
|
||||
}
|
||||
})();
|
||||
}
|
||||
}, [
|
||||
dispatch,
|
||||
sourceChain,
|
||||
signer,
|
||||
signerAddress,
|
||||
solanaWallet,
|
||||
solPK,
|
||||
sourceTokenPublicKey,
|
||||
sourceAsset,
|
||||
amount,
|
||||
decimals,
|
||||
targetChain,
|
||||
originAsset,
|
||||
originChain,
|
||||
]);
|
||||
const { handleClick, disabled, showLoader } = useHandleTransfer();
|
||||
return (
|
||||
<>
|
||||
<div style={{ position: "relative" }}>
|
||||
|
@ -197,12 +19,12 @@ function Send() {
|
|||
color="primary"
|
||||
variant="contained"
|
||||
className={classes.transferButton}
|
||||
onClick={handleTransferClick}
|
||||
disabled={!isTargetComplete || isSending || isSendComplete}
|
||||
onClick={handleClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
Transfer
|
||||
</Button>
|
||||
{isSending ? (
|
||||
{showLoader ? (
|
||||
<CircularProgress
|
||||
size={24}
|
||||
color="inherit"
|
||||
|
|
|
@ -31,7 +31,7 @@ function Source() {
|
|||
const sourceAsset = useSelector(selectTransferSourceAsset);
|
||||
const uiAmountString = useSelector(selectTransferSourceBalanceString);
|
||||
const amount = useSelector(selectTransferAmount);
|
||||
const isSourceComplete = true; // useSelector(selectTransferIsSourceComplete);
|
||||
const isSourceComplete = useSelector(selectTransferIsSourceComplete);
|
||||
const shouldLockFields = useSelector(selectTransferShouldLockFields);
|
||||
const handleSourceChange = useCallback(
|
||||
(event) => {
|
||||
|
|
|
@ -26,14 +26,9 @@ function useFetchTargetAsset() {
|
|||
);
|
||||
const originChain = useSelector(selectTransferOriginChain);
|
||||
const originAsset = useSelector(selectTransferOriginAsset);
|
||||
console.log(
|
||||
"WH Wrapped?",
|
||||
isSourceAssetWormholeWrapped,
|
||||
originChain,
|
||||
originAsset
|
||||
);
|
||||
const targetChain = useSelector(selectTransferTargetChain);
|
||||
const { provider } = useEthereumProvider();
|
||||
// TODO: this may not cover wrapped to wrapped, should always use origin?
|
||||
useEffect(() => {
|
||||
if (isSourceAssetWormholeWrapped && originChain === targetChain) {
|
||||
dispatch(setTargetAsset(originAsset));
|
||||
|
@ -57,7 +52,6 @@ function useFetchTargetAsset() {
|
|||
try {
|
||||
const asset = await getForeignAssetSol(sourceChain, sourceAsset);
|
||||
if (!cancelled) {
|
||||
console.log("solana target asset", asset);
|
||||
dispatch(setTargetAsset(asset));
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
@ -96,7 +96,6 @@ function useGetBalanceEffect(sourceOrTarget: "source" | "target") {
|
|||
.getParsedTokenAccountsByOwner(solPK, { mint })
|
||||
.then(({ value }) => {
|
||||
if (!cancelled) {
|
||||
console.log("parsed token accounts", value);
|
||||
if (value.length) {
|
||||
// TODO: allow selection between these target accounts
|
||||
dispatch(
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
import {
|
||||
attestFromEth,
|
||||
attestFromSolana,
|
||||
attestFromTerra,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
CHAIN_ID_TERRA,
|
||||
getEmitterAddressEth,
|
||||
getEmitterAddressSolana,
|
||||
getEmitterAddressTerra,
|
||||
parseSequenceFromLogEth,
|
||||
parseSequenceFromLogSolana,
|
||||
parseSequenceFromLogTerra,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { WalletContextState } from "@solana/wallet-adapter-react";
|
||||
import { Connection, PublicKey } from "@solana/web3.js";
|
||||
import {
|
||||
ConnectedWallet,
|
||||
useConnectedWallet,
|
||||
} from "@terra-money/wallet-provider";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Signer } from "../../../sdk/js/node_modules/ethers/lib";
|
||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
|
||||
import { setIsSending, setSignedVAAHex } from "../store/attestSlice";
|
||||
import {
|
||||
selectAttestIsSendComplete,
|
||||
selectAttestIsSending,
|
||||
selectAttestIsTargetComplete,
|
||||
selectAttestSourceAsset,
|
||||
selectAttestSourceChain,
|
||||
} from "../store/selectors";
|
||||
import { 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";
|
||||
|
||||
async function eth(
|
||||
dispatch: any,
|
||||
enqueueSnackbar: any,
|
||||
signer: Signer,
|
||||
sourceAsset: string
|
||||
) {
|
||||
dispatch(setIsSending(true));
|
||||
try {
|
||||
const receipt = await attestFromEth(
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
signer,
|
||||
sourceAsset
|
||||
);
|
||||
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
|
||||
);
|
||||
dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes)));
|
||||
enqueueSnackbar("Fetched Signed VAA", { variant: "success" });
|
||||
} catch (e) {
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
dispatch(setIsSending(false));
|
||||
}
|
||||
}
|
||||
|
||||
async function solana(
|
||||
dispatch: any,
|
||||
enqueueSnackbar: any,
|
||||
solPK: PublicKey,
|
||||
sourceAsset: string,
|
||||
wallet: WalletContextState
|
||||
) {
|
||||
dispatch(setIsSending(true));
|
||||
try {
|
||||
// TODO: share connection in context?
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const transaction = await attestFromSolana(
|
||||
connection,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
solPK.toString(),
|
||||
sourceAsset
|
||||
);
|
||||
const txid = await signSendAndConfirm(wallet, connection, transaction);
|
||||
enqueueSnackbar("Transaction confirmed", { variant: "success" });
|
||||
const info = await connection.getTransaction(txid);
|
||||
if (!info) {
|
||||
// TODO: error state
|
||||
throw new Error("An error occurred while fetching the transaction info");
|
||||
}
|
||||
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) {
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
dispatch(setIsSending(false));
|
||||
}
|
||||
}
|
||||
|
||||
async function terra(
|
||||
dispatch: any,
|
||||
enqueueSnackbar: any,
|
||||
wallet: ConnectedWallet,
|
||||
asset: string
|
||||
) {
|
||||
dispatch(setIsSending(true));
|
||||
try {
|
||||
const infoMaybe = await attestFromTerra(
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
wallet,
|
||||
asset
|
||||
);
|
||||
const info = await waitForTerraExecution(wallet, infoMaybe);
|
||||
enqueueSnackbar("Transaction confirmed", { variant: "success" });
|
||||
const sequence = parseSequenceFromLogTerra(info);
|
||||
const emitterAddress = await getEmitterAddressTerra(
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS
|
||||
);
|
||||
enqueueSnackbar("Fetching VAA", { variant: "info" });
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_TERRA,
|
||||
emitterAddress,
|
||||
sequence
|
||||
);
|
||||
dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes)));
|
||||
enqueueSnackbar("Fetched Signed VAA", { variant: "success" });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
dispatch(setIsSending(false));
|
||||
}
|
||||
}
|
||||
|
||||
export function useHandleAttest() {
|
||||
const dispatch = useDispatch();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const sourceChain = useSelector(selectAttestSourceChain);
|
||||
const sourceAsset = useSelector(selectAttestSourceAsset);
|
||||
const isTargetComplete = useSelector(selectAttestIsTargetComplete);
|
||||
const isSending = useSelector(selectAttestIsSending);
|
||||
const isSendComplete = useSelector(selectAttestIsSendComplete);
|
||||
const { signer } = useEthereumProvider();
|
||||
const solanaWallet = useSolanaWallet();
|
||||
const solPK = solanaWallet?.publicKey;
|
||||
const terraWallet = useConnectedWallet();
|
||||
const disabled = !isTargetComplete || isSending || isSendComplete;
|
||||
const handleAttestClick = useCallback(() => {
|
||||
if (sourceChain === CHAIN_ID_ETH && !!signer) {
|
||||
eth(dispatch, enqueueSnackbar, signer, sourceAsset);
|
||||
} else if (sourceChain === CHAIN_ID_SOLANA && !!solanaWallet && !!solPK) {
|
||||
solana(dispatch, enqueueSnackbar, solPK, sourceAsset, solanaWallet);
|
||||
} else if (sourceChain === CHAIN_ID_TERRA && !!terraWallet) {
|
||||
terra(dispatch, enqueueSnackbar, terraWallet, sourceAsset);
|
||||
} else {
|
||||
// enqueueSnackbar("Attesting from this chain is not yet supported", {
|
||||
// variant: "error",
|
||||
// });
|
||||
}
|
||||
}, [
|
||||
dispatch,
|
||||
enqueueSnackbar,
|
||||
sourceChain,
|
||||
signer,
|
||||
solanaWallet,
|
||||
solPK,
|
||||
terraWallet,
|
||||
sourceAsset,
|
||||
]);
|
||||
return useMemo(
|
||||
() => ({
|
||||
handleClick: handleAttestClick,
|
||||
disabled,
|
||||
showLoader: isSending,
|
||||
}),
|
||||
[handleAttestClick, disabled, isSending]
|
||||
);
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
import {
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
CHAIN_ID_TERRA,
|
||||
createWrappedOnEth,
|
||||
createWrappedOnSolana,
|
||||
createWrappedOnTerra,
|
||||
postVaaSolana,
|
||||
} 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 { 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 useAttestSignedVAA from "../hooks/useAttestSignedVAA";
|
||||
import { reset, setIsCreating } from "../store/attestSlice";
|
||||
import {
|
||||
selectAttestIsCreating,
|
||||
selectAttestTargetChain,
|
||||
} from "../store/selectors";
|
||||
import {
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
SOLANA_HOST,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
} from "../utils/consts";
|
||||
import parseError from "../utils/parseError";
|
||||
import { signSendAndConfirm } from "../utils/solana";
|
||||
|
||||
async function eth(
|
||||
dispatch: any,
|
||||
enqueueSnackbar: any,
|
||||
signer: Signer,
|
||||
signedVAA: Uint8Array
|
||||
) {
|
||||
dispatch(setIsCreating(true));
|
||||
try {
|
||||
await createWrappedOnEth(ETH_TOKEN_BRIDGE_ADDRESS, signer, signedVAA);
|
||||
dispatch(reset());
|
||||
enqueueSnackbar("Transaction confirmed", { variant: "success" });
|
||||
} catch (e) {
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
dispatch(setIsCreating(false));
|
||||
}
|
||||
}
|
||||
|
||||
async function solana(
|
||||
dispatch: any,
|
||||
enqueueSnackbar: any,
|
||||
wallet: WalletContextState,
|
||||
payerAddress: string, // TODO: we may not need this since we have wallet
|
||||
signedVAA: Uint8Array
|
||||
) {
|
||||
dispatch(setIsCreating(true));
|
||||
try {
|
||||
// TODO: share connection in context?
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
await postVaaSolana(
|
||||
connection,
|
||||
wallet.signTransaction,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
Buffer.from(signedVAA)
|
||||
);
|
||||
const transaction = await createWrappedOnSolana(
|
||||
connection,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
signedVAA
|
||||
);
|
||||
await signSendAndConfirm(wallet, connection, transaction);
|
||||
dispatch(reset());
|
||||
enqueueSnackbar("Transaction confirmed", { variant: "success" });
|
||||
} catch (e) {
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
dispatch(setIsCreating(false));
|
||||
}
|
||||
}
|
||||
|
||||
async function terra(
|
||||
dispatch: any,
|
||||
enqueueSnackbar: any,
|
||||
wallet: ConnectedWallet,
|
||||
signedVAA: Uint8Array
|
||||
) {
|
||||
dispatch(setIsCreating(true));
|
||||
try {
|
||||
await createWrappedOnTerra(TERRA_TOKEN_BRIDGE_ADDRESS, wallet, signedVAA);
|
||||
dispatch(reset());
|
||||
enqueueSnackbar("Transaction confirmed", { variant: "success" });
|
||||
} catch (e) {
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
dispatch(setIsCreating(false));
|
||||
}
|
||||
}
|
||||
|
||||
export function useHandleCreateWrapped() {
|
||||
const dispatch = useDispatch();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const targetChain = useSelector(selectAttestTargetChain);
|
||||
const solanaWallet = useSolanaWallet();
|
||||
const solPK = solanaWallet?.publicKey;
|
||||
const signedVAA = useAttestSignedVAA();
|
||||
const isCreating = useSelector(selectAttestIsCreating);
|
||||
const { signer } = useEthereumProvider();
|
||||
const terraWallet = useConnectedWallet();
|
||||
const handleCreateClick = useCallback(() => {
|
||||
if (targetChain === CHAIN_ID_ETH && !!signer && !!signedVAA) {
|
||||
eth(dispatch, enqueueSnackbar, signer, signedVAA);
|
||||
} else if (
|
||||
targetChain === CHAIN_ID_SOLANA &&
|
||||
!!solanaWallet &&
|
||||
!!solPK &&
|
||||
!!signedVAA
|
||||
) {
|
||||
solana(
|
||||
dispatch,
|
||||
enqueueSnackbar,
|
||||
solanaWallet,
|
||||
solPK.toString(),
|
||||
signedVAA
|
||||
);
|
||||
} else if (targetChain === CHAIN_ID_TERRA && !!terraWallet && !!signedVAA) {
|
||||
terra(dispatch, enqueueSnackbar, terraWallet, signedVAA);
|
||||
} else {
|
||||
// enqueueSnackbar(
|
||||
// "Creating wrapped tokens on this chain is not yet supported",
|
||||
// {
|
||||
// variant: "error",
|
||||
// }
|
||||
// );
|
||||
}
|
||||
}, [
|
||||
dispatch,
|
||||
enqueueSnackbar,
|
||||
targetChain,
|
||||
solanaWallet,
|
||||
solPK,
|
||||
terraWallet,
|
||||
signedVAA,
|
||||
signer,
|
||||
]);
|
||||
return useMemo(
|
||||
() => ({
|
||||
handleClick: handleCreateClick,
|
||||
disabled: !!isCreating,
|
||||
showLoader: !!isCreating,
|
||||
}),
|
||||
[handleCreateClick, isCreating]
|
||||
);
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
import {
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
CHAIN_ID_TERRA,
|
||||
postVaaSolana,
|
||||
redeemOnEth,
|
||||
redeemOnSolana,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { WalletContextState } from "@solana/wallet-adapter-react";
|
||||
import { Connection } from "@solana/web3.js";
|
||||
import { MsgExecuteContract } from "@terra-money/terra.js";
|
||||
import {
|
||||
ConnectedWallet,
|
||||
useConnectedWallet,
|
||||
} from "@terra-money/wallet-provider";
|
||||
import { Signer } from "ethers";
|
||||
import { fromUint8Array } from "js-base64";
|
||||
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 useTransferSignedVAA from "../hooks/useTransferSignedVAA";
|
||||
import {
|
||||
selectTransferIsRedeeming,
|
||||
selectTransferIsSourceAssetWormholeWrapped,
|
||||
selectTransferOriginChain,
|
||||
selectTransferTargetAsset,
|
||||
selectTransferTargetChain,
|
||||
} from "../store/selectors";
|
||||
import { reset, setIsRedeeming } from "../store/transferSlice";
|
||||
import {
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
SOLANA_HOST,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
} from "../utils/consts";
|
||||
import parseError from "../utils/parseError";
|
||||
import { signSendAndConfirm } from "../utils/solana";
|
||||
|
||||
async function eth(
|
||||
dispatch: any,
|
||||
enqueueSnackbar: any,
|
||||
signer: Signer,
|
||||
signedVAA: Uint8Array
|
||||
) {
|
||||
dispatch(setIsRedeeming(true));
|
||||
try {
|
||||
await redeemOnEth(ETH_TOKEN_BRIDGE_ADDRESS, signer, signedVAA);
|
||||
dispatch(reset());
|
||||
enqueueSnackbar("Transaction confirmed", { variant: "success" });
|
||||
} catch (e) {
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
dispatch(setIsRedeeming(false));
|
||||
}
|
||||
}
|
||||
|
||||
async function solana(
|
||||
dispatch: any,
|
||||
enqueueSnackbar: any,
|
||||
wallet: WalletContextState,
|
||||
payerAddress: string, //TODO: we may not need this since we have wallet
|
||||
signedVAA: Uint8Array,
|
||||
isSolanaNative: boolean,
|
||||
mintAddress?: string // TODO: read the signedVAA and create the account if it doesn't exist
|
||||
) {
|
||||
dispatch(setIsRedeeming(true));
|
||||
try {
|
||||
// TODO: share connection in context?
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
await postVaaSolana(
|
||||
connection,
|
||||
wallet.signTransaction,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
Buffer.from(signedVAA)
|
||||
);
|
||||
// TODO: how do we retry in between these steps
|
||||
const transaction = await redeemOnSolana(
|
||||
connection,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
signedVAA,
|
||||
isSolanaNative,
|
||||
mintAddress
|
||||
);
|
||||
await signSendAndConfirm(wallet, connection, transaction);
|
||||
dispatch(reset());
|
||||
enqueueSnackbar("Transaction confirmed", { variant: "success" });
|
||||
} catch (e) {
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
dispatch(setIsRedeeming(false));
|
||||
}
|
||||
}
|
||||
|
||||
async function terra(
|
||||
dispatch: any,
|
||||
enqueueSnackbar: any,
|
||||
wallet: ConnectedWallet,
|
||||
signedVAA: Uint8Array
|
||||
) {
|
||||
dispatch(setIsRedeeming(true));
|
||||
try {
|
||||
await wallet.post({
|
||||
msgs: [
|
||||
new MsgExecuteContract(
|
||||
wallet.terraAddress,
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
{
|
||||
submit_vaa: {
|
||||
data: fromUint8Array(signedVAA),
|
||||
},
|
||||
},
|
||||
{ uluna: 1000 }
|
||||
),
|
||||
],
|
||||
memo: "Complete Transfer",
|
||||
});
|
||||
dispatch(reset());
|
||||
enqueueSnackbar("Transaction confirmed", { variant: "success" });
|
||||
} catch (e) {
|
||||
enqueueSnackbar(parseError(e), { variant: "error" });
|
||||
dispatch(setIsRedeeming(false));
|
||||
}
|
||||
}
|
||||
|
||||
export function useHandleRedeem() {
|
||||
const dispatch = useDispatch();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const isSourceAssetWormholeWrapped = useSelector(
|
||||
selectTransferIsSourceAssetWormholeWrapped
|
||||
);
|
||||
const originChain = useSelector(selectTransferOriginChain);
|
||||
const targetChain = useSelector(selectTransferTargetChain);
|
||||
const targetAsset = useSelector(selectTransferTargetAsset);
|
||||
const solanaWallet = useSolanaWallet();
|
||||
const solPK = solanaWallet?.publicKey;
|
||||
const { signer } = useEthereumProvider();
|
||||
const terraWallet = useConnectedWallet();
|
||||
const signedVAA = useTransferSignedVAA();
|
||||
const isRedeeming = useSelector(selectTransferIsRedeeming);
|
||||
const handleRedeemClick = useCallback(() => {
|
||||
if (targetChain === CHAIN_ID_ETH && !!signer && signedVAA) {
|
||||
eth(dispatch, enqueueSnackbar, signer, signedVAA);
|
||||
} else if (
|
||||
targetChain === CHAIN_ID_SOLANA &&
|
||||
!!solanaWallet &&
|
||||
!!solPK &&
|
||||
signedVAA
|
||||
) {
|
||||
solana(
|
||||
dispatch,
|
||||
enqueueSnackbar,
|
||||
solanaWallet,
|
||||
solPK.toString(),
|
||||
signedVAA,
|
||||
!!isSourceAssetWormholeWrapped && originChain === CHAIN_ID_SOLANA,
|
||||
targetAsset || undefined
|
||||
);
|
||||
} else if (targetChain === CHAIN_ID_TERRA && !!terraWallet && signedVAA) {
|
||||
terra(dispatch, enqueueSnackbar, terraWallet, signedVAA);
|
||||
} else {
|
||||
// enqueueSnackbar("Redeeming on this chain is not yet supported", {
|
||||
// variant: "error",
|
||||
// });
|
||||
}
|
||||
}, [
|
||||
dispatch,
|
||||
enqueueSnackbar,
|
||||
targetChain,
|
||||
signer,
|
||||
signedVAA,
|
||||
solanaWallet,
|
||||
solPK,
|
||||
terraWallet,
|
||||
isSourceAssetWormholeWrapped,
|
||||
originChain,
|
||||
targetAsset,
|
||||
]);
|
||||
return useMemo(
|
||||
() => ({
|
||||
handleClick: handleRedeemClick,
|
||||
disabled: !!isRedeeming,
|
||||
showLoader: !!isRedeeming,
|
||||
}),
|
||||
[handleRedeemClick, isRedeeming]
|
||||
);
|
||||
}
|
|
@ -0,0 +1,354 @@
|
|||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
CHAIN_ID_TERRA,
|
||||
getEmitterAddressEth,
|
||||
getEmitterAddressSolana,
|
||||
parseSequenceFromLogEth,
|
||||
parseSequenceFromLogSolana,
|
||||
parseSequenceFromLogTerra,
|
||||
transferFromEth,
|
||||
transferFromSolana,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
Token,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
import { WalletContextState } from "@solana/wallet-adapter-react";
|
||||
import { Connection, PublicKey } from "@solana/web3.js";
|
||||
import { MsgExecuteContract } from "@terra-money/terra.js";
|
||||
import {
|
||||
ConnectedWallet,
|
||||
useConnectedWallet,
|
||||
} from "@terra-money/wallet-provider";
|
||||
import { Signer } from "ethers";
|
||||
import { arrayify, parseUnits, zeroPad } from "ethers/lib/utils";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { useCallback, useEffect, useMemo, useRef } 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,
|
||||
selectTransferTargetAsset,
|
||||
selectTransferTargetChain,
|
||||
selectTransferTargetParsedTokenAccount,
|
||||
} from "../store/selectors";
|
||||
import { setIsSending, setSignedVAAHex } 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 { signSendAndConfirm } from "../utils/solana";
|
||||
|
||||
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
|
||||
);
|
||||
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);
|
||||
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,
|
||||
targetAddressStr: string,
|
||||
targetChain: ChainId,
|
||||
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 targetAddress = zeroPad(arrayify(targetAddressStr), 32);
|
||||
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");
|
||||
}
|
||||
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);
|
||||
dispatch(setIsSending(false));
|
||||
}
|
||||
}
|
||||
|
||||
async function terra(
|
||||
dispatch: any,
|
||||
enqueueSnackbar: any,
|
||||
wallet: ConnectedWallet,
|
||||
asset: string,
|
||||
amount: string,
|
||||
targetAddressStr: string,
|
||||
targetChain: ChainId
|
||||
) {
|
||||
dispatch(setIsSending(true));
|
||||
try {
|
||||
// TODO: SDK
|
||||
const result = await wallet.post({
|
||||
msgs: [
|
||||
new MsgExecuteContract(
|
||||
wallet.terraAddress,
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
{
|
||||
initiate_transfer: {
|
||||
asset: asset,
|
||||
amount: amount,
|
||||
recipient_chain: targetChain,
|
||||
recipient: targetAddressStr,
|
||||
fee: 1000,
|
||||
nonce: 0,
|
||||
},
|
||||
},
|
||||
{ uluna: 1000 }
|
||||
),
|
||||
],
|
||||
memo: "Complete Transfer",
|
||||
});
|
||||
enqueueSnackbar("Transaction confirmed", { variant: "success" });
|
||||
console.log(result);
|
||||
const sequence = parseSequenceFromLogTerra(result);
|
||||
console.log(sequence);
|
||||
const emitterAddress = await getEmitterAddressSolana(
|
||||
SOL_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);
|
||||
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 targetAsset = useSelector(selectTransferTargetAsset);
|
||||
const isTargetComplete = useSelector(selectTransferIsTargetComplete);
|
||||
const isSending = useSelector(selectTransferIsSending);
|
||||
const isSendComplete = useSelector(selectTransferIsSendComplete);
|
||||
const { signer, signerAddress } = useEthereumProvider();
|
||||
const solanaWallet = useSolanaWallet();
|
||||
const solPK = solanaWallet?.publicKey;
|
||||
const terraWallet = useConnectedWallet();
|
||||
const sourceParsedTokenAccount = useSelector(
|
||||
selectTransferSourceParsedTokenAccount
|
||||
);
|
||||
const sourceTokenPublicKey = sourceParsedTokenAccount?.publicKey;
|
||||
const decimals = sourceParsedTokenAccount?.decimals;
|
||||
const targetParsedTokenAccount = useSelector(
|
||||
selectTransferTargetParsedTokenAccount
|
||||
);
|
||||
const disabled = !isTargetComplete || isSending || isSendComplete;
|
||||
// TODO: we probably shouldn't get here if we don't have this public key
|
||||
// TODO: also this is just for solana... send help(ers)
|
||||
const targetTokenAccountPublicKey = targetParsedTokenAccount?.publicKey;
|
||||
// TODO: AVOID THIS DANGEROUS CACOPHONY
|
||||
const tpkRef = useRef<undefined | Uint8Array>(undefined);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (targetChain === CHAIN_ID_SOLANA) {
|
||||
tpkRef.current = targetTokenAccountPublicKey
|
||||
? zeroPad(new PublicKey(targetTokenAccountPublicKey).toBytes(), 32) // use the target's TokenAccount if it exists
|
||||
: solPK && targetAsset // otherwise, use the associated token account (which we create in the case it doesn't exist)
|
||||
? zeroPad(
|
||||
(
|
||||
await Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
new PublicKey(targetAsset),
|
||||
solPK
|
||||
)
|
||||
).toBytes(),
|
||||
32
|
||||
)
|
||||
: undefined;
|
||||
} else tpkRef.current = undefined;
|
||||
})();
|
||||
}, [targetChain, solPK, targetAsset, targetTokenAccountPublicKey]);
|
||||
// TODO: dynamically get "to" wallet
|
||||
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 &&
|
||||
decimals !== undefined &&
|
||||
!!tpkRef.current
|
||||
) {
|
||||
eth(
|
||||
dispatch,
|
||||
enqueueSnackbar,
|
||||
signer,
|
||||
sourceAsset,
|
||||
decimals,
|
||||
amount,
|
||||
targetChain,
|
||||
tpkRef.current
|
||||
);
|
||||
} else if (
|
||||
sourceChain === CHAIN_ID_SOLANA &&
|
||||
!!solanaWallet &&
|
||||
!!solPK &&
|
||||
!!sourceTokenPublicKey &&
|
||||
!!signerAddress &&
|
||||
decimals !== undefined
|
||||
) {
|
||||
solana(
|
||||
dispatch,
|
||||
enqueueSnackbar,
|
||||
solanaWallet,
|
||||
solPK.toString(),
|
||||
sourceTokenPublicKey,
|
||||
sourceAsset,
|
||||
amount, //TODO: avoid decimals, pass in parsed amount
|
||||
decimals,
|
||||
signerAddress,
|
||||
targetChain,
|
||||
originAsset,
|
||||
originChain
|
||||
);
|
||||
} else if (
|
||||
sourceChain === CHAIN_ID_TERRA &&
|
||||
!!terraWallet &&
|
||||
decimals !== undefined &&
|
||||
!!signerAddress
|
||||
) {
|
||||
terra(
|
||||
dispatch,
|
||||
enqueueSnackbar,
|
||||
terraWallet,
|
||||
sourceAsset,
|
||||
amount,
|
||||
signerAddress, // TODO: only works for Eth
|
||||
targetChain
|
||||
);
|
||||
} else {
|
||||
// enqueueSnackbar("Transfers from this chain are not yet supported", {
|
||||
// variant: "error",
|
||||
// });
|
||||
}
|
||||
}, [
|
||||
dispatch,
|
||||
enqueueSnackbar,
|
||||
sourceChain,
|
||||
signer,
|
||||
signerAddress,
|
||||
solanaWallet,
|
||||
solPK,
|
||||
terraWallet,
|
||||
sourceTokenPublicKey,
|
||||
sourceAsset,
|
||||
amount,
|
||||
decimals,
|
||||
targetChain,
|
||||
originAsset,
|
||||
originChain,
|
||||
]);
|
||||
return useMemo(
|
||||
() => ({
|
||||
handleClick: handleTransferClick,
|
||||
disabled,
|
||||
showLoader: isSending,
|
||||
}),
|
||||
[handleTransferClick, disabled, isSending]
|
||||
);
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import { CssBaseline } from "@material-ui/core";
|
||||
import { ThemeProvider } from "@material-ui/core/styles";
|
||||
import { SnackbarProvider } from "notistack";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Provider } from "react-redux";
|
||||
import { HashRouter } from "react-router-dom";
|
||||
|
@ -16,15 +17,17 @@ ReactDOM.render(
|
|||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<RadialGradient />
|
||||
<SolanaWalletProvider>
|
||||
<EthereumProviderProvider>
|
||||
<TerraWalletProvider>
|
||||
<HashRouter>
|
||||
<App />
|
||||
</HashRouter>
|
||||
</TerraWalletProvider>
|
||||
</EthereumProviderProvider>
|
||||
</SolanaWalletProvider>
|
||||
<SnackbarProvider maxSnack={3}>
|
||||
<SolanaWalletProvider>
|
||||
<EthereumProviderProvider>
|
||||
<TerraWalletProvider>
|
||||
<HashRouter>
|
||||
<App />
|
||||
</HashRouter>
|
||||
</TerraWalletProvider>
|
||||
</EthereumProviderProvider>
|
||||
</SolanaWalletProvider>
|
||||
</SnackbarProvider>
|
||||
</ThemeProvider>
|
||||
</Provider>,
|
||||
document.getElementById("root")
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
import {
|
||||
attestFromEth as attestEthTx,
|
||||
attestFromSolana as attestSolanaTx,
|
||||
attestFromTerra as attestTerraTx,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
CHAIN_ID_TERRA,
|
||||
getEmitterAddressEth,
|
||||
getEmitterAddressSolana,
|
||||
getEmitterAddressTerra,
|
||||
parseSequenceFromLogEth,
|
||||
parseSequenceFromLogSolana,
|
||||
parseSequenceFromLogTerra,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { WalletContextState } from "@solana/wallet-adapter-react";
|
||||
import { Connection } from "@solana/web3.js";
|
||||
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
|
||||
import { ethers } from "ethers";
|
||||
import {
|
||||
ETH_BRIDGE_ADDRESS,
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
SOLANA_HOST,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
} from "./consts";
|
||||
import { getSignedVAAWithRetry } from "./getSignedVAAWithRetry";
|
||||
import { signSendConfirmAndGet } from "./solana";
|
||||
import { waitForTerraExecution } from "./terra";
|
||||
|
||||
export async function attestFromEth(
|
||||
signer: ethers.Signer | undefined,
|
||||
tokenAddress: string
|
||||
) {
|
||||
if (!signer) return;
|
||||
const receipt = await attestEthTx(
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
signer,
|
||||
tokenAddress
|
||||
);
|
||||
const sequence = parseSequenceFromLogEth(receipt, ETH_BRIDGE_ADDRESS);
|
||||
const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_ETH,
|
||||
emitterAddress,
|
||||
sequence
|
||||
);
|
||||
return vaaBytes;
|
||||
}
|
||||
|
||||
export async function attestFromSolana(
|
||||
wallet: WalletContextState,
|
||||
payerAddress: string | undefined, //TODO: we may not need this since we have wallet
|
||||
mintAddress: string
|
||||
) {
|
||||
if (!wallet || !wallet.publicKey || !payerAddress) return;
|
||||
// TODO: share connection in context?
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const transaction = await attestSolanaTx(
|
||||
connection,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
mintAddress
|
||||
);
|
||||
const info = await signSendConfirmAndGet(wallet, connection, transaction);
|
||||
if (!info) {
|
||||
throw new Error("An error occurred while fetching the transaction info");
|
||||
}
|
||||
const sequence = parseSequenceFromLogSolana(info);
|
||||
const emitterAddress = await getEmitterAddressSolana(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS
|
||||
);
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_SOLANA,
|
||||
emitterAddress,
|
||||
sequence
|
||||
);
|
||||
return vaaBytes;
|
||||
}
|
||||
|
||||
export async function attestFromTerra(
|
||||
wallet: TerraConnectedWallet | undefined,
|
||||
asset: string | undefined
|
||||
) {
|
||||
if (!wallet || !asset) return;
|
||||
const infoMaybe = await attestTerraTx(
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
wallet,
|
||||
asset
|
||||
);
|
||||
const info = await waitForTerraExecution(wallet, infoMaybe);
|
||||
const sequence = parseSequenceFromLogTerra(info);
|
||||
const emitterAddress = await getEmitterAddressTerra(
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS
|
||||
);
|
||||
const result = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_TERRA,
|
||||
emitterAddress,
|
||||
sequence
|
||||
);
|
||||
return result && result.vaaBytes;
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
import {
|
||||
postVaaSolana,
|
||||
createWrappedOnEth as createWrappedOnEthTx,
|
||||
createWrappedOnSolana as createWrappedOnSolanaTx,
|
||||
createWrappedOnTerra as createWrappedOnTerraTx,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { Connection } from "@solana/web3.js";
|
||||
import { ethers } from "ethers";
|
||||
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
|
||||
import {
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
SOLANA_HOST,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
TERRA_BRIDGE_ADDRESS,
|
||||
} from "./consts";
|
||||
import { signSendAndConfirm } from "./solana";
|
||||
import { WalletContextState } from "@solana/wallet-adapter-react";
|
||||
|
||||
export async function createWrappedOnEth(
|
||||
signer: ethers.Signer | undefined,
|
||||
signedVAA: Uint8Array
|
||||
) {
|
||||
if (!signer) return;
|
||||
await createWrappedOnEthTx(ETH_TOKEN_BRIDGE_ADDRESS, signer, signedVAA);
|
||||
}
|
||||
|
||||
export async function createWrappedOnTerra(
|
||||
wallet: TerraConnectedWallet | undefined,
|
||||
signedVAA: Uint8Array
|
||||
) {
|
||||
if (!wallet) return;
|
||||
await createWrappedOnTerraTx(
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
wallet,
|
||||
signedVAA
|
||||
);
|
||||
}
|
||||
|
||||
export async function createWrappedOnSolana(
|
||||
wallet: WalletContextState | undefined,
|
||||
payerAddress: string | undefined, //TODO: we may not need this since we have wallet
|
||||
signedVAA: Uint8Array
|
||||
) {
|
||||
if (!wallet || !wallet.publicKey || !payerAddress) return;
|
||||
// TODO: share connection in context?
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
await postVaaSolana(
|
||||
connection,
|
||||
wallet.signTransaction,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
Buffer.from(signedVAA)
|
||||
);
|
||||
|
||||
const transaction = await createWrappedOnSolanaTx(
|
||||
connection,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
signedVAA
|
||||
);
|
||||
await signSendAndConfirm(wallet, connection, transaction);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
const MM_ERR_WITH_INFO_START =
|
||||
"VM Exception while processing transaction: revert ";
|
||||
const parseError = (e: any) =>
|
||||
e?.data?.message?.startsWith(MM_ERR_WITH_INFO_START)
|
||||
? e.data.message.replace(MM_ERR_WITH_INFO_START, "")
|
||||
: e?.message
|
||||
? e.message
|
||||
: "An unknown error occurred";
|
||||
export default parseError;
|
|
@ -1,80 +0,0 @@
|
|||
import {
|
||||
postVaaSolana,
|
||||
redeemOnEth as redeemOnEthTx,
|
||||
redeemOnSolana as redeemOnSolanaTx,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { Connection } from "@solana/web3.js";
|
||||
import { ethers } from "ethers";
|
||||
import { fromUint8Array } from "js-base64";
|
||||
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
|
||||
import { MsgExecuteContract } from "@terra-money/terra.js";
|
||||
import {
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
SOLANA_HOST,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
} from "./consts";
|
||||
import { signSendAndConfirm } from "./solana";
|
||||
import { WalletContextState } from "@solana/wallet-adapter-react";
|
||||
|
||||
export async function redeemOnEth(
|
||||
signer: ethers.Signer | undefined,
|
||||
signedVAA: Uint8Array
|
||||
) {
|
||||
if (!signer) return;
|
||||
await redeemOnEthTx(ETH_TOKEN_BRIDGE_ADDRESS, signer, signedVAA);
|
||||
}
|
||||
|
||||
export async function redeemOnSolana(
|
||||
wallet: WalletContextState | undefined,
|
||||
payerAddress: string | undefined, //TODO: we may not need this since we have wallet
|
||||
signedVAA: Uint8Array,
|
||||
isSolanaNative: boolean,
|
||||
mintAddress?: string // TODO: read the signedVAA and create the account if it doesn't exist
|
||||
) {
|
||||
if (!wallet || !wallet.publicKey || !payerAddress) return;
|
||||
// TODO: share connection in context?
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
await postVaaSolana(
|
||||
connection,
|
||||
wallet.signTransaction,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
Buffer.from(signedVAA)
|
||||
);
|
||||
|
||||
const transaction = await redeemOnSolanaTx(
|
||||
connection,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
signedVAA,
|
||||
isSolanaNative,
|
||||
mintAddress
|
||||
);
|
||||
await signSendAndConfirm(wallet, connection, transaction);
|
||||
}
|
||||
|
||||
export async function redeemOnTerra(
|
||||
wallet: TerraConnectedWallet | undefined,
|
||||
signedVAA: Uint8Array
|
||||
) {
|
||||
if (!wallet) return;
|
||||
wallet &&
|
||||
(await wallet.post({
|
||||
msgs: [
|
||||
new MsgExecuteContract(
|
||||
wallet.terraAddress,
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
{
|
||||
submit_vaa: {
|
||||
data: fromUint8Array(signedVAA),
|
||||
},
|
||||
},
|
||||
{ uluna: 1000 }
|
||||
),
|
||||
],
|
||||
memo: "Complete Transfer",
|
||||
}));
|
||||
}
|
|
@ -11,12 +11,3 @@ export async function signSendAndConfirm(
|
|||
await connection.confirmTransaction(txid);
|
||||
return txid;
|
||||
}
|
||||
|
||||
export async function signSendConfirmAndGet(
|
||||
wallet: WalletContextState,
|
||||
connection: Connection,
|
||||
transaction: Transaction
|
||||
) {
|
||||
const txid = await signSendAndConfirm(wallet, connection, transaction);
|
||||
return await connection.getTransaction(txid);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { TxResult, ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
|
||||
import { TxInfo, LCDClient } from "@terra-money/terra.js";
|
||||
import {
|
||||
TxResult,
|
||||
ConnectedWallet as TerraConnectedWallet,
|
||||
} from "@terra-money/wallet-provider";
|
||||
import { LCDClient } from "@terra-money/terra.js";
|
||||
|
||||
// TODO: Loop txInfo for timed out transactions.
|
||||
// lcd.tx.txInfo(transaction.result.txhash);
|
||||
|
@ -7,9 +10,9 @@ export async function waitForTerraExecution(
|
|||
wallet: TerraConnectedWallet,
|
||||
transaction: TxResult
|
||||
) {
|
||||
const lcd = new LCDClient({
|
||||
new LCDClient({
|
||||
URL: wallet.network.lcd,
|
||||
chainID: "columbus-4",
|
||||
chainID: "columbus-4",
|
||||
});
|
||||
return transaction;
|
||||
}
|
||||
|
|
|
@ -1,167 +0,0 @@
|
|||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
CHAIN_ID_TERRA,
|
||||
getEmitterAddressEth,
|
||||
getEmitterAddressSolana,
|
||||
parseSequenceFromLogEth,
|
||||
parseSequenceFromLogSolana,
|
||||
parseSequenceFromLogTerra,
|
||||
transferFromEth as transferFromEthTx,
|
||||
transferFromSolana as transferFromSolanaTx,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { fromUint8Array } from "js-base64";
|
||||
import {
|
||||
ConnectedWallet as TerraConnectedWallet,
|
||||
TxResult,
|
||||
} from "@terra-money/wallet-provider";
|
||||
import { Connection } from "@solana/web3.js";
|
||||
import { MsgExecuteContract } from "@terra-money/terra.js";
|
||||
import { ethers } from "ethers";
|
||||
import { arrayify, parseUnits, zeroPad } from "ethers/lib/utils";
|
||||
import { hexToUint8Array } from "./array";
|
||||
import {
|
||||
ETH_BRIDGE_ADDRESS,
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
SOLANA_HOST,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
TERRA_TEST_TOKEN_ADDRESS,
|
||||
} from "./consts";
|
||||
import { getSignedVAAWithRetry } from "./getSignedVAAWithRetry";
|
||||
import { signSendConfirmAndGet } from "./solana";
|
||||
import { WalletContextState } from "@solana/wallet-adapter-react";
|
||||
|
||||
// TODO: overall better input checking and error handling
|
||||
export async function transferFromEth(
|
||||
signer: ethers.Signer | undefined,
|
||||
tokenAddress: string,
|
||||
decimals: number,
|
||||
amount: string,
|
||||
recipientChain: ChainId,
|
||||
recipientAddress: Uint8Array | undefined
|
||||
) {
|
||||
if (!signer || !recipientAddress) return;
|
||||
//TODO: check if token attestation exists on the target chain
|
||||
const amountParsed = parseUnits(amount, decimals);
|
||||
const receipt = await transferFromEthTx(
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
signer,
|
||||
tokenAddress,
|
||||
amountParsed,
|
||||
recipientChain,
|
||||
recipientAddress
|
||||
);
|
||||
const sequence = parseSequenceFromLogEth(receipt, ETH_BRIDGE_ADDRESS);
|
||||
const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_ETH,
|
||||
emitterAddress,
|
||||
sequence.toString()
|
||||
);
|
||||
return vaaBytes;
|
||||
}
|
||||
|
||||
export async function transferFromSolana(
|
||||
wallet: WalletContextState | undefined,
|
||||
payerAddress: string | undefined, //TODO: we may not need this since we have wallet
|
||||
fromAddress: string | undefined,
|
||||
mintAddress: string,
|
||||
amount: string,
|
||||
decimals: number,
|
||||
targetAddressStr: string | undefined,
|
||||
targetChain: ChainId,
|
||||
originAddressStr?: string,
|
||||
originChain?: ChainId
|
||||
) {
|
||||
if (
|
||||
!wallet ||
|
||||
!wallet.publicKey ||
|
||||
!payerAddress ||
|
||||
!fromAddress ||
|
||||
!targetAddressStr ||
|
||||
(originChain && !originAddressStr)
|
||||
)
|
||||
return;
|
||||
// TODO: share connection in context?
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const targetAddress = zeroPad(arrayify(targetAddressStr), 32);
|
||||
const amountParsed = parseUnits(amount, decimals).toBigInt();
|
||||
const originAddress = originAddressStr
|
||||
? zeroPad(hexToUint8Array(originAddressStr), 32)
|
||||
: undefined;
|
||||
const transaction = await transferFromSolanaTx(
|
||||
connection,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
fromAddress,
|
||||
mintAddress,
|
||||
amountParsed,
|
||||
targetAddress,
|
||||
targetChain,
|
||||
originAddress,
|
||||
originChain
|
||||
);
|
||||
const info = await signSendConfirmAndGet(wallet, connection, transaction);
|
||||
if (!info) {
|
||||
throw new Error("An error occurred while fetching the transaction info");
|
||||
}
|
||||
const sequence = parseSequenceFromLogSolana(info);
|
||||
const emitterAddress = await getEmitterAddressSolana(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS
|
||||
);
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_SOLANA,
|
||||
emitterAddress,
|
||||
sequence
|
||||
);
|
||||
return vaaBytes;
|
||||
}
|
||||
|
||||
export async function transferFromTerra(
|
||||
wallet: TerraConnectedWallet | undefined,
|
||||
asset: string,
|
||||
amount: string,
|
||||
targetAddressStr: string | undefined,
|
||||
targetChain: ChainId
|
||||
) {
|
||||
if (!wallet) return;
|
||||
const result: TxResult =
|
||||
wallet &&
|
||||
(await wallet.post({
|
||||
msgs: [
|
||||
new MsgExecuteContract(
|
||||
wallet.terraAddress,
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
{
|
||||
initiate_transfer: {
|
||||
asset: TERRA_TEST_TOKEN_ADDRESS,
|
||||
amount: amount,
|
||||
recipient_chain: targetChain,
|
||||
recipient: targetAddressStr,
|
||||
fee: 1000,
|
||||
nonce: 0,
|
||||
},
|
||||
},
|
||||
{ uluna: 1000 }
|
||||
),
|
||||
],
|
||||
memo: "Complete Transfer",
|
||||
}));
|
||||
console.log(result);
|
||||
const sequence = parseSequenceFromLogTerra(result);
|
||||
console.log(sequence);
|
||||
const emitterAddress = await getEmitterAddressSolana(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS
|
||||
);
|
||||
console.log(emitterAddress);
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_TERRA,
|
||||
emitterAddress,
|
||||
sequence
|
||||
);
|
||||
return vaaBytes;
|
||||
}
|
|
@ -9,7 +9,6 @@ export function parseSequenceFromLogEth(
|
|||
): string {
|
||||
// TODO: dangerous!(?)
|
||||
const bridgeLog = receipt.logs.filter((l) => {
|
||||
console.log(l.address, bridgeAddress);
|
||||
return l.address === bridgeAddress;
|
||||
})[0];
|
||||
const {
|
||||
|
|
|
@ -32,7 +32,6 @@ export async function redeemOnSolana(
|
|||
await import("../solana/token/token_bridge");
|
||||
const ixs = [];
|
||||
if (isSolanaNative) {
|
||||
console.log("COMPLETE TRANSFER NATIVE");
|
||||
ixs.push(
|
||||
ixFromRust(
|
||||
complete_transfer_native_ix(
|
||||
|
|
Loading…
Reference in New Issue