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

Refresh quotes every 20 seconds #2650

Merged
Merged
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
44 changes: 37 additions & 7 deletions wormhole-connect/src/hooks/useRoutesQuotesBulk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,27 @@ type HookReturn = {
isFetching: boolean;
};

const QUOTE_REFRESH_INTERVAL = 20_000;

const MAYAN_BETA_LIMIT = 10_000; // USD
const MAYAN_BETA_PROTOCOLS = ['MCTP', 'SWIFT'];

const useRoutesQuotesBulk = (routes: string[], params: Params): HookReturn => {
const [nonce, setNonce] = useState(new Date().valueOf());
const [refreshTimeout, setRefreshTimeout] = useState<null | ReturnType<
typeof setTimeout
>>(null);

const [isFetching, setIsFetching] = useState(false);
const [quotes, setQuotes] = useState<QuoteResult[]>([]);

// TODO temporary
// Calculate USD amount for temporary $1000 Mayan limit
const tokenConfig = config.tokens[params.sourceToken];
const { usdPrices } = useSelector((state: RootState) => state.tokenPrices);
const { isTransactionInProgress } = useSelector(
(state: RootState) => state.transferInput,
);
const usdAmount = calculateUSDPriceRaw(
params.amount,
usdPrices.data,
Expand All @@ -60,16 +70,34 @@ const useRoutesQuotesBulk = (routes: string[], params: Params): HookReturn => {
// Forcing TS to infer that fields are non-optional
const rParams = params as Required<QuoteParams>;

setIsFetching(true);
config.routes.getQuotes(routes, rParams).then((quoteResults) => {
if (!unmounted) {
setQuotes(quoteResults);
setIsFetching(false);
}
});
const onComplete = () => {
// Refresh quotes in 20 seconds
const refreshTimeout = setTimeout(
() => setNonce(new Date().valueOf()),
QUOTE_REFRESH_INTERVAL,
);
setRefreshTimeout(refreshTimeout);
};

if (isTransactionInProgress) {
// Don't fetch new quotes if the user has committed to one and has initiated a transaction
onComplete();
kev1n-peters marked this conversation as resolved.
Show resolved Hide resolved
} else {
setIsFetching(true);
config.routes.getQuotes(routes, rParams).then((quoteResults) => {
if (!unmounted) {
setQuotes(quoteResults);
setIsFetching(false);
onComplete();
}
});
}

return () => {
unmounted = true;
if (refreshTimeout) {
clearTimeout(refreshTimeout);
}
};
}, [
routes.join(),
Expand All @@ -79,6 +107,8 @@ const useRoutesQuotesBulk = (routes: string[], params: Params): HookReturn => {
params.destToken,
params.amount,
params.nativeGas,
nonce,
artursapek marked this conversation as resolved.
Show resolved Hide resolved
isTransactionInProgress,
]);

const quotesMap = useMemo(
Expand Down
29 changes: 15 additions & 14 deletions wormhole-connect/src/views/v2/Bridge/ReviewTransaction/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import GasSlider from 'views/v2/Bridge/ReviewTransaction/GasSlider';
import SingleRoute from 'views/v2/Bridge/Routes/SingleRoute';

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

import { amount as sdkAmount } from '@wormhole-foundation/sdk';
Expand All @@ -61,6 +60,8 @@ const useStyles = makeStyles()((theme) => ({

type Props = {
onClose: () => void;
quotes: any;
isFetchingQuotes: boolean;
};

const ReviewTransaction = (props: Props) => {
Expand Down Expand Up @@ -103,16 +104,7 @@ const ReviewTransaction = (props: Props) => {
isTransactionInProgress,
});

const routes = useMemo(() => (route ? [route] : []), []);
const { quotesMap, isFetching } = useRoutesQuotesBulk(routes, {
artursapek marked this conversation as resolved.
Show resolved Hide resolved
amount,
sourceChain,
sourceToken,
destChain,
destToken,
nativeGas: toNativeToken,
});
const quoteResult = quotesMap[route ?? ''];
const quoteResult = props.quotes[route ?? ''];
const quote = quoteResult?.success ? quoteResult : undefined;

const receiveNativeAmount = quote?.destinationNativeGas
Expand Down Expand Up @@ -324,7 +316,7 @@ const ReviewTransaction = (props: Props) => {

return (
<Button
disabled={isFetching || isTransactionInProgress}
disabled={props.isFetchingQuotes || isTransactionInProgress}
variant="primary"
className={classes.confirmTransaction}
onClick={() => send()}
Expand All @@ -339,6 +331,16 @@ const ReviewTransaction = (props: Props) => {
<CircularProgress color="secondary" size={16} />
{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'}
Expand All @@ -347,7 +349,7 @@ const ReviewTransaction = (props: Props) => {
</Button>
);
}, [
isFetching,
props.isFetchingQuotes,
isTransactionInProgress,
sourceChain,
sourceToken,
Expand Down Expand Up @@ -375,7 +377,6 @@ const ReviewTransaction = (props: Props) => {
destinationGasDrop={receiveNativeAmount}
title="You will receive"
quote={quote}
isFetchingQuote={isFetching}
/>
<Collapse in={showGasSlider}>
<GasSlider
Expand Down
55 changes: 18 additions & 37 deletions wormhole-connect/src/views/v2/Bridge/Routes/SingleRoute.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import { useSelector } from 'react-redux';
import { CircularProgress, useTheme } from '@mui/material';
import { useTheme } from '@mui/material';
import Card from '@mui/material/Card';
import CardActionArea from '@mui/material/CardActionArea';
import CardContent from '@mui/material/CardContent';
Expand Down Expand Up @@ -62,7 +62,6 @@ type Props = {
isOnlyChoice?: boolean;
onSelect?: (route: string) => void;
quote?: routes.Quote<routes.Options>;
isFetchingQuote: boolean;
};

const SingleRoute = (props: Props) => {
Expand All @@ -82,7 +81,7 @@ const SingleRoute = (props: Props) => {
);

const { name } = props.route;
const { quote, isFetchingQuote } = props;
const { quote } = props;

const destTokenConfig = useMemo(
() => config.tokens[destToken] as TokenConfig | undefined,
Expand Down Expand Up @@ -121,9 +120,7 @@ const SingleRoute = (props: Props) => {
return <></>;
}

let feeValue = isFetchingQuote ? (
<CircularProgress size={14} />
) : (
let feeValue = (
<Typography fontSize={14}>{`${toFixedDecimals(relayFee.toString(), 4)} ${
feeTokenConfig.symbol
} (${feePrice})`}</Typography>
Expand All @@ -144,7 +141,7 @@ const SingleRoute = (props: Props) => {
</Typography>
</Stack>
);
}, [destToken, isFetchingQuote, quote?.relayFee, tokenPrices]);
}, [destToken, quote?.relayFee, tokenPrices]);

const destinationGas = useMemo(() => {
if (!destChain || !props.destinationGasDrop) {
Expand All @@ -169,14 +166,10 @@ const SingleRoute = (props: Props) => {
<Typography color={theme.palette.text.secondary} fontSize={14}>
Gas top up
</Typography>
{isFetchingQuote ? (
<CircularProgress size={14} />
) : (
<Typography fontSize={14}>{gasTokenPrice}</Typography>
)}
<Typography fontSize={14}>{gasTokenPrice}</Typography>
</Stack>
);
}, [destChain, isFetchingQuote, props.destinationGasDrop]);
}, [destChain, props.destinationGasDrop]);

const timeToDestination = useMemo(
() => (
Expand All @@ -185,24 +178,20 @@ const SingleRoute = (props: Props) => {
Time to destination
</Typography>

{isFetchingQuote ? (
<CircularProgress size={14} />
) : (
<Typography
fontSize={14}
sx={{
color:
quote?.eta && quote.eta < 60 * 1000
? theme.palette.success.main
: theme.palette.text.secondary,
}}
>
{quote?.eta ? millisToHumanString(quote.eta) : 'N/A'}
</Typography>
)}
<Typography
fontSize={14}
sx={{
color:
quote?.eta && quote.eta < 60 * 1000
? theme.palette.success.main
: theme.palette.text.secondary,
}}
>
{quote?.eta ? millisToHumanString(quote.eta) : 'N/A'}
</Typography>
</>
),
[quote?.eta, isFetchingQuote],
[quote?.eta],
);

const isManual = useMemo(() => {
Expand Down Expand Up @@ -310,10 +299,6 @@ const SingleRoute = (props: Props) => {
return <Typography color="error">Route is unavailable</Typography>;
}

if (props.isFetchingQuote) {
return <CircularProgress size={18} />;
}

if (receiveAmount === undefined || !destTokenConfig) {
return null;
}
Expand All @@ -330,10 +315,6 @@ const SingleRoute = (props: Props) => {
return null;
}

if (props.isFetchingQuote) {
return <CircularProgress size={18} />;
}

if (receiveAmount === undefined) {
return null;
}
Expand Down
89 changes: 48 additions & 41 deletions wormhole-connect/src/views/v2/Bridge/Routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import AlertBannerV2 from 'components/v2/AlertBanner';
import type { RootState } from 'store';
import { RouteState } from 'store/transferInput';
import { routes } from '@wormhole-foundation/sdk';
import { CircularProgress } from '@mui/material';
import { Box, CircularProgress, Skeleton } from '@mui/material';

const useStyles = makeStyles()((theme: any) => ({
connectWallet: {
Expand Down Expand Up @@ -144,10 +144,6 @@ const Routes = ({ ...props }: Props) => {
return null;
}

if (props.isLoading) {
return <CircularProgress />;
}

if (walletsConnected && !(Number(amount) > 0)) {
return (
<Tooltip title="Please enter the amount to view available routes">
Expand All @@ -160,42 +156,53 @@ const Routes = ({ ...props }: Props) => {

return (
<>
<Typography
fontSize={16}
paddingBottom={0}
marginTop="8px"
marginBottom={0}
width="100%"
textAlign="left"
>
Routes
</Typography>
{renderRoutes.map(({ name }, index) => {
const routeConfig = RoutesConfig[name];
const isSelected = routeConfig.name === props.selectedRoute;
const quoteResult = props.quotes[name];
const quote = quoteResult?.success ? quoteResult : undefined;
// Default message added as precaution, as 'Error' type cannot be trusted
const quoteError =
quoteResult?.success === false
? quoteResult?.error?.message ??
`Error while getting a quote for ${name}.`
: undefined;
return (
<SingleRoute
key={name}
route={routeConfig}
error={quoteError}
isSelected={isSelected && !quoteError}
isFastest={name === fastestRoute.name}
isCheapest={name === cheapestRoute.name}
isOnlyChoice={supportedRoutes.length === 1}
onSelect={props.onRouteChange}
quote={quote}
isFetchingQuote={props.isLoading}
/>
);
})}
<Box sx={{ display: 'flex', width: '100%' }}>
<Typography
align="left"
fontSize={16}
paddingBottom={0}
marginTop="8px"
marginBottom={0}
width="100%"
textAlign="left"
>
Routes
</Typography>
{props.isLoading ? (
<CircularProgress sx={{ 'align-self': 'flex-end' }} size={20} />
) : null}
</Box>

{props.isLoading && renderRoutes.length === 0 ? (
<Skeleton variant="rounded" height={153} width="100%" />
) : (
renderRoutes.map(({ name }, index) => {
const routeConfig = RoutesConfig[name];
const isSelected = routeConfig.name === props.selectedRoute;
const quoteResult = props.quotes[name];
const quote = quoteResult?.success ? quoteResult : undefined;
// Default message added as precaution, as 'Error' type cannot be trusted
const quoteError =
quoteResult?.success === false
? quoteResult?.error?.message ??
`Error while getting a quote for ${name}.`
: undefined;
return (
<SingleRoute
key={name}
route={routeConfig}
error={quoteError}
isSelected={isSelected && !quoteError}
isFastest={name === fastestRoute.name}
isCheapest={name === cheapestRoute.name}
isOnlyChoice={supportedRoutes.length === 1}
onSelect={props.onRouteChange}
quote={quote}
/>
);
})
)}

{props.routes.length > 1 && (
<Link
onClick={() => setShowAll((prev) => !prev)}
Expand Down
Loading
Loading