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:
Arjun Porwal 2025-10-13 22:06:22 +05:30 committed by GitHub
parent 09d508d3e5
commit 9edb3bec12
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 261 additions and 157 deletions

View File

@ -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

View File

@ -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>

View 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);

View File

@ -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>

View File

@ -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}

View File

@ -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) {

View File

@ -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);

View File

@ -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]
);

View File

@ -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>

View File

@ -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,

View File

@ -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();

View File

@ -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 => {

View File

@ -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 || []),

View File

@ -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 => {