feat(deal-ticket): enhance for fee discounts (#5353)

This commit is contained in:
Bartłomiej Głownia 2023-11-29 13:59:42 +01:00 committed by GitHub
parent 7588d0cd11
commit a2b9b0da05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 108 additions and 99 deletions

View File

@ -77,12 +77,10 @@ export const DealTicketFeeDetails = ({
label={
<>
{t('Fees')}
{totalDiscountFactor ? (
{totalDiscountFactor !== '0' ? (
<Pill size="xxs" intent={Intent.Info} className="ml-1">
-
{formatNumberPercentage(
new BigNumber(totalDiscountFactor).multipliedBy(100),
2
new BigNumber(totalDiscountFactor).multipliedBy(100)
)}
</Pill>
) : null}
@ -105,10 +103,7 @@ export const DealTicketFeeDetails = ({
)}
</p>
<FeesBreakdown
totalFeeAmount={feeEstimate?.totalFeeAmount}
referralDiscountFactor={feeEstimate?.referralDiscountFactor}
volumeDiscountFactor={feeEstimate?.volumeDiscountFactor}
fees={feeEstimate?.fees}
feeEstimate={feeEstimate}
feeFactors={market.fees.factors}
symbol={assetSymbol}
decimals={assetDecimals}

View File

@ -6,16 +6,19 @@ describe('getDiscountedFee', () => {
discountedFee: '100',
volumeDiscount: '0',
referralDiscount: '0',
totalDiscount: '0',
});
expect(getDiscountedFee('100', undefined, '0.1')).toEqual({
discountedFee: '90',
volumeDiscount: '10',
referralDiscount: '0',
totalDiscount: '10',
});
expect(getDiscountedFee('100', '0.1', undefined)).toEqual({
discountedFee: '90',
volumeDiscount: '0',
referralDiscount: '10',
totalDiscount: '10',
});
});
@ -24,6 +27,7 @@ describe('getDiscountedFee', () => {
discountedFee: '',
volumeDiscount: '0',
referralDiscount: '0',
totalDiscount: '0',
});
});
});
@ -35,7 +39,7 @@ describe('getTotalDiscountFactor', () => {
volumeDiscountFactor: '0',
referralDiscountFactor: '0',
})
).toEqual(0);
).toEqual('0');
});
it('returns volumeDiscountFactor if referralDiscountFactor is 0', () => {
@ -44,7 +48,7 @@ describe('getTotalDiscountFactor', () => {
volumeDiscountFactor: '0.1',
referralDiscountFactor: '0',
})
).toEqual(0.1);
).toEqual('-0.1');
});
it('returns referralDiscountFactor if volumeDiscountFactor is 0', () => {
expect(
@ -52,7 +56,7 @@ describe('getTotalDiscountFactor', () => {
volumeDiscountFactor: '0',
referralDiscountFactor: '0.1',
})
).toEqual(0.1);
).toEqual('-0.1');
});
it('calculates discount using referralDiscountFactor and volumeDiscountFactor', () => {
@ -61,6 +65,6 @@ describe('getTotalDiscountFactor', () => {
volumeDiscountFactor: '0.2',
referralDiscountFactor: '0.1',
})
).toBeCloseTo(0.28);
).toBe('-0.28');
});
});

View File

