From a94e014918ccfd453eb800475fa825bbddcbb79d Mon Sep 17 00:00:00 2001 From: Evan Gray Date: Wed, 1 Sep 2021 17:21:35 -0400 Subject: [PATCH] bridge_ui: show confirmation progress Change-Id: I48d1a176e7263ab4727c403f80edcca856e00b95 --- bridge_ui/src/components/Transfer/Send.tsx | 2 + .../components/Transfer/TransferProgress.tsx | 102 ++++++++++++++++++ bridge_ui/src/hooks/useHandleTransfer.ts | 11 +- bridge_ui/src/store/selectors.ts | 2 + bridge_ui/src/store/transferSlice.ts | 11 ++ 5 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 bridge_ui/src/components/Transfer/TransferProgress.tsx diff --git a/bridge_ui/src/components/Transfer/Send.tsx b/bridge_ui/src/components/Transfer/Send.tsx index c8b61e517..9967bfd9a 100644 --- a/bridge_ui/src/components/Transfer/Send.tsx +++ b/bridge_ui/src/components/Transfer/Send.tsx @@ -10,6 +10,7 @@ import { CHAINS_BY_ID } from "../../utils/consts"; import ButtonWithLoader from "../ButtonWithLoader"; import KeyAndBalance from "../KeyAndBalance"; import StepDescription from "../StepDescription"; +import TransferProgress from "./TransferProgress"; function Send() { const { handleClick, disabled, showLoader } = useHandleTransfer(); @@ -36,6 +37,7 @@ function Send() { > Transfer + ); } diff --git a/bridge_ui/src/components/Transfer/TransferProgress.tsx b/bridge_ui/src/components/Transfer/TransferProgress.tsx new file mode 100644 index 000000000..eec36ba37 --- /dev/null +++ b/bridge_ui/src/components/Transfer/TransferProgress.tsx @@ -0,0 +1,102 @@ +import { 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 { + selectTransferIsSendComplete, + selectTransferSourceChain, + selectTransferTransferTx, +} from "../../store/selectors"; +import { CHAINS_BY_ID, SOLANA_HOST } from "../../utils/consts"; + +const useStyles = makeStyles((theme) => ({ + root: { + marginTop: theme.spacing(2), + textAlign: "center", + }, + message: { + marginTop: theme.spacing(1), + }, +})); + +export default function TransferProgress() { + const classes = useStyles(); + const sourceChain = useSelector(selectTransferSourceChain); + const transferTx = useSelector(selectTransferTransferTx); + const isSendComplete = useSelector(selectTransferIsSendComplete); + const { provider } = useEthereumProvider(); + const [currentBlock, setCurrentBlock] = useState(0); + useEffect(() => { + if (isSendComplete || !transferTx) return; + let cancelled = false; + if (sourceChain === CHAIN_ID_ETH && provider) { + (async () => { + while (!cancelled) { + await new Promise((resolve) => setTimeout(resolve, 500)); + try { + const newBlock = await provider.getBlockNumber(); + if (!cancelled) { + setCurrentBlock(newBlock); + } + } catch (e) { + console.error(e); + } + } + })(); + } + if (sourceChain === CHAIN_ID_SOLANA) { + (async () => { + // TODO: share connection in context? + const connection = new Connection(SOLANA_HOST, "confirmed"); + while (!cancelled) { + await new Promise((resolve) => setTimeout(resolve, 200)); + try { + const newBlock = await connection.getSlot(); + if (!cancelled) { + setCurrentBlock(newBlock); + } + } catch (e) { + console.error(e); + } + } + })(); + } + return () => { + cancelled = true; + }; + }, [isSendComplete, sourceChain, provider, transferTx]); + const blockDiff = + transferTx && transferTx.block && currentBlock + ? currentBlock - transferTx.block + : undefined; + const expectedBlocks = + sourceChain === CHAIN_ID_SOLANA + ? 32 + : sourceChain === CHAIN_ID_ETH + ? 15 + : 1; + if ( + !isSendComplete && + (sourceChain === CHAIN_ID_SOLANA || sourceChain === CHAIN_ID_ETH) && + blockDiff !== undefined + ) { + return ( +
+ + + {blockDiff < expectedBlocks + ? `Waiting for ${blockDiff} / ${expectedBlocks} confirmations on ${CHAINS_BY_ID[sourceChain].name}...` + : `Waiting for Wormhole Network consensus...`} + +
+ ); + } + return null; +} diff --git a/bridge_ui/src/hooks/useHandleTransfer.ts b/bridge_ui/src/hooks/useHandleTransfer.ts index 359b45e75..4d29dadd4 100644 --- a/bridge_ui/src/hooks/useHandleTransfer.ts +++ b/bridge_ui/src/hooks/useHandleTransfer.ts @@ -38,7 +38,11 @@ import { selectTransferSourceParsedTokenAccount, selectTransferTargetChain, } from "../store/selectors"; -import { setIsSending, setSignedVAAHex } from "../store/transferSlice"; +import { + setIsSending, + setSignedVAAHex, + setTransferTx, +} from "../store/transferSlice"; import { hexToUint8Array, uint8ArrayToHex } from "../utils/array"; import { ETH_BRIDGE_ADDRESS, @@ -75,6 +79,9 @@ async function eth( recipientChain, recipientAddress ); + dispatch( + setTransferTx({ 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); @@ -135,6 +142,7 @@ async function solana( if (!info) { throw new Error("An error occurred while fetching the transaction info"); } + dispatch(setTransferTx({ id: txid, block: info.slot })); enqueueSnackbar("Transaction confirmed", { variant: "success" }); const sequence = parseSequenceFromLogSolana(info); const emitterAddress = await getEmitterAddressSolana( @@ -184,6 +192,7 @@ async function terra( console.log(result); const info = await waitForTerraExecution(result); console.log(info); + dispatch(setTransferTx({ id: info.txhash, block: info.height })); enqueueSnackbar("Transaction confirmed", { variant: "success" }); const sequence = parseSequenceFromLogTerra(info); console.log(sequence); diff --git a/bridge_ui/src/store/selectors.ts b/bridge_ui/src/store/selectors.ts index 12ac53cd7..6be682c41 100644 --- a/bridge_ui/src/store/selectors.ts +++ b/bridge_ui/src/store/selectors.ts @@ -65,6 +65,8 @@ export const selectTransferTargetParsedTokenAccount = (state: RootState) => state.transfer.targetParsedTokenAccount; export const selectTransferTargetBalanceString = (state: RootState) => state.transfer.targetParsedTokenAccount?.uiAmountString || ""; +export const selectTransferTransferTx = (state: RootState) => + state.transfer.transferTx; export const selectTransferSignedVAAHex = (state: RootState) => state.transfer.signedVAAHex; export const selectTransferIsSending = (state: RootState) => diff --git a/bridge_ui/src/store/transferSlice.ts b/bridge_ui/src/store/transferSlice.ts index a6df11d02..35285f65e 100644 --- a/bridge_ui/src/store/transferSlice.ts +++ b/bridge_ui/src/store/transferSlice.ts @@ -26,6 +26,11 @@ export interface ParsedTokenAccount { uiAmountString: string; } +export interface Transaction { + id: string; + block: number; +} + export interface TransferState { activeStep: Steps; sourceChain: ChainId; @@ -39,6 +44,7 @@ export interface TransferState { targetAddressHex: string | undefined; targetAsset: string | null | undefined; targetParsedTokenAccount: ParsedTokenAccount | undefined; + transferTx: Transaction | undefined; signedVAAHex: string | undefined; isSending: boolean; isRedeeming: boolean; @@ -57,6 +63,7 @@ const initialState: TransferState = { targetAddressHex: undefined, targetAsset: undefined, targetParsedTokenAccount: undefined, + transferTx: undefined, signedVAAHex: undefined, isSending: false, isRedeeming: false, @@ -155,6 +162,9 @@ export const transferSlice = createSlice({ ) => { state.targetParsedTokenAccount = action.payload; }, + setTransferTx: (state, action: PayloadAction) => { + state.transferTx = action.payload; + }, setSignedVAAHex: (state, action: PayloadAction) => { state.signedVAAHex = action.payload; state.isSending = false; @@ -185,6 +195,7 @@ export const { setTargetAddressHex, setTargetAsset, setTargetParsedTokenAccount, + setTransferTx, setSignedVAAHex, setIsSending, setIsRedeeming,