bridge_ui: show confirmation progress
Change-Id: I48d1a176e7263ab4727c403f80edcca856e00b95
This commit is contained in:
parent
b5745a063e
commit
a94e014918
|
@ -10,6 +10,7 @@ import { CHAINS_BY_ID } from "../../utils/consts";
|
||||||
import ButtonWithLoader from "../ButtonWithLoader";
|
import ButtonWithLoader from "../ButtonWithLoader";
|
||||||
import KeyAndBalance from "../KeyAndBalance";
|
import KeyAndBalance from "../KeyAndBalance";
|
||||||
import StepDescription from "../StepDescription";
|
import StepDescription from "../StepDescription";
|
||||||
|
import TransferProgress from "./TransferProgress";
|
||||||
|
|
||||||
function Send() {
|
function Send() {
|
||||||
const { handleClick, disabled, showLoader } = useHandleTransfer();
|
const { handleClick, disabled, showLoader } = useHandleTransfer();
|
||||||
|
@ -36,6 +37,7 @@ function Send() {
|
||||||
>
|
>
|
||||||
Transfer
|
Transfer
|
||||||
</ButtonWithLoader>
|
</ButtonWithLoader>
|
||||||
|
<TransferProgress />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 (
|
||||||
|
<div className={classes.root}>
|
||||||
|
<LinearProgress
|
||||||
|
value={
|
||||||
|
blockDiff < expectedBlocks ? (blockDiff / expectedBlocks) * 75 : 75
|
||||||
|
}
|
||||||
|
variant="determinate"
|
||||||
|
/>
|
||||||
|
<Typography variant="body2" className={classes.message}>
|
||||||
|
{blockDiff < expectedBlocks
|
||||||
|
? `Waiting for ${blockDiff} / ${expectedBlocks} confirmations on ${CHAINS_BY_ID[sourceChain].name}...`
|
||||||
|
: `Waiting for Wormhole Network consensus...`}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
|
@ -38,7 +38,11 @@ import {
|
||||||
selectTransferSourceParsedTokenAccount,
|
selectTransferSourceParsedTokenAccount,
|
||||||
selectTransferTargetChain,
|
selectTransferTargetChain,
|
||||||
} from "../store/selectors";
|
} from "../store/selectors";
|
||||||
import { setIsSending, setSignedVAAHex } from "../store/transferSlice";
|
import {
|
||||||
|
setIsSending,
|
||||||
|
setSignedVAAHex,
|
||||||
|
setTransferTx,
|
||||||
|
} from "../store/transferSlice";
|
||||||
import { hexToUint8Array, uint8ArrayToHex } from "../utils/array";
|
import { hexToUint8Array, uint8ArrayToHex } from "../utils/array";
|
||||||
import {
|
import {
|
||||||
ETH_BRIDGE_ADDRESS,
|
ETH_BRIDGE_ADDRESS,
|
||||||
|
@ -75,6 +79,9 @@ async function eth(
|
||||||
recipientChain,
|
recipientChain,
|
||||||
recipientAddress
|
recipientAddress
|
||||||
);
|
);
|
||||||
|
dispatch(
|
||||||
|
setTransferTx({ id: receipt.transactionHash, block: receipt.blockNumber })
|
||||||
|
);
|
||||||
enqueueSnackbar("Transaction confirmed", { variant: "success" });
|
enqueueSnackbar("Transaction confirmed", { variant: "success" });
|
||||||
const sequence = parseSequenceFromLogEth(receipt, ETH_BRIDGE_ADDRESS);
|
const sequence = parseSequenceFromLogEth(receipt, ETH_BRIDGE_ADDRESS);
|
||||||
const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
||||||
|
@ -135,6 +142,7 @@ async function solana(
|
||||||
if (!info) {
|
if (!info) {
|
||||||
throw new Error("An error occurred while fetching the transaction info");
|
throw new Error("An error occurred while fetching the transaction info");
|
||||||
}
|
}
|
||||||
|
dispatch(setTransferTx({ id: txid, block: info.slot }));
|
||||||
enqueueSnackbar("Transaction confirmed", { variant: "success" });
|
enqueueSnackbar("Transaction confirmed", { variant: "success" });
|
||||||
const sequence = parseSequenceFromLogSolana(info);
|
const sequence = parseSequenceFromLogSolana(info);
|
||||||
const emitterAddress = await getEmitterAddressSolana(
|
const emitterAddress = await getEmitterAddressSolana(
|
||||||
|
@ -184,6 +192,7 @@ async function terra(
|
||||||
console.log(result);
|
console.log(result);
|
||||||
const info = await waitForTerraExecution(result);
|
const info = await waitForTerraExecution(result);
|
||||||
console.log(info);
|
console.log(info);
|
||||||
|
dispatch(setTransferTx({ id: info.txhash, block: info.height }));
|
||||||
enqueueSnackbar("Transaction confirmed", { variant: "success" });
|
enqueueSnackbar("Transaction confirmed", { variant: "success" });
|
||||||
const sequence = parseSequenceFromLogTerra(info);
|
const sequence = parseSequenceFromLogTerra(info);
|
||||||
console.log(sequence);
|
console.log(sequence);
|
||||||
|
|
|
@ -65,6 +65,8 @@ export const selectTransferTargetParsedTokenAccount = (state: RootState) =>
|
||||||
state.transfer.targetParsedTokenAccount;
|
state.transfer.targetParsedTokenAccount;
|
||||||
export const selectTransferTargetBalanceString = (state: RootState) =>
|
export const selectTransferTargetBalanceString = (state: RootState) =>
|
||||||
state.transfer.targetParsedTokenAccount?.uiAmountString || "";
|
state.transfer.targetParsedTokenAccount?.uiAmountString || "";
|
||||||
|
export const selectTransferTransferTx = (state: RootState) =>
|
||||||
|
state.transfer.transferTx;
|
||||||
export const selectTransferSignedVAAHex = (state: RootState) =>
|
export const selectTransferSignedVAAHex = (state: RootState) =>
|
||||||
state.transfer.signedVAAHex;
|
state.transfer.signedVAAHex;
|
||||||
export const selectTransferIsSending = (state: RootState) =>
|
export const selectTransferIsSending = (state: RootState) =>
|
||||||
|
|
|
@ -26,6 +26,11 @@ export interface ParsedTokenAccount {
|
||||||
uiAmountString: string;
|
uiAmountString: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Transaction {
|
||||||
|
id: string;
|
||||||
|
block: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TransferState {
|
export interface TransferState {
|
||||||
activeStep: Steps;
|
activeStep: Steps;
|
||||||
sourceChain: ChainId;
|
sourceChain: ChainId;
|
||||||
|
@ -39,6 +44,7 @@ export interface TransferState {
|
||||||
targetAddressHex: string | undefined;
|
targetAddressHex: string | undefined;
|
||||||
targetAsset: string | null | undefined;
|
targetAsset: string | null | undefined;
|
||||||
targetParsedTokenAccount: ParsedTokenAccount | undefined;
|
targetParsedTokenAccount: ParsedTokenAccount | undefined;
|
||||||
|
transferTx: Transaction | undefined;
|
||||||
signedVAAHex: string | undefined;
|
signedVAAHex: string | undefined;
|
||||||
isSending: boolean;
|
isSending: boolean;
|
||||||
isRedeeming: boolean;
|
isRedeeming: boolean;
|
||||||
|
@ -57,6 +63,7 @@ const initialState: TransferState = {
|
||||||
targetAddressHex: undefined,
|
targetAddressHex: undefined,
|
||||||
targetAsset: undefined,
|
targetAsset: undefined,
|
||||||
targetParsedTokenAccount: undefined,
|
targetParsedTokenAccount: undefined,
|
||||||
|
transferTx: undefined,
|
||||||
signedVAAHex: undefined,
|
signedVAAHex: undefined,
|
||||||
isSending: false,
|
isSending: false,
|
||||||
isRedeeming: false,
|
isRedeeming: false,
|
||||||
|
@ -155,6 +162,9 @@ export const transferSlice = createSlice({
|
||||||
) => {
|
) => {
|
||||||
state.targetParsedTokenAccount = action.payload;
|
state.targetParsedTokenAccount = action.payload;
|
||||||
},
|
},
|
||||||
|
setTransferTx: (state, action: PayloadAction<Transaction>) => {
|
||||||
|
state.transferTx = action.payload;
|
||||||
|
},
|
||||||
setSignedVAAHex: (state, action: PayloadAction<string>) => {
|
setSignedVAAHex: (state, action: PayloadAction<string>) => {
|
||||||
state.signedVAAHex = action.payload;
|
state.signedVAAHex = action.payload;
|
||||||
state.isSending = false;
|
state.isSending = false;
|
||||||
|
@ -185,6 +195,7 @@ export const {
|
||||||
setTargetAddressHex,
|
setTargetAddressHex,
|
||||||
setTargetAsset,
|
setTargetAsset,
|
||||||
setTargetParsedTokenAccount,
|
setTargetParsedTokenAccount,
|
||||||
|
setTransferTx,
|
||||||
setSignedVAAHex,
|
setSignedVAAHex,
|
||||||
setIsSending,
|
setIsSending,
|
||||||
setIsRedeeming,
|
setIsRedeeming,
|
||||||
|
|
Loading…
Reference in New Issue