diff --git a/bridge_ui/src/components/Attest/Create.tsx b/bridge_ui/src/components/Attest/Create.tsx index 70b66d4a..0fecefc2 100644 --- a/bridge_ui/src/components/Attest/Create.tsx +++ b/bridge_ui/src/components/Attest/Create.tsx @@ -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 + ); } diff --git a/bridge_ui/src/components/Attest/CreatePreview.tsx b/bridge_ui/src/components/Attest/CreatePreview.tsx new file mode 100644 index 00000000..1a6c8907 --- /dev/null +++ b/bridge_ui/src/components/Attest/CreatePreview.tsx @@ -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 ( + <> + + {explainerString} + + {createTx ? : null} + + Attest Another Token! + + + Return to Transfer + + + ); +} diff --git a/bridge_ui/src/components/Attest/Send.tsx b/bridge_ui/src/components/Attest/Send.tsx index 270285aa..0fdae378 100644 --- a/bridge_ui/src/components/Attest/Send.tsx +++ b/bridge_ui/src/components/Attest/Send.tsx @@ -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 + + ); } diff --git a/bridge_ui/src/components/Attest/SendPreview.tsx b/bridge_ui/src/components/Attest/SendPreview.tsx new file mode 100644 index 00000000..051d3f31 --- /dev/null +++ b/bridge_ui/src/components/Attest/SendPreview.tsx @@ -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 ( + <> + + {explainerString} + + {attestTx ? : null} + + ); +} diff --git a/bridge_ui/src/components/Attest/SourcePreview.tsx b/bridge_ui/src/components/Attest/SourcePreview.tsx new file mode 100644 index 00000000..cba94a2d --- /dev/null +++ b/bridge_ui/src/components/Attest/SourcePreview.tsx @@ -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 ( + + {explainerString} + + ); +} diff --git a/bridge_ui/src/components/Attest/TargetPreview.tsx b/bridge_ui/src/components/Attest/TargetPreview.tsx new file mode 100644 index 00000000..de350de5 --- /dev/null +++ b/bridge_ui/src/components/Attest/TargetPreview.tsx @@ -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 ( + + {explainerString} + + ); +} diff --git a/bridge_ui/src/components/Attest/WaitingForWalletMessage.tsx b/bridge_ui/src/components/Attest/WaitingForWalletMessage.tsx new file mode 100644 index 00000000..da228a60 --- /dev/null +++ b/bridge_ui/src/components/Attest/WaitingForWalletMessage.tsx @@ -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 ? ( + + {WAITING_FOR_WALLET}{" "} + {targetChain === CHAIN_ID_SOLANA && isCreating + ? "Note: there will be several transactions" + : null} + + ) : null; +} diff --git a/bridge_ui/src/components/Attest/index.tsx b/bridge_ui/src/components/Attest/index.tsx index 402d5be2..5c611789 100644 --- a/bridge_ui/src/components/Attest/index.tsx +++ b/bridge_ui/src/components/Attest/index.tsx @@ -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 ( @@ -40,39 +61,41 @@ function Attest() { orientation="vertical" className={classes.rootContainer} > - - dispatch(setStep(0))}> - Select a source - + = 0} + disabled={preventNavigation || isCreateComplete} + > + dispatch(setStep(0))}>Source - + {activeStep === 0 ? : } - - dispatch(setStep(1))}> - Select a target - + = 1} + disabled={preventNavigation || isCreateComplete} + > + dispatch(setStep(1))}>Target - + {activeStep === 1 ? : } - + = 2} disabled={isSendComplete}> dispatch(setStep(2))}> Send attestation - + {activeStep === 2 ? : } - + = 3}> dispatch(setStep(3))} - disabled={!signedVAAHex} + disabled={!isSendComplete} > Create wrapped token - + {isCreateComplete ? : } diff --git a/bridge_ui/src/components/NFT/Redeem.tsx b/bridge_ui/src/components/NFT/Redeem.tsx index df5dc056..29facf6a 100644 --- a/bridge_ui/src/components/NFT/Redeem.tsx +++ b/bridge_ui/src/components/NFT/Redeem.tsx @@ -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 + ); } diff --git a/bridge_ui/src/components/NFT/Send.tsx b/bridge_ui/src/components/NFT/Send.tsx index 1768682a..7c965896 100644 --- a/bridge_ui/src/components/NFT/Send.tsx +++ b/bridge_ui/src/components/NFT/Send.tsx @@ -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 - + + ); } diff --git a/bridge_ui/src/components/NFT/WaitingForWalletMessage.tsx b/bridge_ui/src/components/NFT/WaitingForWalletMessage.tsx new file mode 100644 index 00000000..b3a724b7 --- /dev/null +++ b/bridge_ui/src/components/NFT/WaitingForWalletMessage.tsx @@ -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 ? ( + + {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} + + ) : null; +} diff --git a/bridge_ui/src/components/TransferProgress.tsx b/bridge_ui/src/components/TransactionProgress.tsx similarity index 62% rename from bridge_ui/src/components/TransferProgress.tsx rename to bridge_ui/src/components/TransactionProgress.tsx index 2cdb3205..fdbdd98e 100644 --- a/bridge_ui/src/components/TransferProgress.tsx +++ b/bridge_ui/src/components/TransactionProgress.tsx @@ -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 }) { /> {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...`} diff --git a/bridge_ui/src/components/Transfer/Send.tsx b/bridge_ui/src/components/Transfer/Send.tsx index 750e4f2e..4fe2d00b 100644 --- a/bridge_ui/src/components/Transfer/Send.tsx +++ b/bridge_ui/src/components/Transfer/Send.tsx @@ -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() { )} - + ); } diff --git a/bridge_ui/src/components/Transfer/index.tsx b/bridge_ui/src/components/Transfer/index.tsx index e91a5694..41552f4b 100644 --- a/bridge_ui/src/components/Transfer/index.tsx +++ b/bridge_ui/src/components/Transfer/index.tsx @@ -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; }; } diff --git a/bridge_ui/src/hooks/useHandleAttest.ts b/bridge_ui/src/hooks/useHandleAttest.ts index 557f6018..ab7325d4 100644 --- a/bridge_ui/src/hooks/useHandleAttest.ts +++ b/bridge_ui/src/hooks/useHandleAttest.ts @@ -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) { diff --git a/bridge_ui/src/hooks/useHandleCreateWrapped.ts b/bridge_ui/src/hooks/useHandleCreateWrapped.ts index 769d8583..f135a839 100644 --- a/bridge_ui/src/hooks/useHandleCreateWrapped.ts +++ b/bridge_ui/src/hooks/useHandleCreateWrapped.ts @@ -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" }); diff --git a/bridge_ui/src/store/attestSlice.ts b/bridge_ui/src/store/attestSlice.ts index 3ad31395..17d66426 100644 --- a/bridge_ui/src/store/attestSlice.ts +++ b/bridge_ui/src/store/attestSlice.ts @@ -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) => { + state.attestTx = action.payload; + }, setSignedVAAHex: (state, action: PayloadAction) => { state.signedVAAHex = action.payload; state.isSending = false; @@ -73,6 +81,10 @@ export const attestSlice = createSlice({ setIsCreating: (state, action: PayloadAction) => { state.isCreating = action.payload; }, + setCreateTx: (state, action: PayloadAction) => { + 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; diff --git a/bridge_ui/src/store/selectors.ts b/bridge_ui/src/store/selectors.ts index e9d433f6..33e38846 100644 --- a/bridge_ui/src/store/selectors.ts +++ b/bridge_ui/src/store/selectors.ts @@ -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);