Skip to content

Commit

Permalink
feat: add estimate fee hook and action amount deduction (#1433)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsimao authored Jul 18, 2023
1 parent 8ba7cb2 commit 319753a
Show file tree
Hide file tree
Showing 39 changed files with 743 additions and 533 deletions.
34 changes: 13 additions & 21 deletions src/components/TransactionFeeDetails/TransactionFeeDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { CurrencyExt } from '@interlay/interbtc-api';
import { MonetaryAmount } from '@interlay/monetary-js';
import { mergeProps, useId } from '@react-aria/utils';
import { Key, ReactNode, useState } from 'react';
import { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';

import { displayMonetaryAmountInUSDFormat, formatUSD } from '@/common/utils/utils';
import { Alert, Flex } from '@/component-library';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
import { UseFeeEstimateResult } from '@/utils/hooks/transaction/types/hook';
import { SelectCurrencyFilter, useSelectCurrency } from '@/utils/hooks/use-select-currency';

import {
Expand All @@ -21,57 +20,50 @@ import {
} from '../TransactionDetails';

type Props = {
defaultCurrency?: CurrencyExt;
amount?: MonetaryAmount<CurrencyExt>;
label?: ReactNode;
showInsufficientBalance?: boolean;
tooltipLabel?: ReactNode;
selectProps?: TransactionSelectTokenProps;
fee?: UseFeeEstimateResult<any>;
};

type InheritAttrs = Omit<TransactionDetailsProps, keyof Props>;

type TransactionFeeDetailsProps = Props & InheritAttrs;

const TransactionFeeDetails = ({
amount,
defaultCurrency,
showInsufficientBalance,
selectProps,
label,
tooltipLabel,
className,
fee,
...props
}: TransactionFeeDetailsProps): JSX.Element => {
const prices = useGetPrices();
const { t } = useTranslation();
const selectCurrency = useSelectCurrency(SelectCurrencyFilter.TRADEABLE_FOR_NATIVE_CURRENCY);
const id = useId();
const [ticker, setTicker] = useState(defaultCurrency?.ticker);
const prices = useGetPrices();
const selectCurrency = useSelectCurrency(SelectCurrencyFilter.TRADEABLE_FOR_NATIVE_CURRENCY);

const { selectProps: feeSelectProps, data } = fee || {};
const { amount, isValid } = data || {};

const amountLabel = amount
? `${amount.toHuman()} ${amount.currency.ticker} (${displayMonetaryAmountInUSDFormat(
amount,
getTokenPrice(prices, amount.currency.ticker)?.usd
)})`
: `${0.0} ${ticker} (${formatUSD(0)})`;
: `${0.0} ${selectProps?.value} (${formatUSD(0)})`;

const errorMessage =
showInsufficientBalance &&
t('forms.ensure_adequate_amount_left_to_cover_action', { action: t('fees').toLowerCase() });

const handleSelectionChange = (key: Key) => setTicker(key as string);
isValid === false && t('forms.ensure_adequate_amount_left_to_cover_action', { action: t('fees').toLowerCase() });

return (
<Flex gap='spacing2' direction='column' className={className}>
<TransactionDetails {...props}>
{selectProps && (
<TransactionSelectToken
{...mergeProps(selectProps || {}, {
{...mergeProps(selectProps || {}, feeSelectProps || {}, {
label: t('fee_token'),
items: selectCurrency.items,
value: ticker,
onSelectionChange: handleSelectionChange
items: selectCurrency.items
})}
errorMessage={undefined}
aria-describedby={errorMessage ? id : undefined}
Expand Down
8 changes: 4 additions & 4 deletions src/lib/form/schemas/btc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import yup, { AddressType, MaxAmountValidationParams, MinAmountValidationParams
const BTC_ISSUE_AMOUNT_FIELD = 'issue-amount';
const BTC_ISSUE_CUSTOM_VAULT_FIELD = 'issue-custom-vault';
const BTC_ISSUE_CUSTOM_VAULT_SWITCH = 'issue-custom-vault-switch';
const BTC_ISSUE_GRIEFING_COLLATERAL_TOKEN = 'issue-griefing-collateral-token';
const BTC_ISSUE_SECURITY_DEPOSIT_TOKEN = 'issue-security-deposit-token';
const BTC_ISSUE_FEE_TOKEN = 'issue-fee-token';

type BTCIssueFormData = {
[BTC_ISSUE_AMOUNT_FIELD]?: string;
[BTC_ISSUE_CUSTOM_VAULT_FIELD]?: string;
[BTC_ISSUE_CUSTOM_VAULT_SWITCH]?: boolean;
[BTC_ISSUE_GRIEFING_COLLATERAL_TOKEN]?: string;
[BTC_ISSUE_SECURITY_DEPOSIT_TOKEN]?: string;
[BTC_ISSUE_FEE_TOKEN]?: string;
};

Expand All @@ -38,7 +38,7 @@ const btcIssueSchema = (params: BTCIssueValidationParams): yup.ObjectSchema<any>
is: (isManualVault: string) => isManualVault,
then: (schema) => schema.required(i18n.t('forms.please_select_your_field', { field: 'issue vault' }))
}),
[BTC_ISSUE_GRIEFING_COLLATERAL_TOKEN]: yup.string().required(),
[BTC_ISSUE_SECURITY_DEPOSIT_TOKEN]: yup.string().required(),
[BTC_ISSUE_FEE_TOKEN]: yup.string().required()
});

Expand Down Expand Up @@ -92,7 +92,7 @@ export {
BTC_ISSUE_CUSTOM_VAULT_FIELD,
BTC_ISSUE_CUSTOM_VAULT_SWITCH,
BTC_ISSUE_FEE_TOKEN,
BTC_ISSUE_GRIEFING_COLLATERAL_TOKEN,
BTC_ISSUE_SECURITY_DEPOSIT_TOKEN,
BTC_REDEEM_ADDRESS,
BTC_REDEEM_AMOUNT_FIELD,
BTC_REDEEM_CUSTOM_VAULT_FIELD,
Expand Down
52 changes: 25 additions & 27 deletions src/pages/BTC/BTCOverview/components/IssueForm/IssueForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
BTC_ISSUE_CUSTOM_VAULT_FIELD,
BTC_ISSUE_CUSTOM_VAULT_SWITCH,
BTC_ISSUE_FEE_TOKEN,
BTC_ISSUE_GRIEFING_COLLATERAL_TOKEN,
BTC_ISSUE_SECURITY_DEPOSIT_TOKEN,
BTCIssueFormData,
btcIssueSchema,
useForm
Expand Down Expand Up @@ -104,8 +104,8 @@ const IssueForm = ({ requestLimits, dustValue, issueFee }: IssueFormProps): JSX.
const getTransactionArgs = useCallback(
(values: BTCIssueFormData): TransactionArgs<Transaction.ISSUE_REQUEST> | undefined => {
const amount = values[BTC_ISSUE_AMOUNT_FIELD];
const griefingCollateralCurrencyTicker = values[BTC_ISSUE_GRIEFING_COLLATERAL_TOKEN];
if (!vaultsData || !amount || griefingCollateralCurrencyTicker === undefined || isLoadingCurrencies) return;
const securityDepositTicker = values[BTC_ISSUE_SECURITY_DEPOSIT_TOKEN];
if (!vaultsData || !amount || securityDepositTicker === undefined || isLoadingCurrencies) return;

const monetaryAmount = new BitcoinAmount(amount);

Expand All @@ -127,14 +127,14 @@ const IssueForm = ({ requestLimits, dustValue, issueFee }: IssueFormProps): JSX.
vault = getRandomArrayElement(availableVaults);
}

const griefingCollateralCurrency = getCurrencyFromTicker(griefingCollateralCurrencyTicker);
const securityDeposit = getCurrencyFromTicker(securityDepositTicker);
return [
monetaryAmount,
vault.vaultId.accountId,
vault.collateralCurrency,
false,
vaultsData.map,
griefingCollateralCurrency
securityDeposit
];
},
[getAvailableVaults, getCurrencyFromTicker, isLoadingCurrencies, vaultsData]
Expand All @@ -145,7 +145,7 @@ const IssueForm = ({ requestLimits, dustValue, issueFee }: IssueFormProps): JSX.
[BTC_ISSUE_AMOUNT_FIELD]: '',
[BTC_ISSUE_CUSTOM_VAULT_FIELD]: '',
[BTC_ISSUE_CUSTOM_VAULT_SWITCH]: false,
[BTC_ISSUE_GRIEFING_COLLATERAL_TOKEN]: GOVERNANCE_TOKEN.ticker,
[BTC_ISSUE_SECURITY_DEPOSIT_TOKEN]: GOVERNANCE_TOKEN.ticker,
[BTC_ISSUE_FEE_TOKEN]: transaction.fee.defaultCurrency.ticker
},
validateOnChange: true,
Expand All @@ -157,26 +157,24 @@ const IssueForm = ({ requestLimits, dustValue, issueFee }: IssueFormProps): JSX.

if (!args) return;

const feeTicker = values[BTC_ISSUE_FEE_TOKEN];

transaction.fee.setCurrency(feeTicker).estimate(...args);
transaction.fee.estimate(...args);
}
});

const griefingCollateralTicker = form.values[BTC_ISSUE_GRIEFING_COLLATERAL_TOKEN];
const securityDepositTicker = form.values[BTC_ISSUE_SECURITY_DEPOSIT_TOKEN];

useEffect(() => {
const computeSecurityDeposit = async () => {
const btcAmount = safeBitcoinAmount(amount || 0);
const deposit = await getSecurityDeposit(btcAmount, griefingCollateralTicker);
const btcAmount = safeBitcoinAmount(debouncedAmount || 0);
const deposit = await getSecurityDeposit(btcAmount, securityDepositTicker);

if (!deposit) return;

setSecurityDeposit(deposit);
};

computeSecurityDeposit();
}, [amount, griefingCollateralTicker, setSecurityDeposit, getSecurityDeposit]);
}, [debouncedAmount, securityDepositTicker, setSecurityDeposit, getSecurityDeposit]);

const handleToggleCustomVault = (e: ChangeEvent<HTMLInputElement>) => {
if (!e.target.checked) {
Expand Down Expand Up @@ -211,24 +209,24 @@ const IssueForm = ({ requestLimits, dustValue, issueFee }: IssueFormProps): JSX.

const isSelectingVault = form.values[BTC_ISSUE_CUSTOM_VAULT_SWITCH];

const griefingCollateralCurrencyBalance = griefingCollateralTicker
? getAvailableBalance(griefingCollateralTicker)
: undefined;
const securityDepositBalance = securityDepositTicker ? getAvailableBalance(securityDepositTicker) : undefined;

const hasEnoughGriefingCollateralBalance = useMemo(() => {
const hasAvailableSecurityDepositBalance = useMemo(() => {
if (!debouncedAmount) return true;

if (
!griefingCollateralCurrencyBalance ||
!isCurrencyEqual(securityDeposit.currency, griefingCollateralCurrencyBalance.currency)
) {
if (!securityDepositBalance || !isCurrencyEqual(securityDeposit.currency, securityDepositBalance.currency)) {
return false;
}

return griefingCollateralCurrencyBalance.gte(securityDeposit);
}, [debouncedAmount, griefingCollateralCurrencyBalance, securityDeposit]);
// when security deposit currency is equal to fee currency it should be taken into account
if (transaction.fee.data && transaction.fee.isEqualFeeCurrency(securityDepositBalance.currency)) {
return securityDepositBalance.sub(transaction.fee.data.amount).gte(securityDeposit);
}

return securityDepositBalance.gte(securityDeposit);
}, [debouncedAmount, securityDepositBalance, securityDeposit, transaction.fee]);

const isBtnDisabled = isTransactionFormDisabled(form, transaction.fee) || !hasEnoughGriefingCollateralBalance;
const isBtnDisabled = isTransactionFormDisabled(form, transaction.fee) || !hasAvailableSecurityDepositBalance;

return (
<>
Expand Down Expand Up @@ -270,10 +268,10 @@ const IssueForm = ({ requestLimits, dustValue, issueFee }: IssueFormProps): JSX.
totalTicker={WRAPPED_TOKEN.ticker}
bridgeFee={bridgeFee}
securityDeposit={securityDeposit}
securityDepositSelectProps={form.getSelectFieldProps(BTC_ISSUE_GRIEFING_COLLATERAL_TOKEN, true)}
showInsufficientSecurityBalance={!hasEnoughGriefingCollateralBalance}
securityDepositSelectProps={form.getSelectFieldProps(BTC_ISSUE_SECURITY_DEPOSIT_TOKEN, true)}
showInsufficientSecurityBalance={!hasAvailableSecurityDepositBalance}
feeDetailsProps={{
...transaction.fee.detailsProps,
fee: transaction.fee,
selectProps: form.getSelectFieldProps(BTC_ISSUE_FEE_TOKEN, true)
}}
/>
Expand Down
25 changes: 17 additions & 8 deletions src/pages/BTC/BTCOverview/components/RedeemForm/RedeemForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
btcRedeemSchema,
useForm
} from '@/lib/form';
import { getTokenInputProps } from '@/utils/helpers/input';
import { getTokenPrice } from '@/utils/helpers/prices';
import { RedeemData, useGetRedeemData } from '@/utils/hooks/api/bridge/use-get-redeem-data';
import { BridgeVaultData, GetVaultType, useGetVaults } from '@/utils/hooks/api/bridge/use-get-vaults';
Expand Down Expand Up @@ -151,7 +152,13 @@ const RedeemForm = ({

if (!args) return;

transaction.execute(...args);
let [amount, ...rest] = args;

if (transaction.fee.isEqualFeeCurrency(amount.currency)) {
amount = transaction.calculateAmountWithFeeDeducted(amount);
}

transaction.execute(amount, ...rest);
};

const monetaryAmount = newSafeMonetaryAmount(amount || 0, WRAPPED_TOKEN, true);
Expand Down Expand Up @@ -185,9 +192,7 @@ const RedeemForm = ({

if (!args) return;

const feeTicker = values[BTC_REDEEM_FEE_TOKEN];

transaction.fee.setCurrency(feeTicker).estimate(...args);
transaction.fee.estimate(...args);
}
});

Expand Down Expand Up @@ -274,9 +279,13 @@ const RedeemForm = ({
humanBalance={redeemBalance.toString()}
balanceLabel={t('available')}
valueUSD={amountUSD}
{...mergeProps(form.getFieldProps(BTC_REDEEM_AMOUNT_FIELD, false, true), {
onChange: handleChangeIssueAmount
})}
{...mergeProps(
form.getFieldProps(BTC_REDEEM_AMOUNT_FIELD, false, true),
getTokenInputProps(redeemBalance),
{
onChange: handleChangeIssueAmount
}
)}
/>
{hasPremiumRedeemFeature && (
<PremiumRedeemCard
Expand Down Expand Up @@ -315,7 +324,7 @@ const RedeemForm = ({
bridgeFee={bridgeFee}
bitcoinNetworkFee={currentInclusionFee}
feeDetailsProps={{
...transaction.fee.detailsProps,
fee: transaction.fee,
selectProps: form.getSelectFieldProps(BTC_REDEEM_FEE_TOKEN, true)
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,23 @@ const TransactionDetails = ({
})}
</Alert>
)}
{bitcoinNetworkFee && <TransactionFeeDetails label={t('btc.bitcoin_network_fee')} amount={bitcoinNetworkFee} />}
{bitcoinNetworkFee && (
<BaseTransactionDetails>
<TransactionDetailsGroup>
<TransactionDetailsDt tooltipLabel={t('btc.fee_paids_to_vaults_relayers_maintainers')}>
{t('btc.bitcoin_network_fee')}
</TransactionDetailsDt>
<TransactionDetailsDd>
{bitcoinNetworkFee.toHuman()} {bitcoinNetworkFee.currency.ticker} (
{displayMonetaryAmountInUSDFormat(
bitcoinNetworkFee,
getTokenPrice(prices, bitcoinNetworkFee.currency.ticker)?.usd
)}
)
</TransactionDetailsDd>
</TransactionDetailsGroup>
</BaseTransactionDetails>
)}
{feeDetailsProps && <TransactionFeeDetails {...feeDetailsProps} />}
</Flex>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,11 @@ const CollateralModal = ({ asset, position, onClose, isOpen, ...props }: Collate
},
validationSchema: toggleCollateralLoanSchema(),
onSubmit: handleSubmit,
onComplete: async (values) => {
const feeTicker = values[LOAN_TOGGLE_COLLATERAL_FEE_TOKEN_FIELD];

onComplete: async () => {
if (variant === 'enable') {
return transaction.fee.setCurrency(feeTicker).estimate(Transaction.LOANS_ENABLE_COLLATERAL, asset.currency);
return transaction.fee.estimate(Transaction.LOANS_ENABLE_COLLATERAL, asset.currency);
} else {
return transaction.fee.setCurrency(feeTicker).estimate(Transaction.LOANS_DISABLE_COLLATERAL, asset.currency);
return transaction.fee.estimate(Transaction.LOANS_DISABLE_COLLATERAL, asset.currency);
}
}
});
Expand Down Expand Up @@ -154,7 +152,7 @@ const CollateralModal = ({ asset, position, onClose, isOpen, ...props }: Collate
<form onSubmit={form.handleSubmit}>
<Flex direction='column' gap='spacing4'>
<TransactionFeeDetails
{...transaction.fee.detailsProps}
fee={transaction.fee}
selectProps={{
...form.getSelectFieldProps(LOAN_TOGGLE_COLLATERAL_FEE_TOKEN_FIELD),
modalRef: overlappingModalRef
Expand Down
Loading

2 comments on commit 319753a

@vercel
Copy link

@vercel vercel bot commented on 319753a Jul 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 319753a Jul 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.