From c4df05157a55c7e592e95d6da12edb34feae563a Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Wed, 6 May 2026 15:35:02 +0200 Subject: [PATCH] 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 --- nym-wallet/src/context/delegationQuery.ts | 25 ++--- .../src/context/delegationQueryKeys.test.ts | 4 + nym-wallet/src/context/delegationQueryKeys.ts | 8 +- nym-wallet/src/context/delegations.tsx | 14 ++- nym-wallet/src/context/mocks/rewards.tsx | 101 +++++++----------- nym-wallet/src/context/rewards.tsx | 12 --- 6 files changed, 74 insertions(+), 90 deletions(-) diff --git a/nym-wallet/src/context/delegationQuery.ts b/nym-wallet/src/context/delegationQuery.ts index 3431db38cd..f708ab2f81 100644 --- a/nym-wallet/src/context/delegationQuery.ts +++ b/nym-wallet/src/context/delegationQuery.ts @@ -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 { 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 { 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__']); + }); }); diff --git a/nym-wallet/src/context/delegationQueryKeys.ts b/nym-wallet/src/context/delegationQueryKeys.ts index 19e4ce54b2..b660b14d34 100644 --- a/nym-wallet/src/context/delegationQueryKeys.ts +++ b/nym-wallet/src/context/delegationQueryKeys.ts @@ -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, }; diff --git a/nym-wallet/src/context/delegations.tsx b/nym-wallet/src/context/delegations.tsx index 351be2049e..23b51ef7eb 100644 --- a/nym-wallet/src/context/delegations.tsx +++ b/nym-wallet/src/context/delegations.tsx @@ -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({ 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; diff --git a/nym-wallet/src/context/mocks/rewards.tsx b/nym-wallet/src/context/mocks/rewards.tsx index fe84e5cfe2..65fa112b8e 100644 --- a/nym-wallet/src/context/mocks/rewards.tsx +++ b/nym-wallet/src/context/mocks/rewards.tsx @@ -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 => { - if (!delegations) { - throw new Error('No delegations'); - } + const claimRewards = useCallback( + async (mixId: number): Promise => { + 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 => { - 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 {children}; diff --git a/nym-wallet/src/context/rewards.tsx b/nym-wallet/src/context/rewards.tsx index 060f5d3e48..f4a22eb8b6 100644 --- a/nym-wallet/src/context/rewards.tsx +++ b/nym-wallet/src/context/rewards.tsx @@ -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; claimRewards: (mixId: number, fee?: FeeDetails) => Promise; - redeemAllRewards: () => Promise; }; export const RewardsContext = createContext({ @@ -23,9 +17,6 @@ export const RewardsContext = createContext({ 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], );