bridge_ui: vaa-based recovery

Change-Id: I8604258b7ba5642eac60eb46393a689e718bd757
This commit is contained in:
Evan Gray 2021-08-29 17:03:36 -04:00
parent 82280559bd
commit 98e05e39cb
14 changed files with 535 additions and 196 deletions

View File

@ -12,6 +12,7 @@
"@certusone/wormhole-sdk": "file:..\\sdk\\js",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.60",
"@metamask/detect-provider": "^1.2.0",
"@reduxjs/toolkit": "^1.6.1",
"@solana/spl-token": "^0.1.6",
@ -5001,6 +5002,32 @@
}
}
},
"node_modules/@material-ui/lab": {
"version": "4.0.0-alpha.60",
"resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.60.tgz",
"integrity": "sha512-fadlYsPJF+0fx2lRuyqAuJj7hAS1tLDdIEEdov5jlrpb5pp4b+mRDUqQTUxi4inRZHS1bEXpU8QWUhO6xX88aA==",
"dependencies": {
"@babel/runtime": "^7.4.4",
"@material-ui/utils": "^4.11.2",
"clsx": "^1.0.4",
"prop-types": "^15.7.2",
"react-is": "^16.8.0 || ^17.0.0"
},
"engines": {
"node": ">=8.0.0"
},
"peerDependencies": {
"@material-ui/core": "^4.12.1",
"@types/react": "^16.8.6 || ^17.0.0",
"react": "^16.8.0 || ^17.0.0",
"react-dom": "^16.8.0 || ^17.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@material-ui/styles": {
"version": "4.11.4",
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.4.tgz",
@ -43348,6 +43375,18 @@
"@babel/runtime": "^7.4.4"
}
},
"@material-ui/lab": {
"version": "4.0.0-alpha.60",
"resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.60.tgz",
"integrity": "sha512-fadlYsPJF+0fx2lRuyqAuJj7hAS1tLDdIEEdov5jlrpb5pp4b+mRDUqQTUxi4inRZHS1bEXpU8QWUhO6xX88aA==",
"requires": {
"@babel/runtime": "^7.4.4",
"@material-ui/utils": "^4.11.2",
"clsx": "^1.0.4",
"prop-types": "^15.7.2",
"react-is": "^16.8.0 || ^17.0.0"
}
},
"@material-ui/styles": {
"version": "4.11.4",
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.4.tgz",

View File

@ -6,6 +6,7 @@
"@certusone/wormhole-sdk": "file:..\\sdk\\js",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.60",
"@metamask/detect-provider": "^1.2.0",
"@reduxjs/toolkit": "^1.6.1",
"@solana/spl-token": "^0.1.6",

View File

@ -1,42 +1,16 @@
import { Button, CircularProgress, makeStyles } from "@material-ui/core";
import { useHandleCreateWrapped } from "../../hooks/useHandleCreateWrapped";
const useStyles = makeStyles((theme) => ({
transferButton: {
marginTop: theme.spacing(2),
textTransform: "none",
width: "100%",
},
}));
import ButtonWithLoader from "../ButtonWithLoader";
function Create() {
const classes = useStyles();
const { handleClick, disabled, showLoader } = useHandleCreateWrapped();
return (
<div style={{ position: "relative" }}>
<Button
color="primary"
variant="contained"
className={classes.transferButton}
disabled={disabled}
onClick={handleClick}
>
Create
</Button>
{showLoader ? (
<CircularProgress
size={24}
color="inherit"
style={{
position: "absolute",
bottom: 0,
left: "50%",
marginLeft: -12,
marginBottom: 6,
}}
/>
) : null}
</div>
<ButtonWithLoader
disabled={disabled}
onClick={handleClick}
showLoader={showLoader}
>
Create
</ButtonWithLoader>
);
}

View File

@ -1,44 +1,16 @@
import { Button, CircularProgress, makeStyles } from "@material-ui/core";
import { useHandleAttest } from "../../hooks/useHandleAttest";
const useStyles = makeStyles((theme) => ({
transferButton: {
marginTop: theme.spacing(2),
textTransform: "none",
width: "100%",
},
}));
import ButtonWithLoader from "../ButtonWithLoader";
function Send() {
const classes = useStyles();
const { handleClick, disabled, showLoader } = useHandleAttest();
return (
<>
<div style={{ position: "relative" }}>
<Button
color="primary"
variant="contained"
className={classes.transferButton}
onClick={handleClick}
disabled={disabled}
>
Attest
</Button>
{showLoader ? (
<CircularProgress
size={24}
color="inherit"
style={{
position: "absolute",
bottom: 0,
left: "50%",
marginLeft: -12,
marginBottom: 6,
}}
/>
) : null}
</div>
</>
<ButtonWithLoader
disabled={disabled}
onClick={handleClick}
showLoader={showLoader}
>
Attest
</ButtonWithLoader>
);
}

View File

@ -0,0 +1,54 @@
import { Button, CircularProgress, makeStyles } from "@material-ui/core";
import { ReactChild } from "react";
const useStyles = makeStyles((theme) => ({
root: {
position: "relative",
},
button: {
marginTop: theme.spacing(2),
textTransform: "none",
width: "100%",
},
loader: {
position: "absolute",
bottom: 0,
left: "50%",
marginLeft: -12,
marginBottom: 6,
},
}));
export default function ButtonWithLoader({
disabled,
onClick,
showLoader,
children,
}: {
disabled: boolean;
onClick: () => void;
showLoader: boolean;
children: ReactChild;
}) {
const classes = useStyles();
return (
<div className={classes.root}>
<Button
color="primary"
variant="contained"
className={classes.button}
disabled={disabled}
onClick={onClick}
>
{children}
</Button>
{showLoader ? (
<CircularProgress
size={24}
color="inherit"
className={classes.loader}
/>
) : null}
</div>
);
}

View File

@ -0,0 +1,125 @@
import { Typography } from "@material-ui/core";
import {
ASSOCIATED_TOKEN_PROGRAM_ID,
Token,
TOKEN_PROGRAM_ID,
} from "@solana/spl-token";
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
import { useCallback, useEffect, useState } from "react";
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
import { SOLANA_HOST } from "../utils/consts";
import { signSendAndConfirm } from "../utils/solana";
import ButtonWithLoader from "./ButtonWithLoader";
export default function SolanaCreateAssociatedAddress({
mintAddress,
readableTargetAddress,
}: {
mintAddress: string;
readableTargetAddress: string;
}) {
const [isCreating, setIsCreating] = useState(false);
const [associatedAccountExists, setAssociatedAccountExists] = useState(true); // for now, assume it exists until we confirm it doesn't
const solanaWallet = useSolanaWallet();
const solPK = solanaWallet?.publicKey;
useEffect(() => {
setAssociatedAccountExists(true);
if (!mintAddress || !readableTargetAddress || !solPK) return;
let cancelled = false;
(async () => {
// TODO: share connection in context?
const connection = new Connection(SOLANA_HOST, "confirmed");
const mintPublicKey = new PublicKey(mintAddress);
const payerPublicKey = new PublicKey(solPK); // currently assumes the wallet is the owner
const associatedAddress = await Token.getAssociatedTokenAddress(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
mintPublicKey,
payerPublicKey
);
const match = associatedAddress.toString() === readableTargetAddress;
if (match) {
const associatedAddressInfo = await connection.getAccountInfo(
associatedAddress
);
if (!associatedAddressInfo) {
if (!cancelled) {
setAssociatedAccountExists(false);
}
}
}
})();
return () => {
cancelled = true;
};
}, [mintAddress, readableTargetAddress, solPK]);
const handleClick = useCallback(() => {
if (
associatedAccountExists ||
!mintAddress ||
!readableTargetAddress ||
!solPK
)
return;
(async () => {
// TODO: share connection in context?
const connection = new Connection(SOLANA_HOST, "confirmed");
const mintPublicKey = new PublicKey(mintAddress);
const payerPublicKey = new PublicKey(solPK); // currently assumes the wallet is the owner
const associatedAddress = await Token.getAssociatedTokenAddress(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
mintPublicKey,
payerPublicKey
);
const match = associatedAddress.toString() === readableTargetAddress;
if (match) {
const associatedAddressInfo = await connection.getAccountInfo(
associatedAddress
);
if (!associatedAddressInfo) {
setIsCreating(true);
const transaction = new Transaction().add(
await Token.createAssociatedTokenAccountInstruction(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
mintPublicKey,
associatedAddress,
payerPublicKey, // owner
payerPublicKey // payer
)
);
const { blockhash } = await connection.getRecentBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = new PublicKey(payerPublicKey);
await signSendAndConfirm(solanaWallet, connection, transaction);
setIsCreating(false);
setAssociatedAccountExists(true);
}
}
})();
}, [
associatedAccountExists,
mintAddress,
solPK,
readableTargetAddress,
solanaWallet,
]);
if (associatedAccountExists) return null;
return (
<>
<Typography color="error" variant="body2">
This associated token account doesn't exist.
</Typography>
<ButtonWithLoader
disabled={
!mintAddress || !readableTargetAddress || !solPK || isCreating
}
onClick={handleClick}
showLoader={isCreating}
>
Create Associated Token Account
</ButtonWithLoader>
</>
);
}

View File

@ -0,0 +1,256 @@
import {
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Divider,
Fab,
makeStyles,
TextField,
} from "@material-ui/core";
import { Alert } from "@material-ui/lab";
import { Restore } from "@material-ui/icons";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
setSignedVAAHex,
setStep,
setTargetChain,
} from "../../store/transferSlice";
import { selectTransferSignedVAAHex } from "../../store/selectors";
import { hexToNativeString, hexToUint8Array } from "../../utils/array";
import { ChainId } from "@certusone/wormhole-sdk";
import { BigNumber } from "ethers";
const useStyles = makeStyles((theme) => ({
fab: {
position: "fixed",
bottom: theme.spacing(2),
right: theme.spacing(2),
},
}));
// 0 u256 amount
// 32 [u8; 32] token_address
// 64 u16 token_chain
// 66 [u8; 32] recipient
// 98 u16 recipient_chain
// 100 u256 fee
// TODO: move to wasm / sdk, share with solana
const parsePayload = (arr: Buffer) => ({
amount: BigNumber.from(arr.slice(1, 1 + 32)).toBigInt(),
originAddress: arr.slice(33, 33 + 32).toString("hex"), // TODO: is this origin or source?
originChain: arr.readUInt16BE(65) as ChainId, // TODO: is this origin or source?
targetAddress: arr.slice(67, 67 + 32).toString("hex"),
targetChain: arr.readUInt16BE(99) as ChainId,
});
function RecoveryDialogContent({ onClose }: { onClose: () => void }) {
const dispatch = useDispatch();
const currentSignedVAA = useSelector(selectTransferSignedVAAHex);
const [recoverySignedVAA, setRecoverySignedVAA] = useState(currentSignedVAA);
const [recoveryParsedVAA, setRecoveryParsedVAA] = useState<any>(null);
const handleSignedVAAChange = useCallback((event) => {
setRecoverySignedVAA(event.target.value);
}, []);
useEffect(() => {
let cancelled = false;
if (recoverySignedVAA) {
(async () => {
try {
const { parse_vaa } = await import(
"@certusone/wormhole-sdk/lib/solana/core/bridge"
);
const parsedVAA = parse_vaa(hexToUint8Array(recoverySignedVAA));
if (!cancelled) {
setRecoveryParsedVAA(parsedVAA);
}
} catch (e) {
console.log(e);
if (!cancelled) {
setRecoveryParsedVAA(null);
}
}
})();
}
return () => {
cancelled = true;
};
}, [recoverySignedVAA]);
const parsedPayload = useMemo(
() =>
recoveryParsedVAA?.payload
? parsePayload(Buffer.from(new Uint8Array(recoveryParsedVAA.payload)))
: null,
[recoveryParsedVAA]
);
const parsedPayloadTargetChain = parsedPayload?.targetChain;
const enableRecovery = recoverySignedVAA && parsedPayloadTargetChain;
const handleRecoverClick = useCallback(() => {
if (enableRecovery && recoverySignedVAA && parsedPayloadTargetChain) {
// TODO: make recovery reducer
dispatch(setSignedVAAHex(recoverySignedVAA));
dispatch(setTargetChain(parsedPayloadTargetChain));
dispatch(setStep(3));
onClose();
}
}, [
dispatch,
enableRecovery,
recoverySignedVAA,
parsedPayloadTargetChain,
onClose,
]);
return (
<>
<DialogContent>
<Alert severity="info">
If you have sent your tokens but have not redeemed them, you may paste
the signed VAA here to resume from the redeem step.
</Alert>
<TextField
label="Signed VAA (Hex)"
value={recoverySignedVAA || ""}
onChange={handleSignedVAAChange}
fullWidth
margin="normal"
/>
<Box my={4}>
<Divider />
</Box>
<TextField
label="Emitter Chain"
disabled
value={recoveryParsedVAA?.emitter_chain || ""}
fullWidth
margin="normal"
/>
<TextField
label="Emitter Address"
disabled
value={
(recoveryParsedVAA &&
hexToNativeString(
recoveryParsedVAA.emitter_address,
recoveryParsedVAA.emitter_chain
)) ||
""
}
fullWidth
margin="normal"
/>
<TextField
label="Sequence"
disabled
value={recoveryParsedVAA?.sequence || ""}
fullWidth
margin="normal"
/>
<TextField
label="Timestamp"
disabled
value={
(recoveryParsedVAA &&
new Date(recoveryParsedVAA.timestamp * 1000).toLocaleString()) ||
""
}
fullWidth
margin="normal"
/>
<Box my={4}>
<Divider />
</Box>
<TextField
label="Origin Chain"
disabled
value={parsedPayload?.originChain.toString() || ""}
fullWidth
margin="normal"
/>
<TextField
label="Origin Token Address"
disabled
value={
(parsedPayload &&
hexToNativeString(
parsedPayload.originAddress,
parsedPayload.originChain
)) ||
""
}
fullWidth
margin="normal"
/>
<TextField
label="Target Chain"
disabled
value={parsedPayload?.targetChain.toString() || ""}
fullWidth
margin="normal"
/>
<TextField
label="Target Address"
disabled
value={
(parsedPayload &&
hexToNativeString(
parsedPayload.targetAddress,
parsedPayload.targetChain
)) ||
""
}
fullWidth
margin="normal"
/>
<TextField
label="Amount"
disabled
value={parsedPayload?.amount.toString() || ""}
fullWidth
margin="normal"
/>
<Box my={4}>
<Divider />
</Box>
</DialogContent>
<DialogActions>
<Button onClick={onClose} variant="contained" color="default">
Cancel
</Button>
<Button
onClick={handleRecoverClick}
variant="contained"
color="primary"
disabled={!enableRecovery}
>
Recover
</Button>
</DialogActions>
</>
);
}
export default function Recovery() {
const classes = useStyles();
const [open, setOpen] = useState(false);
const handleOpenClick = useCallback(() => {
setOpen(true);
}, []);
const handleCloseClick = useCallback(() => {
setOpen(false);
}, []);
return (
<>
<Fab className={classes.fab} onClick={handleOpenClick}>
<Restore />
</Fab>
<Dialog open={open} onClose={handleCloseClick} maxWidth="md" fullWidth>
<DialogTitle>Recovery</DialogTitle>
<RecoveryDialogContent onClose={handleCloseClick} />
</Dialog>
</>
);
}

View File

@ -1,42 +1,23 @@
import { Button, CircularProgress, makeStyles } from "@material-ui/core";
import { useSelector } from "react-redux";
import { useHandleRedeem } from "../../hooks/useHandleRedeem";
const useStyles = makeStyles((theme) => ({
transferButton: {
marginTop: theme.spacing(2),
textTransform: "none",
width: "100%",
},
}));
import { selectTransferTargetChain } from "../../store/selectors";
import ButtonWithLoader from "../ButtonWithLoader";
import KeyAndBalance from "../KeyAndBalance";
function Redeem() {
const classes = useStyles();
const { handleClick, disabled, showLoader } = useHandleRedeem();
const targetChain = useSelector(selectTransferTargetChain);
return (
<div style={{ position: "relative" }}>
<Button
color="primary"
variant="contained"
className={classes.transferButton}
<>
<KeyAndBalance chainId={targetChain} />
<ButtonWithLoader
disabled={disabled}
onClick={handleClick}
showLoader={showLoader}
>
Redeem
</Button>
{showLoader ? (
<CircularProgress
size={24}
color="inherit"
style={{
position: "absolute",
bottom: 0,
left: "50%",
marginLeft: -12,
marginBottom: 6,
}}
/>
) : null}
</div>
</ButtonWithLoader>
</>
);
}

View File

@ -1,43 +1,22 @@
import { Button, CircularProgress, makeStyles } from "@material-ui/core";
import { useSelector } from "react-redux";
import { useHandleTransfer } from "../../hooks/useHandleTransfer";
const useStyles = makeStyles((theme) => ({
transferButton: {
marginTop: theme.spacing(2),
textTransform: "none",
width: "100%",
},
}));
import { selectTransferSourceChain } from "../../store/selectors";
import ButtonWithLoader from "../ButtonWithLoader";
import KeyAndBalance from "../KeyAndBalance";
function Send() {
const classes = useStyles();
const { handleClick, disabled, showLoader } = useHandleTransfer();
const sourceChain = useSelector(selectTransferSourceChain);
return (
<>
<div style={{ position: "relative" }}>
<Button
color="primary"
variant="contained"
className={classes.transferButton}
onClick={handleClick}
disabled={disabled}
>
Transfer
</Button>
{showLoader ? (
<CircularProgress
size={24}
color="inherit"
style={{
position: "absolute",
bottom: 0,
left: "50%",
marginLeft: -12,
marginBottom: 6,
}}
/>
) : null}
</div>
<KeyAndBalance chainId={sourceChain} />
<ButtonWithLoader
disabled={disabled}
onClick={handleClick}
showLoader={showLoader}
>
Transfer
</ButtonWithLoader>
</>
);
}

View File

@ -1,6 +1,7 @@
import { Button, makeStyles, MenuItem, TextField } from "@material-ui/core";
import { useCallback, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
import useSyncTargetAddress from "../../hooks/useSyncTargetAddress";
import {
selectTransferIsTargetComplete,
@ -15,6 +16,7 @@ import { incrementStep, setTargetChain } from "../../store/transferSlice";
import { hexToNativeString } from "../../utils/array";
import { CHAINS } from "../../utils/consts";
import KeyAndBalance from "../KeyAndBalance";
import SolanaCreateAssociatedAddress from "../SolanaCreateAssociatedAddress";
const useStyles = makeStyles((theme) => ({
transferField: {
@ -74,6 +76,12 @@ function Target() {
value={readableTargetAddress}
disabled={true}
/>
{targetChain === CHAIN_ID_SOLANA && targetAsset ? (
<SolanaCreateAssociatedAddress
mintAddress={targetAsset}
readableTargetAddress={readableTargetAddress}
/>
) : null}
<TextField
label="Asset"
fullWidth

View File

@ -14,6 +14,7 @@ import {
selectTransferSignedVAAHex,
} from "../../store/selectors";
import { setStep } from "../../store/transferSlice";
import Recovery from "./Recovery";
import Redeem from "./Redeem";
import Send from "./Send";
import Source from "./Source";
@ -73,6 +74,7 @@ function Transfer() {
</StepContent>
</Step>
</Stepper>
<Recovery />
</Container>
);
}

View File

@ -10,7 +10,6 @@ export const SolanaWalletProvider: FC = (props) => {
// @solana/wallet-adapter-wallets includes all the adapters but supports tree shaking --
// Only the wallets you want to instantiate here will be compiled into your application
const wallets = useMemo(() => {
console.log("running wallets memo again");
return [
getPhantomWallet(),
// getSolflareWallet(),

View File

@ -23,9 +23,6 @@ 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";
@ -61,9 +58,7 @@ async function solana(
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
signedVAA: Uint8Array
) {
dispatch(setIsRedeeming(true));
try {
@ -82,9 +77,7 @@ async function solana(
SOL_BRIDGE_ADDRESS,
SOL_TOKEN_BRIDGE_ADDRESS,
payerAddress,
signedVAA,
isSolanaNative,
mintAddress
signedVAA
);
await signSendAndConfirm(wallet, connection, transaction);
dispatch(reset());
@ -129,12 +122,7 @@ async function terra(
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();
@ -155,9 +143,7 @@ export function useHandleRedeem() {
enqueueSnackbar,
solanaWallet,
solPK.toString(),
signedVAA,
!!isSourceAssetWormholeWrapped && originChain === CHAIN_ID_SOLANA,
targetAsset || undefined
signedVAA
);
} else if (targetChain === CHAIN_ID_TERRA && !!terraWallet && signedVAA) {
terra(dispatch, enqueueSnackbar, terraWallet, signedVAA);
@ -175,9 +161,6 @@ export function useHandleRedeem() {
solanaWallet,
solPK,
terraWallet,
isSourceAssetWormholeWrapped,
originChain,
targetAsset,
]);
return useMemo(
() => ({

View File

@ -1,12 +1,8 @@
import {
ASSOCIATED_TOKEN_PROGRAM_ID,
Token,
TOKEN_PROGRAM_ID,
} from "@solana/spl-token";
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
import { ethers } from "ethers";
import { Bridge__factory } from "../ethers-contracts";
import { ixFromRust } from "../solana";
import { CHAIN_ID_SOLANA } from "../utils";
export async function redeemOnEth(
tokenBridgeAddress: string,
@ -24,16 +20,13 @@ export async function redeemOnSolana(
bridgeAddress: string,
tokenBridgeAddress: string,
payerAddress: string,
signedVAA: Uint8Array,
isSolanaNative: boolean,
mintAddress?: string // TODO: read the signedVAA and create the account if it doesn't exist
signedVAA: Uint8Array
) {
// TODO: this gets the target account off the vaa, but is there a way to do this via wasm?
// also, would this always be safe to do?
// should we rely on this function to create accounts at all?
// const { parse_vaa } = await import("../solana/core/bridge")
// const parsedVAA = parse_vaa(signedVAA);
// const targetAddress = new PublicKey(parsedVAA.payload.slice(67, 67 + 32)).toString()
const { parse_vaa } = await import("../solana/core/bridge");
const parsedVAA = parse_vaa(signedVAA);
const isSolanaNative =
Buffer.from(new Uint8Array(parsedVAA.payload)).readUInt16BE(65) ===
CHAIN_ID_SOLANA;
const { complete_transfer_wrapped_ix, complete_transfer_native_ix } =
await import("../solana/token/token_bridge");
const ixs = [];
@ -49,33 +42,6 @@ export async function redeemOnSolana(
)
);
} 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) {
const mintPublicKey = new PublicKey(mintAddress);
// TODO: re: todo above, this should be swapped for the address from the vaa (may not be the same as the payer)
const payerPublicKey = new PublicKey(payerAddress);
const associatedAddress = await Token.getAssociatedTokenAddress(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
mintPublicKey,
payerPublicKey
);
const associatedAddressInfo = await connection.getAccountInfo(
associatedAddress
);
if (!associatedAddressInfo) {
ixs.push(
await Token.createAssociatedTokenAccountInstruction(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
mintPublicKey,
associatedAddress,
payerPublicKey, // owner
payerPublicKey // payer
)
);
}
}
ixs.push(
ixFromRust(
complete_transfer_wrapped_ix(