Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
361622dc5b | ||
|
|
21888cac64 | ||
|
|
a59f7dfd29 | ||
|
|
61471228aa | ||
|
|
cd8374ad56 | ||
|
|
996a8b8722 | ||
|
|
c1fb1c692a | ||
|
|
9113c0fabe | ||
|
|
5923eac56b | ||
|
|
e1c9323e3c | ||
|
|
aa7c33b1a9 | ||
|
|
b83c10a532 | ||
|
|
70d748fb15 | ||
|
|
6551c2b02b | ||
|
|
dcf36acb0b | ||
|
|
44719c9ea5 | ||
|
|
773b1cdc13 | ||
|
|
8d9f2cdda3 | ||
|
|
3b4fa06927 | ||
|
|
6de1e0b83d | ||
|
|
85e9c1300c | ||
|
|
4e9c47c27e | ||
|
|
9c3c0a30f6 |
@@ -12,7 +12,7 @@ import { type VegaICellRendererParams } from '@vegaprotocol/datagrid';
|
|||||||
import { useRef, useLayoutEffect } from 'react';
|
import { useRef, useLayoutEffect } from 'react';
|
||||||
import { BREAKPOINT_MD } from '../../config/breakpoints';
|
import { BREAKPOINT_MD } from '../../config/breakpoints';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { ColDef } from 'ag-grid-community';
|
import { type ColDef } from 'ag-grid-community';
|
||||||
import type { RowClickedEvent } from 'ag-grid-community';
|
import type { RowClickedEvent } from 'ag-grid-community';
|
||||||
|
|
||||||
type AssetsTableProps = {
|
type AssetsTableProps = {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
type VegaValueFormatterParams,
|
type VegaValueFormatterParams,
|
||||||
} from '@vegaprotocol/datagrid';
|
} from '@vegaprotocol/datagrid';
|
||||||
import { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
import { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { ColDef } from 'ag-grid-community';
|
import { type ColDef } from 'ag-grid-community';
|
||||||
import type { RowClickedEvent } from 'ag-grid-community';
|
import type { RowClickedEvent } from 'ag-grid-community';
|
||||||
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export const useStats = ({
|
|||||||
t.discountFactor === discountFactorValue
|
t.discountFactor === discountFactorValue
|
||||||
);
|
);
|
||||||
const nextBenefitTierValue = currentBenefitTierValue
|
const nextBenefitTierValue = currentBenefitTierValue
|
||||||
? benefitTiers.find((t) => t.tier === currentBenefitTierValue.tier - 1)
|
? benefitTiers.find((t) => t.tier === currentBenefitTierValue.tier + 1)
|
||||||
: minBy(benefitTiers, (bt) => bt.tier); // min tier number is lowest tier
|
: minBy(benefitTiers, (bt) => bt.tier); // min tier number is lowest tier
|
||||||
const epochsValue =
|
const epochsValue =
|
||||||
!isNaN(currentEpoch) && refereeInfo?.atEpoch
|
!isNaN(currentEpoch) && refereeInfo?.atEpoch
|
||||||
|
|||||||
@@ -16,18 +16,11 @@ query DiscountPrograms {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query Fees(
|
query Fees($partyId: ID!) {
|
||||||
$partyId: ID!
|
|
||||||
$volumeDiscountEpochs: Int!
|
|
||||||
$referralDiscountEpochs: Int!
|
|
||||||
) {
|
|
||||||
epoch {
|
epoch {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
volumeDiscountStats(
|
volumeDiscountStats(partyId: $partyId, pagination: { last: 1 }) {
|
||||||
partyId: $partyId
|
|
||||||
pagination: { last: $volumeDiscountEpochs }
|
|
||||||
) {
|
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
atEpoch
|
atEpoch
|
||||||
@@ -59,10 +52,7 @@ query Fees(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
referralSetStats(
|
referralSetStats(partyId: $partyId, pagination: { last: 1 }) {
|
||||||
partyId: $partyId
|
|
||||||
pagination: { last: $referralDiscountEpochs }
|
|
||||||
) {
|
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
atEpoch
|
atEpoch
|
||||||
|
|||||||
+3
-10
@@ -10,8 +10,6 @@ export type DiscountProgramsQuery = { __typename?: 'Query', currentReferralProgr
|
|||||||
|
|
||||||
export type FeesQueryVariables = Types.Exact<{
|
export type FeesQueryVariables = Types.Exact<{
|
||||||
partyId: Types.Scalars['ID'];
|
partyId: Types.Scalars['ID'];
|
||||||
volumeDiscountEpochs: Types.Scalars['Int'];
|
|
||||||
referralDiscountEpochs: Types.Scalars['Int'];
|
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
@@ -65,14 +63,11 @@ export type DiscountProgramsQueryHookResult = ReturnType<typeof useDiscountProgr
|
|||||||
export type DiscountProgramsLazyQueryHookResult = ReturnType<typeof useDiscountProgramsLazyQuery>;
|
export type DiscountProgramsLazyQueryHookResult = ReturnType<typeof useDiscountProgramsLazyQuery>;
|
||||||
export type DiscountProgramsQueryResult = Apollo.QueryResult<DiscountProgramsQuery, DiscountProgramsQueryVariables>;
|
export type DiscountProgramsQueryResult = Apollo.QueryResult<DiscountProgramsQuery, DiscountProgramsQueryVariables>;
|
||||||
export const FeesDocument = gql`
|
export const FeesDocument = gql`
|
||||||
query Fees($partyId: ID!, $volumeDiscountEpochs: Int!, $referralDiscountEpochs: Int!) {
|
query Fees($partyId: ID!) {
|
||||||
epoch {
|
epoch {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
volumeDiscountStats(
|
volumeDiscountStats(partyId: $partyId, pagination: {last: 1}) {
|
||||||
partyId: $partyId
|
|
||||||
pagination: {last: $volumeDiscountEpochs}
|
|
||||||
) {
|
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
atEpoch
|
atEpoch
|
||||||
@@ -104,7 +99,7 @@ export const FeesDocument = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
referralSetStats(partyId: $partyId, pagination: {last: $referralDiscountEpochs}) {
|
referralSetStats(partyId: $partyId, pagination: {last: 1}) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
atEpoch
|
atEpoch
|
||||||
@@ -129,8 +124,6 @@ export const FeesDocument = gql`
|
|||||||
* const { data, loading, error } = useFeesQuery({
|
* const { data, loading, error } = useFeesQuery({
|
||||||
* variables: {
|
* variables: {
|
||||||
* partyId: // value for 'partyId'
|
* partyId: // value for 'partyId'
|
||||||
* volumeDiscountEpochs: // value for 'volumeDiscountEpochs'
|
|
||||||
* referralDiscountEpochs: // value for 'referralDiscountEpochs'
|
|
||||||
* },
|
* },
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -42,19 +42,19 @@ export const FeesContainer = () => {
|
|||||||
programData?.currentVolumeDiscountProgram?.windowLength || 1;
|
programData?.currentVolumeDiscountProgram?.windowLength || 1;
|
||||||
const referralDiscountWindowLength =
|
const referralDiscountWindowLength =
|
||||||
programData?.currentReferralProgram?.windowLength || 1;
|
programData?.currentReferralProgram?.windowLength || 1;
|
||||||
|
|
||||||
const { data: feesData, loading: feesLoading } = useFeesQuery({
|
const { data: feesData, loading: feesLoading } = useFeesQuery({
|
||||||
variables: {
|
variables: {
|
||||||
partyId: pubKey || '',
|
partyId: pubKey || '',
|
||||||
volumeDiscountEpochs: volumeDiscountWindowLength,
|
|
||||||
referralDiscountEpochs: referralDiscountWindowLength,
|
|
||||||
},
|
},
|
||||||
skip: !pubKey || !programData,
|
skip: !pubKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const previousEpoch = (Number(feesData?.epoch.id) || 0) - 1;
|
||||||
|
|
||||||
const { volumeDiscount, volumeTierIndex, volumeInWindow, volumeTiers } =
|
const { volumeDiscount, volumeTierIndex, volumeInWindow, volumeTiers } =
|
||||||
useVolumeStats(
|
useVolumeStats(
|
||||||
feesData?.volumeDiscountStats,
|
previousEpoch,
|
||||||
|
feesData?.volumeDiscountStats.edges?.[0]?.node,
|
||||||
programData?.currentVolumeDiscountProgram
|
programData?.currentVolumeDiscountProgram
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -67,12 +67,12 @@ export const FeesContainer = () => {
|
|||||||
code,
|
code,
|
||||||
isReferrer,
|
isReferrer,
|
||||||
} = useReferralStats(
|
} = useReferralStats(
|
||||||
feesData?.referralSetStats,
|
previousEpoch,
|
||||||
feesData?.referralSetReferees,
|
feesData?.referralSetStats.edges?.[0]?.node,
|
||||||
|
feesData?.referralSetReferees.edges?.[0]?.node,
|
||||||
programData?.currentReferralProgram,
|
programData?.currentReferralProgram,
|
||||||
feesData?.epoch,
|
feesData?.referrer.edges?.[0]?.node,
|
||||||
feesData?.referrer,
|
feesData?.referee.edges?.[0]?.node
|
||||||
feesData?.referee
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const loading = paramsLoading || feesLoading || programLoading;
|
const loading = paramsLoading || feesLoading || programLoading;
|
||||||
@@ -466,7 +466,7 @@ const VolumeTiers = ({
|
|||||||
</THead>
|
</THead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{Array.from(tiers).map((tier, i) => {
|
{Array.from(tiers).map((tier, i) => {
|
||||||
const isUserTier = tiers.length - 1 - tierIndex === i;
|
const isUserTier = tierIndex === i;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tr key={i}>
|
<Tr key={i}>
|
||||||
@@ -521,7 +521,7 @@ const ReferralTiers = ({
|
|||||||
</THead>
|
</THead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{Array.from(tiers).map((t, i) => {
|
{Array.from(tiers).map((t, i) => {
|
||||||
const isUserTier = tiers.length - 1 - tierIndex === i;
|
const isUserTier = tierIndex === i;
|
||||||
|
|
||||||
const requiredVolume = Number(t.minimumRunningNotionalTakerVolume);
|
const requiredVolume = Number(t.minimumRunningNotionalTakerVolume);
|
||||||
let unlocksIn = null;
|
let unlocksIn = null;
|
||||||
|
|||||||
@@ -2,46 +2,15 @@ import { renderHook } from '@testing-library/react';
|
|||||||
import { useReferralStats } from './use-referral-stats';
|
import { useReferralStats } from './use-referral-stats';
|
||||||
|
|
||||||
describe('useReferralStats', () => {
|
describe('useReferralStats', () => {
|
||||||
const setStats = {
|
const stat = {
|
||||||
edges: [
|
__typename: 'ReferralSetStats' as const,
|
||||||
{
|
atEpoch: 9,
|
||||||
__typename: 'ReferralSetStatsEdge' as const,
|
discountFactor: '0.01',
|
||||||
node: {
|
referralSetRunningNotionalTakerVolume: '100',
|
||||||
__typename: 'ReferralSetStats' as const,
|
|
||||||
atEpoch: 9,
|
|
||||||
discountFactor: '0.2',
|
|
||||||
referralSetRunningNotionalTakerVolume: '100',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
__typename: 'ReferralSetStatsEdge' as const,
|
|
||||||
node: {
|
|
||||||
__typename: 'ReferralSetStats' as const,
|
|
||||||
atEpoch: 10,
|
|
||||||
discountFactor: '0.3',
|
|
||||||
referralSetRunningNotionalTakerVolume: '200',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const sets = {
|
const set = {
|
||||||
edges: [
|
atEpoch: 4,
|
||||||
{
|
|
||||||
node: {
|
|
||||||
atEpoch: 3,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
node: {
|
|
||||||
atEpoch: 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const epoch = {
|
|
||||||
id: '10',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const program = {
|
const program = {
|
||||||
@@ -78,102 +47,36 @@ describe('useReferralStats', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns formatted data and tiers', () => {
|
it('returns default values if set is not from previous epoch', () => {
|
||||||
const { result } = renderHook(() =>
|
const { result } = renderHook(() =>
|
||||||
useReferralStats(setStats, sets, program, epoch)
|
useReferralStats(10, stat, set, program)
|
||||||
);
|
);
|
||||||
|
|
||||||
// should use stats from latest epoch
|
|
||||||
const stats = setStats.edges[1].node;
|
|
||||||
const set = sets.edges[1].node;
|
|
||||||
|
|
||||||
expect(result.current).toEqual({
|
expect(result.current).toEqual({
|
||||||
referralDiscount: Number(stats.discountFactor),
|
referralDiscount: 0,
|
||||||
referralVolumeInWindow: Number(
|
referralVolumeInWindow: 0,
|
||||||
stats.referralSetRunningNotionalTakerVolume
|
referralTierIndex: -1,
|
||||||
),
|
|
||||||
referralTierIndex: 1,
|
|
||||||
referralTiers: program.benefitTiers,
|
referralTiers: program.benefitTiers,
|
||||||
epochsInSet: Number(epoch.id) - set.atEpoch,
|
epochsInSet: 0,
|
||||||
code: undefined,
|
code: undefined,
|
||||||
isReferrer: false,
|
isReferrer: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it('returns formatted data and tiers', () => {
|
||||||
{ joinedAt: 2, index: -1 },
|
|
||||||
{ joinedAt: 3, index: -1 },
|
|
||||||
{ joinedAt: 4, index: 0 },
|
|
||||||
{ joinedAt: 5, index: 0 },
|
|
||||||
{ joinedAt: 6, index: 1 },
|
|
||||||
{ joinedAt: 7, index: 1 },
|
|
||||||
{ joinedAt: 8, index: 2 },
|
|
||||||
{ joinedAt: 9, index: 2 },
|
|
||||||
])('joined at epoch: $joinedAt should be index: $index', (obj) => {
|
|
||||||
const statsA = {
|
|
||||||
edges: [
|
|
||||||
{
|
|
||||||
__typename: 'ReferralSetStatsEdge' as const,
|
|
||||||
node: {
|
|
||||||
__typename: 'ReferralSetStats' as const,
|
|
||||||
atEpoch: 10,
|
|
||||||
discountFactor: '0.3',
|
|
||||||
referralSetRunningNotionalTakerVolume: '100000',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const setsA = {
|
|
||||||
edges: [
|
|
||||||
{
|
|
||||||
node: {
|
|
||||||
atEpoch: Number(epoch.id) - obj.joinedAt,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const { result } = renderHook(() =>
|
const { result } = renderHook(() =>
|
||||||
useReferralStats(statsA, setsA, program, epoch)
|
useReferralStats(9, stat, set, program)
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result.current.referralTierIndex).toEqual(obj.index);
|
expect(result.current).toEqual({
|
||||||
});
|
referralDiscount: Number(stat.discountFactor),
|
||||||
|
referralVolumeInWindow: Number(
|
||||||
it.each([
|
stat.referralSetRunningNotionalTakerVolume
|
||||||
{ volume: '50', index: -1 },
|
),
|
||||||
{ volume: '100', index: 0 },
|
referralTierIndex: 0,
|
||||||
{ volume: '150', index: 0 },
|
referralTiers: program.benefitTiers,
|
||||||
{ volume: '200', index: 1 },
|
epochsInSet: stat.atEpoch - set.atEpoch,
|
||||||
{ volume: '250', index: 1 },
|
code: undefined,
|
||||||
{ volume: '300', index: 2 },
|
isReferrer: false,
|
||||||
{ volume: '999', index: 2 },
|
});
|
||||||
])('volume: $volume should be index: $index', (obj) => {
|
|
||||||
const statsA = {
|
|
||||||
edges: [
|
|
||||||
{
|
|
||||||
__typename: 'ReferralSetStatsEdge' as const,
|
|
||||||
node: {
|
|
||||||
__typename: 'ReferralSetStats' as const,
|
|
||||||
atEpoch: 10,
|
|
||||||
discountFactor: '0.3',
|
|
||||||
referralSetRunningNotionalTakerVolume: obj.volume,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const setsA = {
|
|
||||||
edges: [
|
|
||||||
{
|
|
||||||
node: {
|
|
||||||
atEpoch: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const { result } = renderHook(() =>
|
|
||||||
useReferralStats(statsA, setsA, program, epoch)
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result.current.referralTierIndex).toEqual(obj.index);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
import compact from 'lodash/compact';
|
|
||||||
import maxBy from 'lodash/maxBy';
|
|
||||||
import { getReferralBenefitTier } from './utils';
|
|
||||||
import type { DiscountProgramsQuery, FeesQuery } from './__generated__/Fees';
|
import type { DiscountProgramsQuery, FeesQuery } from './__generated__/Fees';
|
||||||
import { first } from 'lodash';
|
|
||||||
|
|
||||||
export const useReferralStats = (
|
export const useReferralStats = (
|
||||||
setStats?: FeesQuery['referralSetStats'],
|
previousEpoch?: number,
|
||||||
setReferees?: FeesQuery['referralSetReferees'],
|
referralStats?: NonNullable<
|
||||||
|
FeesQuery['referralSetStats']['edges']['0']
|
||||||
|
>['node'],
|
||||||
|
setReferees?: NonNullable<
|
||||||
|
FeesQuery['referralSetReferees']['edges']['0']
|
||||||
|
>['node'],
|
||||||
program?: DiscountProgramsQuery['currentReferralProgram'],
|
program?: DiscountProgramsQuery['currentReferralProgram'],
|
||||||
epoch?: FeesQuery['epoch'],
|
setIfReferrer?: NonNullable<FeesQuery['referrer']['edges']['0']>['node'],
|
||||||
setIfReferrer?: FeesQuery['referrer'],
|
setIfReferee?: NonNullable<FeesQuery['referee']['edges']['0']>['node']
|
||||||
setIfReferee?: FeesQuery['referee']
|
|
||||||
) => {
|
) => {
|
||||||
const referralTiers = program?.benefitTiers || [];
|
const referralTiers = program?.benefitTiers || [];
|
||||||
|
|
||||||
if (!setStats || !setReferees || !program || !epoch) {
|
if (
|
||||||
|
!previousEpoch ||
|
||||||
|
referralStats?.atEpoch !== previousEpoch ||
|
||||||
|
!program ||
|
||||||
|
!setReferees
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
referralDiscount: 0,
|
referralDiscount: 0,
|
||||||
referralVolumeInWindow: 0,
|
referralVolumeInWindow: 0,
|
||||||
@@ -26,41 +30,22 @@ export const useReferralStats = (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const setIfReferrerData = first(
|
|
||||||
compact(setIfReferrer?.edges).map((e) => e.node)
|
|
||||||
);
|
|
||||||
const setIfRefereeData = first(
|
|
||||||
compact(setIfReferee?.edges).map((e) => e.node)
|
|
||||||
);
|
|
||||||
|
|
||||||
const referralSetsStats = compact(setStats.edges).map((e) => e.node);
|
|
||||||
const referralSets = compact(setReferees.edges).map((e) => e.node);
|
|
||||||
|
|
||||||
const referralSet = maxBy(referralSets, (s) => s.atEpoch);
|
|
||||||
const referralStats = maxBy(referralSetsStats, (s) => s.atEpoch);
|
|
||||||
|
|
||||||
const epochsInSet = referralSet ? Number(epoch.id) - referralSet.atEpoch : 0;
|
|
||||||
|
|
||||||
const referralDiscount = Number(referralStats?.discountFactor || 0);
|
const referralDiscount = Number(referralStats?.discountFactor || 0);
|
||||||
const referralVolumeInWindow = Number(
|
const referralVolumeInWindow = Number(
|
||||||
referralStats?.referralSetRunningNotionalTakerVolume || 0
|
referralStats?.referralSetRunningNotionalTakerVolume || 0
|
||||||
);
|
);
|
||||||
|
|
||||||
const referralTierIndex = referralStats
|
const referralTierIndex = referralTiers.findIndex(
|
||||||
? getReferralBenefitTier(
|
(tier) => tier.referralDiscountFactor === referralStats?.discountFactor
|
||||||
epochsInSet,
|
);
|
||||||
Number(referralStats.referralSetRunningNotionalTakerVolume),
|
|
||||||
referralTiers
|
|
||||||
)
|
|
||||||
: -1;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
referralDiscount,
|
referralDiscount,
|
||||||
referralVolumeInWindow,
|
referralVolumeInWindow,
|
||||||
referralTierIndex,
|
referralTierIndex,
|
||||||
referralTiers,
|
referralTiers,
|
||||||
epochsInSet,
|
epochsInSet: referralStats.atEpoch - setReferees.atEpoch,
|
||||||
code: (setIfReferrerData || setIfRefereeData)?.id,
|
code: (setIfReferrer || setIfReferee)?.id,
|
||||||
isReferrer: Boolean(setIfReferrerData),
|
isReferrer: Boolean(setIfReferrer),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,27 +2,11 @@ import { renderHook } from '@testing-library/react';
|
|||||||
import { useVolumeStats } from './use-volume-stats';
|
import { useVolumeStats } from './use-volume-stats';
|
||||||
|
|
||||||
describe('useReferralStats', () => {
|
describe('useReferralStats', () => {
|
||||||
const statsList = {
|
const stats = {
|
||||||
edges: [
|
__typename: 'VolumeDiscountStats' as const,
|
||||||
{
|
atEpoch: 10,
|
||||||
__typename: 'VolumeDiscountStatsEdge' as const,
|
discountFactor: '0.05',
|
||||||
node: {
|
runningVolume: '200',
|
||||||
__typename: 'VolumeDiscountStats' as const,
|
|
||||||
atEpoch: 9,
|
|
||||||
discountFactor: '0.1',
|
|
||||||
runningVolume: '100',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
__typename: 'VolumeDiscountStatsEdge' as const,
|
|
||||||
node: {
|
|
||||||
__typename: 'VolumeDiscountStats' as const,
|
|
||||||
atEpoch: 10,
|
|
||||||
discountFactor: '0.3',
|
|
||||||
runningVolume: '200',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const program = {
|
const program = {
|
||||||
@@ -44,7 +28,7 @@ describe('useReferralStats', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
it('returns correct default values', () => {
|
it('returns correct default values', () => {
|
||||||
const { result } = renderHook(() => useVolumeStats());
|
const { result } = renderHook(() => useVolumeStats(10));
|
||||||
expect(result.current).toEqual({
|
expect(result.current).toEqual({
|
||||||
volumeDiscount: 0,
|
volumeDiscount: 0,
|
||||||
volumeInWindow: 0,
|
volumeInWindow: 0,
|
||||||
@@ -53,11 +37,18 @@ describe('useReferralStats', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns formatted data and tiers', () => {
|
it('returns default values if no stat is not from previous epoch', () => {
|
||||||
const { result } = renderHook(() => useVolumeStats(statsList, program));
|
const { result } = renderHook(() => useVolumeStats(11, stats, program));
|
||||||
|
expect(result.current).toEqual({
|
||||||
|
volumeDiscount: 0,
|
||||||
|
volumeInWindow: 0,
|
||||||
|
volumeTierIndex: -1,
|
||||||
|
volumeTiers: program.benefitTiers,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// should use stats from latest epoch
|
it('returns formatted data and tiers', () => {
|
||||||
const stats = statsList.edges[1].node;
|
const { result } = renderHook(() => useVolumeStats(10, stats, program));
|
||||||
|
|
||||||
expect(result.current).toEqual({
|
expect(result.current).toEqual({
|
||||||
volumeDiscount: Number(stats.discountFactor),
|
volumeDiscount: Number(stats.discountFactor),
|
||||||
@@ -66,30 +57,4 @@ describe('useReferralStats', () => {
|
|||||||
volumeTiers: program.benefitTiers,
|
volumeTiers: program.benefitTiers,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
|
||||||
{ volume: '100', index: 0 },
|
|
||||||
{ volume: '150', index: 0 },
|
|
||||||
{ volume: '200', index: 1 },
|
|
||||||
{ volume: '250', index: 1 },
|
|
||||||
{ volume: '300', index: 2 },
|
|
||||||
{ volume: '350', index: 2 },
|
|
||||||
])('returns index: $index for the running volume: $volume', (obj) => {
|
|
||||||
const statsA = {
|
|
||||||
edges: [
|
|
||||||
{
|
|
||||||
__typename: 'VolumeDiscountStatsEdge' as const,
|
|
||||||
node: {
|
|
||||||
__typename: 'VolumeDiscountStats' as const,
|
|
||||||
atEpoch: 10,
|
|
||||||
discountFactor: '0.3',
|
|
||||||
runningVolume: obj.volume,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const { result } = renderHook(() => useVolumeStats(statsA, program));
|
|
||||||
expect(result.current.volumeTierIndex).toBe(obj.index);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import compact from 'lodash/compact';
|
|
||||||
import maxBy from 'lodash/maxBy';
|
|
||||||
import { getVolumeTier } from './utils';
|
|
||||||
import type { DiscountProgramsQuery, FeesQuery } from './__generated__/Fees';
|
import type { DiscountProgramsQuery, FeesQuery } from './__generated__/Fees';
|
||||||
|
|
||||||
export const useVolumeStats = (
|
export const useVolumeStats = (
|
||||||
stats?: FeesQuery['volumeDiscountStats'],
|
previousEpoch: number,
|
||||||
|
lastEpochStats?: NonNullable<
|
||||||
|
FeesQuery['volumeDiscountStats']['edges']['0']
|
||||||
|
>['node'],
|
||||||
program?: DiscountProgramsQuery['currentVolumeDiscountProgram']
|
program?: DiscountProgramsQuery['currentVolumeDiscountProgram']
|
||||||
) => {
|
) => {
|
||||||
const volumeTiers = program?.benefitTiers || [];
|
const volumeTiers = program?.benefitTiers || [];
|
||||||
|
|
||||||
if (!stats || !program) {
|
if (!lastEpochStats || lastEpochStats.atEpoch !== previousEpoch || !program) {
|
||||||
return {
|
return {
|
||||||
volumeDiscount: 0,
|
volumeDiscount: 0,
|
||||||
volumeTierIndex: -1,
|
volumeTierIndex: -1,
|
||||||
@@ -18,11 +18,11 @@ export const useVolumeStats = (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const volumeStats = compact(stats.edges).map((e) => e.node);
|
|
||||||
const lastEpochStats = maxBy(volumeStats, (s) => s.atEpoch);
|
|
||||||
const volumeDiscount = Number(lastEpochStats?.discountFactor || 0);
|
const volumeDiscount = Number(lastEpochStats?.discountFactor || 0);
|
||||||
const volumeInWindow = Number(lastEpochStats?.runningVolume || 0);
|
const volumeInWindow = Number(lastEpochStats?.runningVolume || 0);
|
||||||
const volumeTierIndex = getVolumeTier(volumeInWindow, volumeTiers);
|
const volumeTierIndex = volumeTiers.findIndex(
|
||||||
|
(tier) => tier.volumeDiscountFactor === lastEpochStats?.discountFactor
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
volumeDiscount,
|
volumeDiscount,
|
||||||
|
|||||||
@@ -20,73 +20,6 @@ export const formatPercentage = (num: number) => {
|
|||||||
return formatter.format(parseFloat(pct.toFixed(5)));
|
return formatter.format(parseFloat(pct.toFixed(5)));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the index of the benefit tier for volume discounts. A user
|
|
||||||
* only needs to fulfill a minimum volume requirement for the tier
|
|
||||||
*/
|
|
||||||
export const getVolumeTier = (
|
|
||||||
volume: number,
|
|
||||||
tiers: Array<{
|
|
||||||
minimumRunningNotionalTakerVolume: string;
|
|
||||||
}>
|
|
||||||
) => {
|
|
||||||
return tiers.findIndex((tier, i) => {
|
|
||||||
const nextTier = tiers[i + 1];
|
|
||||||
const validVolume =
|
|
||||||
volume >= Number(tier.minimumRunningNotionalTakerVolume);
|
|
||||||
|
|
||||||
if (nextTier) {
|
|
||||||
return (
|
|
||||||
validVolume &&
|
|
||||||
volume < Number(nextTier.minimumRunningNotionalTakerVolume)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return validVolume;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the index of the benefit tiers for referrals. A user must
|
|
||||||
* fulfill both the minimum epochs in the referral set, and the set
|
|
||||||
* must reach the combined total volume
|
|
||||||
*/
|
|
||||||
export const getReferralBenefitTier = (
|
|
||||||
epochsInSet: number,
|
|
||||||
volume: number,
|
|
||||||
tiers: Array<{
|
|
||||||
minimumRunningNotionalTakerVolume: string;
|
|
||||||
minimumEpochs: number;
|
|
||||||
}>
|
|
||||||
) => {
|
|
||||||
const indexByEpoch = tiers.findIndex((tier, i) => {
|
|
||||||
const nextTier = tiers[i + 1];
|
|
||||||
const validEpochs = epochsInSet >= tier.minimumEpochs;
|
|
||||||
|
|
||||||
if (nextTier) {
|
|
||||||
return validEpochs && epochsInSet < nextTier.minimumEpochs;
|
|
||||||
}
|
|
||||||
|
|
||||||
return validEpochs;
|
|
||||||
});
|
|
||||||
const indexByVolume = tiers.findIndex((tier, i) => {
|
|
||||||
const nextTier = tiers[i + 1];
|
|
||||||
const validVolume =
|
|
||||||
volume >= Number(tier.minimumRunningNotionalTakerVolume);
|
|
||||||
|
|
||||||
if (nextTier) {
|
|
||||||
return (
|
|
||||||
validVolume &&
|
|
||||||
volume < Number(nextTier.minimumRunningNotionalTakerVolume)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return validVolume;
|
|
||||||
});
|
|
||||||
|
|
||||||
return Math.min(indexByEpoch, indexByVolume);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a set of fees and a set of discounts return
|
* Given a set of fees and a set of discounts return
|
||||||
* the adjusted fee factor
|
* the adjusted fee factor
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ import { type IterableElement } from 'type-fest';
|
|||||||
import {
|
import {
|
||||||
AccountEventsDocument,
|
AccountEventsDocument,
|
||||||
AccountsDocument,
|
AccountsDocument,
|
||||||
AccountFieldsFragment,
|
type AccountFieldsFragment,
|
||||||
AccountsQuery,
|
type AccountsQuery,
|
||||||
AccountEventsSubscription,
|
type AccountEventsSubscription,
|
||||||
AccountsQueryVariables,
|
type AccountsQueryVariables,
|
||||||
} from './__generated__/Accounts';
|
} from './__generated__/Accounts';
|
||||||
import { type Asset } from '@vegaprotocol/assets';
|
import { type Asset } from '@vegaprotocol/assets';
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
import { ApolloError } from '@apollo/client';
|
import { ApolloError } from '@apollo/client';
|
||||||
import type { GraphQLErrors } from '@apollo/client/errors';
|
import type { GraphQLErrors } from '@apollo/client/errors';
|
||||||
import { GraphQLError } from 'graphql';
|
import { GraphQLError } from 'graphql';
|
||||||
import { Subscription, Observable } from 'zen-observable-ts';
|
import { type Subscription, type Observable } from 'zen-observable-ts';
|
||||||
import { waitFor } from '@testing-library/react';
|
import { waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
type Item = {
|
type Item = {
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import {
|
|||||||
type NodeCheckTimeUpdateSubscription,
|
type NodeCheckTimeUpdateSubscription,
|
||||||
} from '../../utils/__generated__/NodeCheck';
|
} from '../../utils/__generated__/NodeCheck';
|
||||||
import { Networks } from '../../types';
|
import { Networks } from '../../types';
|
||||||
import { createMockClient, RequestHandlerResponse } from 'mock-apollo-client';
|
import {
|
||||||
|
createMockClient,
|
||||||
|
type RequestHandlerResponse,
|
||||||
|
} from 'mock-apollo-client';
|
||||||
|
|
||||||
export type MockRequestConfig = {
|
export type MockRequestConfig = {
|
||||||
hasError?: boolean;
|
hasError?: boolean;
|
||||||
|
|||||||
@@ -4,14 +4,8 @@ import { getDateTimeFormat } from '@vegaprotocol/utils';
|
|||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
import type { PartialDeep } from 'type-fest';
|
import type { PartialDeep } from 'type-fest';
|
||||||
import type { Trade } from './fills-data-provider';
|
import type { Trade } from './fills-data-provider';
|
||||||
import {
|
import { FeesDiscountBreakdownTooltip, FillsTable } from './fills-table';
|
||||||
FeesDiscountBreakdownTooltip,
|
|
||||||
FillsTable,
|
|
||||||
getFeesBreakdown,
|
|
||||||
getTotalFeesDiscounts,
|
|
||||||
} from './fills-table';
|
|
||||||
import { generateFill } from './test-helpers';
|
import { generateFill } from './test-helpers';
|
||||||
import type { TradeFeeFieldsFragment } from './__generated__/Fills';
|
|
||||||
|
|
||||||
const partyId = 'party-id';
|
const partyId = 'party-id';
|
||||||
const defaultFill: PartialDeep<Trade> = {
|
const defaultFill: PartialDeep<Trade> = {
|
||||||
@@ -35,6 +29,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
|
||||||
@@ -65,7 +60,7 @@ describe('FillsTable', () => {
|
|||||||
expect(headers.map((h) => h.textContent?.trim())).toEqual(expectedHeaders);
|
expect(headers.map((h) => h.textContent?.trim())).toEqual(expectedHeaders);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('formats cells correctly for buyer fill', async () => {
|
it('formats cells correctly for buyer fill for maker', async () => {
|
||||||
const buyerFill = generateFill({
|
const buyerFill = generateFill({
|
||||||
...defaultFill,
|
...defaultFill,
|
||||||
buyer: {
|
buyer: {
|
||||||
@@ -89,7 +84,7 @@ describe('FillsTable', () => {
|
|||||||
'3.00 BTC',
|
'3.00 BTC',
|
||||||
'Maker',
|
'Maker',
|
||||||
'2.00 BTC',
|
'2.00 BTC',
|
||||||
'0.27 BTC',
|
'0.09 BTC',
|
||||||
getDateTimeFormat().format(new Date(buyerFill.createdAt)),
|
getDateTimeFormat().format(new Date(buyerFill.createdAt)),
|
||||||
'', // action column
|
'', // action column
|
||||||
];
|
];
|
||||||
@@ -271,96 +266,48 @@ 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 = {
|
const props = {
|
||||||
data,
|
data,
|
||||||
partyId,
|
partyId,
|
||||||
value: data.market,
|
value: data.market,
|
||||||
} as Parameters<typeof FeesDiscountBreakdownTooltip>['0'];
|
} as Parameters<typeof FeesDiscountBreakdownTooltip>['0'];
|
||||||
const { container } = render(<FeesDiscountBreakdownTooltip {...props} />);
|
const { container } = render(<FeesDiscountBreakdownTooltip {...props} />);
|
||||||
const dt = container.querySelectorAll('dt');
|
const dt = container.querySelectorAll('dt');
|
||||||
const dd = container.querySelectorAll('dd');
|
const dd = container.querySelectorAll('dd');
|
||||||
const expectedDt = [
|
const expectedDt = [
|
||||||
'Infrastructure Fee',
|
'Infrastructure Fee',
|
||||||
'Referral Discount',
|
'Referral Discount',
|
||||||
'Volume Discount',
|
'Volume Discount',
|
||||||
'Liquidity Fee',
|
'Liquidity Fee',
|
||||||
'Referral Discount',
|
'Referral Discount',
|
||||||
'Volume Discount',
|
'Volume Discount',
|
||||||
'Maker Fee',
|
'Maker Fee',
|
||||||
'Referral Discount',
|
'Referral Discount',
|
||||||
'Volume Discount',
|
'Volume Discount',
|
||||||
];
|
];
|
||||||
const expectedDD = [
|
const expectedDD = [
|
||||||
'0.05 BTC',
|
'0.05 BTC',
|
||||||
'0.06 BTC',
|
'0.06 BTC',
|
||||||
'0.01 BTC',
|
'0.01 BTC',
|
||||||
'0.02 BTC',
|
'0.02 BTC',
|
||||||
'0.03 BTC',
|
'0.03 BTC',
|
||||||
'0.04 BTC',
|
'0.04 BTC',
|
||||||
];
|
];
|
||||||
expectedDt.forEach((label, i) => {
|
expectedDt.forEach((label, i) => {
|
||||||
expect(dt[i]).toHaveTextContent(label);
|
expect(dt[i]).toHaveTextContent(label);
|
||||||
});
|
});
|
||||||
expectedDD.forEach((label, i) => {
|
expectedDD.forEach((label, i) => {
|
||||||
expect(dd[i]).toHaveTextContent(label);
|
expect(dd[i]).toHaveTextContent(label);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
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()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -28,20 +28,12 @@ import {
|
|||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import { type Trade } from './fills-data-provider';
|
import { type Trade } from './fills-data-provider';
|
||||||
import {
|
|
||||||
type FillFieldsFragment,
|
|
||||||
type TradeFeeFieldsFragment,
|
|
||||||
} from './__generated__/Fills';
|
|
||||||
import { FillActionsDropdown } from './fill-actions-dropdown';
|
import { FillActionsDropdown } from './fill-actions-dropdown';
|
||||||
import { getAsset } from '@vegaprotocol/markets';
|
import { getAsset } from '@vegaprotocol/markets';
|
||||||
import { useT } from './use-t';
|
import { useT } from './use-t';
|
||||||
|
import { MAKER, TAKER, getFeesBreakdown, getRoleAndFees } from './fills-utils';
|
||||||
|
|
||||||
const TAKER = 'Taker';
|
type Props = (AgGridReactProps | AgReactUiProps) & {
|
||||||
const MAKER = 'Maker';
|
|
||||||
|
|
||||||
export type Role = typeof TAKER | typeof MAKER | '-';
|
|
||||||
|
|
||||||
export type Props = (AgGridReactProps | AgReactUiProps) & {
|
|
||||||
partyId: string;
|
partyId: string;
|
||||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||||
};
|
};
|
||||||
@@ -262,63 +254,13 @@ const formatFeeDiscount = (partyId: string) => {
|
|||||||
}: VegaValueFormatterParams<Trade, 'market'>) => {
|
}: VegaValueFormatterParams<Trade, 'market'>) => {
|
||||||
if (!market || !data) return '-';
|
if (!market || !data) return '-';
|
||||||
const asset = getAsset(market);
|
const asset = getAsset(market);
|
||||||
const { fees } = getRoleAndFees({ data, partyId });
|
const { fees: roleFees, role } = getRoleAndFees({ data, partyId });
|
||||||
if (!fees) return '-';
|
if (!roleFees) return '-';
|
||||||
|
const { totalFeeDiscount } = getFeesBreakdown(role, roleFees);
|
||||||
const total = getTotalFeesDiscounts(fees);
|
return addDecimalsFormatNumber(totalFeeDiscount, asset.decimals);
|
||||||
return addDecimalsFormatNumber(total, asset.decimals);
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isEmptyFeeObj = (feeObj: Schema.TradeFee) => {
|
|
||||||
if (!feeObj) return true;
|
|
||||||
return (
|
|
||||||
feeObj.liquidityFee === '0' &&
|
|
||||||
feeObj.makerFee === '0' &&
|
|
||||||
feeObj.infrastructureFee === '0'
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getRoleAndFees = ({
|
|
||||||
data,
|
|
||||||
partyId,
|
|
||||||
}: {
|
|
||||||
data: Pick<
|
|
||||||
FillFieldsFragment,
|
|
||||||
'buyerFee' | 'sellerFee' | 'buyer' | 'seller' | 'aggressor'
|
|
||||||
>;
|
|
||||||
partyId?: string;
|
|
||||||
}) => {
|
|
||||||
let role: Role;
|
|
||||||
let fees;
|
|
||||||
if (data?.buyer.id === partyId) {
|
|
||||||
if (data.aggressor === Schema.Side.SIDE_BUY) {
|
|
||||||
role = TAKER;
|
|
||||||
fees = data?.buyerFee;
|
|
||||||
} else if (data.aggressor === Schema.Side.SIDE_SELL) {
|
|
||||||
role = MAKER;
|
|
||||||
fees = data?.sellerFee;
|
|
||||||
} else {
|
|
||||||
role = '-';
|
|
||||||
fees = !isEmptyFeeObj(data?.buyerFee) ? data.buyerFee : data.sellerFee;
|
|
||||||
}
|
|
||||||
} else if (data?.seller.id === partyId) {
|
|
||||||
if (data.aggressor === Schema.Side.SIDE_SELL) {
|
|
||||||
role = TAKER;
|
|
||||||
fees = data?.sellerFee;
|
|
||||||
} else if (data.aggressor === Schema.Side.SIDE_BUY) {
|
|
||||||
role = MAKER;
|
|
||||||
fees = data?.buyerFee;
|
|
||||||
} else {
|
|
||||||
role = '-';
|
|
||||||
fees = !isEmptyFeeObj(data.sellerFee) ? data.sellerFee : data.buyerFee;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return { role: '-', fees: undefined };
|
|
||||||
}
|
|
||||||
return { role, fees };
|
|
||||||
};
|
|
||||||
|
|
||||||
const FeesBreakdownTooltip = ({
|
const FeesBreakdownTooltip = ({
|
||||||
data,
|
data,
|
||||||
value: market,
|
value: market,
|
||||||
@@ -331,16 +273,23 @@ 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="bg-vega-light-100 dark:bg-vega-dark-100 border-vega-light-200 dark:border-vega-dark-200 break-word z-20 max-w-sm rounded border px-4 py-2 text-sm text-black dark:text-white"
|
className="bg-vega-light-100 dark:bg-vega-dark-100 border-vega-light-200 dark:border-vega-dark-200 break-word z-20 max-w-sm rounded border px-4 py-2 text-xs text-black dark:text-white"
|
||||||
>
|
>
|
||||||
|
{marketState && (
|
||||||
|
<p className="mb-1 italic">
|
||||||
|
{t('If the market was {{state}}', {
|
||||||
|
state: 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>
|
||||||
@@ -354,7 +303,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.'
|
||||||
@@ -395,8 +344,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="col-span-1 text-right">
|
<dd className="col-span-2 text-right">
|
||||||
{addDecimalsFormatNumber(value, asset.decimals)} {asset.symbol}
|
{addDecimalsFormatNumber(value, asset.decimals)} {asset.symbol}
|
||||||
</dd>
|
</dd>
|
||||||
</>
|
</>
|
||||||
@@ -412,15 +361,19 @@ export const FeesDiscountBreakdownTooltip = ({
|
|||||||
}
|
}
|
||||||
const asset = getAsset(data.market);
|
const asset = getAsset(data.market);
|
||||||
|
|
||||||
const { fees } = getRoleAndFees({ data, partyId }) ?? {};
|
const {
|
||||||
if (!fees) return null;
|
fees: roleFees,
|
||||||
|
marketState,
|
||||||
|
role,
|
||||||
|
} = getRoleAndFees({ data, partyId }) ?? {};
|
||||||
|
if (!roleFees) return null;
|
||||||
|
const fees = getFeesBreakdown(role, roleFees, marketState);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-testid="fee-discount-breakdown-tooltip"
|
data-testid="fee-discount-breakdown-tooltip"
|
||||||
className="bg-vega-light-100 dark:bg-vega-dark-100 border-vega-light-200 dark:border-vega-dark-200 break-word z-20 max-w-sm rounded border px-4 py-2 text-sm text-black dark:text-white"
|
className="bg-vega-light-100 dark:bg-vega-dark-100 border-vega-light-200 dark:border-vega-dark-200 break-word z-20 max-w-sm rounded border px-4 py-2 text-sm 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>
|
||||||
@@ -464,42 +417,14 @@ export const FeesDiscountBreakdownTooltip = ({
|
|||||||
label={t('Volume Discount')}
|
label={t('Volume Discount')}
|
||||||
asset={asset}
|
asset={asset}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<dt className="col-span-2">{t('Total Fee Discount')}</dt>
|
||||||
|
<FeesDiscountBreakdownTooltipItem
|
||||||
|
value={fees.totalFeeDiscount}
|
||||||
|
label={''}
|
||||||
|
asset={asset}
|
||||||
|
/>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</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: TradeFeeFieldsFragment
|
|
||||||
) => {
|
|
||||||
const makerFee =
|
|
||||||
role === MAKER
|
|
||||||
? new BigNumber(feesObj.makerFee).times(-1).toString()
|
|
||||||
: feesObj.makerFee;
|
|
||||||
|
|
||||||
const infrastructureFee = feesObj.infrastructureFee;
|
|
||||||
const liquidityFee = feesObj.liquidityFee;
|
|
||||||
|
|
||||||
const totalFee = new BigNumber(infrastructureFee)
|
|
||||||
.plus(makerFee)
|
|
||||||
.plus(liquidityFee)
|
|
||||||
.toString();
|
|
||||||
return {
|
|
||||||
infrastructureFee,
|
|
||||||
liquidityFee,
|
|
||||||
makerFee,
|
|
||||||
totalFee,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
import { getFeesBreakdown } from './fills-utils';
|
||||||
|
import * as Schema from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
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',
|
||||||
|
totalFeeDiscount: '0',
|
||||||
|
};
|
||||||
|
expect(getFeesBreakdown('Taker', 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',
|
||||||
|
totalFeeDiscount: '0',
|
||||||
|
};
|
||||||
|
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',
|
||||||
|
totalFeeDiscount: '0',
|
||||||
|
};
|
||||||
|
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',
|
||||||
|
totalFeeDiscount: '0',
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
getFeesBreakdown('Taker', fees, Schema.MarketState.STATE_SUSPENDED)
|
||||||
|
).toEqual(expectedBreakdown);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct fees breakdown for a taker if market is active', () => {
|
||||||
|
const fees = {
|
||||||
|
makerFee: '1000',
|
||||||
|
infrastructureFee: '2000',
|
||||||
|
liquidityFee: '3000',
|
||||||
|
};
|
||||||
|
const expectedBreakdown = {
|
||||||
|
infrastructureFee: '2000',
|
||||||
|
liquidityFee: '3000',
|
||||||
|
makerFee: '1000',
|
||||||
|
totalFee: '6000',
|
||||||
|
totalFeeDiscount: '0',
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
getFeesBreakdown('Taker', fees, Schema.MarketState.STATE_ACTIVE)
|
||||||
|
).toEqual(expectedBreakdown);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct fees breakdown for a maker', () => {
|
||||||
|
const fees = {
|
||||||
|
makerFee: '1000',
|
||||||
|
infrastructureFee: '2000',
|
||||||
|
liquidityFee: '3000',
|
||||||
|
};
|
||||||
|
const expectedBreakdown = {
|
||||||
|
infrastructureFee: '0',
|
||||||
|
liquidityFee: '0',
|
||||||
|
makerFee: '-1000',
|
||||||
|
totalFee: '-1000',
|
||||||
|
totalFeeDiscount: '0',
|
||||||
|
};
|
||||||
|
expect(getFeesBreakdown('Maker', fees)).toEqual(expectedBreakdown);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct total fees discount value for a taker (if the market is active - default)', () => {
|
||||||
|
const fees = {
|
||||||
|
infrastructureFeeReferralDiscount: '1',
|
||||||
|
infrastructureFeeVolumeDiscount: '2',
|
||||||
|
liquidityFeeReferralDiscount: '3',
|
||||||
|
liquidityFeeVolumeDiscount: '4',
|
||||||
|
makerFeeReferralDiscount: '5',
|
||||||
|
makerFeeVolumeDiscount: '6',
|
||||||
|
infrastructureFee: '1000',
|
||||||
|
liquidityFee: '2000',
|
||||||
|
makerFee: '3000',
|
||||||
|
};
|
||||||
|
const { totalFeeDiscount } = getFeesBreakdown('Taker', fees);
|
||||||
|
expect(totalFeeDiscount).toEqual((1 + 2 + 3 + 4 + 5 + 6).toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct total fees discount value for a maker (if the market is active - default)', () => {
|
||||||
|
const fees = {
|
||||||
|
infrastructureFeeReferralDiscount: '1',
|
||||||
|
infrastructureFeeVolumeDiscount: '2',
|
||||||
|
liquidityFeeReferralDiscount: '3',
|
||||||
|
liquidityFeeVolumeDiscount: '4',
|
||||||
|
makerFeeReferralDiscount: '5',
|
||||||
|
makerFeeVolumeDiscount: '6',
|
||||||
|
infrastructureFee: '1000',
|
||||||
|
liquidityFee: '2000',
|
||||||
|
makerFee: '3000',
|
||||||
|
};
|
||||||
|
const { totalFeeDiscount } = getFeesBreakdown('Maker', fees);
|
||||||
|
// makerFeeReferralDiscount and makerFeeVolumeDiscount are added, infra and liq. fees are zeroed
|
||||||
|
expect(totalFeeDiscount).toEqual((5 + 6).toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct total fees discount value for a maker (if the market is suspended)', () => {
|
||||||
|
const fees = {
|
||||||
|
infrastructureFeeReferralDiscount: '1',
|
||||||
|
infrastructureFeeVolumeDiscount: '2',
|
||||||
|
liquidityFeeReferralDiscount: '3',
|
||||||
|
liquidityFeeVolumeDiscount: '4',
|
||||||
|
makerFeeReferralDiscount: '5',
|
||||||
|
makerFeeVolumeDiscount: '6',
|
||||||
|
infrastructureFee: '1000',
|
||||||
|
liquidityFee: '2000',
|
||||||
|
makerFee: '3000',
|
||||||
|
};
|
||||||
|
const { totalFeeDiscount } = getFeesBreakdown(
|
||||||
|
'Maker',
|
||||||
|
fees,
|
||||||
|
Schema.MarketState.STATE_SUSPENDED
|
||||||
|
);
|
||||||
|
// makerFeeReferralDiscount and makerFeeVolumeDiscount are zeroed, infra and liq. fees are halved
|
||||||
|
expect(totalFeeDiscount).toEqual(((1 + 2 + 3 + 4) / 2).toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct total fees discount value for a taker (if the market is suspended)', () => {
|
||||||
|
const fees = {
|
||||||
|
infrastructureFeeReferralDiscount: '1',
|
||||||
|
infrastructureFeeVolumeDiscount: '2',
|
||||||
|
liquidityFeeReferralDiscount: '3',
|
||||||
|
liquidityFeeVolumeDiscount: '4',
|
||||||
|
makerFeeReferralDiscount: '5',
|
||||||
|
makerFeeVolumeDiscount: '6',
|
||||||
|
infrastructureFee: '1000',
|
||||||
|
liquidityFee: '2000',
|
||||||
|
makerFee: '3000',
|
||||||
|
};
|
||||||
|
const { totalFeeDiscount } = getFeesBreakdown(
|
||||||
|
'Taker',
|
||||||
|
fees,
|
||||||
|
Schema.MarketState.STATE_SUSPENDED
|
||||||
|
);
|
||||||
|
// makerFeeReferralDiscount and makerFeeVolumeDiscount are zeroed, infra and liq. fees are halved
|
||||||
|
expect(totalFeeDiscount).toEqual(((1 + 2 + 3 + 4) / 2).toString());
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import type {
|
||||||
|
FillFieldsFragment,
|
||||||
|
TradeFeeFieldsFragment,
|
||||||
|
} from './__generated__/Fills';
|
||||||
|
import * as Schema from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
export const TAKER = 'Taker';
|
||||||
|
export const MAKER = 'Maker';
|
||||||
|
|
||||||
|
export type Role = typeof TAKER | typeof MAKER | '-';
|
||||||
|
|
||||||
|
export const getRoleAndFees = ({
|
||||||
|
data,
|
||||||
|
partyId,
|
||||||
|
}: {
|
||||||
|
data: Pick<
|
||||||
|
FillFieldsFragment,
|
||||||
|
'buyerFee' | 'sellerFee' | 'buyer' | 'seller' | 'aggressor'
|
||||||
|
>;
|
||||||
|
partyId?: string;
|
||||||
|
}): {
|
||||||
|
role: Role;
|
||||||
|
fees?: TradeFeeFieldsFragment;
|
||||||
|
marketState?: Schema.MarketState;
|
||||||
|
} => {
|
||||||
|
let role: Role;
|
||||||
|
let fees;
|
||||||
|
|
||||||
|
if (data?.buyer.id === partyId) {
|
||||||
|
if (data.aggressor === Schema.Side.SIDE_BUY) {
|
||||||
|
role = TAKER;
|
||||||
|
fees = data?.buyerFee;
|
||||||
|
} else if (data.aggressor === Schema.Side.SIDE_SELL) {
|
||||||
|
role = MAKER;
|
||||||
|
fees = data?.sellerFee;
|
||||||
|
} else {
|
||||||
|
role = '-';
|
||||||
|
fees = !isEmptyFeeObj(data?.buyerFee) ? data.buyerFee : data.sellerFee;
|
||||||
|
}
|
||||||
|
} else if (data?.seller.id === partyId) {
|
||||||
|
if (data.aggressor === Schema.Side.SIDE_SELL) {
|
||||||
|
role = TAKER;
|
||||||
|
fees = data?.sellerFee;
|
||||||
|
} else if (data.aggressor === Schema.Side.SIDE_BUY) {
|
||||||
|
role = MAKER;
|
||||||
|
fees = data?.buyerFee;
|
||||||
|
} else {
|
||||||
|
role = '-';
|
||||||
|
fees = !isEmptyFeeObj(data.sellerFee) ? data.sellerFee : data.buyerFee;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return { role: '-', fees: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFeesBreakdown = (
|
||||||
|
role: Role,
|
||||||
|
fees: TradeFeeFieldsFragment,
|
||||||
|
marketState: Schema.MarketState = Schema.MarketState.STATE_ACTIVE
|
||||||
|
) => {
|
||||||
|
// If market is in auction we assume maker fee is zero
|
||||||
|
const isMarketActive = marketState === Schema.MarketState.STATE_ACTIVE;
|
||||||
|
|
||||||
|
// If role is taker, then these are the fees to be paid
|
||||||
|
let { makerFee, infrastructureFee, liquidityFee } = fees;
|
||||||
|
// If role is taker, then these are the fees discounts to be applied
|
||||||
|
let {
|
||||||
|
makerFeeVolumeDiscount,
|
||||||
|
makerFeeReferralDiscount,
|
||||||
|
infrastructureFeeVolumeDiscount,
|
||||||
|
infrastructureFeeReferralDiscount,
|
||||||
|
liquidityFeeVolumeDiscount,
|
||||||
|
liquidityFeeReferralDiscount,
|
||||||
|
} = fees;
|
||||||
|
|
||||||
|
if (isMarketActive) {
|
||||||
|
if (role === MAKER) {
|
||||||
|
makerFee = new BigNumber(fees.makerFee).times(-1).toString();
|
||||||
|
infrastructureFee = '0';
|
||||||
|
liquidityFee = '0';
|
||||||
|
|
||||||
|
// discounts are also zero or we can leave them undefined
|
||||||
|
infrastructureFeeReferralDiscount =
|
||||||
|
infrastructureFeeReferralDiscount && '0';
|
||||||
|
infrastructureFeeVolumeDiscount = infrastructureFeeVolumeDiscount && '0';
|
||||||
|
liquidityFeeReferralDiscount = liquidityFeeReferralDiscount && '0';
|
||||||
|
liquidityFeeVolumeDiscount = liquidityFeeVolumeDiscount && '0';
|
||||||
|
|
||||||
|
// we leave maker discount fees as they are defined
|
||||||
|
}
|
||||||
|
} 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';
|
||||||
|
|
||||||
|
// discounts are also halved
|
||||||
|
infrastructureFeeReferralDiscount =
|
||||||
|
infrastructureFeeReferralDiscount &&
|
||||||
|
new BigNumber(infrastructureFeeReferralDiscount).dividedBy(2).toString();
|
||||||
|
infrastructureFeeVolumeDiscount =
|
||||||
|
infrastructureFeeVolumeDiscount &&
|
||||||
|
new BigNumber(infrastructureFeeVolumeDiscount).dividedBy(2).toString();
|
||||||
|
liquidityFeeReferralDiscount =
|
||||||
|
liquidityFeeReferralDiscount &&
|
||||||
|
new BigNumber(liquidityFeeReferralDiscount).dividedBy(2).toString();
|
||||||
|
liquidityFeeVolumeDiscount =
|
||||||
|
liquidityFeeVolumeDiscount &&
|
||||||
|
new BigNumber(liquidityFeeVolumeDiscount).dividedBy(2).toString();
|
||||||
|
// maker discount fees should already be zero
|
||||||
|
makerFeeReferralDiscount = makerFeeReferralDiscount && '0';
|
||||||
|
makerFeeVolumeDiscount = makerFeeVolumeDiscount && '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalFee = new BigNumber(infrastructureFee)
|
||||||
|
.plus(makerFee)
|
||||||
|
.plus(liquidityFee)
|
||||||
|
.toString();
|
||||||
|
|
||||||
|
const totalFeeDiscount = new BigNumber(makerFeeVolumeDiscount || '0')
|
||||||
|
.plus(makerFeeReferralDiscount || '0')
|
||||||
|
.plus(infrastructureFeeReferralDiscount || '0')
|
||||||
|
.plus(infrastructureFeeVolumeDiscount || '0')
|
||||||
|
.plus(liquidityFeeReferralDiscount || '0')
|
||||||
|
.plus(liquidityFeeVolumeDiscount || '0')
|
||||||
|
.toString();
|
||||||
|
|
||||||
|
return {
|
||||||
|
infrastructureFee,
|
||||||
|
infrastructureFeeReferralDiscount,
|
||||||
|
infrastructureFeeVolumeDiscount,
|
||||||
|
liquidityFee,
|
||||||
|
liquidityFeeReferralDiscount,
|
||||||
|
liquidityFeeVolumeDiscount,
|
||||||
|
makerFee,
|
||||||
|
makerFeeReferralDiscount,
|
||||||
|
makerFeeVolumeDiscount,
|
||||||
|
totalFee,
|
||||||
|
totalFeeDiscount,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isEmptyFeeObj = (feeObj: Schema.TradeFee) => {
|
||||||
|
if (!feeObj) return true;
|
||||||
|
return (
|
||||||
|
feeObj.liquidityFee === '0' &&
|
||||||
|
feeObj.makerFee === '0' &&
|
||||||
|
feeObj.infrastructureFee === '0'
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -7,7 +7,11 @@ import {
|
|||||||
} from '@vegaprotocol/data-provider';
|
} from '@vegaprotocol/data-provider';
|
||||||
import { type Market } from '@vegaprotocol/markets';
|
import { type Market } from '@vegaprotocol/markets';
|
||||||
import { marketsMapProvider } from '@vegaprotocol/markets';
|
import { marketsMapProvider } from '@vegaprotocol/markets';
|
||||||
import { Cursor, type PageInfo, type Edge } from '@vegaprotocol/data-provider';
|
import {
|
||||||
|
type Cursor,
|
||||||
|
type PageInfo,
|
||||||
|
type Edge,
|
||||||
|
} from '@vegaprotocol/data-provider';
|
||||||
import { OrderStatus } from '@vegaprotocol/types';
|
import { OrderStatus } from '@vegaprotocol/types';
|
||||||
import {
|
import {
|
||||||
OrdersDocument,
|
OrdersDocument,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
} from './__generated__/Erc20Approval';
|
} from './__generated__/Erc20Approval';
|
||||||
import {
|
import {
|
||||||
PendingWithdrawalFragmentDoc,
|
PendingWithdrawalFragmentDoc,
|
||||||
PendingWithdrawalFragment,
|
type PendingWithdrawalFragment,
|
||||||
} from './__generated__/Withdrawal';
|
} from './__generated__/Withdrawal';
|
||||||
|
|
||||||
export const useCompleteWithdraw = () => {
|
export const useCompleteWithdraw = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user