From a2b9b0da053303d8eb73d1a08dba9cdd3c4adbbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20G=C5=82ownia?= Date: Wed, 29 Nov 2023 13:59:42 +0100 Subject: [PATCH] feat(deal-ticket): enhance for fee discounts (#5353) --- .../deal-ticket/deal-ticket-fee-details.tsx | 11 +- .../src/components/discounts.spec.ts | 12 +- libs/deal-ticket/src/components/discounts.ts | 39 ++++-- .../fees-breakdown/fees-breakdown.spec.tsx | 11 +- .../fees-breakdown/fees-breakdown.tsx | 130 ++++++++---------- libs/i18n/src/locales/en/deal-ticket.json | 4 +- 6 files changed, 108 insertions(+), 99 deletions(-) diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx index c6adb8531..2b3b88f48 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx @@ -77,12 +77,10 @@ export const DealTicketFeeDetails = ({ label={ <> {t('Fees')} - {totalDiscountFactor ? ( + {totalDiscountFactor !== '0' ? ( - - {formatNumberPercentage( - new BigNumber(totalDiscountFactor).multipliedBy(100), - 2 + new BigNumber(totalDiscountFactor).multipliedBy(100) )} ) : null} @@ -105,10 +103,7 @@ export const DealTicketFeeDetails = ({ )}

{ 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'); }); }); diff --git a/libs/deal-ticket/src/components/discounts.ts b/libs/deal-ticket/src/components/discounts.ts index 452e193db..9ddf8e632 100644 --- a/libs/deal-ticket/src/components/discounts.ts +++ b/libs/deal-ticket/src/components/discounts.ts @@ -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(); }; diff --git a/libs/deal-ticket/src/components/fees-breakdown/fees-breakdown.spec.tsx b/libs/deal-ticket/src/components/fees-breakdown/fees-breakdown.spec.tsx index 9d99789c5..b6383bf15 100644 --- a/libs/deal-ticket/src/components/fees-breakdown/fees-breakdown.spec.tsx +++ b/libs/deal-ticket/src/components/fees-breakdown/fees-breakdown.spec.tsx @@ -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(); expect(screen.getByText('Maker fee').nextElementSibling).toHaveTextContent( diff --git a/libs/deal-ticket/src/components/fees-breakdown/fees-breakdown.tsx b/libs/deal-ticket/src/components/fees-breakdown/fees-breakdown.tsx index bfeaad205..7fca7ce33 100644 --- a/libs/deal-ticket/src/components/fees-breakdown/fees-breakdown.tsx +++ b/libs/deal-ticket/src/components/fees-breakdown/fees-breakdown.tsx @@ -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; }) => ( <>
{label}
- {factor && ( -
- {formatNumberPercentage(new BigNumber(factor).times(100))} -
- )} +
+ {factor ? formatNumberPercentage(new BigNumber(factor).times(100)) : ''} +
{formatValue(value, decimals)} {symbol || ''}
@@ -43,59 +42,35 @@ const FeesBreakdownItem = ({ ); export const FeesBreakdown = ({ - totalFeeAmount, - fees, + feeEstimate, feeFactors, symbol, decimals, - referralDiscountFactor, - volumeDiscountFactor, }: { - totalFeeAmount?: string; - fees?: TradeFee; + feeEstimate: ReturnType; 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 (
@@ -103,7 +78,7 @@ export const FeesBreakdown = ({ @@ -111,35 +86,50 @@ export const FeesBreakdown = ({ - {volumeDiscountFactor && volumeDiscount !== '0' && ( - + {totalDiscount && totalDiscount !== '0' ? ( + <> + +
+ + + + ) : ( + <> +
+ + )} - {referralDiscountFactor && referralDiscount !== '0' && ( - - )} -
); }; diff --git a/libs/i18n/src/locales/en/deal-ticket.json b/libs/i18n/src/locales/en/deal-ticket.json index e7d4cc346..e2ca3324d 100644 --- a/libs/i18n/src/locales/en/deal-ticket.json +++ b/libs/i18n/src/locales/en/deal-ticket.json @@ -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. 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. 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",