feat(trading): display fees discounts (#4800)

This commit is contained in:
Bartłomiej Głownia 2023-10-12 16:58:57 +02:00 committed by GitHub
parent efe9b7a0e7
commit 852c9bb8f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 614 additions and 283 deletions

View File

@ -1,6 +1,6 @@
import { useCallback, useState } from 'react';
import { t } from '@vegaprotocol/i18n';
import { FeesBreakdown, getAsset, getQuoteName } from '@vegaprotocol/markets';
import { getAsset, getQuoteName } from '@vegaprotocol/markets';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet';
@ -8,7 +8,11 @@ import type { Market } from '@vegaprotocol/markets';
import type { EstimatePositionQuery } from '@vegaprotocol/positions';
import { AccountBreakdownDialog } from '@vegaprotocol/accounts';
import { formatRange, formatValue } from '@vegaprotocol/utils';
import {
formatNumberPercentage,
formatRange,
formatValue,
} from '@vegaprotocol/utils';
import { marketMarginDataProvider } from '@vegaprotocol/accounts';
import { useDataProvider } from '@vegaprotocol/data-provider';
import * as AccordionPrimitive from '@radix-ui/react-accordion';
@ -22,15 +26,23 @@ import {
EST_TOTAL_MARGIN_TOOLTIP_TEXT,
MARGIN_ACCOUNT_TOOLTIP_TEXT,
} from '../../constants';
import { useEstimateFees } from '../../hooks';
import {
sumFees,
sumFeesDiscounts,
useEstimateFees,
} from '../../hooks/use-estimate-fees';
import { KeyValue } from './key-value';
import {
Accordion,
AccordionChevron,
AccordionPanel,
Intent,
Pill,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import BigNumber from 'bignumber.js';
import { FeesBreakdown } from '../fees-breakdown';
const emptyValue = '-';
@ -50,7 +62,18 @@ export const DealTicketFeeDetails = ({
const feeEstimate = useEstimateFees(order, isMarketInAuction);
const asset = getAsset(market);
const { decimals: assetDecimals, quantum } = asset;
const totalFees = feeEstimate?.fees && sumFees(feeEstimate?.fees);
const feesDiscounts =
feeEstimate?.fees && sumFeesDiscounts(feeEstimate?.fees);
const totalPercentageDiscount =
feesDiscounts &&
totalFees &&
feesDiscounts.total !== '0' &&
totalFees !== '0' &&
new BigNumber(feesDiscounts.total)
.dividedBy(BigNumber.sum(totalFees, feesDiscounts.total))
.times(100);
return (
<KeyValue
label={t('Fees')}
@ -59,8 +82,19 @@ export const DealTicketFeeDetails = ({
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`
}
formattedValue={
feeEstimate?.totalFeeAmount &&
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals, quantum)}`
<>
{totalPercentageDiscount && (
<Pill size="xxs" intent={Intent.Warning} className="mr-1">
-{formatNumberPercentage(totalPercentageDiscount, 2)}
</Pill>
)}
{feeEstimate?.totalFeeAmount &&
`~${formatValue(
feeEstimate?.totalFeeAmount,
assetDecimals,
quantum
)}`}
</>
}
labelDescription={
<>

View File

@ -9,7 +9,7 @@ export interface KeyValuePros {
symbol: string;
indent?: boolean | undefined;
labelDescription?: ReactNode;
formattedValue?: string;
formattedValue?: ReactNode;
onClick?: () => void;
}
@ -23,7 +23,11 @@ export const KeyValue = ({
onClick,
formattedValue,
}: KeyValuePros) => {
const displayValue = `${formattedValue ?? '-'} ${symbol || ''}`;
const displayValue = (
<>
{formattedValue || '-'} {symbol || ''}
</>
);
const valueElement = onClick ? (
<button onClick={onClick} className="font-mono ml-auto">
{displayValue}

View File

@ -0,0 +1,130 @@
import { sumFeesFactors } from '@vegaprotocol/markets';
import type { TradeFee, FeeFactors } from '@vegaprotocol/types';
import {
addDecimalsFormatNumber,
formatNumberPercentage,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import BigNumber from 'bignumber.js';
import { sumFees, sumFeesDiscounts } from '../../hooks';
const formatValue = (
value: string | number | null | undefined,
decimals: number
): string =>
value && !isNaN(Number(value))
? addDecimalsFormatNumber(value, decimals)
: '-';
const FeesBreakdownItem = ({
label,
factor,
value,
symbol,
decimals,
}: {
label: string;
factor?: BigNumber;
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), 2)}
</dd>
)}
<dd className="text-right col-span-3">
{formatValue(value, decimals)} {symbol || ''}
</dd>
</>
);
export const FeesBreakdown = ({
fees,
feeFactors,
symbol,
decimals,
}: {
fees?: TradeFee;
feeFactors?: FeeFactors;
symbol?: string;
decimals: number;
}) => {
if (!fees) return null;
const totalFees = sumFees(fees);
const {
total: totalDiscount,
referral: referralDiscount,
volume: volumeDiscount,
} = sumFeesDiscounts(fees);
if (totalFees === '0') return null;
return (
<dl className="grid grid-cols-6">
<FeesBreakdownItem
label={t('Infrastructure fee')}
factor={
feeFactors?.infrastructureFee
? new BigNumber(feeFactors?.infrastructureFee)
: undefined
}
value={fees.infrastructureFee}
symbol={symbol}
decimals={decimals}
/>
<FeesBreakdownItem
label={t('Liquidity fee')}
factor={
feeFactors?.liquidityFee
? new BigNumber(feeFactors?.liquidityFee)
: undefined
}
value={fees.liquidityFee}
symbol={symbol}
decimals={decimals}
/>
<FeesBreakdownItem
label={t('Maker fee')}
factor={
feeFactors?.makerFee ? new BigNumber(feeFactors?.makerFee) : undefined
}
value={fees.makerFee}
symbol={symbol}
decimals={decimals}
/>
{volumeDiscount && volumeDiscount !== '0' && (
<FeesBreakdownItem
label={t('Volume discount')}
factor={new BigNumber(volumeDiscount).dividedBy(
BigNumber.sum(totalFees, totalDiscount)
)}
value={volumeDiscount}
symbol={symbol}
decimals={decimals}
/>
)}
{referralDiscount && referralDiscount !== '0' && (
<FeesBreakdownItem
label={t('Referral discount')}
factor={new BigNumber(referralDiscount).dividedBy(
BigNumber.sum(totalFees, totalDiscount)
)}
value={referralDiscount}
symbol={symbol}
decimals={decimals}
/>
)}
<FeesBreakdownItem
label={t('Total fees')}
factor={feeFactors ? sumFeesFactors(feeFactors) : undefined}
value={totalFees}
symbol={symbol}
decimals={decimals}
/>
</dl>
);
};

View File

@ -1,3 +1,4 @@
export * from './deal-ticket';
export * from './deal-ticket-validation';
export * from './fees-breakdown';
export * from './trading-mode-tooltip';

View File

@ -22,6 +22,12 @@ query EstimateFees(
makerFee
infrastructureFee
liquidityFee
makerFeeReferralDiscount
makerFeeVolumeDiscount
infrastructureFeeReferralDiscount
infrastructureFeeVolumeDiscount
liquidityFeeReferralDiscount
liquidityFeeVolumeDiscount
}
totalFeeAmount
}

View File

@ -15,7 +15,7 @@ export type EstimateFeesQueryVariables = Types.Exact<{
}>;
export type EstimateFeesQuery = { __typename?: 'Query', estimateFees: { __typename?: 'FeeEstimate', totalFeeAmount: string, fees: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } } };
export type EstimateFeesQuery = { __typename?: 'Query', estimateFees: { __typename?: 'FeeEstimate', totalFeeAmount: string, fees: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null } } };
export const EstimateFeesDocument = gql`
@ -34,6 +34,12 @@ export const EstimateFeesDocument = gql`
makerFee
infrastructureFee
liquidityFee
makerFeeReferralDiscount
makerFeeVolumeDiscount
infrastructureFeeReferralDiscount
infrastructureFeeVolumeDiscount
liquidityFeeReferralDiscount
liquidityFeeVolumeDiscount
}
totalFeeAmount
}

View File

@ -6,11 +6,17 @@ import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
const data: EstimateFeesQuery = {
estimateFees: {
totalFeeAmount: '12',
totalFeeAmount: '120',
fees: {
infrastructureFee: '2',
liquidityFee: '4',
makerFee: '6',
infrastructureFee: '20',
infrastructureFeeReferralDiscount: '2',
infrastructureFeeVolumeDiscount: '4',
liquidityFee: '40',
liquidityFeeReferralDiscount: '6',
liquidityFeeVolumeDiscount: '8',
makerFee: '60',
makerFeeReferralDiscount: '10',
makerFeeVolumeDiscount: '12',
},
},
};
@ -67,11 +73,17 @@ describe('useEstimateFees', () => {
)
);
expect(result.current).toEqual({
totalFeeAmount: '6',
totalFeeAmount: '60',
fees: {
infrastructureFee: '1',
liquidityFee: '2',
makerFee: '3',
infrastructureFee: '10',
infrastructureFeeReferralDiscount: '1',
infrastructureFeeVolumeDiscount: '2',
liquidityFee: '20',
liquidityFeeReferralDiscount: '3',
liquidityFeeVolumeDiscount: '4',
makerFee: '30',
makerFeeReferralDiscount: '5',
makerFeeVolumeDiscount: '6',
},
});
});

