diff --git a/wormhole-connect/src/components/AlertBanner.tsx b/wormhole-connect/src/components/AlertBanner.tsx
index 63e2c13a1..bd03a1926 100644
--- a/wormhole-connect/src/components/AlertBanner.tsx
+++ b/wormhole-connect/src/components/AlertBanner.tsx
@@ -2,6 +2,7 @@ import { Collapse } from '@mui/material';
import React from 'react';
import { makeStyles } from 'tss-react/mui';
import AlertIcon from 'icons/Alert';
+import InfoIcon from 'icons/Info';
import { OPACITY, joinClass } from 'utils/style';
const useStyles = makeStyles()((theme: any) => ({
@@ -21,6 +22,9 @@ const useStyles = makeStyles()((theme: any) => ({
warning: {
backgroundColor: theme.palette.warning[500] + OPACITY[25],
},
+ info: {
+ backgroundColor: theme.palette.info[500] + OPACITY[25],
+ },
}));
type Props = {
@@ -28,6 +32,7 @@ type Props = {
content: React.ReactNode | undefined;
warning?: boolean;
error?: boolean;
+ info?: boolean;
margin?: string;
testId?: string;
};
@@ -42,11 +47,12 @@ function AlertBanner(props: Props) {
classes.base,
!!props.warning && classes.warning,
!!props.error && classes.error,
+ !!props.info && classes.info,
])}
style={{ margin: props.margin || 0 }}
data-testid={props.testId}
>
-
+ {props.info ? : }
{props.content}
diff --git a/wormhole-connect/src/store/transferInput.ts b/wormhole-connect/src/store/transferInput.ts
index 1d2d61c56..a8ca645ee 100644
--- a/wormhole-connect/src/store/transferInput.ts
+++ b/wormhole-connect/src/store/transferInput.ts
@@ -132,6 +132,7 @@ export interface TransferInputState {
supportedDestTokens: TokenConfig[];
manualAddressTarget: boolean;
showManualAddressInput: boolean;
+ resolvingRoutes: boolean;
}
// This is a function because config might have changed since we last cleared this store
@@ -174,6 +175,7 @@ function getInitialState(): TransferInputState {
supportedDestTokens: [],
manualAddressTarget: false,
showManualAddressInput: config.manualTargetAddress || false,
+ resolvingRoutes: false,
};
}
@@ -297,6 +299,12 @@ export const transferInputSlice = createSlice({
name: 'transfer',
initialState: getInitialState(),
reducers: {
+ setResolvingRoutes(
+ state: TransferInputState,
+ { payload }: PayloadAction,
+ ) {
+ state.resolvingRoutes = payload;
+ },
// validations
setValidations: (
state: TransferInputState,
@@ -574,6 +582,7 @@ export const {
swapChains,
setManualAddressTarget,
showManualAddressInput,
+ setResolvingRoutes,
} = transferInputSlice.actions;
export default transferInputSlice.reducer;
diff --git a/wormhole-connect/src/utils/transferValidation.ts b/wormhole-connect/src/utils/transferValidation.ts
index d3e6d75e9..192b55b1d 100644
--- a/wormhole-connect/src/utils/transferValidation.ts
+++ b/wormhole-connect/src/utils/transferValidation.ts
@@ -183,7 +183,9 @@ export const validateToNativeAmt = (
export const validateRoute = (
route: Route | undefined,
availableRoutes: string[] | undefined,
+ resolvingRoutes = false,
): ValidationErr => {
+ if (resolvingRoutes) return '';
if (!route || !availableRoutes || !availableRoutes.includes(route)) {
return 'No bridge or swap route available for selected tokens';
}
@@ -295,6 +297,7 @@ export const validateAll = async (
routeStates,
receiveAmount,
manualAddressTarget,
+ resolvingRoutes,
} = transferData;
const { maxSwapAmt, toNativeToken } = relayData;
const { sending, receiving } = walletData;
@@ -326,7 +329,7 @@ export const validateAll = async (
maxSendAmount,
isCctpTx,
),
- route: validateRoute(route, availableRoutes),
+ route: validateRoute(route, availableRoutes, resolvingRoutes),
toNativeToken: '',
foreignAsset: validateForeignAsset(foreignAsset),
relayerFee: '',
diff --git a/wormhole-connect/src/views/Bridge/Bridge.tsx b/wormhole-connect/src/views/Bridge/Bridge.tsx
index 909b68b67..896e1b6a1 100644
--- a/wormhole-connect/src/views/Bridge/Bridge.tsx
+++ b/wormhole-connect/src/views/Bridge/Bridge.tsx
@@ -53,6 +53,7 @@ import { isNttRoute } from 'routes/utils';
import { useConnectToLastUsedWallet } from 'utils/wallet';
import { USDTBridge } from 'routes/porticoBridge/usdtBridge';
import { isAutomatic } from 'utils/route';
+import AlertBanner from 'components/AlertBanner';
const useStyles = makeStyles()((_theme) => ({
spacer: {
@@ -105,6 +106,7 @@ function Bridge() {
isTransactionInProgress,
amount,
manualAddressTarget,
+ resolvingRoutes,
}: TransferInputState = useSelector(
(state: RootState) => state.transferInput,
);
@@ -351,6 +353,13 @@ function Bridge() {
+
{
let isActive = true;
- if (!fromChain || !toChain || !token || !destToken) return;
- const getAvailable = async () => {
+ const getAvailable = async (fromChain: ChainName, toChain: ChainName) => {
const routes: RouteState[] = [];
for (const value of config.routes) {
const r = value as Route;
- const available = await RouteOperator.isRouteAvailable(
- r,
- token,
- destToken,
- debouncedAmount,
- fromChain,
- toChain,
- );
-
- const supported = await RouteOperator.isRouteSupported(
- r,
- token,
- destToken,
- debouncedAmount,
- fromChain,
- toChain,
- );
-
+ // don't await, we want to resolve all routes in parallel
+ const [available, supported] = await Promise.all([
+ RouteOperator.isRouteAvailable(
+ r,
+ token,
+ destToken,
+ debouncedAmount,
+ fromChain,
+ toChain,
+ ),
+ RouteOperator.isRouteSupported(
+ r,
+ token,
+ destToken,
+ debouncedAmount,
+ fromChain,
+ toChain,
+ ),
+ ]);
routes.push({ name: r, supported, availability: available });
}
if (isActive) {
dispatch(setRoutes(routes));
}
+ dispatch(setResolvingRoutes(false));
};
- getAvailable();
+
+ if (!fromChain || !toChain || !token || !destToken) {
+ dispatch(setRoutes([])); // reset routes if we don't have all the info
+ } else {
+ dispatch(setRoutes([])); // defensive remove routes until we have all the info to decide if the routes are availables between renders
+ dispatch(setResolvingRoutes(true));
+ getAvailable(fromChain, toChain);
+ }
return () => {
isActive = false;