@ -15,6 +15,7 @@ export const getDiscountedFee = (
discountedFee: feeAmount,
volumeDiscount: '0',
referralDiscount: '0',
totalDiscount: '0',
};
}
const referralDiscount = new BigNumber(referralDiscountFactor || '0')
@ -23,12 +24,14 @@ export const getDiscountedFee = (
const volumeDiscount = new BigNumber(volumeDiscountFactor || '0')
.multipliedBy((BigInt(feeAmount) - BigInt(referralDiscount)).toString())
.toFixed(0, BigNumber.ROUND_FLOOR);
const totalDiscount = (
BigInt(referralDiscount) + BigInt(volumeDiscount)
).toString();
const discountedFee = (
BigInt(feeAmount || '0') -
BigInt(referralDiscount) -
BigInt(volumeDiscount)
BigInt(feeAmount || '0') - BigInt(totalDiscount)
).toString();
return {
totalDiscount,
referralDiscount,
volumeDiscount,
discountedFee,
@ -39,16 +42,28 @@ export const getTotalDiscountFactor = (feeEstimate?: {
volumeDiscountFactor?: string;
referralDiscountFactor?: string;
}) => {
if (!feeEstimate) {
return 0;
if (
!feeEstimate ||
(feeEstimate.referralDiscountFactor === '0' &&
feeEstimate.volumeDiscountFactor === '0')
) {
return '0';
}
const volumeFactor = Number(feeEstimate?.volumeDiscountFactor) || 0;
const referralFactor = Number(feeEstimate?.referralDiscountFactor) || 0;
if (!volumeFactor) {
return referralFactor;
const volumeFactor = new BigNumber(
feeEstimate?.volumeDiscountFactor || 0
).minus(1);
const referralFactor = new BigNumber(
feeEstimate?.referralDiscountFactor || 0
).minus(1);
if (volumeFactor.isZero()) {
return feeEstimate.referralDiscountFactor
? `-${feeEstimate.referralDiscountFactor}`
: '0';
}
if (!referralFactor) {
return volumeFactor;
if (referralFactor.isZero()) {
return feeEstimate.volumeDiscountFactor
? `-${feeEstimate.volumeDiscountFactor}`
: '0';
}
return 1 - (1 - volumeFactor) * (1 - referralFactor);
return volumeFactor.multipliedBy(referralFactor).minus(1).toString();
};

View File

@ -14,13 +14,16 @@ describe('FeesBreakdown', () => {
liquidityFee: '100',
};
const props = {
totalFeeAmount: '100',
fees,
feeFactors,
symbol: 'USD',
decimals: 2,
referralDiscountFactor: '0.01',
volumeDiscountFactor: '0.01',
feeEstimate: {
referralDiscountFactor: '0.01',
volumeDiscountFactor: '0.01',
totalFeeAmount: '100',
fees,
},
};
render(<FeesBreakdown {...props} />);
expect(screen.getByText('Maker fee').nextElementSibling).toHaveTextContent(

View File

@ -1,12 +1,13 @@
import { sumFeesFactors } from '@vegaprotocol/markets';
import type { TradeFee, FeeFactors } from '@vegaprotocol/types';
import type { FeeFactors } from '@vegaprotocol/types';
import {
addDecimalsFormatNumber,
formatNumberPercentage,
} from '@vegaprotocol/utils';
import BigNumber from 'bignumber.js';
import { getDiscountedFee } from '../discounts';
import { getDiscountedFee, getTotalDiscountFactor } from '../discounts';
import { useT } from '../../use-t';
import { type useEstimateFees } from '../../hooks/use-estimate-fees';
const formatValue = (
value: string | number | null | undefined,
@ -24,18 +25,16 @@ const FeesBreakdownItem = ({
decimals,
}: {
label: string;
factor?: string;
factor?: string | number;
value: string;
symbol?: string;
decimals: number;
}) => (
<>
<dt className="col-span-2">{label}</dt>
{factor && (
<dd className="text-right col-span-1">
{formatNumberPercentage(new BigNumber(factor).times(100))}
</dd>
)}
<dd className="text-right col-span-1">
{factor ? formatNumberPercentage(new BigNumber(factor).times(100)) : ''}
</dd>
<dd className="text-right col-span-3">
{formatValue(value, decimals)} {symbol || ''}
</dd>
@ -43,59 +42,35 @@ const FeesBreakdownItem = ({
);
export const FeesBreakdown = ({
totalFeeAmount,
fees,
feeEstimate,
feeFactors,
symbol,
decimals,
referralDiscountFactor,
volumeDiscountFactor,
}: {
totalFeeAmount?: string;
fees?: TradeFee;
feeEstimate: ReturnType<typeof useEstimateFees>;
feeFactors?: FeeFactors;
symbol?: string;
decimals: number;
referralDiscountFactor?: string;
volumeDiscountFactor?: string;
}) => {
const t = useT();
const { fees, totalFeeAmount, referralDiscountFactor, volumeDiscountFactor } =
feeEstimate || {};
if (!fees || !totalFeeAmount || totalFeeAmount === '0') return null;
const { discountedFee: discountedInfrastructureFee } = getDiscountedFee(
fees.infrastructureFee,
referralDiscountFactor,
volumeDiscountFactor
);
const { discountedFee: discountedLiquidityFee } = getDiscountedFee(
fees.liquidityFee,
referralDiscountFactor,
volumeDiscountFactor
);
const { discountedFee: discountedMakerFee } = getDiscountedFee(
fees.makerFee,
referralDiscountFactor,
volumeDiscountFactor
);
const {
discountedFee: discountedTotalFeeAmount,
volumeDiscount,
referralDiscount,
} = getDiscountedFee(
totalFeeAmount,
referralDiscountFactor,
volumeDiscountFactor
);
const totalDiscountFactor = getTotalDiscountFactor(feeEstimate);
const { discountedFee: discountedTotalFeeAmount, totalDiscount } =
getDiscountedFee(
totalFeeAmount,
referralDiscountFactor,
volumeDiscountFactor
);
return (
<dl className="grid grid-cols-6">
<FeesBreakdownItem
label={t('Infrastructure fee')}
factor={feeFactors?.infrastructureFee}
value={discountedInfrastructureFee}
value={fees.infrastructureFee}
symbol={symbol}
decimals={decimals}
/>
@ -103,7 +78,7 @@ export const FeesBreakdown = ({
<FeesBreakdownItem
label={t('Liquidity fee')}
factor={feeFactors?.liquidityFee}
value={discountedLiquidityFee}
value={fees.liquidityFee}
symbol={symbol}
decimals={decimals}
/>
@ -111,35 +86,50 @@ export const FeesBreakdown = ({
<FeesBreakdownItem
label={t('Maker fee')}
factor={feeFactors?.makerFee}
value={discountedMakerFee}
value={fees.makerFee}
symbol={symbol}
decimals={decimals}
/>
{volumeDiscountFactor && volumeDiscount !== '0' && (
<FeesBreakdownItem
label={t('Volume discount')}
factor={volumeDiscountFactor}
value={volumeDiscount}
symbol={symbol}
decimals={decimals}
/>
{totalDiscount && totalDiscount !== '0' ? (
<>
<FeesBreakdownItem
label={t('Subtotal')}
value={totalFeeAmount}
factor={
feeFactors ? sumFeesFactors(feeFactors)?.toString() : undefined
}
symbol={symbol}
decimals={decimals}
/>
<div className="col-span-6 mt-2"></div>
<FeesBreakdownItem
label={t('Discount')}
factor={totalDiscountFactor}
value={`-${totalDiscount}`}
symbol={symbol}
decimals={decimals}
/>
<FeesBreakdownItem
label={t('Total')}
value={discountedTotalFeeAmount}
symbol={symbol}
decimals={decimals}
/>
</>
) : (
<>
<div className="col-span-6 mt-2"></div>
<FeesBreakdownItem
label={t('Total')}
factor={
feeFactors ? sumFeesFactors(feeFactors)?.toString() : undefined
}
value={totalFeeAmount}
symbol={symbol}
decimals={decimals}
/>
</>
)}
{referralDiscountFactor && referralDiscount !== '0' && (
<FeesBreakdownItem
label={t('Referral discount')}
factor={referralDiscountFactor}
value={referralDiscount}
symbol={symbol}
decimals={decimals}
/>
)}
<FeesBreakdownItem
label={t('Total fees')}
factor={feeFactors ? sumFeesFactors(feeFactors)?.toString() : undefined}
value={discountedTotalFeeAmount}
symbol={symbol}
decimals={decimals}
/>
</dl>
);
};

View File

@ -21,6 +21,7 @@
"DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT": "To cover the required margin, this amount will be drawn from your general ({{assetSymbol}}) account.",
"Deposit {{assetSymbol}}": "Deposit {{assetSymbol}}",
"Devnet": "Devnet",
"Discount": "Discount",
"EST_TOTAL_MARGIN_TOOLTIP_TEXT": "Estimated total margin that will cover open positions, active orders and this order.",
"Est. uncrossing price": "Est. uncrossing price",
"Est. uncrossing vol": "Est. uncrossing vol",
@ -78,6 +79,7 @@
"Size": "Size",
"Size cannot be lower than {{sizeStep}}": "Size cannot be lower than {{sizeStep}}",
"sizeAtPrice-market": "market",
"Subtotal": "Subtotal",
"Stagnet": "Stagnet",
"Stop": "Stop",
"Stop Limit": "Stop Limit",
@ -104,7 +106,7 @@
"Time in force": "Time in force",
"TIME_IN_FORCE_SELECTOR_LIQUIDITY_MONITORING_AUCTION": "This market is in auction until it reaches <0>sufficient liquidity</0>. Until the auction ends, you can only place GFA, GTT, or GTC limit orders.",
"TIME_IN_FORCE_SELECTOR_PRICE_MONITORING_AUCTION": "This market is in auction due to <0>high price volatility</0>. Until the auction ends, you can only place GFA, GTT, or GTC limit orders.",
"Total fees": "Total fees",
"Total": "Total",
"Total margin available": "Total margin available",
"TOTAL_MARGIN_AVAILABLE": "Total margin available = general {{assetSymbol}} balance ({{generalAccountBalance}} {{assetSymbol}}) + margin balance ({{marginAccountBalance}} {{assetSymbol}}) - maintenance level ({{marginMaintenance}} {{assetSymbol}}).",
"Trading terminated": "Trading terminated",