Skip to content

Commit

Permalink
Step by step swap modal
Browse files Browse the repository at this point in the history
  • Loading branch information
vrtnd committed Sep 4, 2023
1 parent 3aa9ab6 commit 10e7acf
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 63 deletions.
24 changes: 18 additions & 6 deletions src/components/Aggregator/hooks/useTokenApprove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,22 @@ export const useGetAllowance = (token: `0x${string}`, spender: `0x${string}`, am
return { allowance, shouldRemoveApproval, refetch, isRefetching };
};

const setOverrides = (func, overrides) => {
const setOverrides = (func, overrides, onApprove = () => {}) => {
if (!overrides) return func;

return () => func({ recklesslySetUnpreparedOverrides: overrides });
return () => {
onApprove();
return func({ recklesslySetUnpreparedOverrides: overrides });
};
};

export const useTokenApprove = (
token: `0x${string}`,
spender: `0x${string}`,
amount: string,
onSuccess: () => void = () => {}
onApprove: () => void = () => {},
onSuccess: () => void = () => {},
afterApprove: () => void = () => {}
) => {
const [isConfirmingApproval, setIsConfirmingApproval] = useState(false);
const [isConfirmingInfiniteApproval, setIsConfirmingInfiniteApproval] = useState(false);
Expand Down Expand Up @@ -103,10 +108,12 @@ export const useTokenApprove = (
onSuccess: (data) => {
setIsConfirmingApproval(true);
onSuccess();
refetch();

data
.wait()
.then(() => {
afterApprove();
refetch();
})
.catch((err) => console.log(err))
Expand All @@ -121,9 +128,12 @@ export const useTokenApprove = (
onSuccess: (data) => {
setIsConfirmingInfiniteApproval(true);
onSuccess();
refetch();

data
.wait()
.then(() => {
afterApprove();
refetch();
})
.catch((err) => console.log(err))
Expand All @@ -138,9 +148,11 @@ export const useTokenApprove = (
onSuccess: (data) => {
setIsConfirmingResetApproval(true);
onSuccess();
refetch();
data
.wait()
.then(() => {
afterApprove();
refetch();
})
.catch((err) => console.log(err))
Expand All @@ -161,9 +173,9 @@ export const useTokenApprove = (

return {
isApproved: false,
approve: setOverrides(approve, customGasLimit),
approveInfinite: setOverrides(approveInfinite, customGasLimit),
approveReset: setOverrides(approveReset, customGasLimit),
approve: setOverrides(approve, customGasLimit, onApprove),
approveInfinite: setOverrides(approveInfinite, customGasLimit, onApprove),
approveReset: setOverrides(approveReset, customGasLimit, onApprove),
isLoading: isLoading || isConfirmingApproval,
isConfirmingApproval,
isInfiniteLoading: isInfiniteLoading || isConfirmingInfiniteApproval,
Expand Down
37 changes: 24 additions & 13 deletions src/components/Aggregator/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ enum STATES {
ROUTES
}

export enum SWAP_STATES {
SELECTING_ROUTES,
WAITING_FOR_APPROVAL,
CONFIRMING_APPROVAL,
WAITING_FOR_SWAP,
CONFIRMING_SWAP
}

const Body = styled.div`
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -330,10 +338,9 @@ export function AggregatorContainer({ tokenList, sandwichList }) {
const toggleUi = () => setUiState((state) => (state === STATES.INPUT ? STATES.ROUTES : STATES.INPUT));

// post approval
const [approvalModalOpen, setApprovalModelOpen] = useState(false);
const [swapState, setSwapState] = useState(SWAP_STATES.SELECTING_ROUTES);

// post swap states
const [txModalOpen, setTxModalOpen] = useState(false);
const [txUrl, setTxUrl] = useState('');
const confirmingTxToastRef = useRef<ToastId>();
const toast = useToast();
Expand Down Expand Up @@ -624,10 +631,6 @@ export function AggregatorContainer({ tokenList, sandwichList }) {
});
};

const onApprove = () => {
setApprovalModelOpen(true);
};

useEffect(() => {
const isUnknown =
selectedToToken === null &&
Expand Down Expand Up @@ -716,7 +719,9 @@ export function AggregatorContainer({ tokenList, sandwichList }) {
finalSelectedFromToken?.address as `0x${string}`,
selectedRoute && selectedRoute.price ? selectedRoute.price.tokenApprovalAddress : null,
amountToApprove,
onApprove
() => setSwapState(SWAP_STATES.WAITING_FOR_APPROVAL),
() => setSwapState(SWAP_STATES.CONFIRMING_APPROVAL),
() => setSwapState(SWAP_STATES.WAITING_FOR_SWAP)
);

const isUSDTNotApprovedOnEthereum =
Expand Down Expand Up @@ -744,11 +749,10 @@ export function AggregatorContainer({ tokenList, sandwichList }) {
description: `Swap transaction using ${variables.adapter} is sent.`
});
const explorerUrl = chainOnWallet.blockExplorers.default.url;
setTxModalOpen(true);

txUrl = `${explorerUrl}/tx/${data.hash}`;
setTxUrl(txUrl);
} else {
setTxModalOpen(true);
txUrl = `https://explorer.cow.fi/orders/${data.id}`;
setTxUrl(txUrl);
data.waitForOrder(() => {
Expand All @@ -775,6 +779,8 @@ export function AggregatorContainer({ tokenList, sandwichList }) {
});
}

setSwapState(SWAP_STATES.CONFIRMING_SWAP);

confirmingTxToastRef.current = toast({
title: 'Confirming Transaction',
description: '',
Expand Down Expand Up @@ -863,6 +869,7 @@ export function AggregatorContainer({ tokenList, sandwichList }) {
});
return;
}

swapMutation.mutate({
chain: selectedChain.value,
from: finalSelectedFromToken.value,
Expand Down Expand Up @@ -1451,13 +1458,17 @@ export function AggregatorContainer({ tokenList, sandwichList }) {

{window === parent ? <FAQs /> : null}

<TransactionModal open={txModalOpen} setOpen={setTxModalOpen} link={txUrl} />
<SwapModal
open={approvalModalOpen}
setOpen={setApprovalModelOpen}
isLoading={isConfirmingApproval || isConfirmingInfiniteApproval}
txUrl={txUrl}
isOpen={swapState !== SWAP_STATES.SELECTING_ROUTES}
close={() => {
setTxUrl('');
setSwapState(SWAP_STATES.SELECTING_ROUTES);
}}
state={swapState}
swap={handleSwap}
fromToken={finalSelectedFromToken}
toToken={finalSelectedToToken}
/>
</Wrapper>
);
Expand Down
136 changes: 92 additions & 44 deletions src/components/SwapModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CheckCircleIcon, TriangleDownIcon } from '@chakra-ui/icons';
import { CheckIcon, EditIcon, ExternalLinkIcon, UnlockIcon } from '@chakra-ui/icons';
import {
Modal,
ModalBody,
Expand All @@ -7,69 +7,117 @@ import {
ModalOverlay,
Text,
Spinner,
Button
Button,
Box,
Link
} from '@chakra-ui/react';
import { IToken } from '~/types';
import { SWAP_STATES } from '../Aggregator';
import { IconImage } from '../Aggregator/Search';

interface Props {
open: boolean;
setOpen: (value: boolean) => void;
isOpen: boolean;
close: () => void;
swap: () => void;
isLoading: boolean;
fromToken: IToken;
toToken: IToken;
state: SWAP_STATES;
txUrl?: string;
}

export const SwapModal = ({ open, setOpen, swap, isLoading, fromToken }: Props) => {
return (
<Modal
isCentered
motionPreset="slideInBottom"
closeOnOverlayClick={true}
isOpen={open}
onClose={() => setOpen(false)}
>
<ModalOverlay />
<ModalContent>
<ModalCloseButton color="white" />
export const SwapModal = ({ isOpen, close, swap, state, fromToken, toToken, txUrl }: Props) => {
const states = {
[SWAP_STATES.WAITING_FOR_APPROVAL]: (
<Box display={'flex'} flexDirection="column" alignItems={'center'} mt="16px">
<UnlockIcon w={16} h={16} color="blue.300" mb="8px" />
<Text fontSize={'lg'} fontWeight="bold" mt="8px">
Enable spending {fromToken?.symbol} on LlamaSwap
</Text>
<Text fontSize={'md'}>Proceed in your wallet</Text>
</Box>
),

<ModalBody display="flex" gap="8px" flexDir="column" alignItems="center" marginY="4rem" color="white">
<Text display={'flex'} fontSize="xl" fontWeight={'bold'}>
Swapping {fromToken?.symbol}
<IconImage
style={{ marginLeft: '10px', height: '26px', marginTop: '2px' }}
src={fromToken?.logoURI}
onError={(e) => (e.currentTarget.src = fromToken?.logoURI2 || '/placeholder.png')}
/>
[SWAP_STATES.CONFIRMING_APPROVAL]: (
<Box display={'flex'} flexDirection="column" alignItems={'center'} mt="16px">
<UnlockIcon w={16} h={16} color="green.300" mb="8px" />
<Text fontSize={'lg'} fontWeight="bold" mt="8px">
Approve submitted <Spinner color="blue.300" ml="8px" w="1rem" h="1rem" mb="-2px" />
</Text>
</Box>
),
[SWAP_STATES.WAITING_FOR_SWAP]: (
<>
<Box display={'flex'} flexDirection="column" alignItems={'center'} mt="16px">
<EditIcon w={16} h={16} color="blue.300" mb="8px" />
<Text fontSize={'lg'} fontWeight="bold" mt="8px">
Please confirm swap
</Text>
<TriangleDownIcon />
<Text as="h1" fontSize="xl" fontWeight={'bold'}>
{isLoading ? 'Waiting for approval' : 'Token approved'}{' '}
{isLoading ? (
<Spinner color="blue.300" ml="8px" w="1.25rem" h="1.25rem" />
) : (
<CheckCircleIcon color={'green.300'} ml="8px" />
)}
</Box>
<Button colorScheme={'messenger'} width="100%" mt="16px" onClick={() => swap()}>
Swap
</Button>
</>
),
[SWAP_STATES.CONFIRMING_SWAP]: (
<>
<Box display={'flex'} flexDirection="column" alignItems={'center'} mt="16px">
<CheckIcon w={16} h={16} color="green.300" mb="8px" />
<Text fontSize={'lg'} fontWeight="bold">
Transaction Submitted
</Text>
<TriangleDownIcon />
<Button
w={'180px'}
isDisabled={isLoading}
</Box>
{txUrl ? (
<Link
href={txUrl}
isExternal
fontSize={'lg'}
textAlign={'center'}
padding="6px 1rem"
borderRadius="0.375rem"
bg="#a2cdff"
margin="0 1rem 1rem"
mt="16px"
color="black"
w="100%"
_hover={{ textDecoration: 'none' }}
onClick={() => {
swap();
setOpen(false);
}}
>
Swap
</Button>
View on explorer <ExternalLinkIcon mx="2px" />
</Link>
) : null}
</>
)
};

return (
<Modal isCentered motionPreset="slideInBottom" closeOnOverlayClick={true} isOpen={isOpen} onClose={() => close()}>
<ModalOverlay />
<ModalContent h="280px">
<ModalCloseButton color="white" mt="8px" />

<ModalBody
display="flex"
gap="8px"
flexDir="column"
alignItems="center"
marginY="4rem"
color="white"
mt="4rem"
h="480px"
>
<Text display={'flex'} fontSize="lg" fontWeight={'bold'} position="absolute" top="18px">
Swapping {fromToken?.symbol}
<IconImage
style={{ marginLeft: '10px', height: '26px', width: '26px', marginTop: '2px', marginRight: '8px' }}
src={fromToken?.logoURI}
onError={(e) => (e.currentTarget.src = fromToken?.logoURI2 || '/placeholder.png')}
/>
to {toToken?.symbol}{' '}
<IconImage
style={{ marginLeft: '10px', height: '26px', width: '26px', marginTop: '2px' }}
src={toToken?.logoURI}
onError={(e) => (e.currentTarget.src = toToken?.logoURI2 || '/placeholder.png')}
/>
</Text>{' '}
{states[state]}
</ModalBody>
</ModalContent>
</Modal>
Expand Down

0 comments on commit 10e7acf

Please sign in to comment.