959a986e2c
- 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
238 lines
6.9 KiB
TypeScript
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>;
|
|
};
|