View File

@ -5,6 +5,32 @@ import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
import { useEstimateFeesQuery } from './__generated__/EstimateOrder';
const divideByTwo = (n: string) => (BigInt(n) / BigInt(2)).toString();
export const sumFeesDiscounts = (
fees: EstimateFeesQuery['estimateFees']['fees']
) => {
const volume = (
BigInt(fees.makerFeeVolumeDiscount || '0') +
BigInt(fees.infrastructureFeeVolumeDiscount || '0') +
BigInt(fees.liquidityFeeVolumeDiscount || '0')
).toString();
const referral = (
BigInt(fees.makerFeeReferralDiscount || '0') +
BigInt(fees.infrastructureFeeReferralDiscount || '0') +
BigInt(fees.liquidityFeeReferralDiscount || '0')
).toString();
return {
volume,
referral,
total: (BigInt(volume) + BigInt(referral)).toString(),
};
};
export const sumFees = (fees: EstimateFeesQuery['estimateFees']['fees']) =>
(
BigInt(fees.makerFee || '0') +
BigInt(fees.infrastructureFee || '0') +
BigInt(fees.liquidityFee || '0')
).toString();
export const useEstimateFees = (
order?: OrderSubmissionBody['orderSubmission'],
@ -43,6 +69,26 @@ export const useEstimateFees = (
),
liquidityFee: divideByTwo(data.estimateFees.fees.liquidityFee),
makerFee: divideByTwo(data.estimateFees.fees.makerFee),
infrastructureFeeReferralDiscount:
data.estimateFees.fees.infrastructureFeeReferralDiscount &&
divideByTwo(
data.estimateFees.fees.infrastructureFeeReferralDiscount
),
infrastructureFeeVolumeDiscount:
data.estimateFees.fees.infrastructureFeeVolumeDiscount &&
divideByTwo(data.estimateFees.fees.infrastructureFeeVolumeDiscount),
liquidityFeeReferralDiscount:
data.estimateFees.fees.liquidityFeeReferralDiscount &&
divideByTwo(data.estimateFees.fees.liquidityFeeReferralDiscount),
liquidityFeeVolumeDiscount:
data.estimateFees.fees.liquidityFeeVolumeDiscount &&
divideByTwo(data.estimateFees.fees.liquidityFeeVolumeDiscount),
makerFeeReferralDiscount:
data.estimateFees.fees.makerFeeReferralDiscount &&
divideByTwo(data.estimateFees.fees.makerFeeReferralDiscount),
makerFeeVolumeDiscount:
data.estimateFees.fees.makerFeeVolumeDiscount &&
divideByTwo(data.estimateFees.fees.makerFeeVolumeDiscount),
},
}
: data?.estimateFees;

