bridge_ui: nft and attest niceties

Change-Id: Ib7d9c533bd974751da4c615c9589f3a285f3c9a9
This commit is contained in:
Evan Gray 2021-09-14 16:41:24 -04:00
parent c8aee80b1d
commit 0498454193
18 changed files with 388 additions and 68 deletions

View File

@ -4,6 +4,7 @@ import useIsWalletReady from "../../hooks/useIsWalletReady";
import { selectAttestTargetChain } from "../../store/selectors";
import ButtonWithLoader from "../ButtonWithLoader";
import KeyAndBalance from "../KeyAndBalance";
import WaitingForWalletMessage from "./WaitingForWalletMessage";
function Create() {
const { handleClick, disabled, showLoader } = useHandleCreateWrapped();
@ -20,6 +21,7 @@ function Create() {
>
Create
</ButtonWithLoader>
<WaitingForWalletMessage />
</>
);
}

View File

@ -0,0 +1,54 @@
import { makeStyles, Typography } from "@material-ui/core";
import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
selectAttestCreateTx,
selectAttestTargetChain,
} from "../../store/selectors";
import { reset } from "../../store/attestSlice";
import ButtonWithLoader from "../ButtonWithLoader";
import ShowTx from "../ShowTx";
import { useHistory } from "react-router";
const useStyles = makeStyles((theme) => ({
description: {
textAlign: "center",
},
}));
export default function CreatePreview() {
const { push } = useHistory();
const classes = useStyles();
const dispatch = useDispatch();
const targetChain = useSelector(selectAttestTargetChain);
const createTx = useSelector(selectAttestCreateTx);
const handleResetClick = useCallback(() => {
dispatch(reset());
}, [dispatch]);
const handleReturnClick = useCallback(() => {
dispatch(reset());
push("/transfer");
}, [dispatch, push]);
const explainerString =
"Success! The redeem transaction was submitted. The tokens will become available once the transaction confirms.";
return (
<>
<Typography
component="div"
variant="subtitle2"
className={classes.description}
>
{explainerString}
</Typography>
{createTx ? <ShowTx chainId={targetChain} tx={createTx} /> : null}
<ButtonWithLoader onClick={handleResetClick}>
Attest Another Token!
</ButtonWithLoader>
<ButtonWithLoader onClick={handleReturnClick}>
Return to Transfer
</ButtonWithLoader>
</>
);
}

View File

