Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove "Review transaction" view #3164

Open
wants to merge 8 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion wormhole-connect/src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const useStyles = makeStyles()((theme: any) => ({
},
disabled: {
cursor: 'not-allowed',
clickEvents: 'none',
pointerEvents: 'none',
backgroundColor: theme.palette.button.disabled + ' !important',
color: theme.palette.button.disabledText + ' !important',
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
import React, { useContext, useMemo, useState } from 'react';
import { useContext, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
import { useMediaQuery, useTheme } from '@mui/material';
import CircularProgress from '@mui/material/CircularProgress';
import Collapse from '@mui/material/Collapse';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import ChevronLeft from '@mui/icons-material/ChevronLeft';
import IconButton from '@mui/material/IconButton';
import { getTransferDetails } from 'telemetry';
import { Context } from 'sdklegacy';

import Button from 'components/v2/Button';
import config from 'config';
import { addTxToLocalStorage } from 'utils/inProgressTxCache';
import { RoutesConfig } from 'config/routes';
import { RouteContext } from 'contexts/RouteContext';
import { useGasSlider } from 'hooks/useGasSlider';
import { useUSDamountGetter } from 'hooks/useUSDamountGetter';
import {
setTxDetails,
setSendTx,
Expand All @@ -25,56 +13,41 @@ import {
} from 'store/redeem';
import { setRoute as setAppRoute } from 'store/router';
import { setAmount, setIsTransactionInProgress } from 'store/transferInput';
import { getTransferDetails } from 'telemetry';
import { ERR_USER_REJECTED } from 'telemetry/types';
import { getTokenDecimals, getWrappedToken } from 'utils';
import { toDecimals } from 'utils/balance';
import { interpretTransferError } from 'utils/errors';
import { addTxToLocalStorage } from 'utils/inProgressTxCache';
import { validate, isTransferValid } from 'utils/transferValidation';
import {
registerWalletSigner,
switchChain,
TransferWallet,
} from 'utils/wallet';
import GasSlider from 'views/v2/Bridge/ReviewTransaction/GasSlider';
import SingleRoute from 'views/v2/Bridge/Routes/SingleRoute';

import type { RootState } from 'store';
import { RelayerFee } from 'store/relay';

import { amount as sdkAmount } from '@wormhole-foundation/sdk';
import { toDecimals } from 'utils/balance';
import { useUSDamountGetter } from 'hooks/useUSDamountGetter';
import SendError from './SendError';
import { ERR_USER_REJECTED } from 'telemetry/types';

const useStyles = makeStyles()((theme) => ({
container: {
gap: '16px',
width: '100%',
maxWidth: '420px',
},
confirmTransaction: {
padding: '8px 16px',
borderRadius: '8px',
margin: 'auto',
maxWidth: '420px',
width: '100%',
},
}));
import type { RelayerFee } from 'store/relay';
import type { QuoteResult } from 'routes/operator';

type Props = {
onClose: () => void;
quotes: any;
isFetchingQuotes: boolean;
quotes: Record<string, QuoteResult | undefined>;
};

const ReviewTransaction = (props: Props) => {
const { classes } = useStyles();
const dispatch = useDispatch();
const theme = useTheme();
type ReturnProps = {
error: string | undefined;
// errorInternal can be a result of custom validation, hence of any type.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
errorInternal: any | undefined;
onConfirm: () => void;
};

const mobile = useMediaQuery(theme.breakpoints.down('sm'));
const useSendTransaction = (props: Props): ReturnProps => {
const dispatch = useDispatch();

const [sendError, setSendError] = useState<string | undefined>(undefined);
const [sendErrorInternal, setSendErrorInternal] = useState<any | undefined>(
const [error, setError] = useState<string | undefined>(undefined);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [errorInternal, setErrorInternal] = useState<any | undefined>(
undefined,
);

Expand All @@ -88,7 +61,6 @@ const ReviewTransaction = (props: Props) => {
toChain: destChain,
token: sourceToken,
destToken,
isTransactionInProgress,
route,
validations,
} = transferInput;
Expand All @@ -99,26 +71,20 @@ const ReviewTransaction = (props: Props) => {
const relay = useSelector((state: RootState) => state.relay);
const { toNativeToken } = relay;

const getUSDAmount = useUSDamountGetter();

const { disabled: isGasSliderDisabled, showGasSlider } = useGasSlider({
destChain,
destToken,
route,
valid: true,
isTransactionInProgress,
});

const quoteResult = props.quotes[route ?? ''];
const quote = quoteResult?.success ? quoteResult : undefined;

const receiveNativeAmount = quote?.destinationNativeGas;

const send = async () => {
setSendError(undefined);
const getUSDAmount = useUSDamountGetter();

const onConfirm = async () => {
// Clear previous errors
if (error) {
setError(undefined);
}

if (config.ui.previewMode) {
setSendError('Connect is in preview mode');
setError('Connect is in preview mode');
return;
}

Expand All @@ -135,11 +101,11 @@ const ReviewTransaction = (props: Props) => {
return;
}

// Validate all inputs
// The results of this check will be written back to Redux store (see transferInput.validations).
await validate({ transferInput, relay, wallet }, dispatch, () => false);

const valid = isTransferValid(validations);

if (!valid || !route) {
if (!isTransferValid(validations)) {
return;
}

Expand All @@ -162,12 +128,12 @@ const ReviewTransaction = (props: Props) => {
toWalletAddress: receivingWallet.address,
});
if (!isValid) {
setSendError(error ?? 'Transfer validation failed');
setError(error ?? 'Transfer validation failed');
return;
}
} catch (e) {
setSendError('Error validating transfer');
setSendErrorInternal(e);
} catch (e: unknown) {
setError('Error validating transfer');
setErrorInternal(e);
console.error(e);
return;
}
Expand All @@ -178,9 +144,9 @@ const ReviewTransaction = (props: Props) => {
const sourceTokenConfig = config.tokens[sourceToken];

try {
const fromConfig = config.chains[sourceChain!];
const fromConfig = config.chains[sourceChain];

if (fromConfig?.context === Context.ETH) {
if (fromConfig && fromConfig?.context === Context.ETH) {
const chainId = fromConfig.chainId;

if (typeof chainId !== 'number') {
Expand Down Expand Up @@ -240,14 +206,15 @@ const ReviewTransaction = (props: Props) => {
}

const txTimestamp = Date.now();

const txDetails = {
sendTx: txId,
sender: sendingWallet.address,
amount,
recipient: receivingWallet.address,
toChain: receipt.to,
fromChain: receipt.from,
tokenAddress: getWrappedToken(sourceTokenConfig).tokenId!.address,
tokenAddress: getWrappedToken(sourceTokenConfig).tokenId?.address ?? '',
tokenKey: sourceTokenConfig.key,
tokenDecimals: getTokenDecimals(
sourceChain,
Expand Down Expand Up @@ -288,8 +255,8 @@ const ReviewTransaction = (props: Props) => {
dispatch(setSendTx(txId));
dispatch(setRedeemRoute(route));
dispatch(setAppRoute('redeem'));
setSendError(undefined);
} catch (e: any) {
setError(undefined);
} catch (e: unknown) {
const [uiError, transferError] = interpretTransferError(
e,
transferDetails,
Expand All @@ -302,8 +269,8 @@ const ReviewTransaction = (props: Props) => {
console.error('Wormhole Connect: error completing transfer', e);

// Show error in UI
setSendError(uiError);
setSendErrorInternal(e);
setError(uiError);
setErrorInternal(e);

// Trigger transfer error event to integrator
config.triggerEvent({
Expand All @@ -317,108 +284,11 @@ const ReviewTransaction = (props: Props) => {
}
};

const walletsConnected = useMemo(
() => !!sendingWallet.address && !!receivingWallet.address,
[sendingWallet.address, receivingWallet.address],
);

// Review transaction button is shown only when everything is ready
const confirmTransactionButton = useMemo(() => {
if (
!sourceChain ||
!sourceToken ||
!destChain ||
!destToken ||
!route ||
!amount
) {
return null;
}

return (
<Button
disabled={props.isFetchingQuotes || isTransactionInProgress}
variant="primary"
className={classes.confirmTransaction}
onClick={() => send()}
>
{isTransactionInProgress ? (
<Typography
display="flex"
alignItems="center"
gap={1}
textTransform="none"
>
<CircularProgress
size={16}
sx={{ color: theme.palette.primary.contrastText }}
/>
{mobile ? 'Preparing' : 'Preparing transaction'}
</Typography>
) : !isTransactionInProgress && props.isFetchingQuotes ? (
<Typography
display="flex"
alignItems="center"
gap={1}
textTransform="none"
>
<CircularProgress color="secondary" size={16} />
{mobile ? 'Refreshing' : 'Refreshing quote'}
</Typography>
) : (
<Typography textTransform="none">
{mobile ? 'Confirm' : 'Confirm transaction'}
</Typography>
)}
</Button>
);
}, [
props.isFetchingQuotes,
isTransactionInProgress,
sourceChain,
sourceToken,
destChain,
destToken,
route,
amount,
send,
]);

if (!route || !walletsConnected) {
return <></>;
}

return (
<Stack className={classes.container}>
<div>
<IconButton
disabled={isTransactionInProgress}
sx={{ padding: 0 }}
onClick={() => props.onClose?.()}
>
<ChevronLeft sx={{ fontSize: '32px' }} />
</IconButton>
</div>
<SingleRoute
route={RoutesConfig[route]}
isSelected={false}
destinationGasDrop={receiveNativeAmount}
quote={quote}
/>
{showGasSlider && (
<Collapse in={showGasSlider}>
<GasSlider
destinationGasDrop={
receiveNativeAmount || sdkAmount.fromBaseUnits(0n, 8)
}
disabled={isGasSliderDisabled}
/>
</Collapse>
)}
<SendError humanError={sendError} internalError={sendErrorInternal} />
{confirmTransactionButton}
</Stack>
);
return {
onConfirm,
error,
errorInternal,
};
};

export default ReviewTransaction;
export default useSendTransaction;
5 changes: 2 additions & 3 deletions wormhole-connect/src/hooks/useGasSlider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ type Props = {
destChain: Chain | undefined;
destToken: string;
route?: string;
valid: boolean;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If gas top-up is not available for the route, we don't show it in the first place; hence this property is no longer needed.

isTransactionInProgress: boolean;
};

Expand All @@ -17,9 +16,9 @@ export const useGasSlider = (
disabled: boolean;
showGasSlider: boolean | undefined;
} => {
const { destChain, destToken, route, isTransactionInProgress, valid } = props;
const { destChain, destToken, route, isTransactionInProgress } = props;

const disabled = !valid || isTransactionInProgress;
const disabled = isTransactionInProgress;
const toChainConfig = destChain ? config.chains[destChain] : undefined;
const gasTokenConfig = toChainConfig
? config.tokens[toChainConfig.gasToken]
Expand Down
Loading
Loading