// Copyright 2017-2026 @pezkuwi/react-components authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { ApiPromise } from '@pezkuwi/api'; import type { DeriveBalancesAccountData, DeriveBalancesAll, DeriveDemocracyLock, DeriveStakingAccount } from '@pezkuwi/api-derive/types'; import type { VestingInfo } from '@pezkuwi/react-hooks'; import type { Raw } from '@pezkuwi/types'; import type { BlockNumber, ValidatorPrefsTo145, Voting } from '@pezkuwi/types/interfaces'; import type { PezpalletBalancesReserveData } from '@pezkuwi/types/lookup'; import type { BN } from '@pezkuwi/util'; import React, { useRef } from 'react'; import { withCalls, withMulti } from '@pezkuwi/react-api/hoc'; import { useBestNumberRelay, useStakingAsyncApis } from '@pezkuwi/react-hooks'; import { BlockToTime, FormatBalance } from '@pezkuwi/react-query'; import { BN_MAX_INTEGER, BN_ZERO, bnMax, formatBalance, formatNumber, isObject } from '@pezkuwi/util'; import { recalculateVesting } from './util/calculateVesting.js'; import CryptoType from './CryptoType.js'; import DemocracyLocks from './DemocracyLocks.js'; import Expander from './Expander.js'; import Icon from './Icon.js'; import Label from './Label.js'; import StakingRedeemable from './StakingRedeemable.js'; import StakingUnbonding from './StakingUnbonding.js'; import { styled } from './styled.js'; import Tooltip from './Tooltip.js'; import { useTranslation } from './translate.js'; // true to display, or (for bonded) provided values [own, ...all extras] export interface BalanceActiveType { available?: boolean; bonded?: boolean | BN[]; extraInfo?: [React.ReactNode, React.ReactNode][]; locked?: boolean; nonce?: boolean; redeemable?: boolean; reserved?: boolean; total?: boolean; unlocking?: boolean; vested?: boolean; } export interface CryptoActiveType { crypto?: boolean; nonce?: boolean; } export interface ValidatorPrefsType { unstakeThreshold?: boolean; validatorPayment?: boolean; } interface Props { apiOverride?: ApiPromise; address: string; balancesAll?: DeriveBalancesAll; children?: React.ReactNode; className?: string; convictionLocks?: RefLock[]; democracyLocks?: DeriveDemocracyLock[]; extraInfo?: [string, string][]; stakingInfo?: DeriveStakingAccount; vestingBestNumber?: BlockNumber; vestingInfo?: VestingInfo; votingOf?: Voting; withBalance?: boolean | BalanceActiveType; withBalanceToggle?: false; withExtended?: boolean | CryptoActiveType; withHexSessionId?: (string | null)[]; withValidatorPrefs?: boolean | ValidatorPrefsType; withLabel?: boolean; } interface RefLock { endBlock: BN; locked: string; refId: BN; total: BN; } type TFunction = (key: string, options?: { replace: Record }) => string; const DEFAULT_BALANCES: BalanceActiveType = { available: true, bonded: true, locked: true, redeemable: true, reserved: true, total: true, unlocking: true, vested: true }; const DEFAULT_EXTENDED = { crypto: true, nonce: true }; const DEFAULT_PREFS = { unstakeThreshold: true, validatorPayment: true }; // auxiliary component that helps aligning balances details, fills up the space when no icon for a balance is specified function IconVoid (): React.ReactElement { return  ; } function lookupLock (lookup: Record, lockId: Raw): string { const lockHex = lockId.toHuman() as string; try { return lookup[lockHex] || lockHex; } catch { return lockHex; } } // skip balances retrieval of none of this matches function skipBalancesIf ({ withBalance = true, withExtended = false }: Props): boolean { // NOTE Unsure why we don't have a check for balancesAll in here (check skipStakingIf). adding // it doesn't break on Accounts/Addresses, but this gets used a lot, so there _may_ be an actual // reason behind the madness. However, derives are memoized, so no issue overall. if (withBalance === true || withExtended === true) { return false; } else if (isObject(withBalance)) { // these all pull from the all balances if (withBalance.available || withBalance.locked || withBalance.reserved || withBalance.total || withBalance.vested) { return false; } } else if (isObject(withExtended)) { if (withExtended.nonce) { return false; } } return true; } function skipStakingIf ({ stakingInfo, withBalance = true, withValidatorPrefs = false }: Props): boolean { if (stakingInfo) { return true; } else if (withBalance === true || withValidatorPrefs) { return false; } else if (isObject(withBalance)) { if (withBalance.unlocking || withBalance.redeemable) { return false; } else if (withBalance.bonded) { return Array.isArray(withBalance.bonded); } } return true; } // calculates the bonded, first being the own, the second being nominated function calcBonded (stakingInfo?: DeriveStakingAccount, bonded?: boolean | BN[]): [BN, BN[]] { let other: BN[] = []; let own = BN_ZERO; if (Array.isArray(bonded)) { other = bonded .filter((_, index) => index !== 0) .filter((value) => value.gt(BN_ZERO)); own = bonded[0]; } else if (stakingInfo?.stakingLedger?.active && stakingInfo.accountId.eq(stakingInfo.stashId)) { own = stakingInfo.stakingLedger.active.unwrap(); } return [own, other]; } function renderExtended ({ address, balancesAll, withExtended }: Props, t: TFunction): React.ReactNode { const extendedDisplay = withExtended === true ? DEFAULT_EXTENDED : withExtended || undefined; if (!extendedDisplay) { return null; } return (
{balancesAll && extendedDisplay.nonce && ( <>
); } function renderValidatorPrefs ({ stakingInfo, withValidatorPrefs = false }: Props, t: TFunction): React.ReactNode { const validatorPrefsDisplay = withValidatorPrefs === true ? DEFAULT_PREFS : withValidatorPrefs; if (!validatorPrefsDisplay || !stakingInfo?.validatorPrefs) { return null; } return ( <>
{validatorPrefsDisplay.unstakeThreshold && (stakingInfo.validatorPrefs as any as ValidatorPrefsTo145).unstakeThreshold && ( <>