Skip to content

Commit

Permalink
feat: connect executing routes to wallet address
Browse files Browse the repository at this point in the history
  • Loading branch information
chybisov committed Jul 26, 2022
1 parent 5293eed commit 37a736b
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 74 deletions.
10 changes: 6 additions & 4 deletions packages/widget/src/components/SwapInProgress/SwapInProgress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ import { Box, BoxProps, Stack } from '@mui/material';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { useWallet } from '../../providers/WalletProvider';
import { useExecutingRoutes } from '../../stores';
import { navigationRoutes } from '../../utils';
import { CardTitle } from '../Card';
import { TokenAvatar, TokenAvatarGroup } from '../TokenAvatar';
import { Card, RouteCard } from './SwapInProgress.style';
import { ProgressCard, RouteCard } from './SwapInProgress.style';

export const SwapInProgress: React.FC<BoxProps> = (props) => {
const { t } = useTranslation();
const navigate = useNavigate();
const executingRoutes = useExecutingRoutes();
const { account } = useWallet();
const executingRoutes = useExecutingRoutes(account.address);

const handleCardClick = useCallback(
(routeId: string) => {
Expand All @@ -29,7 +31,7 @@ export const SwapInProgress: React.FC<BoxProps> = (props) => {
}

return (
<Card {...props}>
<ProgressCard {...props}>
<CardTitle>{t('swap.inProgress')}</CardTitle>
<Stack spacing={2} py={2}>
{executingRoutes.map(({ route, status }) => (
Expand All @@ -53,6 +55,6 @@ export const SwapInProgress: React.FC<BoxProps> = (props) => {
/>
))}
</Stack>
</Card>
</ProgressCard>
);
};
54 changes: 32 additions & 22 deletions packages/widget/src/hooks/useRouteExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { deepClone } from '../utils';
export const useRouteExecution = (routeId: string) => {
const { account, switchChain } = useWallet();
const resumedAfterMount = useRef(false);
const { route, status } = useRouteStore((state) => state.routes[routeId]);
const [updateRoute, restartRoute, removeRoute] = useRouteStore(
(state) => [state.updateRoute, state.restartRoute, state.removeRoute],
const routeExecution = useRouteStore((state) => state.routes[routeId]);
const [updateRoute, restartRoute, deleteRoute] = useRouteStore(
(state) => [state.updateRoute, state.restartRoute, state.deleteRoute],
shallow,
);

Expand Down Expand Up @@ -42,10 +42,10 @@ export const useRouteExecution = (routeId: string) => {
if (!account.signer) {
throw Error('Account signer not found.');
}
if (!route) {
if (!routeExecution?.route) {
throw Error('Execution route not found.');
}
return LiFi.executeRoute(account.signer, route, {
return LiFi.executeRoute(account.signer, routeExecution.route, {
updateCallback,
switchChainHook,
infiniteApproval: false,
Expand All @@ -69,14 +69,18 @@ export const useRouteExecution = (routeId: string) => {
if (!account.signer) {
throw Error('Account signer not found.');
}
if (!route) {
if (!routeExecution?.route) {
throw Error('Execution route not found.');
}
return LiFi.resumeRoute(account.signer, resumedRoute ?? route, {
updateCallback,
switchChainHook,
infiniteApproval: false,
});
return LiFi.resumeRoute(
account.signer,
resumedRoute ?? routeExecution.route,
{
updateCallback,
switchChainHook,
infiniteApproval: false,
},
);
},
{
onMutate: () => {
Expand Down Expand Up @@ -120,24 +124,26 @@ export const useRouteExecution = (routeId: string) => {

const restartRouteMutation = useCallback(() => {
restartRoute(routeId);
resumeRoute(route);
resumeRoute(routeExecution?.route);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [resumeRoute, route, routeId]);
}, [resumeRoute, routeExecution?.route, routeId]);

const removeRouteMutation = useCallback(() => {
removeRoute(routeId);
const deleteRouteMutation = useCallback(() => {
deleteRoute(routeId);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [routeId]);

useEffect(() => {
// check if route is eligible for automatic resuming
const isDone = route.steps.every(
const isDone = routeExecution?.route.steps.every(
(step) => step.execution?.status === 'DONE',
);
const isFailed = route.steps.some(
const isFailed = routeExecution?.route.steps.some(
(step) => step.execution?.status === 'FAILED',
);
const alreadyStarted = route.steps.some((step) => step.execution);
const alreadyStarted = routeExecution?.route.steps.some(
(step) => step.execution,
);
if (
!isDone &&
!isFailed &&
Expand All @@ -148,15 +154,19 @@ export const useRouteExecution = (routeId: string) => {
resumedAfterMount.current = true;
resumeRoute();
}
return () => LiFi.moveExecutionToBackground(route);
return () => {
if (routeExecution?.route) {
LiFi.moveExecutionToBackground(routeExecution.route);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [account.signer]);

return {
executeRoute,
restartRoute: restartRouteMutation,
removeRoute: removeRouteMutation,
route,
status,
deleteRoute: deleteRouteMutation,
route: routeExecution?.route,
status: routeExecution?.status,
};
};
31 changes: 15 additions & 16 deletions packages/widget/src/pages/SwapPage/SwapPage.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import { Box } from '@mui/material';
import { Box, Button } from '@mui/material';
import { Fragment } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation, useNavigate } from 'react-router-dom';
import { GasSufficiencyMessage } from '../../components/GasSufficiencyMessage';
import { Step } from '../../components/Step';
import { StepDivider } from '../../components/StepDivider';
import { SwapButton } from '../../components/SwapButton';
import { useRouteExecution } from '../../hooks';
import { StatusBottomSheet } from './StatusBottomSheet';
import { StepDivider } from './StepDivider';
import { StepItem } from './StepItem';
import { Button, Container } from './SwapPage.style';
import { Container } from './SwapPage.style';

export const SwapPage: React.FC = () => {
const { t } = useTranslation();
const { state }: any = useLocation();
const navigate = useNavigate();
const { route, status, executeRoute, restartRoute, removeRoute } =
const { route, status, executeRoute, restartRoute, deleteRoute } =
useRouteExecution(state?.routeId);

const handleRemoveRoute = () => {
removeRoute();
navigate(-1);
deleteRoute();
};

const handleSwapClick = () => {
Expand All @@ -45,7 +45,7 @@ export const SwapPage: React.FC = () => {
<Container>
{route?.steps.map((step, index, steps) => (
<Fragment key={step.id}>
<StepItem
<Step
step={step}
fromToken={
index === 0
Expand Down Expand Up @@ -75,16 +75,15 @@ export const SwapPage: React.FC = () => {
</Box>
) : null}
{status === 'error' ? (
<Button
variant="outlined"
disableElevation
fullWidth
onClick={handleRemoveRoute}
>
{t('button.removeSwap')}
</Button>
<Box mt={2}>
<Button variant="outlined" onClick={handleRemoveRoute} fullWidth>
{t('button.removeSwap')}
</Button>
</Box>
) : null}
{route && status ? (
<StatusBottomSheet status={status} route={route} />
) : null}
<StatusBottomSheet status={status} route={route} />
</Container>
);
};
4 changes: 2 additions & 2 deletions packages/widget/src/stores/route/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Route } from '@lifi/sdk';

export interface RouteExecutionStore {
routes: Record<string, RouteExecution>;
routes: Partial<Record<string, RouteExecution>>;
setExecutableRoute: (route: Route) => void;
updateRoute: (route: Route) => void;
restartRoute: (routeId: string) => void;
removeRoute: (routeId: string) => void;
deleteRoute: (routeId: string) => void;
}

export type RouteExecutionStatus = 'error' | 'idle' | 'loading' | 'success';
Expand Down
16 changes: 11 additions & 5 deletions packages/widget/src/stores/route/useExecutingRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import shallow from 'zustand/shallow';
import { RouteExecution } from './types';
import { useRouteStore } from './useRouteStore';

export const useExecutingRoutes = () => {
return useRouteStore((state) =>
Object.values(state.routes).filter(
(route) => route.status === 'loading' || route.status === 'error',
),
export const useExecutingRoutes = (address?: string) => {
return useRouteStore(
(state) =>
Object.values(state.routes).filter(
(item) =>
item?.route.fromAddress === address &&
(item?.status === 'loading' || item?.status === 'error'),
) as RouteExecution[],
shallow,
);
};
52 changes: 27 additions & 25 deletions packages/widget/src/stores/route/useRouteStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const useRouteStore = create<RouteExecutionStore>()(
if (!state.routes[route.id]) {
// clean previous idle routes that were not executed
Object.keys(state.routes)
.filter((routeId) => state.routes[routeId].status === 'idle')
.filter((routeId) => state.routes[routeId]?.status === 'idle')
.forEach((routeId) => delete state.routes[routeId]);
state.routes[route.id] = {
route,
Expand All @@ -23,29 +23,31 @@ export const useRouteStore = create<RouteExecutionStore>()(
}),
updateRoute: (route: Route) =>
set((state: RouteExecutionStore) => {
state.routes[route.id].route = route;
const isFailed = route.steps.some(
(step) => step.execution?.status === 'FAILED',
);
if (isFailed) {
state.routes[route.id].status = 'error';
return;
}
const isDone = route.steps.every(
(step) => step.execution?.status === 'DONE',
);
if (isDone) {
state.routes[route.id].status = 'success';
return;
}
const isLoading = route.steps.some((step) => step.execution);
if (isLoading) {
state.routes[route.id].status = 'loading';
if (state.routes[route.id]) {
state.routes[route.id]!.route = route;
const isFailed = route.steps.some(
(step) => step.execution?.status === 'FAILED',
);
if (isFailed) {
state.routes[route.id]!.status = 'error';
return;
}
const isDone = route.steps.every(
(step) => step.execution?.status === 'DONE',
);
if (isDone) {
state.routes[route.id]!.status = 'success';
return;
}
const isLoading = route.steps.some((step) => step.execution);
if (isLoading) {
state.routes[route.id]!.status = 'loading';
}
}
}),
restartRoute: (routeId: string) =>
set((state: RouteExecutionStore) => {
state.routes[routeId].route.steps.forEach((step) => {
state.routes[routeId]?.route.steps.forEach((step) => {
const stepHasFailed = step.execution?.status === 'FAILED';
// check if the step has been cancelled which is a "failed" state
const stepHasBeenCancelled = step.execution?.process.some(
Expand All @@ -57,13 +59,13 @@ export const useRouteStore = create<RouteExecutionStore>()(
step.execution.process.pop();
}
});
state.routes[routeId].status = 'loading';
state.routes[routeId]!.status = 'loading';
}),
removeRoute: (routeId: string) =>
deleteRoute: (routeId: string) =>
set((state: RouteExecutionStore) => {
// TODO: handle immediate removal
// delete state.routes[routeId];
state.routes[routeId].status = 'idle';
if (state.routes[routeId]) {
delete state.routes[routeId];
}
}),
})),
{
Expand Down

0 comments on commit 37a736b

Please sign in to comment.