Compare commits

..
45 changed files with 285 additions and 949 deletions
@@ -39,6 +39,7 @@ context('Market page', { tags: '@regression' }, function () {
cy.contains('Test market 1').click();
cy.getByTestId(marketHeaders).should('have.text', 'Test market 1');
cy.validate_element_from_table('Name', 'Test market 1');
cy.validate_element_from_table('Market ID', this.createdMarketId);
cy.validate_element_from_table('Trading Mode', 'Opening auction');
cy.validate_element_from_table('Market Decimal Places', '5');
cy.validate_element_from_table('Position Decimal Places', '5');
@@ -537,10 +537,10 @@ describe(
cy.VegaWalletSubmitProposal(createGovernanceTransferProposalTxBody());
cy.reload();
getProposalFromTitle('Governance transfer proposal').within(() => {
cy.getByTestId(marketProposalType).should('have.text', 'New transfer');
cy.getByTestId(marketProposalType).should('have.text', 'NewTransfer');
cy.getByTestId(viewProposalButton).click();
});
cy.getByTestId(marketProposalType).should('have.text', 'New transfer');
cy.getByTestId(marketProposalType).should('have.text', 'NewTransfer');
cy.getByTestId(governanceTransferToggle).click();
cy.getByTestId('proposal-transfer-details-table').within(() => {
getProposalInformationFromTable('Source Type')
@@ -590,7 +590,7 @@ describe(
);
cy.getByTestId(viewProposalButton).click();
});
cy.getByTestId(marketProposalType).should('have.text', 'Cancel transfer');
cy.getByTestId(marketProposalType).should('have.text', 'CancelTransfer');
getProposalInformationFromTable('Error details')
.invoke('text')
.and('eq', 'Governance transfer invalid transfer id not found');
@@ -18,7 +18,6 @@ import { Routes } from '../../lib/links';
import { useTransactionEventSubscription } from '@vegaprotocol/web3';
import { t } from '@vegaprotocol/i18n';
import { Statistics } from './referral-statistics';
import { useReferralProgram } from './hooks/use-referral-program';
const RELOAD_DELAY = 3000;
@@ -33,7 +32,6 @@ const validateCode = (value: string) => {
};
export const ApplyCodeForm = () => {
const program = useReferralProgram();
const navigate = useNavigate();
const openWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
@@ -239,7 +237,7 @@ export const ApplyCodeForm = () => {
{previewData ? (
<div className="mt-10">
<h2 className="text-2xl mb-5">{t('You are joining')}</h2>
<Statistics data={previewData} program={program} as="referee" />
<Statistics data={previewData} as="referee" />
</div>
) : null}
</>
@@ -1,5 +1,5 @@
query Referees($code: ID!, $aggregationEpochs: Int) {
referralSetReferees(id: $code, aggregationEpochs: $aggregationEpochs) {
query Referees($code: ID!, $aggregationDays: Int) {
referralSetReferees(id: $code, aggregationDays: $aggregationDays) {
edges {
node {
referralSetId
@@ -10,7 +10,6 @@ query ReferralSetStats($code: ID!, $epoch: Int) {
referralSetRunningNotionalTakerVolume
rewardsMultiplier
rewardsFactorMultiplier
referrerTakerVolume
}
}
}
@@ -5,7 +5,7 @@ import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type RefereesQueryVariables = Types.Exact<{
code: Types.Scalars['ID'];
aggregationEpochs?: Types.InputMaybe<Types.Scalars['Int']>;
aggregationDays?: Types.InputMaybe<Types.Scalars['Int']>;
}>;
@@ -13,8 +13,8 @@ export type RefereesQuery = { __typename?: 'Query', referralSetReferees: { __typ
export const RefereesDocument = gql`
query Referees($code: ID!, $aggregationEpochs: Int) {
referralSetReferees(id: $code, aggregationEpochs: $aggregationEpochs) {
query Referees($code: ID!, $aggregationDays: Int) {
referralSetReferees(id: $code, aggregationDays: $aggregationDays) {
edges {
node {
referralSetId
@@ -42,7 +42,7 @@ export const RefereesDocument = gql`
* const { data, loading, error } = useRefereesQuery({
* variables: {
* code: // value for 'code'
* aggregationEpochs: // value for 'aggregationEpochs'
* aggregationDays: // value for 'aggregationDays'
* },
* });
*/
@@ -9,7 +9,7 @@ export type ReferralSetStatsQueryVariables = Types.Exact<{
}>;
export type ReferralSetStatsQuery = { __typename?: 'Query', referralSetStats: { __typename?: 'ReferralSetStatsConnection', edges: Array<{ __typename?: 'ReferralSetStatsEdge', node: { __typename?: 'ReferralSetStats', atEpoch: number, partyId: string, discountFactor: string, rewardFactor: string, epochNotionalTakerVolume: string, referralSetRunningNotionalTakerVolume: string, rewardsMultiplier: string, rewardsFactorMultiplier: string, referrerTakerVolume: string } } | null> } };
export type ReferralSetStatsQuery = { __typename?: 'Query', referralSetStats: { __typename?: 'ReferralSetStatsConnection', edges: Array<{ __typename?: 'ReferralSetStatsEdge', node: { __typename?: 'ReferralSetStats', atEpoch: number, partyId: string, discountFactor: string, rewardFactor: string, epochNotionalTakerVolume: string, referralSetRunningNotionalTakerVolume: string, rewardsMultiplier: string, rewardsFactorMultiplier: string } } | null> } };
export const ReferralSetStatsDocument = gql`
@@ -25,7 +25,6 @@ export const ReferralSetStatsDocument = gql`
referralSetRunningNotionalTakerVolume
rewardsMultiplier
rewardsFactorMultiplier
referrerTakerVolume
}
}
}
@@ -5,14 +5,14 @@ import compact from 'lodash/compact';
import type { ReferralSetsQueryVariables } from './__generated__/ReferralSets';
import { useReferralSetsQuery } from './__generated__/ReferralSets';
export const DEFAULT_AGGREGATION_DAYS = 30;
const DEFAULT_AGGREGATION_DAYS = 30;
export type Role = 'referrer' | 'referee';
type UseReferralArgs = (
| { code: string }
| { pubKey: string | null; role: Role }
) & {
aggregationEpochs?: number;
aggregationDays?: number;
};
const prepareVariables = (
@@ -70,9 +70,9 @@ export const useReferral = (args: UseReferralArgs) => {
} = useRefereesQuery({
variables: {
code: referralSet?.id as string,
aggregationEpochs:
args.aggregationEpochs != null
? args.aggregationEpochs
aggregationDays:
args.aggregationDays != null
? args.aggregationDays
: DEFAULT_AGGREGATION_DAYS,
},
skip: !referralSet?.id,
@@ -6,7 +6,7 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { DEFAULT_AGGREGATION_DAYS, useReferral } from './hooks/use-referral';
import { useReferral } from './hooks/use-referral';
import { CreateCodeContainer } from './create-code-form';
import classNames from 'classnames';
import { Table } from './table';
@@ -32,25 +32,21 @@ import maxBy from 'lodash/maxBy';
export const ReferralStatistics = () => {
const { pubKey } = useVegaWallet();
const program = useReferralProgram();
const { data: referee } = useReferral({
pubKey,
role: 'referee',
aggregationEpochs: program.details?.windowLength,
});
const { data: referrer } = useReferral({
pubKey,
role: 'referrer',
aggregationEpochs: program.details?.windowLength,
});
if (referee?.code) {
return <Statistics data={referee} program={program} as="referee" />;
return <Statistics data={referee} as="referee" />;
}
if (referrer?.code) {
return <Statistics data={referrer} program={program} as="referrer" />;
return <Statistics data={referrer} as="referrer" />;
}
return <CreateCodeContainer />;
@@ -58,16 +54,14 @@ export const ReferralStatistics = () => {
export const Statistics = ({
data,
program,
as,
}: {
data: NonNullable<ReturnType<typeof useReferral>['data']>;
program: ReturnType<typeof useReferralProgram>;
as: 'referrer' | 'referee';
}) => {
const { benefitTiers, details } = program;
const { data: epochData } = useCurrentEpochInfoQuery();
const { stakeAvailable } = useStakeAvailable();
const { benefitTiers } = useReferralProgram();
const { data: statsData } = useReferralSetStatsQuery({
variables: {
code: data.code,
@@ -78,13 +72,6 @@ export const Statistics = ({
const currentEpoch = Number(epochData?.epoch.id);
const compactNumFormat = new Intl.NumberFormat(getUserLocale(), {
minimumFractionDigits: 0,
maximumFractionDigits: 2,
notation: 'compact',
compactDisplay: 'short',
});
const stats =
statsData?.referralSetStats.edges &&
compact(removePaginationWrapper(statsData.referralSetStats.edges));
@@ -100,13 +87,10 @@ export const Statistics = ({
const runningVolumeValue = statsAvailable
? Number(statsAvailable.referralSetRunningNotionalTakerVolume)
: 0;
const referrerVolumeValue = statsAvailable
? Number(statsAvailable.referrerTakerVolume)
: 0;
const multiplier = statsAvailable
? Number(statsAvailable.rewardsMultiplier)
: 1;
const finalCommissionValue = isNaN(multiplier)
const finalCommissionValue = !isNaN(multiplier)
? baseCommissionValue
: multiplier * baseCommissionValue;
@@ -134,13 +118,7 @@ export const Statistics = ({
: 0;
const baseCommissionTile = (
<StatTile
title={t('Base commission rate')}
description={t('(Combined set volume %s over last %s epochs)', [
compactNumFormat.format(runningVolumeValue),
(details?.windowLength || DEFAULT_AGGREGATION_DAYS).toString(),
])}
>
<StatTile title={t('Base commission rate')}>
{baseCommissionValue * 100}%
</StatTile>
);
@@ -156,16 +134,7 @@ export const Statistics = ({
</StatTile>
);
const finalCommissionTile = (
<StatTile
title={t('Final commission rate')}
description={
!isNaN(multiplier)
? `(${baseCommissionValue * 100}% ⨉ ${multiplier} = ${
finalCommissionValue * 100
}%)`
: undefined
}
>
<StatTile title={t('Final commission rate')}>
{finalCommissionValue * 100}%
</StatTile>
);
@@ -174,21 +143,12 @@ export const Statistics = ({
<StatTile title={t('Number of traders')}>{numberOfTradersValue}</StatTile>
);
const codeTile = (
<CodeTile
code={data?.code}
createdAt={getDateFormat().format(new Date(data.createdAt))}
/>
);
const referrerVolumeTile = (
<StatTile
title={t(
'My volume (last %s epochs)',
(details?.windowLength || DEFAULT_AGGREGATION_DAYS).toString()
)}
>
{compactNumFormat.format(referrerVolumeValue)}
const codeTile = <CodeTile code={data?.code} />;
const createdAtTile = (
<StatTile title={t('Created at')}>
<span className="text-3xl">
{getDateFormat().format(new Date(data.createdAt))}
</span>
</StatTile>
);
@@ -197,10 +157,7 @@ export const Statistics = ({
.reduce((all, r) => all.plus(r), new BigNumber(0));
const totalCommissionTile = (
<StatTile
title={t(
'Total commission (last %s epochs)',
(details?.windowLength || DEFAULT_AGGREGATION_DAYS).toString()
)}
title={t('Total commission (last 30 days)')}
description={t('(qUSD)')}
>
{getNumberFormat(0).format(Number(totalCommissionValue))}
@@ -217,13 +174,20 @@ export const Statistics = ({
<div className="grid grid-rows-1 gap-5 grid-cols-1 sm:grid-cols-2 xl:grid-cols-4">
{codeTile}
{referrerVolumeTile}
{createdAtTile}
{numberOfTradersTile}
{totalCommissionTile}
</div>
</>
);
const compactNumFormat = new Intl.NumberFormat(getUserLocale(), {
minimumFractionDigits: 0,
maximumFractionDigits: 2,
notation: 'compact',
compactDisplay: 'short',
});
const currentBenefitTierTile = (
<StatTile title={t('Current tier')}>
{currentBenefitTierValue?.tier || 'None'}
@@ -302,7 +266,7 @@ export const Statistics = ({
{/* Referees (only for referrer view) */}
{as === 'referrer' && data.referees.length > 0 && (
<div className="mt-20 mb-20">
<h2 className="mb-5 text-2xl">{t('Referees')}</h2>
<h2 className="text-2xl mb-5">{t('Referees')}</h2>
<div
className={classNames(
collapsed && [
@@ -328,23 +292,10 @@ export const Statistics = ({
columns={[
{ name: 'party', displayName: t('Trader') },
{ name: 'joined', displayName: t('Date Joined') },
{
name: 'volume',
displayName: t(
'Volume (last %s epochs)',
(
details?.windowLength || DEFAULT_AGGREGATION_DAYS
).toString()
),
},
{ name: 'volume', displayName: t('Volume (last 30 days)') },
{
name: 'commission',
displayName: t(
'Commission earned (last %s epochs)',
(
details?.windowLength || DEFAULT_AGGREGATION_DAYS
).toString()
),
displayName: t('Commission earned (last 30 days)'),
},
]}
data={sortBy(
+1 -12
View File
@@ -109,7 +109,6 @@ export const TiersContainer = () => {
<Loading variant="large" />
) : (
<TiersTable
windowLength={details?.windowLength}
data={benefitTiers.map((bt) => ({
...bt,
tierElement: (
@@ -163,7 +162,6 @@ const StakingTiers = ({
const TiersTable = ({
data,
windowLength,
}: {
data: Array<{
tier: number;
@@ -172,7 +170,6 @@ const TiersTable = ({
discount: string;
volume: string;
}>;
windowLength?: number;
}) => {
return (
<Table
@@ -184,15 +181,7 @@ const TiersTable = ({
tooltip: t('A percentage of commission earned by the referrer'),
},
{ name: 'discount', displayName: t('Referrer trading discount') },
{
name: 'volume',
displayName: t(
'Min. trading volume %s',
windowLength
? t('(last %s epochs)', windowLength.toString())
: undefined
),
},
{ name: 'volume', displayName: t('Min. trading volume') },
{ name: 'epochs', displayName: t('Min. epochs') },
]}
data={data.map((d) => ({
+2 -8
View File
@@ -7,7 +7,6 @@ import {
import classNames from 'classnames';
import type { HTMLAttributes, ReactNode } from 'react';
import { Button } from './buttons';
import { t } from '@vegaprotocol/i18n';
export const Tile = ({
className,
@@ -55,18 +54,13 @@ const FADE_OUT_STYLE = classNames(
export const CodeTile = ({
code,
createdAt,
className,
}: {
code: string;
createdAt?: string;
className?: string;
}) => {
return (
<StatTile
title={t('Your referral code')}
description={createdAt ? t('(Created at: %s)', createdAt) : undefined}
>
<StatTile title="Your referral code">
<div className="flex gap-2 items-center justify-between">
<Tooltip
description={
@@ -88,7 +82,7 @@ export const CodeTile = ({
</Tooltip>
<CopyWithTooltip text={code}>
<Button className="text-sm no-underline !py-0 !px-0 h-fit !bg-transparent">
<span className="sr-only">{t('Copy')}</span>
<span className="sr-only">Copy</span>
<VegaIcon size={24} name={VegaIconNames.COPY} />
</Button>
</CopyWithTooltip>
@@ -7,7 +7,7 @@ import {
NetworkParams,
} from '@vegaprotocol/network-parameters';
import { useMarketList } from '@vegaprotocol/markets';
import { formatNumber, formatNumberRounded } from '@vegaprotocol/utils';
import { formatNumber } from '@vegaprotocol/utils';
import { useDiscountProgramsQuery, useFeesQuery } from './__generated__/Fees';
import { FeeCard } from './fees-card';
import { MarketFees } from './market-fees';
@@ -284,7 +284,7 @@ export const CurrentVolume = ({
return (
<div>
<Stat
value={formatNumberRounded(new BigNumber(windowLengthVolume))}
value={formatNumber(windowLengthVolume)}
text={t('Past %s epochs', epochs.toString())}
/>
{requiredForNextTier > 0 && (
@@ -1,74 +0,0 @@
import BigNumber from 'bignumber.js';
import { getAdjustedFee } from './utils';
describe('getAdjustedFee', () => {
it('simple', () => {
const volumeDiscount = 0.5;
const referralDiscount = 0.5;
const infraFee = 0.1;
const makerFee = 0.1;
const liqFee = 0.1;
const fees = [
new BigNumber(infraFee),
new BigNumber(makerFee),
new BigNumber(liqFee),
];
const discounts = [
new BigNumber(volumeDiscount),
new BigNumber(referralDiscount),
];
// 1 - 0.5 - 0.5
const v = new BigNumber(1).minus(new BigNumber(volumeDiscount));
// 1 - 0.5 = 0.5
const r = new BigNumber(1).minus(new BigNumber(referralDiscount));
// 0.5 * 0.5 = 0.25
// 1 - 0.25 = 0.75
const factor = new BigNumber(1).minus(v.times(r));
// 0.1 + 0.1 + 0.1 = 0.3
const totalFees = fees.reduce((sum, x) => sum.plus(x), new BigNumber(0));
// 0.3 * 0.75 = 0.225
const expected = new BigNumber(totalFees).times(factor).toNumber();
expect(getAdjustedFee(fees, discounts)).toBe(expected);
});
it('combines discount factors multiplicativly', () => {
const volumeDiscount = 0.4;
const referralDiscount = 0.1;
const infraFee = 0.0005;
const makerFee = 0.0002;
const liqFee = 0.01;
const fees = [
new BigNumber(infraFee),
new BigNumber(makerFee),
new BigNumber(liqFee),
];
const discounts = [
new BigNumber(volumeDiscount),
new BigNumber(referralDiscount),
];
// formula for calculating adjusted fees
const v = new BigNumber(1).minus(new BigNumber(volumeDiscount));
const r = new BigNumber(1).minus(new BigNumber(referralDiscount));
const factor = new BigNumber(1).minus(v.times(r));
// summed fees
const totalFees = fees.reduce((sum, x) => sum.plus(x), new BigNumber(0));
const expected = new BigNumber(totalFees).times(factor).toNumber();
expect(getAdjustedFee(fees, discounts)).toBe(expected);
});
});
@@ -88,18 +88,14 @@ export const getReferralBenefitTier = (
/**
* Given a set of fees and a set of discounts return
* the adjusted fee factor
*
* Formula for calculating the adjusted fees
* total_discount_factor = 1 - (1 - volumeDiscount) * (1 - referralDiscount)
*/
export const getAdjustedFee = (fees: BigNumber[], discounts: BigNumber[]) => {
const totalFee = fees.reduce((sum, f) => sum.plus(f), new BigNumber(0));
const combinedFactors = discounts.reduce((acc, d) => {
return acc.times(new BigNumber(1).minus(d));
}, new BigNumber(1));
const totalFactor = new BigNumber(1).minus(combinedFactors);
return totalFee.times(BigNumber.max(0, totalFactor)).toNumber();
const totalDiscount = discounts.reduce(
(sum, d) => sum.plus(d),
new BigNumber(0)
);
return totalFee
.times(BigNumber.max(0, new BigNumber(1).minus(totalDiscount)))
.toNumber();
};
@@ -1,83 +1,31 @@
import { render, screen, within } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { CandlesMenu } from './candles-menu';
import {
useCandlesChartSettingsStore,
DEFAULT_CHART_SETTINGS,
} from './use-candles-chart-settings';
import { Overlay, Study, overlayLabels, studyLabels } from 'pennant';
describe('CandlesMenu', () => {
const openDropdown = async () => {
await userEvent.click(
screen.getByRole('button', {
name: 'Indicators',
})
);
};
beforeEach(() => {
// clear store each time to avoid conditional testing of defaults
useCandlesChartSettingsStore.setState({ overlays: [], studies: [] });
});
it.each(Object.values(Overlay))('can set %s overlay', async (overlay) => {
render(<CandlesMenu />);
await openDropdown();
const menu = within(await screen.findByRole('menu'));
await userEvent.click(menu.getByText(overlayLabels[overlay as Overlay]));
// re-open the dropdown
await openDropdown();
expect(screen.getByText(overlayLabels[overlay as Overlay])).toHaveAttribute(
'data-state',
'checked'
);
});
it.each(Object.values(Study))('can set %s study', async (study) => {
render(<CandlesMenu />);
await openDropdown();
const menu = within(await screen.findByRole('menu'));
await userEvent.click(menu.getByText(studyLabels[study as Study]));
// re-open the dropdown
await openDropdown();
expect(screen.getByText(studyLabels[study as Study])).toHaveAttribute(
'data-state',
'checked'
);
});
it('should render with the correct default studies and overlays', async () => {
useCandlesChartSettingsStore.setState(DEFAULT_CHART_SETTINGS);
it('should render with the correct default studies', async () => {
render(<CandlesMenu />);
await userEvent.click(
screen.getByRole('button', {
name: 'Indicators',
name: 'Studies',
})
);
const menu = within(await screen.findByRole('menu'));
expect(await screen.findByRole('menu')).toBeInTheDocument();
expect(screen.getByText('Volume')).toHaveAttribute('data-state', 'checked');
expect(screen.getByText('MACD')).toHaveAttribute('data-state', 'checked');
});
expect(menu.getByText(studyLabels.volume)).toHaveAttribute(
'data-state',
'checked'
it('should render with the correct default overlays', async () => {
render(<CandlesMenu />);
await userEvent.click(
screen.getByRole('button', {
name: 'Overlays',
})
);
expect(menu.getByText(studyLabels.macd)).toHaveAttribute(
'data-state',
'checked'
);
expect(menu.getByText(overlayLabels.movingAverage)).toHaveAttribute(
expect(await screen.findByRole('menu')).toBeInTheDocument();
expect(screen.getByText('Moving average')).toHaveAttribute(
'data-state',
'checked'
);
+13 -1
View File
@@ -107,7 +107,7 @@ export const CandlesMenu = () => {
trigger={
<TradingDropdownTrigger className={triggerClasses}>
<TradingButton {...triggerButtonProps}>
{t('Indicators')}
{t('Overlays')}
</TradingButton>
</TradingDropdownTrigger>
}
@@ -132,6 +132,18 @@ export const CandlesMenu = () => {
<TradingDropdownItemIndicator />
</TradingDropdownCheckboxItem>
))}
</TradingDropdownContent>
</TradingDropdown>
<TradingDropdown
trigger={
<TradingDropdownTrigger className={triggerClasses}>
<TradingButton {...triggerButtonProps}>
{t('Studies')}
</TradingButton>
</TradingDropdownTrigger>
}
>
<TradingDropdownContent align={contentAlign}>
{Object.values(Study).map((study) => (
<TradingDropdownCheckboxItem
key={study}
@@ -24,7 +24,7 @@ const STUDY_ORDER: Study[] = [
Study.VOLUME,
];
export const DEFAULT_CHART_SETTINGS = {
const DEFAULT_CHART_SETTINGS = {
interval: Interval.I15M,
type: ChartType.CANDLE,
overlays: [Overlay.MOVING_AVERAGE],
+10 -27
View File
@@ -3,12 +3,7 @@ import throttle from 'lodash/throttle';
import isEqualWith from 'lodash/isEqualWith';
import { useApolloClient } from '@apollo/client';
import type { OperationVariables } from '@apollo/client';
import type {
Subscribe,
Load,
UpdateCallback,
PageInfo,
} from './generic-data-provider';
import type { Subscribe, Load, UpdateCallback } from './generic-data-provider';
import { variablesIsEqualCustomizer } from './generic-data-provider';
export interface useDataProviderParams<
@@ -17,23 +12,13 @@ export interface useDataProviderParams<
Variables extends OperationVariables | undefined = undefined
> {
dataProvider: Subscribe<Data, Delta, Variables>;
update?: ({
delta,
data,
pageInfo,
}: {
delta?: Delta;
data: Data | null;
pageInfo: PageInfo | null;
}) => boolean;
update?: ({ delta, data }: { delta?: Delta; data: Data | null }) => boolean;
insert?: ({
insertionData,
data,
pageInfo,
}: {
insertionData?: Data | null;
data: Data | null;
pageInfo: PageInfo | null;
}) => boolean;
variables: Variables;
skipUpdates?: boolean;
@@ -45,7 +30,7 @@ export interface useDataProviderParams<
* @param dataProvider subscribe function created by makeDataProvider
* @param update optional function called on each delta received in subscription, if returns true updated data will be not passed from hook (component handles updates internally)
* @param variables optional
* @returns state: data, loading, pageInfo, error, methods: flush (pass updated data to update function without delta), restart: () => void}};
* @returns state: data, loading, error, methods: flush (pass updated data to update function without delta), restart: () => void}};
*/
export const useDataProvider = <
Data,
@@ -63,7 +48,6 @@ export const useDataProvider = <
const [data, setData] = useState<Data | null>(null);
const [loading, setLoading] = useState<boolean>(!skip);
const [error, setError] = useState<Error | undefined>(undefined);
const [pageInfo, setPageInfo] = useState<PageInfo | null>(null);
const flushRef = useRef<(() => void) | undefined>(undefined);
const reloadRef = useRef<((force?: boolean) => void) | undefined>(undefined);
const loadRef = useRef<Load<Data> | undefined>(undefined);
@@ -109,7 +93,6 @@ export const useDataProvider = <
isInsert,
isUpdate,
loaded,
pageInfo,
} = args;
setError(error);
setLoading(!loaded && loading);
@@ -121,22 +104,21 @@ export const useDataProvider = <
(skipUpdatesRef.current ||
(!skipUpdatesRef.current &&
updateRef.current &&
updateRef.current({ delta, data, pageInfo })))
updateRef.current({ delta, data })))
) {
return;
}
if (
isInsert &&
insertRef.current &&
insertRef.current({ insertionData, data, pageInfo })
insertRef.current({ insertionData, data })
) {
return;
}
}
setData(data);
setPageInfo(pageInfo);
if (!loading && !isUpdate && updateRef.current) {
updateRef.current({ data, pageInfo });
updateRef.current({ data });
}
}, []);
@@ -154,13 +136,15 @@ export const useDataProvider = <
useEffect(() => {
setData(null);
setPageInfo(null);
setError(undefined);
if (updateRef.current) {
updateRef.current({ data: null, pageInfo: null });
updateRef.current({ data: null });
}
if (skip) {
setLoading(false);
if (updateRef.current) {
updateRef.current({ data: null });
}
return;
}
setLoading(true);
@@ -181,7 +165,6 @@ export const useDataProvider = <
}, [client, dataProvider, callback, variables, skip]);
return {
data,
pageInfo,
loading,
error,
flush,
@@ -26,7 +26,11 @@ import {
EST_TOTAL_MARGIN_TOOLTIP_TEXT,
MARGIN_ACCOUNT_TOOLTIP_TEXT,
} from '../../constants';
import { useEstimateFees } from '../../hooks/use-estimate-fees';
import {
sumFees,
sumFeesDiscounts,
useEstimateFees,
} from '../../hooks/use-estimate-fees';
import { KeyValue } from './key-value';
import {
Accordion,
@@ -40,7 +44,6 @@ import {
import classNames from 'classnames';
import BigNumber from 'bignumber.js';
import { FeesBreakdown } from '../fees-breakdown';
import { getTotalDiscountFactor, getDiscountedFee } from '../discounts';
const emptyValue = '-';
@@ -60,49 +63,48 @@ export const DealTicketFeeDetails = ({
const feeEstimate = useEstimateFees(order, isMarketInAuction);
const asset = getAsset(market);
const { decimals: assetDecimals, quantum } = asset;
const totalFees = feeEstimate?.fees && sumFees(feeEstimate?.fees);
const feesDiscounts =
feeEstimate?.fees && sumFeesDiscounts(feeEstimate?.fees);
const totalDiscountFactor = getTotalDiscountFactor(feeEstimate);
const totalDiscountedFeeAmount =
feeEstimate?.totalFeeAmount &&
getDiscountedFee(
feeEstimate.totalFeeAmount,
feeEstimate.referralDiscountFactor,
feeEstimate.volumeDiscountFactor
).discountedFee;
const totalPercentageDiscount =
feesDiscounts &&
totalFees &&
feesDiscounts.total !== '0' &&
totalFees !== '0' &&
new BigNumber(feesDiscounts.total)
.dividedBy(BigNumber.sum(totalFees, feesDiscounts.total))
.times(100);
return (
<KeyValue
label={t('Fees')}
value={
totalDiscountedFeeAmount &&
`~${formatValue(totalDiscountedFeeAmount, assetDecimals)}`
feeEstimate?.totalFeeAmount &&
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`
}
formattedValue={
<>
{totalDiscountFactor && (
{totalPercentageDiscount && (
<Pill size="xxs" intent={Intent.Warning} className="mr-1">
-
{formatNumberPercentage(
new BigNumber(totalDiscountFactor).multipliedBy(100),
2
)}
-{formatNumberPercentage(totalPercentageDiscount, 2)}
</Pill>
)}
{totalDiscountedFeeAmount &&
`~${formatValue(totalDiscountedFeeAmount, assetDecimals, quantum)}`}
{feeEstimate?.totalFeeAmount &&
`~${formatValue(
feeEstimate?.totalFeeAmount,
assetDecimals,
quantum
)}`}
</>
}
labelDescription={
<>
<p className="mb-2">
<span>
{t(
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}. Fees estimated are "taker" fees and will only be payable if the order trades aggressively. Rebate equal to the maker portion will be paid to the trader if the order trades passively.`
)}
</p>
</span>
<FeesBreakdown
totalFeeAmount={feeEstimate?.totalFeeAmount}
referralDiscountFactor={feeEstimate?.referralDiscountFactor}
volumeDiscountFactor={feeEstimate?.volumeDiscountFactor}
fees={feeEstimate?.fees}
feeFactors={market.fees.factors}
symbol={assetSymbol}
@@ -109,7 +109,7 @@ describe('DealTicket', () => {
variables: {
partyId: 'pubKey',
filter: { liveOnly: true },
pagination: { first: 1000 },
pagination: { first: 5000 },
},
},
result: {
@@ -1,66 +0,0 @@
import { getDiscountedFee, getTotalDiscountFactor } from './discounts';
describe('getDiscountedFee', () => {
it('calculates values if volumeDiscount or referralDiscount is undefined', () => {
expect(getDiscountedFee('100')).toEqual({
discountedFee: '100',
volumeDiscount: '0',
referralDiscount: '0',
});
expect(getDiscountedFee('100', undefined, '0.1')).toEqual({
discountedFee: '90',
volumeDiscount: '10',
referralDiscount: '0',
});
expect(getDiscountedFee('100', '0.1', undefined)).toEqual({
discountedFee: '90',
volumeDiscount: '0',
referralDiscount: '10',
});
});
it('calculates values using volumeDiscount or referralDiscount', () => {
expect(getDiscountedFee('', '0.1', '0.2')).toEqual({
discountedFee: '',
volumeDiscount: '0',
referralDiscount: '0',
});
});
});
describe('getTotalDiscountFactor', () => {
it('returns 0 if discounts are 0', () => {
expect(
getTotalDiscountFactor({
volumeDiscountFactor: '0',
referralDiscountFactor: '0',
})
).toEqual(0);
});
it('returns volumeDiscountFactor if referralDiscountFactor is 0', () => {
expect(
getTotalDiscountFactor({
volumeDiscountFactor: '0.1',
referralDiscountFactor: '0',
})
).toEqual(0.1);
});
it('returns referralDiscountFactor if volumeDiscountFactor is 0', () => {
expect(
getTotalDiscountFactor({
volumeDiscountFactor: '0',
referralDiscountFactor: '0.1',
})
).toEqual(0.1);
});
it('calculates discount using referralDiscountFactor and volumeDiscountFactor', () => {
expect(
getTotalDiscountFactor({
volumeDiscountFactor: '0.2',
referralDiscountFactor: '0.1',
})
).toBeCloseTo(0.28);
});
});
@@ -1,54 +0,0 @@
import BigNumber from 'bignumber.js';
export const getDiscountedFee = (
feeAmount: string,
referralDiscountFactor?: string,
volumeDiscountFactor?: string
) => {
if (
((!referralDiscountFactor || referralDiscountFactor === '0') &&
(!volumeDiscountFactor || volumeDiscountFactor === '0')) ||
!feeAmount ||
feeAmount === '0'
) {
return {
discountedFee: feeAmount,
volumeDiscount: '0',
referralDiscount: '0',
};
}
const referralDiscount = new BigNumber(referralDiscountFactor || '0')
.multipliedBy(feeAmount)
.toFixed(0, BigNumber.ROUND_FLOOR);
const volumeDiscount = new BigNumber(volumeDiscountFactor || '0')
.multipliedBy((BigInt(feeAmount) - BigInt(referralDiscount)).toString())
.toFixed(0, BigNumber.ROUND_FLOOR);
const discountedFee = (
BigInt(feeAmount || '0') -
BigInt(referralDiscount) -
BigInt(volumeDiscount)
).toString();
return {
referralDiscount,
volumeDiscount,
discountedFee,
};
};
export const getTotalDiscountFactor = (feeEstimate?: {
volumeDiscountFactor?: string;
referralDiscountFactor?: string;
}) => {
if (!feeEstimate) {
return 0;
}
const volumeFactor = Number(feeEstimate?.volumeDiscountFactor) || 0;
const referralFactor = Number(feeEstimate?.referralDiscountFactor) || 0;
if (!volumeFactor) {
return referralFactor;
}
if (!referralFactor) {
return volumeFactor;
}
return 1 - (1 - volumeFactor) * (1 - referralFactor);
};
@@ -6,7 +6,7 @@ import {
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import BigNumber from 'bignumber.js';
import { getDiscountedFee } from '../discounts';
import { sumFees, sumFeesDiscounts } from '../../hooks';
const formatValue = (
value: string | number | null | undefined,
@@ -24,7 +24,7 @@ const FeesBreakdownItem = ({
decimals,
}: {
label: string;
factor?: string;
factor?: BigNumber;
value: string;
symbol?: string;
decimals: number;
@@ -43,86 +43,76 @@ const FeesBreakdownItem = ({
);
export const FeesBreakdown = ({
totalFeeAmount,
fees,
feeFactors,
symbol,
decimals,
referralDiscountFactor,
volumeDiscountFactor,
}: {
totalFeeAmount?: string;
fees?: TradeFee;
feeFactors?: FeeFactors;
symbol?: string;
decimals: number;
referralDiscountFactor?: string;
volumeDiscountFactor?: string;
}) => {
if (!fees || !totalFeeAmount || totalFeeAmount === '0') return null;
const { discountedFee: discountedInfrastructureFee } = getDiscountedFee(
fees.infrastructureFee,
referralDiscountFactor,
volumeDiscountFactor
);
const { discountedFee: discountedLiquidityFee } = getDiscountedFee(
fees.liquidityFee,
referralDiscountFactor,
volumeDiscountFactor
);
const { discountedFee: discountedMakerFee } = getDiscountedFee(
fees.makerFee,
referralDiscountFactor,
volumeDiscountFactor
);
const { volumeDiscount, referralDiscount } = getDiscountedFee(
totalFeeAmount,
referralDiscountFactor,
volumeDiscountFactor
);
if (!fees) return null;
const totalFees = sumFees(fees);
const {
total: totalDiscount,
referral: referralDiscount,
volume: volumeDiscount,
} = sumFeesDiscounts(fees);
if (totalFees === '0') return null;
return (
<dl className="grid grid-cols-6">
<FeesBreakdownItem
label={t('Infrastructure fee')}
factor={feeFactors?.infrastructureFee}
value={discountedInfrastructureFee}
factor={
feeFactors?.infrastructureFee
? new BigNumber(feeFactors?.infrastructureFee)
: undefined
}
value={fees.infrastructureFee}
symbol={symbol}
decimals={decimals}
/>
<FeesBreakdownItem
label={t('Liquidity fee')}
factor={feeFactors?.liquidityFee}
value={discountedLiquidityFee}
factor={
feeFactors?.liquidityFee
? new BigNumber(feeFactors?.liquidityFee)
: undefined
}
value={fees.liquidityFee}
symbol={symbol}
decimals={decimals}
/>
<FeesBreakdownItem
label={t('Maker fee')}
factor={feeFactors?.makerFee}
value={discountedMakerFee}
factor={
feeFactors?.makerFee ? new BigNumber(feeFactors?.makerFee) : undefined
}
value={fees.makerFee}
symbol={symbol}
decimals={decimals}
/>
{volumeDiscountFactor && volumeDiscount !== '0' && (
{volumeDiscount && volumeDiscount !== '0' && (
<FeesBreakdownItem
label={t('Volume discount')}
factor={volumeDiscountFactor}
factor={new BigNumber(volumeDiscount).dividedBy(
BigNumber.sum(totalFees, totalDiscount)
)}
value={volumeDiscount}
symbol={symbol}
decimals={decimals}
/>
)}
{referralDiscountFactor && referralDiscount !== '0' && (
{referralDiscount && referralDiscount !== '0' && (
<FeesBreakdownItem
label={t('Referral discount')}
factor={referralDiscountFactor}
factor={new BigNumber(referralDiscount).dividedBy(
BigNumber.sum(totalFees, totalDiscount)
)}
value={referralDiscount}
symbol={symbol}
decimals={decimals}
@@ -130,8 +120,8 @@ export const FeesBreakdown = ({
)}
<FeesBreakdownItem
label={t('Total fees')}
factor={feeFactors ? sumFeesFactors(feeFactors)?.toString() : undefined}
value={totalFeeAmount}
factor={feeFactors ? sumFeesFactors(feeFactors) : undefined}
value={totalFees}
symbol={symbol}
decimals={decimals}
/>
@@ -31,25 +31,4 @@ query EstimateFees(
}
totalFeeAmount
}
epoch {
id
}
volumeDiscountStats(partyId: $partyId, pagination: { last: 1 }) {
edges {
node {
atEpoch
discountFactor
runningVolume
}
}
}
referralSetStats(partyId: $partyId, pagination: { last: 1 }) {
edges {
node {
atEpoch
discountFactor
referralSetRunningNotionalTakerVolume
}
}
}
}
+1 -22
View File
@@ -15,7 +15,7 @@ export type EstimateFeesQueryVariables = Types.Exact<{
}>;
export type EstimateFeesQuery = { __typename?: 'Query', estimateFees: { __typename?: 'FeeEstimate', totalFeeAmount: string, fees: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null } }, epoch: { __typename?: 'Epoch', id: string }, volumeDiscountStats: { __typename?: 'VolumeDiscountStatsConnection', edges: Array<{ __typename?: 'VolumeDiscountStatsEdge', node: { __typename?: 'VolumeDiscountStats', atEpoch: number, discountFactor: string, runningVolume: string } } | null> }, referralSetStats: { __typename?: 'ReferralSetStatsConnection', edges: Array<{ __typename?: 'ReferralSetStatsEdge', node: { __typename?: 'ReferralSetStats', atEpoch: number, discountFactor: string, referralSetRunningNotionalTakerVolume: string } } | null> } };
export type EstimateFeesQuery = { __typename?: 'Query', estimateFees: { __typename?: 'FeeEstimate', totalFeeAmount: string, fees: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null } } };
export const EstimateFeesDocument = gql`
@@ -43,27 +43,6 @@ export const EstimateFeesDocument = gql`
}
totalFeeAmount
}
epoch {
id
}
volumeDiscountStats(partyId: $partyId, pagination: {last: 1}) {
edges {
node {
atEpoch
discountFactor
runningVolume
}
}
}
referralSetStats(partyId: $partyId, pagination: {last: 1}) {
edges {
node {
atEpoch
discountFactor
referralSetRunningNotionalTakerVolume
}
}
}
}
`;
@@ -6,15 +6,6 @@ export const estimateFeesQuery = (
override?: PartialDeep<EstimateFeesQuery>
): EstimateFeesQuery => {
const defaultResult: EstimateFeesQuery = {
epoch: {
id: '1',
},
referralSetStats: {
edges: [],
},
volumeDiscountStats: {
edges: [],
},
estimateFees: {
__typename: 'FeeEstimate',
totalFeeAmount: '0.0006',
@@ -5,31 +5,6 @@ import { Side, OrderTimeInForce, OrderType } from '@vegaprotocol/types';
import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
const data: EstimateFeesQuery = {
epoch: {
id: '2',
},
volumeDiscountStats: {
edges: [
{
node: {
atEpoch: 1,
discountFactor: '0.1',
runningVolume: '100',
},
},
],
},
referralSetStats: {
edges: [
{
node: {
atEpoch: 1,
discountFactor: '0.2',
referralSetRunningNotionalTakerVolume: '100',
},
},
],
},
estimateFees: {
totalFeeAmount: '120',
fees: {
@@ -79,8 +54,6 @@ describe('useEstimateFees', () => {
liquidityFee: '0',
makerFee: '0',
},
referralDiscountFactor: '0',
volumeDiscountFactor: '0',
});
expect(mockUseEstimateFeesQuery.mock.lastCall?.[0].skip).toBeTruthy();
});
@@ -112,46 +85,6 @@ describe('useEstimateFees', () => {
makerFeeReferralDiscount: '5',
makerFeeVolumeDiscount: '6',
},
referralDiscountFactor: '0',
volumeDiscountFactor: '0',
});
});
it('returns 0 discounts if discount stats are not at the current epoch', () => {
const { result } = renderHook(() =>
useEstimateFees(
{
marketId: 'marketId',
side: Side.SIDE_BUY,
size: '1',
price: '1',
timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK,
type: OrderType.TYPE_LIMIT,
},
true
)
);
expect(result.current?.referralDiscountFactor).toEqual('0');
expect(result.current?.volumeDiscountFactor).toEqual('0');
});
it('returns discounts', () => {
data.epoch.id = '1';
const { result } = renderHook(() =>
useEstimateFees(
{
marketId: 'marketId',
side: Side.SIDE_BUY,
size: '1',
price: '1',
timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK,
type: OrderType.TYPE_LIMIT,
},
true
)
);
expect(result.current?.referralDiscountFactor).toEqual('0.2');
expect(result.current?.volumeDiscountFactor).toEqual('0.1');
});
});
@@ -5,22 +5,39 @@ import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
import { useEstimateFeesQuery } from './__generated__/EstimateOrder';
const divideByTwo = (n: string) => (BigInt(n) / BigInt(2)).toString();
export const sumFeesDiscounts = (
fees: EstimateFeesQuery['estimateFees']['fees']
) => {
const volume = (
BigInt(fees.makerFeeVolumeDiscount || '0') +
BigInt(fees.infrastructureFeeVolumeDiscount || '0') +
BigInt(fees.liquidityFeeVolumeDiscount || '0')
).toString();
const referral = (
BigInt(fees.makerFeeReferralDiscount || '0') +
BigInt(fees.infrastructureFeeReferralDiscount || '0') +
BigInt(fees.liquidityFeeReferralDiscount || '0')
).toString();
return {
volume,
referral,
total: (BigInt(volume) + BigInt(referral)).toString(),
};
};
export const sumFees = (fees: EstimateFeesQuery['estimateFees']['fees']) =>
(
BigInt(fees.makerFee || '0') +
BigInt(fees.infrastructureFee || '0') +
BigInt(fees.liquidityFee || '0')
).toString();
export const useEstimateFees = (
order?: OrderSubmissionBody['orderSubmission'],
isMarketInAuction?: boolean
):
| (EstimateFeesQuery['estimateFees'] & {
referralDiscountFactor: string;
volumeDiscountFactor: string;
})
| undefined => {
): EstimateFeesQuery['estimateFees'] | undefined => {
const { pubKey } = useVegaWallet();
const {
data: currentData,
previousData,
loading,
} = useEstimateFeesQuery({
const { data } = useEstimateFeesQuery({
variables: order && {
marketId: order.marketId,
partyId: pubKey || '',
@@ -33,21 +50,8 @@ export const useEstimateFees = (
fetchPolicy: 'no-cache',
skip: !pubKey || !order?.size || !order?.price || order.postOnly,
});
const data = loading ? currentData || previousData : currentData;
const volumeDiscountFactor =
(data?.volumeDiscountStats.edges[0]?.node.atEpoch.toString() ===
data?.epoch.id &&
data?.volumeDiscountStats.edges[0]?.node.discountFactor) ||
'0';
const referralDiscountFactor =
(data?.referralSetStats.edges[0]?.node.atEpoch.toString() ===
data?.epoch.id &&
data?.referralSetStats.edges[0]?.node.discountFactor) ||
'0';
if (order?.postOnly) {
return {
volumeDiscountFactor,
referralDiscountFactor,
totalFeeAmount: '0',
fees: {
infrastructureFee: '0',
@@ -56,13 +60,8 @@ export const useEstimateFees = (
},
};
}
if (!data?.estimateFees) {
return undefined;
}
return isMarketInAuction
return isMarketInAuction && data?.estimateFees
? {
volumeDiscountFactor,
referralDiscountFactor,
totalFeeAmount: divideByTwo(data.estimateFees.totalFeeAmount),
fees: {
infrastructureFee: divideByTwo(
@@ -92,9 +91,5 @@ export const useEstimateFees = (
divideByTwo(data.estimateFees.fees.makerFeeVolumeDiscount),
},
}
: {
volumeDiscountFactor,
referralDiscountFactor,
...data.estimateFees,
};
: data?.estimateFees;
};
+2 -6
View File
@@ -42,12 +42,8 @@ fragment FillEdge on TradeEdge {
cursor
}
query Fills(
$filter: TradesFilter
$pagination: Pagination
$dateRange: DateRange
) {
trades(filter: $filter, dateRange: $dateRange, pagination: $pagination) {
query Fills($filter: TradesFilter, $pagination: Pagination) {
trades(filter: $filter, pagination: $pagination) {
edges {
...FillEdge
}
+2 -4
View File
@@ -12,7 +12,6 @@ export type FillEdgeFragment = { __typename?: 'TradeEdge', cursor: string, node:
export type FillsQueryVariables = Types.Exact<{
filter?: Types.InputMaybe<Types.TradesFilter>;
pagination?: Types.InputMaybe<Types.Pagination>;
dateRange?: Types.InputMaybe<Types.DateRange>;
}>;
@@ -96,8 +95,8 @@ export const FillUpdateFieldsFragmentDoc = gql`
}
${TradeFeeFieldsFragmentDoc}`;
export const FillsDocument = gql`
query Fills($filter: TradesFilter, $pagination: Pagination, $dateRange: DateRange) {
trades(filter: $filter, dateRange: $dateRange, pagination: $pagination) {
query Fills($filter: TradesFilter, $pagination: Pagination) {
trades(filter: $filter, pagination: $pagination) {
edges {
...FillEdge
}
@@ -125,7 +124,6 @@ export const FillsDocument = gql`
* variables: {
* filter: // value for 'filter'
* pagination: // value for 'pagination'
* dateRange: // value for 'dateRange'
* },
* });
*/
+18 -53
View File
@@ -1,12 +1,11 @@
import type { AgGridReact } from 'ag-grid-react';
import { useCallback, useRef, useState } from 'react';
import { useRef } from 'react';
import { t } from '@vegaprotocol/i18n';
import { FillsTable } from './fills-table';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/data-provider';
import type * as Schema from '@vegaprotocol/types';
import { fillsWithMarketProvider } from './fills-data-provider';
import { TradingButton as Button } from '@vegaprotocol/ui-toolkit';
interface FillsManagerProps {
partyId: string;
@@ -23,60 +22,26 @@ export const FillsManager = ({
const filter: Schema.TradesFilter | Schema.TradesSubscriptionFilter = {
partyIds: [partyId],
};
const [hasDisplayedRow, setHasDisplayedRow] = useState<boolean | undefined>(
undefined
);
const { onFilterChanged, ...props } = gridProps || {};
const onRowDataUpdated = useCallback(
({ api }: { api: AgGridReact['api'] }) => {
setHasDisplayedRow(!!api.getDisplayedRowCount());
},
[]
);
const { data, error, load, pageInfo } = useDataProvider({
const { data, error } = useDataProvider({
dataProvider: fillsWithMarketProvider,
update: ({ data }) => {
if (data?.length && gridRef.current?.api) {
gridRef.current?.api.setRowData(data);
return true;
}
return false;
},
variables: { filter },
});
return (
<div className="flex flex-col h-full">
<FillsTable
ref={gridRef}
rowData={data}
onFilterChanged={(event) => {
onRowDataUpdated(event);
onFilterChanged(event);
}}
onRowDataUpdated={onRowDataUpdated}
partyId={partyId}
onMarketClick={onMarketClick}
overlayNoRowsTemplate={error ? error.message : t('No fills')}
{...props}
/>
<div className="flex justify-between border-t border-default p-1 items-center">
<div className="text-xs">
{t(
'Depending on data node retention you may not be able see the "full" history'
)}
</div>
<div className="flex text-xs items-center">
{data?.length && !pageInfo?.hasNextPage
? t('all %s items loaded', [data.length.toString()])
: t('%s items loaded', [
data?.length ? data.length.toString() : ' ',
])}
{pageInfo?.hasNextPage ? (
<Button size="extra-small" className="ml-1" onClick={() => load()}>
{t('Load more')}
</Button>
) : null}
</div>
{data?.length && hasDisplayedRow === false ? (
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-xs">
{t('No fills matching selected filters')}
</div>
) : null}
</div>
</div>
return (
<FillsTable
ref={gridRef}
rowData={data}
partyId={partyId}
onMarketClick={onMarketClick}
overlayNoRowsTemplate={error ? error.message : t('No fills')}
{...gridProps}
/>
);
};
-2
View File
@@ -20,7 +20,6 @@ import {
negativeClassNames,
MarketNameCell,
COL_DEFS,
DateRangeFilter,
} from '@vegaprotocol/datagrid';
import type {
VegaValueFormatterParams,
@@ -121,7 +120,6 @@ export const FillsTable = forwardRef<AgGridReact, Props>(
},
{
headerName: t('Date'),
filter: DateRangeFilter,
field: 'createdAt',
valueFormatter: ({
value,
@@ -43,7 +43,7 @@ export const fundingPaymentsProvider = makeDataProvider<
pagination: {
getPageInfo,
append,
first: 1000,
first: 100,
},
});
@@ -1,11 +1,10 @@
import type { AgGridReact } from 'ag-grid-react';
import { useCallback, useRef, useState } from 'react';
import { useRef } from 'react';
import { t } from '@vegaprotocol/i18n';
import { FundingPaymentsTable } from './funding-payments-table';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { fundingPaymentsWithMarketProvider } from './funding-payments-data-provider';
import { TradingButton as Button } from '@vegaprotocol/ui-toolkit';
interface FundingPaymentsManagerProps {
partyId: string;
@@ -21,17 +20,7 @@ export const FundingPaymentsManager = ({
gridProps,
}: FundingPaymentsManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const [hasDisplayedRow, setHasDisplayedRow] = useState<boolean | undefined>(
undefined
);
const { onFilterChanged, ...props } = gridProps || {};
const onRowDataUpdated = useCallback(
({ api }: { api: AgGridReact['api'] }) => {
setHasDisplayedRow(!!api.getDisplayedRowCount());
},
[]
);
const { data, error, load, pageInfo } = useDataProvider({
const { data, error } = useDataProvider({
dataProvider: fundingPaymentsWithMarketProvider,
update: ({ data }) => {
if (data?.length && gridRef.current?.api) {
@@ -44,43 +33,12 @@ export const FundingPaymentsManager = ({
});
return (
<div className="flex flex-col h-full">
<FundingPaymentsTable
ref={gridRef}
rowData={data}
onMarketClick={onMarketClick}
onFilterChanged={(event) => {
onRowDataUpdated(event);
onFilterChanged(event);
}}
onRowDataUpdated={onRowDataUpdated}
overlayNoRowsTemplate={error ? error.message : t('No funding payments')}
{...props}
/>
<div className="flex justify-between border-t border-default p-1 items-center">
<div className="text-xs">
{t(
'Depending on data node retention you may not be able see the "full" history'
)}
</div>
<div className="flex text-xs items-center">
{data?.length && !pageInfo?.hasNextPage
? t('all %s items loaded', [data.length.toString()])
: t('%s items loaded', [
data?.length ? data.length.toString() : ' ',
])}
{pageInfo?.hasNextPage ? (
<Button size="extra-small" className="ml-1" onClick={() => load()}>
{t('Load more')}
</Button>
) : null}
</div>
{data?.length && hasDisplayedRow === false ? (
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-xs">
{t('No funding payments matching selected filters')}
</div>
) : null}
</div>
</div>
<FundingPaymentsTable
ref={gridRef}
rowData={data}
onMarketClick={onMarketClick}
overlayNoRowsTemplate={error ? error.message : t('No funding payments')}
{...gridProps}
/>
);
};
@@ -3,7 +3,6 @@ import type { Market, MarketMaybeWithDataAndCandles } from './markets-provider';
import {
calcTradedFactor,
filterAndSortMarkets,
sumFeesFactors,
totalFeesFactorsPercentage,
} from './market-utils';
const { MarketState, MarketTradingMode } = Schema;
@@ -133,15 +132,3 @@ describe('calcTradedFactor', () => {
expect(fa > fb).toBeTruthy();
});
});
describe('sumFeesFactors', () => {
it('does not result in flop errors', () => {
expect(
sumFeesFactors({
makerFee: '0.1',
infrastructureFee: '0.2',
liquidityFee: '0.3',
})
).toEqual(0.6);
});
});
+6 -9
View File
@@ -50,19 +50,16 @@ export const getQuoteName = (market: Partial<Market>) => {
};
export const sumFeesFactors = (fees: Market['fees']['factors']) => {
if (!fees) return;
return new BigNumber(fees.makerFee)
.plus(fees.liquidityFee)
.plus(fees.infrastructureFee)
.toNumber();
return fees
? new BigNumber(fees.makerFee)
.plus(fees.liquidityFee)
.plus(fees.infrastructureFee)
: undefined;
};
export const totalFeesFactorsPercentage = (fees: Market['fees']['factors']) => {
const total = fees && sumFeesFactors(fees);
return total
? formatNumberPercentage(new BigNumber(total).times(100))
: undefined;
return total ? formatNumberPercentage(total.times(100)) : undefined;
};
export const filterAndSortMarkets = (markets: MarketMaybeWithData[]) => {
@@ -166,7 +166,7 @@ export const ordersProvider = makeDataProvider<
pagination: {
getPageInfo,
append,
first: 1000,
first: 5000,
},
resetDelay: 1000,
additionalContext: { isEnlargedTimeout: true },
@@ -27,7 +27,6 @@ describe('OrderListManager', () => {
flush: jest.fn(),
reload: jest.fn(),
load: jest.fn(),
pageInfo: null,
});
render(generateJsx());
@@ -46,7 +45,6 @@ describe('OrderListManager', () => {
flush: jest.fn(),
reload: jest.fn(),
load: jest.fn(),
pageInfo: null,
});
render(generateJsx());
@@ -64,7 +62,6 @@ describe('OrderListManager', () => {
flush: jest.fn(),
reload: jest.fn(),
load: jest.fn(),
pageInfo: null,
});
render(
@@ -1,6 +1,7 @@
import { t } from '@vegaprotocol/i18n';
import { useCallback, useRef, useState, useEffect } from 'react';
import type { AgGridReact } from 'ag-grid-react';
import type { FilterChangedEvent } from 'ag-grid-community';
import { OrderListTable } from '../order-list';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/data-provider';
@@ -11,7 +12,7 @@ import type { OrderTxUpdateFieldsFragment } from '@vegaprotocol/web3';
import { OrderEditDialog } from '../order-list/order-edit-dialog';
import type { Order } from '../order-data-provider';
import { OrderViewDialog } from '../order-list/order-view-dialog';
import { Splash, TradingButton as Button } from '@vegaprotocol/ui-toolkit';
import { Splash } from '@vegaprotocol/ui-toolkit';
export enum Filter {
'Open' = 'Open',
@@ -47,11 +48,33 @@ export const OrderListManager = ({
? { partyId, filter: { liveOnly: true } }
: { partyId };
const { data, error, pageInfo, load } = useDataProvider({
const { data, error } = useDataProvider({
dataProvider: ordersWithMarketProvider,
variables,
update: ({ data }) => {
if (data && gridRef.current?.api) {
gridRef.current.api.setRowData(data);
return true;
}
return false;
},
});
const onFilterChanged = useCallback(
(event: FilterChangedEvent) => {
gridProps?.onFilterChanged?.(event);
if (event.api) {
const isEmpty = event.api.getDisplayedRowCount() === 0;
if (isEmpty) {
event.api.showNoRowsOverlay();
} else {
event.api.hideOverlay();
}
}
},
[gridProps]
);
useEffect(() => {
if (!data || !data.length) {
gridRef.current?.api?.showNoRowsOverlay();
@@ -73,17 +96,6 @@ export const OrderListManager = ({
[create]
);
const [hasDisplayedRow, setHasDisplayedRow] = useState<boolean | undefined>(
undefined
);
const { onFilterChanged, ...props } = gridProps || {};
const onRowDataUpdated = useCallback(
({ api }: { api: AgGridReact['api'] }) => {
setHasDisplayedRow(!!api.getDisplayedRowCount());
},
[]
);
if (error) {
return <Splash>{t(`Something went wrong: ${error.message}`)}</Splash>;
}
@@ -91,60 +103,20 @@ export const OrderListManager = ({
return (
<>
<div className="h-full relative">
<div className="flex flex-col h-full">
<OrderListTable
rowData={data}
ref={gridRef}
filter={filter}
onCancel={cancel}
onEdit={setEditOrder}
onView={setViewOrder}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
onFilterChanged={(event) => {
onRowDataUpdated(event);
if (onFilterChanged) {
onFilterChanged(event);
}
}}
onRowDataUpdated={onRowDataUpdated}
isReadOnly={isReadOnly}
overlayNoRowsTemplate={noRowsMessage || t('No orders')}
{...props}
/>
<div className="flex justify-between border-t border-default p-1 items-center">
<div className="text-xs">
{variables.filter?.liveOnly
? null
: t(
'Depending on data node retention you may not be able see the "full" history'
)}
</div>
{data ? (
<div className="flex text-xs items-center">
{data?.length && !pageInfo?.hasNextPage
? t('all %s items loaded', [data.length.toString()])
: t('%s items loaded', [
data?.length ? data.length.toString() : ' ',
])}
{pageInfo?.hasNextPage ? (
<Button
size="extra-small"
className="ml-1"
onClick={() => load()}
>
{t('Load more')}
</Button>
) : null}
</div>
) : null}
{data?.length && hasDisplayedRow === false ? (
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-xs">
{t('No orders matching selected filters')}
</div>
) : null}
</div>
</div>
<OrderListTable
rowData={data}
ref={gridRef}
filter={filter}
onCancel={cancel}
onEdit={setEditOrder}
onView={setViewOrder}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
isReadOnly={isReadOnly}
overlayNoRowsTemplate={noRowsMessage || t('No orders')}
{...gridProps}
onFilterChanged={onFilterChanged}
/>
</div>
{editOrder && (
<OrderEditDialog
@@ -30,7 +30,6 @@ describe('StopOrdersManager', () => {
flush: jest.fn(),
reload: jest.fn(),
load: jest.fn(),
pageInfo: null,
});
await act(async () => {
render(generateJsx());
+10 -54
View File
@@ -4,13 +4,10 @@ import { TradesTable } from './trades-table';
import { useDealTicketFormValues } from '@vegaprotocol/deal-ticket';
import { t } from '@vegaprotocol/i18n';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
import { useCallback, useState } from 'react';
import type { AgGridReact } from 'ag-grid-react';
import { TradingButton as Button } from '@vegaprotocol/ui-toolkit';
interface TradesContainerProps {
marketId: string;
gridProps: ReturnType<typeof useDataGridEvents>;
gridProps?: ReturnType<typeof useDataGridEvents>;
}
export const TradesManager = ({
@@ -19,60 +16,19 @@ export const TradesManager = ({
}: TradesContainerProps) => {
const update = useDealTicketFormValues((state) => state.updateAll);
const { data, error, load, pageInfo } = useDataProvider({
const { data, error } = useDataProvider({
dataProvider: tradesWithMarketProvider,
variables: { marketId },
});
const [hasDisplayedRow, setHasDisplayedRow] = useState<boolean | undefined>(
undefined
);
const { onFilterChanged, ...props } = gridProps || {};
const onRowDataUpdated = useCallback(
({ api }: { api: AgGridReact['api'] }) => {
setHasDisplayedRow(!!api.getDisplayedRowCount());
},
[]
);
return (
<div className="flex flex-col h-full">
<TradesTable
rowData={data}
onClick={(price?: string) => {
update(marketId, { price });
}}
onFilterChanged={(event) => {
onRowDataUpdated(event);
onFilterChanged(event);
}}
onRowDataUpdated={onRowDataUpdated}
overlayNoRowsTemplate={error ? error.message : t('No trades')}
{...props}
/>
<div className="flex justify-between border-t border-default p-1 items-center">
<div className="text-xs">
{t(
'Depending on data node retention you may not be able see the "full" history'
)}
</div>
<div className="flex text-xs items-center">
{data?.length && !pageInfo?.hasNextPage
? t('all %s items loaded', [data.length.toString()])
: t('%s items loaded', [
data?.length ? data.length.toString() : ' ',
])}
{pageInfo?.hasNextPage ? (
<Button size="extra-small" className="ml-1" onClick={() => load()}>
{t('Load more')}
</Button>
) : null}
</div>
{data?.length && hasDisplayedRow === false ? (
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-xs">
{t('No trades matching selected filters')}
</div>
) : null}
</div>
</div>
<TradesTable
rowData={data}
onClick={(price?: string) => {
update(marketId, { price });
}}
overlayNoRowsTemplate={error ? error.message : t('No trades')}
{...gridProps}
/>
);
};
+2 -6
View File
@@ -813,7 +813,7 @@ export type DispatchStrategy = {
/** Minimum notional time-weighted averaged position required for a party to be considered eligible */
notionalTimeWeightedAveragePositionRequirement: Scalars['String'];
/** Ascending order list of start rank and corresponding share ratio */
rankTable?: Maybe<Array<Maybe<RankTable>>>;
rankTable?: Maybe<RankTable>;
/** Minimum number of governance tokens, e.g. VEGA, staked for a party to be considered eligible */
stakingRequirement: Scalars['String'];
/** The teams in scope for the reward, if the entity is teams */
@@ -3624,8 +3624,6 @@ export type PartyVestingStats = {
__typename?: 'PartyVestingStats';
/** Epoch for which the statistics are valid */
epochSeq: Scalars['Int'];
/** The balance of the party, in quantum. */
quantumBalance: Scalars['String'];
/** The reward bonus multiplier */
rewardBonusMultiplier: Scalars['String'];
};
@@ -4833,7 +4831,7 @@ export type QueryprotocolUpgradeProposalsArgs = {
/** Queries allow a caller to read data and filter data via GraphQL. */
export type QueryreferralSetRefereesArgs = {
aggregationEpochs?: InputMaybe<Scalars['Int']>;
aggregationDays?: InputMaybe<Scalars['Int']>;
id?: InputMaybe<Scalars['ID']>;
pagination?: InputMaybe<Pagination>;
referee?: InputMaybe<Scalars['ID']>;
@@ -5090,8 +5088,6 @@ export type ReferralSetStats = {
partyId: Scalars['ID'];
/** Running volume for the set based on the window length of the current referral program. */
referralSetRunningNotionalTakerVolume: Scalars['String'];
/** The referrer's taker volume */
referrerTakerVolume: Scalars['String'];
/** Reward factor applied to the party. */
rewardFactor: Scalars['String'];
/** The proportion of the referees taker fees to be rewarded to the referrer. */
@@ -72,7 +72,7 @@ LessThan24HoursIncrease.args = {
export const LessThan24HoursDecrease = Template.bind({});
LessThan24HoursDecrease.args = {
data: [20990000, 20939973, 20980130],
data: [20, 21, 22, 23, 24, 6, 7, 9, 11, 13, 11, 9],
width: 110,
height: 30,
};
@@ -44,12 +44,14 @@ export const SparklineView = ({
return null;
}
const midValue = (min + max) / 2;
// Market may be less than 24hr old so padd the data array
// with values that is the mid value (avg of min and max).
// This will rendera horizontal line until the real data shifts the line
const trimmedData = data.slice(-points);
const padCount = data.length < points ? points - data.length : 0;
const padArr = new Array(padCount).fill(trimmedData[0]);
const padArr = new Array(padCount).fill(midValue);
const trimmedData = data.slice(-points);
// Get the last 24 values if data has more than needed
const lineData: [number, number][] = [...padArr, ...trimmedData].map(
-30
View File
@@ -179,33 +179,3 @@ export const toNumberParts = (
export const isNumeric = (
value?: string | number | BigNumber | bigint | null
): value is NonNullable<number | string> => /^-?\d*\.?\d+$/.test(String(value));
/**
* Format a number greater than 1 million with m for million, b for billion
* and t for trillion
*/
export const formatNumberRounded = (num: BigNumber) => {
let value = '';
const format = (divisor: string) => {
const result = num.dividedBy(divisor);
return result.isInteger() ? result.toString() : result.toFixed(1);
};
if (num.isGreaterThan(new BigNumber('1e14'))) {
value = '>100t';
} else if (num.isGreaterThanOrEqualTo(new BigNumber('1e12'))) {
// Trillion
value = `${format('1e12')}t`;
} else if (num.isGreaterThanOrEqualTo(new BigNumber('1e9'))) {
// Billion
value = `${format('1e9')}b`;
} else if (num.isGreaterThanOrEqualTo(new BigNumber('1e6'))) {
// Million
value = `${format('1e6')}m`;
} else {
value = formatNumber(num);
}
return value;
};