// Copyright 2017-2025 @pezkuwi/app-staking-async authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { DeriveEraExposure, DeriveSessionIndexes } from '@pezkuwi/api-derive/types'; import type { BN } from '@pezkuwi/util'; import React, { useMemo } from 'react'; import { AddressMini, ExpanderScroll, MarkWarning, Spinner } from '@pezkuwi/react-components'; import { useApi, useCall } from '@pezkuwi/react-hooks'; import { isToBn } from '@pezkuwi/util'; import { useTranslation } from '../../translate.js'; import useInactives from '../useInactives.js'; interface Props { nominating?: string[]; stashId: string; } const EMPTY_MAP = {}; function mapExposure (stashId: string, all: string[], eraExposure?: DeriveEraExposure): Record { if (!eraExposure?.validators) { return EMPTY_MAP; } const nomBalanceMap: Record = {}; // for every active nominee all.forEach((nom) => { // cycle through its nominator to find our current stash eraExposure.validators[nom]?.others.some((o) => { // NOTE Some chains have non-standard implementations, without value if (o.who.eq(stashId) && isToBn(o.value)) { nomBalanceMap[nom] = o.value.toBn(); return true; } return false; }); }); return nomBalanceMap; } function renderNominators (stashId: string, all: string[] = [], eraExposure?: DeriveEraExposure): null | [number, () => React.ReactNode[]] { return all.length ? [ all.length, (): React.ReactNode[] => { const nomBalanceMap = mapExposure(stashId, all, eraExposure); return all.map((nomineeId, index): React.ReactNode => ( )); } ] : null; } function ListNominees ({ nominating, stashId }: Props): React.ReactElement { const { t } = useTranslation(); const { api } = useApi(); // 1. Fetch session info first. It's the primary dependency. const sessionInfo = useCall(api.query.staking && api.derive.session?.indexes); // 2. CORRECTED: Fetch eraExposure only when sessionInfo.activeEra is available. // Removed the broken check for 'erasStakers'. const eraExposure = useCall(sessionInfo && api.derive.staking.eraExposure, [sessionInfo?.activeEra]); // 3. The useInactives hook is self-contained and fetches its own data. const { nomsActive, nomsAtRisk, nomsInactive, nomsOver, nomsWaiting } = useInactives(stashId, nominating); const [renActive, renAtRisk, renInactive, renOver, renWaiting] = useMemo( () => [ renderNominators(stashId, nomsActive, eraExposure), // eraExposure is not needed for at-risk, as it's not about rewards renderNominators(stashId, nomsAtRisk), renderNominators(stashId, nomsInactive), renderNominators(stashId, nomsOver), renderNominators(stashId, nomsWaiting) ], [eraExposure, nomsActive, nomsAtRisk, nomsInactive, nomsOver, nomsWaiting, stashId] ); const isLoading = useMemo(() => !eraExposure || !nominating || nomsActive === undefined, [eraExposure, nominating, nomsActive]); if (isLoading) { return ( ); } return ( <> {renOver && ( )} {renActive && ( )} {renInactive && ( )} {renAtRisk && ( )} {renWaiting && ( )} {nomsActive && nomsInactive && (nomsActive.length === 0) && (nomsInactive.length !== 0) && ( )} ); } export default React.memo(ListNominees);