// Copyright 2017-2025 @polkadot/react-hooks authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { Nominations, ValidatorPrefs } from '@polkadot/types/interfaces'; import type { KeyringJson$Meta } from '@polkadot/ui-keyring/types'; import type { HexString } from '@polkadot/util/types'; import type { AddressFlags, AddressIdentity, UseAccountInfo } from './types.js'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { keyring } from '@polkadot/ui-keyring'; import { isFunction, isHex } from '@polkadot/util'; import { isEmpty } from './utils/isEmpty.js'; import { createNamedHook } from './createNamedHook.js'; import { useApi } from './useApi.js'; import { useCall } from './useCall.js'; import { useDeriveAccountFlags } from './useDeriveAccountFlags.js'; import { useDeriveAccountInfo } from './useDeriveAccountInfo.js'; import { useKeyring } from './useKeyring.js'; import { useToggle } from './useToggle.js'; const IS_NONE = { isCouncil: false, isDevelopment: false, isEditable: false, isEthereum: false, isExternal: false, isFavorite: false, isHardware: false, isInContacts: false, isInjected: false, isMultisig: false, isNominator: false, isOwned: false, isProxied: false, isSociety: false, isSudo: false, isTechCommittee: false, isValidator: false }; function useAccountInfoImpl (value: string | null, isContract = false): UseAccountInfo { const { api, apiIdentity, apiSystemPeople } = useApi(); const { accounts: { isAccount }, addresses: { isAddress } } = useKeyring(); const accountInfo = useDeriveAccountInfo(value); const accountFlags = useDeriveAccountFlags(value); const nominator = useCall(api.query.staking?.nominators, [value]); const validator = useCall(api.query.staking?.validators, [value]); const [accountIndex, setAccountIndex] = useState(undefined); const [tags, setSortedTags] = useState([]); const [name, setName] = useState(''); const [genesisHash, setGenesisHash] = useState(null); const [identity, setIdentity] = useState(); const [flags, setFlags] = useState(IS_NONE); const [meta, setMeta] = useState(); const [isEditingName, toggleIsEditingName, setIsEditingName] = useToggle(); const [isEditingTags, toggleIsEditingTags, setIsEditingTags] = useToggle(); useEffect((): void => { validator && setFlags((flags) => ({ ...flags, isValidator: !isEmpty(validator) })); }, [validator]); useEffect((): void => { nominator && setFlags((flags) => ({ ...flags, isNominator: !isEmpty(nominator) })); }, [nominator]); useEffect((): void => { accountFlags && setFlags((flags) => ({ ...flags, ...accountFlags })); }, [accountFlags]); useEffect((): void => { const { accountIndex, identity, nickname } = accountInfo || {}; const newIndex = accountIndex?.toString(); setAccountIndex((oldIndex) => oldIndex !== newIndex ? newIndex : oldIndex ); let name; if (isFunction(apiIdentity.query.identity?.identityOf)) { if (identity?.display) { name = identity.display; } } else if (nickname) { name = nickname; } setName(name || ''); if (identity) { const judgements = identity.judgements.filter(([, judgement]) => !judgement.isFeePaid); const isKnownGood = judgements.some(([, judgement]) => judgement.isKnownGood); setIdentity({ ...identity, isExistent: !!identity.display, isKnownGood, judgements, waitCount: identity.judgements.length - judgements.length }); } else { setIdentity(undefined); } }, [accountInfo, api, apiSystemPeople, apiIdentity]); useEffect((): void => { if (value) { try { const accountOrAddress = keyring.getAccount(value) || keyring.getAddress(value); const isOwned = isAccount(value); const isInContacts = isAddress(value); setGenesisHash(accountOrAddress?.meta.genesisHash || null); setFlags((flags): AddressFlags => ({ ...flags, isDevelopment: accountOrAddress?.meta.isTesting || false, isEditable: !!(!identity?.display && (isInContacts || accountOrAddress?.meta.isMultisig || (accountOrAddress && !(accountOrAddress.meta.isInjected)))) || false, isEthereum: isHex(value, 160), isExternal: !!accountOrAddress?.meta.isExternal || false, isHardware: !!accountOrAddress?.meta.isHardware || false, isInContacts, isInjected: !!accountOrAddress?.meta.isInjected || false, isMultisig: !!accountOrAddress?.meta.isMultisig || false, isOwned, isProxied: !!accountOrAddress?.meta.isProxied || false })); setMeta(accountOrAddress?.meta); setName(accountOrAddress?.meta.name || ''); setSortedTags(accountOrAddress?.meta.tags?.sort() || []); } catch { // ignore } } }, [identity, isAccount, isAddress, value]); const onSaveName = useCallback( (): void => { if (isEditingName) { toggleIsEditingName(); } const meta = { name, whenEdited: Date.now() }; if (isContract) { try { if (value) { const originalMeta = keyring.getAddress(value)?.meta; keyring.saveContract(value, { ...originalMeta, ...meta }); } } catch (error) { console.error(error); } } else if (value) { try { const pair = keyring.getPair(value); pair && keyring.saveAccountMeta(pair, meta); } catch { const pair = keyring.getAddress(value); if (pair) { keyring.saveAddress(value, meta); } else { keyring.saveAddress(value, { genesisHash: api.genesisHash.toHex(), ...meta }); } } } }, [api, isContract, isEditingName, name, toggleIsEditingName, value] ); const onSaveTags = useCallback( (): void => { const meta = { tags, whenEdited: Date.now() }; if (isContract) { try { if (value) { const originalMeta = keyring.getAddress(value)?.meta; value && keyring.saveContract(value, { ...originalMeta, ...meta }); } } catch (error) { console.error(error); } } else if (value) { try { const currentKeyring = keyring.getPair(value); currentKeyring && keyring.saveAccountMeta(currentKeyring, meta); } catch { keyring.saveAddress(value, meta); } } }, [isContract, tags, value] ); const onForgetAddress = useCallback( (): void => { if (isEditingName) { toggleIsEditingName(); } if (isEditingTags) { toggleIsEditingTags(); } try { value && keyring.forgetAddress(value); } catch (e) { console.error(e); } }, [isEditingName, isEditingTags, toggleIsEditingName, toggleIsEditingTags, value] ); const onSetGenesisHash = useCallback( (genesisHash: HexString | null): void => { if (value) { const account = keyring.getPair(value); account && keyring.saveAccountMeta(account, { ...account.meta, genesisHash }); setGenesisHash(genesisHash); } }, [value] ); const setTags = useCallback( (tags: string[]) => setSortedTags(tags.sort()), [] ); const isEditing = useCallback(() => isEditingName || isEditingTags, [isEditingName, isEditingTags]); return useMemo(() => ({ accountIndex, flags, genesisHash, identity, isEditing, isEditingName, isEditingTags, isNull: !value, meta, name, onForgetAddress, onSaveName, onSaveTags, onSetGenesisHash, setIsEditingName, setIsEditingTags, setName, setTags, tags, toggleIsEditingName, toggleIsEditingTags }), [accountIndex, flags, genesisHash, identity, isEditing, isEditingName, isEditingTags, meta, name, onForgetAddress, onSaveName, onSaveTags, onSetGenesisHash, setIsEditingName, setIsEditingTags, setName, setTags, tags, toggleIsEditingName, toggleIsEditingTags, value]); } export const useAccountInfo = createNamedHook('useAccountInfo', useAccountInfoImpl);