View File

@ -1,3 +1,15 @@
fragment TradeFeeFields on TradeFee {
makerFee
infrastructureFee
liquidityFee
makerFeeReferralDiscount
makerFeeVolumeDiscount
infrastructureFeeReferralDiscount
infrastructureFeeVolumeDiscount
liquidityFeeReferralDiscount
liquidityFeeVolumeDiscount
}
fragment FillFields on Trade {
id
market {
@ -16,14 +28,10 @@ fragment FillFields on Trade {
id
}
buyerFee {
makerFee
infrastructureFee
liquidityFee
...TradeFeeFields
}
sellerFee {
makerFee
infrastructureFee
liquidityFee
...TradeFeeFields
}
}
@ -61,14 +69,10 @@ fragment FillUpdateFields on TradeUpdate {
createdAt
type
buyerFee {
makerFee
infrastructureFee
liquidityFee
...TradeFeeFields
}
sellerFee {
makerFee
infrastructureFee
liquidityFee
...TradeFeeFields
}
}

View File

@ -3,9 +3,11 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type FillFieldsFragment = { __typename?: 'Trade', id: string, createdAt: any, price: string, size: string, buyOrder: string, sellOrder: string, aggressor: Types.Side, market: { __typename?: 'Market', id: string }, buyer: { __typename?: 'Party', id: string }, seller: { __typename?: 'Party', id: string }, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } };
export type TradeFeeFieldsFragment = { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null };
export type FillEdgeFragment = { __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, createdAt: any, price: string, size: string, buyOrder: string, sellOrder: string, aggressor: Types.Side, market: { __typename?: 'Market', id: string }, buyer: { __typename?: 'Party', id: string }, seller: { __typename?: 'Party', id: string }, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } } };
export type FillFieldsFragment = { __typename?: 'Trade', id: string, createdAt: any, price: string, size: string, buyOrder: string, sellOrder: string, aggressor: Types.Side, market: { __typename?: 'Market', id: string }, buyer: { __typename?: 'Party', id: string }, seller: { __typename?: 'Party', id: string }, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null } };
export type FillEdgeFragment = { __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, createdAt: any, price: string, size: string, buyOrder: string, sellOrder: string, aggressor: Types.Side, market: { __typename?: 'Market', id: string }, buyer: { __typename?: 'Party', id: string }, seller: { __typename?: 'Party', id: string }, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null } } };
export type FillsQueryVariables = Types.Exact<{
filter?: Types.InputMaybe<Types.TradesFilter>;
@ -13,17 +15,30 @@ export type FillsQueryVariables = Types.Exact<{
}>;
export type FillsQuery = { __typename?: 'Query', trades?: { __typename?: 'TradeConnection', edges: Array<{ __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, createdAt: any, price: string, size: string, buyOrder: string, sellOrder: string, aggressor: Types.Side, market: { __typename?: 'Market', id: string }, buyer: { __typename?: 'Party', id: string }, seller: { __typename?: 'Party', id: string }, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } | null };
export type FillsQuery = { __typename?: 'Query', trades?: { __typename?: 'TradeConnection', edges: Array<{ __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, createdAt: any, price: string, size: string, buyOrder: string, sellOrder: string, aggressor: Types.Side, market: { __typename?: 'Market', id: string }, buyer: { __typename?: 'Party', id: string }, seller: { __typename?: 'Party', id: string }, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null } } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } | null };
export type FillUpdateFieldsFragment = { __typename?: 'TradeUpdate', id: string, marketId: string, buyOrder: string, sellOrder: string, buyerId: string, sellerId: string, aggressor: Types.Side, price: string, size: string, createdAt: any, type: Types.TradeType, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } };
export type FillUpdateFieldsFragment = { __typename?: 'TradeUpdate', id: string, marketId: string, buyOrder: string, sellOrder: string, buyerId: string, sellerId: string, aggressor: Types.Side, price: string, size: string, createdAt: any, type: Types.TradeType, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null } };
export type FillsEventSubscriptionVariables = Types.Exact<{
filter: Types.TradesSubscriptionFilter;
}>;
export type FillsEventSubscription = { __typename?: 'Subscription', tradesStream?: Array<{ __typename?: 'TradeUpdate', id: string, marketId: string, buyOrder: string, sellOrder: string, buyerId: string, sellerId: string, aggressor: Types.Side, price: string, size: string, createdAt: any, type: Types.TradeType, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } }> | null };
export type FillsEventSubscription = { __typename?: 'Subscription', tradesStream?: Array<{ __typename?: 'TradeUpdate', id: string, marketId: string, buyOrder: string, sellOrder: string, buyerId: string, sellerId: string, aggressor: Types.Side, price: string, size: string, createdAt: any, type: Types.TradeType, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null } }> | null };
export const TradeFeeFieldsFragmentDoc = gql`
fragment TradeFeeFields on TradeFee {
makerFee
infrastructureFee
liquidityFee
makerFeeReferralDiscount
makerFeeVolumeDiscount
infrastructureFeeReferralDiscount
infrastructureFeeVolumeDiscount
liquidityFeeReferralDiscount
liquidityFeeVolumeDiscount
}
`;
export const FillFieldsFragmentDoc = gql`
fragment FillFields on Trade {
id
@ -43,17 +58,13 @@ export const FillFieldsFragmentDoc = gql`
id
}
buyerFee {
makerFee
infrastructureFee
liquidityFee
...TradeFeeFields
}
sellerFee {
makerFee
infrastructureFee
liquidityFee
...TradeFeeFields
}
}
`;
${TradeFeeFieldsFragmentDoc}`;
export const FillEdgeFragmentDoc = gql`
fragment FillEdge on TradeEdge {
node {
@ -76,17 +87,13 @@ export const FillUpdateFieldsFragmentDoc = gql`
createdAt
type
buyerFee {
makerFee
infrastructureFee
liquidityFee
...TradeFeeFields
}
sellerFee {
makerFee
infrastructureFee
liquidityFee
...TradeFeeFields
}
}
`;
${TradeFeeFieldsFragmentDoc}`;
export const FillsDocument = gql`
query Fills($filter: TradesFilter, $pagination: Pagination) {
trades(filter: $filter, pagination: $pagination) {

View File

@ -4,36 +4,38 @@ import { getDateTimeFormat } from '@vegaprotocol/utils';
import * as Schema from '@vegaprotocol/types';
import type { PartialDeep } from 'type-fest';
import type { Trade } from './fills-data-provider';
import { FillsTable, getFeesBreakdown } from './fills-table';
import {
FeesDiscountBreakdownTooltip,
FillsTable,
getFeesBreakdown,
getTotalFeesDiscounts,
} from './fills-table';
import { generateFill } from './test-helpers';
import type { TradeFeeFieldsFragment } from './__generated__/Fills';
describe('FillsTable', () => {
let defaultFill: PartialDeep<Trade>;
beforeEach(() => {
defaultFill = {
price: '100',
size: '300000',
market: {
decimalPlaces: 2,
positionDecimalPlaces: 5,
tradableInstrument: {
instrument: {
code: 'test market',
product: {
__typename: 'Future',
settlementAsset: {
decimals: 2,
symbol: 'BTC',
},
},
const partyId = 'party-id';
const defaultFill: PartialDeep<Trade> = {
price: '100',
size: '300000',
market: {
decimalPlaces: 2,
positionDecimalPlaces: 5,
tradableInstrument: {
instrument: {
code: 'test market',
product: {
__typename: 'Future',
settlementAsset: {
decimals: 2,
symbol: 'BTC',
},
},
},
createdAt: new Date('2022-02-02T14:00:00').toISOString(),
};
});
},
},
createdAt: new Date('2022-02-02T14:00:00').toISOString(),
};
describe('FillsTable', () => {
it('correct columns are rendered', async () => {
await act(async () => {
render(<FillsTable partyId="party-id" rowData={[generateFill()]} />);
@ -47,6 +49,7 @@ describe('FillsTable', () => {
'Notional',
'Role',
'Fee',
'Fee Discount',
'Date',
'', // action column
];
@ -55,7 +58,6 @@ describe('FillsTable', () => {
});
it('formats cells correctly for buyer fill', async () => {
const partyId = 'party-id';
const buyerFill = generateFill({
...defaultFill,
buyer: {
@ -68,7 +70,9 @@ describe('FillsTable', () => {
liquidityFee: '2',
},
});
render(<FillsTable partyId={partyId} rowData={[{ ...buyerFill }]} />);
await act(async () => {
render(<FillsTable partyId={partyId} rowData={[{ ...buyerFill }]} />);
});
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
buyerFill.market?.tradableInstrument.instrument.code || '',
@ -77,6 +81,7 @@ describe('FillsTable', () => {
'3.00 BTC',
'Maker',
'2.00 BTC',
'0.27 BTC',
getDateTimeFormat().format(new Date(buyerFill.createdAt)),
'', // action column
];
@ -89,7 +94,6 @@ describe('FillsTable', () => {
});
it('should format cells correctly for seller fill', async () => {
const partyId = 'party-id';
const buyerFill = generateFill({
...defaultFill,
seller: {
@ -102,7 +106,9 @@ describe('FillsTable', () => {
liquidityFee: '1',
},
});
render(<FillsTable partyId={partyId} rowData={[buyerFill]} />);
await act(async () => {
render(<FillsTable partyId={partyId} rowData={[buyerFill]} />);
});
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
@ -112,6 +118,7 @@ describe('FillsTable', () => {
'3.00 BTC',
'Taker',
'0.03 BTC',
'0.27 BTC',
getDateTimeFormat().format(new Date(buyerFill.createdAt)),
'', // action column
];
@ -124,7 +131,6 @@ describe('FillsTable', () => {
});
it('should format cells correctly for side unspecified', async () => {
const partyId = 'party-id';
const buyerFill = generateFill({
...defaultFill,
seller: {
@ -137,7 +143,9 @@ describe('FillsTable', () => {
liquidityFee: '1',
},
});
render(<FillsTable partyId={partyId} rowData={[buyerFill]} />);
await act(async () => {
render(<FillsTable partyId={partyId} rowData={[buyerFill]} />);
});
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
@ -147,6 +155,7 @@ describe('FillsTable', () => {
'3.00 BTC',
'-',
'0.03 BTC',
'0.27 BTC',
getDateTimeFormat().format(new Date(buyerFill.createdAt)),
'', // action column
];
@ -158,30 +167,31 @@ describe('FillsTable', () => {
expect(amountCell).toHaveClass('text-market-red');
});
it('should render correct maker or taker role', async () => {
const partyId = 'party-id';
it('should render correct taker role', async () => {
const takerFill = generateFill({
seller: {
id: partyId,
},
aggressor: Schema.Side.SIDE_SELL,
});
const { rerender } = render(
<FillsTable partyId={partyId} rowData={[takerFill]} />
);
await act(async () => {
render(<FillsTable partyId={partyId} rowData={[takerFill]} />);
});
expect(
screen
.getAllByRole('gridcell')
.find((c) => c.getAttribute('col-id') === 'aggressor')
).toHaveTextContent('Taker');
});
it('should render correct maker role', async () => {
const makerFill = generateFill({
seller: {
id: partyId,
},
aggressor: Schema.Side.SIDE_BUY,
});
rerender(<FillsTable partyId={partyId} rowData={[makerFill]} />);
render(<FillsTable partyId={partyId} rowData={[makerFill]} />);
expect(
screen
@ -191,22 +201,19 @@ describe('FillsTable', () => {
});
it('should render tooltip over fees', async () => {
const partyId = 'party-id';
const takerFill = generateFill({
seller: {
id: partyId,
},
aggressor: Schema.Side.SIDE_SELL,
});
render(<FillsTable partyId={partyId} rowData={[takerFill]} />);
await act(async () => {
render(<FillsTable partyId={partyId} rowData={[takerFill]} />);
});
const feeCell = screen
.getAllByRole('gridcell')
.find(
(c) =>
c.getAttribute('col-id') ===
'market.tradableInstrument.instrument.product'
);
.find((c) => c.getAttribute('col-id') === 'fee');
expect(feeCell).toBeInTheDocument();
await userEvent.hover(feeCell as HTMLElement);
@ -215,8 +222,29 @@ describe('FillsTable', () => {
).toBeInTheDocument();
});
it('should render tooltip over fees discounts', async () => {
const takerFill = generateFill({
seller: {
id: partyId,
},
aggressor: Schema.Side.SIDE_SELL,
});
await act(async () => {
render(<FillsTable partyId={partyId} rowData={[takerFill]} />);
});
const cell = screen
.getAllByRole('gridcell')
.find((c) => c.getAttribute('col-id') === 'fee-discount');
expect(cell).toBeInTheDocument();
await userEvent.hover(cell as HTMLElement);
expect(
await screen.findByTestId('fee-discount-breakdown-tooltip')
).toBeInTheDocument();
});
it('negative positionDecimalPoints should be properly rendered in size column', async () => {
const partyId = 'party-id';
const negativeDecimalPositionFill = generateFill({
...defaultFill,
market: {
@ -235,36 +263,83 @@ describe('FillsTable', () => {
.find((c) => c.getAttribute('col-id') === 'size');
expect(sizeCell).toHaveTextContent('3,000,000,000');
});
});
describe('getFeesBreakdown', () => {
it('should return correct fees breakdown for a taker', () => {
const fees = {
makerFee: '1000',
infrastructureFee: '2000',
liquidityFee: '3000',
};
const expectedBreakdown = {
infrastructureFee: '2000',
liquidityFee: '3000',
makerFee: '1000',
totalFee: '6000',
};
expect(getFeesBreakdown('Taker', fees)).toEqual(expectedBreakdown);
describe('FeesDiscountBreakdownTooltip', () => {
it('shows all discounts', () => {
const data = generateFill({
...defaultFill,
buyer: {
id: partyId,
},
});
it('should return correct fees breakdown for a maker', () => {
const fees = {
makerFee: '1000',
infrastructureFee: '2000',
liquidityFee: '3000',
};
const expectedBreakdown = {
infrastructureFee: '2000',
liquidityFee: '3000',
makerFee: '-1000',
totalFee: '4000',
};
expect(getFeesBreakdown('Maker', fees)).toEqual(expectedBreakdown);
const props = {
data,
partyId,
value: data.market,
} as Parameters<typeof FeesDiscountBreakdownTooltip>['0'];
const { container } = render(<FeesDiscountBreakdownTooltip {...props} />);
const dt = container.querySelectorAll('dt');
const dd = container.querySelectorAll('dd');
const expected = [
{ label: 'Infrastructure Fee Referral Discount', value: '0.05 BTC' },
{ label: 'Infrastructure Fee Volume Discount', value: '0.06 BTC' },
{ label: 'Liquidity Fee Referral Discount', value: '0.01 BTC' },
{ label: 'Liquidity Fee Volume Discount', value: '0.02 BTC' },
{ label: 'Maker Fee Referral Discount', value: '0.03 BTC' },
{ label: 'Maker Fee Volume Discount', value: '0.04 BTC' },
];
expected.forEach(({ label, value }, i) => {
expect(dt[i]).toHaveTextContent(label);
expect(dd[i]).toHaveTextContent(value);
});
});
});
describe('getFeesBreakdown', () => {
it('should return correct fees breakdown for a taker', () => {
const fees = {
makerFee: '1000',
infrastructureFee: '2000',
liquidityFee: '3000',
};
const expectedBreakdown = {
infrastructureFee: '2000',
liquidityFee: '3000',
makerFee: '1000',
totalFee: '6000',
};
expect(getFeesBreakdown('Taker', fees)).toEqual(expectedBreakdown);
});
it('should return correct fees breakdown for a maker', () => {
const fees = {
makerFee: '1000',
infrastructureFee: '2000',
liquidityFee: '3000',
};
const expectedBreakdown = {
infrastructureFee: '2000',
liquidityFee: '3000',
makerFee: '-1000',
totalFee: '4000',
};
expect(getFeesBreakdown('Maker', fees)).toEqual(expectedBreakdown);
});
});
describe('getTotalFeesDiscounts', () => {
it('should return correct total value', () => {
const fees = {
infrastructureFeeReferralDiscount: '1',
infrastructureFeeVolumeDiscount: '2',
liquidityFeeReferralDiscount: '3',
liquidityFeeVolumeDiscount: '4',
makerFeeReferralDiscount: '5',
makerFeeVolumeDiscount: '6',
};
expect(getTotalFeesDiscounts(fees as TradeFeeFieldsFragment)).toEqual(
(1 + 2 + 3 + 4 + 5 + 6).toString()
);
});
});

View File

@ -28,7 +28,10 @@ import type {
import { forwardRef } from 'react';
import BigNumber from 'bignumber.js';
import type { Trade } from './fills-data-provider';
import type { FillFieldsFragment } from './__generated__/Fills';
import type {
FillFieldsFragment,
TradeFeeFieldsFragment,
} from './__generated__/Fills';
import { FillActionsDropdown } from './fill-actions-dropdown';
import { getAsset } from '@vegaprotocol/markets';
@ -87,13 +90,34 @@ export const FillsTable = forwardRef<AgGridReact, Props>(
},
{
headerName: t('Fee'),
field: 'market.tradableInstrument.instrument.product',
colId: 'fee',
field: 'market',
valueFormatter: formatFee(partyId),
type: 'rightAligned',
tooltipField: 'market.tradableInstrument.instrument.product',
tooltipField: 'market',
tooltipComponent: FeesBreakdownTooltip,
tooltipComponentParams: { partyId },
},
{
headerName: t('Fee Discount'),
colId: 'fee-discount',
field: 'market',
valueFormatter: formatFeeDiscount(partyId),
type: 'rightAligned',
// return null to disable tooltip if fee discount is 0 or empty
tooltipValueGetter: ({ valueFormatted, value }) => {
return valueFormatted && /[1-9]/.test(valueFormatted)
? valueFormatted
: null;
},
cellRenderer: ({
value,
valueFormatted,
}: VegaICellRendererParams<Trade, 'market'>) =>
`${valueFormatted} ${(value && getAsset(value))?.symbol}`,
tooltipComponent: FeesDiscountBreakdownTooltip,
tooltipComponentParams: { partyId },
},
{
headerName: t('Date'),
field: 'createdAt',
@ -214,14 +238,11 @@ const formatRole = (partyId: string) => {
const formatFee = (partyId: string) => {
return ({
value,
value: market,
data,
}: VegaValueFormatterParams<
Trade,
'market.tradableInstrument.instrument.product'
>) => {
if (!value || !data || !data?.market) return '-';
const asset = getAsset(data.market);
}: VegaValueFormatterParams<Trade, 'market'>) => {
if (!market || !data) return '-';
const asset = getAsset(market);
const { fees: feesObj, role } = getRoleAndFees({ data, partyId });
if (!feesObj) return '-';
@ -231,6 +252,21 @@ const formatFee = (partyId: string) => {
};
};
const formatFeeDiscount = (partyId: string) => {
return ({
value: market,
data,
}: VegaValueFormatterParams<Trade, 'market'>) => {
if (!market || !data) return '-';
const asset = getAsset(market);
const { fees } = getRoleAndFees({ data, partyId });
if (!fees) return '-';
const total = getTotalFeesDiscounts(fees);
return addDecimalsFormatNumber(total, asset.decimals);
};
};
export const isEmptyFeeObj = (feeObj: Schema.TradeFee) => {
if (!feeObj) return true;
return (
@ -251,50 +287,50 @@ export const getRoleAndFees = ({
partyId?: string;
}) => {
let role: Role;
let feesObj;
let fees;
if (data?.buyer.id === partyId) {
if (data.aggressor === Schema.Side.SIDE_BUY) {
role = TAKER;
feesObj = data?.buyerFee;
fees = data?.buyerFee;
} else if (data.aggressor === Schema.Side.SIDE_SELL) {
role = MAKER;
feesObj = data?.sellerFee;
fees = data?.sellerFee;
} else {
role = '-';
feesObj = !isEmptyFeeObj(data?.buyerFee) ? data.buyerFee : data.sellerFee;
fees = !isEmptyFeeObj(data?.buyerFee) ? data.buyerFee : data.sellerFee;
}
} else if (data?.seller.id === partyId) {
if (data.aggressor === Schema.Side.SIDE_SELL) {
role = TAKER;
feesObj = data?.sellerFee;
fees = data?.sellerFee;
} else if (data.aggressor === Schema.Side.SIDE_BUY) {
role = MAKER;
feesObj = data?.buyerFee;
fees = data?.buyerFee;
} else {
role = '-';
feesObj = !isEmptyFeeObj(data.sellerFee) ? data.sellerFee : data.buyerFee;
fees = !isEmptyFeeObj(data.sellerFee) ? data.sellerFee : data.buyerFee;
}
} else {
return { role: '-', feesObj: '-' };
return { role: '-', fees: undefined };
}
return { role, fees: feesObj };
return { role, fees };
};
const FeesBreakdownTooltip = ({
data,
value,
value: market,
partyId,
}: ITooltipParams & { partyId?: string }) => {
if (!value?.settlementAsset || !data) {
}: ITooltipParams<Trade, Trade['market']> & { partyId?: string }) => {
if (!market || !data) {
return null;
}
const asset = value.settlementAsset;
const asset = getAsset(market);
const { role, fees: feesObj } = getRoleAndFees({ data, partyId }) ?? {};
if (!feesObj) return null;
const { role, fees } = getRoleAndFees({ data, partyId }) ?? {};
if (!fees) return null;
const { infrastructureFee, liquidityFee, makerFee, totalFee } =
getFeesBreakdown(role, feesObj);
getFeesBreakdown(role, fees);
return (
<div
@ -344,14 +380,91 @@ const FeesBreakdownTooltip = ({
);
};
const FeesDiscountBreakdownTooltipItem = ({
value,
label,
asset,
}: {
value?: string | null;
label: string;
asset: ReturnType<typeof getAsset>;
}) =>
value ? (
<>
<dt className="col-span-1">{label}</dt>
<dd className="text-right col-span-1">
{addDecimalsFormatNumber(value, asset.decimals)} {asset.symbol}
</dd>
</>
) : null;
export const FeesDiscountBreakdownTooltip = ({
data,
partyId,
}: ITooltipParams<Trade, Trade['market']> & { partyId?: string }) => {
if (!data || !data.market) {
return null;
}
const asset = getAsset(data.market);
const { fees } = getRoleAndFees({ data, partyId }) ?? {};
if (!fees) return null;
return (
<div
data-testid="fee-discount-breakdown-tooltip"
className="max-w-sm bg-vega-light-100 dark:bg-vega-dark-100 border border-vega-light-200 dark:border-vega-dark-200 px-4 py-2 z-20 rounded text-sm break-word text-black dark:text-white"
>
<dl className="grid grid-cols-2 gap-x-1">
<FeesDiscountBreakdownTooltipItem
value={fees.infrastructureFeeReferralDiscount}
label={t('Infrastructure Fee Referral Discount')}
asset={asset}
/>
<FeesDiscountBreakdownTooltipItem
value={fees.infrastructureFeeVolumeDiscount}
label={t('Infrastructure Fee Volume Discount')}
asset={asset}
/>
<FeesDiscountBreakdownTooltipItem
value={fees.liquidityFeeReferralDiscount}
label={t('Liquidity Fee Referral Discount')}
asset={asset}
/>
<FeesDiscountBreakdownTooltipItem
value={fees.liquidityFeeVolumeDiscount}
label={t('Liquidity Fee Volume Discount')}
asset={asset}
/>
<FeesDiscountBreakdownTooltipItem
value={fees.makerFeeReferralDiscount}
label={t('Maker Fee Referral Discount')}
asset={asset}
/>
<FeesDiscountBreakdownTooltipItem
value={fees.makerFeeVolumeDiscount}
label={t('Maker Fee Volume Discount')}
asset={asset}
/>
</dl>
</div>
);
};
export const getTotalFeesDiscounts = (fees: TradeFeeFieldsFragment) => {
return (
BigInt(fees.infrastructureFeeReferralDiscount || '0') +
BigInt(fees.infrastructureFeeVolumeDiscount || '0') +
BigInt(fees.liquidityFeeReferralDiscount || '0') +
BigInt(fees.liquidityFeeVolumeDiscount || '0') +
BigInt(fees.makerFeeReferralDiscount || '0') +
BigInt(fees.makerFeeVolumeDiscount || '0')
).toString();
};
export const getFeesBreakdown = (
role: Role,
feesObj: {
__typename?: 'TradeFee' | undefined;
makerFee: string;
infrastructureFee: string;
liquidityFee: string;
}
feesObj: TradeFeeFieldsFragment
) => {
const makerFee =
role === MAKER

View File

@ -28,12 +28,24 @@ export const generateFill = (override?: PartialDeep<Trade>) => {
makerFee: '100',
infrastructureFee: '100',
liquidityFee: '100',
liquidityFeeReferralDiscount: '1',
liquidityFeeVolumeDiscount: '2',
makerFeeReferralDiscount: '3',
makerFeeVolumeDiscount: '4',
infrastructureFeeReferralDiscount: '5',
infrastructureFeeVolumeDiscount: '6',
},
sellerFee: {
__typename: 'TradeFee',
makerFee: '200',
infrastructureFee: '200',
liquidityFee: '200',
liquidityFeeReferralDiscount: '2',
liquidityFeeVolumeDiscount: '3',
makerFeeReferralDiscount: '4',
makerFeeVolumeDiscount: '5',
infrastructureFeeReferralDiscount: '6',
infrastructureFeeVolumeDiscount: '7',
},
market: {
__typename: 'Market',

View File

@ -1,115 +0,0 @@
import { totalFeesPercentage } from '../../market-utils';
import type { TradeFee, FeeFactors } from '@vegaprotocol/types';
import {
addDecimalsFormatNumber,
formatNumberPercentage,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { Tooltip } from '@vegaprotocol/ui-toolkit';
import BigNumber from 'bignumber.js';
export const FeesCell = ({ feeFactors }: { feeFactors: FeeFactors }) => (
<Tooltip description={<FeesBreakdownPercentage feeFactors={feeFactors} />}>
<span>{totalFeesPercentage(feeFactors) ?? '-'}</span>
</Tooltip>
);
export const FeesBreakdownPercentage = ({
feeFactors,
}: {
feeFactors?: FeeFactors;
}) => {
if (!feeFactors) return null;
return (
<dl className="grid grid-cols-2 gap-x-2">
<dt>{t('Infrastructure fee')}</dt>
<dd className="text-right">
{formatNumberPercentage(
new BigNumber(feeFactors.infrastructureFee).times(100)
)}
</dd>
<dt>{t('Liquidity fee')}</dt>
<dd className="text-right">
{formatNumberPercentage(
new BigNumber(feeFactors.liquidityFee).times(100)
)}
</dd>
<dt>{t('Maker fee')}</dt>
<dd className="text-right">
{formatNumberPercentage(new BigNumber(feeFactors.makerFee).times(100))}
</dd>
<dt>{t('Total fees')}</dt>
<dd className="text-right">{totalFeesPercentage(feeFactors)}</dd>
</dl>
);
};
export const FeesBreakdown = ({
fees,
feeFactors,
symbol,
decimals,
}: {
fees?: TradeFee;
feeFactors?: FeeFactors;
symbol?: string;
decimals: number;
}) => {
if (!fees) return null;
const totalFees = new BigNumber(fees.makerFee)
.plus(fees.infrastructureFee)
.plus(fees.liquidityFee)
.toString();
if (totalFees === '0') return null;
const formatValue = (value: string | number | null | undefined): string => {
return value && !isNaN(Number(value))
? addDecimalsFormatNumber(value, decimals)
: '-';
};
return (
<dl className="grid grid-cols-6">
<dt className="col-span-2">{t('Infrastructure fee')}</dt>
{feeFactors && (
<dd className="text-right col-span-1">
{formatNumberPercentage(
new BigNumber(feeFactors.infrastructureFee).times(100)
)}
</dd>
)}
<dd className="text-right col-span-3">
{formatValue(fees.infrastructureFee)} {symbol || ''}
</dd>
<dt className="col-span-2">{t('Liquidity fee')}</dt>
{feeFactors && (
<dd className="text-right col-span-1">
{formatNumberPercentage(
new BigNumber(feeFactors.liquidityFee).times(100)
)}
</dd>
)}
<dd className="text-right col-span-3">
{formatValue(fees.liquidityFee)} {symbol || ''}
</dd>
<dt className="col-span-2">{t('Maker fee')}</dt>
{feeFactors && (
<dd className="text-right col-span-1">
{formatNumberPercentage(
new BigNumber(feeFactors.makerFee).times(100)
)}
</dd>
)}
<dd className="text-right col-span-3">
{formatValue(fees.makerFee)} {symbol || ''}
</dd>
<dt className="col-span-2">{t('Total fees')}</dt>
{feeFactors && (
<dd className="text-right col-span-1">
{totalFeesPercentage(feeFactors)}
</dd>
)}
<dd className="text-right col-span-3">
{formatValue(totalFees)} {symbol || ''}
</dd>
</dl>
);
};

View File

@ -1,4 +1,3 @@
export * from './fees-breakdown';
export * from './last-24h-price-change';
export * from './last-24h-volume';
export * from './market-info';

View File

@ -4,7 +4,7 @@ import { Fragment, useMemo, useState } from 'react';
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n';
import { marketDataProvider } from '../../market-data-provider';
import { totalFeesPercentage } from '../../market-utils';
import { totalFeesFactorsPercentage } from '../../market-utils';
import {
Accordion,
AccordionChevron,
@ -91,7 +91,7 @@ export const CurrentFeesInfoPanel = ({ market }: MarketInfoProps) => (
makerFee: market.fees.factors.makerFee,
infrastructureFee: market.fees.factors.infrastructureFee,
liquidityFee: market.fees.factors.liquidityFee,
totalFees: totalFeesPercentage(market.fees.factors),
totalFees: totalFeesFactorsPercentage(market.fees.factors),
}}
asPercentage={true}
/>

View File

@ -3,7 +3,7 @@ import type { Market, MarketMaybeWithDataAndCandles } from './markets-provider';
import {
calcTradedFactor,
filterAndSortMarkets,
totalFeesPercentage,
totalFeesFactorsPercentage,
} from './market-utils';
const { MarketState, MarketTradingMode } = Schema;
@ -63,7 +63,7 @@ describe('mapDataToMarketList', () => {
});
});
describe('totalFees', () => {
describe('totalFeesFactorsPercentage', () => {
const createFee = (...f: number[]): Market['fees']['factors'] => ({
__typename: 'FeeFactors',
infrastructureFee: f[0].toString(),
@ -78,7 +78,7 @@ describe('totalFees', () => {
{ i: createFee(0.01, 0.056782, 0.003), o: '6.9782%' },
{ i: createFee(0.01, 0.056782, 0), o: '6.6782%' },
])('adds fees correctly', ({ i, o }) => {
expect(totalFeesPercentage(i)).toEqual(o);
expect(totalFeesFactorsPercentage(i)).toEqual(o);
});
});

View File

@ -49,18 +49,17 @@ export const getQuoteName = (market: Partial<Market>) => {
throw new Error('Failed to retrieve quoteName. Invalid product type');
};
export const totalFees = (fees: Market['fees']['factors']) => {
export const sumFeesFactors = (fees: Market['fees']['factors']) => {
return fees
? new BigNumber(fees.makerFee)
.plus(fees.liquidityFee)
.plus(fees.infrastructureFee)
.times(100)
: undefined;
};
export const totalFeesPercentage = (fees: Market['fees']['factors']) => {
const total = fees && totalFees(fees);
return total ? formatNumberPercentage(total) : undefined;
export const totalFeesFactorsPercentage = (fees: Market['fees']['factors']) => {
const total = fees && sumFeesFactors(fees);
return total ? formatNumberPercentage(total.times(100)) : undefined;
};
export const filterAndSortMarkets = (markets: MarketMaybeWithData[]) => {

View File

@ -123,11 +123,9 @@ export const OrderViewDialog = ({
{t('rejection reason')}
</div>
<div data-testid={`order-rejection-reason-value`}>
{
Schema.OrderRejectionReasonMapping[
order.rejectionReason as Schema.OrderRejectionReason
]
}
{Schema.OrderRejectionReasonMapping[
order.rejectionReason as Schema.OrderRejectionReason
] || order.rejectionReason}
</div>
</KeyValueTableRow>
)}