diff --git a/apps/explorer/src/app/components/assets/assets-table.tsx b/apps/explorer/src/app/components/assets/assets-table.tsx index 8519406bdf..55b551c34d 100644 --- a/apps/explorer/src/app/components/assets/assets-table.tsx +++ b/apps/explorer/src/app/components/assets/assets-table.tsx @@ -12,7 +12,7 @@ import { type VegaICellRendererParams } from '@vegaprotocol/datagrid'; import { useRef, useLayoutEffect } from 'react'; import { BREAKPOINT_MD } from '../../config/breakpoints'; 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'; type AssetsTableProps = { diff --git a/apps/explorer/src/app/components/proposals/proposals-table.tsx b/apps/explorer/src/app/components/proposals/proposals-table.tsx index 61ac0718e5..017d120824 100644 --- a/apps/explorer/src/app/components/proposals/proposals-table.tsx +++ b/apps/explorer/src/app/components/proposals/proposals-table.tsx @@ -8,7 +8,7 @@ import { type VegaValueFormatterParams, } from '@vegaprotocol/datagrid'; 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 { getDateTimeFormat } from '@vegaprotocol/utils'; import { t } from '@vegaprotocol/i18n'; diff --git a/apps/trading/client-pages/referrals/referral-statistics.tsx b/apps/trading/client-pages/referrals/referral-statistics.tsx index 5f76f0aef0..0c384b1114 100644 --- a/apps/trading/client-pages/referrals/referral-statistics.tsx +++ b/apps/trading/client-pages/referrals/referral-statistics.tsx @@ -127,7 +127,7 @@ export const useStats = ({ t.discountFactor === discountFactorValue ); 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 const epochsValue = !isNaN(currentEpoch) && refereeInfo?.atEpoch diff --git a/apps/trading/components/fees-container/Fees.graphql b/apps/trading/components/fees-container/Fees.graphql index f4e4f6b44c..339edead61 100644 --- a/apps/trading/components/fees-container/Fees.graphql +++ b/apps/trading/components/fees-container/Fees.graphql @@ -16,18 +16,11 @@ query DiscountPrograms { } } -query Fees( - $partyId: ID! - $volumeDiscountEpochs: Int! - $referralDiscountEpochs: Int! -) { +query Fees($partyId: ID!) { epoch { id } - volumeDiscountStats( - partyId: $partyId - pagination: { last: $volumeDiscountEpochs } - ) { + volumeDiscountStats(partyId: $partyId, pagination: { last: 1 }) { edges { node { atEpoch @@ -59,10 +52,7 @@ query Fees( } } } - referralSetStats( - partyId: $partyId - pagination: { last: $referralDiscountEpochs } - ) { + referralSetStats(partyId: $partyId, pagination: { last: 1 }) { edges { node { atEpoch diff --git a/apps/trading/components/fees-container/__generated__/Fees.ts b/apps/trading/components/fees-container/__generated__/Fees.ts index aeb340c17b..ef67344a73 100644 --- a/apps/trading/components/fees-container/__generated__/Fees.ts +++ b/apps/trading/components/fees-container/__generated__/Fees.ts @@ -10,8 +10,6 @@ export type DiscountProgramsQuery = { __typename?: 'Query', currentReferralProgr export type FeesQueryVariables = Types.Exact<{ partyId: Types.Scalars['ID']; - volumeDiscountEpochs: Types.Scalars['Int']; - referralDiscountEpochs: Types.Scalars['Int']; }>; @@ -65,14 +63,11 @@ export type DiscountProgramsQueryHookResult = ReturnType; export type DiscountProgramsQueryResult = Apollo.QueryResult; export const FeesDocument = gql` - query Fees($partyId: ID!, $volumeDiscountEpochs: Int!, $referralDiscountEpochs: Int!) { + query Fees($partyId: ID!) { epoch { id } - volumeDiscountStats( - partyId: $partyId - pagination: {last: $volumeDiscountEpochs} - ) { + volumeDiscountStats(partyId: $partyId, pagination: {last: 1}) { edges { node { atEpoch @@ -104,7 +99,7 @@ export const FeesDocument = gql` } } } - referralSetStats(partyId: $partyId, pagination: {last: $referralDiscountEpochs}) { + referralSetStats(partyId: $partyId, pagination: {last: 1}) { edges { node { atEpoch @@ -129,8 +124,6 @@ export const FeesDocument = gql` * const { data, loading, error } = useFeesQuery({ * variables: { * partyId: // value for 'partyId' - * volumeDiscountEpochs: // value for 'volumeDiscountEpochs' - * referralDiscountEpochs: // value for 'referralDiscountEpochs' * }, * }); */ diff --git a/apps/trading/components/fees-container/fees-container.tsx b/apps/trading/components/fees-container/fees-container.tsx index 1c199ae395..72186f6cd7 100644 --- a/apps/trading/components/fees-container/fees-container.tsx +++ b/apps/trading/components/fees-container/fees-container.tsx @@ -42,19 +42,19 @@ export const FeesContainer = () => { programData?.currentVolumeDiscountProgram?.windowLength || 1; const referralDiscountWindowLength = programData?.currentReferralProgram?.windowLength || 1; - const { data: feesData, loading: feesLoading } = useFeesQuery({ variables: { partyId: pubKey || '', - volumeDiscountEpochs: volumeDiscountWindowLength, - referralDiscountEpochs: referralDiscountWindowLength, }, - skip: !pubKey || !programData, + skip: !pubKey, }); + const previousEpoch = (Number(feesData?.epoch.id) || 0) - 1; + const { volumeDiscount, volumeTierIndex, volumeInWindow, volumeTiers } = useVolumeStats( - feesData?.volumeDiscountStats, + previousEpoch, + feesData?.volumeDiscountStats.edges?.[0]?.node, programData?.currentVolumeDiscountProgram ); @@ -67,12 +67,12 @@ export const FeesContainer = () => { code, isReferrer, } = useReferralStats( - feesData?.referralSetStats, - feesData?.referralSetReferees, + previousEpoch, + feesData?.referralSetStats.edges?.[0]?.node, + feesData?.referralSetReferees.edges?.[0]?.node, programData?.currentReferralProgram, - feesData?.epoch, - feesData?.referrer, - feesData?.referee + feesData?.referrer.edges?.[0]?.node, + feesData?.referee.edges?.[0]?.node ); const loading = paramsLoading || feesLoading || programLoading; @@ -466,7 +466,7 @@ const VolumeTiers = ({ {Array.from(tiers).map((tier, i) => { - const isUserTier = tiers.length - 1 - tierIndex === i; + const isUserTier = tierIndex === i; return ( @@ -521,7 +521,7 @@ const ReferralTiers = ({ {Array.from(tiers).map((t, i) => { - const isUserTier = tiers.length - 1 - tierIndex === i; + const isUserTier = tierIndex === i; const requiredVolume = Number(t.minimumRunningNotionalTakerVolume); let unlocksIn = null; diff --git a/apps/trading/components/fees-container/use-referral-stats.spec.ts b/apps/trading/components/fees-container/use-referral-stats.spec.ts index e38eb899d3..b1f5ff098a 100644 --- a/apps/trading/components/fees-container/use-referral-stats.spec.ts +++ b/apps/trading/components/fees-container/use-referral-stats.spec.ts @@ -2,46 +2,15 @@ import { renderHook } from '@testing-library/react'; import { useReferralStats } from './use-referral-stats'; describe('useReferralStats', () => { - const setStats = { - edges: [ - { - __typename: 'ReferralSetStatsEdge' as const, - node: { - __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 stat = { + __typename: 'ReferralSetStats' as const, + atEpoch: 9, + discountFactor: '0.01', + referralSetRunningNotionalTakerVolume: '100', }; - const sets = { - edges: [ - { - node: { - atEpoch: 3, - }, - }, - { - node: { - atEpoch: 4, - }, - }, - ], - }; - - const epoch = { - id: '10', + const set = { + atEpoch: 4, }; 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(() => - 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({ - referralDiscount: Number(stats.discountFactor), - referralVolumeInWindow: Number( - stats.referralSetRunningNotionalTakerVolume - ), - referralTierIndex: 1, + referralDiscount: 0, + referralVolumeInWindow: 0, + referralTierIndex: -1, referralTiers: program.benefitTiers, - epochsInSet: Number(epoch.id) - set.atEpoch, + epochsInSet: 0, code: undefined, isReferrer: false, }); }); - it.each([ - { 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(() => - useReferralStats(statsA, setsA, program, epoch) - ); - - expect(result.current.referralTierIndex).toEqual(obj.index); - }); - - it.each([ - { volume: '50', index: -1 }, - { volume: '100', index: 0 }, - { volume: '150', index: 0 }, - { volume: '200', index: 1 }, - { volume: '250', index: 1 }, - { volume: '300', index: 2 }, - { 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, - }, - }, - ], - }; + it('returns formatted data and tiers', () => { 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( + stat.referralSetRunningNotionalTakerVolume + ), + referralTierIndex: 0, + referralTiers: program.benefitTiers, + epochsInSet: stat.atEpoch - set.atEpoch, + code: undefined, + isReferrer: false, + }); }); }); diff --git a/apps/trading/components/fees-container/use-referral-stats.ts b/apps/trading/components/fees-container/use-referral-stats.ts index 94821ed70c..10d7df55e3 100644 --- a/apps/trading/components/fees-container/use-referral-stats.ts +++ b/apps/trading/components/fees-container/use-referral-stats.ts @@ -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 { first } from 'lodash'; - export const useReferralStats = ( - setStats?: FeesQuery['referralSetStats'], - setReferees?: FeesQuery['referralSetReferees'], + previousEpoch?: number, + referralStats?: NonNullable< + FeesQuery['referralSetStats']['edges']['0'] + >['node'], + setReferees?: NonNullable< + FeesQuery['referralSetReferees']['edges']['0'] + >['node'], program?: DiscountProgramsQuery['currentReferralProgram'], - epoch?: FeesQuery['epoch'], - setIfReferrer?: FeesQuery['referrer'], - setIfReferee?: FeesQuery['referee'] + setIfReferrer?: NonNullable['node'], + setIfReferee?: NonNullable['node'] ) => { const referralTiers = program?.benefitTiers || []; - if (!setStats || !setReferees || !program || !epoch) { + if ( + !previousEpoch || + referralStats?.atEpoch !== previousEpoch || + !program || + !setReferees + ) { return { referralDiscount: 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 referralVolumeInWindow = Number( referralStats?.referralSetRunningNotionalTakerVolume || 0 ); - const referralTierIndex = referralStats - ? getReferralBenefitTier( - epochsInSet, - Number(referralStats.referralSetRunningNotionalTakerVolume), - referralTiers - ) - : -1; + const referralTierIndex = referralTiers.findIndex( + (tier) => tier.referralDiscountFactor === referralStats?.discountFactor + ); return { referralDiscount, referralVolumeInWindow, referralTierIndex, referralTiers, - epochsInSet, - code: (setIfReferrerData || setIfRefereeData)?.id, - isReferrer: Boolean(setIfReferrerData), + epochsInSet: referralStats.atEpoch - setReferees.atEpoch, + code: (setIfReferrer || setIfReferee)?.id, + isReferrer: Boolean(setIfReferrer), }; }; diff --git a/apps/trading/components/fees-container/use-volume-stats.spec.ts b/apps/trading/components/fees-container/use-volume-stats.spec.ts index b9c3270547..a3bfb6afdc 100644 --- a/apps/trading/components/fees-container/use-volume-stats.spec.ts +++ b/apps/trading/components/fees-container/use-volume-stats.spec.ts @@ -2,27 +2,11 @@ import { renderHook } from '@testing-library/react'; import { useVolumeStats } from './use-volume-stats'; describe('useReferralStats', () => { - const statsList = { - edges: [ - { - __typename: 'VolumeDiscountStatsEdge' as const, - node: { - __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 stats = { + __typename: 'VolumeDiscountStats' as const, + atEpoch: 10, + discountFactor: '0.05', + runningVolume: '200', }; const program = { @@ -44,7 +28,7 @@ describe('useReferralStats', () => { }; it('returns correct default values', () => { - const { result } = renderHook(() => useVolumeStats()); + const { result } = renderHook(() => useVolumeStats(10)); expect(result.current).toEqual({ volumeDiscount: 0, volumeInWindow: 0, @@ -53,11 +37,18 @@ describe('useReferralStats', () => { }); }); - it('returns formatted data and tiers', () => { - const { result } = renderHook(() => useVolumeStats(statsList, program)); + it('returns default values if no stat is not from previous epoch', () => { + 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 - const stats = statsList.edges[1].node; + it('returns formatted data and tiers', () => { + const { result } = renderHook(() => useVolumeStats(10, stats, program)); expect(result.current).toEqual({ volumeDiscount: Number(stats.discountFactor), @@ -66,30 +57,4 @@ describe('useReferralStats', () => { 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); - }); }); diff --git a/apps/trading/components/fees-container/use-volume-stats.ts b/apps/trading/components/fees-container/use-volume-stats.ts index 39c1b787a4..81eef72000 100644 --- a/apps/trading/components/fees-container/use-volume-stats.ts +++ b/apps/trading/components/fees-container/use-volume-stats.ts @@ -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'; export const useVolumeStats = ( - stats?: FeesQuery['volumeDiscountStats'], + previousEpoch: number, + lastEpochStats?: NonNullable< + FeesQuery['volumeDiscountStats']['edges']['0'] + >['node'], program?: DiscountProgramsQuery['currentVolumeDiscountProgram'] ) => { const volumeTiers = program?.benefitTiers || []; - if (!stats || !program) { + if (!lastEpochStats || lastEpochStats.atEpoch !== previousEpoch || !program) { return { volumeDiscount: 0, 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 volumeInWindow = Number(lastEpochStats?.runningVolume || 0); - const volumeTierIndex = getVolumeTier(volumeInWindow, volumeTiers); + const volumeTierIndex = volumeTiers.findIndex( + (tier) => tier.volumeDiscountFactor === lastEpochStats?.discountFactor + ); return { volumeDiscount, diff --git a/apps/trading/components/fees-container/utils.ts b/apps/trading/components/fees-container/utils.ts index 68b980035f..3a1ae57033 100644 --- a/apps/trading/components/fees-container/utils.ts +++ b/apps/trading/components/fees-container/utils.ts @@ -20,73 +20,6 @@ export const formatPercentage = (num: number) => { 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 * the adjusted fee factor diff --git a/libs/accounts/src/lib/accounts-data-provider.ts b/libs/accounts/src/lib/accounts-data-provider.ts index 6fc72da9d7..01b1109817 100644 --- a/libs/accounts/src/lib/accounts-data-provider.ts +++ b/libs/accounts/src/lib/accounts-data-provider.ts @@ -13,10 +13,10 @@ import { type IterableElement } from 'type-fest'; import { AccountEventsDocument, AccountsDocument, - AccountFieldsFragment, - AccountsQuery, - AccountEventsSubscription, - AccountsQueryVariables, + type AccountFieldsFragment, + type AccountsQuery, + type AccountEventsSubscription, + type AccountsQueryVariables, } from './__generated__/Accounts'; import { type Asset } from '@vegaprotocol/assets'; diff --git a/libs/data-provider/src/generic-data-provider.spec.ts b/libs/data-provider/src/generic-data-provider.spec.ts index c4f349ae86..2d4b02f366 100644 --- a/libs/data-provider/src/generic-data-provider.spec.ts +++ b/libs/data-provider/src/generic-data-provider.spec.ts @@ -25,7 +25,7 @@ import { import { ApolloError } from '@apollo/client'; import type { GraphQLErrors } from '@apollo/client/errors'; 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'; type Item = { diff --git a/libs/environment/src/hooks/mocks/apollo-client.tsx b/libs/environment/src/hooks/mocks/apollo-client.tsx index a3c20b68e5..58c21e7875 100644 --- a/libs/environment/src/hooks/mocks/apollo-client.tsx +++ b/libs/environment/src/hooks/mocks/apollo-client.tsx @@ -5,7 +5,10 @@ import { type NodeCheckTimeUpdateSubscription, } from '../../utils/__generated__/NodeCheck'; import { Networks } from '../../types'; -import { createMockClient, RequestHandlerResponse } from 'mock-apollo-client'; +import { + createMockClient, + type RequestHandlerResponse, +} from 'mock-apollo-client'; export type MockRequestConfig = { hasError?: boolean; diff --git a/libs/fills/src/lib/fills-table.spec.tsx b/libs/fills/src/lib/fills-table.spec.tsx index 8c0d395ab8..81136946b2 100644 --- a/libs/fills/src/lib/fills-table.spec.tsx +++ b/libs/fills/src/lib/fills-table.spec.tsx @@ -4,14 +4,8 @@ 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 { - FeesDiscountBreakdownTooltip, - FillsTable, - getFeesBreakdown, - getTotalFeesDiscounts, -} from './fills-table'; +import { FeesDiscountBreakdownTooltip, FillsTable } from './fills-table'; import { generateFill } from './test-helpers'; -import type { TradeFeeFieldsFragment } from './__generated__/Fills'; const partyId = 'party-id'; const defaultFill: PartialDeep = { @@ -35,6 +29,7 @@ const defaultFill: PartialDeep = { }, createdAt: new Date('2022-02-02T14:00:00').toISOString(), }; + describe('FillsTable', () => { it('correct columns are rendered', async () => { // 7005-FILL-001 @@ -65,7 +60,7 @@ describe('FillsTable', () => { 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({ ...defaultFill, buyer: { @@ -89,7 +84,7 @@ describe('FillsTable', () => { '3.00 BTC', 'Maker', '2.00 BTC', - '0.27 BTC', + '0.09 BTC', getDateTimeFormat().format(new Date(buyerFill.createdAt)), '', // action column ]; @@ -271,96 +266,48 @@ describe('FillsTable', () => { .find((c) => c.getAttribute('col-id') === 'size'); expect(sizeCell).toHaveTextContent('3,000,000,000'); }); -}); -describe('FeesDiscountBreakdownTooltip', () => { - it('shows all discounts', () => { - const data = generateFill({ - ...defaultFill, - buyer: { - id: partyId, - }, - }); - const props = { - data, - partyId, - value: data.market, - } as Parameters['0']; - const { container } = render(); - 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); + describe('FeesDiscountBreakdownTooltip', () => { + it('shows all discounts', () => { + const data = generateFill({ + ...defaultFill, + buyer: { + id: partyId, + }, + }); + const props = { + data, + partyId, + value: data.market, + } as Parameters['0']; + const { container } = render(); + 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); + }); }); }); }); - -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() - ); - }); -}); diff --git a/libs/fills/src/lib/fills-table.tsx b/libs/fills/src/lib/fills-table.tsx index 85e60561f7..ab61323a83 100644 --- a/libs/fills/src/lib/fills-table.tsx +++ b/libs/fills/src/lib/fills-table.tsx @@ -28,20 +28,12 @@ import { import { forwardRef } from 'react'; import BigNumber from 'bignumber.js'; import { type Trade } from './fills-data-provider'; -import { - type FillFieldsFragment, - type TradeFeeFieldsFragment, -} from './__generated__/Fills'; import { FillActionsDropdown } from './fill-actions-dropdown'; import { getAsset } from '@vegaprotocol/markets'; import { useT } from './use-t'; +import { MAKER, TAKER, getFeesBreakdown, getRoleAndFees } from './fills-utils'; -const TAKER = 'Taker'; -const MAKER = 'Maker'; - -export type Role = typeof TAKER | typeof MAKER | '-'; - -export type Props = (AgGridReactProps | AgReactUiProps) & { +type Props = (AgGridReactProps | AgReactUiProps) & { partyId: string; onMarketClick?: (marketId: string, metaKey?: boolean) => void; }; @@ -262,63 +254,13 @@ const formatFeeDiscount = (partyId: string) => { }: VegaValueFormatterParams) => { 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); + const { fees: roleFees, role } = getRoleAndFees({ data, partyId }); + if (!roleFees) return '-'; + const { totalFeeDiscount } = getFeesBreakdown(role, roleFees); + return addDecimalsFormatNumber(totalFeeDiscount, 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 = ({ data, value: market, @@ -331,16 +273,23 @@ const FeesBreakdownTooltip = ({ const asset = getAsset(market); - const { role, fees } = getRoleAndFees({ data, partyId }) ?? {}; + const { role, fees, marketState } = getRoleAndFees({ data, partyId }) ?? {}; if (!fees) return null; const { infrastructureFee, liquidityFee, makerFee, totalFee } = - getFeesBreakdown(role, fees); + getFeesBreakdown(role, fees, marketState); return (
+ {marketState && ( +

+ {t('If the market was {{state}}', { + state: Schema.MarketStateMapping[marketState].toLowerCase(), + })} +

+ )} {role === MAKER && ( <>

{t('The maker will receive the maker fee.')}

@@ -354,7 +303,7 @@ const FeesBreakdownTooltip = ({ {role === TAKER && (

{t('Fees to be paid by the taker.')}

)} - {role === '-' && ( + {(role === '-' || marketState === Schema.MarketState.STATE_SUSPENDED) && (

{t( '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' ? ( <> -

{label}
-
+
{label}
+
{addDecimalsFormatNumber(value, asset.decimals)} {asset.symbol}
@@ -412,15 +361,19 @@ export const FeesDiscountBreakdownTooltip = ({ } const asset = getAsset(data.market); - const { fees } = getRoleAndFees({ data, partyId }) ?? {}; - if (!fees) return null; - + const { + fees: roleFees, + marketState, + role, + } = getRoleAndFees({ data, partyId }) ?? {}; + if (!roleFees) return null; + const fees = getFeesBreakdown(role, roleFees, marketState); return (
-
+
{(fees.infrastructureFeeReferralDiscount || '0') !== '0' || (fees.infrastructureFeeVolumeDiscount || '0') !== '0' ? (
{t('Infrastructure Fee')}
@@ -464,42 +417,14 @@ export const FeesDiscountBreakdownTooltip = ({ label={t('Volume Discount')} asset={asset} /> + +
{t('Total Fee Discount')}
+
); }; - -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, - }; -}; diff --git a/libs/fills/src/lib/fills-utils.spec.ts b/libs/fills/src/lib/fills-utils.spec.ts new file mode 100644 index 0000000000..fd62ebd180 --- /dev/null +++ b/libs/fills/src/lib/fills-utils.spec.ts @@ -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()); + }); +}); diff --git a/libs/fills/src/lib/fills-utils.ts b/libs/fills/src/lib/fills-utils.ts new file mode 100644 index 0000000000..d5fc0b47a7 --- /dev/null +++ b/libs/fills/src/lib/fills-utils.ts @@ -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' + ); +}; diff --git a/libs/orders/src/lib/components/order-data-provider/order-data-provider.ts b/libs/orders/src/lib/components/order-data-provider/order-data-provider.ts index 766211c6c9..4cb2747a05 100644 --- a/libs/orders/src/lib/components/order-data-provider/order-data-provider.ts +++ b/libs/orders/src/lib/components/order-data-provider/order-data-provider.ts @@ -7,7 +7,11 @@ import { } from '@vegaprotocol/data-provider'; import { type Market } 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 { OrdersDocument, diff --git a/libs/withdraws/src/lib/use-complete-withdraw.ts b/libs/withdraws/src/lib/use-complete-withdraw.ts index 35616a3e1b..e5ba930476 100644 --- a/libs/withdraws/src/lib/use-complete-withdraw.ts +++ b/libs/withdraws/src/lib/use-complete-withdraw.ts @@ -14,7 +14,7 @@ import { } from './__generated__/Erc20Approval'; import { PendingWithdrawalFragmentDoc, - PendingWithdrawalFragment, + type PendingWithdrawalFragment, } from './__generated__/Withdrawal'; export const useCompleteWithdraw = () => {