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",
|
"@terra-money/wallet-provider": "^1.4.0-alpha.1",
|
||||||
"ethers": "^5.4.1",
|
"ethers": "^5.4.1",
|
||||||
"js-base64": "^3.6.1",
|
"js-base64": "^3.6.1",
|
||||||
|
"notistack": "^1.0.10",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-redux": "^7.2.4",
|
"react-redux": "^7.2.4",
|
||||||
|
@ -26881,6 +26882,24 @@
|
||||||
"node": ">=4"
|
"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": {
|
"node_modules/npm-bundled": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz",
|
||||||
|
@ -61189,6 +61208,15 @@
|
||||||
"sort-keys": "^1.0.0"
|
"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": {
|
"npm-bundled": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz",
|
"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",
|
"@terra-money/wallet-provider": "^1.4.0-alpha.1",
|
||||||
"ethers": "^5.4.1",
|
"ethers": "^5.4.1",
|
||||||
"js-base64": "^3.6.1",
|
"js-base64": "^3.6.1",
|
||||||
|
"notistack": "^1.0.10",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-redux": "^7.2.4",
|
"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 { Button, CircularProgress, makeStyles } from "@material-ui/core";
|
||||||
import { useCallback } from "react";
|
import { useHandleCreateWrapped } from "../../hooks/useHandleCreateWrapped";
|
||||||
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";
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
transferButton: {
|
transferButton: {
|
||||||
|
@ -30,49 +10,20 @@ const useStyles = makeStyles((theme) => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function Create() {
|
function Create() {
|
||||||
const dispatch = useDispatch();
|
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const targetChain = useSelector(selectAttestTargetChain);
|
const { handleClick, disabled, showLoader } = useHandleCreateWrapped();
|
||||||
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]);
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: "relative" }}>
|
<div style={{ position: "relative" }}>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
className={classes.transferButton}
|
className={classes.transferButton}
|
||||||
disabled={isCreating}
|
disabled={disabled}
|
||||||
onClick={handleCreateClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
Create
|
Create
|
||||||
</Button>
|
</Button>
|
||||||
{isCreating ? (
|
{showLoader ? (
|
||||||
<CircularProgress
|
<CircularProgress
|
||||||
size={24}
|
size={24}
|
||||||
color="inherit"
|
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 { Button, CircularProgress, makeStyles } from "@material-ui/core";
|
||||||
import { useCallback } from "react";
|
import { useHandleAttest } from "../../hooks/useHandleAttest";
|
||||||
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";
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
transferButton: {
|
transferButton: {
|
||||||
|
@ -32,67 +9,9 @@ const useStyles = makeStyles((theme) => ({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// TODO: move attest to its own workflow
|
|
||||||
|
|
||||||
function Send() {
|
function Send() {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const dispatch = useDispatch();
|
const { handleClick, disabled, showLoader } = useHandleAttest();
|
||||||
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]);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div style={{ position: "relative" }}>
|
<div style={{ position: "relative" }}>
|
||||||
|
@ -100,12 +19,12 @@ function Send() {
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
className={classes.transferButton}
|
className={classes.transferButton}
|
||||||
onClick={handleAttestClick}
|
onClick={handleClick}
|
||||||
disabled={!isTargetComplete || isSending || isSendComplete}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
Attest
|
Attest
|
||||||
</Button>
|
</Button>
|
||||||
{isSending ? (
|
{showLoader ? (
|
||||||
<CircularProgress
|
<CircularProgress
|
||||||
size={24}
|
size={24}
|
||||||
color="inherit"
|
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 DisconnectIcon from "@material-ui/icons/LinkOff";
|
||||||
import {
|
import {
|
||||||
WalletDisconnectButton,
|
WalletDisconnectButton,
|
||||||
WalletMultiButton,
|
WalletMultiButton,
|
||||||
} from "@solana/wallet-adapter-material-ui";
|
} from "@solana/wallet-adapter-material-ui";
|
||||||
import { useWallet } from "@solana/wallet-adapter-react";
|
|
||||||
import React, { FC } from "react";
|
|
||||||
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
|
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
|
||||||
|
|
||||||
const SolanaWalletKey = () => {
|
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 { Button, CircularProgress, makeStyles } from "@material-ui/core";
|
||||||
import { useCallback } from "react";
|
import { useHandleRedeem } from "../../hooks/useHandleRedeem";
|
||||||
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";
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
transferButton: {
|
transferButton: {
|
||||||
|
@ -33,69 +10,20 @@ const useStyles = makeStyles((theme) => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function Redeem() {
|
function Redeem() {
|
||||||
const dispatch = useDispatch();
|
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const isSourceAssetWormholeWrapped = useSelector(
|
const { handleClick, disabled, showLoader } = useHandleRedeem();
|
||||||
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,
|
|
||||||
]);
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: "relative" }}>
|
<div style={{ position: "relative" }}>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
className={classes.transferButton}
|
className={classes.transferButton}
|
||||||
disabled={isRedeeming}
|
disabled={disabled}
|
||||||
onClick={handleRedeemClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
Redeem
|
Redeem
|
||||||
</Button>
|
</Button>
|
||||||
{isRedeeming ? (
|
{showLoader ? (
|
||||||
<CircularProgress
|
<CircularProgress
|
||||||
size={24}
|
size={24}
|
||||||
color="inherit"
|
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 { Button, CircularProgress, makeStyles } from "@material-ui/core";
|
||||||
import {
|
import { useHandleTransfer } from "../../hooks/useHandleTransfer";
|
||||||
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";
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
transferButton: {
|
transferButton: {
|
||||||
|
@ -46,150 +9,9 @@ const useStyles = makeStyles((theme) => ({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// TODO: move attest to its own workflow
|
|
||||||
|
|
||||||
function Send() {
|
function Send() {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const dispatch = useDispatch();
|
const { handleClick, disabled, showLoader } = useHandleTransfer();
|
||||||
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,
|
|
||||||
]);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div style={{ position: "relative" }}>
|
<div style={{ position: "relative" }}>
|
||||||
|
@ -197,12 +19,12 @@ function Send() {
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
className={classes.transferButton}
|
className={classes.transferButton}
|
||||||
onClick={handleTransferClick}
|
onClick={handleClick}
|
||||||
disabled={!isTargetComplete || isSending || isSendComplete}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
Transfer
|
Transfer
|
||||||
</Button>
|
</Button>
|
||||||
{isSending ? (
|
{showLoader ? (
|
||||||
<CircularProgress
|
<CircularProgress
|
||||||
size={24}
|
size={24}
|
||||||
color="inherit"
|
color="inherit"
|
||||||
|
|
|
@ -31,7 +31,7 @@ function Source() {
|
||||||
const sourceAsset = useSelector(selectTransferSourceAsset);
|
const sourceAsset = useSelector(selectTransferSourceAsset);
|
||||||
const uiAmountString = useSelector(selectTransferSourceBalanceString);
|
const uiAmountString = useSelector(selectTransferSourceBalanceString);
|
||||||
const amount = useSelector(selectTransferAmount);
|
const amount = useSelector(selectTransferAmount);
|
||||||
const isSourceComplete = true; // useSelector(selectTransferIsSourceComplete);
|
const isSourceComplete = useSelector(selectTransferIsSourceComplete);
|
||||||
const shouldLockFields = useSelector(selectTransferShouldLockFields);
|
const shouldLockFields = useSelector(selectTransferShouldLockFields);
|
||||||
const handleSourceChange = useCallback(
|
const handleSourceChange = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
|
|
|
@ -26,14 +26,9 @@ function useFetchTargetAsset() {
|
||||||
);
|
);
|
||||||
const originChain = useSelector(selectTransferOriginChain);
|
const originChain = useSelector(selectTransferOriginChain);
|
||||||
const originAsset = useSelector(selectTransferOriginAsset);
|
const originAsset = useSelector(selectTransferOriginAsset);
|
||||||
console.log(
|
|
||||||
"WH Wrapped?",
|
|
||||||
isSourceAssetWormholeWrapped,
|
|
||||||
originChain,
|
|
||||||
originAsset
|
|
||||||
);
|
|
||||||
const targetChain = useSelector(selectTransferTargetChain);
|
const targetChain = useSelector(selectTransferTargetChain);
|
||||||
const { provider } = useEthereumProvider();
|
const { provider } = useEthereumProvider();
|
||||||
|
// TODO: this may not cover wrapped to wrapped, should always use origin?
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSourceAssetWormholeWrapped && originChain === targetChain) {
|
if (isSourceAssetWormholeWrapped && originChain === targetChain) {
|
||||||
dispatch(setTargetAsset(originAsset));
|
dispatch(setTargetAsset(originAsset));
|
||||||
|
@ -57,7 +52,6 @@ function useFetchTargetAsset() {
|
||||||
try {
|
try {
|
||||||
const asset = await getForeignAssetSol(sourceChain, sourceAsset);
|
const asset = await getForeignAssetSol(sourceChain, sourceAsset);
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
console.log("solana target asset", asset);
|
|
||||||
dispatch(setTargetAsset(asset));
|
dispatch(setTargetAsset(asset));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -96,7 +96,6 @@ function useGetBalanceEffect(sourceOrTarget: "source" | "target") {
|
||||||
.getParsedTokenAccountsByOwner(solPK, { mint })
|
.getParsedTokenAccountsByOwner(solPK, { mint })
|
||||||
.then(({ value }) => {
|
.then(({ value }) => {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
console.log("parsed token accounts", value);
|
|
||||||
if (value.length) {
|
if (value.length) {
|
||||||
// TODO: allow selection between these target accounts
|
// TODO: allow selection between these target accounts
|
||||||
dispatch(
|
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 { CssBaseline } from "@material-ui/core";
|
||||||
import { ThemeProvider } from "@material-ui/core/styles";
|
import { ThemeProvider } from "@material-ui/core/styles";
|
||||||
|
import { SnackbarProvider } from "notistack";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { HashRouter } from "react-router-dom";
|
import { HashRouter } from "react-router-dom";
|
||||||
|
@ -16,15 +17,17 @@ ReactDOM.render(
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<RadialGradient />
|
<RadialGradient />
|
||||||
<SolanaWalletProvider>
|
<SnackbarProvider maxSnack={3}>
|
||||||
<EthereumProviderProvider>
|
<SolanaWalletProvider>
|
||||||
<TerraWalletProvider>
|
<EthereumProviderProvider>
|
||||||
<HashRouter>
|
<TerraWalletProvider>
|
||||||
<App />
|
<HashRouter>
|
||||||
</HashRouter>
|
<App />
|
||||||
</TerraWalletProvider>
|
</HashRouter>
|
||||||
</EthereumProviderProvider>
|
</TerraWalletProvider>
|
||||||
</SolanaWalletProvider>
|
</EthereumProviderProvider>
|
||||||
|
</SolanaWalletProvider>
|
||||||
|
</SnackbarProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</Provider>,
|
</Provider>,
|
||||||
document.getElementById("root")
|
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);
|
await connection.confirmTransaction(txid);
|
||||||
return 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 {
|
||||||
import { TxInfo, LCDClient } from "@terra-money/terra.js";
|
TxResult,
|
||||||
|
ConnectedWallet as TerraConnectedWallet,
|
||||||
|
} from "@terra-money/wallet-provider";
|
||||||
|
import { LCDClient } from "@terra-money/terra.js";
|
||||||
|
|
||||||
// TODO: Loop txInfo for timed out transactions.
|
// TODO: Loop txInfo for timed out transactions.
|
||||||
// lcd.tx.txInfo(transaction.result.txhash);
|
// lcd.tx.txInfo(transaction.result.txhash);
|
||||||
|
@ -7,7 +10,7 @@ export async function waitForTerraExecution(
|
||||||
wallet: TerraConnectedWallet,
|
wallet: TerraConnectedWallet,
|
||||||
transaction: TxResult
|
transaction: TxResult
|
||||||
) {
|
) {
|
||||||
const lcd = new LCDClient({
|
new LCDClient({
|
||||||
URL: wallet.network.lcd,
|
URL: wallet.network.lcd,
|
||||||
chainID: "columbus-4",
|
chainID: "columbus-4",
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 {
|
): string {
|
||||||
// TODO: dangerous!(?)
|
// TODO: dangerous!(?)
|
||||||
const bridgeLog = receipt.logs.filter((l) => {
|
const bridgeLog = receipt.logs.filter((l) => {
|
||||||
console.log(l.address, bridgeAddress);
|
|
||||||
return l.address === bridgeAddress;
|
return l.address === bridgeAddress;
|
||||||
})[0];
|
})[0];
|
||||||
const {
|
const {
|
||||||
|
|
|
@ -32,7 +32,6 @@ export async function redeemOnSolana(
|
||||||
await import("../solana/token/token_bridge");
|
await import("../solana/token/token_bridge");
|
||||||
const ixs = [];
|
const ixs = [];
|
||||||
if (isSolanaNative) {
|
if (isSolanaNative) {
|
||||||
console.log("COMPLETE TRANSFER NATIVE");
|
|
||||||
ixs.push(
|
ixs.push(
|
||||||
ixFromRust(
|
ixFromRust(
|
||||||
complete_transfer_native_ix(
|
complete_transfer_native_ix(
|
||||||
|
|
Loading…
Reference in New Issue