bridge_ui: add createAssociatedTokenAccount to redeem step in transfer
Change-Id: I3ccb1895613e7b2bb6fa8c1ddb08c138c14c0d0d
This commit is contained in:
parent
0f8eb3b933
commit
51cbec55b8
|
@ -38,16 +38,8 @@ import { useDispatch } from "react-redux";
|
||||||
import { useHistory } from "react-router";
|
import { useHistory } from "react-router";
|
||||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||||
import { COLORS } from "../muiTheme";
|
import { COLORS } from "../muiTheme";
|
||||||
import {
|
import { setRecoveryVaa as setRecoveryNFTVaa } from "../store/nftSlice";
|
||||||
setSignedVAAHex as setNFTSignedVAAHex,
|
import { setRecoveryVaa } from "../store/transferSlice";
|
||||||
setStep as setNFTStep,
|
|
||||||
setTargetChain as setNFTTargetChain,
|
|
||||||
} from "../store/nftSlice";
|
|
||||||
import {
|
|
||||||
setSignedVAAHex,
|
|
||||||
setStep,
|
|
||||||
setTargetChain,
|
|
||||||
} from "../store/transferSlice";
|
|
||||||
import {
|
import {
|
||||||
CHAINS,
|
CHAINS,
|
||||||
CHAINS_WITH_NFT_SUPPORT,
|
CHAINS_WITH_NFT_SUPPORT,
|
||||||
|
@ -311,14 +303,30 @@ export default function Recovery() {
|
||||||
if (enableRecovery && recoverySignedVAA && parsedPayloadTargetChain) {
|
if (enableRecovery && recoverySignedVAA && parsedPayloadTargetChain) {
|
||||||
// TODO: make recovery reducer
|
// TODO: make recovery reducer
|
||||||
if (isNFT) {
|
if (isNFT) {
|
||||||
dispatch(setNFTSignedVAAHex(recoverySignedVAA));
|
dispatch(
|
||||||
dispatch(setNFTTargetChain(parsedPayloadTargetChain));
|
setRecoveryNFTVaa({
|
||||||
dispatch(setNFTStep(3));
|
vaa: recoverySignedVAA,
|
||||||
|
parsedPayload: {
|
||||||
|
targetChain: parsedPayload.targetChain,
|
||||||
|
targetAddress: parsedPayload.targetAddress,
|
||||||
|
originChain: parsedPayload.originChain,
|
||||||
|
originAddress: parsedPayload.originAddress,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
push("/nft");
|
push("/nft");
|
||||||
} else {
|
} else {
|
||||||
dispatch(setSignedVAAHex(recoverySignedVAA));
|
dispatch(
|
||||||
dispatch(setTargetChain(parsedPayloadTargetChain));
|
setRecoveryVaa({
|
||||||
dispatch(setStep(3));
|
vaa: recoverySignedVAA,
|
||||||
|
parsedPayload: {
|
||||||
|
targetChain: parsedPayload.targetChain,
|
||||||
|
targetAddress: parsedPayload.targetAddress,
|
||||||
|
originChain: parsedPayload.originChain,
|
||||||
|
originAddress: parsedPayload.originAddress,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
push("/transfer");
|
push("/transfer");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -327,6 +335,7 @@ export default function Recovery() {
|
||||||
enableRecovery,
|
enableRecovery,
|
||||||
recoverySignedVAA,
|
recoverySignedVAA,
|
||||||
parsedPayloadTargetChain,
|
parsedPayloadTargetChain,
|
||||||
|
parsedPayload,
|
||||||
isNFT,
|
isNFT,
|
||||||
push,
|
push,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
import { ChainId, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
|
import {
|
||||||
|
ChainId,
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
getForeignAssetSolana,
|
||||||
|
hexToNativeString,
|
||||||
|
hexToUint8Array,
|
||||||
|
} from "@certusone/wormhole-sdk";
|
||||||
import { Typography } from "@material-ui/core";
|
import { Typography } from "@material-ui/core";
|
||||||
import {
|
import {
|
||||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||||
|
@ -7,10 +13,17 @@ import {
|
||||||
} from "@solana/spl-token";
|
} from "@solana/spl-token";
|
||||||
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
|
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
|
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
|
||||||
import { SOLANA_HOST } from "../utils/consts";
|
import {
|
||||||
|
selectTransferOriginAsset,
|
||||||
|
selectTransferOriginChain,
|
||||||
|
selectTransferTargetAddressHex,
|
||||||
|
} from "../store/selectors";
|
||||||
|
import { SOLANA_HOST, SOL_TOKEN_BRIDGE_ADDRESS } from "../utils/consts";
|
||||||
import { signSendAndConfirm } from "../utils/solana";
|
import { signSendAndConfirm } from "../utils/solana";
|
||||||
import ButtonWithLoader from "./ButtonWithLoader";
|
import ButtonWithLoader from "./ButtonWithLoader";
|
||||||
|
import SmartAddress from "./SmartAddress";
|
||||||
|
|
||||||
export function useAssociatedAccountExistsState(
|
export function useAssociatedAccountExistsState(
|
||||||
targetChain: ChainId,
|
targetChain: ChainId,
|
||||||
|
@ -117,6 +130,8 @@ export default function SolanaCreateAssociatedAddress({
|
||||||
await signSendAndConfirm(solanaWallet, connection, transaction);
|
await signSendAndConfirm(solanaWallet, connection, transaction);
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
setAssociatedAccountExists(true);
|
setAssociatedAccountExists(true);
|
||||||
|
} else {
|
||||||
|
console.log("Account already exists.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -146,3 +161,77 @@ export default function SolanaCreateAssociatedAddress({
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SolanaCreateAssociatedAddressAlternate() {
|
||||||
|
const originChain = useSelector(selectTransferOriginChain);
|
||||||
|
const originAsset = useSelector(selectTransferOriginAsset);
|
||||||
|
const addressHex = useSelector(selectTransferTargetAddressHex);
|
||||||
|
const base58TargetAddress = useMemo(
|
||||||
|
() => hexToNativeString(addressHex, CHAIN_ID_SOLANA) || "",
|
||||||
|
[addressHex]
|
||||||
|
);
|
||||||
|
const base58OriginAddress = useMemo(
|
||||||
|
() => hexToNativeString(originAsset, CHAIN_ID_SOLANA) || "",
|
||||||
|
[originAsset]
|
||||||
|
);
|
||||||
|
const connection = useMemo(() => new Connection(SOLANA_HOST), []);
|
||||||
|
const [targetAsset, setTargetAsset] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
if (!(originChain && originAsset && addressHex && base58TargetAddress)) {
|
||||||
|
setTargetAsset(null);
|
||||||
|
} else if (originChain === CHAIN_ID_SOLANA && base58OriginAddress) {
|
||||||
|
setTargetAsset(base58OriginAddress);
|
||||||
|
} else {
|
||||||
|
getForeignAssetSolana(
|
||||||
|
connection,
|
||||||
|
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
originChain,
|
||||||
|
hexToUint8Array(originAsset)
|
||||||
|
).then((result) => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setTargetAsset(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
originChain,
|
||||||
|
originAsset,
|
||||||
|
addressHex,
|
||||||
|
base58TargetAddress,
|
||||||
|
connection,
|
||||||
|
base58OriginAddress,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { associatedAccountExists, setAssociatedAccountExists } =
|
||||||
|
useAssociatedAccountExistsState(
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
targetAsset,
|
||||||
|
base58TargetAddress
|
||||||
|
);
|
||||||
|
|
||||||
|
return targetAsset && !associatedAccountExists ? (
|
||||||
|
<div style={{ textAlign: "center" }}>
|
||||||
|
<Typography variant="subtitle2">Recipient Address:</Typography>
|
||||||
|
<Typography component="div">
|
||||||
|
<SmartAddress
|
||||||
|
chainId={CHAIN_ID_SOLANA}
|
||||||
|
address={base58TargetAddress}
|
||||||
|
variant="h6"
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<SolanaCreateAssociatedAddress
|
||||||
|
mintAddress={targetAsset}
|
||||||
|
readableTargetAddress={base58TargetAddress}
|
||||||
|
associatedAccountExists={associatedAccountExists}
|
||||||
|
setAssociatedAccountExists={setAssociatedAccountExists}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
import { WBNB_ADDRESS, WETH_ADDRESS } from "../../utils/consts";
|
import { WBNB_ADDRESS, WETH_ADDRESS } from "../../utils/consts";
|
||||||
import ButtonWithLoader from "../ButtonWithLoader";
|
import ButtonWithLoader from "../ButtonWithLoader";
|
||||||
import KeyAndBalance from "../KeyAndBalance";
|
import KeyAndBalance from "../KeyAndBalance";
|
||||||
|
import { SolanaCreateAssociatedAddressAlternate } from "../SolanaCreateAssociatedAddress";
|
||||||
import StepDescription from "../StepDescription";
|
import StepDescription from "../StepDescription";
|
||||||
import WaitingForWalletMessage from "./WaitingForWalletMessage";
|
import WaitingForWalletMessage from "./WaitingForWalletMessage";
|
||||||
|
|
||||||
|
@ -60,8 +61,12 @@ function Redeem() {
|
||||||
label="Automatically unwrap to native currency"
|
label="Automatically unwrap to native currency"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{targetChain === CHAIN_ID_SOLANA ? (
|
||||||
|
<SolanaCreateAssociatedAddressAlternate />
|
||||||
|
) : null}
|
||||||
|
|
||||||
<ButtonWithLoader
|
<ButtonWithLoader
|
||||||
|
//TODO disable when the associated token account is confirmed to not exist
|
||||||
disabled={!isReady || disabled}
|
disabled={!isReady || disabled}
|
||||||
onClick={
|
onClick={
|
||||||
isNativeEligible && useNativeRedeem ? handleNativeClick : handleClick
|
isNativeEligible && useNativeRedeem ? handleNativeClick : handleClick
|
||||||
|
|
|
@ -19,9 +19,11 @@ import { useDispatch, useSelector } from "react-redux";
|
||||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||||
import { setSourceWormholeWrappedInfo as setNFTSourceWormholeWrappedInfo } from "../store/nftSlice";
|
import { setSourceWormholeWrappedInfo as setNFTSourceWormholeWrappedInfo } from "../store/nftSlice";
|
||||||
import {
|
import {
|
||||||
|
selectNFTIsRecovery,
|
||||||
selectNFTSourceAsset,
|
selectNFTSourceAsset,
|
||||||
selectNFTSourceChain,
|
selectNFTSourceChain,
|
||||||
selectNFTSourceParsedTokenAccount,
|
selectNFTSourceParsedTokenAccount,
|
||||||
|
selectTransferIsRecovery,
|
||||||
selectTransferSourceAsset,
|
selectTransferSourceAsset,
|
||||||
selectTransferSourceChain,
|
selectTransferSourceChain,
|
||||||
} from "../store/selectors";
|
} from "../store/selectors";
|
||||||
|
@ -69,7 +71,13 @@ function useCheckIfWormholeWrapped(nft?: boolean) {
|
||||||
? setNFTSourceWormholeWrappedInfo
|
? setNFTSourceWormholeWrappedInfo
|
||||||
: setTransferSourceWormholeWrappedInfo;
|
: setTransferSourceWormholeWrappedInfo;
|
||||||
const { provider } = useEthereumProvider();
|
const { provider } = useEthereumProvider();
|
||||||
|
const isRecovery = useSelector(
|
||||||
|
nft ? selectNFTIsRecovery : selectTransferIsRecovery
|
||||||
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (isRecovery) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// TODO: loading state, error state
|
// TODO: loading state, error state
|
||||||
dispatch(setSourceWormholeWrappedInfo(undefined));
|
dispatch(setSourceWormholeWrappedInfo(undefined));
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
@ -133,6 +141,7 @@ function useCheckIfWormholeWrapped(nft?: boolean) {
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
dispatch,
|
dispatch,
|
||||||
|
isRecovery,
|
||||||
sourceChain,
|
sourceChain,
|
||||||
sourceAsset,
|
sourceAsset,
|
||||||
provider,
|
provider,
|
||||||
|
|
|
@ -20,11 +20,13 @@ import { useDispatch, useSelector } from "react-redux";
|
||||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||||
import { setTargetAsset as setNFTTargetAsset } from "../store/nftSlice";
|
import { setTargetAsset as setNFTTargetAsset } from "../store/nftSlice";
|
||||||
import {
|
import {
|
||||||
|
selectNFTIsRecovery,
|
||||||
selectNFTIsSourceAssetWormholeWrapped,
|
selectNFTIsSourceAssetWormholeWrapped,
|
||||||
selectNFTOriginAsset,
|
selectNFTOriginAsset,
|
||||||
selectNFTOriginChain,
|
selectNFTOriginChain,
|
||||||
selectNFTOriginTokenId,
|
selectNFTOriginTokenId,
|
||||||
selectNFTTargetChain,
|
selectNFTTargetChain,
|
||||||
|
selectTransferIsRecovery,
|
||||||
selectTransferIsSourceAssetWormholeWrapped,
|
selectTransferIsSourceAssetWormholeWrapped,
|
||||||
selectTransferOriginAsset,
|
selectTransferOriginAsset,
|
||||||
selectTransferOriginChain,
|
selectTransferOriginChain,
|
||||||
|
@ -65,7 +67,13 @@ function useFetchTargetAsset(nft?: boolean) {
|
||||||
const { provider, chainId: evmChainId } = useEthereumProvider();
|
const { provider, chainId: evmChainId } = useEthereumProvider();
|
||||||
const correctEvmNetwork = getEvmChainId(targetChain);
|
const correctEvmNetwork = getEvmChainId(targetChain);
|
||||||
const hasCorrectEvmNetwork = evmChainId === correctEvmNetwork;
|
const hasCorrectEvmNetwork = evmChainId === correctEvmNetwork;
|
||||||
|
const isRecovery = useSelector(
|
||||||
|
nft ? selectNFTIsRecovery : selectTransferIsRecovery
|
||||||
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (isRecovery) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (isSourceAssetWormholeWrapped && originChain === targetChain) {
|
if (isSourceAssetWormholeWrapped && originChain === targetChain) {
|
||||||
dispatch(setTargetAsset(hexToNativeString(originAsset, originChain)));
|
dispatch(setTargetAsset(hexToNativeString(originAsset, originChain)));
|
||||||
return;
|
return;
|
||||||
|
@ -158,6 +166,7 @@ function useFetchTargetAsset(nft?: boolean) {
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
dispatch,
|
dispatch,
|
||||||
|
isRecovery,
|
||||||
isSourceAssetWormholeWrapped,
|
isSourceAssetWormholeWrapped,
|
||||||
originChain,
|
originChain,
|
||||||
originAsset,
|
originAsset,
|
||||||
|
|
|
@ -49,6 +49,7 @@ export interface NFTState {
|
||||||
isSending: boolean;
|
isSending: boolean;
|
||||||
isRedeeming: boolean;
|
isRedeeming: boolean;
|
||||||
redeemTx: Transaction | undefined;
|
redeemTx: Transaction | undefined;
|
||||||
|
isRecovery: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: NFTState = {
|
const initialState: NFTState = {
|
||||||
|
@ -70,6 +71,7 @@ const initialState: NFTState = {
|
||||||
isSending: false,
|
isSending: false,
|
||||||
isRedeeming: false,
|
isRedeeming: false,
|
||||||
redeemTx: undefined,
|
redeemTx: undefined,
|
||||||
|
isRecovery: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const nftSlice = createSlice({
|
export const nftSlice = createSlice({
|
||||||
|
@ -203,6 +205,26 @@ export const nftSlice = createSlice({
|
||||||
sourceChain: state.sourceChain,
|
sourceChain: state.sourceChain,
|
||||||
targetChain: state.targetChain,
|
targetChain: state.targetChain,
|
||||||
}),
|
}),
|
||||||
|
setRecoveryVaa: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{
|
||||||
|
vaa: any;
|
||||||
|
parsedPayload: {
|
||||||
|
targetChain: ChainId;
|
||||||
|
targetAddress: string;
|
||||||
|
originChain: ChainId;
|
||||||
|
originAddress: string; //TODO maximum amount of fields
|
||||||
|
};
|
||||||
|
}>
|
||||||
|
) => {
|
||||||
|
state.signedVAAHex = action.payload.vaa;
|
||||||
|
state.targetChain = action.payload.parsedPayload.targetChain;
|
||||||
|
state.targetAddressHex = action.payload.parsedPayload.targetAddress;
|
||||||
|
state.originChain = action.payload.parsedPayload.originChain;
|
||||||
|
state.originAsset = action.payload.parsedPayload.originAddress;
|
||||||
|
state.activeStep = 3;
|
||||||
|
state.isRecovery = true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -228,6 +250,7 @@ export const {
|
||||||
setIsRedeeming,
|
setIsRedeeming,
|
||||||
setRedeemTx,
|
setRedeemTx,
|
||||||
reset,
|
reset,
|
||||||
|
setRecoveryVaa,
|
||||||
} = nftSlice.actions;
|
} = nftSlice.actions;
|
||||||
|
|
||||||
export default nftSlice.reducer;
|
export default nftSlice.reducer;
|
||||||
|
|
|
@ -139,7 +139,7 @@ export const selectNFTIsRedeemComplete = (state: RootState) =>
|
||||||
!!selectNFTRedeemTx(state);
|
!!selectNFTRedeemTx(state);
|
||||||
export const selectNFTShouldLockFields = (state: RootState) =>
|
export const selectNFTShouldLockFields = (state: RootState) =>
|
||||||
selectNFTIsSending(state) || selectNFTIsSendComplete(state);
|
selectNFTIsSending(state) || selectNFTIsSendComplete(state);
|
||||||
|
export const selectNFTIsRecovery = (state: RootState) => state.nft.isRecovery;
|
||||||
/*
|
/*
|
||||||
* Transfer
|
* Transfer
|
||||||
*/
|
*/
|
||||||
|
@ -277,6 +277,8 @@ export const selectTransferIsRedeemComplete = (state: RootState) =>
|
||||||
!!selectTransferRedeemTx(state);
|
!!selectTransferRedeemTx(state);
|
||||||
export const selectTransferShouldLockFields = (state: RootState) =>
|
export const selectTransferShouldLockFields = (state: RootState) =>
|
||||||
selectTransferIsSending(state) || selectTransferIsSendComplete(state);
|
selectTransferIsSending(state) || selectTransferIsSendComplete(state);
|
||||||
|
export const selectTransferIsRecovery = (state: RootState) =>
|
||||||
|
state.transfer.isRecovery;
|
||||||
|
|
||||||
export const selectSolanaTokenMap = (state: RootState) => {
|
export const selectSolanaTokenMap = (state: RootState) => {
|
||||||
return state.tokens.solanaTokenMap;
|
return state.tokens.solanaTokenMap;
|
||||||
|
|
|
@ -55,6 +55,7 @@ export interface TransferState {
|
||||||
isRedeeming: boolean;
|
isRedeeming: boolean;
|
||||||
redeemTx: Transaction | undefined;
|
redeemTx: Transaction | undefined;
|
||||||
isApproving: boolean;
|
isApproving: boolean;
|
||||||
|
isRecovery: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: TransferState = {
|
const initialState: TransferState = {
|
||||||
|
@ -77,6 +78,7 @@ const initialState: TransferState = {
|
||||||
isRedeeming: false,
|
isRedeeming: false,
|
||||||
redeemTx: undefined,
|
redeemTx: undefined,
|
||||||
isApproving: false,
|
isApproving: false,
|
||||||
|
isRecovery: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transferSlice = createSlice({
|
export const transferSlice = createSlice({
|
||||||
|
@ -214,6 +216,26 @@ export const transferSlice = createSlice({
|
||||||
sourceChain: state.sourceChain,
|
sourceChain: state.sourceChain,
|
||||||
targetChain: state.targetChain,
|
targetChain: state.targetChain,
|
||||||
}),
|
}),
|
||||||
|
setRecoveryVaa: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{
|
||||||
|
vaa: any;
|
||||||
|
parsedPayload: {
|
||||||
|
targetChain: ChainId;
|
||||||
|
targetAddress: string;
|
||||||
|
originChain: ChainId;
|
||||||
|
originAddress: string;
|
||||||
|
};
|
||||||
|
}>
|
||||||
|
) => {
|
||||||
|
state.signedVAAHex = action.payload.vaa;
|
||||||
|
state.targetChain = action.payload.parsedPayload.targetChain;
|
||||||
|
state.targetAddressHex = action.payload.parsedPayload.targetAddress;
|
||||||
|
state.originChain = action.payload.parsedPayload.originChain;
|
||||||
|
state.originAsset = action.payload.parsedPayload.originAddress;
|
||||||
|
state.activeStep = 3;
|
||||||
|
state.isRecovery = true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -241,6 +263,7 @@ export const {
|
||||||
setRedeemTx,
|
setRedeemTx,
|
||||||
setIsApproving,
|
setIsApproving,
|
||||||
reset,
|
reset,
|
||||||
|
setRecoveryVaa,
|
||||||
} = transferSlice.actions;
|
} = transferSlice.actions;
|
||||||
|
|
||||||
export default transferSlice.reducer;
|
export default transferSlice.reducer;
|
||||||
|
|
Loading…
Reference in New Issue