feat(governance): paginate rewards (#3391)
This commit is contained in:
parent
ec12c7ecfd
commit
4d266963ae
@ -1,9 +1,8 @@
|
|||||||
import type { ObservableQuery } from '@apollo/client';
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
export const useRefreshAfterEpoch = (
|
export const useRefreshAfterEpoch = (
|
||||||
epochExpiry: string | undefined,
|
epochExpiry: string | undefined,
|
||||||
refetch: ObservableQuery['refetch']
|
refetch: () => void
|
||||||
) => {
|
) => {
|
||||||
return useEffect(() => {
|
return useEffect(() => {
|
||||||
const epochInterval = setInterval(() => {
|
const epochInterval = setInterval(() => {
|
||||||
|
15
apps/governance/src/lib/epoch-pagination.ts
Normal file
15
apps/governance/src/lib/epoch-pagination.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export const calculateEpochOffset = ({
|
||||||
|
epochId,
|
||||||
|
page,
|
||||||
|
size,
|
||||||
|
}: {
|
||||||
|
epochId: number;
|
||||||
|
page: number;
|
||||||
|
size: number;
|
||||||
|
}) => {
|
||||||
|
// offset the epoch by the current page number times the page size while making sure it doesn't go below the minimum epoch value
|
||||||
|
return {
|
||||||
|
fromEpoch: Math.max(0, epochId - size * page) + 1,
|
||||||
|
toEpoch: epochId - size * page + size,
|
||||||
|
};
|
||||||
|
};
|
@ -3,7 +3,7 @@ import { AppStateProvider } from '../../../contexts/app-state/app-state-provider
|
|||||||
import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table';
|
import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table';
|
||||||
|
|
||||||
const mockData = {
|
const mockData = {
|
||||||
epoch: '4441',
|
epoch: 4441,
|
||||||
rewards: [
|
rewards: [
|
||||||
{
|
{
|
||||||
asset: 'tDAI',
|
asset: 'tDAI',
|
||||||
|
@ -1,32 +1,43 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo, useEffect, useState, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer, Pagination } from '@vegaprotocol/ui-toolkit';
|
||||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||||
|
import type { EpochFieldsFragment } from '../home/__generated__/Rewards';
|
||||||
import { useRewardsQuery } from '../home/__generated__/Rewards';
|
import { useRewardsQuery } from '../home/__generated__/Rewards';
|
||||||
import { ENV } from '../../../config';
|
import { ENV } from '../../../config';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table';
|
import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table';
|
||||||
import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list';
|
import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list';
|
||||||
|
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
|
||||||
|
|
||||||
export const EpochIndividualRewards = () => {
|
const EPOCHS_PAGE_SIZE = 10;
|
||||||
|
|
||||||
|
type EpochTotalRewardsProps = {
|
||||||
|
currentEpoch: EpochFieldsFragment;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EpochIndividualRewards = ({
|
||||||
|
currentEpoch,
|
||||||
|
}: EpochTotalRewardsProps) => {
|
||||||
|
// we start from the previous epoch when displaying rewards data, because the current one has no calculated data while ongoing
|
||||||
|
const epochId = Number(currentEpoch.id) - 1;
|
||||||
|
const totalPages = Math.ceil(epochId / EPOCHS_PAGE_SIZE);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { pubKey } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
const { delegationsPagination } = ENV;
|
const { delegationsPagination } = ENV;
|
||||||
|
|
||||||
const { data, loading, error } = useRewardsQuery({
|
const { data, loading, error, refetch } = useRewardsQuery({
|
||||||
|
notifyOnNetworkStatusChange: true,
|
||||||
variables: {
|
variables: {
|
||||||
partyId: pubKey || '',
|
partyId: pubKey || '',
|
||||||
|
fromEpoch: epochId - EPOCHS_PAGE_SIZE,
|
||||||
|
toEpoch: epochId,
|
||||||
delegationsPagination: delegationsPagination
|
delegationsPagination: delegationsPagination
|
||||||
? {
|
? {
|
||||||
first: Number(delegationsPagination),
|
first: Number(delegationsPagination),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
// we can use the same value for rewardsPagination as delegationsPagination
|
|
||||||
rewardsPagination: delegationsPagination
|
|
||||||
? {
|
|
||||||
first: Number(delegationsPagination),
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
},
|
},
|
||||||
skip: !pubKey,
|
skip: !pubKey,
|
||||||
});
|
});
|
||||||
@ -39,8 +50,37 @@ export const EpochIndividualRewards = () => {
|
|||||||
|
|
||||||
const epochIndividualRewardSummaries = useMemo(() => {
|
const epochIndividualRewardSummaries = useMemo(() => {
|
||||||
if (!data?.party) return [];
|
if (!data?.party) return [];
|
||||||
return generateEpochIndividualRewardsList(rewards);
|
return generateEpochIndividualRewardsList({
|
||||||
}, [data?.party, rewards]);
|
rewards,
|
||||||
|
epochId,
|
||||||
|
page,
|
||||||
|
size: EPOCHS_PAGE_SIZE,
|
||||||
|
});
|
||||||
|
}, [data?.party, epochId, page, rewards]);
|
||||||
|
|
||||||
|
const refetchData = useCallback(
|
||||||
|
async (toPage?: number) => {
|
||||||
|
const targetPage = toPage ?? page;
|
||||||
|
await refetch({
|
||||||
|
partyId: pubKey || '',
|
||||||
|
...calculateEpochOffset({ epochId, page, size: EPOCHS_PAGE_SIZE }),
|
||||||
|
delegationsPagination: delegationsPagination
|
||||||
|
? {
|
||||||
|
first: Number(delegationsPagination),
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
setPage(targetPage);
|
||||||
|
},
|
||||||
|
[epochId, page, refetch, delegationsPagination, pubKey]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// when the epoch changes, we want to refetch the data to update the current page
|
||||||
|
if (data) {
|
||||||
|
refetchData();
|
||||||
|
}
|
||||||
|
}, [epochId, data, refetchData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncRenderer
|
<AsyncRenderer
|
||||||
@ -53,17 +93,24 @@ export const EpochIndividualRewards = () => {
|
|||||||
{t('Connected Vega key')}:{' '}
|
{t('Connected Vega key')}:{' '}
|
||||||
<span className="text-white">{pubKey}</span>
|
<span className="text-white">{pubKey}</span>
|
||||||
</p>
|
</p>
|
||||||
{epochIndividualRewardSummaries.length ? (
|
{epochIndividualRewardSummaries.map(
|
||||||
epochIndividualRewardSummaries.map(
|
|
||||||
(epochIndividualRewardSummary) => (
|
(epochIndividualRewardSummary) => (
|
||||||
<EpochIndividualRewardsTable
|
<EpochIndividualRewardsTable
|
||||||
data={epochIndividualRewardSummary}
|
data={epochIndividualRewardSummary}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<p>{t('noRewards')}</p>
|
|
||||||
)}
|
)}
|
||||||
|
<Pagination
|
||||||
|
isLoading={loading}
|
||||||
|
hasPrevPage={page > 1}
|
||||||
|
hasNextPage={page < totalPages}
|
||||||
|
onBack={() => refetchData(page - 1)}
|
||||||
|
onNext={() => refetchData(page + 1)}
|
||||||
|
onFirst={() => refetchData(1)}
|
||||||
|
onLast={() => refetchData(totalPages)}
|
||||||
|
>
|
||||||
|
{t('Page')} {page}
|
||||||
|
</Pagination>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -43,6 +43,16 @@ describe('generateEpochIndividualRewardsList', () => {
|
|||||||
epoch: { id: '1' },
|
epoch: { id: '1' },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const reward5: RewardFieldsFragment = {
|
||||||
|
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||||
|
amount: '150',
|
||||||
|
percentageOfTotal: '0.15',
|
||||||
|
receivedAt: new Date(),
|
||||||
|
asset: { id: 'usd', symbol: 'USD', name: 'USD' },
|
||||||
|
party: { id: 'blah' },
|
||||||
|
epoch: { id: '3' },
|
||||||
|
};
|
||||||
|
|
||||||
const rewardWrongType: RewardFieldsFragment = {
|
const rewardWrongType: RewardFieldsFragment = {
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_INSURANCE,
|
rewardType: AccountType.ACCOUNT_TYPE_INSURANCE,
|
||||||
amount: '50',
|
amount: '50',
|
||||||
@ -54,20 +64,38 @@ describe('generateEpochIndividualRewardsList', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
it('should return an empty array if no rewards are provided', () => {
|
it('should return an empty array if no rewards are provided', () => {
|
||||||
expect(generateEpochIndividualRewardsList([])).toEqual([]);
|
expect(
|
||||||
|
generateEpochIndividualRewardsList({ rewards: [], epochId: 1 })
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
epoch: 1,
|
||||||
|
rewards: [],
|
||||||
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should filter out any rewards of the wrong type', () => {
|
it('should filter out any rewards of the wrong type', () => {
|
||||||
const result = generateEpochIndividualRewardsList([rewardWrongType]);
|
const result = generateEpochIndividualRewardsList({
|
||||||
|
rewards: [rewardWrongType],
|
||||||
|
epochId: 1,
|
||||||
|
});
|
||||||
|
|
||||||
expect(result).toEqual([]);
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
epoch: 1,
|
||||||
|
rewards: [],
|
||||||
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return reward in the correct format', () => {
|
it('should return reward in the correct format', () => {
|
||||||
const result = generateEpochIndividualRewardsList([reward1]);
|
const result = generateEpochIndividualRewardsList({
|
||||||
|
rewards: [reward1],
|
||||||
|
epochId: 1,
|
||||||
|
});
|
||||||
|
|
||||||
expect(result[0]).toEqual({
|
expect(result[0]).toEqual({
|
||||||
epoch: '1',
|
epoch: 1,
|
||||||
rewards: [
|
rewards: [
|
||||||
{
|
{
|
||||||
asset: 'USD',
|
asset: 'USD',
|
||||||
@ -105,21 +133,24 @@ describe('generateEpochIndividualRewardsList', () => {
|
|||||||
|
|
||||||
it('should return an array sorted by epoch descending', () => {
|
it('should return an array sorted by epoch descending', () => {
|
||||||
const rewards = [reward1, reward2, reward3, reward4];
|
const rewards = [reward1, reward2, reward3, reward4];
|
||||||
const result1 = generateEpochIndividualRewardsList(rewards);
|
const result1 = generateEpochIndividualRewardsList({ rewards, epochId: 2 });
|
||||||
|
|
||||||
expect(result1[0].epoch).toEqual('2');
|
expect(result1[0].epoch).toEqual(2);
|
||||||
expect(result1[1].epoch).toEqual('1');
|
expect(result1[1].epoch).toEqual(1);
|
||||||
|
|
||||||
const reorderedRewards = [reward4, reward3, reward2, reward1];
|
const reorderedRewards = [reward4, reward3, reward2, reward1];
|
||||||
const result2 = generateEpochIndividualRewardsList(reorderedRewards);
|
const result2 = generateEpochIndividualRewardsList({
|
||||||
|
rewards: reorderedRewards,
|
||||||
|
epochId: 2,
|
||||||
|
});
|
||||||
|
|
||||||
expect(result2[0].epoch).toEqual('2');
|
expect(result2[0].epoch).toEqual(2);
|
||||||
expect(result2[1].epoch).toEqual('1');
|
expect(result2[1].epoch).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('correctly calculates the total value of rewards for an asset', () => {
|
it('correctly calculates the total value of rewards for an asset', () => {
|
||||||
const rewards = [reward1, reward4];
|
const rewards = [reward1, reward4];
|
||||||
const result = generateEpochIndividualRewardsList(rewards);
|
const result = generateEpochIndividualRewardsList({ rewards, epochId: 1 });
|
||||||
|
|
||||||
expect(result[0].rewards[0].totalAmount).toEqual('200');
|
expect(result[0].rewards[0].totalAmount).toEqual('200');
|
||||||
});
|
});
|
||||||
@ -127,11 +158,11 @@ describe('generateEpochIndividualRewardsList', () => {
|
|||||||
it('returns data in the expected shape', () => {
|
it('returns data in the expected shape', () => {
|
||||||
// Just sanity checking the whole structure here
|
// Just sanity checking the whole structure here
|
||||||
const rewards = [reward1, reward2, reward3, reward4];
|
const rewards = [reward1, reward2, reward3, reward4];
|
||||||
const result = generateEpochIndividualRewardsList(rewards);
|
const result = generateEpochIndividualRewardsList({ rewards, epochId: 2 });
|
||||||
|
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([
|
||||||
{
|
{
|
||||||
epoch: '2',
|
epoch: 2,
|
||||||
rewards: [
|
rewards: [
|
||||||
{
|
{
|
||||||
asset: 'GBP',
|
asset: 'GBP',
|
||||||
@ -196,7 +227,165 @@ describe('generateEpochIndividualRewardsList', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
epoch: '1',
|
epoch: 1,
|
||||||
|
rewards: [
|
||||||
|
{
|
||||||
|
asset: 'USD',
|
||||||
|
totalAmount: '200',
|
||||||
|
rewardTypes: {
|
||||||
|
[AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
|
||||||
|
amount: '100',
|
||||||
|
percentageOfTotal: '0.1',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
|
||||||
|
amount: '100',
|
||||||
|
percentageOfTotal: '0.1',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns data correctly for the requested epoch range', () => {
|
||||||
|
const rewards = [reward1, reward2, reward3, reward4, reward5];
|
||||||
|
const resultPageOne = generateEpochIndividualRewardsList({
|
||||||
|
rewards,
|
||||||
|
epochId: 3,
|
||||||
|
page: 1,
|
||||||
|
size: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(resultPageOne).toEqual([
|
||||||
|
{
|
||||||
|
epoch: 3,
|
||||||
|
rewards: [
|
||||||
|
{
|
||||||
|
asset: 'USD',
|
||||||
|
totalAmount: '150',
|
||||||
|
rewardTypes: {
|
||||||
|
[AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
|
||||||
|
amount: '150',
|
||||||
|
percentageOfTotal: '0.15',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
epoch: 2,
|
||||||
|
rewards: [
|
||||||
|
{
|
||||||
|
asset: 'GBP',
|
||||||
|
totalAmount: '200',
|
||||||
|
rewardTypes: {
|
||||||
|
[AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
|
||||||
|
amount: '200',
|
||||||
|
percentageOfTotal: '0.2',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
asset: 'EUR',
|
||||||
|
totalAmount: '50',
|
||||||
|
rewardTypes: {
|
||||||
|
[AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
|
||||||
|
amount: '50',
|
||||||
|
percentageOfTotal: '0.05',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
[AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
|
||||||
|
amount: '0',
|
||||||
|
percentageOfTotal: '0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const resultPageTwo = generateEpochIndividualRewardsList({
|
||||||
|
rewards,
|
||||||
|
epochId: 3,
|
||||||
|
page: 2,
|
||||||
|
size: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(resultPageTwo).toEqual([
|
||||||
|
{
|
||||||
|
epoch: 1,
|
||||||
rewards: [
|
rewards: [
|
||||||
{
|
{
|
||||||
asset: 'USD',
|
asset: 'USD',
|
||||||
|
@ -2,9 +2,10 @@ import { BigNumber } from '../../../lib/bignumber';
|
|||||||
import { RowAccountTypes } from '../shared-rewards-table-assets/shared-rewards-table-assets';
|
import { RowAccountTypes } from '../shared-rewards-table-assets/shared-rewards-table-assets';
|
||||||
import type { RewardFieldsFragment } from '../home/__generated__/Rewards';
|
import type { RewardFieldsFragment } from '../home/__generated__/Rewards';
|
||||||
import type { AccountType } from '@vegaprotocol/types';
|
import type { AccountType } from '@vegaprotocol/types';
|
||||||
|
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
|
||||||
|
|
||||||
export interface EpochIndividualReward {
|
export interface EpochIndividualReward {
|
||||||
epoch: string;
|
epoch: number;
|
||||||
rewards: {
|
rewards: {
|
||||||
asset: string;
|
asset: string;
|
||||||
totalAmount: string;
|
totalAmount: string;
|
||||||
@ -27,11 +28,29 @@ const emptyRowAccountTypes = accountTypes.map((type) => [
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const generateEpochIndividualRewardsList = (
|
export const generateEpochIndividualRewardsList = ({
|
||||||
rewards: RewardFieldsFragment[]
|
rewards,
|
||||||
) => {
|
epochId,
|
||||||
|
page = 1,
|
||||||
|
size = 10,
|
||||||
|
}: {
|
||||||
|
rewards: RewardFieldsFragment[];
|
||||||
|
epochId: number;
|
||||||
|
page?: number;
|
||||||
|
size?: number;
|
||||||
|
}) => {
|
||||||
|
const map: Map<string, EpochIndividualReward> = new Map();
|
||||||
|
const { fromEpoch, toEpoch } = calculateEpochOffset({ epochId, page, size });
|
||||||
|
|
||||||
|
for (let i = toEpoch; i >= fromEpoch; i--) {
|
||||||
|
map.set(i.toString(), {
|
||||||
|
epoch: i,
|
||||||
|
rewards: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// We take the rewards and aggregate them by epoch and asset.
|
// We take the rewards and aggregate them by epoch and asset.
|
||||||
const epochIndividualRewards = rewards.reduce((map, reward) => {
|
const epochIndividualRewards = rewards.reduce((acc, reward) => {
|
||||||
const epochId = reward.epoch.id;
|
const epochId = reward.epoch.id;
|
||||||
const assetName = reward.asset.name;
|
const assetName = reward.asset.name;
|
||||||
const rewardType = reward.rewardType;
|
const rewardType = reward.rewardType;
|
||||||
@ -40,14 +59,14 @@ export const generateEpochIndividualRewardsList = (
|
|||||||
|
|
||||||
// if the rewardType is not of a type we display in the table, we skip it.
|
// if the rewardType is not of a type we display in the table, we skip it.
|
||||||
if (!accountTypes.includes(rewardType)) {
|
if (!accountTypes.includes(rewardType)) {
|
||||||
return map;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!map.has(epochId)) {
|
if (!acc.has(epochId)) {
|
||||||
map.set(epochId, { epoch: epochId, rewards: [] });
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
const epoch = map.get(epochId);
|
const epoch = acc.get(epochId);
|
||||||
|
|
||||||
let asset = epoch?.rewards.find((r) => r.asset === assetName);
|
let asset = epoch?.rewards.find((r) => r.asset === assetName);
|
||||||
|
|
||||||
@ -76,8 +95,8 @@ export const generateEpochIndividualRewardsList = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return map;
|
return acc;
|
||||||
}, new Map<string, EpochIndividualReward>());
|
}, map);
|
||||||
|
|
||||||
return Array.from(epochIndividualRewards.values()).sort(
|
return Array.from(epochIndividualRewards.values()).sort(
|
||||||
(a, b) => Number(b.epoch) - Number(a.epoch)
|
(a, b) => Number(b.epoch) - Number(a.epoch)
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import { AppStateProvider } from '../../../contexts/app-state/app-state-provider';
|
import { AppStateProvider } from '../../../contexts/app-state/app-state-provider';
|
||||||
import { EpochTotalRewardsTable } from './epoch-total-rewards-table';
|
import { EpochTotalRewardsTable } from './epoch-total-rewards-table';
|
||||||
|
import type {
|
||||||
|
AggregatedEpochRewardSummary,
|
||||||
|
RewardType,
|
||||||
|
RewardItem,
|
||||||
|
} from './generate-epoch-total-rewards-list';
|
||||||
import { AccountType } from '@vegaprotocol/types';
|
import { AccountType } from '@vegaprotocol/types';
|
||||||
|
|
||||||
const mockData = {
|
const assetId =
|
||||||
epoch: 4431,
|
'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663';
|
||||||
assetRewards: [
|
|
||||||
{
|
const rewardsList = [
|
||||||
assetId:
|
|
||||||
'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
|
|
||||||
name: 'tDAI TEST',
|
|
||||||
rewards: [
|
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||||
amount: '0',
|
amount: '0',
|
||||||
@ -35,10 +36,29 @@ const mockData = {
|
|||||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
rewardType: AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
||||||
amount: '0',
|
amount: '0',
|
||||||
},
|
},
|
||||||
],
|
];
|
||||||
|
|
||||||
|
const rewards: Map<RewardType, RewardItem> = new Map();
|
||||||
|
|
||||||
|
rewardsList.forEach((r) => {
|
||||||
|
rewards.set(r.rewardType, r);
|
||||||
|
});
|
||||||
|
|
||||||
|
const assetRewards: Map<
|
||||||
|
AggregatedEpochRewardSummary['assetId'],
|
||||||
|
AggregatedEpochRewardSummary
|
||||||
|
> = new Map();
|
||||||
|
|
||||||
|
assetRewards.set(assetId, {
|
||||||
|
assetId,
|
||||||
|
name: 'tDAI TEST',
|
||||||
|
rewards,
|
||||||
totalAmount: '295',
|
totalAmount: '295',
|
||||||
},
|
});
|
||||||
],
|
|
||||||
|
const mockData = {
|
||||||
|
epoch: 4431,
|
||||||
|
assetRewards,
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('EpochTotalRewardsTable', () => {
|
describe('EpochTotalRewardsTable', () => {
|
||||||
|
@ -48,17 +48,19 @@ export const EpochTotalRewardsTable = ({
|
|||||||
}: EpochTotalRewardsGridProps) => {
|
}: EpochTotalRewardsGridProps) => {
|
||||||
return (
|
return (
|
||||||
<RewardsTable dataTestId="epoch-total-rewards-table" epoch={data.epoch}>
|
<RewardsTable dataTestId="epoch-total-rewards-table" epoch={data.epoch}>
|
||||||
{data.assetRewards.map(({ name, rewards, totalAmount }, i) => (
|
{Array.from(data.assetRewards.values()).map(
|
||||||
|
({ name, rewards, totalAmount }, i) => (
|
||||||
<div className="contents" key={i}>
|
<div className="contents" key={i}>
|
||||||
<div data-testid="asset" className={`${rowGridItemStyles()} p-5`}>
|
<div data-testid="asset" className={`${rowGridItemStyles()} p-5`}>
|
||||||
{name}
|
{name}
|
||||||
</div>
|
</div>
|
||||||
{rewards.map(({ rewardType, amount }, i) => (
|
{Array.from(rewards.values()).map(({ rewardType, amount }, i) => (
|
||||||
<RewardItem key={i} dataTestId={rewardType} value={amount} />
|
<RewardItem key={i} dataTestId={rewardType} value={amount} />
|
||||||
))}
|
))}
|
||||||
<RewardItem dataTestId="total" value={totalAmount} last={true} />
|
<RewardItem dataTestId="total" value={totalAmount} last={true} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
)
|
||||||
|
)}
|
||||||
</RewardsTable>
|
</RewardsTable>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,21 +1,62 @@
|
|||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { AsyncRenderer, Pagination } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import type { EpochFieldsFragment } from '../home/__generated__/Rewards';
|
||||||
import { useEpochAssetsRewardsQuery } from '../home/__generated__/Rewards';
|
import { useEpochAssetsRewardsQuery } from '../home/__generated__/Rewards';
|
||||||
import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch';
|
|
||||||
import { generateEpochTotalRewardsList } from './generate-epoch-total-rewards-list';
|
import { generateEpochTotalRewardsList } from './generate-epoch-total-rewards-list';
|
||||||
import { NoRewards } from '../no-rewards';
|
|
||||||
import { EpochTotalRewardsTable } from './epoch-total-rewards-table';
|
import { EpochTotalRewardsTable } from './epoch-total-rewards-table';
|
||||||
|
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
|
||||||
|
|
||||||
export const EpochTotalRewards = () => {
|
const EPOCHS_PAGE_SIZE = 10;
|
||||||
|
|
||||||
|
type EpochTotalRewardsProps = {
|
||||||
|
currentEpoch: EpochFieldsFragment;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EpochTotalRewards = ({ currentEpoch }: EpochTotalRewardsProps) => {
|
||||||
|
// we start from the previous epoch when displaying rewards data, because the current one has no calculated data while ongoing
|
||||||
|
const epochId = Number(currentEpoch.id) - 1;
|
||||||
|
const totalPages = Math.ceil(epochId / EPOCHS_PAGE_SIZE);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
const { data, loading, error, refetch } = useEpochAssetsRewardsQuery({
|
const { data, loading, error, refetch } = useEpochAssetsRewardsQuery({
|
||||||
|
notifyOnNetworkStatusChange: true,
|
||||||
variables: {
|
variables: {
|
||||||
epochRewardSummariesPagination: {
|
epochRewardSummariesFilter: {
|
||||||
first: 10,
|
fromEpoch: epochId - EPOCHS_PAGE_SIZE,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
useRefreshAfterEpoch(data?.epoch.timestamps.expiry, refetch);
|
|
||||||
|
|
||||||
const epochTotalRewardSummaries = generateEpochTotalRewardsList(data) || [];
|
const refetchData = useCallback(
|
||||||
|
async (toPage?: number) => {
|
||||||
|
const targetPage = toPage ?? page;
|
||||||
|
await refetch({
|
||||||
|
epochRewardSummariesFilter: calculateEpochOffset({
|
||||||
|
epochId,
|
||||||
|
page: targetPage,
|
||||||
|
size: EPOCHS_PAGE_SIZE,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
setPage(targetPage);
|
||||||
|
},
|
||||||
|
[epochId, page, refetch]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// when the epoch changes, we want to refetch the data to update the current page
|
||||||
|
if (data) {
|
||||||
|
refetchData();
|
||||||
|
}
|
||||||
|
}, [epochId, data, refetchData]);
|
||||||
|
|
||||||
|
const epochTotalRewardSummaries =
|
||||||
|
generateEpochTotalRewardsList({
|
||||||
|
data,
|
||||||
|
epochId,
|
||||||
|
page,
|
||||||
|
size: EPOCHS_PAGE_SIZE,
|
||||||
|
}) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncRenderer
|
<AsyncRenderer
|
||||||
@ -27,15 +68,22 @@ export const EpochTotalRewards = () => {
|
|||||||
className="max-w-full overflow-auto"
|
className="max-w-full overflow-auto"
|
||||||
data-testid="epoch-rewards-total"
|
data-testid="epoch-rewards-total"
|
||||||
>
|
>
|
||||||
{epochTotalRewardSummaries.length === 0 ? (
|
{Array.from(epochTotalRewardSummaries.values()).map(
|
||||||
<NoRewards />
|
(epochTotalSummary, index) => (
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{epochTotalRewardSummaries.map((epochTotalSummary, index) => (
|
|
||||||
<EpochTotalRewardsTable data={epochTotalSummary} key={index} />
|
<EpochTotalRewardsTable data={epochTotalSummary} key={index} />
|
||||||
))}
|
)
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
<Pagination
|
||||||
|
isLoading={loading}
|
||||||
|
hasPrevPage={page > 1}
|
||||||
|
hasNextPage={page < totalPages}
|
||||||
|
onBack={() => refetchData(page - 1)}
|
||||||
|
onNext={() => refetchData(page + 1)}
|
||||||
|
onFirst={() => refetchData(1)}
|
||||||
|
onLast={() => refetchData(totalPages)}
|
||||||
|
>
|
||||||
|
{t('Page')} {page}
|
||||||
|
</Pagination>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -3,13 +3,23 @@ import { AccountType } from '@vegaprotocol/types';
|
|||||||
|
|
||||||
describe('generateEpochAssetRewardsList', () => {
|
describe('generateEpochAssetRewardsList', () => {
|
||||||
it('should return an empty array if data is undefined', () => {
|
it('should return an empty array if data is undefined', () => {
|
||||||
const result = generateEpochTotalRewardsList(undefined);
|
const result = generateEpochTotalRewardsList({ epochId: 1 });
|
||||||
|
|
||||||
expect(result).toEqual([]);
|
expect(result).toEqual(
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
'1',
|
||||||
|
{
|
||||||
|
epoch: 1,
|
||||||
|
assetRewards: new Map(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an empty array if empty data is provided', () => {
|
it('should return an empty map if empty data is provided', () => {
|
||||||
const epochData = {
|
const data = {
|
||||||
assetsConnection: {
|
assetsConnection: {
|
||||||
edges: [],
|
edges: [],
|
||||||
},
|
},
|
||||||
@ -23,13 +33,23 @@ describe('generateEpochAssetRewardsList', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = generateEpochTotalRewardsList(epochData);
|
const result = generateEpochTotalRewardsList({ data, epochId: 1 });
|
||||||
|
|
||||||
expect(result).toEqual([]);
|
expect(result).toEqual(
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
'1',
|
||||||
|
{
|
||||||
|
epoch: 1,
|
||||||
|
assetRewards: new Map(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an empty array if no epochRewardSummaries are provided', () => {
|
it('should return an empty map if no epochRewardSummaries are provided', () => {
|
||||||
const epochData = {
|
const data = {
|
||||||
assetsConnection: {
|
assetsConnection: {
|
||||||
edges: [
|
edges: [
|
||||||
{
|
{
|
||||||
@ -56,13 +76,23 @@ describe('generateEpochAssetRewardsList', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = generateEpochTotalRewardsList(epochData);
|
const result = generateEpochTotalRewardsList({ data, epochId: 1 });
|
||||||
|
|
||||||
expect(result).toEqual([]);
|
expect(result).toEqual(
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
'1',
|
||||||
|
{
|
||||||
|
epoch: 1,
|
||||||
|
assetRewards: new Map(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an array of unnamed assets if no asset names are provided (should not happen)', () => {
|
it('should return a map of unnamed assets if no asset names are provided (should not happen)', () => {
|
||||||
const epochData = {
|
const data = {
|
||||||
assetsConnection: {
|
assetsConnection: {
|
||||||
edges: [],
|
edges: [],
|
||||||
},
|
},
|
||||||
@ -85,50 +115,80 @@ describe('generateEpochAssetRewardsList', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = generateEpochTotalRewardsList(epochData);
|
const result = generateEpochTotalRewardsList({ data, epochId: 1 });
|
||||||
|
|
||||||
expect(result).toEqual([
|
expect(result).toEqual(
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
'1',
|
||||||
{
|
{
|
||||||
epoch: 1,
|
epoch: 1,
|
||||||
assetRewards: [
|
assetRewards: new Map([
|
||||||
|
[
|
||||||
|
'1',
|
||||||
{
|
{
|
||||||
assetId: '1',
|
assetId: '1',
|
||||||
name: '',
|
name: '',
|
||||||
rewards: [
|
rewards: new Map([
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||||
amount: '123',
|
amount: '123',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||||
amount: '0',
|
amount: '0',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
|
||||||
amount: '0',
|
amount: '0',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
|
||||||
amount: '0',
|
amount: '0',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||||
amount: '0',
|
amount: '0',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
||||||
amount: '0',
|
amount: '0',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
]),
|
||||||
totalAmount: '123',
|
totalAmount: '123',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
]);
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an array of aggregated epoch summaries', () => {
|
it('should return the aggregated epoch summaries', () => {
|
||||||
const epochData = {
|
const data = {
|
||||||
assetsConnection: {
|
assetsConnection: {
|
||||||
edges: [
|
edges: [
|
||||||
{
|
{
|
||||||
@ -180,81 +240,425 @@ describe('generateEpochAssetRewardsList', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = generateEpochTotalRewardsList(epochData);
|
const result = generateEpochTotalRewardsList({ data, epochId: 2 });
|
||||||
|
|
||||||
expect(result).toEqual([
|
expect(result).toEqual(
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
'1',
|
||||||
{
|
{
|
||||||
epoch: 1,
|
epoch: 1,
|
||||||
assetRewards: [
|
assetRewards: new Map([
|
||||||
|
[
|
||||||
|
'1',
|
||||||
{
|
{
|
||||||
assetId: '1',
|
assetId: '1',
|
||||||
name: 'Asset 1',
|
name: 'Asset 1',
|
||||||
rewards: [
|
rewards: new Map([
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||||
amount: '0',
|
amount: '0',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||||
amount: '100',
|
amount: '100',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
|
||||||
amount: '123',
|
amount: '123',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
|
||||||
amount: '0',
|
amount: '0',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||||
amount: '0',
|
amount: '0',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
||||||
amount: '0',
|
amount: '0',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
]),
|
||||||
totalAmount: '223',
|
totalAmount: '223',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'2',
|
||||||
{
|
{
|
||||||
epoch: 2,
|
epoch: 2,
|
||||||
assetRewards: [
|
assetRewards: new Map([
|
||||||
|
[
|
||||||
|
'1',
|
||||||
{
|
{
|
||||||
assetId: '1',
|
assetId: '1',
|
||||||
name: 'Asset 1',
|
name: 'Asset 1',
|
||||||
rewards: [
|
rewards: new Map([
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||||
amount: '0',
|
amount: '0',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||||
amount: '0',
|
amount: '0',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
|
||||||
amount: '0',
|
amount: '0',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
|
||||||
amount: '0',
|
amount: '0',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||||
amount: '5',
|
amount: '5',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
||||||
{
|
{
|
||||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
||||||
amount: '0',
|
amount: '0',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
]),
|
||||||
totalAmount: '5',
|
totalAmount: '5',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
]);
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the requested range for aggregated epoch summaries', () => {
|
||||||
|
const data = {
|
||||||
|
assetsConnection: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: '1',
|
||||||
|
name: 'Asset 1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: '2',
|
||||||
|
name: 'Asset 2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
epochRewardSummaries: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
epoch: 1,
|
||||||
|
assetId: '1',
|
||||||
|
rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
|
||||||
|
amount: '123',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
epoch: 1,
|
||||||
|
assetId: '1',
|
||||||
|
rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||||
|
amount: '100',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
epoch: 2,
|
||||||
|
assetId: '1',
|
||||||
|
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||||
|
amount: '6',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
epoch: 2,
|
||||||
|
assetId: '1',
|
||||||
|
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||||
|
amount: '27',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
epoch: 3,
|
||||||
|
assetId: '1',
|
||||||
|
rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||||
|
amount: '15',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
epoch: {
|
||||||
|
timestamps: {
|
||||||
|
expiry: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const resultPageOne = generateEpochTotalRewardsList({
|
||||||
|
data,
|
||||||
|
epochId: 3,
|
||||||
|
page: 1,
|
||||||
|
size: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(resultPageOne).toEqual(
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
'2',
|
||||||
|
{
|
||||||
|
epoch: 2,
|
||||||
|
assetRewards: new Map([
|
||||||
|
[
|
||||||
|
'1',
|
||||||
|
{
|
||||||
|
assetId: '1',
|
||||||
|
name: 'Asset 1',
|
||||||
|
rewards: new Map([
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||||
|
{
|
||||||
|
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||||
|
amount: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||||
|
{
|
||||||
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||||
|
amount: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
|
||||||
|
{
|
||||||
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
|
||||||
|
amount: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
|
||||||
|
{
|
||||||
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
|
||||||
|
amount: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||||
|
{
|
||||||
|
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||||
|
amount: '33',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
||||||
|
{
|
||||||
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
||||||
|
amount: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
totalAmount: '33',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'3',
|
||||||
|
{
|
||||||
|
epoch: 3,
|
||||||
|
assetRewards: new Map([
|
||||||
|
[
|
||||||
|
'1',
|
||||||
|
{
|
||||||
|
assetId: '1',
|
||||||
|
name: 'Asset 1',
|
||||||
|
rewards: new Map([
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||||
|
{
|
||||||
|
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||||
|
amount: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||||
|
{
|
||||||
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||||
|
amount: '15',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
|
||||||
|
{
|
||||||
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
|
||||||
|
amount: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
|
||||||
|
{
|
||||||
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
|
||||||
|
amount: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||||
|
{
|
||||||
|
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||||
|
amount: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
||||||
|
{
|
||||||
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
||||||
|
amount: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
totalAmount: '15',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
const resultPageTwo = generateEpochTotalRewardsList({
|
||||||
|
data,
|
||||||
|
epochId: 3,
|
||||||
|
page: 2,
|
||||||
|
size: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(resultPageTwo).toEqual(
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
'1',
|
||||||
|
{
|
||||||
|
epoch: 1,
|
||||||
|
assetRewards: new Map([
|
||||||
|
[
|
||||||
|
'1',
|
||||||
|
{
|
||||||
|
assetId: '1',
|
||||||
|
name: 'Asset 1',
|
||||||
|
rewards: new Map([
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||||
|
{
|
||||||
|
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||||
|
amount: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||||
|
{
|
||||||
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||||
|
amount: '100',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
|
||||||
|
{
|
||||||
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
|
||||||
|
amount: '123',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
|
||||||
|
{
|
||||||
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
|
||||||
|
amount: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||||
|
{
|
||||||
|
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||||
|
amount: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
||||||
|
{
|
||||||
|
rewardType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
|
||||||
|
amount: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
totalAmount: '223',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,127 +5,97 @@ import type {
|
|||||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||||
import { RowAccountTypes } from '../shared-rewards-table-assets/shared-rewards-table-assets';
|
import { RowAccountTypes } from '../shared-rewards-table-assets/shared-rewards-table-assets';
|
||||||
import type { AccountType } from '@vegaprotocol/types';
|
import type { AccountType } from '@vegaprotocol/types';
|
||||||
import { BigNumber } from '../../../lib/bignumber';
|
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
|
||||||
|
|
||||||
interface EpochSummaryWithNamedReward extends EpochRewardSummaryFieldsFragment {
|
interface EpochSummaryWithNamedReward extends EpochRewardSummaryFieldsFragment {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AggregatedEpochRewardSummary {
|
export type RewardType = EpochRewardSummaryFieldsFragment['rewardType'];
|
||||||
|
export type RewardItem = Pick<
|
||||||
|
EpochRewardSummaryFieldsFragment,
|
||||||
|
'rewardType' | 'amount'
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type AggregatedEpochRewardSummary = {
|
||||||
assetId: EpochRewardSummaryFieldsFragment['assetId'];
|
assetId: EpochRewardSummaryFieldsFragment['assetId'];
|
||||||
name: EpochSummaryWithNamedReward['name'];
|
name: EpochSummaryWithNamedReward['name'];
|
||||||
rewards: {
|
rewards: Map<RewardType, RewardItem>;
|
||||||
rewardType: EpochRewardSummaryFieldsFragment['rewardType'];
|
|
||||||
amount: EpochRewardSummaryFieldsFragment['amount'];
|
|
||||||
}[];
|
|
||||||
totalAmount: string;
|
totalAmount: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface EpochTotalSummary {
|
export type EpochTotalSummary = {
|
||||||
epoch: EpochRewardSummaryFieldsFragment['epoch'];
|
epoch: EpochRewardSummaryFieldsFragment['epoch'];
|
||||||
assetRewards: AggregatedEpochRewardSummary[];
|
assetRewards: Map<
|
||||||
}
|
EpochRewardSummaryFieldsFragment['assetId'],
|
||||||
|
AggregatedEpochRewardSummary
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
const emptyRowAccountTypes = Object.keys(RowAccountTypes).map((type) => ({
|
const emptyRowAccountTypes: Map<RewardType, RewardItem> = new Map();
|
||||||
|
|
||||||
|
Object.keys(RowAccountTypes).forEach((type) => {
|
||||||
|
emptyRowAccountTypes.set(type as AccountType, {
|
||||||
rewardType: type as AccountType,
|
rewardType: type as AccountType,
|
||||||
amount: '0',
|
amount: '0',
|
||||||
}));
|
});
|
||||||
|
});
|
||||||
|
|
||||||
export const generateEpochTotalRewardsList = (
|
export const generateEpochTotalRewardsList = ({
|
||||||
epochData: EpochAssetsRewardsQuery | undefined
|
data,
|
||||||
) => {
|
epochId,
|
||||||
|
page = 1,
|
||||||
|
size = 10,
|
||||||
|
}: {
|
||||||
|
data?: EpochAssetsRewardsQuery | undefined;
|
||||||
|
epochId: number;
|
||||||
|
page?: number;
|
||||||
|
size?: number;
|
||||||
|
}) => {
|
||||||
const epochRewardSummaries = removePaginationWrapper(
|
const epochRewardSummaries = removePaginationWrapper(
|
||||||
epochData?.epochRewardSummaries?.edges
|
data?.epochRewardSummaries?.edges
|
||||||
);
|
);
|
||||||
|
|
||||||
const assets = removePaginationWrapper(epochData?.assetsConnection?.edges);
|
const assets = removePaginationWrapper(data?.assetsConnection?.edges);
|
||||||
|
|
||||||
// Because the epochRewardSummaries don't have the asset name, we need to find it in the assets list
|
const map: Map<string, EpochTotalSummary> = new Map();
|
||||||
const epochSummariesWithNamedReward: EpochSummaryWithNamedReward[] =
|
const { fromEpoch, toEpoch } = calculateEpochOffset({ epochId, page, size });
|
||||||
epochRewardSummaries.map((epochReward) => ({
|
|
||||||
...epochReward,
|
|
||||||
name:
|
|
||||||
assets.find((asset) => asset.id === epochReward.assetId)?.name || '',
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Aggregating the epoch summaries by epoch number
|
for (let i = toEpoch; i >= fromEpoch; i--) {
|
||||||
const aggregatedEpochSummariesByEpochNumber =
|
map.set(i.toString(), {
|
||||||
epochSummariesWithNamedReward.reduce((acc, epochReward) => {
|
epoch: i,
|
||||||
const epoch = epochReward.epoch;
|
assetRewards: new Map(),
|
||||||
const epochSummaryIndex = acc.findIndex(
|
|
||||||
(epochSummary) => epochSummary[0].epoch === epoch
|
|
||||||
);
|
|
||||||
|
|
||||||
if (epochSummaryIndex === -1) {
|
|
||||||
acc.push([epochReward]);
|
|
||||||
} else {
|
|
||||||
acc[epochSummaryIndex].push(epochReward);
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, [] as EpochSummaryWithNamedReward[][]);
|
|
||||||
|
|
||||||
// Now aggregate the array of arrays of epoch summaries by asset rewards.
|
|
||||||
const epochTotalRewards: EpochTotalSummary[] =
|
|
||||||
aggregatedEpochSummariesByEpochNumber.map((epochSummaries) => {
|
|
||||||
const assetRewards = epochSummaries.reduce((acc, epochSummary) => {
|
|
||||||
const assetRewardIndex = acc.findIndex(
|
|
||||||
(assetReward) =>
|
|
||||||
assetReward.assetId === epochSummary.assetId &&
|
|
||||||
assetReward.name === epochSummary.name
|
|
||||||
);
|
|
||||||
|
|
||||||
if (assetRewardIndex === -1) {
|
|
||||||
acc.push({
|
|
||||||
assetId: epochSummary.assetId,
|
|
||||||
name: epochSummary.name,
|
|
||||||
rewards: [
|
|
||||||
...emptyRowAccountTypes.map((emptyRowAccountType) => {
|
|
||||||
if (
|
|
||||||
emptyRowAccountType.rewardType === epochSummary.rewardType
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
rewardType: epochSummary.rewardType,
|
|
||||||
amount: epochSummary.amount,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return emptyRowAccountType;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
totalAmount: epochSummary.amount,
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
acc[assetRewardIndex].rewards = acc[assetRewardIndex].rewards.map(
|
|
||||||
(reward) => {
|
|
||||||
if (reward.rewardType === epochSummary.rewardType) {
|
|
||||||
return {
|
|
||||||
rewardType: epochSummary.rewardType,
|
|
||||||
amount: (
|
|
||||||
Number(reward.amount) + Number(epochSummary.amount)
|
|
||||||
).toString(),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return reward;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
);
|
return epochRewardSummaries.reduce((acc, reward) => {
|
||||||
acc[assetRewardIndex].totalAmount = (
|
const epoch = acc.get(reward.epoch.toString());
|
||||||
Number(acc[assetRewardIndex].totalAmount) +
|
|
||||||
Number(epochSummary.amount)
|
if (epoch) {
|
||||||
|
const matchingAsset = assets.find((asset) => asset.id === reward.assetId);
|
||||||
|
const assetWithRewards = epoch.assetRewards.get(reward.assetId);
|
||||||
|
|
||||||
|
const rewards =
|
||||||
|
assetWithRewards?.rewards || new Map(emptyRowAccountTypes);
|
||||||
|
const rewardItem = rewards?.get(reward.rewardType);
|
||||||
|
const amount = (
|
||||||
|
(Number(rewardItem?.amount) || 0) + Number(reward.amount)
|
||||||
).toString();
|
).toString();
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
rewards?.set(reward.rewardType, {
|
||||||
}, [] as AggregatedEpochRewardSummary[]);
|
rewardType: reward.rewardType,
|
||||||
|
amount,
|
||||||
return {
|
|
||||||
epoch: epochSummaries[0].epoch,
|
|
||||||
assetRewards: assetRewards.sort((a, b) => {
|
|
||||||
return new BigNumber(b.totalAmount).comparedTo(a.totalAmount);
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return epochTotalRewards;
|
epoch.assetRewards.set(reward.assetId, {
|
||||||
|
assetId: reward.assetId,
|
||||||
|
name: matchingAsset?.name || '',
|
||||||
|
rewards: rewards || new Map(emptyRowAccountTypes),
|
||||||
|
totalAmount: (
|
||||||
|
Number(reward.amount) + Number(assetWithRewards?.totalAmount || 0)
|
||||||
|
).toString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, map);
|
||||||
};
|
};
|
||||||
|
@ -23,12 +23,18 @@ fragment DelegationFields on Delegation {
|
|||||||
|
|
||||||
query Rewards(
|
query Rewards(
|
||||||
$partyId: ID!
|
$partyId: ID!
|
||||||
$delegationsPagination: Pagination
|
$fromEpoch: Int
|
||||||
|
$toEpoch: Int
|
||||||
$rewardsPagination: Pagination
|
$rewardsPagination: Pagination
|
||||||
|
$delegationsPagination: Pagination
|
||||||
) {
|
) {
|
||||||
party(id: $partyId) {
|
party(id: $partyId) {
|
||||||
id
|
id
|
||||||
rewardsConnection(pagination: $rewardsPagination) {
|
rewardsConnection(
|
||||||
|
fromEpoch: $fromEpoch
|
||||||
|
toEpoch: $toEpoch
|
||||||
|
pagination: $rewardsPagination
|
||||||
|
) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
...RewardFields
|
...RewardFields
|
||||||
@ -43,14 +49,6 @@ query Rewards(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
epoch {
|
|
||||||
id
|
|
||||||
timestamps {
|
|
||||||
start
|
|
||||||
end
|
|
||||||
expiry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment EpochRewardSummaryFields on EpochRewardSummary {
|
fragment EpochRewardSummaryFields on EpochRewardSummary {
|
||||||
@ -60,7 +58,10 @@ fragment EpochRewardSummaryFields on EpochRewardSummary {
|
|||||||
rewardType
|
rewardType
|
||||||
}
|
}
|
||||||
|
|
||||||
query EpochAssetsRewards($epochRewardSummariesPagination: Pagination) {
|
query EpochAssetsRewards(
|
||||||
|
$epochRewardSummariesFilter: RewardSummaryFilter
|
||||||
|
$epochRewardSummariesPagination: Pagination
|
||||||
|
) {
|
||||||
assetsConnection {
|
assetsConnection {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
@ -69,18 +70,16 @@ query EpochAssetsRewards($epochRewardSummariesPagination: Pagination) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
epochRewardSummaries(pagination: $epochRewardSummariesPagination) {
|
epochRewardSummaries(
|
||||||
|
filter: $epochRewardSummariesFilter
|
||||||
|
pagination: $epochRewardSummariesPagination
|
||||||
|
) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
...EpochRewardSummaryFields
|
...EpochRewardSummaryFields
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
epoch {
|
|
||||||
timestamps {
|
|
||||||
expiry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment EpochFields on Epoch {
|
fragment EpochFields on Epoch {
|
||||||
|
@ -9,21 +9,24 @@ export type DelegationFieldsFragment = { __typename?: 'Delegation', amount: stri
|
|||||||
|
|
||||||
export type RewardsQueryVariables = Types.Exact<{
|
export type RewardsQueryVariables = Types.Exact<{
|
||||||
partyId: Types.Scalars['ID'];
|
partyId: Types.Scalars['ID'];
|
||||||
delegationsPagination?: Types.InputMaybe<Types.Pagination>;
|
fromEpoch?: Types.InputMaybe<Types.Scalars['Int']>;
|
||||||
|
toEpoch?: Types.InputMaybe<Types.Scalars['Int']>;
|
||||||
rewardsPagination?: Types.InputMaybe<Types.Pagination>;
|
rewardsPagination?: Types.InputMaybe<Types.Pagination>;
|
||||||
|
delegationsPagination?: Types.InputMaybe<Types.Pagination>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type RewardsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, rewardsConnection?: { __typename?: 'RewardsConnection', edges?: Array<{ __typename?: 'RewardEdge', node: { __typename?: 'Reward', rewardType: Types.AccountType, amount: string, percentageOfTotal: string, receivedAt: any, asset: { __typename?: 'Asset', id: string, symbol: string, name: string }, party: { __typename?: 'Party', id: string }, epoch: { __typename?: 'Epoch', id: string } } } | null> | null } | null, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number } } | null> | null } | null } | null, epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null, expiry?: any | null } } };
|
export type RewardsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, rewardsConnection?: { __typename?: 'RewardsConnection', edges?: Array<{ __typename?: 'RewardEdge', node: { __typename?: 'Reward', rewardType: Types.AccountType, amount: string, percentageOfTotal: string, receivedAt: any, asset: { __typename?: 'Asset', id: string, symbol: string, name: string }, party: { __typename?: 'Party', id: string }, epoch: { __typename?: 'Epoch', id: string } } } | null> | null } | null, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number } } | null> | null } | null } | null };
|
||||||
|
|
||||||
export type EpochRewardSummaryFieldsFragment = { __typename?: 'EpochRewardSummary', epoch: number, assetId: string, amount: string, rewardType: Types.AccountType };
|
export type EpochRewardSummaryFieldsFragment = { __typename?: 'EpochRewardSummary', epoch: number, assetId: string, amount: string, rewardType: Types.AccountType };
|
||||||
|
|
||||||
export type EpochAssetsRewardsQueryVariables = Types.Exact<{
|
export type EpochAssetsRewardsQueryVariables = Types.Exact<{
|
||||||
|
epochRewardSummariesFilter?: Types.InputMaybe<Types.RewardSummaryFilter>;
|
||||||
epochRewardSummariesPagination?: Types.InputMaybe<Types.Pagination>;
|
epochRewardSummariesPagination?: Types.InputMaybe<Types.Pagination>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type EpochAssetsRewardsQuery = { __typename?: 'Query', assetsConnection?: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, name: string } } | null> | null } | null, epochRewardSummaries?: { __typename?: 'EpochRewardSummaryConnection', edges?: Array<{ __typename?: 'EpochRewardSummaryEdge', node: { __typename?: 'EpochRewardSummary', epoch: number, assetId: string, amount: string, rewardType: Types.AccountType } } | null> | null } | null, epoch: { __typename?: 'Epoch', timestamps: { __typename?: 'EpochTimestamps', expiry?: any | null } } };
|
export type EpochAssetsRewardsQuery = { __typename?: 'Query', assetsConnection?: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, name: string } } | null> | null } | null, epochRewardSummaries?: { __typename?: 'EpochRewardSummaryConnection', edges?: Array<{ __typename?: 'EpochRewardSummaryEdge', node: { __typename?: 'EpochRewardSummary', epoch: number, assetId: string, amount: string, rewardType: Types.AccountType } } | null> | null } | null };
|
||||||
|
|
||||||
export type EpochFieldsFragment = { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null, expiry?: any | null } };
|
export type EpochFieldsFragment = { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null, expiry?: any | null } };
|
||||||
|
|
||||||
@ -76,10 +79,14 @@ export const EpochFieldsFragmentDoc = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const RewardsDocument = gql`
|
export const RewardsDocument = gql`
|
||||||
query Rewards($partyId: ID!, $delegationsPagination: Pagination, $rewardsPagination: Pagination) {
|
query Rewards($partyId: ID!, $fromEpoch: Int, $toEpoch: Int, $rewardsPagination: Pagination, $delegationsPagination: Pagination) {
|
||||||
party(id: $partyId) {
|
party(id: $partyId) {
|
||||||
id
|
id
|
||||||
rewardsConnection(pagination: $rewardsPagination) {
|
rewardsConnection(
|
||||||
|
fromEpoch: $fromEpoch
|
||||||
|
toEpoch: $toEpoch
|
||||||
|
pagination: $rewardsPagination
|
||||||
|
) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
...RewardFields
|
...RewardFields
|
||||||
@ -94,14 +101,6 @@ export const RewardsDocument = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
epoch {
|
|
||||||
id
|
|
||||||
timestamps {
|
|
||||||
start
|
|
||||||
end
|
|
||||||
expiry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
${RewardFieldsFragmentDoc}
|
${RewardFieldsFragmentDoc}
|
||||||
${DelegationFieldsFragmentDoc}`;
|
${DelegationFieldsFragmentDoc}`;
|
||||||
@ -119,8 +118,10 @@ ${DelegationFieldsFragmentDoc}`;
|
|||||||
* const { data, loading, error } = useRewardsQuery({
|
* const { data, loading, error } = useRewardsQuery({
|
||||||
* variables: {
|
* variables: {
|
||||||
* partyId: // value for 'partyId'
|
* partyId: // value for 'partyId'
|
||||||
* delegationsPagination: // value for 'delegationsPagination'
|
* fromEpoch: // value for 'fromEpoch'
|
||||||
|
* toEpoch: // value for 'toEpoch'
|
||||||
* rewardsPagination: // value for 'rewardsPagination'
|
* rewardsPagination: // value for 'rewardsPagination'
|
||||||
|
* delegationsPagination: // value for 'delegationsPagination'
|
||||||
* },
|
* },
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
@ -136,7 +137,7 @@ export type RewardsQueryHookResult = ReturnType<typeof useRewardsQuery>;
|
|||||||
export type RewardsLazyQueryHookResult = ReturnType<typeof useRewardsLazyQuery>;
|
export type RewardsLazyQueryHookResult = ReturnType<typeof useRewardsLazyQuery>;
|
||||||
export type RewardsQueryResult = Apollo.QueryResult<RewardsQuery, RewardsQueryVariables>;
|
export type RewardsQueryResult = Apollo.QueryResult<RewardsQuery, RewardsQueryVariables>;
|
||||||
export const EpochAssetsRewardsDocument = gql`
|
export const EpochAssetsRewardsDocument = gql`
|
||||||
query EpochAssetsRewards($epochRewardSummariesPagination: Pagination) {
|
query EpochAssetsRewards($epochRewardSummariesFilter: RewardSummaryFilter, $epochRewardSummariesPagination: Pagination) {
|
||||||
assetsConnection {
|
assetsConnection {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
@ -145,18 +146,16 @@ export const EpochAssetsRewardsDocument = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
epochRewardSummaries(pagination: $epochRewardSummariesPagination) {
|
epochRewardSummaries(
|
||||||
|
filter: $epochRewardSummariesFilter
|
||||||
|
pagination: $epochRewardSummariesPagination
|
||||||
|
) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
...EpochRewardSummaryFields
|
...EpochRewardSummaryFields
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
epoch {
|
|
||||||
timestamps {
|
|
||||||
expiry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
${EpochRewardSummaryFieldsFragmentDoc}`;
|
${EpochRewardSummaryFieldsFragmentDoc}`;
|
||||||
|
|
||||||
@ -172,6 +171,7 @@ export const EpochAssetsRewardsDocument = gql`
|
|||||||
* @example
|
* @example
|
||||||
* const { data, loading, error } = useEpochAssetsRewardsQuery({
|
* const { data, loading, error } = useEpochAssetsRewardsQuery({
|
||||||
* variables: {
|
* variables: {
|
||||||
|
* epochRewardSummariesFilter: // value for 'epochRewardSummariesFilter'
|
||||||
* epochRewardSummariesPagination: // value for 'epochRewardSummariesPagination'
|
* epochRewardSummariesPagination: // value for 'epochRewardSummariesPagination'
|
||||||
* },
|
* },
|
||||||
* });
|
* });
|
||||||
|
@ -136,11 +136,15 @@ export const RewardsPage = () => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{toggleRewardsView === 'total' ? (
|
{toggleRewardsView === 'total' ? (
|
||||||
<EpochTotalRewards />
|
epochData?.epoch ? (
|
||||||
|
<EpochTotalRewards currentEpoch={epochData?.epoch} />
|
||||||
|
) : null
|
||||||
) : (
|
) : (
|
||||||
<section>
|
<section>
|
||||||
{pubKey && pubKeys?.length ? (
|
{pubKey && pubKeys?.length ? (
|
||||||
<EpochIndividualRewards />
|
epochData?.epoch ? (
|
||||||
|
<EpochIndividualRewards currentEpoch={epochData?.epoch} />
|
||||||
|
) : null
|
||||||
) : (
|
) : (
|
||||||
<ConnectToSeeRewards />
|
<ConnectToSeeRewards />
|
||||||
)}
|
)}
|
||||||
|
@ -25,6 +25,7 @@ export * from './nav-dropdown';
|
|||||||
export * from './nav';
|
export * from './nav';
|
||||||
export * from './navigation';
|
export * from './navigation';
|
||||||
export * from './notification';
|
export * from './notification';
|
||||||
|
export * from './pagination';
|
||||||
export * from './popover';
|
export * from './popover';
|
||||||
export * from './progress-bar';
|
export * from './progress-bar';
|
||||||
export * from './radio-group';
|
export * from './radio-group';
|
||||||
|
1
libs/ui-toolkit/src/components/pagination/index.tsx
Normal file
1
libs/ui-toolkit/src/components/pagination/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './pagination';
|
@ -0,0 +1,28 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Pagination } from './pagination';
|
||||||
|
|
||||||
|
import type { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
|
export default {
|
||||||
|
title: 'Pagination',
|
||||||
|
component: Pagination,
|
||||||
|
} as ComponentMeta<typeof Pagination>;
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof Pagination> = (args) => {
|
||||||
|
const MAX_PAGE = 3;
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
hasPrevPage={page !== 1}
|
||||||
|
hasNextPage={page < MAX_PAGE}
|
||||||
|
onBack={() => setPage(Math.max(1, page - 1))}
|
||||||
|
onNext={() => setPage(Math.min(MAX_PAGE, page + 1))}
|
||||||
|
>
|
||||||
|
Page {page}
|
||||||
|
</Pagination>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Default = Template.bind({});
|
73
libs/ui-toolkit/src/components/pagination/pagination.tsx
Normal file
73
libs/ui-toolkit/src/components/pagination/pagination.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { Button } from '../button';
|
||||||
|
import { Icon } from '../icon';
|
||||||
|
|
||||||
|
export type PaginationProps = {
|
||||||
|
hasPrevPage: boolean;
|
||||||
|
hasNextPage: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
|
children?: ReactNode;
|
||||||
|
onBack: () => void;
|
||||||
|
onNext: () => void;
|
||||||
|
onFirst?: () => void;
|
||||||
|
onLast?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonClass = 'rounded-full w-[34px] h-[34px]';
|
||||||
|
|
||||||
|
export const Pagination = ({
|
||||||
|
hasPrevPage,
|
||||||
|
hasNextPage,
|
||||||
|
isLoading,
|
||||||
|
children,
|
||||||
|
onBack,
|
||||||
|
onNext,
|
||||||
|
onFirst,
|
||||||
|
onLast,
|
||||||
|
}: PaginationProps) => {
|
||||||
|
return (
|
||||||
|
<div className={'flex gap-2 my-2 items-center justify-center'}>
|
||||||
|
{onFirst && (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
data-testid="goto-first-page"
|
||||||
|
disabled={isLoading || !hasPrevPage}
|
||||||
|
className={buttonClass}
|
||||||
|
onClick={onFirst}
|
||||||
|
>
|
||||||
|
<Icon name="double-chevron-left" ariaLabel="Back" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
data-testid="goto-previous-page"
|
||||||
|
disabled={isLoading || !hasPrevPage}
|
||||||
|
className={buttonClass}
|
||||||
|
onClick={onBack}
|
||||||
|
>
|
||||||
|
<Icon name="chevron-left" ariaLabel="Back" />
|
||||||
|
</Button>
|
||||||
|
{children}
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
data-testid="goto-next-page"
|
||||||
|
disabled={isLoading || !hasNextPage}
|
||||||
|
className={buttonClass}
|
||||||
|
onClick={onNext}
|
||||||
|
>
|
||||||
|
<Icon name="chevron-right" ariaLabel="Next" />
|
||||||
|
</Button>
|
||||||
|
{onLast && (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
data-testid="goto-last-page"
|
||||||
|
disabled={isLoading || !hasNextPage}
|
||||||
|
className={buttonClass}
|
||||||
|
onClick={onLast}
|
||||||
|
>
|
||||||
|
<Icon name="double-chevron-right" ariaLabel="Back" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user