Tighten delegation summary query keys and rewards context
- Remove redeemAllRewards / TRewardsTransaction from rewards context - Use a dedicated React Query key when no client address is set
This commit is contained in:
@@ -1,23 +1,21 @@
|
||||
import type { DelegationWithEverything, WrappedDelegationEvent } from '@nymproject/types';
|
||||
import type { DecCoin, DelegationWithEverything, WrappedDelegationEvent } from '@nymproject/types';
|
||||
import { getAllPendingDelegations, getDelegationSummary } from 'src/requests';
|
||||
import { decCoinToDisplay } from 'src/utils';
|
||||
|
||||
export type DelegationSummaryBundle = {
|
||||
delegations: (DelegationWithEverything | WrappedDelegationEvent)[];
|
||||
pendingDelegations: WrappedDelegationEvent[];
|
||||
totalDelegations: string;
|
||||
totalRewards: string;
|
||||
totalDelegationsAndRewards: string;
|
||||
totalDelegations: DecCoin;
|
||||
totalRewards: DecCoin;
|
||||
totalDelegationsAndRewards: DecCoin;
|
||||
};
|
||||
|
||||
export async function fetchDelegationSummaryQuery(): Promise<DelegationSummaryBundle> {
|
||||
const data = await getDelegationSummary();
|
||||
const pending = await getAllPendingDelegations();
|
||||
|
||||
const pendingOnNewNodes = pending.filter((event) => {
|
||||
const some = data.delegations.some(({ node_identity }) => node_identity === event.node_identity);
|
||||
return !some;
|
||||
});
|
||||
const delegatedIdentities = new Set(data.delegations.map((d) => d.node_identity));
|
||||
const pendingOnNewNodes = pending.filter((event) => !delegatedIdentities.has(event.node_identity));
|
||||
const items = data.delegations.map((delegation) => ({
|
||||
...delegation,
|
||||
amount: decCoinToDisplay(delegation.amount),
|
||||
@@ -30,14 +28,17 @@ export async function fetchDelegationSummaryQuery(): Promise<DelegationSummaryBu
|
||||
|
||||
const td = parseFloat(data.total_delegations.amount);
|
||||
const tr = parseFloat(data.total_rewards.amount);
|
||||
const delegationsAndRewards = Number.isFinite(td) && Number.isFinite(tr) ? (td + tr).toFixed(6) : '0';
|
||||
const combinedAmount = Number.isFinite(td) && Number.isFinite(tr) ? (td + tr).toFixed(6) : '0';
|
||||
|
||||
return {
|
||||
delegations: [...items, ...pendingOnNewNodes],
|
||||
pendingDelegations: pending,
|
||||
totalDelegations: `${data.total_delegations.amount} ${data.total_delegations.denom}`,
|
||||
totalRewards: `${data.total_rewards.amount} ${data.total_rewards.denom}`,
|
||||
totalDelegationsAndRewards: `${delegationsAndRewards} ${data.total_delegations.denom}`,
|
||||
totalDelegations: data.total_delegations,
|
||||
totalRewards: data.total_rewards,
|
||||
totalDelegationsAndRewards: {
|
||||
amount: combinedAmount,
|
||||
denom: data.total_delegations.denom,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,4 +4,8 @@ describe('delegationQueryKeys', () => {
|
||||
it('builds a stable summary key per client address', () => {
|
||||
expect(delegationQueryKeys.summary('nyc1test')).toStrictEqual(['delegation', 'summary', 'nyc1test']);
|
||||
});
|
||||
|
||||
it('uses a stable disabled summary key without empty address', () => {
|
||||
expect(delegationQueryKeys.summaryDisabled).toStrictEqual(['delegation', 'summary', '__disabled__']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
const delegationRoot = ['delegation'] as const;
|
||||
|
||||
export const delegationQueryKeys = {
|
||||
all: ['delegation'] as const,
|
||||
summary: (clientAddress: string) => [...delegationQueryKeys.all, 'summary', clientAddress] as const,
|
||||
all: delegationRoot,
|
||||
/** Used when no client address so React Query never caches `summary('')`. */
|
||||
summaryDisabled: [...delegationRoot, 'summary', '__disabled__'] as const,
|
||||
summary: (clientAddress: string) => [...delegationRoot, 'summary', clientAddress] as const,
|
||||
};
|
||||
|
||||
@@ -53,6 +53,10 @@ export const isPendingDelegation = (delegation: DelegationWithEvent): delegation
|
||||
export const isDelegation = (delegation: DelegationWithEvent): delegation is DelegationWithEverything =>
|
||||
'owner' in delegation;
|
||||
|
||||
function formatDelegationSummaryCoin(coin: DecCoin): string {
|
||||
return `${coin.amount} ${coin.denom}`;
|
||||
}
|
||||
|
||||
export const DelegationContext = createContext<TDelegationContext>({
|
||||
isLoading: false,
|
||||
isFetching: false,
|
||||
@@ -89,7 +93,7 @@ export const DelegationContextProvider: FC<{
|
||||
const [delegationItemErrors, setDelegationItemErrors] = React.useState<{ nodeId: string; errors: string }>();
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: delegationQueryKeys.summary(clientAddress ?? ''),
|
||||
queryKey: clientAddress ? delegationQueryKeys.summary(clientAddress) : delegationQueryKeys.summaryDisabled,
|
||||
queryFn: fetchDelegationSummaryQuery,
|
||||
enabled: Boolean(clientAddress) && onDelegationRoute,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
@@ -132,9 +136,11 @@ export const DelegationContextProvider: FC<{
|
||||
|
||||
const delegations = bundle?.delegations;
|
||||
const pendingDelegations = bundle?.pendingDelegations;
|
||||
const totalDelegations = bundle?.totalDelegations;
|
||||
const totalRewards = bundle?.totalRewards;
|
||||
const totalDelegationsAndRewards = bundle?.totalDelegationsAndRewards;
|
||||
const totalDelegations = bundle ? formatDelegationSummaryCoin(bundle.totalDelegations) : undefined;
|
||||
const totalRewards = bundle ? formatDelegationSummaryCoin(bundle.totalRewards) : undefined;
|
||||
const totalDelegationsAndRewards = bundle
|
||||
? formatDelegationSummaryCoin(bundle.totalDelegationsAndRewards)
|
||||
: undefined;
|
||||
|
||||
const isLoading = Boolean(clientAddress) && onDelegationRoute && query.isPending;
|
||||
const isFetching = Boolean(clientAddress) && onDelegationRoute && query.isFetching;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { DelegationWithEverything, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { RewardsContext, TRewardsTransaction } from '../rewards';
|
||||
import { RewardsContext } from '../rewards';
|
||||
import { useDelegationContext } from '../delegations';
|
||||
import { mockSleep } from './utils';
|
||||
|
||||
@@ -45,69 +45,51 @@ export const MockRewardsContextProvider: FCWithChildren = ({ children }) => {
|
||||
refresh();
|
||||
}, []);
|
||||
|
||||
const claimRewards = async (mixId: number): Promise<TransactionExecuteResult[]> => {
|
||||
if (!delegations) {
|
||||
throw new Error('No delegations');
|
||||
}
|
||||
const claimRewards = useCallback(
|
||||
async (mixId: number): Promise<TransactionExecuteResult[]> => {
|
||||
if (!delegations) {
|
||||
throw new Error('No delegations');
|
||||
}
|
||||
|
||||
const d = delegations.find((d1) => (d1 as DelegationWithEverything).mix_id === mixId);
|
||||
const d = delegations.find((d1) => (d1 as DelegationWithEverything).mix_id === mixId);
|
||||
|
||||
if (!d) {
|
||||
throw new Error(`Unable to find delegation for id = ${mixId}`);
|
||||
}
|
||||
if (!d) {
|
||||
throw new Error(`Unable to find delegation for id = ${mixId}`);
|
||||
}
|
||||
|
||||
await mockSleep(1000);
|
||||
await mockSleep(1000);
|
||||
|
||||
return [
|
||||
{
|
||||
transaction_hash: '55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354',
|
||||
fee: {
|
||||
amount: '1',
|
||||
denom: 'nym',
|
||||
return [
|
||||
{
|
||||
transaction_hash: '55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354',
|
||||
fee: {
|
||||
amount: '1',
|
||||
denom: 'nym',
|
||||
},
|
||||
logs_json: '[]',
|
||||
msg_responses_json: '[]',
|
||||
gas_info: {
|
||||
gas_wanted: { gas_units: BigInt(1) },
|
||||
gas_used: { gas_units: BigInt(1) },
|
||||
},
|
||||
},
|
||||
logs_json: '[]',
|
||||
msg_responses_json: '[]',
|
||||
gas_info: {
|
||||
gas_wanted: { gas_units: BigInt(1) },
|
||||
gas_used: { gas_units: BigInt(1) },
|
||||
{
|
||||
transaction_hash: '55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354',
|
||||
fee: {
|
||||
amount: '1',
|
||||
denom: 'nym',
|
||||
},
|
||||
msg_responses_json: '[]',
|
||||
logs_json: '[]',
|
||||
gas_info: {
|
||||
gas_wanted: { gas_units: BigInt(1) },
|
||||
gas_used: { gas_units: BigInt(1) },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
transaction_hash: '55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354',
|
||||
fee: {
|
||||
amount: '1',
|
||||
denom: 'nym',
|
||||
},
|
||||
msg_responses_json: '[]',
|
||||
logs_json: '[]',
|
||||
gas_info: {
|
||||
gas_wanted: { gas_units: BigInt(1) },
|
||||
gas_used: { gas_units: BigInt(1) },
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const redeemAllRewards = async (): Promise<TRewardsTransaction[]> => {
|
||||
if (!delegations) {
|
||||
throw new Error('No delegations');
|
||||
}
|
||||
|
||||
await mockSleep(1000);
|
||||
|
||||
return [
|
||||
{
|
||||
transactionUrl:
|
||||
'https://sandbox-blocks.nymtech.net/transactions/55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354',
|
||||
transactionHash: '55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354',
|
||||
},
|
||||
{
|
||||
transactionUrl:
|
||||
'https://sandbox-blocks.nymtech.net/transactions/55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354',
|
||||
transactionHash: '55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354',
|
||||
},
|
||||
];
|
||||
};
|
||||
];
|
||||
},
|
||||
[delegations],
|
||||
);
|
||||
|
||||
const memoizedValue = useMemo(
|
||||
() => ({
|
||||
@@ -116,9 +98,8 @@ export const MockRewardsContextProvider: FCWithChildren = ({ children }) => {
|
||||
totalRewards,
|
||||
refresh,
|
||||
claimRewards,
|
||||
redeemAllRewards,
|
||||
}),
|
||||
[isLoading, error, totalRewards],
|
||||
[isLoading, error, totalRewards, refresh, claimRewards],
|
||||
);
|
||||
|
||||
return <RewardsContext.Provider value={memoizedValue}>{children}</RewardsContext.Provider>;
|
||||
|
||||
@@ -3,18 +3,12 @@ import { FeeDetails, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { useDelegationContext } from './delegations';
|
||||
import { claimDelegatorRewards } from '../requests';
|
||||
|
||||
export type TRewardsTransaction = {
|
||||
transactionUrl: string;
|
||||
transactionHash: string;
|
||||
};
|
||||
|
||||
type TRewardsContext = {
|
||||
isLoading: boolean;
|
||||
error?: string;
|
||||
totalRewards?: string;
|
||||
refresh: () => Promise<void>;
|
||||
claimRewards: (mixId: number, fee?: FeeDetails) => Promise<TransactionExecuteResult[]>;
|
||||
redeemAllRewards: () => Promise<TRewardsTransaction[]>;
|
||||
};
|
||||
|
||||
export const RewardsContext = createContext<TRewardsContext>({
|
||||
@@ -23,9 +17,6 @@ export const RewardsContext = createContext<TRewardsContext>({
|
||||
claimRewards: async () => {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
redeemAllRewards: async () => {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
});
|
||||
|
||||
export const RewardsContextProvider: FCWithChildren = ({ children }) => {
|
||||
@@ -47,9 +38,6 @@ export const RewardsContextProvider: FCWithChildren = ({ children }) => {
|
||||
totalRewards,
|
||||
refresh,
|
||||
claimRewards: claimDelegatorRewards,
|
||||
redeemAllRewards: async () => {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
}),
|
||||
[isLoading, error, totalRewards, refresh],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user