// Copyright 2017-2025 @pezkuwi/app-staking authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { ApiPromise } from '@pezkuwi/api'; import type { DeriveHeartbeatAuthor } from '@pezkuwi/api-derive/types'; import type { Option } from '@pezkuwi/types'; import type { SlashingSpans, ValidatorPrefs } from '@pezkuwi/types/interfaces'; import type { BN } from '@pezkuwi/util'; import type { NominatedBy as NominatedByType, ValidatorInfo } from '../../types.js'; import type { NominatorValue } from './types.js'; import React, { useMemo } from 'react'; import { AddressSmall, Columar, Icon, LinkExternal, Table, Tag } from '@pezkuwi/react-components'; import { checkVisibility } from '@pezkuwi/react-components/util'; import { useApi, useCall, useDeriveAccountInfo, useToggle } from '@pezkuwi/react-hooks'; import { FormatBalance } from '@pezkuwi/react-query'; import { BN_ZERO } from '@pezkuwi/util'; import { useTranslation } from '../../translate.js'; import NominatedBy from './NominatedBy.js'; import StakeOther from './StakeOther.js'; import Status from './Status.js'; interface Props { address: string; className?: string; filterName: string; hasQueries: boolean; isElected: boolean; isFavorite: boolean; isMain?: boolean; isPara?: boolean; lastBlock?: string; minCommission?: BN; nominatedBy?: NominatedByType[]; points?: string; recentlyOnline?: DeriveHeartbeatAuthor; toggleFavorite: (accountId: string) => void; validatorInfo?: ValidatorInfo; withIdentity?: boolean; } interface StakingState { isChilled?: boolean; commission?: string; nominators?: NominatorValue[]; stakeTotal?: BN; stakeOther?: BN; stakeOwn?: BN; } function expandInfo ({ exposure, validatorPrefs }: ValidatorInfo, minCommission?: BN): StakingState { let nominators: NominatorValue[] | undefined; let stakeTotal: BN | undefined; let stakeOther: BN | undefined; let stakeOwn: BN | undefined; if (exposure?.total) { nominators = exposure.others.map(({ value, who }) => ({ nominatorId: who.toString(), value: value.unwrap() })); stakeTotal = exposure.total?.unwrap() || BN_ZERO; stakeOwn = exposure.own.unwrap(); stakeOther = stakeTotal.sub(stakeOwn); } const commission = (validatorPrefs as ValidatorPrefs)?.commission?.unwrap(); return { commission: commission?.toHuman(), isChilled: commission && minCommission && commission.isZero() && commission.lt(minCommission), nominators, stakeOther, stakeOwn, stakeTotal }; } const transformSlashes = { transform: (opt: Option) => opt.unwrapOr(null) }; function useAddressCalls (api: ApiPromise, address: string, isMain?: boolean) { const params = useMemo(() => [address], [address]); const accountInfo = useDeriveAccountInfo(address); const slashingSpans = useCall(!isMain && api.query.staking.slashingSpans, params, transformSlashes); return { accountInfo, slashingSpans }; } function Address ({ address, className = '', filterName, hasQueries, isElected, isFavorite, isMain, isPara, lastBlock, minCommission, nominatedBy, points, recentlyOnline, toggleFavorite, validatorInfo, withIdentity }: Props): React.ReactElement | null { const { t } = useTranslation(); const { api, apiIdentity } = useApi(); const [isExpanded, toggleIsExpanded] = useToggle(false); const { accountInfo, slashingSpans } = useAddressCalls(api, address, isMain); const { commission, isChilled, nominators, stakeOther, stakeOwn } = useMemo( () => validatorInfo ? expandInfo(validatorInfo, minCommission) : {}, [minCommission, validatorInfo] ); const isVisible = useMemo( () => accountInfo ? checkVisibility(apiIdentity, address, accountInfo, filterName, withIdentity) : true, [accountInfo, address, filterName, apiIdentity, withIdentity] ); const statsLink = useMemo( () => `#/staking/query/${address}`, [address] ); const pointsAnimClass = useMemo( () => points && `greyAnim-${Date.now() % 25}`, [points] ); if (!isVisible) { return null; } return ( <> {isMain && pointsAnimClass && ( )} {isMain ? ( ) : ( ) } {commission || 50.00%} {isMain && ( {lastBlock} )} {isExpanded && ( {isMain && stakeOwn?.gtn(0) && ( <>
{t('own stake')}
)}
{hasQueries && ( <>
{t('graphs')}
 {t('historic results')} )}
)} ); } export default React.memo(Address);