mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-07-21 21:25:44 +00:00
d21bfb1320
Rebranded terminology: - Polkadot → Pezkuwi - Kusama → Dicle - Westend → Zagros - Rococo → PezkuwiChain - Substrate → Bizinikiwi - parachain → teyrchain Custom logos with Kurdistan brand colors (#e6007a → #86e62a): - bizinikiwi-hexagon.svg - sora-bizinikiwi.svg - hezscanner.svg - heztreasury.svg - pezkuwiscan.svg - pezkuwistats.svg - pezkuwiassembly.svg - pezkuwiholic.svg
844 lines
29 KiB
TypeScript
844 lines
29 KiB
TypeScript
// Copyright 2017-2025 @pezkuwi/app-accounts authors & contributors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// This is for the use of `Ledger`
|
|
//
|
|
/* eslint-disable deprecation/deprecation */
|
|
|
|
import type { ApiPromise } from '@pezkuwi/api';
|
|
import type { SubmittableExtrinsic } from '@pezkuwi/api/types';
|
|
import type { DeriveDemocracyLock, DeriveStakingAccount } from '@pezkuwi/api-derive/types';
|
|
import type { Ledger, LedgerGeneric } from '@pezkuwi/hw-ledger';
|
|
import type { ActionStatus } from '@pezkuwi/react-components/Status/types';
|
|
import type { Option } from '@pezkuwi/types';
|
|
import type { BlockNumber, ProxyDefinition, RecoveryConfig } from '@pezkuwi/types/interfaces';
|
|
import type { KeyringAddress, KeyringJson$Meta } from '@pezkuwi/ui-keyring/types';
|
|
import type { AccountBalance, Delegation } from '../types.js';
|
|
|
|
import FileSaver from 'file-saver';
|
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
|
|
import useAccountLocks from '@pezkuwi/app-referenda/useAccountLocks';
|
|
import { AddressInfo, AddressSmall, Badge, Button, ChainLock, Columar, CryptoType, Forget, LinkExternal, Menu, Popup, styled, Table, Tags, TransferModal } from '@pezkuwi/react-components';
|
|
import { useAccountInfo, useApi, useBalancesAll, useBestNumberRelay, useCall, useLedger, useQueue, useStakingAsyncApis, useStakingInfo, useToggle, useVesting } from '@pezkuwi/react-hooks';
|
|
import { keyring } from '@pezkuwi/ui-keyring';
|
|
import { settings } from '@pezkuwi/ui-settings';
|
|
import { BN, BN_ZERO, formatBalance, formatNumber, isFunction } from '@pezkuwi/util';
|
|
|
|
import Backup from '../modals/Backup.js';
|
|
import ChangePass from '../modals/ChangePass.js';
|
|
import DelegateModal from '../modals/Delegate.js';
|
|
import Derive from '../modals/Derive.js';
|
|
import IdentityMain from '../modals/IdentityMain.js';
|
|
import IdentitySub from '../modals/IdentitySub.js';
|
|
import MultisigApprove from '../modals/MultisigApprove.js';
|
|
import ProxyOverview from '../modals/ProxyOverview.js';
|
|
import RecoverAccount from '../modals/RecoverAccount.js';
|
|
import RecoverSetup from '../modals/RecoverSetup.js';
|
|
import UndelegateModal from '../modals/Undelegate.js';
|
|
import { useTranslation } from '../translate.js';
|
|
import { createMenuGroup } from '../util.js';
|
|
import useMultisigApprovals from './useMultisigApprovals.js';
|
|
import useProxies from './useProxies.js';
|
|
|
|
interface Props {
|
|
account: KeyringAddress;
|
|
className?: string;
|
|
delegation?: Delegation;
|
|
filter: string;
|
|
isFavorite: boolean;
|
|
proxy?: [ProxyDefinition[], BN];
|
|
setBalance: (address: string, value: AccountBalance) => void;
|
|
toggleFavorite: (address: string) => void;
|
|
onStatusChange: (status: ActionStatus) => void;
|
|
}
|
|
|
|
interface DemocracyUnlockable {
|
|
democracyUnlockTx: SubmittableExtrinsic<'promise'> | null;
|
|
ids: BN[];
|
|
}
|
|
|
|
interface ReferendaUnlockable {
|
|
referendaUnlockTx: SubmittableExtrinsic<'promise'> | null;
|
|
ids: [classId: BN, refId: BN][];
|
|
}
|
|
|
|
const BAL_OPTS_DEFAULT = {
|
|
available: false,
|
|
bonded: false,
|
|
locked: false,
|
|
redeemable: false,
|
|
reserved: false,
|
|
total: true,
|
|
unlocking: false,
|
|
vested: false
|
|
};
|
|
|
|
const BAL_OPTS_EXPANDED = {
|
|
available: true,
|
|
bonded: true,
|
|
locked: true,
|
|
nonce: true,
|
|
redeemable: true,
|
|
reserved: true,
|
|
total: false,
|
|
unlocking: true,
|
|
vested: true
|
|
};
|
|
|
|
function calcVisible (filter: string, name: string, tags: string[]): boolean {
|
|
if (filter.length === 0) {
|
|
return true;
|
|
}
|
|
|
|
const _filter = filter.toLowerCase();
|
|
|
|
return tags.reduce((result: boolean, tag: string): boolean => {
|
|
return result || tag.toLowerCase().includes(_filter);
|
|
}, name.toLowerCase().includes(_filter));
|
|
}
|
|
|
|
function calcUnbonding (stakingInfo?: DeriveStakingAccount) {
|
|
if (!stakingInfo?.unlocking) {
|
|
return BN_ZERO;
|
|
}
|
|
|
|
const filtered = stakingInfo.unlocking
|
|
.filter(({ remainingEras, value }) => value.gt(BN_ZERO) && remainingEras.gt(BN_ZERO))
|
|
.map((unlock) => unlock.value);
|
|
const total = filtered.reduce((total, value) => total.iadd(value), new BN(0));
|
|
|
|
return total;
|
|
}
|
|
|
|
function createClearDemocracyTx (api: ApiPromise, address: string, ids: BN[]): SubmittableExtrinsic<'promise'> | null {
|
|
return api.tx.utility && ids.length
|
|
? api.tx.utility.batch(
|
|
ids
|
|
.map((id) => api.tx.democracy.removeVote(id))
|
|
.concat(api.tx.democracy.unlock(address))
|
|
)
|
|
: null;
|
|
}
|
|
|
|
function createClearReferendaTx (api: ApiPromise, address: string, ids: [BN, BN][], palletReferenda = 'convictionVoting'): SubmittableExtrinsic<'promise'> | null {
|
|
if (!api.tx.utility || !ids.length) {
|
|
return null;
|
|
}
|
|
|
|
const inner = ids.map(([classId, refId]) => api.tx[palletReferenda].removeVote(classId, refId));
|
|
|
|
ids
|
|
.reduce((all: BN[], [classId]) => {
|
|
if (!all.find((id) => id.eq(classId))) {
|
|
all.push(classId);
|
|
}
|
|
|
|
return all;
|
|
}, [])
|
|
.forEach((classId): void => {
|
|
inner.push(api.tx[palletReferenda].unlock(classId, address));
|
|
});
|
|
|
|
return api.tx.utility.batch(inner);
|
|
}
|
|
|
|
async function showLedgerAddress (getLedger: () => LedgerGeneric | Ledger, meta: KeyringJson$Meta, ss58Prefix: number): Promise<void> {
|
|
const currApp = settings.get().ledgerApp;
|
|
const ledger = getLedger();
|
|
|
|
if (currApp === 'migration' || currApp === 'generic') {
|
|
await (ledger as LedgerGeneric).getAddress(ss58Prefix, true, meta.accountOffset || 0, meta.addressOffset || 0);
|
|
} else {
|
|
// This will always be the `chainSpecific` setting if the above condition is not met
|
|
await (ledger as Ledger).getAddress(true, meta.accountOffset || 0, meta.addressOffset || 0);
|
|
}
|
|
}
|
|
|
|
const transformRecovery = {
|
|
transform: (opt: Option<RecoveryConfig>) => opt.unwrapOr(null)
|
|
};
|
|
|
|
function Account ({ account: { address, meta }, className = '', delegation, filter, isFavorite, onStatusChange, proxy, setBalance, toggleFavorite }: Props): React.ReactElement<Props> | null {
|
|
const { t } = useTranslation();
|
|
const [isExpanded, toggleIsExpanded] = useToggle(false);
|
|
const { queueExtrinsic } = useQueue();
|
|
const { api, apiIdentity, enableIdentity, isDevelopment: isDevelopmentApiProps, isEthereum: isEthereumApiProps } = useApi();
|
|
const { getLedger } = useLedger();
|
|
const { ahApi, isRelayChain, rcApi } = useStakingAsyncApis();
|
|
const bestNumber = useBestNumberRelay();
|
|
const balancesAll = useBalancesAll(address);
|
|
const vestingInfoRaw = useVesting(address);
|
|
// Don't show vesting on relay chain - it's migrated to Asset Hub
|
|
// Users should connect to Asset Hub to view vesting info
|
|
const vestingInfo = isRelayChain ? undefined : vestingInfoRaw;
|
|
const stakingInfo = useStakingInfo(address);
|
|
|
|
// Vesting schedules use relay chain blocks after Asset Hub migration.
|
|
// When on Asset Hub, query relay chain block number for accurate vesting calculations.
|
|
// For other chains, use normal block numbers (no cross-chain adjustment needed).
|
|
const relayBestNumber = useCall<BlockNumber>(
|
|
rcApi?.derive.chain.bestNumber
|
|
);
|
|
|
|
// Use relay chain block for vesting ONLY when on Asset Hub with relay connection available.
|
|
// This corrects the block number mismatch caused by Asset Hub migration.
|
|
// For all other chains, use undefined to let normal block numbers apply.
|
|
const vestingBestNumber = (vestingInfo && rcApi) ? relayBestNumber : undefined;
|
|
const democracyLocks = useCall<DeriveDemocracyLock[]>(api.derive.democracy?.locks, [address]);
|
|
const recoveryInfo = useCall<RecoveryConfig | null>(api.query.recovery?.recoverable, [address], transformRecovery);
|
|
const multiInfos = useMultisigApprovals(address);
|
|
const proxyInfo = useProxies(address);
|
|
const { flags: { isDevelopment, isEditable, isEthereum, isExternal, isHardware, isInjected, isMultisig, isProxied }, genesisHash, identity, name: accName, onSetGenesisHash, tags } = useAccountInfo(address);
|
|
const convictionLocks = useAccountLocks('referenda', 'convictionVoting', address);
|
|
const [{ democracyUnlockTx }, setDemocracyUnlock] = useState<DemocracyUnlockable>({ democracyUnlockTx: null, ids: [] });
|
|
const [{ referendaUnlockTx }, setReferandaUnlock] = useState<ReferendaUnlockable>({ ids: [], referendaUnlockTx: null });
|
|
const [vestingVestTx, setVestingTx] = useState<SubmittableExtrinsic<'promise'> | null>(null);
|
|
const [isBackupOpen, toggleBackup] = useToggle();
|
|
const [isDeriveOpen, toggleDerive] = useToggle();
|
|
const [isForgetOpen, toggleForget] = useToggle();
|
|
const [isIdentityMainOpen, toggleIdentityMain] = useToggle();
|
|
const [isIdentitySubOpen, toggleIdentitySub] = useToggle();
|
|
const [isMultisigOpen, toggleMultisig] = useToggle();
|
|
const [isProxyOverviewOpen, toggleProxyOverview] = useToggle();
|
|
const [isPasswordOpen, togglePassword] = useToggle();
|
|
const [isRecoverAccountOpen, toggleRecoverAccount] = useToggle();
|
|
const [isRecoverSetupOpen, toggleRecoverSetup] = useToggle();
|
|
const [isTransferOpen, toggleTransfer] = useToggle();
|
|
const [isDelegateOpen, toggleDelegate] = useToggle();
|
|
const [isUndelegateOpen, toggleUndelegate] = useToggle();
|
|
|
|
useEffect((): void => {
|
|
if (balancesAll) {
|
|
setBalance(address, {
|
|
// some chains don't have "active" in the Ledger
|
|
bonded: stakingInfo?.stakingLedger.active?.unwrap() || BN_ZERO,
|
|
locked: balancesAll.lockedBalance,
|
|
redeemable: stakingInfo?.redeemable || BN_ZERO,
|
|
total: balancesAll.freeBalance.add(balancesAll.reservedBalance),
|
|
transferable: balancesAll.transferable || balancesAll.availableBalance,
|
|
unbonding: calcUnbonding(stakingInfo)
|
|
});
|
|
}
|
|
}, [address, balancesAll, setBalance, stakingInfo]);
|
|
|
|
useEffect((): void => {
|
|
// Vesting transactions must be sent to Asset Hub (after migration)
|
|
// Use ahApi when on relay chain, otherwise use the current api
|
|
const vestingApi = isRelayChain && ahApi ? ahApi : api;
|
|
|
|
if (vestingInfo && vestingApi.tx.vesting?.vest) {
|
|
setVestingTx(() =>
|
|
vestingInfo.vestingLocked.isZero()
|
|
? null
|
|
: vestingApi.tx.vesting.vest()
|
|
);
|
|
} else {
|
|
setVestingTx(null);
|
|
}
|
|
}, [address, ahApi, api, isRelayChain, vestingInfo]);
|
|
|
|
useEffect((): void => {
|
|
bestNumber && democracyLocks && setDemocracyUnlock(
|
|
(prev): DemocracyUnlockable => {
|
|
const ids = democracyLocks
|
|
.filter(({ isFinished, unlockAt }) => isFinished && bestNumber.gt(unlockAt))
|
|
.map(({ referendumId }) => referendumId);
|
|
|
|
if (JSON.stringify(prev.ids) === JSON.stringify(ids)) {
|
|
return prev;
|
|
}
|
|
|
|
return {
|
|
democracyUnlockTx: createClearDemocracyTx(api, address, ids),
|
|
ids
|
|
};
|
|
}
|
|
);
|
|
}, [address, api, bestNumber, democracyLocks]);
|
|
|
|
useEffect((): void => {
|
|
bestNumber && convictionLocks && setReferandaUnlock(
|
|
(prev): ReferendaUnlockable => {
|
|
const ids = convictionLocks
|
|
.filter(({ endBlock }) => endBlock.gt(BN_ZERO) && bestNumber.gt(endBlock))
|
|
.map(({ classId, refId }): [classId: BN, refId: BN] => [classId, refId]);
|
|
|
|
if (JSON.stringify(prev.ids) === JSON.stringify(ids)) {
|
|
return prev;
|
|
}
|
|
|
|
return {
|
|
ids,
|
|
referendaUnlockTx: createClearReferendaTx(api, address, ids)
|
|
};
|
|
}
|
|
);
|
|
}, [address, api, bestNumber, convictionLocks]);
|
|
|
|
const isVisible = useMemo(
|
|
() => calcVisible(filter, accName, tags),
|
|
[accName, filter, tags]
|
|
);
|
|
|
|
const _onForget = useCallback(
|
|
(): void => {
|
|
if (!address) {
|
|
return;
|
|
}
|
|
|
|
const status: Partial<ActionStatus> = {
|
|
account: address,
|
|
action: 'forget'
|
|
};
|
|
|
|
try {
|
|
keyring.forgetAccount(address);
|
|
status.status = 'success';
|
|
status.message = t('account forgotten');
|
|
} catch (error) {
|
|
status.status = 'error';
|
|
status.message = (error as Error).message;
|
|
}
|
|
},
|
|
[address, t]
|
|
);
|
|
|
|
const _onExportMultisig = useCallback(() => {
|
|
try {
|
|
if (!isMultisig) {
|
|
throw new Error('not a multisig account');
|
|
}
|
|
|
|
if (!meta.who) {
|
|
throw new Error('signatories not found');
|
|
}
|
|
|
|
const signatories: string[] = meta.who;
|
|
const blob = new Blob([JSON.stringify(signatories, null, 2)], { type: 'application/json; charset=utf-8' });
|
|
|
|
FileSaver.saveAs(blob, `${accName}_${address}_${new Date().getTime()}.json`);
|
|
} catch (error) {
|
|
const status: ActionStatus = {
|
|
account: address,
|
|
action: 'export',
|
|
message: (error as Error).message,
|
|
status: 'error'
|
|
};
|
|
|
|
onStatusChange(status);
|
|
}
|
|
}, [accName, address, isMultisig, meta.who, onStatusChange]);
|
|
|
|
const _clearDemocracyLocks = useCallback(
|
|
() => democracyUnlockTx && queueExtrinsic({
|
|
accountId: address,
|
|
extrinsic: democracyUnlockTx
|
|
}),
|
|
[address, democracyUnlockTx, queueExtrinsic]
|
|
);
|
|
|
|
const _clearReferendaLocks = useCallback(
|
|
() => referendaUnlockTx && queueExtrinsic({
|
|
accountId: address,
|
|
extrinsic: referendaUnlockTx
|
|
}),
|
|
[address, referendaUnlockTx, queueExtrinsic]
|
|
);
|
|
|
|
const _vestingVest = useCallback(
|
|
() => vestingVestTx && queueExtrinsic({
|
|
accountId: address,
|
|
extrinsic: vestingVestTx
|
|
}),
|
|
[address, queueExtrinsic, vestingVestTx]
|
|
);
|
|
|
|
const _showOnHardware = useCallback(
|
|
// TODO: we should check the hardwareType from metadata here as well,
|
|
// for now we are always assuming hardwareType === 'ledger'
|
|
(): void => {
|
|
showLedgerAddress(getLedger, meta, api.consts.system.ss58Prefix.toNumber()).catch((error): void => {
|
|
console.error(`ledger: ${(error as Error).message}`);
|
|
});
|
|
},
|
|
[getLedger, meta, api.consts.system.ss58Prefix]
|
|
);
|
|
|
|
const menuItems = useMemo(() => [
|
|
createMenuGroup('identityGroup', [
|
|
isFunction(apiIdentity.tx.identity?.setIdentity) && enableIdentity && !isHardware && (
|
|
<Menu.Item
|
|
icon='link'
|
|
key='identityMain'
|
|
label={t('Set on-chain identity')}
|
|
onClick={toggleIdentityMain}
|
|
/>
|
|
),
|
|
isFunction(apiIdentity.tx.identity?.setSubs) && enableIdentity && identity?.display && !isHardware && (
|
|
<Menu.Item
|
|
icon='vector-square'
|
|
key='identitySub'
|
|
label={t('Set on-chain sub-identities')}
|
|
onClick={toggleIdentitySub}
|
|
/>
|
|
),
|
|
isFunction(api.tx.democracy?.unlock) && democracyUnlockTx && (
|
|
<Menu.Item
|
|
icon='broom'
|
|
key='clearDemocracy'
|
|
label={t('Clear expired democracy locks')}
|
|
onClick={_clearDemocracyLocks}
|
|
/>
|
|
),
|
|
isFunction(api.tx.convictionVoting?.unlock) && referendaUnlockTx && (
|
|
<Menu.Item
|
|
icon='broom'
|
|
key='clearReferenda'
|
|
label={t('Clear expired referenda locks')}
|
|
onClick={_clearReferendaLocks}
|
|
/>
|
|
),
|
|
isFunction(api.tx.vesting?.vest) && vestingVestTx && (
|
|
<Menu.Item
|
|
icon='unlock'
|
|
key='vestingVest'
|
|
label={t('Unlock vested amount')}
|
|
onClick={_vestingVest}
|
|
/>
|
|
)
|
|
], t('Identity')),
|
|
createMenuGroup('deriveGroup', [
|
|
!(isEthereum || isExternal || isHardware || isInjected || isMultisig || isEthereumApiProps) && (
|
|
<Menu.Item
|
|
icon='download'
|
|
key='deriveAccount'
|
|
label={t('Derive account via derivation path')}
|
|
onClick={toggleDerive}
|
|
/>
|
|
),
|
|
isHardware && (
|
|
<Menu.Item
|
|
icon='eye'
|
|
key='showHwAddress'
|
|
label={t('Show address on hardware device')}
|
|
onClick={_showOnHardware}
|
|
/>
|
|
)
|
|
], t('Derive')),
|
|
createMenuGroup('backupGroup', [
|
|
!(isExternal || isHardware || isInjected || isMultisig || isDevelopment) && (
|
|
<Menu.Item
|
|
icon='database'
|
|
key='backupJson'
|
|
label={t('Create a backup file for this account')}
|
|
onClick={toggleBackup}
|
|
/>
|
|
),
|
|
!(isInjected || isDevelopment) && isMultisig && (
|
|
<Menu.Item
|
|
icon='database'
|
|
key='backupJson'
|
|
label={t('Export JSON file with signatories')}
|
|
onClick={_onExportMultisig}
|
|
/>
|
|
),
|
|
!(isExternal || isHardware || isInjected || isMultisig || isDevelopment) && (
|
|
<Menu.Item
|
|
icon='edit'
|
|
key='changePassword'
|
|
label={t("Change this account's password")}
|
|
onClick={togglePassword}
|
|
/>
|
|
),
|
|
!(isInjected || isDevelopment) && (
|
|
<Menu.Item
|
|
icon='trash-alt'
|
|
key='forgetAccount'
|
|
label={t('Forget this account')}
|
|
onClick={toggleForget}
|
|
/>
|
|
)
|
|
], t('Backup')),
|
|
isFunction(api.tx.recovery?.createRecovery) && createMenuGroup('reoveryGroup', [
|
|
!recoveryInfo && (
|
|
<Menu.Item
|
|
icon='redo'
|
|
key='makeRecoverable'
|
|
label={t('Make recoverable')}
|
|
onClick={toggleRecoverSetup}
|
|
/>
|
|
),
|
|
<Menu.Item
|
|
icon='screwdriver'
|
|
key='initRecovery'
|
|
label={t('Initiate recovery for another')}
|
|
onClick={toggleRecoverAccount}
|
|
/>
|
|
], t('Recovery')),
|
|
isFunction(api.tx.multisig?.asMulti) && isMultisig && createMenuGroup('multisigGroup', [
|
|
<Menu.Item
|
|
icon='file-signature'
|
|
isDisabled={!multiInfos?.length}
|
|
key='multisigApprovals'
|
|
label={t('Multisig approvals')}
|
|
onClick={toggleMultisig}
|
|
/>
|
|
], t('Multisig')),
|
|
isFunction(api.query.democracy?.votingOf) && delegation?.accountDelegated && createMenuGroup('undelegateGroup', [
|
|
<Menu.Item
|
|
icon='user-edit'
|
|
key='changeDelegate'
|
|
label={t('Change democracy delegation')}
|
|
onClick={toggleDelegate}
|
|
/>,
|
|
<Menu.Item
|
|
icon='user-minus'
|
|
key='undelegate'
|
|
label= {t('Undelegate')}
|
|
onClick={toggleUndelegate}
|
|
/>
|
|
], t('Undelegate')),
|
|
createMenuGroup('delegateGroup', [
|
|
isFunction(api.query.democracy?.votingOf) && !delegation?.accountDelegated && (
|
|
<Menu.Item
|
|
icon='user-plus'
|
|
key='delegate'
|
|
label={t('Delegate democracy votes')}
|
|
onClick={toggleDelegate}
|
|
/>
|
|
),
|
|
isFunction(api.query.proxy?.proxies) && (
|
|
<Menu.Item
|
|
icon='sitemap'
|
|
key='proxy-overview'
|
|
label={proxy?.[0].length
|
|
? t('Manage proxies')
|
|
: t('Add proxy')
|
|
}
|
|
onClick={toggleProxyOverview}
|
|
/>
|
|
)
|
|
], t('Delegate')),
|
|
isEditable && !isDevelopmentApiProps && createMenuGroup('genesisGroup', [
|
|
<ChainLock
|
|
className='accounts--network-toggle'
|
|
genesisHash={genesisHash}
|
|
key='chainlock'
|
|
onChange={onSetGenesisHash}
|
|
/>
|
|
])
|
|
].filter((i) => i),
|
|
[_clearDemocracyLocks, _clearReferendaLocks, _showOnHardware, _vestingVest, _onExportMultisig, api, apiIdentity.tx.identity, enableIdentity, delegation, democracyUnlockTx, genesisHash, identity, isDevelopment, isDevelopmentApiProps, isEthereumApiProps, isEditable, isEthereum, isExternal, isHardware, isInjected, isMultisig, multiInfos, onSetGenesisHash, proxy, referendaUnlockTx, recoveryInfo, t, toggleBackup, toggleDelegate, toggleDerive, toggleForget, toggleIdentityMain, toggleIdentitySub, toggleMultisig, togglePassword, toggleProxyOverview, toggleRecoverAccount, toggleRecoverSetup, toggleUndelegate, vestingVestTx]);
|
|
|
|
if (!isVisible) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<StyledTr className={`${className} isExpanded isFirst packedBottom`}>
|
|
<Table.Column.Favorite
|
|
address={address}
|
|
isFavorite={isFavorite}
|
|
toggle={toggleFavorite}
|
|
/>
|
|
<td className='address all relative'>
|
|
<AddressSmall
|
|
parentAddress={meta.parentAddress}
|
|
value={address}
|
|
withShortAddress
|
|
/>
|
|
{isBackupOpen && (
|
|
<Backup
|
|
address={address}
|
|
key='modal-backup-account'
|
|
onClose={toggleBackup}
|
|
/>
|
|
)}
|
|
{isDelegateOpen && (
|
|
<DelegateModal
|
|
key='modal-delegate'
|
|
onClose={toggleDelegate}
|
|
previousAmount={delegation?.amount}
|
|
previousConviction={delegation?.conviction}
|
|
previousDelegatedAccount={delegation?.accountDelegated}
|
|
previousDelegatingAccount={address}
|
|
/>
|
|
)}
|
|
{isDeriveOpen && (
|
|
<Derive
|
|
from={address}
|
|
key='modal-derive-account'
|
|
onClose={toggleDerive}
|
|
/>
|
|
)}
|
|
{isForgetOpen && (
|
|
<Forget
|
|
address={address}
|
|
key='modal-forget-account'
|
|
onClose={toggleForget}
|
|
onForget={_onForget}
|
|
/>
|
|
)}
|
|
{isIdentityMainOpen && (
|
|
<IdentityMain
|
|
address={address}
|
|
key='modal-identity-main'
|
|
onClose={toggleIdentityMain}
|
|
/>
|
|
)}
|
|
{isIdentitySubOpen && (
|
|
<IdentitySub
|
|
address={address}
|
|
key='modal-identity-sub'
|
|
onClose={toggleIdentitySub}
|
|
/>
|
|
)}
|
|
{isPasswordOpen && (
|
|
<ChangePass
|
|
address={address}
|
|
key='modal-change-pass'
|
|
onClose={togglePassword}
|
|
/>
|
|
)}
|
|
{isTransferOpen && (
|
|
<TransferModal
|
|
key='modal-transfer'
|
|
onClose={toggleTransfer}
|
|
senderId={address}
|
|
/>
|
|
)}
|
|
{isProxyOverviewOpen && (
|
|
<ProxyOverview
|
|
key='modal-proxy-overview'
|
|
onClose={toggleProxyOverview}
|
|
previousProxy={proxy}
|
|
proxiedAccount={address}
|
|
/>
|
|
)}
|
|
{isMultisig && isMultisigOpen && multiInfos && multiInfos.length !== 0 && (
|
|
<MultisigApprove
|
|
address={address}
|
|
key='multisig-approve'
|
|
onClose={toggleMultisig}
|
|
ongoing={multiInfos}
|
|
threshold={meta.threshold}
|
|
who={meta.who}
|
|
/>
|
|
)}
|
|
{isRecoverAccountOpen && (
|
|
<RecoverAccount
|
|
address={address}
|
|
key='recover-account'
|
|
onClose={toggleRecoverAccount}
|
|
/>
|
|
)}
|
|
{isRecoverSetupOpen && (
|
|
<RecoverSetup
|
|
address={address}
|
|
key='recover-setup'
|
|
onClose={toggleRecoverSetup}
|
|
/>
|
|
)}
|
|
{isUndelegateOpen && (
|
|
<UndelegateModal
|
|
accountDelegating={address}
|
|
key='modal-delegate'
|
|
onClose={toggleUndelegate}
|
|
/>
|
|
)}
|
|
<div className='absolute'>
|
|
{meta.genesisHash
|
|
? <Badge color='transparent' />
|
|
: isDevelopment
|
|
? (
|
|
<Badge
|
|
className='warning'
|
|
hover={t('This is a development account derived from the known development seed. Do not use for any funds on a non-development network.')}
|
|
icon='wrench'
|
|
/>
|
|
)
|
|
: (
|
|
<Badge
|
|
className='warning'
|
|
hover={
|
|
<div>
|
|
<p>{t('This account is available on all networks. It is recommended to link to a specific network via the account options ("only this network" option) to limit availability. For accounts from an extension, set the network on the extension.')}</p>
|
|
<p>{t('This does not send any transaction, rather it only sets the genesis in the account JSON.')}</p>
|
|
</div>
|
|
}
|
|
icon='exclamation-triangle'
|
|
/>
|
|
)
|
|
}
|
|
{recoveryInfo && (
|
|
<Badge
|
|
className='recovery'
|
|
hover={
|
|
<div>
|
|
<p>{t('This account is recoverable, with the following friends:')}</p>
|
|
<div>
|
|
{recoveryInfo.friends.map((friend, index): React.ReactNode => (
|
|
<AddressSmall
|
|
key={index}
|
|
value={friend}
|
|
/>
|
|
))}
|
|
</div>
|
|
<table>
|
|
<tbody>
|
|
<tr>
|
|
<td>{t('threshold')}</td>
|
|
<td>{formatNumber(recoveryInfo.threshold)}</td>
|
|
</tr>
|
|
<tr>
|
|
<td>{t('delay')}</td>
|
|
<td>{formatNumber(recoveryInfo.delayPeriod)}</td>
|
|
</tr>
|
|
<tr>
|
|
<td>{t('deposit')}</td>
|
|
<td>{formatBalance(recoveryInfo.deposit)}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
}
|
|
icon='redo'
|
|
/>
|
|
)}
|
|
{isProxied && proxyInfo?.isEmpty && (
|
|
<Badge
|
|
className='important'
|
|
hover={t('Proxied account has no owned proxies')}
|
|
icon='sitemap'
|
|
info='0'
|
|
/>
|
|
)}
|
|
{isMultisig && multiInfos && multiInfos.length !== 0 && (
|
|
<Badge
|
|
className='important'
|
|
color='purple'
|
|
hover={t('Multisig approvals pending')}
|
|
hoverAction={t('View pending approvals')}
|
|
icon='file-signature'
|
|
onClick={toggleMultisig}
|
|
/>
|
|
)}
|
|
{delegation?.accountDelegated && (
|
|
<Badge
|
|
className='information'
|
|
hover={t('This account has a governance delegation')}
|
|
hoverAction={t('Manage delegation')}
|
|
icon='calendar-check'
|
|
onClick={toggleDelegate}
|
|
/>
|
|
)}
|
|
{proxy && proxy[0].length !== 0 && api.tx.utility && (
|
|
<Badge
|
|
className='information'
|
|
hover={
|
|
proxy[0].length === 1
|
|
? t('This account has a proxy set')
|
|
: t('This account has {{proxyNumber}} proxies set', { replace: { proxyNumber: proxy[0].length } })
|
|
}
|
|
hoverAction={t('Manage proxies')}
|
|
icon='sitemap'
|
|
onClick={toggleProxyOverview}
|
|
/>
|
|
)}
|
|
</div>
|
|
</td>
|
|
<td className='actions button'>
|
|
<Button.Group>
|
|
{(isFunction(api.tx.balances?.transferAllowDeath) || isFunction(api.tx.balances?.transfer)) && (
|
|
<Button
|
|
className='send-button'
|
|
icon='paper-plane'
|
|
label={t('send')}
|
|
onClick={toggleTransfer}
|
|
/>
|
|
)}
|
|
<Popup
|
|
isDisabled={!menuItems.length}
|
|
value={
|
|
<Menu>
|
|
{menuItems}
|
|
</Menu>
|
|
}
|
|
/>
|
|
</Button.Group>
|
|
</td>
|
|
<Table.Column.Expand
|
|
isExpanded={isExpanded}
|
|
toggle={toggleIsExpanded}
|
|
/>
|
|
</StyledTr>
|
|
<StyledTr className={`${className} isExpanded ${isExpanded ? '' : 'isLast'} packedTop`}>
|
|
<td />
|
|
<td
|
|
className='balance all'
|
|
colSpan={2}
|
|
>
|
|
<AddressInfo
|
|
address={address}
|
|
balancesAll={balancesAll}
|
|
vestingBestNumber={vestingBestNumber}
|
|
vestingInfo={vestingInfo}
|
|
withBalance={BAL_OPTS_DEFAULT}
|
|
/>
|
|
</td>
|
|
<td />
|
|
</StyledTr>
|
|
<StyledTr className={`${className} ${isExpanded ? 'isExpanded isLast' : 'isCollapsed'} packedTop`}>
|
|
<td />
|
|
<td
|
|
className='balance columar'
|
|
colSpan={2}
|
|
>
|
|
<AddressInfo
|
|
address={address}
|
|
balancesAll={balancesAll}
|
|
convictionLocks={convictionLocks}
|
|
vestingBestNumber={vestingBestNumber}
|
|
vestingInfo={vestingInfo}
|
|
withBalance={BAL_OPTS_EXPANDED}
|
|
/>
|
|
<Columar size='tiny'>
|
|
<Columar.Column>
|
|
<div data-testid='tags'>
|
|
<Tags
|
|
value={tags}
|
|
withTitle
|
|
/>
|
|
</div>
|
|
</Columar.Column>
|
|
<Columar.Column>
|
|
<h5>{t('account type')}</h5>
|
|
<CryptoType accountId={address} />
|
|
</Columar.Column>
|
|
</Columar>
|
|
<Columar is100>
|
|
<Columar.Column>
|
|
<LinkExternal
|
|
data={address}
|
|
type='address'
|
|
withTitle
|
|
/>
|
|
</Columar.Column>
|
|
</Columar>
|
|
</td>
|
|
<td />
|
|
</StyledTr>
|
|
</>
|
|
);
|
|
}
|
|
|
|
const StyledTr = styled.tr`
|
|
.devBadge {
|
|
opacity: var(--opacity-light);
|
|
}
|
|
`;
|
|
|
|
export default React.memo(Account);
|