@ -1,13 +1,21 @@
import { useSelector } from "react-redux";
import { useHandleAttest } from "../../hooks/useHandleAttest";
import useIsWalletReady from "../../hooks/useIsWalletReady";
import { selectAttestSourceChain } from "../../store/selectors";
import {
selectAttestAttestTx,
selectAttestIsSendComplete,
selectAttestSourceChain,
} from "../../store/selectors";
import ButtonWithLoader from "../ButtonWithLoader";
import KeyAndBalance from "../KeyAndBalance";
import TransactionProgress from "../TransactionProgress";
import WaitingForWalletMessage from "./WaitingForWalletMessage";
function Send() {
const { handleClick, disabled, showLoader } = useHandleAttest();
const sourceChain = useSelector(selectAttestSourceChain);
const attestTx = useSelector(selectAttestAttestTx);
const isSendComplete = useSelector(selectAttestIsSendComplete);
const { isReady, statusMessage } = useIsWalletReady(sourceChain);
return (
@ -21,6 +29,12 @@ function Send() {
>
Attest
</ButtonWithLoader>
<WaitingForWalletMessage />
<TransactionProgress
chainId={sourceChain}
tx={attestTx}
isSendComplete={isSendComplete}
/>
</>
);
}

View File

@ -0,0 +1,41 @@
import { makeStyles, Typography } from "@material-ui/core";
import { useSelector } from "react-redux";
import {
selectAttestSourceChain,
selectAttestAttestTx,
} from "../../store/selectors";
import ShowTx from "../ShowTx";
const useStyles = makeStyles((theme) => ({
description: {
textAlign: "center",
},
tx: {
marginTop: theme.spacing(1),
textAlign: "center",
},
viewButton: {
marginTop: theme.spacing(1),
},
}));
export default function SendPreview() {
const classes = useStyles();
const sourceChain = useSelector(selectAttestSourceChain);
const attestTx = useSelector(selectAttestAttestTx);
const explainerString = "The token has been attested!";
return (
<>
<Typography
component="div"
variant="subtitle2"
className={classes.description}
>
{explainerString}
</Typography>
{attestTx ? <ShowTx chainId={sourceChain} tx={attestTx} /> : null}
</>
);
}

View File

@ -0,0 +1,36 @@
import { makeStyles, Typography } from "@material-ui/core";
import { useSelector } from "react-redux";
import {
selectAttestSourceAsset,
selectAttestSourceChain,
} from "../../store/selectors";
import { CHAINS_BY_ID } from "../../utils/consts";
import { shortenAddress } from "../../utils/solana";
const useStyles = makeStyles((theme) => ({
description: {
textAlign: "center",
},
}));
export default function SourcePreview() {
const classes = useStyles();
const sourceChain = useSelector(selectAttestSourceChain);
const sourceAsset = useSelector(selectAttestSourceAsset);
const explainerString = sourceAsset
? `You will attest ${shortenAddress(sourceAsset)} on ${
CHAINS_BY_ID[sourceChain].name
}`
: "Step complete.";
return (
<Typography
component="div"
variant="subtitle2"
className={classes.description}
>
{explainerString}
</Typography>
);
}

View File

@ -0,0 +1,27 @@
import { makeStyles, Typography } from "@material-ui/core";
import { useSelector } from "react-redux";
import { selectAttestTargetChain } from "../../store/selectors";
import { CHAINS_BY_ID } from "../../utils/consts";
const useStyles = makeStyles((theme) => ({
description: {
textAlign: "center",
},
}));
export default function TargetPreview() {
const classes = useStyles();
const targetChain = useSelector(selectAttestTargetChain);
const explainerString = `to ${CHAINS_BY_ID[targetChain].name}`;
return (
<Typography
component="div"
variant="subtitle2"
className={classes.description}
>
{explainerString}
</Typography>
);
}

View File

@ -0,0 +1,38 @@
import { CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
import { makeStyles, Typography } from "@material-ui/core";
import { useSelector } from "react-redux";
import {
selectAttestAttestTx,
selectAttestCreateTx,
selectAttestIsCreating,
selectAttestIsSending,
selectAttestTargetChain,
} from "../../store/selectors";
const useStyles = makeStyles((theme) => ({
message: {
color: theme.palette.warning.light,
marginTop: theme.spacing(1),
textAlign: "center",
},
}));
const WAITING_FOR_WALLET = "Waiting for wallet approval (likely in a popup)...";
export default function WaitingForWalletMessage() {
const classes = useStyles();
const isSending = useSelector(selectAttestIsSending);
const attestTx = useSelector(selectAttestAttestTx);
const targetChain = useSelector(selectAttestTargetChain);
const isCreating = useSelector(selectAttestIsCreating);
const createTx = useSelector(selectAttestCreateTx);
const showWarning = (isSending && !attestTx) || (isCreating && !createTx);
return showWarning ? (
<Typography className={classes.message} variant="body2">
{WAITING_FOR_WALLET}{" "}
{targetChain === CHAIN_ID_SOLANA && isCreating
? "Note: there will be several transactions"
: null}
</Typography>
) : null;
}

View File

@ -6,17 +6,25 @@ import {
StepContent,
Stepper,
} from "@material-ui/core";
import { Alert } from "@material-ui/lab";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { setStep } from "../../store/attestSlice";
import {
selectAttestActiveStep,
selectAttestSignedVAAHex,
selectAttestIsCreateComplete,
selectAttestIsCreating,
selectAttestIsSendComplete,
selectAttestIsSending,
} from "../../store/selectors";
import { setStep } from "../../store/attestSlice";
import Create from "./Create";
import CreatePreview from "./CreatePreview";
import Send from "./Send";
import SendPreview from "./SendPreview";
import Source from "./Source";
import SourcePreview from "./SourcePreview";
import Target from "./Target";
import { Alert } from "@material-ui/lab";
import TargetPreview from "./TargetPreview";
const useStyles = makeStyles(() => ({
rootContainer: {
@ -28,7 +36,20 @@ function Attest() {
const classes = useStyles();
const dispatch = useDispatch();
const activeStep = useSelector(selectAttestActiveStep);
const signedVAAHex = useSelector(selectAttestSignedVAAHex);
const isSending = useSelector(selectAttestIsSending);
const isSendComplete = useSelector(selectAttestIsSendComplete);
const isCreating = useSelector(selectAttestIsCreating);
const isCreateComplete = useSelector(selectAttestIsCreateComplete);
const preventNavigation =
(isSending || isSendComplete || isCreating) && !isCreateComplete;
useEffect(() => {
if (preventNavigation) {
window.onbeforeunload = () => true;
return () => {
window.onbeforeunload = null;
};
}
}, [preventNavigation]);
return (
<Container maxWidth="md">
<Alert severity="info">
@ -40,39 +61,41 @@ function Attest() {
orientation="vertical"
className={classes.rootContainer}
>
<Step>
<StepButton onClick={() => dispatch(setStep(0))}>
Select a source
</StepButton>
<Step
expanded={activeStep >= 0}
disabled={preventNavigation || isCreateComplete}
>
<StepButton onClick={() => dispatch(setStep(0))}>Source</StepButton>
<StepContent>
<Source />
{activeStep === 0 ? <Source /> : <SourcePreview />}
</StepContent>
</Step>
<Step>
<StepButton onClick={() => dispatch(setStep(1))}>
Select a target
</StepButton>
<Step
expanded={activeStep >= 1}
disabled={preventNavigation || isCreateComplete}
>
<StepButton onClick={() => dispatch(setStep(1))}>Target</StepButton>
<StepContent>
<Target />
{activeStep === 1 ? <Target /> : <TargetPreview />}
</StepContent>
</Step>
<Step>
<Step expanded={activeStep >= 2} disabled={isSendComplete}>
<StepButton onClick={() => dispatch(setStep(2))}>
Send attestation
</StepButton>
<StepContent>
<Send />
{activeStep === 2 ? <Send /> : <SendPreview />}
</StepContent>
</Step>
<Step>
<Step expanded={activeStep >= 3}>
<StepButton
onClick={() => dispatch(setStep(3))}
disabled={!signedVAAHex}
disabled={!isSendComplete}
>
Create wrapped token
</StepButton>
<StepContent>
<Create />
{isCreateComplete ? <CreatePreview /> : <Create />}
</StepContent>
</Step>
</Stepper>

View File

@ -5,6 +5,7 @@ import { selectNFTTargetChain } from "../../store/selectors";
import ButtonWithLoader from "../ButtonWithLoader";
import KeyAndBalance from "../KeyAndBalance";
import StepDescription from "../StepDescription";
import WaitingForWalletMessage from "./WaitingForWalletMessage";
function Redeem() {
const { handleClick, disabled, showLoader } = useHandleNFTRedeem();
@ -22,6 +23,7 @@ function Redeem() {
>
Redeem
</ButtonWithLoader>
<WaitingForWalletMessage />
</>
);
}

View File

@ -6,12 +6,15 @@ import {
selectNFTSourceWalletAddress,
selectNFTSourceChain,
selectNFTTargetError,
selectNFTTransferTx,
selectNFTIsSendComplete,
} from "../../store/selectors";
import { CHAINS_BY_ID } from "../../utils/consts";
import ButtonWithLoader from "../ButtonWithLoader";
import KeyAndBalance from "../KeyAndBalance";
import StepDescription from "../StepDescription";
import TransferProgress from "../TransferProgress";
import TransactionProgress from "../TransactionProgress";
import WaitingForWalletMessage from "./WaitingForWalletMessage";
function Send() {
const { handleClick, disabled, showLoader } = useHandleNFTTransfer();
@ -20,6 +23,8 @@ function Send() {
const { isReady, statusMessage, walletAddress } =
useIsWalletReady(sourceChain);
const sourceWalletAddress = useSelector(selectNFTSourceWalletAddress);
const transferTx = useSelector(selectNFTTransferTx);
const isSendComplete = useSelector(selectNFTIsSendComplete);
//The chain ID compare is handled implicitly, as the isWalletReady hook should report !isReady if the wallet is on the wrong chain.
const isWrongWallet =
sourceWalletAddress &&
@ -49,7 +54,12 @@ function Send() {
>
Transfer
</ButtonWithLoader>
<TransferProgress nft />
<WaitingForWalletMessage />
<TransactionProgress
chainId={sourceChain}
tx={transferTx}
isSendComplete={isSendComplete}
/>
</>
);
}

View File

@ -0,0 +1,42 @@
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
import { makeStyles, Typography } from "@material-ui/core";
import { useSelector } from "react-redux";
import {
selectNFTIsRedeeming,
selectNFTIsSending,
selectNFTRedeemTx,
selectNFTSourceChain,
selectNFTTargetChain,
selectNFTTransferTx,
} from "../../store/selectors";
const useStyles = makeStyles((theme) => ({
message: {
color: theme.palette.warning.light,
marginTop: theme.spacing(1),
textAlign: "center",
},
}));
const WAITING_FOR_WALLET = "Waiting for wallet approval (likely in a popup)...";
export default function WaitingForWalletMessage() {
const classes = useStyles();
const sourceChain = useSelector(selectNFTSourceChain);
const isSending = useSelector(selectNFTIsSending);
const transferTx = useSelector(selectNFTTransferTx);
const targetChain = useSelector(selectNFTTargetChain);
const isRedeeming = useSelector(selectNFTIsRedeeming);
const redeemTx = useSelector(selectNFTRedeemTx);
const showWarning = (isSending && !transferTx) || (isRedeeming && !redeemTx);
return showWarning ? (
<Typography className={classes.message} variant="body2">
{WAITING_FOR_WALLET}{" "}
{targetChain === CHAIN_ID_SOLANA && isRedeeming
? "Note: there will be several transactions"
: sourceChain === CHAIN_ID_ETH && isSending
? "Note: there will be two transactions"
: null}
</Typography>
) : null;
}

View File

@ -1,17 +1,13 @@
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
import {
ChainId,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA,
} from "@certusone/wormhole-sdk";
import { LinearProgress, makeStyles, Typography } from "@material-ui/core";
import { Connection } from "@solana/web3.js";
import { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
import {
selectNFTIsSendComplete,
selectNFTSourceChain,
selectNFTTransferTx,
selectTransferIsSendComplete,
selectTransferSourceChain,
selectTransferTransferTx,
} from "../store/selectors";
import { Transaction } from "../store/transferSlice";
import { CHAINS_BY_ID, SOLANA_HOST } from "../utils/consts";
const useStyles = makeStyles((theme) => ({
@ -24,22 +20,21 @@ const useStyles = makeStyles((theme) => ({
},
}));
export default function TransferProgress({ nft }: { nft?: boolean }) {
export default function TransactionProgress({
chainId,
tx,
isSendComplete,
}: {
chainId: ChainId;
tx: Transaction | undefined;
isSendComplete: boolean;
}) {
const classes = useStyles();
const sourceChain = useSelector(
nft ? selectNFTSourceChain : selectTransferSourceChain
);
const transferTx = useSelector(
nft ? selectNFTTransferTx : selectTransferTransferTx
);
const isSendComplete = useSelector(
nft ? selectNFTIsSendComplete : selectTransferIsSendComplete
);
const { provider } = useEthereumProvider();
const [currentBlock, setCurrentBlock] = useState(0);
useEffect(() => {
if (isSendComplete || !transferTx) return;
if (sourceChain === CHAIN_ID_ETH && provider) {
if (isSendComplete || !tx) return;
if (chainId === CHAIN_ID_ETH && provider) {
let cancelled = false;
(async () => {
while (!cancelled) {
@ -58,7 +53,7 @@ export default function TransferProgress({ nft }: { nft?: boolean }) {
cancelled = true;
};
}
if (sourceChain === CHAIN_ID_SOLANA) {
if (chainId === CHAIN_ID_SOLANA) {
let cancelled = false;
const connection = new Connection(SOLANA_HOST, "confirmed");
const sub = connection.onSlotChange((slotInfo) => {
@ -71,20 +66,14 @@ export default function TransferProgress({ nft }: { nft?: boolean }) {
connection.removeSlotChangeListener(sub);
};
}
}, [isSendComplete, sourceChain, provider, transferTx]);
}, [isSendComplete, chainId, provider, tx]);
const blockDiff =
transferTx && transferTx.block && currentBlock
? currentBlock - transferTx.block
: undefined;
tx && tx.block && currentBlock ? currentBlock - tx.block : undefined;
const expectedBlocks =
sourceChain === CHAIN_ID_SOLANA
? 32
: sourceChain === CHAIN_ID_ETH
? 15
: 1;
chainId === CHAIN_ID_SOLANA ? 32 : chainId === CHAIN_ID_ETH ? 15 : 1;
if (
!isSendComplete &&
(sourceChain === CHAIN_ID_SOLANA || sourceChain === CHAIN_ID_ETH) &&
(chainId === CHAIN_ID_SOLANA || chainId === CHAIN_ID_ETH) &&
blockDiff !== undefined
) {
return (
@ -97,7 +86,7 @@ export default function TransferProgress({ nft }: { nft?: boolean }) {
/>
<Typography variant="body2" className={classes.message}>
{blockDiff < expectedBlocks
? `Waiting for ${blockDiff} / ${expectedBlocks} confirmations on ${CHAINS_BY_ID[sourceChain].name}...`
? `Waiting for ${blockDiff} / ${expectedBlocks} confirmations on ${CHAINS_BY_ID[chainId].name}...`
: `Waiting for Wormhole Network consensus...`}
</Typography>
</div>

View File

@ -11,16 +11,18 @@ import useIsWalletReady from "../../hooks/useIsWalletReady";
import {
selectSourceWalletAddress,
selectTransferAmount,
selectTransferIsSendComplete,
selectTransferSourceAsset,
selectTransferSourceChain,
selectTransferSourceParsedTokenAccount,
selectTransferTargetError,
selectTransferTransferTx,
} from "../../store/selectors";
import { CHAINS_BY_ID } from "../../utils/consts";
import ButtonWithLoader from "../ButtonWithLoader";
import KeyAndBalance from "../KeyAndBalance";
import StepDescription from "../StepDescription";
import TransferProgress from "../TransferProgress";
import TransactionProgress from "../TransactionProgress";
import WaitingForWalletMessage from "./WaitingForWalletMessage";
function Send() {
@ -41,6 +43,8 @@ function Send() {
sourceDecimals !== undefined &&
sourceDecimals !== null &&
parseUnits("1", sourceDecimals).toBigInt();
const transferTx = useSelector(selectTransferTransferTx);
const isSendComplete = useSelector(selectTransferIsSendComplete);
const error = useSelector(selectTransferTargetError);
const [allowanceError, setAllowanceError] = useState("");
@ -149,7 +153,11 @@ function Send() {
</ButtonWithLoader>
)}
<WaitingForWalletMessage />
<TransferProgress />
<TransactionProgress
chainId={sourceChain}
tx={transferTx}
isSendComplete={isSendComplete}
/>
</>
);
}

View File

@ -51,10 +51,8 @@ function Transfer() {
(isSending || isSendComplete || isRedeeming) && !isRedeemComplete;
useEffect(() => {
if (preventNavigation) {
console.log("add onbeforeunload");
window.onbeforeunload = () => true;
return () => {
console.log("remove onbeforeunload");
window.onbeforeunload = null;
};
}

View File

@ -24,7 +24,11 @@ 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 {
setAttestTx,
setIsSending,
setSignedVAAHex,
} from "../store/attestSlice";
import {
selectAttestIsSendComplete,
selectAttestIsSending,
@ -59,6 +63,9 @@ async function eth(
signer,
sourceAsset
);
dispatch(
setAttestTx({ id: receipt.transactionHash, block: receipt.blockNumber })
);
enqueueSnackbar("Transaction confirmed", { variant: "success" });
const sequence = parseSequenceFromLogEth(receipt, ETH_BRIDGE_ADDRESS);
const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
@ -101,6 +108,7 @@ async function solana(
// TODO: error state
throw new Error("An error occurred while fetching the transaction info");
}
dispatch(setAttestTx({ id: txid, block: info.slot }));
const sequence = parseSequenceFromLogSolana(info);
const emitterAddress = await getEmitterAddressSolana(
SOL_TOKEN_BRIDGE_ADDRESS
@ -134,6 +142,7 @@ async function terra(
asset
);
const info = await waitForTerraExecution(result);
dispatch(setAttestTx({ id: info.txhash, block: info.height }));
enqueueSnackbar("Transaction confirmed", { variant: "success" });
const sequence = parseSequenceFromLogTerra(info);
if (!sequence) {

View File

@ -20,7 +20,7 @@ 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 { setCreateTx, setIsCreating } from "../store/attestSlice";
import {
selectAttestIsCreating,
selectAttestTargetChain,
@ -43,8 +43,14 @@ async function eth(
) {
dispatch(setIsCreating(true));
try {
await createWrappedOnEth(ETH_TOKEN_BRIDGE_ADDRESS, signer, signedVAA);
dispatch(reset());
const receipt = await createWrappedOnEth(
ETH_TOKEN_BRIDGE_ADDRESS,
signer,
signedVAA
);
dispatch(
setCreateTx({ id: receipt.transactionHash, block: receipt.blockNumber })
);
enqueueSnackbar("Transaction confirmed", { variant: "success" });
} catch (e) {
enqueueSnackbar(parseError(e), { variant: "error" });
@ -76,8 +82,9 @@ async function solana(
payerAddress,
signedVAA
);
await signSendAndConfirm(wallet, connection, transaction);
dispatch(reset());
const txid = await signSendAndConfirm(wallet, connection, transaction);
// TODO: didn't want to make an info call we didn't need, can we get the block without it by modifying the above call?
dispatch(setCreateTx({ id: txid, block: 1 }));
enqueueSnackbar("Transaction confirmed", { variant: "success" });
} catch (e) {
enqueueSnackbar(parseError(e), { variant: "error" });
@ -98,11 +105,13 @@ async function terra(
wallet.terraAddress,
signedVAA
);
await wallet.post({
const result = await wallet.post({
msgs: [msg],
memo: "Wormhole - Create Wrapped",
});
dispatch(reset());
dispatch(
setCreateTx({ id: result.result.txhash, block: result.result.height })
);
enqueueSnackbar("Transaction confirmed", { variant: "success" });
} catch (e) {
enqueueSnackbar(parseError(e), { variant: "error" });

View File

@ -4,6 +4,7 @@ import {
CHAIN_ID_SOLANA,
} from "@certusone/wormhole-sdk";
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { Transaction } from "./transferSlice";
const LAST_STEP = 3;
@ -14,9 +15,11 @@ export interface AttestState {
sourceChain: ChainId;
sourceAsset: string;
targetChain: ChainId;
attestTx: Transaction | undefined;
signedVAAHex: string | undefined;
isSending: boolean;
isCreating: boolean;
createTx: Transaction | undefined;
}
const initialState: AttestState = {
@ -24,9 +27,11 @@ const initialState: AttestState = {
sourceChain: CHAIN_ID_SOLANA,
sourceAsset: "",
targetChain: CHAIN_ID_ETH,
attestTx: undefined,
signedVAAHex: undefined,
isSending: false,
isCreating: false,
createTx: undefined,
};
export const attestSlice = createSlice({
@ -62,6 +67,9 @@ export const attestSlice = createSlice({
state.sourceAsset = "";
}
},
setAttestTx: (state, action: PayloadAction<Transaction>) => {
state.attestTx = action.payload;
},
setSignedVAAHex: (state, action: PayloadAction<string>) => {
state.signedVAAHex = action.payload;
state.isSending = false;
@ -73,6 +81,10 @@ export const attestSlice = createSlice({
setIsCreating: (state, action: PayloadAction<boolean>) => {
state.isCreating = action.payload;
},
setCreateTx: (state, action: PayloadAction<Transaction>) => {
state.createTx = action.payload;
state.isCreating = false;
},
reset: (state) => ({
...initialState,
sourceChain: state.sourceChain,
@ -88,9 +100,11 @@ export const {
setSourceChain,
setSourceAsset,
setTargetChain,
setAttestTx,
setSignedVAAHex,
setIsSending,
setIsCreating,
setCreateTx,
reset,
} = attestSlice.actions;

View File

@ -15,12 +15,14 @@ export const selectAttestSourceAsset = (state: RootState) =>
state.attest.sourceAsset;
export const selectAttestTargetChain = (state: RootState) =>
state.attest.targetChain;
export const selectAttestAttestTx = (state: RootState) => state.attest.attestTx;
export const selectAttestSignedVAAHex = (state: RootState) =>
state.attest.signedVAAHex;
export const selectAttestIsSending = (state: RootState) =>
state.attest.isSending;
export const selectAttestIsCreating = (state: RootState) =>
state.attest.isCreating;
export const selectAttestCreateTx = (state: RootState) => state.attest.createTx;
export const selectAttestIsSourceComplete = (state: RootState) =>
!!state.attest.sourceChain && !!state.attest.sourceAsset;
// TODO: check wrapped asset exists or is native attest
@ -28,6 +30,8 @@ export const selectAttestIsTargetComplete = (state: RootState) =>
selectAttestIsSourceComplete(state) && !!state.attest.targetChain;
export const selectAttestIsSendComplete = (state: RootState) =>
!!selectAttestSignedVAAHex(state);
export const selectAttestIsCreateComplete = (state: RootState) =>
!!selectAttestCreateTx(state);
export const selectAttestShouldLockFields = (state: RootState) =>
selectAttestIsSending(state) || selectAttestIsSendComplete(state);