Skip to content

Commit

Permalink
Add fee balance check
Browse files Browse the repository at this point in the history
  • Loading branch information
nikitayutanov committed Sep 3, 2024
1 parent b1f9bc7 commit a5a9620
Show file tree
Hide file tree
Showing 18 changed files with 146 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ETH_CHAIN_ID } from '@/consts';
import { useEthAccount } from '@/hooks';

import { NETWORK_INDEX } from '../../consts';
import { useEthBalance, useHandleEthSubmit } from '../../hooks';
import { useEthFTBalance, useHandleEthSubmit, useEthAccountBalance } from '../../hooks';

import { SwapForm } from './swap-form';

Expand All @@ -19,7 +19,8 @@ function SwapEthForm({ renderSwapNetworkButton }: Props) {
networkIndex={NETWORK_INDEX.ETH}
disabled={!ethAccount.isConnected || !isSupportedChain}
useHandleSubmit={useHandleEthSubmit}
useBalance={useEthBalance}
useAccountBalance={useEthAccountBalance}
useFTBalance={useEthFTBalance}
renderSwapNetworkButton={renderSwapNetworkButton}
/>
);
Expand Down
32 changes: 22 additions & 10 deletions frontend/src/features/swap/components/swap-form/swap-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Input } from '@/components';
import GasSVG from '../../assets/gas.svg?react';
import { FIELD_NAME, NETWORK_INDEX } from '../../consts';
import { useSwapForm, useBridge, useVaraConfig } from '../../hooks';
import { UseBalance, UseHandleSubmit } from '../../types';
import { UseHandleSubmit, UseAccountBalance, UseFTBalance } from '../../types';
import { Balance } from '../balance';
import { Network } from '../network';

Expand All @@ -15,34 +15,44 @@ import styles from './swap-form.module.scss';
type Props = {
networkIndex: number;
disabled: boolean;
useBalance: UseBalance;
useAccountBalance: UseAccountBalance;
useFTBalance: UseFTBalance;
useHandleSubmit: UseHandleSubmit;
renderSwapNetworkButton: () => JSX.Element;
};

