Skip to content

Commit

Permalink
fix: improve gas sufficiency handling
Browse files Browse the repository at this point in the history
  • Loading branch information
chybisov committed Jul 21, 2022
1 parent 61506cf commit 164501c
Show file tree
Hide file tree
Showing 12 changed files with 211 additions and 152 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export const MessageCard = styled(Box)(({ theme }) => ({
borderRadius: theme.shape.borderRadius,
position: 'relative',
display: 'flex',
whiteSpace: 'pre-line',
}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Warning as WarningIcon } from '@mui/icons-material';
import { Box, BoxProps, Typography } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { useGasSufficiency } from '../../hooks';
import { CardTitle } from '../Card';
import { MessageCard } from './GasSufficiencyMessage.style';

export const GasSufficiencyMessage: React.FC<BoxProps> = (props) => {
const { t } = useTranslation();
const { insufficientFunds, insufficientGas } = useGasSufficiency();

if (!insufficientFunds || !insufficientGas.length) {
return null;
}

let title;
let message;
if (insufficientFunds) {
message = t(`swap.warning.message.insufficientFunds`);
}
if (insufficientGas.length) {
title = t(`swap.warning.title.insufficientGas`);
message = t(`swap.warning.message.insufficientGas`);
}
return (
<MessageCard {...props}>
<WarningIcon
sx={{
marginTop: 2,
marginLeft: 2,
}}
/>
<Box>
{title ? <CardTitle>{title}</CardTitle> : null}
<Typography
variant="body2"
px={2}
pb={insufficientGas.length ? 0 : 2}
pt={title ? 1 : 2}
>
{message}
</Typography>
{insufficientGas.length
? insufficientGas.map((item, index) => (
<Typography
variant="body2"
px={2}
pb={insufficientGas.length - 1 === index ? 2 : 0}
pt={0.5}
>
{t(`swap.gasAmount`, {
amount: item.insufficientAmount?.toString(),
tokenSymbol: item.token.symbol,
chainName: item.chain?.name,
})}
</Typography>
))
: null}
</Box>
</MessageCard>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './GasSufficiencyMessage';

This file was deleted.

This file was deleted.

10 changes: 3 additions & 7 deletions packages/widget/src/components/SwapButton/SwapButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ChainId } from '@lifi/sdk';
import { useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { useChains, useHasSufficientBalance } from '../../hooks';
import { useChains, useGasSufficiency } from '../../hooks';
import { SwapFormKeyHelper } from '../../providers/SwapFormProvider';
import { useWallet } from '../../providers/WalletProvider';
import { useWidgetConfig } from '../../providers/WidgetProvider';
Expand All @@ -21,8 +21,7 @@ export const SwapButton: React.FC<SwapButtonProps> = ({
const config = useWidgetConfig();
const { account, switchChain, connect: walletConnect } = useWallet();

const { hasGasOnStartChain, hasGasOnCrossChain, hasSufficientBalance } =
useHasSufficientBalance();
const { insufficientFunds, insufficientGas } = useGasSufficiency();

const [chainId] = useWatch({
name: [SwapFormKeyHelper.getChainKey('from')],
Expand Down Expand Up @@ -64,10 +63,7 @@ export const SwapButton: React.FC<SwapButtonProps> = ({
onClick={handleSwapButtonClick}
// loading={isLoading || isFetching}
disabled={
(!hasSufficientBalance ||
!hasGasOnStartChain ||
!hasGasOnCrossChain ||
loading) &&
(insufficientFunds || !!insufficientGas.length || loading) &&
isCurrentChainMatch
}
>
Expand Down
2 changes: 1 addition & 1 deletion packages/widget/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export * from './useChain';
export * from './useChains';
export * from './useContentHeight';
export * from './useDebouncedWatch';
export * from './useHasSufficientBalance';
export * from './useGasSufficiency';
export * from './useRouteExecution';
export * from './useScrollableContainer';
export * from './useSwapRoutes';
Expand Down
135 changes: 135 additions & 0 deletions packages/widget/src/hooks/useGasSufficiency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { EVMChain, Token } from '@lifi/sdk';
import Big from 'big.js';
import { useMemo } from 'react';
import { useWatch } from 'react-hook-form';
import { useChains, useDebouncedWatch } from '.';
import { SwapFormKey, SwapFormKeyHelper } from '../providers/SwapFormProvider';
import { useWallet } from '../providers/WalletProvider';
import { useCurrentRoute } from '../stores';
import { useTokenBalances } from './useTokenBalances';

interface GasSufficiency {
gasAmount: Big;
tokenAmount?: Big;
insufficientAmount?: Big;
insufficient?: boolean;
token: Token;
chain?: EVMChain;
}

export const useGasSufficiency = () => {
const { account } = useWallet();
const [route] = useCurrentRoute();
const [fromChainId, toChainId, fromToken]: [number, number, string] =
useWatch({
name: [
SwapFormKeyHelper.getChainKey('from'),
SwapFormKeyHelper.getChainKey('to'),
SwapFormKey.FromToken,
],
});
const fromAmount = useDebouncedWatch(SwapFormKey.FromAmount, 250);
const { tokens: fromChainTokenBalances } = useTokenBalances(fromChainId);
const { tokens: toChainTokenBalances } = useTokenBalances(toChainId);
const { getChainById } = useChains();

const insufficientGas = useMemo(() => {
if (!account.isActive || !route || !fromAmount) {
return [];
}

const tokenBalancesByChain = {
[fromChainId]: fromChainTokenBalances,
[toChainId]: toChainTokenBalances,
};

const gasCosts = route.steps.reduce((groupedGasCosts, step) => {
if (step.estimate.gasCosts) {
const { token } = step.estimate.gasCosts[0];
const gasCostAmount = step.estimate.gasCosts
.reduce(
(amount, gasCost) => amount.plus(Big(gasCost.amount || 0)),
Big(0),
)
.div(10 ** token.decimals);
const groupedGasCost = groupedGasCosts[token.chainId];
const gasAmount = groupedGasCost
? groupedGasCost.gasAmount.plus(gasCostAmount)
: gasCostAmount;
groupedGasCosts[token.chainId] = {
gasAmount,
tokenAmount: gasAmount,
token,
chain: getChainById(token.chainId),
};
return groupedGasCosts;
}
return groupedGasCosts;
}, {} as Record<number, GasSufficiency>);

if (
gasCosts[fromChainId] &&
route.fromToken.address === gasCosts[fromChainId].token.address
) {
gasCosts[fromChainId].tokenAmount = gasCosts[fromChainId]?.gasAmount.plus(
Big(fromAmount),
);
}

[fromChainId, toChainId].forEach((chainId) => {
if (gasCosts[chainId]) {
const gasTokenBalance = Big(
tokenBalancesByChain[chainId]?.find(
(t) => t.address === gasCosts[chainId].token.address,
)?.amount ?? 0,
);

const insufficientFromChainGas =
gasTokenBalance.lte(0) ||
gasTokenBalance.lt(gasCosts[chainId].gasAmount ?? Big(0)) ||
gasTokenBalance.lt(gasCosts[chainId].tokenAmount ?? Big(0));

const insufficientFromChainGasAmount = insufficientFromChainGas
? gasCosts[chainId].tokenAmount?.minus(gasTokenBalance) ??
gasCosts[chainId].gasAmount.minus(gasTokenBalance)
: undefined;

gasCosts[chainId] = {
...gasCosts[chainId],
insufficient: insufficientFromChainGas,
insufficientAmount: insufficientFromChainGasAmount,
};
}
});

const gasCostResult = Object.values(gasCosts).filter(
(gasCost) => gasCost.insufficient,
);

return gasCostResult;
}, [
account.isActive,
fromAmount,
fromChainId,
fromChainTokenBalances,
getChainById,
route,
toChainId,
toChainTokenBalances,
]);

const insufficientFunds = useMemo(() => {
if (!account.isActive || !fromToken || !fromAmount) {
return true;
}
const balance = Big(
fromChainTokenBalances?.find((t) => t.address === fromToken)?.amount ?? 0,
);
return Big(fromAmount).lte(balance);
}, [account.isActive, fromAmount, fromChainTokenBalances, fromToken]);

return {
insufficientGas,
insufficientFunds,
};
};
89 changes: 0 additions & 89 deletions packages/widget/src/hooks/useHasSufficientBalance.ts

This file was deleted.

Loading

0 comments on commit 164501c

Please sign in to comment.