Improved Validator DX for set_keys in staking POST AHM (#11863)
* feat: improved validator set keys ux * feat: refactor InjectKeys and useStakingAsyncApis for improved API readiness checks
This commit is contained in:
parent
082b338f22
commit
bf0cc2fcfb
@ -10,8 +10,8 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { Dropdown, styled } from '@polkadot/react-components';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
import { getApi } from '@polkadot/react-hooks/useStakingAsyncApis';
|
||||
|
||||
import { getApi } from '../utils.js';
|
||||
import AssetHubSection from './ah.js';
|
||||
import RelaySection from './relay.js';
|
||||
|
||||
|
||||
@ -1,58 +1,18 @@
|
||||
// Copyright 2017-2025 @polkadot/app-staking-async authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { AppProps as Props } from '@polkadot/react-components/types';
|
||||
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { createWsEndpoints } from '@polkadot/apps-config';
|
||||
import { styled } from '@polkadot/react-components';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
import { useStakingAsyncApis } from '@polkadot/react-hooks';
|
||||
|
||||
import StakingRelayApp from './Relay/index.js';
|
||||
import StakingSystemApp from './System/index.js';
|
||||
import { getApi } from './utils.js';
|
||||
|
||||
const allEndPoints = createWsEndpoints((k, v) => v?.toString() || k);
|
||||
|
||||
function StakingApp ({ basePath, className = '', onStatusChange }: Props): React.ReactElement<Props> {
|
||||
const { api, apiEndpoint } = useApi();
|
||||
|
||||
const [ahApi, setAhApi] = useState<ApiPromise>();
|
||||
const [rcApi, setRcApi] = useState<ApiPromise>();
|
||||
|
||||
const isRelayChain = useMemo(() => !!api.tx.stakingAhClient, [api.tx.stakingAhClient]);
|
||||
|
||||
const rcEndPoints = useMemo(() => {
|
||||
return (isRelayChain
|
||||
? apiEndpoint?.providers
|
||||
: apiEndpoint?.valueRelay) || [];
|
||||
}, [apiEndpoint?.providers, apiEndpoint?.valueRelay, isRelayChain]);
|
||||
|
||||
const ahEndPoints: string[] = useMemo(() => {
|
||||
if (isRelayChain) {
|
||||
return allEndPoints.find(({ genesisHashRelay, paraId }) =>
|
||||
paraId === 1000 && genesisHashRelay === api.genesisHash.toHex()
|
||||
)?.providers || [];
|
||||
}
|
||||
|
||||
return apiEndpoint?.providers || [];
|
||||
}, [api.genesisHash, apiEndpoint?.providers, isRelayChain]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isRelayChain) {
|
||||
// Pick random endpoint
|
||||
const ahUrl = ahEndPoints.at(Math.floor(Math.random() * ahEndPoints.length));
|
||||
|
||||
!!ahUrl && getApi(ahUrl).then((ahApi) => setAhApi(ahApi)).catch(console.log);
|
||||
} else {
|
||||
// Pick random endpoint
|
||||
const rcUrl = rcEndPoints.at(Math.floor(Math.random() * rcEndPoints.length));
|
||||
|
||||
!!rcUrl && getApi(rcUrl).then((rcApi) => setRcApi(rcApi)).catch(console.log);
|
||||
}
|
||||
}, [ahEndPoints, isRelayChain, rcEndPoints]);
|
||||
const { ahApi, ahEndPoints, isRelayChain, rcApi, rcEndPoints } = useStakingAsyncApis();
|
||||
|
||||
return (
|
||||
<StyledMain className={`${className} staking--App`}>
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
// Copyright 2017-2025 @polkadot/app-staking-async authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { ApiPromise, WsProvider } from '@polkadot/api';
|
||||
|
||||
export const getApi = async (url: string[]|string) => {
|
||||
const api = await ApiPromise.create({
|
||||
provider: new WsProvider(url)
|
||||
});
|
||||
|
||||
await api.isReadyOrError;
|
||||
|
||||
return api;
|
||||
};
|
||||
@ -30,6 +30,7 @@ const EMPTY_KEY = '0x';
|
||||
function InjectKeys ({ onClose }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const { queueRpc } = useQueue();
|
||||
|
||||
// this needs to align with what is set as the first value in `type`
|
||||
const [crypto, setCrypto] = useState<KeypairType>('sr25519');
|
||||
const [publicKey, setPublicKey] = useState(EMPTY_KEY);
|
||||
@ -86,6 +87,9 @@ 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
|
||||
|
||||
@ -5,8 +5,8 @@ import type { SessionInfo } from './types.js';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { Input, Modal } from '@polkadot/react-components';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
import { Input, MarkWarning, Modal } from '@polkadot/react-components';
|
||||
import { useApi, useStakingAsyncApis } from '@polkadot/react-hooks';
|
||||
import { isHex } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from '../../translate.js';
|
||||
@ -26,22 +26,26 @@ const EMPTY_PROOF = new Uint8Array();
|
||||
function SessionKey ({ className = '', controllerId, onChange, stashId, withFocus, withSenders }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const { isStakingAsyncPage, rcApi } = useStakingAsyncApis();
|
||||
const [keys, setKeys] = useState<string | null>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
try {
|
||||
onChange({
|
||||
sessionTx: isHex(keys)
|
||||
? api.tx.session.setKeys(keys, EMPTY_PROOF)
|
||||
? (isStakingAsyncPage ? rcApi : api)?.tx.session.setKeys(keys, EMPTY_PROOF)
|
||||
: null
|
||||
});
|
||||
} catch {
|
||||
onChange({ sessionTx: null });
|
||||
}
|
||||
}, [api, keys, onChange]);
|
||||
}, [api, isStakingAsyncPage, keys, onChange, rcApi]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Modal.Columns>
|
||||
<MarkWarning content={t('This operation will be performed on the relay chain.')} />
|
||||
</Modal.Columns>
|
||||
{withSenders && (
|
||||
<SenderInfo
|
||||
controllerId={controllerId}
|
||||
|
||||
@ -83,6 +83,7 @@ export { useRegions } from './useRegions.js';
|
||||
export { useRegistrars } from './useRegistrars.js';
|
||||
export { useSavedFlags } from './useSavedFlags.js';
|
||||
export { useScroll } from './useScroll.js';
|
||||
export { useStakingAsyncApis } from './useStakingAsyncApis.js';
|
||||
export { useStakingInfo } from './useStakingInfo.js';
|
||||
export { useStepper } from './useStepper.js';
|
||||
export { useSubidentities } from './useSubidentities.js';
|
||||
|
||||
93
packages/react-hooks/src/useStakingAsyncApis.ts
Normal file
93
packages/react-hooks/src/useStakingAsyncApis.ts
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright 2017-2025 @polkadot/react-hooks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { ApiPromise, WsProvider } from '@polkadot/api';
|
||||
import { createWsEndpoints } from '@polkadot/apps-config';
|
||||
import { createNamedHook, useApi } from '@polkadot/react-hooks';
|
||||
|
||||
export const getApi = async (url: string[]|string) => {
|
||||
const api = await ApiPromise.create({
|
||||
provider: new WsProvider(url)
|
||||
});
|
||||
|
||||
await api.isReadyOrError;
|
||||
|
||||
return api;
|
||||
};
|
||||
|
||||
const allEndPoints = createWsEndpoints((k, v) => v?.toString() || k);
|
||||
|
||||
function useStakingAsyncApisImpl () {
|
||||
const { api, apiEndpoint, isApiReady } = useApi(); // <- check readiness
|
||||
const [ahApi, setAhApi] = useState<ApiPromise>();
|
||||
const [rcApi, setRcApi] = useState<ApiPromise>();
|
||||
|
||||
const isRelayChain = useMemo(() => {
|
||||
if (!isApiReady) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!api.tx.stakingAhClient;
|
||||
}, [isApiReady, api]);
|
||||
|
||||
const isStakingAsyncPage = useMemo(() => {
|
||||
if (!isApiReady) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!((api.tx.stakingAhClient) || (api.tx.staking && api.tx.stakingRcClient));
|
||||
}, [isApiReady, api]);
|
||||
|
||||
const rcEndPoints = useMemo(() => {
|
||||
if (!isApiReady) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return (isRelayChain
|
||||
? apiEndpoint?.providers
|
||||
: apiEndpoint?.valueRelay) || [];
|
||||
}, [isApiReady, apiEndpoint, isRelayChain]);
|
||||
|
||||
const ahEndPoints: string[] = useMemo(() => {
|
||||
if (!isApiReady) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (isRelayChain) {
|
||||
return allEndPoints.find(({ genesisHashRelay, paraId }) =>
|
||||
paraId === 1000 && genesisHashRelay === api.genesisHash.toHex()
|
||||
)?.providers || [];
|
||||
}
|
||||
|
||||
return apiEndpoint?.providers || [];
|
||||
}, [isApiReady, api, apiEndpoint, isRelayChain]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isApiReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isRelayChain) {
|
||||
const ahUrl = ahEndPoints.at(Math.floor(Math.random() * ahEndPoints.length));
|
||||
|
||||
!!ahUrl && getApi(ahUrl).then(setAhApi).catch(console.error);
|
||||
} else {
|
||||
const rcUrl = rcEndPoints.at(Math.floor(Math.random() * rcEndPoints.length));
|
||||
|
||||
!!rcUrl && getApi(rcUrl).then(setRcApi).catch(console.error);
|
||||
}
|
||||
}, [ahEndPoints, isApiReady, isRelayChain, rcEndPoints]);
|
||||
|
||||
return {
|
||||
ahApi,
|
||||
ahEndPoints,
|
||||
isRelayChain,
|
||||
isStakingAsyncPage,
|
||||
rcApi,
|
||||
rcEndPoints
|
||||
};
|
||||
}
|
||||
|
||||
export const useStakingAsyncApis = createNamedHook('useStakingAsyncApis', useStakingAsyncApisImpl);
|
||||
@ -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 } from '@polkadot/react-hooks';
|
||||
import { useApi, useQueue, useStakingAsyncApis } from '@polkadot/react-hooks';
|
||||
import { assert, isFunction, loggerFormat } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from './translate.js';
|
||||
@ -28,6 +28,7 @@ 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> {
|
||||
@ -93,6 +94,7 @@ function Signer ({ children, className = '' }: Props): React.ReactElement<Props>
|
||||
const { t } = useTranslation();
|
||||
const { queueSetTxStatus, txqueue } = useQueue();
|
||||
const [isQueueSubmit, setIsQueueSubmit] = useState(false);
|
||||
const { isStakingAsyncPage, rcApi } = useStakingAsyncApis();
|
||||
|
||||
const { currentItem, isRpc, isVisible, queueSize, requestAddress } = useMemo(
|
||||
() => extractCurrent(txqueue),
|
||||
@ -104,9 +106,15 @@ function Signer ({ children, className = '' }: Props): React.ReactElement<Props>
|
||||
}, [queueSize]);
|
||||
|
||||
useEffect((): void => {
|
||||
isRpc && currentItem &&
|
||||
sendRpc(api, queueSetTxStatus, currentItem).catch(console.error);
|
||||
}, [api, isRpc, currentItem, queueSetTxStatus]);
|
||||
if (isRpc && currentItem) {
|
||||
const apiForCall = isStakingAsyncPage
|
||||
// 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, isStakingAsyncPage, queueSetTxStatus, rcApi]);
|
||||
|
||||
const _onCancel = useCallback(
|
||||
(): void => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user