Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44719c9ea5 | ||
|
|
773b1cdc13 | ||
|
|
3b4fa06927 | ||
|
|
85e9c1300c | ||
|
|
4e9c47c27e | ||
|
|
9c3c0a30f6 |
@@ -35,6 +35,7 @@ const defaultFill: PartialDeep<Trade> = {
|
|||||||
},
|
},
|
||||||
createdAt: new Date('2022-02-02T14:00:00').toISOString(),
|
createdAt: new Date('2022-02-02T14:00:00').toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('FillsTable', () => {
|
describe('FillsTable', () => {
|
||||||
it('correct columns are rendered', async () => {
|
it('correct columns are rendered', async () => {
|
||||||
// 7005-FILL-001
|
// 7005-FILL-001
|
||||||
@@ -271,96 +272,147 @@ describe('FillsTable', () => {
|
|||||||
.find((c) => c.getAttribute('col-id') === 'size');
|
.find((c) => c.getAttribute('col-id') === 'size');
|
||||||
expect(sizeCell).toHaveTextContent('3,000,000,000');
|
expect(sizeCell).toHaveTextContent('3,000,000,000');
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('FeesDiscountBreakdownTooltip', () => {
|
describe('FeesDiscountBreakdownTooltip', () => {
|
||||||
it('shows all discounts', () => {
|
it('shows all discounts', () => {
|
||||||
const data = generateFill({
|
const data = generateFill({
|
||||||
...defaultFill,
|
...defaultFill,
|
||||||
buyer: {
|
buyer: {
|
||||||
id: partyId,
|
id: partyId,
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
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 expectedDt = [
|
||||||
|
'Infrastructure Fee',
|
||||||
|
'Referral Discount',
|
||||||
|
'Volume Discount',
|
||||||
|
'Liquidity Fee',
|
||||||
|
'Referral Discount',
|
||||||
|
'Volume Discount',
|
||||||
|
'Maker Fee',
|
||||||
|
'Referral Discount',
|
||||||
|
'Volume Discount',
|
||||||
|
];
|
||||||
|
const expectedDD = [
|
||||||
|
'0.05 BTC',
|
||||||
|
'0.06 BTC',
|
||||||
|
'0.01 BTC',
|
||||||
|
'0.02 BTC',
|
||||||
|
'0.03 BTC',
|
||||||
|
'0.04 BTC',
|
||||||
|
];
|
||||||
|
expectedDt.forEach((label, i) => {
|
||||||
|
expect(dt[i]).toHaveTextContent(label);
|
||||||
|
});
|
||||||
|
expectedDD.forEach((label, i) => {
|
||||||
|
expect(dd[i]).toHaveTextContent(label);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
const props = {
|
});
|
||||||
data,
|
|
||||||
partyId,
|
describe('getFeesBreakdown', () => {
|
||||||
value: data.market,
|
it('should return correct fees breakdown for a taker', () => {
|
||||||
} as Parameters<typeof FeesDiscountBreakdownTooltip>['0'];
|
const fees = {
|
||||||
const { container } = render(<FeesDiscountBreakdownTooltip {...props} />);
|
makerFee: '1000',
|
||||||
const dt = container.querySelectorAll('dt');
|
infrastructureFee: '2000',
|
||||||
const dd = container.querySelectorAll('dd');
|
liquidityFee: '3000',
|
||||||
const expectedDt = [
|
};
|
||||||
'Infrastructure Fee',
|
const expectedBreakdown = {
|
||||||
'Referral Discount',
|
infrastructureFee: '2000',
|
||||||
'Volume Discount',
|
liquidityFee: '3000',
|
||||||
'Liquidity Fee',
|
makerFee: '1000',
|
||||||
'Referral Discount',
|
totalFee: '6000',
|
||||||
'Volume Discount',
|
};
|
||||||
'Maker Fee',
|
expect(getFeesBreakdown('Taker', fees)).toEqual(expectedBreakdown);
|
||||||
'Referral Discount',
|
|
||||||
'Volume Discount',
|
|
||||||
];
|
|
||||||
const expectedDD = [
|
|
||||||
'0.05 BTC',
|
|
||||||
'0.06 BTC',
|
|
||||||
'0.01 BTC',
|
|
||||||
'0.02 BTC',
|
|
||||||
'0.03 BTC',
|
|
||||||
'0.04 BTC',
|
|
||||||
];
|
|
||||||
expectedDt.forEach((label, i) => {
|
|
||||||
expect(dt[i]).toHaveTextContent(label);
|
|
||||||
});
|
});
|
||||||
expectedDD.forEach((label, i) => {
|
|
||||||
expect(dd[i]).toHaveTextContent(label);
|
it('should return correct fees breakdown for a maker if market', () => {
|
||||||
|
const fees = {
|
||||||
|
makerFee: '1000',
|
||||||
|
infrastructureFee: '2000',
|
||||||
|
liquidityFee: '3000',
|
||||||
|
};
|
||||||
|
const expectedBreakdown = {
|
||||||
|
infrastructureFee: '0',
|
||||||
|
liquidityFee: '0',
|
||||||
|
makerFee: '-1000',
|
||||||
|
totalFee: '-1000',
|
||||||
|
};
|
||||||
|
expect(getFeesBreakdown('Maker', fees)).toEqual(expectedBreakdown);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct fees breakdown for a maker if market is active', () => {
|
||||||
|
const fees = {
|
||||||
|
makerFee: '1000',
|
||||||
|
infrastructureFee: '2000',
|
||||||
|
liquidityFee: '3000',
|
||||||
|
};
|
||||||
|
const expectedBreakdown = {
|
||||||
|
infrastructureFee: '0',
|
||||||
|
liquidityFee: '0',
|
||||||
|
makerFee: '-1000',
|
||||||
|
totalFee: '-1000',
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
getFeesBreakdown('Maker', fees, Schema.MarketState.STATE_ACTIVE)
|
||||||
|
).toEqual(expectedBreakdown);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct fees breakdown for a maker if the market is suspended', () => {
|
||||||
|
const fees = {
|
||||||
|
infrastructureFee: '2000',
|
||||||
|
liquidityFee: '3000',
|
||||||
|
makerFee: '0',
|
||||||
|
};
|
||||||
|
const expectedBreakdown = {
|
||||||
|
infrastructureFee: '1000',
|
||||||
|
liquidityFee: '1500',
|
||||||
|
makerFee: '0',
|
||||||
|
totalFee: '2500',
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
getFeesBreakdown('Maker', fees, Schema.MarketState.STATE_SUSPENDED)
|
||||||
|
).toEqual(expectedBreakdown);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct fees breakdown for a taker if the market is suspended', () => {
|
||||||
|
const fees = {
|
||||||
|
infrastructureFee: '2000',
|
||||||
|
liquidityFee: '3000',
|
||||||
|
makerFee: '0',
|
||||||
|
};
|
||||||
|
const expectedBreakdown = {
|
||||||
|
infrastructureFee: '1000',
|
||||||
|
liquidityFee: '1500',
|
||||||
|
makerFee: '0',
|
||||||
|
totalFee: '2500',
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
getFeesBreakdown('Taker', fees, Schema.MarketState.STATE_SUSPENDED)
|
||||||
|
).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()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
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()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import type {
|
import {
|
||||||
AgGridReact,
|
type AgGridReact,
|
||||||
AgGridReactProps,
|
type AgGridReactProps,
|
||||||
AgReactUiProps,
|
type AgReactUiProps,
|
||||||
} from 'ag-grid-react';
|
} from 'ag-grid-react';
|
||||||
import type { ITooltipParams, ColDef } from 'ag-grid-community';
|
import type { ColDef } from 'ag-grid-community';
|
||||||
|
import type { ITooltipParams } from 'ag-grid-community';
|
||||||
import {
|
import {
|
||||||
addDecimal,
|
addDecimal,
|
||||||
addDecimalsFormatNumber,
|
addDecimalsFormatNumber,
|
||||||
@@ -290,6 +291,7 @@ export const getRoleAndFees = ({
|
|||||||
}) => {
|
}) => {
|
||||||
let role: Role;
|
let role: Role;
|
||||||
let fees;
|
let fees;
|
||||||
|
|
||||||
if (data?.buyer.id === partyId) {
|
if (data?.buyer.id === partyId) {
|
||||||
if (data.aggressor === Schema.Side.SIDE_BUY) {
|
if (data.aggressor === Schema.Side.SIDE_BUY) {
|
||||||
role = TAKER;
|
role = TAKER;
|
||||||
@@ -315,7 +317,16 @@ export const getRoleAndFees = ({
|
|||||||
} else {
|
} else {
|
||||||
return { role: '-', fees: undefined };
|
return { role: '-', fees: undefined };
|
||||||
}
|
}
|
||||||
return { role, fees };
|
|
||||||
|
// We make the assumption that the market state is active if the maker fee is zero on both sides
|
||||||
|
// This needs to be updated when we have a way to get the correct market state when that fill happened from the API
|
||||||
|
// because the maker fee factor can be set to 0 via governance
|
||||||
|
const marketState =
|
||||||
|
data?.buyerFee.makerFee === data.sellerFee.makerFee &&
|
||||||
|
new BigNumber(data?.buyerFee.makerFee).isZero()
|
||||||
|
? Schema.MarketState.STATE_SUSPENDED
|
||||||
|
: Schema.MarketState.STATE_ACTIVE;
|
||||||
|
return { role, fees, marketState };
|
||||||
};
|
};
|
||||||
|
|
||||||
const FeesBreakdownTooltip = ({
|
const FeesBreakdownTooltip = ({
|
||||||
@@ -329,16 +340,21 @@ const FeesBreakdownTooltip = ({
|
|||||||
|
|
||||||
const asset = getAsset(market);
|
const asset = getAsset(market);
|
||||||
|
|
||||||
const { role, fees } = getRoleAndFees({ data, partyId }) ?? {};
|
const { role, fees, marketState } = getRoleAndFees({ data, partyId }) ?? {};
|
||||||
if (!fees) return null;
|
if (!fees) return null;
|
||||||
const { infrastructureFee, liquidityFee, makerFee, totalFee } =
|
const { infrastructureFee, liquidityFee, makerFee, totalFee } =
|
||||||
getFeesBreakdown(role, fees);
|
getFeesBreakdown(role, fees, marketState);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-testid="fee-breakdown-tooltip"
|
data-testid="fee-breakdown-tooltip"
|
||||||
className="z-20 max-w-sm px-4 py-2 text-sm text-black border rounded bg-vega-light-100 dark:bg-vega-dark-100 border-vega-light-200 dark:border-vega-dark-200 break-word dark:text-white"
|
className="z-20 max-w-sm px-4 py-2 text-xs text-black border rounded bg-vega-light-100 dark:bg-vega-dark-100 border-vega-light-200 dark:border-vega-dark-200 break-word dark:text-white"
|
||||||
>
|
>
|
||||||
|
<p className="mb-1 italic">
|
||||||
|
{t('If the market was %s', [
|
||||||
|
Schema.MarketStateMapping[marketState].toLowerCase(),
|
||||||
|
])}
|
||||||
|
</p>
|
||||||
{role === MAKER && (
|
{role === MAKER && (
|
||||||
<>
|
<>
|
||||||
<p className="mb-1">{t('The maker will receive the maker fee.')}</p>
|
<p className="mb-1">{t('The maker will receive the maker fee.')}</p>
|
||||||
@@ -352,7 +368,7 @@ const FeesBreakdownTooltip = ({
|
|||||||
{role === TAKER && (
|
{role === TAKER && (
|
||||||
<p className="mb-1">{t('Fees to be paid by the taker.')}</p>
|
<p className="mb-1">{t('Fees to be paid by the taker.')}</p>
|
||||||
)}
|
)}
|
||||||
{role === '-' && (
|
{(role === '-' || marketState === Schema.MarketState.STATE_SUSPENDED) && (
|
||||||
<p className="mb-1">
|
<p className="mb-1">
|
||||||
{t(
|
{t(
|
||||||
'If the market is in monitoring auction, half of the infrastructure and liquidity fees will be paid.'
|
'If the market is in monitoring auction, half of the infrastructure and liquidity fees will be paid.'
|
||||||
@@ -393,8 +409,8 @@ const FeesDiscountBreakdownTooltipItem = ({
|
|||||||
}) =>
|
}) =>
|
||||||
value && value !== '0' ? (
|
value && value !== '0' ? (
|
||||||
<>
|
<>
|
||||||
<dt className="col-span-1">{label}</dt>
|
<dt className="col-span-2">{label}</dt>
|
||||||
<dd className="text-right col-span-1">
|
<dd className="text-right col-span-2">
|
||||||
{addDecimalsFormatNumber(value, asset.decimals)} {asset.symbol}
|
{addDecimalsFormatNumber(value, asset.decimals)} {asset.symbol}
|
||||||
</dd>
|
</dd>
|
||||||
</>
|
</>
|
||||||
@@ -417,7 +433,7 @@ export const FeesDiscountBreakdownTooltip = ({
|
|||||||
data-testid="fee-discount-breakdown-tooltip"
|
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"
|
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">
|
<dl className="grid grid-cols-6 gap-x-1 text-xs">
|
||||||
{(fees.infrastructureFeeReferralDiscount || '0') !== '0' ||
|
{(fees.infrastructureFeeReferralDiscount || '0') !== '0' ||
|
||||||
(fees.infrastructureFeeVolumeDiscount || '0') !== '0' ? (
|
(fees.infrastructureFeeVolumeDiscount || '0') !== '0' ? (
|
||||||
<dt className="col-span-2">{t('Infrastructure Fee')}</dt>
|
<dt className="col-span-2">{t('Infrastructure Fee')}</dt>
|
||||||
@@ -479,20 +495,36 @@ export const getTotalFeesDiscounts = (fees: TradeFeeFieldsFragment) => {
|
|||||||
|
|
||||||
export const getFeesBreakdown = (
|
export const getFeesBreakdown = (
|
||||||
role: Role,
|
role: Role,
|
||||||
feesObj: TradeFeeFieldsFragment
|
feesObj: TradeFeeFieldsFragment,
|
||||||
|
marketState: Schema.MarketState = Schema.MarketState.STATE_ACTIVE
|
||||||
) => {
|
) => {
|
||||||
const makerFee =
|
// If market is in auction we assume maker fee is zero
|
||||||
role === MAKER
|
const isMarketActive = marketState === Schema.MarketState.STATE_ACTIVE;
|
||||||
? new BigNumber(feesObj.makerFee).times(-1).toString()
|
|
||||||
: feesObj.makerFee;
|
|
||||||
|
|
||||||
const infrastructureFee = feesObj.infrastructureFee;
|
// If role is taker, then these are the fees to be paid
|
||||||
const liquidityFee = feesObj.liquidityFee;
|
let { makerFee, infrastructureFee, liquidityFee } = feesObj;
|
||||||
|
|
||||||
|
if (isMarketActive) {
|
||||||
|
if (role === MAKER) {
|
||||||
|
makerFee = new BigNumber(feesObj.makerFee).times(-1).toString();
|
||||||
|
infrastructureFee = '0';
|
||||||
|
liquidityFee = '0';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If market is suspended (in monitoring auction), then half of the fees are paid
|
||||||
|
infrastructureFee = new BigNumber(infrastructureFee)
|
||||||
|
.dividedBy(2)
|
||||||
|
.toString();
|
||||||
|
liquidityFee = new BigNumber(liquidityFee).dividedBy(2).toString();
|
||||||
|
// maker fee is already zero
|
||||||
|
makerFee = '0';
|
||||||
|
}
|
||||||
|
|
||||||
const totalFee = new BigNumber(infrastructureFee)
|
const totalFee = new BigNumber(infrastructureFee)
|
||||||
.plus(makerFee)
|
.plus(makerFee)
|
||||||
.plus(liquidityFee)
|
.plus(liquidityFee)
|
||||||
.toString();
|
.toString();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
infrastructureFee,
|
infrastructureFee,
|
||||||
liquidityFee,
|
liquidityFee,
|
||||||
|
|||||||
Reference in New Issue
Block a user