Files
nym/nym-wallet/src/context/mocks/delegations.tsx
T
Tommy Verrall 959a986e2c PR review comments
- Add `historical_node_identity` to `DelegationWithEverything` and populate via `lookup_historical_node_identity` in `delegate.rs` so search works after unbond.
- `searchDelegations` searches `historical_node_identity` and guards null/empty `node_identity` with optional chaining.
- Acceptance tests: historical identity search, bonded-unbonding vs synthetic branch semantics, empty-identity search safety.
- Fix linting
2026-06-08 12:23:00 +02:00

238 lines
6.9 KiB
TypeScript

import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
CurrencyDenom,
DecCoin,
DelegationWithEverything,
Fee,
FeeDetails,
TransactionExecuteResult,
} from '@nymproject/types';
import { DelegationContext } from '../delegations';
import { mockSleep } from './utils';
import { TPoolOption } from '../../components';
const SLEEP_MS = 1000;
let mockDelegations: DelegationWithEverything[] = [
{
mix_id: 1234,
node_identity: 'FiojKW7oY9WQmLCiYAsCA21tpowZHS6zcUoyYm319p6Z',
delegated_on_iso_datetime: new Date(2021, 1, 1).toDateString(),
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
amount: { amount: '10', denom: 'nym' },
owner: '',
block_height: BigInt(100),
cost_params: {
profit_margin_percent: '0.04',
interval_operating_cost: {
amount: '20',
denom: 'nym',
},
},
stake_saturation: '0.2',
avg_uptime_percent: 0.5,
accumulated_by_delegates: { amount: '0', denom: 'nym' },
accumulated_by_operator: { amount: '0', denom: 'nym' },
uses_vesting_contract_tokens: false,
pending_events: [],
mixnode_is_unbonding: false,
historical_node_identity: null,
errors: null,
},
{
mix_id: 5678,
node_identity: 'DT8S942S8AQs2zKHS9SVo1GyHmuca3pfL2uLhLksJ3D8',
unclaimed_rewards: { amount: '0.1', denom: 'nym' },
amount: { amount: '100', denom: 'nym' },
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
owner: '',
block_height: BigInt(4000),
stake_saturation: '0.5',
avg_uptime_percent: 0.1,
cost_params: {
profit_margin_percent: '0.04',
interval_operating_cost: {
amount: '60',
denom: 'nym',
},
},
accumulated_by_delegates: { amount: '0', denom: 'nym' },
accumulated_by_operator: { amount: '0', denom: 'nym' },
uses_vesting_contract_tokens: true,
pending_events: [],
mixnode_is_unbonding: false,
historical_node_identity: null,
errors: null,
},
];
export const MockDelegationContextProvider: FCWithChildren = ({ children }) => {
const [trigger, setTrigger] = useState<Date>(new Date());
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string>();
const [delegations, setDelegations] = useState<undefined | DelegationWithEverything[]>();
const [totalDelegations, setTotalDelegations] = useState<undefined | string>();
const [totalRewards, setTotalRewards] = useState<undefined | string>();
const [totalDelegationsAndRewards, setTotalDelegationsAndRewards] = useState<undefined | string>();
const [lastUpdatedAtMs, setLastUpdatedAtMs] = useState(0);
const [delegationItemErrors, setDelegationItemErrors] = useState<{ nodeId: string; errors: string }>();
const triggerStateUpdate = () => setTrigger(new Date());
const getDelegations = async (): Promise<DelegationWithEverything[]> =>
mockDelegations.sort((a, b) => a.node_identity.localeCompare(b.node_identity));
const recalculate = async () => {
const newDelegations = await getDelegations();
const newTotalDelegations = `${newDelegations.length * 100} NYM`;
const rewardsSum = newDelegations.reduce((acc, d) => {
const n = parseFloat(d.unclaimed_rewards?.amount ?? '0');
return acc + (Number.isFinite(n) ? n : 0);
}, 0);
const newTotalRewards = `${rewardsSum} nym`;
setDelegations(newDelegations);
setTotalDelegations(newTotalDelegations);
setTotalRewards(newTotalRewards);
setTotalDelegationsAndRewards(`${newTotalDelegations} + ${newTotalRewards}`);
setLastUpdatedAtMs(Date.now());
};
const addDelegation = async (
data: { mix_id: number; amount: DecCoin },
_tokenPool: TPoolOption,
_fee?: FeeDetails,
): Promise<TransactionExecuteResult> => {
await mockSleep(SLEEP_MS);
await recalculate();
triggerStateUpdate();
setTimeout(async () => {
mockDelegations = mockDelegations.map((d) => {
if (d.mix_id === data.mix_id) {
return { ...d, isPending: undefined };
}
return d;
});
await recalculate();
triggerStateUpdate();
}, 3000);
return {
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' },
};
};
const undelegate = async (mix_id: number, _fee?: Fee): Promise<TransactionExecuteResult> => {
await mockSleep(SLEEP_MS);
mockDelegations = mockDelegations.map((d) => {
if (d.mix_id === mix_id) {
return { ...d, isPending: { blockHeight: 5678, actionType: 'undelegate' } };
}
return d;
});
await recalculate();
triggerStateUpdate();
setTimeout(async () => {
mockDelegations = mockDelegations.filter((d) => d.mix_id !== mix_id);
await recalculate();
triggerStateUpdate();
}, 3000);
return {
logs_json: '',
msg_responses_json: '',
transaction_hash: '',
gas_info: {
gas_wanted: { gas_units: BigInt(1) },
gas_used: { gas_units: BigInt(1) },
},
fee: { amount: '1', denom: 'nym' as CurrencyDenom },
};
};
const undelegateVesting = async (_mix_id: number): Promise<TransactionExecuteResult> => ({
msg_responses_json: '',
logs_json: '',
transaction_hash: '',
gas_info: {
gas_wanted: { gas_units: BigInt(1) },
gas_used: { gas_units: BigInt(1) },
},
fee: { amount: '1', denom: 'nym' as CurrencyDenom },
});
const resetState = () => {
setIsLoading(true);
setError(undefined);
setTotalDelegations(undefined);
setTotalRewards(undefined);
setTotalDelegationsAndRewards(undefined);
setLastUpdatedAtMs(0);
setDelegations([]);
};
const refresh = useCallback(async () => {
resetState();
setTimeout(async () => {
try {
await mockSleep(SLEEP_MS);
await recalculate();
} catch (e) {
setError((e as Error).message);
}
setIsLoading(false);
}, 2000);
}, []);
useEffect(() => {
resetState();
refresh();
}, []);
const memoizedValue = useMemo(
() => ({
delegationItemErrors,
setDelegationItemErrors,
isLoading,
isFetching: isLoading,
isError: Boolean(error),
lastUpdatedAtMs,
delegations,
pendingDelegations: [],
totalDelegations,
totalRewards,
totalDelegationsAndRewards,
refresh,
addDelegation,
undelegate,
undelegateVesting,
}),
[
isLoading,
error,
delegations,
totalDelegations,
totalRewards,
totalDelegationsAndRewards,
lastUpdatedAtMs,
refresh,
addDelegation,
undelegate,
undelegateVesting,
delegationItemErrors,
trigger,
],
);
return <DelegationContext.Provider value={memoizedValue}>{children}</DelegationContext.Provider>;
};