Improved session keys UX (#11909)
* feat: adding stash info for relay chain * feat: set session keys in relay chain explorer * fix: remove session keys setting from asset hub ui * feat: info fr setting session key * Update packages/page-staking-async/src/Actions/SessionKeyInfo.tsx Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update packages/page-staking-async/src/Actions/SessionKeyInfo.tsx Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --------- Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
This commit is contained in:
parent
09d508d3e5
commit
9edb3bec12
@ -87,9 +87,6 @@ function InjectKeys ({ onClose }: Props): React.ReactElement<Props> | null {
|
||||
size='large'
|
||||
>
|
||||
<Modal.Content>
|
||||
<Modal.Columns>
|
||||
<MarkWarning content={t('This operation will be performed on the relay chain.')} />
|
||||
</Modal.Columns>
|
||||
<Modal.Columns hint={t('The seed and derivation path will be submitted to the validator node. this is an advanced operation, only to be performed when you are sure of the security and connection risks.')}>
|
||||
<Input
|
||||
autoFocus
|
||||
|
||||
@ -12,7 +12,7 @@ import type { Slash } from '../types.js';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
import { AddressInfo, AddressMini, AddressSmall, Badge, Button, Menu, Popup, StakingBonded, StakingRedeemable, StakingUnbonding, styled, TxButton } from '@polkadot/react-components';
|
||||
import { useApi, useCall, useQueue, useToggle } from '@polkadot/react-hooks';
|
||||
import { useApi, useCall, useQueue, useStakingAsyncApis, useToggle } from '@polkadot/react-hooks';
|
||||
import { formatNumber, isFunction } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from '../../translate.js';
|
||||
@ -64,6 +64,7 @@ function useStashCalls (api: ApiPromise, stashId: string) {
|
||||
function Account ({ allSlashes, className = '', info: { controllerId, destination, hexSessionIdNext, hexSessionIdQueue, isLoading, isOwnController, isOwnStash, isStashNominating, isStashValidating, nominating, sessionIds, stakingLedger, stashId }, isDisabled, minCommission, targets }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const { isRelayChain } = useStakingAsyncApis();
|
||||
const { queueExtrinsic } = useQueue();
|
||||
const [isBondExtraOpen, toggleBondExtra] = useToggle();
|
||||
const [isInjectOpen, toggleInject] = useToggle();
|
||||
@ -241,7 +242,7 @@ function Account ({ allSlashes, className = '', info: { controllerId, destinatio
|
||||
{!isLoading && (
|
||||
<>
|
||||
{(isStashNominating || isStashValidating)
|
||||
? (
|
||||
? (!isRelayChain &&
|
||||
<TxButton
|
||||
accountId={controllerId}
|
||||
icon='stop'
|
||||
@ -255,6 +256,7 @@ function Account ({ allSlashes, className = '', info: { controllerId, destinatio
|
||||
<Button.Group>
|
||||
{(!sessionIds.length || hexSessionIdNext === '0x')
|
||||
? (
|
||||
isRelayChain &&
|
||||
<Button
|
||||
icon='sign-in-alt'
|
||||
isDisabled={!isOwnController || isDisabled}
|
||||
@ -264,101 +266,114 @@ function Account ({ allSlashes, className = '', info: { controllerId, destinatio
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Button
|
||||
icon='certificate'
|
||||
isDisabled={!isOwnController || isDisabled || !hasBonded}
|
||||
key='validate'
|
||||
label={t('Validate')}
|
||||
onClick={toggleValidate}
|
||||
/>
|
||||
!isRelayChain &&
|
||||
<Button
|
||||
icon='certificate'
|
||||
isDisabled={!isOwnController || isDisabled || !hasBonded}
|
||||
key='validate'
|
||||
label={t('Validate')}
|
||||
onClick={toggleValidate}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<Button
|
||||
icon='hand-paper'
|
||||
isDisabled={!isOwnController || isDisabled || !hasBonded}
|
||||
key='nominate'
|
||||
label={t('Nominate')}
|
||||
onClick={toggleNominate}
|
||||
/>
|
||||
{!isRelayChain &&
|
||||
<Button
|
||||
icon='hand-paper'
|
||||
isDisabled={!isOwnController || isDisabled || !hasBonded}
|
||||
key='nominate'
|
||||
label={t('Nominate')}
|
||||
onClick={toggleNominate}
|
||||
/>
|
||||
}
|
||||
</Button.Group>
|
||||
)
|
||||
}
|
||||
<Popup
|
||||
isDisabled={isDisabled}
|
||||
key='settings'
|
||||
value={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
isDisabled={!isOwnStash || !balancesAll?.freeBalance.gtn(0)}
|
||||
label={t('Bond more funds')}
|
||||
onClick={toggleBondExtra}
|
||||
/>
|
||||
<Menu.Item
|
||||
isDisabled={!isOwnController || !stakingAccount?.stakingLedger || stakingAccount.stakingLedger.active?.isEmpty}
|
||||
label={t('Unbond funds')}
|
||||
onClick={toggleUnbond}
|
||||
/>
|
||||
<Menu.Item
|
||||
isDisabled={!isOwnController || !stakingAccount?.unlocking?.length}
|
||||
label={t('Rebond funds')}
|
||||
onClick={toggleRebond}
|
||||
/>
|
||||
<Menu.Item
|
||||
isDisabled={!isOwnController || !stakingAccount?.redeemable || !stakingAccount.redeemable.gtn(0)}
|
||||
label={t('Withdraw unbonded funds')}
|
||||
onClick={withdrawFunds}
|
||||
/>
|
||||
<Menu.Divider />
|
||||
<Menu.Item
|
||||
isDisabled={!isOwnStash || !needsSetController}
|
||||
label={t('Change controller account')}
|
||||
onClick={toggleSetController}
|
||||
/>
|
||||
<Menu.Item
|
||||
isDisabled={!isOwnController}
|
||||
label={t('Change reward destination')}
|
||||
onClick={toggleRewardDestination}
|
||||
/>
|
||||
{isStashValidating && (
|
||||
<>
|
||||
{isRelayChain
|
||||
? (
|
||||
!isStashNominating &&
|
||||
<Popup
|
||||
isDisabled={isDisabled}
|
||||
key='settings'
|
||||
value={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
isDisabled={!isOwnController}
|
||||
label={t('Change validator preferences')}
|
||||
onClick={toggleValidate}
|
||||
label={t('Change session keys')}
|
||||
onClick={toggleSetSession}
|
||||
/>
|
||||
{isFunction(api.tx.staking.kick) && (
|
||||
<Menu.Item
|
||||
label={t('Inject session keys (advanced)')}
|
||||
onClick={toggleInject}
|
||||
/>
|
||||
</Menu>
|
||||
}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Popup
|
||||
isDisabled={isDisabled}
|
||||
key='settings'
|
||||
value={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
isDisabled={!isOwnStash || !balancesAll?.freeBalance.gtn(0)}
|
||||
label={t('Bond more funds')}
|
||||
onClick={toggleBondExtra}
|
||||
/>
|
||||
<Menu.Item
|
||||
isDisabled={!isOwnController || !stakingAccount?.stakingLedger || stakingAccount.stakingLedger.active?.isEmpty}
|
||||
label={t('Unbond funds')}
|
||||
onClick={toggleUnbond}
|
||||
/>
|
||||
<Menu.Item
|
||||
isDisabled={!isOwnController || !stakingAccount?.unlocking?.length}
|
||||
label={t('Rebond funds')}
|
||||
onClick={toggleRebond}
|
||||
/>
|
||||
<Menu.Item
|
||||
isDisabled={!isOwnController || !stakingAccount?.redeemable || !stakingAccount.redeemable.gtn(0)}
|
||||
label={t('Withdraw unbonded funds')}
|
||||
onClick={withdrawFunds}
|
||||
/>
|
||||
<Menu.Divider />
|
||||
<Menu.Item
|
||||
isDisabled={!isOwnStash || !needsSetController}
|
||||
label={t('Change controller account')}
|
||||
onClick={toggleSetController}
|
||||
/>
|
||||
<Menu.Item
|
||||
isDisabled={!isOwnController}
|
||||
label={t('Change reward destination')}
|
||||
onClick={toggleRewardDestination}
|
||||
/>
|
||||
{isStashValidating && (
|
||||
<>
|
||||
<Menu.Item
|
||||
isDisabled={!isOwnController}
|
||||
label={t('Change validator preferences')}
|
||||
onClick={toggleValidate}
|
||||
/>
|
||||
{isFunction(api.tx.staking.kick) && (
|
||||
<Menu.Item
|
||||
isDisabled={!isOwnController}
|
||||
label={t('Remove nominees')}
|
||||
onClick={toggleKick}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Menu.Divider />
|
||||
{isStashNominating && (
|
||||
<Menu.Item
|
||||
isDisabled={!isOwnController}
|
||||
label={t('Remove nominees')}
|
||||
onClick={toggleKick}
|
||||
isDisabled={!isOwnController || !targets.validators?.length}
|
||||
label={t('Set nominees')}
|
||||
onClick={toggleNominate}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Menu.Divider />
|
||||
{!isStashNominating && (
|
||||
<Menu.Item
|
||||
isDisabled={!isOwnController}
|
||||
label={t('Change session keys')}
|
||||
onClick={toggleSetSession}
|
||||
/>
|
||||
)}
|
||||
{isStashNominating && (
|
||||
<Menu.Item
|
||||
isDisabled={!isOwnController || !targets.validators?.length}
|
||||
label={t('Set nominees')}
|
||||
onClick={toggleNominate}
|
||||
/>
|
||||
)}
|
||||
{!isStashNominating && (
|
||||
<Menu.Item
|
||||
label={t('Inject session keys (advanced)')}
|
||||
onClick={toggleInject}
|
||||
/>
|
||||
)}
|
||||
</Menu>
|
||||
}
|
||||
/>
|
||||
</Menu>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
|
||||
38
packages/page-staking-async/src/Actions/SessionKeyInfo.tsx
Normal file
38
packages/page-staking-async/src/Actions/SessionKeyInfo.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright 2017-2025 @polkadot/app-staking-async authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Icon, styled } from '@polkadot/react-components';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
withIcon?: boolean;
|
||||
}
|
||||
|
||||
function SessionKeyInfo ({ children, className = '', withIcon = true }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<StyledArticle className={`${className} mark warning`}>
|
||||
{withIcon && <Icon icon='exclamation-triangle' />}
|
||||
The <strong>session pallet</strong> in Asset Hub is specifically for <strong>collator selection</strong>. You can no longer set session keys <strong> for your relay chain validators </strong> here.
|
||||
<br />
|
||||
<br />
|
||||
<p>As a validator, to manage your relay session keys, please navigate to the Relay Chain, set your session keys there, and then return here to continue with your staking actions.</p>
|
||||
{children}
|
||||
</StyledArticle>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
const StyledArticle = styled.article`
|
||||
max-width: 50vw;
|
||||
margin-inline: auto !important;
|
||||
|
||||
.ui--Icon {
|
||||
color: rgba(255, 196, 12, 1);
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(SessionKeyInfo);
|
||||
@ -11,7 +11,7 @@ import React, { useMemo, useRef, useState } from 'react';
|
||||
|
||||
import ElectionBanner from '@polkadot/app-staking/ElectionBanner';
|
||||
import { Button, ToggleGroup } from '@polkadot/react-components';
|
||||
import { useApi, useAvailableSlashes } from '@polkadot/react-hooks';
|
||||
import { useAvailableSlashes, useStakingAsyncApis } from '@polkadot/react-hooks';
|
||||
import { FormatBalance } from '@polkadot/react-query';
|
||||
import { BN, BN_ZERO } from '@polkadot/util';
|
||||
|
||||
@ -21,6 +21,7 @@ import NewNominator from './NewNominator.js';
|
||||
import NewStash from './NewStash.js';
|
||||
import NewValidator from './NewValidator.js';
|
||||
import Pools from './Pools.js';
|
||||
import SessionKeyInfo from './SessionKeyInfo.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
@ -117,8 +118,8 @@ function formatTotal (stashTypeIndex: number, state: State): React.ReactNode {
|
||||
|
||||
function Actions ({ className = '', isInElection, minCommission, ownPools, ownStashes, targets }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const allSlashes = useAvailableSlashes();
|
||||
const { ahApi, isRelayChain } = useStakingAsyncApis();
|
||||
const allSlashes = useAvailableSlashes(ahApi);
|
||||
const [accTypeIndex, setAccTypeIndex] = useState(0);
|
||||
const [stashTypeIndex, setStashTypeIndex] = useState(0);
|
||||
|
||||
@ -141,7 +142,7 @@ function Actions ({ className = '', isInElection, minCommission, ownPools, ownSt
|
||||
|
||||
const [filtered, footer] = useMemo(
|
||||
() => [
|
||||
state.foundStashes && filterStashes(stashTypeIndex, state.foundStashes),
|
||||
ahApi && state.foundStashes && filterStashes(stashTypeIndex, state.foundStashes),
|
||||
(
|
||||
<tr key='footer'>
|
||||
<td colSpan={4} />
|
||||
@ -150,13 +151,14 @@ function Actions ({ className = '', isInElection, minCommission, ownPools, ownSt
|
||||
</tr>
|
||||
)
|
||||
],
|
||||
[state, stashTypeIndex]
|
||||
[ahApi, state, stashTypeIndex]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{!isRelayChain && <SessionKeyInfo />}
|
||||
<Button.Group>
|
||||
{api.consts.nominationPools && (
|
||||
{ahApi?.consts.nominationPools && !isRelayChain && (
|
||||
<ToggleGroup
|
||||
onChange={setAccTypeIndex}
|
||||
options={accTypes.current}
|
||||
@ -170,16 +172,20 @@ function Actions ({ className = '', isInElection, minCommission, ownPools, ownSt
|
||||
options={stashTypes.current}
|
||||
value={stashTypeIndex}
|
||||
/>
|
||||
<NewNominator
|
||||
isInElection={isInElection}
|
||||
targets={targets}
|
||||
/>
|
||||
<NewValidator
|
||||
isInElection={isInElection}
|
||||
minCommission={minCommission}
|
||||
targets={targets}
|
||||
/>
|
||||
<NewStash />
|
||||
{!isRelayChain &&
|
||||
<>
|
||||
<NewNominator
|
||||
isInElection={isInElection}
|
||||
targets={targets}
|
||||
/>
|
||||
<NewValidator
|
||||
isInElection={isInElection}
|
||||
minCommission={minCommission}
|
||||
targets={targets}
|
||||
/>
|
||||
<NewStash />
|
||||
</>
|
||||
}
|
||||
</>
|
||||
)}
|
||||
</Button.Group>
|
||||
|
||||
@ -5,8 +5,8 @@ import type { SessionInfo } from './types.js';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { Input, MarkWarning, Modal } from '@polkadot/react-components';
|
||||
import { useApi, useStakingAsyncApis } from '@polkadot/react-hooks';
|
||||
import { Input, Modal } from '@polkadot/react-components';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
import { isHex } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from '../../translate.js';
|
||||
@ -26,26 +26,22 @@ const EMPTY_PROOF = new Uint8Array();
|
||||
function SessionKey ({ className = '', controllerId, onChange, stashId, withFocus, withSenders }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const { isStakingAsync, rcApi } = useStakingAsyncApis();
|
||||
const [keys, setKeys] = useState<string | null>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
try {
|
||||
onChange({
|
||||
sessionTx: isHex(keys)
|
||||
? (isStakingAsync ? rcApi : api)?.tx.session.setKeys(keys, EMPTY_PROOF)
|
||||
? api?.tx.session.setKeys(keys, EMPTY_PROOF)
|
||||
: null
|
||||
});
|
||||
} catch {
|
||||
onChange({ sessionTx: null });
|
||||
}
|
||||
}, [api, isStakingAsync, keys, onChange, rcApi]);
|
||||
}, [api?.tx.session, keys, onChange]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Modal.Columns>
|
||||
<MarkWarning content={t('This operation will be performed on the relay chain.')} />
|
||||
</Modal.Columns>
|
||||
{withSenders && (
|
||||
<SenderInfo
|
||||
controllerId={controllerId}
|
||||
|
||||
@ -9,7 +9,7 @@ import type { EraIndex, Exposure, Nominations, UnappliedSlash } from '@polkadot/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useCall, useIsMountedRef } from '@polkadot/react-hooks';
|
||||
import { createNamedHook, useCall, useIsMountedRef, useStakingAsyncApis } from '@polkadot/react-hooks';
|
||||
import { BN } from '@polkadot/util';
|
||||
|
||||
/**
|
||||
@ -36,7 +36,7 @@ interface NominationInfo {
|
||||
}
|
||||
|
||||
interface ExtractStateParams {
|
||||
api: ApiPromise;
|
||||
api?: ApiPromise;
|
||||
stashId: string;
|
||||
unappliedSlashes: string[];
|
||||
nominationsInfo: NominationInfo[];
|
||||
@ -56,7 +56,7 @@ function extractState (params: ExtractStateParams): Inactives {
|
||||
const { activeEra, activeValidators, api, nominationsInfo, stashId, submittedIn, unappliedSlashes } = params;
|
||||
|
||||
// The maximum number of nominators that can be rewarded for a single validator.
|
||||
const max = api.consts.staking?.maxNominatorRewardedPerValidator as u32 || new BN(512);
|
||||
const max = api?.consts.staking?.maxNominatorRewardedPerValidator as u32 || new BN(512);
|
||||
|
||||
const atRiskSet = new Set(unappliedSlashes);
|
||||
|
||||
@ -121,7 +121,7 @@ function extractState (params: ExtractStateParams): Inactives {
|
||||
}
|
||||
|
||||
function useInactivesImpl (stashId: string, nominees?: string[]): Inactives {
|
||||
const { api } = useApi();
|
||||
const { ahApi: api } = useStakingAsyncApis();
|
||||
const mountedRef = useIsMountedRef();
|
||||
const [state, setState] = useState<Inactives>({});
|
||||
|
||||
@ -131,11 +131,11 @@ function useInactivesImpl (stashId: string, nominees?: string[]): Inactives {
|
||||
// A list of the user's nominees who have pending slashes.
|
||||
const [unappliedSlashes, setUnappliedSlashes] = useState<string[]>([]);
|
||||
|
||||
const indexes = useCall<DeriveSessionIndexes>(api.derive.session?.indexes);
|
||||
const indexes = useCall<DeriveSessionIndexes>(api?.derive.session?.indexes);
|
||||
|
||||
// Fetches the current version of the staking pallet.
|
||||
const version = useCall<u16>(api.query.staking.palletVersion)?.toNumber();
|
||||
const eraExposure = useCall<DeriveEraExposure>(indexes && api.derive.staking.eraExposure, [indexes?.activeEra]);
|
||||
const version = useCall<u16>(api?.query.staking.palletVersion)?.toNumber();
|
||||
const eraExposure = useCall<DeriveEraExposure>(indexes && api?.derive.staking.eraExposure, [indexes?.activeEra]);
|
||||
|
||||
/**
|
||||
* @description Effect for fetching primary chain data.
|
||||
@ -145,10 +145,10 @@ function useInactivesImpl (stashId: string, nominees?: string[]): Inactives {
|
||||
let unsub: (() => void) | undefined;
|
||||
|
||||
if (mountedRef.current && nominees?.length && stashId && indexes) {
|
||||
api.queryMulti<[Option<Nominations>, Option<Vec<UnappliedSlash>>]>(
|
||||
api?.queryMulti<[Option<Nominations>, Option<Vec<UnappliedSlash>>]>(
|
||||
[
|
||||
[api.query.staking.nominators, stashId] as QueryableStorageMultiArg<'promise'>,
|
||||
[api.query.staking.unappliedSlashes, [indexes.activeEra, null]]
|
||||
[api?.query.staking.nominators, stashId] as QueryableStorageMultiArg<'promise'>,
|
||||
[api?.query.staking.unappliedSlashes, [indexes.activeEra, null]]
|
||||
],
|
||||
([optNominators, eraSlashes]) => {
|
||||
if (optNominators.isSome) {
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
import type { Option } from '@polkadot/types';
|
||||
import type { PalletStakingSlashingSlashingSpans } from '@polkadot/types/lookup';
|
||||
|
||||
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
|
||||
import { createNamedHook, useCall, useStakingAsyncApis } from '@polkadot/react-hooks';
|
||||
|
||||
const OPT_SPAN = {
|
||||
transform: (optSpans: Option<PalletStakingSlashingSlashingSpans>): number =>
|
||||
@ -14,9 +14,9 @@ const OPT_SPAN = {
|
||||
};
|
||||
|
||||
function useSlashingSpansImpl (stashId: string): number {
|
||||
const { api } = useApi();
|
||||
const { ahApi: api } = useStakingAsyncApis();
|
||||
|
||||
return useCall<number>(api.query.staking.slashingSpans, [stashId], OPT_SPAN) || 0;
|
||||
return useCall<number>(api?.query.staking.slashingSpans, [stashId], OPT_SPAN) || 0;
|
||||
}
|
||||
|
||||
export default createNamedHook('useSlashingSpans', useSlashingSpansImpl);
|
||||
|
||||
@ -6,16 +6,16 @@ import type { BN } from '@polkadot/util';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
|
||||
import { BN_ONE } from '@polkadot/util';
|
||||
import { createNamedHook, useCall, useStakingAsyncApis } from '@polkadot/react-hooks';
|
||||
import { BN_ONE, BN_ZERO } from '@polkadot/util';
|
||||
|
||||
function useUnbondDurationImpl (): BN | undefined {
|
||||
const { api } = useApi();
|
||||
const sessionInfo = useCall<DeriveSessionInfo>(api.derive.session.info);
|
||||
const { ahApi: api } = useStakingAsyncApis();
|
||||
const sessionInfo = useCall<DeriveSessionInfo>(api?.derive.session.info);
|
||||
|
||||
return useMemo(
|
||||
() => (sessionInfo && sessionInfo.sessionLength.gt(BN_ONE))
|
||||
? sessionInfo.eraLength.mul(api.consts.staking.bondingDuration)
|
||||
? sessionInfo.eraLength.mul(api?.consts.staking.bondingDuration ?? BN_ZERO)
|
||||
: undefined,
|
||||
[api, sessionInfo]
|
||||
);
|
||||
|
||||
@ -1,15 +1,21 @@
|
||||
// Copyright 2017-2025 @polkadot/app-staking-async authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type BN from 'bn.js';
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { AppProps } from '@polkadot/react-components/types';
|
||||
import type { ElectionStatus, ParaValidatorIndex, ValidatorId } from '@polkadot/types/interfaces';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
|
||||
import useSortedTargets from '@polkadot/app-staking/useSortedTargets';
|
||||
import { Tabs } from '@polkadot/react-components';
|
||||
import { useCallMulti, useFavorites, useOwnStashInfos } from '@polkadot/react-hooks';
|
||||
|
||||
import Actions from '../Actions/index.js';
|
||||
import CommandCenter from '../CommandCenter/index.js';
|
||||
import { STORE_FAVS_BASE } from '../constants.js';
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
interface Props extends AppProps {
|
||||
@ -20,15 +26,51 @@ interface Props extends AppProps {
|
||||
ahEndPoints: string[]
|
||||
}
|
||||
|
||||
function StakingApp ({ ahApi, ahEndPoints, basePath, isRelayChain, rcApi, rcEndPoints }: Props): React.ReactElement<Props> {
|
||||
const OPT_MULTI = {
|
||||
defaultValue: [false, undefined, {}] as [boolean, BN | undefined, Record<string, boolean>],
|
||||
transform: ([eraElectionStatus, minValidatorBond, validators, activeValidatorIndices]: [ElectionStatus | null, BN | undefined, ValidatorId[] | null, ParaValidatorIndex[] | null]): [boolean, BN | undefined, Record<string, boolean>] => [
|
||||
!!eraElectionStatus && eraElectionStatus.isOpen,
|
||||
minValidatorBond && !minValidatorBond.isZero()
|
||||
? minValidatorBond
|
||||
: undefined,
|
||||
validators && activeValidatorIndices
|
||||
? activeValidatorIndices.reduce((all, index) => ({ ...all, [validators[index.toNumber()].toString()]: true }), {})
|
||||
: {}
|
||||
]
|
||||
};
|
||||
|
||||
function StakingApp ({ ahApi: api, ahEndPoints, basePath, isRelayChain, rcApi, rcEndPoints }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [withLedger] = useState(false);
|
||||
const [favorites] = useFavorites(STORE_FAVS_BASE);
|
||||
const ownStashes = useOwnStashInfos(api);
|
||||
const targets = useSortedTargets(favorites, withLedger, api);
|
||||
const queries = useMemo(
|
||||
() => api
|
||||
? [
|
||||
api.query.staking.eraElectionStatus,
|
||||
api.query.staking.minCommission,
|
||||
api.query.session.validators,
|
||||
(api.query.parasShared || api.query.shared)?.activeValidatorIndices
|
||||
]
|
||||
: [],
|
||||
[api]
|
||||
);
|
||||
|
||||
const [isInElection, minCommission] = useCallMulti<[boolean, BN | undefined, Record<string, boolean>]>(queries, OPT_MULTI);
|
||||
|
||||
const items = useMemo(() => [
|
||||
{
|
||||
isRoot: true,
|
||||
name: 'actions',
|
||||
text: t('Accounts')
|
||||
},
|
||||
{
|
||||
name: 'command-center',
|
||||
text: t('Command Center')
|
||||
}], [t]);
|
||||
}
|
||||
], [t]);
|
||||
|
||||
return <>
|
||||
<Tabs
|
||||
@ -40,13 +82,24 @@ function StakingApp ({ ahApi, ahEndPoints, basePath, isRelayChain, rcApi, rcEndP
|
||||
<Route
|
||||
element={
|
||||
<CommandCenter
|
||||
ahApi={ahApi}
|
||||
ahApi={api}
|
||||
ahEndPoints={ahEndPoints}
|
||||
isRelayChain={isRelayChain}
|
||||
rcApi={rcApi}
|
||||
rcEndPoints={rcEndPoints}
|
||||
/>
|
||||
}
|
||||
path='command-center'
|
||||
/>
|
||||
<Route
|
||||
element={
|
||||
<Actions
|
||||
isInElection={isInElection}
|
||||
minCommission={minCommission}
|
||||
ownStashes={ownStashes}
|
||||
targets={targets}
|
||||
/>
|
||||
}
|
||||
index
|
||||
/>
|
||||
</Route>
|
||||
|
||||
@ -287,8 +287,9 @@ function extractBaseInfo (api: ApiPromise, allAccounts: string[], electedDerive:
|
||||
};
|
||||
}
|
||||
|
||||
function useSortedTargetsImpl (favorites: string[], withLedger: boolean): SortedTargets {
|
||||
const { api } = useApi();
|
||||
function useSortedTargetsImpl (favorites: string[], withLedger: boolean, apiOverride?: ApiPromise): SortedTargets {
|
||||
const { api: connectedApi } = useApi();
|
||||
const api = useMemo(() => apiOverride ?? connectedApi, [apiOverride, connectedApi]);
|
||||
const { allAccounts } = useAccounts();
|
||||
const { counterForNominators, counterForValidators, historyDepth, maxNominatorsCount, maxValidatorsCount, minNominatorBond, minValidatorBond, totalIssuance } = useCallMulti<MultiResult>([
|
||||
api.query.staking.historyDepth,
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
// Copyright 2017-2025 @polkadot/react-hooks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { DeriveSessionIndexes } from '@polkadot/api-derive/types';
|
||||
import type { Option } from '@polkadot/types';
|
||||
import type { EraIndex } from '@polkadot/types/interfaces';
|
||||
import type { PalletStakingUnappliedSlash } from '@polkadot/types/lookup';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { BN, BN_HUNDRED, BN_ONE, BN_ZERO } from '@polkadot/util';
|
||||
|
||||
@ -17,8 +18,9 @@ import { useIsMountedRef } from './useIsMountedRef.js';
|
||||
|
||||
type Unsub = () => void;
|
||||
|
||||
function useAvailableSlashesImpl (): [BN, PalletStakingUnappliedSlash[]][] {
|
||||
const { api } = useApi();
|
||||
function useAvailableSlashesImpl (apiOverride?: ApiPromise): [BN, PalletStakingUnappliedSlash[]][] {
|
||||
const { api: connectedApi } = useApi();
|
||||
const api = useMemo(() => apiOverride ?? connectedApi, [apiOverride, connectedApi]);
|
||||
const indexes = useCall<DeriveSessionIndexes>(api.derive.session?.indexes);
|
||||
const earliestSlash = useCall<Option<EraIndex>>(api.query.staking?.earliestUnappliedSlash);
|
||||
const mountedRef = useIsMountedRef();
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// Copyright 2017-2025 @polkadot/react-hooks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { CombinatorFunction } from '@polkadot/api/promise/Combinator';
|
||||
import type { DeriveStakingAccount } from '@polkadot/api-derive/types';
|
||||
import type { AccountId, ValidatorPrefs } from '@polkadot/types/interfaces';
|
||||
@ -74,11 +75,12 @@ function getStakerState (stashId: string, allAccounts: string[], [isOwnStash, {
|
||||
};
|
||||
}
|
||||
|
||||
function useOwnStashInfosImpl (): StakerState[] | undefined {
|
||||
const { api } = useApi();
|
||||
function useOwnStashInfosImpl (apiOverride?: ApiPromise): StakerState[] | undefined {
|
||||
const { api: connectedApi } = useApi();
|
||||
const api = useMemo(() => apiOverride ?? connectedApi, [apiOverride, connectedApi]);
|
||||
const { allAccounts } = useAccounts();
|
||||
const mountedRef = useIsMountedRef();
|
||||
const ownStashes = useOwnStashes();
|
||||
const ownStashes = useOwnStashes(undefined, api);
|
||||
const [queried, setQueried] = useState<Queried | undefined>();
|
||||
|
||||
useEffect((): () => void => {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// Copyright 2017-2025 @polkadot/react-hooks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { Option } from '@polkadot/types';
|
||||
import type { AccountId, StakingLedger } from '@polkadot/types/interfaces';
|
||||
|
||||
@ -31,9 +32,10 @@ function getStashes (allAccounts: string[], ownBonded: Option<AccountId>[], ownL
|
||||
return result;
|
||||
}
|
||||
|
||||
function useOwnStashesImpl (additional?: string[]): [string, IsInKeyring][] | undefined {
|
||||
function useOwnStashesImpl (additional?: string[], apiOverride?: ApiPromise): [string, IsInKeyring][] | undefined {
|
||||
const { allAccounts } = useAccounts();
|
||||
const { api } = useApi();
|
||||
const { api: connectedApi } = useApi();
|
||||
const api = useMemo(() => apiOverride ?? connectedApi, [apiOverride, connectedApi]);
|
||||
|
||||
const ids = useMemo(
|
||||
() => allAccounts.concat(additional || []),
|
||||
|
||||
@ -9,7 +9,7 @@ import type { DefinitionRpcExt } from '@polkadot/types/types';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { Modal, styled } from '@polkadot/react-components';
|
||||
import { useApi, useQueue, useStakingAsyncApis } from '@polkadot/react-hooks';
|
||||
import { useApi, useQueue } from '@polkadot/react-hooks';
|
||||
import { assert, isFunction, loggerFormat } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from './translate.js';
|
||||
@ -28,7 +28,6 @@ interface ItemState {
|
||||
|
||||
const NOOP = () => undefined;
|
||||
|
||||
const STAKING_RELAY_CHAIN_RPC = ['author.insertKey'];
|
||||
const AVAIL_STATUS = ['queued', 'qr', 'signing'];
|
||||
|
||||
async function submitRpc (api: ApiPromise, { method, section }: DefinitionRpcExt, values: unknown[]): Promise<QueueTxResult> {
|
||||
@ -94,7 +93,6 @@ function Signer ({ children, className = '' }: Props): React.ReactElement<Props>
|
||||
const { t } = useTranslation();
|
||||
const { queueSetTxStatus, txqueue } = useQueue();
|
||||
const [isQueueSubmit, setIsQueueSubmit] = useState(false);
|
||||
const { isStakingAsync, rcApi } = useStakingAsyncApis();
|
||||
|
||||
const { currentItem, isRpc, isVisible, queueSize, requestAddress } = useMemo(
|
||||
() => extractCurrent(txqueue),
|
||||
@ -106,15 +104,9 @@ function Signer ({ children, className = '' }: Props): React.ReactElement<Props>
|
||||
}, [queueSize]);
|
||||
|
||||
useEffect((): void => {
|
||||
if (isRpc && currentItem) {
|
||||
const apiForCall = isStakingAsync
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
? STAKING_RELAY_CHAIN_RPC.includes(`${currentItem?.rpc.section}.${currentItem?.rpc.method}`) ? (rcApi!) : api
|
||||
: api;
|
||||
|
||||
sendRpc(apiForCall, queueSetTxStatus, currentItem).catch(console.error);
|
||||
}
|
||||
}, [api, currentItem, isRpc, isStakingAsync, queueSetTxStatus, rcApi]);
|
||||
isRpc && currentItem &&
|
||||
sendRpc(api, queueSetTxStatus, currentItem).catch(console.error);
|
||||
}, [api, isRpc, currentItem, queueSetTxStatus]);
|
||||
|
||||
const _onCancel = useCallback(
|
||||
(): void => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user