function SwapForm({ networkIndex, disabled, useHandleSubmit, useBalance, renderSwapNetworkButton }: Props) {
function SwapForm({
networkIndex,
disabled,
useHandleSubmit,
useAccountBalance,
useFTBalance,
renderSwapNetworkButton,
}: Props) {
const isVaraNetwork = networkIndex === NETWORK_INDEX.VARA;
const FromNetwork = isVaraNetwork ? Network.Vara : Network.Eth;
const ToNetwork = isVaraNetwork ? Network.Eth : Network.Vara;

const { address, options, symbol, pair, nativeSymbol, ...bridge } = useBridge(networkIndex);
const { address, options, symbol, pair, ...bridge } = useBridge(networkIndex);
const { fee, ...config } = useVaraConfig(isVaraNetwork);
const balance = useBalance(address);
const accountBalance = useAccountBalance();
const ftBalance = useFTBalance(address);
const { onSubmit, isSubmitting } = useHandleSubmit(address, fee.value);

const { form, onValueChange, onExpectedValueChange, handleSubmit, setMaxBalance } = useSwapForm(
isVaraNetwork,
balance,
accountBalance,
ftBalance,
fee.value,
disabled,
onSubmit,
);

const renderFromBalance = () => (
<Balance
value={balance.formattedValue}
value={ftBalance.formattedValue}
unit={symbol}
isLoading={balance.isLoading}
isLoading={ftBalance.isLoading || bridge.isLoading}
onMaxButtonClick={setMaxBalance}
/>
);
Expand Down Expand Up @@ -81,14 +91,16 @@ function SwapForm({ networkIndex, disabled, useHandleSubmit, useBalance, renderS
heading="Expected Fee"
value={fee.formattedValue}
isLoading={config.isLoading}
unit={nativeSymbol}
unit={isVaraNetwork ? 'VARA' : 'ETH'}
/>

<Button
type="submit"
text="Swap"
disabled={disabled}
isLoading={isSubmitting || balance.isLoading || config.isLoading || bridge.isLoading}
isLoading={
isSubmitting || accountBalance.isLoading || ftBalance.isLoading || config.isLoading || bridge.isLoading
}
/>
</footer>
</form>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useAccount } from '@gear-js/react-hooks';

import { NETWORK_INDEX } from '../../consts';
import { useHandleVaraSubmit, useVaraBalance } from '../../hooks';
import { useHandleVaraSubmit, useVaraFTBalance, useVaraAccountBalance } from '../../hooks';

import { SwapForm } from './swap-form';

Expand All @@ -17,7 +17,8 @@ function SwapVaraForm({ renderSwapNetworkButton }: Props) {
networkIndex={NETWORK_INDEX.VARA}
disabled={!account}
useHandleSubmit={useHandleVaraSubmit}
useBalance={useVaraBalance}
useAccountBalance={useVaraAccountBalance}
useFTBalance={useVaraFTBalance}
renderSwapNetworkButton={renderSwapNetworkButton}
/>
);
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/features/swap/consts/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ const DEFAULT_VALUES = {
};

const ERROR_MESSAGE = {
NO_BALANCE: 'Insufficient balance',
NO_FT_BALANCE: 'Insufficient token balance',
NO_ACCOUNT_BALANCE: 'Insufficient account balance to pay fee',
INVALID_ADDRESS: 'Invalid address',
} as const;

Expand Down
1 change: 1 addition & 0 deletions frontend/src/features/swap/consts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { BridgingPaymentProgram, VftGatewayProgram, VftProgram } from './sails';
import { SERVICE_NAME, QUERY_NAME } from './vara';

const BRIDGING_PAYMENT_CONTRACT_ADDRESS = import.meta.env.VITE_BRIDGING_PAYMENT_CONTRACT_ADDRESS as HexString;
console.log('RIDGING_PAYMENT_CONTRACT_ADDRESS: ', BRIDGING_PAYMENT_CONTRACT_ADDRESS);
const ERC20_TREASURY_CONTRACT_ADDRESS = import.meta.env.VITE_ERC20_TREASURY_CONTRACT_ADDRESS as HexString;

const NETWORK_INDEX = {
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/features/swap/hooks/eth/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useFungibleTokenBalance } from './use-fungible-token-balance';
import { useEthAccountBalance } from './use-eth-account-balance';
import { useEthFTBalance } from './use-eth-ft-balance';
import { useHandleEthSubmit } from './use-handle-eth-submit';

export { useFungibleTokenBalance, useHandleEthSubmit };
export { useEthFTBalance, useEthAccountBalance, useHandleEthSubmit };
27 changes: 27 additions & 0 deletions frontend/src/features/swap/hooks/eth/use-eth-account-balance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { formatEther } from 'viem';
import { useBalance } from 'wagmi';

import { useEthAccount } from '@/hooks';

const withPrecision = (value: string) => {
// simplest solution without rounding for now
const digitsCount = 3;

return value.slice(0, value.indexOf('.') + digitsCount + 1);
};

function useEthAccountBalance() {
const ethAccount = useEthAccount();

const { data, isPending } = useBalance({
address: ethAccount?.address,
});

const { value } = data || {};
const formattedValue = data ? withPrecision(formatEther(data.value)) : undefined;
const isLoading = isPending;

return { value, formattedValue, isLoading };
}

export { useEthAccountBalance };
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { FUNCTION_NAME } from '../../consts/eth';

const abi = FUNGIBLE_TOKEN_ABI;

function useFungibleTokenBalance(address: HexString | undefined) {
function useEthFTBalance(address: HexString | undefined) {
const ethAccount = useEthAccount();

// TODO: logger
Expand Down Expand Up @@ -42,4 +42,4 @@ function useFungibleTokenBalance(address: HexString | undefined) {
return { value, formattedValue, decimals, isLoading };
}

export { useFungibleTokenBalance };
export { useEthFTBalance };
10 changes: 6 additions & 4 deletions frontend/src/features/swap/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { useFungibleTokenBalance as useEthBalance, useHandleEthSubmit } from './eth';
import { useEthFTBalance, useEthAccountBalance, useHandleEthSubmit } from './eth';
import { useBridge } from './use-bridge';
import { useSwapForm } from './use-swap-form';
import { useVaraConfig, useFungibleTokenBalance as useVaraBalance, useHandleVaraSubmit } from './vara';
import { useVaraConfig, useVaraAccountBalance, useVaraFTBalance, useHandleVaraSubmit } from './vara';

export {
useEthBalance,
useEthFTBalance,
useEthAccountBalance,
useHandleEthSubmit,
useSwapForm,
useVaraConfig,
useVaraBalance,
useVaraAccountBalance,
useVaraFTBalance,
useHandleVaraSubmit,
useBridge,
};
3 changes: 1 addition & 2 deletions frontend/src/features/swap/hooks/use-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { useFTAddresses } from './vara';

function useBridge(networkIndex: number) {
const isVaraNetwork = networkIndex === NETWORK_INDEX.VARA;
const nativeSymbol = isVaraNetwork ? 'VARA' : 'ETH';

const { data: ftAddresses } = useFTAddresses();
const { data: ftSymbols, isPending } = useFTSymbols(ftAddresses);
Expand All @@ -22,7 +21,7 @@ function useBridge(networkIndex: number) {
const address = ftAddresses?.[pairIndex][networkIndex].toString() as HexString | undefined;
const symbol = ftSymbols?.[pairIndex][networkIndex];

return { address, options, symbol, nativeSymbol, pair: { value: pair, set: setPair }, isLoading: isPending };
return { address, options, symbol, pair: { value: pair, set: setPair }, isLoading: isPending };
}

export { useBridge };
13 changes: 7 additions & 6 deletions frontend/src/features/swap/hooks/use-swap-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@ type Values = {

function useSwapForm(
isVaraNetwork: boolean,
balance: Values & { decimals: number | undefined },
accountBalance: Values,
ftBalance: Values & { decimals: number | undefined },
fee: bigint | undefined,
disabled: boolean,
onSubmit: (values: FormattedValues, reset: () => void) => void,
) {
const alert = useAlert();

const valueSchema = getAmountSchema(balance.value, fee, balance.decimals);
const expectedValueSchema = getAmountSchema(balance.value, BigInt(0), balance.decimals);
const valueSchema = getAmountSchema(accountBalance.value, ftBalance.value, fee, ftBalance.decimals);
const expectedValueSchema = getAmountSchema(accountBalance.value, ftBalance.value, BigInt(0), ftBalance.decimals);
const addressSchema = isVaraNetwork ? ADDRESS_SCHEMA.ETH : ADDRESS_SCHEMA.VARA;

const schema = z.object({
Expand Down Expand Up @@ -61,10 +62,10 @@ function useSwapForm(
}, [disabled]);

const setMaxBalance = () => {
if (!balance.formattedValue) throw new Error('Balance is not defined');
if (!ftBalance.formattedValue) throw new Error('Balance is not defined');

setOriginalValue(balance.formattedValue);
onValueChange(balance.formattedValue);
setOriginalValue(ftBalance.formattedValue);
onValueChange(ftBalance.formattedValue);
};

return {
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/features/swap/hooks/vara/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useFTAddresses } from './use-ft-addresses';
import { useFungibleTokenBalance } from './use-fungible-token-balance';
import { useHandleVaraSubmit } from './use-handle-vara-submit';
import { useVaraAccountBalance } from './use-vara-account-balance';
import { useVaraConfig } from './use-vara-config';
import { useVaraFTBalance } from './use-vara-ft-balance';

export { useVaraConfig, useFungibleTokenBalance, useHandleVaraSubmit, useFTAddresses };
export { useVaraConfig, useVaraFTBalance, useHandleVaraSubmit, useFTAddresses, useVaraAccountBalance };
24 changes: 24 additions & 0 deletions frontend/src/features/swap/hooks/vara/use-derive-balances-all.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useApi } from '@gear-js/react-hooks';
import { useQuery } from '@tanstack/react-query';

import { BALANCE_REFETCH_INTERVAL } from '../../consts';

function useDeriveBalancesAll(accountAddress: string | undefined) {
const { api, isApiReady } = useApi();

const getDeriveBalancesAll = async () => {
if (!isApiReady) throw new Error('API is not initialized');
if (!accountAddress) throw new Error('Account is not found');

return api.derive.balances.all(accountAddress);
};

return useQuery({
queryKey: ['deriveBalancesAll', isApiReady, accountAddress],
queryFn: getDeriveBalancesAll,
enabled: isApiReady && Boolean(accountAddress),
refetchInterval: BALANCE_REFETCH_INTERVAL,
});
}

export { useDeriveBalancesAll };
26 changes: 26 additions & 0 deletions frontend/src/features/swap/hooks/vara/use-vara-account-balance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useAccount, useBalanceFormat } from '@gear-js/react-hooks';
import { useMemo } from 'react';

import { useDeriveBalancesAll } from './use-derive-balances-all';

function useVaraAccountBalance() {
const { account, isAccountReady } = useAccount();
const { getFormattedBalance } = useBalanceFormat();

const { data, isPending } = useDeriveBalancesAll(account?.address);
const { freeBalance } = data || {};
const value = freeBalance?.toBigInt();
const formattedValue = value !== undefined ? getFormattedBalance(value).value : undefined;

// cuz swap vara form is rendered by default without login and we have to handle empty balance state
const isLoading = useMemo(() => {
if (!isAccountReady) return true;
if (!account) return false;

return isPending;
}, [account, isAccountReady, isPending]);

return { value, formattedValue, isLoading };
}

export { useVaraAccountBalance };
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { isUndefined } from '@/utils';

import { BALANCE_REFETCH_INTERVAL, QUERY_NAME, SERVICE_NAME, VftProgram } from '../../consts';

function useFungibleTokenBalance(address: HexString | undefined) {
function useVaraFTBalance(address: HexString | undefined) {
const { account } = useAccount();

const { data: program } = useProgram({
Expand Down Expand Up @@ -37,4 +37,4 @@ function useFungibleTokenBalance(address: HexString | undefined) {
return { value, formattedValue, decimals, isLoading };
}

export { useFungibleTokenBalance };
export { useVaraFTBalance };
8 changes: 6 additions & 2 deletions frontend/src/features/swap/types/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ type BalanceValues = {
formattedValue: string | undefined;
};

type UseBalance = (ftAddress: HexString | undefined) => BalanceValues & {
type UseAccountBalance = () => BalanceValues & {
isLoading: boolean;
};

type UseFTBalance = (ftAddress: HexString | undefined) => BalanceValues & {
decimals: number | undefined;
isLoading: boolean;
};
Expand All @@ -20,4 +24,4 @@ type UseHandleSubmit = (
isSubmitting: boolean;
};

export type { UseBalance, UseHandleSubmit };
export type { UseAccountBalance, UseFTBalance, UseHandleSubmit };
4 changes: 2 additions & 2 deletions frontend/src/features/swap/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Values, FormattedValues } from './form';
import { UseBalance, UseHandleSubmit } from './hooks';
import { UseAccountBalance, UseFTBalance, UseHandleSubmit } from './hooks';

export type { UseBalance, UseHandleSubmit, Values, FormattedValues };
export type { UseAccountBalance, UseFTBalance, UseHandleSubmit, Values, FormattedValues };
9 changes: 6 additions & 3 deletions frontend/src/features/swap/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@ import { isUndefined } from '@/utils';
import { ERROR_MESSAGE } from './consts';

const getAmountSchema = (
balanceValue: bigint | undefined,
accountBalanceValue: bigint | undefined,
ftBalanceValue: bigint | undefined,
feeValue: bigint | undefined,
decimals: number | undefined,
) => {
if (isUndefined(balanceValue) || isUndefined(feeValue) || isUndefined(decimals)) return z.bigint();
if (isUndefined(accountBalanceValue) || isUndefined(ftBalanceValue) || isUndefined(feeValue) || isUndefined(decimals))
return z.bigint();

return z
.string()
.trim() // TODO: required field check
.transform((value) => parseUnits(value, decimals)) // if fraction is > decimals, value will be rounded
.refine((value) => value + feeValue <= balanceValue, { message: ERROR_MESSAGE.NO_BALANCE });
.refine((value) => value <= ftBalanceValue, { message: ERROR_MESSAGE.NO_FT_BALANCE })
.refine(() => feeValue <= accountBalanceValue, { message: ERROR_MESSAGE.NO_ACCOUNT_BALANCE });
};

const getOptions = (symbols: [string, string][] | undefined) => {
Expand Down

0 comments on commit a5a9620

Please sign in to comment.