bridge_ui: sol, eth bidirectional transfers
Change-Id: I0bbbbffddd3bec7771c79953556271000731cd36
This commit is contained in:
parent
b9359aab87
commit
6875559d4c
|
@ -3,7 +3,6 @@ import { useCallback } from "react";
|
|||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useEthereumProvider } from "../../contexts/EthereumProviderContext";
|
||||
import { useSolanaWallet } from "../../contexts/SolanaWalletContext";
|
||||
import useWrappedAsset from "../../hooks/useWrappedAsset";
|
||||
import { setIsSending, setSignedVAAHex } from "../../store/attestSlice";
|
||||
import {
|
||||
selectAttestIsSendComplete,
|
||||
|
@ -11,7 +10,6 @@ import {
|
|||
selectAttestIsTargetComplete,
|
||||
selectAttestSourceAsset,
|
||||
selectAttestSourceChain,
|
||||
selectAttestTargetChain,
|
||||
} from "../../store/selectors";
|
||||
import { uint8ArrayToHex } from "../../utils/array";
|
||||
import attestFrom, {
|
||||
|
@ -35,21 +33,12 @@ function Send() {
|
|||
const dispatch = useDispatch();
|
||||
const sourceChain = useSelector(selectAttestSourceChain);
|
||||
const sourceAsset = useSelector(selectAttestSourceAsset);
|
||||
const targetChain = useSelector(selectAttestTargetChain);
|
||||
const isTargetComplete = useSelector(selectAttestIsTargetComplete);
|
||||
const isSending = useSelector(selectAttestIsSending);
|
||||
const isSendComplete = useSelector(selectAttestIsSendComplete);
|
||||
const { provider, signer } = useEthereumProvider();
|
||||
const { wallet } = useSolanaWallet();
|
||||
const solPK = wallet?.publicKey;
|
||||
const {
|
||||
isLoading: isCheckingWrapped,
|
||||
// isWrapped,
|
||||
wrappedAsset,
|
||||
} = useWrappedAsset(targetChain, sourceChain, sourceAsset, provider);
|
||||
// TODO: check this and send to separate flow
|
||||
const isWrapped = true;
|
||||
console.log(isCheckingWrapped, isWrapped, wrappedAsset);
|
||||
// TODO: dynamically get "to" wallet
|
||||
const handleAttestClick = useCallback(() => {
|
||||
// TODO: more generic way of calling these
|
||||
|
|
|
@ -20,7 +20,7 @@ import Target from "./Target";
|
|||
// TODO: ensure that both wallets are connected to the same known network
|
||||
|
||||
function Attest() {
|
||||
useGetBalanceEffect();
|
||||
useGetBalanceEffect("source");
|
||||
const dispatch = useDispatch();
|
||||
const activeStep = useSelector(selectAttestActiveStep);
|
||||
const signedVAAHex = useSelector(selectAttestSignedVAAHex);
|
||||
|
|
|
@ -2,15 +2,18 @@ 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 useTransferSignedVAA from "../../hooks/useTransferSignedVAA";
|
||||
import {
|
||||
selectTransferIsRedeeming,
|
||||
selectTransferIsSourceAssetWormholeWrapped,
|
||||
selectTransferOriginChain,
|
||||
selectTransferTargetAsset,
|
||||
selectTransferTargetChain,
|
||||
} from "../../store/selectors";
|
||||
import { setIsRedeeming } from "../../store/transferSlice";
|
||||
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../../utils/consts";
|
||||
import redeemOn, { redeemOnEth, redeemOnSolana } from "../../utils/redeemOn";
|
||||
import { useSolanaWallet } from "../../contexts/SolanaWalletContext";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
transferButton: {
|
||||
|
@ -23,7 +26,12 @@ 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 { wallet } = useSolanaWallet();
|
||||
const solPK = wallet?.publicKey;
|
||||
const { provider, signer } = useEthereumProvider();
|
||||
|
@ -44,9 +52,26 @@ function Redeem() {
|
|||
signedVAA
|
||||
) {
|
||||
dispatch(setIsRedeeming(true));
|
||||
redeemOnSolana(wallet, solPK?.toString(), signedVAA);
|
||||
redeemOnSolana(
|
||||
wallet,
|
||||
solPK?.toString(),
|
||||
signedVAA,
|
||||
!!isSourceAssetWormholeWrapped && originChain === CHAIN_ID_SOLANA,
|
||||
targetAsset || undefined
|
||||
);
|
||||
}
|
||||
}, [dispatch, targetChain, provider, signer, signedVAA, wallet, solPK]);
|
||||
}, [
|
||||
dispatch,
|
||||
targetChain,
|
||||
provider,
|
||||
signer,
|
||||
signedVAA,
|
||||
wallet,
|
||||
solPK,
|
||||
isSourceAssetWormholeWrapped,
|
||||
originChain,
|
||||
targetAsset,
|
||||
]);
|
||||
return (
|
||||
<div style={{ position: "relative" }}>
|
||||
<Button
|
||||
|
|
|
@ -1,18 +1,28 @@
|
|||
import { Button, CircularProgress, makeStyles } from "@material-ui/core";
|
||||
import { useCallback } from "react";
|
||||
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 { useSolanaWallet } from "../../contexts/SolanaWalletContext";
|
||||
import useWrappedAsset from "../../hooks/useWrappedAsset";
|
||||
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";
|
||||
|
@ -37,8 +47,11 @@ function Send() {
|
|||
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);
|
||||
|
@ -48,16 +61,43 @@ function Send() {
|
|||
const sourceParsedTokenAccount = useSelector(
|
||||
selectTransferSourceParsedTokenAccount
|
||||
);
|
||||
const tokenPK = sourceParsedTokenAccount?.publicKey;
|
||||
const sourceTokenPublicKey = sourceParsedTokenAccount?.publicKey;
|
||||
const decimals = sourceParsedTokenAccount?.decimals;
|
||||
const {
|
||||
isLoading: isCheckingWrapped,
|
||||
// isWrapped,
|
||||
wrappedAsset,
|
||||
} = useWrappedAsset(targetChain, sourceChain, sourceAsset, provider);
|
||||
// TODO: check this and send to separate flow
|
||||
const isWrapped = true;
|
||||
console.log(isCheckingWrapped, isWrapped, wrappedAsset);
|
||||
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
|
||||
|
@ -72,6 +112,7 @@ function Send() {
|
|||
(async () => {
|
||||
dispatch(setIsSending(true));
|
||||
try {
|
||||
console.log("actually sending", tpkRef.current);
|
||||
const vaaBytes = await transferFromEth(
|
||||
provider,
|
||||
signer,
|
||||
|
@ -79,7 +120,7 @@ function Send() {
|
|||
decimals,
|
||||
amount,
|
||||
targetChain,
|
||||
solPK?.toBytes()
|
||||
tpkRef.current
|
||||
);
|
||||
console.log("bytes in transfer", vaaBytes);
|
||||
vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes)));
|
||||
|
@ -101,12 +142,14 @@ function Send() {
|
|||
const vaaBytes = await transferFromSolana(
|
||||
wallet,
|
||||
solPK?.toString(),
|
||||
tokenPK,
|
||||
sourceTokenPublicKey,
|
||||
sourceAsset,
|
||||
amount,
|
||||
amount, //TODO: avoid decimals, pass in parsed amount
|
||||
decimals,
|
||||
signerAddress,
|
||||
targetChain
|
||||
targetChain,
|
||||
originAsset,
|
||||
originChain
|
||||
);
|
||||
console.log("bytes in transfer", vaaBytes);
|
||||
vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes)));
|
||||
|
@ -125,11 +168,13 @@ function Send() {
|
|||
signerAddress,
|
||||
wallet,
|
||||
solPK,
|
||||
tokenPK,
|
||||
sourceTokenPublicKey,
|
||||
sourceAsset,
|
||||
amount,
|
||||
decimals,
|
||||
targetChain,
|
||||
originAsset,
|
||||
originChain,
|
||||
]);
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -73,6 +73,7 @@ function Source() {
|
|||
))}
|
||||
</TextField>
|
||||
<KeyAndBalance chainId={sourceChain} balance={uiAmountString} />
|
||||
{/* TODO: token list for eth, check own */}
|
||||
<TextField
|
||||
placeholder="Asset"
|
||||
fullWidth
|
||||
|
|
|
@ -1,17 +1,29 @@
|
|||
import { Button, MenuItem, TextField } from "@material-ui/core";
|
||||
import { Button, makeStyles, MenuItem, TextField } from "@material-ui/core";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
selectTransferIsSourceAssetWormholeWrapped,
|
||||
selectTransferIsTargetComplete,
|
||||
selectTransferShouldLockFields,
|
||||
selectTransferSourceChain,
|
||||
selectTransferTargetAsset,
|
||||
selectTransferTargetBalanceString,
|
||||
selectTransferTargetChain,
|
||||
} from "../../store/selectors";
|
||||
import { incrementStep, setTargetChain } from "../../store/transferSlice";
|
||||
import { CHAINS } from "../../utils/consts";
|
||||
import { hexToUint8Array } from "../../utils/array";
|
||||
import { CHAINS, CHAIN_ID_SOLANA } from "../../utils/consts";
|
||||
import KeyAndBalance from "../KeyAndBalance";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
transferField: {
|
||||
marginTop: theme.spacing(5),
|
||||
},
|
||||
}));
|
||||
|
||||
function Target() {
|
||||
const classes = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const sourceChain = useSelector(selectTransferSourceChain);
|
||||
const chains = useMemo(
|
||||
|
@ -19,6 +31,19 @@ function Target() {
|
|||
[sourceChain]
|
||||
);
|
||||
const targetChain = useSelector(selectTransferTargetChain);
|
||||
const targetAsset = useSelector(selectTransferTargetAsset);
|
||||
const isSourceAssetWormholeWrapped = useSelector(
|
||||
selectTransferIsSourceAssetWormholeWrapped
|
||||
);
|
||||
// TODO: wrapped stuff in hex, but native in not hex?
|
||||
const readableTargetAsset =
|
||||
isSourceAssetWormholeWrapped &&
|
||||
targetChain === CHAIN_ID_SOLANA &&
|
||||
targetAsset
|
||||
? new PublicKey(hexToUint8Array(targetAsset)).toString()
|
||||
: targetAsset || "";
|
||||
// TODO: why doesn't this show up for solana wrapped?
|
||||
const uiAmountString = useSelector(selectTransferTargetBalanceString);
|
||||
const isTargetComplete = useSelector(selectTransferIsTargetComplete);
|
||||
const shouldLockFields = useSelector(selectTransferShouldLockFields);
|
||||
const handleTargetChange = useCallback(
|
||||
|
@ -48,8 +73,14 @@ function Target() {
|
|||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
{/* TODO: determine "to" token address */}
|
||||
<KeyAndBalance chainId={targetChain} />
|
||||
<KeyAndBalance chainId={targetChain} balance={uiAmountString} />
|
||||
<TextField
|
||||
placeholder="Asset"
|
||||
fullWidth
|
||||
className={classes.transferField}
|
||||
value={readableTargetAsset}
|
||||
disabled={true}
|
||||
/>
|
||||
<Button
|
||||
disabled={!isTargetComplete}
|
||||
onClick={handleNextClick}
|
||||
|
|
|
@ -6,6 +6,8 @@ import {
|
|||
Stepper,
|
||||
} from "@material-ui/core";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import useCheckIfWormholeWrapped from "../../hooks/useCheckIfWormholeWrapped";
|
||||
import useFetchTargetAsset from "../../hooks/useFetchTargetAsset";
|
||||
import useGetBalanceEffect from "../../hooks/useGetBalanceEffect";
|
||||
import {
|
||||
selectTransferActiveStep,
|
||||
|
@ -23,7 +25,10 @@ import Target from "./Target";
|
|||
// TODO: warn if amount exceeds balance
|
||||
|
||||
function Transfer() {
|
||||
useGetBalanceEffect();
|
||||
useGetBalanceEffect("source");
|
||||
useCheckIfWormholeWrapped();
|
||||
useFetchTargetAsset();
|
||||
useGetBalanceEffect("target");
|
||||
const dispatch = useDispatch();
|
||||
const activeStep = useSelector(selectTransferActiveStep);
|
||||
const signedVAAHex = useSelector(selectTransferSignedVAAHex);
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import { useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||
import {
|
||||
selectTransferSourceAsset,
|
||||
selectTransferSourceChain,
|
||||
} from "../store/selectors";
|
||||
import { setSourceWormholeWrappedInfo } from "../store/transferSlice";
|
||||
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../utils/consts";
|
||||
import {
|
||||
getOriginalAssetEth,
|
||||
getOriginalAssetSol,
|
||||
} from "../utils/getOriginalAsset";
|
||||
|
||||
function useCheckIfWormholeWrapped() {
|
||||
const dispatch = useDispatch();
|
||||
const sourceChain = useSelector(selectTransferSourceChain);
|
||||
const sourceAsset = useSelector(selectTransferSourceAsset);
|
||||
const { provider } = useEthereumProvider();
|
||||
useEffect(() => {
|
||||
// TODO: loading state, error state
|
||||
dispatch(setSourceWormholeWrappedInfo(undefined));
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
if (sourceChain === CHAIN_ID_ETH && provider) {
|
||||
const wrappedInfo = await getOriginalAssetEth(provider, sourceAsset);
|
||||
if (!cancelled) {
|
||||
dispatch(setSourceWormholeWrappedInfo(wrappedInfo));
|
||||
}
|
||||
} else if (sourceChain === CHAIN_ID_SOLANA) {
|
||||
try {
|
||||
const wrappedInfo = await getOriginalAssetSol(sourceAsset);
|
||||
if (!cancelled) {
|
||||
dispatch(setSourceWormholeWrappedInfo(wrappedInfo));
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [dispatch, sourceChain, sourceAsset, provider]);
|
||||
}
|
||||
|
||||
export default useCheckIfWormholeWrapped;
|
|
@ -0,0 +1,83 @@
|
|||
import { useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||
import {
|
||||
selectTransferIsSourceAssetWormholeWrapped,
|
||||
selectTransferOriginAsset,
|
||||
selectTransferOriginChain,
|
||||
selectTransferSourceAsset,
|
||||
selectTransferSourceChain,
|
||||
selectTransferTargetChain,
|
||||
} from "../store/selectors";
|
||||
import { setTargetAsset } from "../store/transferSlice";
|
||||
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../utils/consts";
|
||||
import {
|
||||
getForeignAssetEth,
|
||||
getForeignAssetSol,
|
||||
} from "../utils/getForeignAsset";
|
||||
|
||||
function useFetchTargetAsset() {
|
||||
const dispatch = useDispatch();
|
||||
const sourceChain = useSelector(selectTransferSourceChain);
|
||||
const sourceAsset = useSelector(selectTransferSourceAsset);
|
||||
const isSourceAssetWormholeWrapped = useSelector(
|
||||
selectTransferIsSourceAssetWormholeWrapped
|
||||
);
|
||||
const originChain = useSelector(selectTransferOriginChain);
|
||||
const originAsset = useSelector(selectTransferOriginAsset);
|
||||
console.log(
|
||||
"WH Wrapped?",
|
||||
isSourceAssetWormholeWrapped,
|
||||
originChain,
|
||||
originAsset
|
||||
);
|
||||
const targetChain = useSelector(selectTransferTargetChain);
|
||||
const { provider } = useEthereumProvider();
|
||||
useEffect(() => {
|
||||
if (isSourceAssetWormholeWrapped && originChain === targetChain) {
|
||||
dispatch(setTargetAsset(originAsset));
|
||||
return;
|
||||
}
|
||||
// TODO: loading state, error state
|
||||
dispatch(setTargetAsset(undefined));
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
if (targetChain === CHAIN_ID_ETH && provider) {
|
||||
const asset = await getForeignAssetEth(
|
||||
provider,
|
||||
sourceChain,
|
||||
sourceAsset
|
||||
);
|
||||
if (!cancelled) {
|
||||
dispatch(setTargetAsset(asset));
|
||||
}
|
||||
} else if (targetChain === CHAIN_ID_SOLANA) {
|
||||
try {
|
||||
const asset = await getForeignAssetSol(sourceChain, sourceAsset);
|
||||
if (!cancelled) {
|
||||
console.log("solana target asset", asset);
|
||||
dispatch(setTargetAsset(asset));
|
||||
}
|
||||
} catch (e) {
|
||||
if (!cancelled) {
|
||||
// TODO: warning for this
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [
|
||||
dispatch,
|
||||
isSourceAssetWormholeWrapped,
|
||||
originChain,
|
||||
originAsset,
|
||||
targetChain,
|
||||
sourceChain,
|
||||
sourceAsset,
|
||||
provider,
|
||||
]);
|
||||
}
|
||||
|
||||
export default useFetchTargetAsset;
|
|
@ -8,8 +8,14 @@ import { TokenImplementation__factory } from "../ethers-contracts";
|
|||
import {
|
||||
selectTransferSourceAsset,
|
||||
selectTransferSourceChain,
|
||||
selectTransferTargetAsset,
|
||||
selectTransferTargetChain,
|
||||
} from "../store/selectors";
|
||||
import { setSourceParsedTokenAccount } from "../store/transferSlice";
|
||||
import {
|
||||
setSourceParsedTokenAccount,
|
||||
setTargetParsedTokenAccount,
|
||||
} from "../store/transferSlice";
|
||||
import { hexToUint8Array } from "../utils/array";
|
||||
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA, SOLANA_HOST } from "../utils/consts";
|
||||
|
||||
function createParsedTokenAccount(
|
||||
|
@ -28,24 +34,44 @@ function createParsedTokenAccount(
|
|||
};
|
||||
}
|
||||
|
||||
function useGetBalanceEffect() {
|
||||
/**
|
||||
* Fetches the balance of an asset for the connected wallet
|
||||
* @param sourceOrTarget determines whether this will fetch balance for the source or target account. Not intended to be switched on the same hook!
|
||||
*/
|
||||
function useGetBalanceEffect(sourceOrTarget: "source" | "target") {
|
||||
const dispatch = useDispatch();
|
||||
const sourceChain = useSelector(selectTransferSourceChain);
|
||||
const sourceAsset = useSelector(selectTransferSourceAsset);
|
||||
const setAction =
|
||||
sourceOrTarget === "source"
|
||||
? setSourceParsedTokenAccount
|
||||
: setTargetParsedTokenAccount;
|
||||
const lookupChain = useSelector(
|
||||
sourceOrTarget === "source"
|
||||
? selectTransferSourceChain
|
||||
: selectTransferTargetChain
|
||||
);
|
||||
const lookupAsset = useSelector(
|
||||
sourceOrTarget === "source"
|
||||
? selectTransferSourceAsset
|
||||
: selectTransferTargetAsset
|
||||
);
|
||||
const { wallet } = useSolanaWallet();
|
||||
const solPK = wallet?.publicKey;
|
||||
const { provider, signerAddress } = useEthereumProvider();
|
||||
useEffect(() => {
|
||||
// TODO: loading state
|
||||
dispatch(setSourceParsedTokenAccount(undefined));
|
||||
if (!sourceAsset) {
|
||||
dispatch(setAction(undefined));
|
||||
if (!lookupAsset) {
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
if (sourceChain === CHAIN_ID_SOLANA && solPK) {
|
||||
if (lookupChain === CHAIN_ID_SOLANA && solPK) {
|
||||
let mint;
|
||||
try {
|
||||
mint = new PublicKey(sourceAsset);
|
||||
mint = new PublicKey(
|
||||
sourceOrTarget === "source"
|
||||
? lookupAsset
|
||||
: hexToUint8Array(lookupAsset)
|
||||
);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
@ -54,9 +80,11 @@ function useGetBalanceEffect() {
|
|||
.getParsedTokenAccountsByOwner(solPK, { mint })
|
||||
.then(({ value }) => {
|
||||
if (!cancelled) {
|
||||
console.log("parsed token accounts", value);
|
||||
if (value.length) {
|
||||
// TODO: allow selection between these target accounts
|
||||
dispatch(
|
||||
setSourceParsedTokenAccount(
|
||||
setAction(
|
||||
createParsedTokenAccount(
|
||||
value[0].pubkey,
|
||||
value[0].account.data.parsed?.info?.tokenAmount?.amount,
|
||||
|
@ -78,15 +106,15 @@ function useGetBalanceEffect() {
|
|||
}
|
||||
});
|
||||
}
|
||||
if (sourceChain === CHAIN_ID_ETH && provider && signerAddress) {
|
||||
const token = TokenImplementation__factory.connect(sourceAsset, provider);
|
||||
if (lookupChain === CHAIN_ID_ETH && provider && signerAddress) {
|
||||
const token = TokenImplementation__factory.connect(lookupAsset, provider);
|
||||
token
|
||||
.decimals()
|
||||
.then((decimals) => {
|
||||
token.balanceOf(signerAddress).then((n) => {
|
||||
if (!cancelled) {
|
||||
dispatch(
|
||||
setSourceParsedTokenAccount(
|
||||
setAction(
|
||||
// TODO: verify accuracy
|
||||
createParsedTokenAccount(
|
||||
undefined,
|
||||
|
@ -109,7 +137,16 @@ function useGetBalanceEffect() {
|
|||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [dispatch, sourceChain, sourceAsset, solPK, provider, signerAddress]);
|
||||
}, [
|
||||
dispatch,
|
||||
sourceOrTarget,
|
||||
setAction,
|
||||
lookupChain,
|
||||
lookupAsset,
|
||||
solPK,
|
||||
provider,
|
||||
signerAddress,
|
||||
]);
|
||||
}
|
||||
|
||||
export default useGetBalanceEffect;
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
import { ethers } from "ethers";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ChainId, CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../utils/consts";
|
||||
import {
|
||||
getAttestedAssetEth,
|
||||
getAttestedAssetSol,
|
||||
} from "../utils/getAttestedAsset";
|
||||
export interface WrappedAssetState {
|
||||
isLoading: boolean;
|
||||
isWrapped: boolean;
|
||||
wrappedAsset: string | null;
|
||||
}
|
||||
|
||||
function useWrappedAsset(
|
||||
checkChain: ChainId,
|
||||
originChain: ChainId,
|
||||
originAsset: string,
|
||||
provider: ethers.providers.Web3Provider | undefined
|
||||
) {
|
||||
const [state, setState] = useState<WrappedAssetState>({
|
||||
isLoading: false,
|
||||
isWrapped: false,
|
||||
wrappedAsset: null,
|
||||
});
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
if (checkChain === CHAIN_ID_ETH && provider) {
|
||||
setState({ isLoading: true, isWrapped: false, wrappedAsset: null });
|
||||
const asset = await getAttestedAssetEth(
|
||||
provider,
|
||||
originChain,
|
||||
originAsset
|
||||
);
|
||||
if (!cancelled) {
|
||||
setState({
|
||||
isLoading: false,
|
||||
isWrapped: !!asset && asset !== ethers.constants.AddressZero,
|
||||
wrappedAsset: asset,
|
||||
});
|
||||
}
|
||||
} else if (checkChain === CHAIN_ID_SOLANA) {
|
||||
setState({ isLoading: true, isWrapped: false, wrappedAsset: null });
|
||||
try {
|
||||
const asset = await getAttestedAssetSol(originChain, originAsset);
|
||||
if (!cancelled) {
|
||||
setState({
|
||||
isLoading: false,
|
||||
isWrapped: !!asset,
|
||||
wrappedAsset: asset,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (!cancelled) {
|
||||
// TODO: warning for this
|
||||
setState({
|
||||
isLoading: false,
|
||||
isWrapped: false,
|
||||
wrappedAsset: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setState({ isLoading: false, isWrapped: false, wrappedAsset: null });
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [checkChain, originChain, originAsset, provider]);
|
||||
return state;
|
||||
}
|
||||
|
||||
export default useWrappedAsset;
|
|
@ -1,6 +1,7 @@
|
|||
import { ethers } from "ethers";
|
||||
import { parseUnits } from "ethers/lib/utils";
|
||||
import { RootState } from ".";
|
||||
import { CHAIN_ID_SOLANA } from "../utils/consts";
|
||||
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../utils/consts";
|
||||
|
||||
/*
|
||||
* Attest
|
||||
|
@ -43,6 +44,12 @@ export const selectTransferSourceChain = (state: RootState) =>
|
|||
state.transfer.sourceChain;
|
||||
export const selectTransferSourceAsset = (state: RootState) =>
|
||||
state.transfer.sourceAsset;
|
||||
export const selectTransferIsSourceAssetWormholeWrapped = (state: RootState) =>
|
||||
state.transfer.isSourceAssetWormholeWrapped;
|
||||
export const selectTransferOriginChain = (state: RootState) =>
|
||||
state.transfer.originChain;
|
||||
export const selectTransferOriginAsset = (state: RootState) =>
|
||||
state.transfer.originAsset;
|
||||
export const selectTransferSourceParsedTokenAccount = (state: RootState) =>
|
||||
state.transfer.sourceParsedTokenAccount;
|
||||
export const selectTransferSourceBalanceString = (state: RootState) =>
|
||||
|
@ -50,6 +57,12 @@ export const selectTransferSourceBalanceString = (state: RootState) =>
|
|||
export const selectTransferAmount = (state: RootState) => state.transfer.amount;
|
||||
export const selectTransferTargetChain = (state: RootState) =>
|
||||
state.transfer.targetChain;
|
||||
export const selectTransferTargetAsset = (state: RootState) =>
|
||||
state.transfer.targetAsset;
|
||||
export const selectTransferTargetParsedTokenAccount = (state: RootState) =>
|
||||
state.transfer.targetParsedTokenAccount;
|
||||
export const selectTransferTargetBalanceString = (state: RootState) =>
|
||||
state.transfer.targetParsedTokenAccount?.uiAmountString || "";
|
||||
export const selectTransferSignedVAAHex = (state: RootState) =>
|
||||
state.transfer.signedVAAHex;
|
||||
export const selectTransferIsSending = (state: RootState) =>
|
||||
|
@ -79,7 +92,15 @@ export const selectTransferIsSourceComplete = (state: RootState) =>
|
|||
);
|
||||
// TODO: check wrapped asset exists or is native transfer
|
||||
export const selectTransferIsTargetComplete = (state: RootState) =>
|
||||
selectTransferIsSourceComplete(state) && !!state.transfer.targetChain;
|
||||
selectTransferIsSourceComplete(state) &&
|
||||
!!state.transfer.targetChain &&
|
||||
!!state.transfer.targetAsset &&
|
||||
(state.transfer.targetChain !== CHAIN_ID_ETH ||
|
||||
state.transfer.targetAsset !== ethers.constants.AddressZero); //&&
|
||||
// Associated Token Account exists
|
||||
// (state.transfer.targetChain !== CHAIN_ID_SOLANA ||
|
||||
// (!!state.transfer.targetParsedTokenAccount &&
|
||||
// !!state.transfer.targetParsedTokenAccount.publicKey));
|
||||
export const selectTransferIsSendComplete = (state: RootState) =>
|
||||
!!selectTransferSignedVAAHex(state);
|
||||
export const selectTransferShouldLockFields = (state: RootState) =>
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
ETH_TEST_TOKEN_ADDRESS,
|
||||
SOL_TEST_TOKEN_ADDRESS,
|
||||
} from "../utils/consts";
|
||||
import { WormholeWrappedInfo } from "../utils/getOriginalAsset";
|
||||
|
||||
const LAST_STEP = 3;
|
||||
|
||||
|
@ -23,9 +24,14 @@ export interface TransferState {
|
|||
activeStep: Steps;
|
||||
sourceChain: ChainId;
|
||||
sourceAsset: string;
|
||||
isSourceAssetWormholeWrapped: boolean | undefined;
|
||||
originChain: ChainId | undefined;
|
||||
originAsset: string | undefined;
|
||||
sourceParsedTokenAccount: ParsedTokenAccount | undefined;
|
||||
amount: string;
|
||||
targetChain: ChainId;
|
||||
targetAsset: string | null | undefined;
|
||||
targetParsedTokenAccount: ParsedTokenAccount | undefined;
|
||||
signedVAAHex: string | undefined;
|
||||
isSending: boolean;
|
||||
isRedeeming: boolean;
|
||||
|
@ -35,9 +41,14 @@ const initialState: TransferState = {
|
|||
activeStep: 0,
|
||||
sourceChain: CHAIN_ID_SOLANA,
|
||||
sourceAsset: SOL_TEST_TOKEN_ADDRESS,
|
||||
isSourceAssetWormholeWrapped: false,
|
||||
sourceParsedTokenAccount: undefined,
|
||||
originChain: undefined,
|
||||
originAsset: undefined,
|
||||
amount: "",
|
||||
targetChain: CHAIN_ID_ETH,
|
||||
targetAsset: undefined,
|
||||
targetParsedTokenAccount: undefined,
|
||||
signedVAAHex: undefined,
|
||||
isSending: false,
|
||||
isRedeeming: false,
|
||||
|
@ -73,6 +84,20 @@ export const transferSlice = createSlice({
|
|||
setSourceAsset: (state, action: PayloadAction<string>) => {
|
||||
state.sourceAsset = action.payload;
|
||||
},
|
||||
setSourceWormholeWrappedInfo: (
|
||||
state,
|
||||
action: PayloadAction<WormholeWrappedInfo | undefined>
|
||||
) => {
|
||||
if (action.payload) {
|
||||
state.isSourceAssetWormholeWrapped = action.payload.isWrapped;
|
||||
state.originChain = action.payload.chainId;
|
||||
state.originAsset = action.payload.assetAddress;
|
||||
} else {
|
||||
state.isSourceAssetWormholeWrapped = undefined;
|
||||
state.originChain = undefined;
|
||||
state.originAsset = undefined;
|
||||
}
|
||||
},
|
||||
setSourceParsedTokenAccount: (
|
||||
state,
|
||||
action: PayloadAction<ParsedTokenAccount | undefined>
|
||||
|
@ -97,6 +122,18 @@ export const transferSlice = createSlice({
|
|||
}
|
||||
}
|
||||
},
|
||||
setTargetAsset: (
|
||||
state,
|
||||
action: PayloadAction<string | null | undefined>
|
||||
) => {
|
||||
state.targetAsset = action.payload;
|
||||
},
|
||||
setTargetParsedTokenAccount: (
|
||||
state,
|
||||
action: PayloadAction<ParsedTokenAccount | undefined>
|
||||
) => {
|
||||
state.targetParsedTokenAccount = action.payload;
|
||||
},
|
||||
setSignedVAAHex: (state, action: PayloadAction<string>) => {
|
||||
state.signedVAAHex = action.payload;
|
||||
state.isSending = false;
|
||||
|
@ -117,9 +154,12 @@ export const {
|
|||
setStep,
|
||||
setSourceChain,
|
||||
setSourceAsset,
|
||||
setSourceWormholeWrappedInfo,
|
||||
setSourceParsedTokenAccount,
|
||||
setAmount,
|
||||
setTargetChain,
|
||||
setTargetAsset,
|
||||
setTargetParsedTokenAccount,
|
||||
setSignedVAAHex,
|
||||
setIsSending,
|
||||
setIsRedeeming,
|
||||
|
|
|
@ -56,7 +56,7 @@ export async function attestFromEth(
|
|||
const { vaaBytes } = await getSignedVAA(
|
||||
CHAIN_ID_ETH,
|
||||
emitterAddress,
|
||||
sequence
|
||||
sequence.toString()
|
||||
);
|
||||
console.log("SIGNED VAA:", vaaBytes);
|
||||
return vaaBytes;
|
||||
|
|
|
@ -57,6 +57,7 @@ export async function createWrappedOnSolana(
|
|||
signedVAA
|
||||
)
|
||||
);
|
||||
console.log(ix.keys.map((x) => x.pubkey.toString()));
|
||||
const transaction = new Transaction().add(ix);
|
||||
const { blockhash } = await connection.getRecentBlockhash();
|
||||
transaction.recentBlockhash = blockhash;
|
||||
|
|
|
@ -10,7 +10,14 @@ import {
|
|||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
} from "./consts";
|
||||
|
||||
export async function getAttestedAssetEth(
|
||||
/**
|
||||
* Returns a foreign asset address on Ethereum for a provided native chain and asset address
|
||||
* @param provider
|
||||
* @param originChain
|
||||
* @param originAsset
|
||||
* @returns
|
||||
*/
|
||||
export async function getForeignAssetEth(
|
||||
provider: ethers.providers.Web3Provider,
|
||||
originChain: ChainId,
|
||||
originAsset: string
|
||||
|
@ -33,7 +40,13 @@ export async function getAttestedAssetEth(
|
|||
}
|
||||
}
|
||||
|
||||
export async function getAttestedAssetSol(
|
||||
/**
|
||||
* Returns a foreign asset address on Solana for a provided native chain and asset address
|
||||
* @param originChain
|
||||
* @param originAsset
|
||||
* @returns
|
||||
*/
|
||||
export async function getForeignAssetSol(
|
||||
originChain: ChainId,
|
||||
originAsset: string
|
||||
) {
|
|
@ -0,0 +1,47 @@
|
|||
import { Connection, PublicKey } from "@solana/web3.js";
|
||||
import { ethers } from "ethers";
|
||||
import { Bridge__factory } from "../ethers-contracts";
|
||||
import {
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
SOLANA_HOST,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
} from "./consts";
|
||||
|
||||
/**
|
||||
* Returns whether or not an asset address on Ethereum is a wormhole wrapped asset
|
||||
* @param provider
|
||||
* @param assetAddress
|
||||
* @returns
|
||||
*/
|
||||
export async function getIsWrappedAssetEth(
|
||||
provider: ethers.providers.Web3Provider,
|
||||
assetAddress: string
|
||||
) {
|
||||
if (!assetAddress) return false;
|
||||
const tokenBridge = Bridge__factory.connect(
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
provider
|
||||
);
|
||||
return await tokenBridge.isWrappedAsset(assetAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not an asset on Solana is a wormhole wrapped asset
|
||||
* @param assetAddress
|
||||
* @returns
|
||||
*/
|
||||
export async function getIsWrappedAssetSol(mintAddress: string) {
|
||||
if (!mintAddress) return false;
|
||||
const { wrapped_meta_address } = await import("token-bridge");
|
||||
const wrappedMetaAddress = wrapped_meta_address(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
new PublicKey(mintAddress).toBytes()
|
||||
);
|
||||
const wrappedMetaAddressPK = new PublicKey(wrappedMetaAddress);
|
||||
// TODO: share connection in context?
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const wrappedMetaAccountInfo = await connection.getAccountInfo(
|
||||
wrappedMetaAddressPK
|
||||
);
|
||||
return !!wrappedMetaAccountInfo;
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
import { Connection, PublicKey } from "@solana/web3.js";
|
||||
import { ethers } from "ethers";
|
||||
import { arrayify } from "ethers/lib/utils";
|
||||
import { TokenImplementation__factory } from "../ethers-contracts";
|
||||
import { uint8ArrayToHex } from "./array";
|
||||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
SOLANA_HOST,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
} from "./consts";
|
||||
import { getIsWrappedAssetEth } from "./getIsWrappedAsset";
|
||||
|
||||
export interface WormholeWrappedInfo {
|
||||
isWrapped: boolean;
|
||||
chainId: ChainId;
|
||||
assetAddress: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a origin chain and asset address on {originChain} for a provided Wormhole wrapped address
|
||||
* @param provider
|
||||
* @param wrappedAddress
|
||||
* @returns
|
||||
*/
|
||||
export async function getOriginalAssetEth(
|
||||
provider: ethers.providers.Web3Provider,
|
||||
wrappedAddress: string
|
||||
): Promise<WormholeWrappedInfo> {
|
||||
const isWrapped = await getIsWrappedAssetEth(provider, wrappedAddress);
|
||||
if (isWrapped) {
|
||||
const token = TokenImplementation__factory.connect(
|
||||
wrappedAddress,
|
||||
provider
|
||||
);
|
||||
const chainId = (await token.chainId()) as ChainId; // origin chain
|
||||
const assetAddress = await token.nativeContract(); // origin address
|
||||
// TODO: type this?
|
||||
return {
|
||||
isWrapped: true,
|
||||
chainId,
|
||||
assetAddress: uint8ArrayToHex(arrayify(assetAddress)),
|
||||
};
|
||||
}
|
||||
return {
|
||||
isWrapped: false,
|
||||
chainId: CHAIN_ID_ETH,
|
||||
assetAddress: wrappedAddress,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getOriginalAssetSol(
|
||||
mintAddress: string
|
||||
): Promise<WormholeWrappedInfo> {
|
||||
if (mintAddress) {
|
||||
// TODO: share some of this with getIsWrappedAssetSol, like a getWrappedMetaAccountAddress or something
|
||||
const { parse_wrapped_meta, wrapped_meta_address } = await import(
|
||||
"token-bridge"
|
||||
);
|
||||
const wrappedMetaAddress = wrapped_meta_address(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
new PublicKey(mintAddress).toBytes()
|
||||
);
|
||||
const wrappedMetaAddressPK = new PublicKey(wrappedMetaAddress);
|
||||
// TODO: share connection in context?
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const wrappedMetaAccountInfo = await connection.getAccountInfo(
|
||||
wrappedMetaAddressPK
|
||||
);
|
||||
if (wrappedMetaAccountInfo) {
|
||||
const parsed = parse_wrapped_meta(wrappedMetaAccountInfo.data);
|
||||
return {
|
||||
isWrapped: true,
|
||||
chainId: parsed.chain,
|
||||
assetAddress: uint8ArrayToHex(parsed.token_address),
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
isWrapped: false,
|
||||
chainId: CHAIN_ID_SOLANA,
|
||||
assetAddress: mintAddress,
|
||||
};
|
||||
}
|
|
@ -12,6 +12,11 @@ import Wallet from "@project-serum/sol-wallet-adapter";
|
|||
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
|
||||
import { postVaa } from "./postVaa";
|
||||
import { ixFromRust } from "../sdk";
|
||||
import {
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
Token,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
|
||||
export async function redeemOnEth(
|
||||
provider: ethers.providers.Web3Provider | undefined,
|
||||
|
@ -30,7 +35,9 @@ export async function redeemOnEth(
|
|||
export async function redeemOnSolana(
|
||||
wallet: Wallet | undefined,
|
||||
payerAddress: string | undefined, //TODO: we may not need this since we have wallet
|
||||
signedVAA: Uint8Array
|
||||
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;
|
||||
console.log("completing transfer");
|
||||
|
@ -40,7 +47,8 @@ export async function redeemOnSolana(
|
|||
console.log("VAA:", signedVAA);
|
||||
// TODO: share connection in context?
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const { complete_transfer_wrapped_ix } = await import("token-bridge");
|
||||
const { complete_transfer_wrapped_ix, complete_transfer_native_ix } =
|
||||
await import("token-bridge");
|
||||
|
||||
await postVaa(
|
||||
connection,
|
||||
|
@ -50,15 +58,63 @@ export async function redeemOnSolana(
|
|||
Buffer.from(signedVAA)
|
||||
);
|
||||
console.log(Buffer.from(signedVAA).toString("hex"));
|
||||
const ix = ixFromRust(
|
||||
complete_transfer_wrapped_ix(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
signedVAA
|
||||
)
|
||||
);
|
||||
const transaction = new Transaction().add(ix);
|
||||
const ixs = [];
|
||||
if (isSolanaNative) {
|
||||
console.log("COMPLETE TRANSFER NATIVE");
|
||||
ixs.push(
|
||||
ixFromRust(
|
||||
complete_transfer_native_ix(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
signedVAA
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// TODO: we should always do this, they could buy wrapped somewhere else and transfer it back for the first time, but again, do it based on vaa
|
||||
if (mintAddress) {
|
||||
console.log("CHECK ASSOCIATED TOKEN ACCOUNT FOR", mintAddress);
|
||||
const mintPublicKey = new PublicKey(mintAddress);
|
||||
const associatedAddress = await Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
mintPublicKey,
|
||||
wallet.publicKey
|
||||
);
|
||||
const associatedAddressInfo = await connection.getAccountInfo(
|
||||
associatedAddress
|
||||
);
|
||||
console.log(
|
||||
"CREATE ASSOCIATED TOKEN ACCOUNT",
|
||||
associatedAddress.toString()
|
||||
);
|
||||
if (!associatedAddressInfo) {
|
||||
ixs.push(
|
||||
await Token.createAssociatedTokenAccountInstruction(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
mintPublicKey,
|
||||
associatedAddress,
|
||||
wallet.publicKey,
|
||||
wallet.publicKey
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
console.log("COMPLETE TRANSFER WRAPPED");
|
||||
ixs.push(
|
||||
ixFromRust(
|
||||
complete_transfer_wrapped_ix(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
signedVAA
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
const transaction = new Transaction().add(...ixs);
|
||||
const { blockhash } = await connection.getRecentBlockhash();
|
||||
transaction.recentBlockhash = blockhash;
|
||||
transaction.feePayer = new PublicKey(payerAddress);
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
TokenImplementation__factory,
|
||||
} from "../ethers-contracts";
|
||||
import { getSignedVAA, ixFromRust } from "../sdk";
|
||||
import { hexToUint8Array } from "./array";
|
||||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_ETH,
|
||||
|
@ -108,14 +109,17 @@ export async function transferFromSolana(
|
|||
amount: string,
|
||||
decimals: number,
|
||||
targetAddressStr: string | undefined,
|
||||
targetChain: ChainId
|
||||
targetChain: ChainId,
|
||||
originAddress?: string,
|
||||
originChain?: ChainId
|
||||
) {
|
||||
if (
|
||||
!wallet ||
|
||||
!wallet.publicKey ||
|
||||
!payerAddress ||
|
||||
!fromAddress ||
|
||||
!targetAddressStr
|
||||
!targetAddressStr ||
|
||||
(originChain && !originAddress)
|
||||
)
|
||||
return;
|
||||
const targetAddress = zeroPad(arrayify(targetAddressStr), 32);
|
||||
|
@ -154,8 +158,12 @@ export async function transferFromSolana(
|
|||
});
|
||||
// TODO: pass in connection
|
||||
// Add transfer instruction to transaction
|
||||
const { transfer_native_ix, approval_authority_address, emitter_address } =
|
||||
await import("token-bridge");
|
||||
const {
|
||||
transfer_native_ix,
|
||||
transfer_wrapped_ix,
|
||||
approval_authority_address,
|
||||
emitter_address,
|
||||
} = await import("token-bridge");
|
||||
const approvalIx = Token.createApproveInstruction(
|
||||
TOKEN_PROGRAM_ID,
|
||||
new PublicKey(fromAddress),
|
||||
|
@ -166,20 +174,39 @@ export async function transferFromSolana(
|
|||
);
|
||||
|
||||
let messageKey = Keypair.generate();
|
||||
const isSolanaNative =
|
||||
originChain === undefined || originChain === CHAIN_ID_SOLANA;
|
||||
console.log(isSolanaNative ? "SENDING NATIVE" : "SENDING WRAPPED");
|
||||
const ix = ixFromRust(
|
||||
transfer_native_ix(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
messageKey.publicKey.toString(),
|
||||
fromAddress,
|
||||
mintAddress,
|
||||
nonce,
|
||||
amountParsed,
|
||||
fee,
|
||||
targetAddress,
|
||||
targetChain
|
||||
)
|
||||
isSolanaNative
|
||||
? transfer_native_ix(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
messageKey.publicKey.toString(),
|
||||
fromAddress,
|
||||
mintAddress,
|
||||
nonce,
|
||||
amountParsed,
|
||||
fee,
|
||||
targetAddress,
|
||||
targetChain
|
||||
)
|
||||
: transfer_wrapped_ix(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
messageKey.publicKey.toString(),
|
||||
fromAddress,
|
||||
payerAddress,
|
||||
originChain as number, // checked by isSolanaNative
|
||||
zeroPad(hexToUint8Array(originAddress as string), 32), // checked by initial check
|
||||
nonce,
|
||||
amountParsed,
|
||||
fee,
|
||||
targetAddress,
|
||||
targetChain
|
||||
)
|
||||
);
|
||||
const transaction = new Transaction().add(transferIx, approvalIx, ix);
|
||||
const { blockhash } = await connection.getRecentBlockhash();
|
||||
|
|
Loading…
Reference in New Issue