bridge_ui: nft and attest niceties
Change-Id: Ib7d9c533bd974751da4c615c9589f3a285f3c9a9
This commit is contained in:
parent
c8aee80b1d
commit
0498454193
|
@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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" });
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue