// Copyright 2017-2026 @pezkuwi/react-hooks authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { ApiPromise } from '@pezkuwi/api'; import type { CombinatorFunction } from '@pezkuwi/api/promise/Combinator'; import type { DeriveStakingAccount } from '@pezkuwi/api-derive/types'; import type { AccountId, ValidatorPrefs } from '@pezkuwi/types/interfaces'; import type { Codec, ITuple } from '@pezkuwi/types/types'; import type { StakerState } from './types.js'; import { useEffect, useMemo, useState } from 'react'; import { u8aConcat, u8aToHex } from '@pezkuwi/util'; import { isEmpty } from './utils/isEmpty.js'; import { createNamedHook } from './createNamedHook.js'; import { useAccounts } from './useAccounts.js'; import { useApi } from './useApi.js'; import { useIsMountedRef } from './useIsMountedRef.js'; import { useOwnStashes } from './useOwnStashes.js'; type ValidatorInfo = ITuple<[ValidatorPrefs, Codec]> | ValidatorPrefs; type Queried = Record; function toIdString (id?: AccountId | null): string | null { return id ? id.toString() : null; } const QUERY_OPTS = { withClaimedRewardsEras: true, withDestination: true, withLedger: true, withNominations: true, withPrefs: true }; function getStakerState (stashId: string, allAccounts: string[], [isOwnStash, { claimedRewardsEras, controllerId: _controllerId, exposureMeta, exposurePaged, nextSessionIds: _nextSessionIds, nominators, rewardDestination, sessionIds: _sessionIds, stakingLedger, validatorPrefs }, validateInfo]: [boolean, DeriveStakingAccount, ValidatorInfo]): StakerState { const isStashNominating = !!(nominators?.length); const isStashValidating = !(Array.isArray(validateInfo) ? isEmpty(validateInfo[1] as ValidatorPrefs) : isEmpty(validateInfo)); const nextSessionIds = _nextSessionIds instanceof Map ? [..._nextSessionIds.values()] : _nextSessionIds; const nextConcat = u8aConcat(...nextSessionIds.map((id) => id.toU8a())); const sessionIds = _sessionIds instanceof Map ? [..._sessionIds.values()] : _sessionIds; const currConcat = u8aConcat(...sessionIds.map((id) => id.toU8a())); const controllerId = toIdString(_controllerId); return { claimedRewardsEras, controllerId, destination: rewardDestination, exposureMeta, exposurePaged, hexSessionIdNext: u8aToHex(nextConcat, 48), hexSessionIdQueue: u8aToHex(currConcat.length ? currConcat : nextConcat, 48), isLoading: false, isOwnController: allAccounts.includes(controllerId || ''), isOwnStash, isStashNominating, isStashValidating, // we assume that all ids are non-null nominating: nominators?.map(toIdString) as string[], sessionIds: ( nextSessionIds.length ? nextSessionIds : sessionIds ).map(toIdString) as string[], stakingLedger, stashId, validatorPrefs }; } function useOwnStashInfosImpl (apiOverride?: ApiPromise): StakerState[] | undefined { const { api: connectedApi } = useApi(); const api = useMemo(() => apiOverride ?? connectedApi, [apiOverride, connectedApi]); const { allAccounts } = useAccounts(); const mountedRef = useIsMountedRef(); const ownStashes = useOwnStashes(undefined, api); const [queried, setQueried] = useState(); useEffect((): () => void => { let unsub: (() => void) | undefined; if (ownStashes) { if (ownStashes.length) { const stashIds = ownStashes.map(([stashId]) => stashId); const fns = [ [api.derive.staking.accounts, stashIds, QUERY_OPTS], [api.query.staking.validators.multi, stashIds] ] as unknown as CombinatorFunction[]; api.combineLatest<[DeriveStakingAccount[], ValidatorInfo[]]>(fns, ([accounts, validators]): void => { mountedRef.current && ownStashes.length === accounts.length && ownStashes.length === validators.length && setQueried( ownStashes.reduce((queried: Queried, [stashId, isOwnStash], index): Queried => ({ ...queried, [stashId]: [isOwnStash, accounts[index], validators[index]] }), {}) ); }).then((u): void => { unsub = u; }).catch(console.error); } else { mountedRef.current && setQueried({}); } } return (): void => { unsub && unsub(); }; }, [api, mountedRef, ownStashes]); return useMemo( () => ownStashes && queried && ownStashes.length === Object.keys(queried).length ? ownStashes .filter(([stashId]) => queried[stashId]) .map(([stashId]) => getStakerState(stashId, allAccounts, queried[stashId])) : undefined, [allAccounts, ownStashes, queried] ); } export const useOwnStashInfos = createNamedHook('useOwnStashInfos', useOwnStashInfosImpl);