Refactor: Move Relay and Asset Hub API Connections to Context (#11891)
* feat: add StakingAsync context and hooks for improved staking API integration * refactor: rename isStakingAsyncPage to isStakingAsync for consistency across components * feat: introduce useBestNumberRelay hook for enhanced best number retrieval based on staking async state * feat: add 'Kusama Asset Hub' link to Polkassembly and Subsquare configurations
This commit is contained in:
parent
7393de3071
commit
5630635d4c
@ -25,6 +25,7 @@ export const PolkassemblyIo: ExternalDef = {
|
||||
Khala: 'khala',
|
||||
Kintsugi: 'kintsugi',
|
||||
Kusama: 'kusama',
|
||||
'Kusama Asset Hub': 'kusama',
|
||||
'Kusama CC3': 'kusama',
|
||||
'Kusama People': 'kusama',
|
||||
Parallel: 'parallel',
|
||||
|
||||
@ -24,6 +24,7 @@ export const Subsquare: ExternalDef = {
|
||||
Karura: 'karura',
|
||||
Khala: 'khala',
|
||||
Kusama: 'kusama',
|
||||
'Kusama Asset Hub': 'kusama',
|
||||
'Paseo Testnet': 'paseo',
|
||||
Phala: 'phala',
|
||||
Polkadot: 'polkadot',
|
||||
|
||||
@ -9,7 +9,7 @@ import { HashRouter } from 'react-router-dom';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import { ApiCtxRoot } from '@polkadot/react-api';
|
||||
import { ApiStatsCtxRoot, BlockAuthorsCtxRoot, BlockEventsCtxRoot, KeyringCtxRoot, PayWithAssetCtxRoot, QueueCtxRoot, WindowSizeCtxRoot } from '@polkadot/react-hooks';
|
||||
import { ApiStatsCtxRoot, BlockAuthorsCtxRoot, BlockEventsCtxRoot, KeyringCtxRoot, PayWithAssetCtxRoot, QueueCtxRoot, StakingAsyncApisCtxRoot, WindowSizeCtxRoot } from '@polkadot/react-hooks';
|
||||
import { settings } from '@polkadot/ui-settings';
|
||||
|
||||
import BeforeApiInit from './overlays/BeforeInit.js';
|
||||
@ -56,7 +56,9 @@ function Root ({ isElectron, store }: Props): React.ReactElement<Props> {
|
||||
<HashRouter>
|
||||
<WindowSizeCtxRoot>
|
||||
<PayWithAssetCtxRoot>
|
||||
<Apps />
|
||||
<StakingAsyncApisCtxRoot>
|
||||
<Apps />
|
||||
</StakingAsyncApisCtxRoot>
|
||||
</PayWithAssetCtxRoot>
|
||||
</WindowSizeCtxRoot>
|
||||
</HashRouter>
|
||||
|
||||
@ -5,7 +5,7 @@ import type { BN } from '@polkadot/util';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { useBestNumber } from '@polkadot/react-hooks';
|
||||
import { useBestNumberRelay } from '@polkadot/react-hooks';
|
||||
import { BlockToTime } from '@polkadot/react-query';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
@ -16,7 +16,7 @@ interface Props {
|
||||
}
|
||||
|
||||
function RefEnd ({ className = '', label, when }: Props): React.ReactElement<Props> {
|
||||
const bestNumber = useBestNumber();
|
||||
const bestNumber = useBestNumberRelay();
|
||||
|
||||
return (
|
||||
<td className={`${className} number`}>
|
||||
|
||||
@ -10,7 +10,7 @@ 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 '@polkadot/react-hooks/ctx/StakingAsync';
|
||||
|
||||
import AssetHubSection from './ah.js';
|
||||
import RelaySection from './relay.js';
|
||||
|
||||
@ -26,20 +26,20 @@ 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 { isStakingAsync, rcApi } = useStakingAsyncApis();
|
||||
const [keys, setKeys] = useState<string | null>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
try {
|
||||
onChange({
|
||||
sessionTx: isHex(keys)
|
||||
? (isStakingAsyncPage ? rcApi : api)?.tx.session.setKeys(keys, EMPTY_PROOF)
|
||||
? (isStakingAsync ? rcApi : api)?.tx.session.setKeys(keys, EMPTY_PROOF)
|
||||
: null
|
||||
});
|
||||
} catch {
|
||||
onChange({ sessionTx: null });
|
||||
}
|
||||
}, [api, isStakingAsyncPage, keys, onChange, rcApi]);
|
||||
}, [api, isStakingAsync, keys, onChange, rcApi]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
|
||||
@ -10,7 +10,7 @@ import type { BN } from '@polkadot/util';
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { withCalls, withMulti } from '@polkadot/react-api/hoc';
|
||||
import { useBestNumber } from '@polkadot/react-hooks';
|
||||
import { useBestNumberRelay } from '@polkadot/react-hooks';
|
||||
import { BlockToTime, FormatBalance } from '@polkadot/react-query';
|
||||
import { BN_MAX_INTEGER, BN_ZERO, bnMax, formatBalance, formatNumber, isObject } from '@polkadot/util';
|
||||
|
||||
@ -550,7 +550,7 @@ function renderBalances (props: Props, lookup: Record<string, string>, bestNumbe
|
||||
|
||||
function AddressInfo (props: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const bestNumber = useBestNumber();
|
||||
const bestNumber = useBestNumberRelay();
|
||||
const { children, className = '', extraInfo, withBalanceToggle, withHexSessionId } = props;
|
||||
|
||||
const lookup = useRef<Record<string, string>>({
|
||||
|
||||
96
packages/react-hooks/src/ctx/StakingAsync.tsx
Normal file
96
packages/react-hooks/src/ctx/StakingAsync.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
// Copyright 2017-2025 @polkadot/react-hooks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import type { StakingAsyncApis } from './types.js';
|
||||
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { ApiPromise, WsProvider } from '@polkadot/api';
|
||||
import { createWsEndpoints } from '@polkadot/apps-config';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
|
||||
const allEndPoints = createWsEndpoints((k, v) => v?.toString() || k);
|
||||
|
||||
export const getApi = async (url: string[]|string) => {
|
||||
const api = await ApiPromise.create({
|
||||
provider: new WsProvider(url)
|
||||
});
|
||||
|
||||
await api.isReadyOrError;
|
||||
|
||||
return api;
|
||||
};
|
||||
|
||||
const EMPTY_STATE: StakingAsyncApis = {
|
||||
ahEndPoints: [],
|
||||
isRelayChain: false,
|
||||
isStakingAsync: false,
|
||||
rcEndPoints: []
|
||||
};
|
||||
|
||||
export const StakingAsyncApisCtx = React.createContext<StakingAsyncApis>(EMPTY_STATE);
|
||||
|
||||
export const StakingAsyncApisCtxRoot = ({ children }: PropsWithChildren) => {
|
||||
const { api, isApiReady } = useApi();
|
||||
|
||||
return isApiReady &&
|
||||
!!((api.tx.stakingAhClient) || (api.tx.staking && api.tx.stakingRcClient))
|
||||
? <StakingAsyncProvider>{children}</StakingAsyncProvider>
|
||||
: <>{children}</>;
|
||||
};
|
||||
|
||||
export const StakingAsyncProvider = ({ children }: PropsWithChildren) => {
|
||||
const { api, apiEndpoint } = useApi();
|
||||
const [ahApi, setAhApi] = useState<ApiPromise>();
|
||||
const [rcApi, setRcApi] = useState<ApiPromise>();
|
||||
|
||||
const isRelayChain = useMemo(() => {
|
||||
return !!api.tx.stakingAhClient;
|
||||
}, [api]);
|
||||
|
||||
const isStakingAsync = useMemo(() => {
|
||||
return !!((api.tx.stakingAhClient) || (api.tx.staking && api.tx.stakingRcClient));
|
||||
}, [api]);
|
||||
|
||||
const rcEndPoints = useMemo(() => {
|
||||
return (isRelayChain
|
||||
? apiEndpoint?.providers
|
||||
: apiEndpoint?.valueRelay) || [];
|
||||
}, [apiEndpoint, isRelayChain]);
|
||||
|
||||
const ahEndPoints: string[] = useMemo(() => {
|
||||
if (isRelayChain) {
|
||||
return allEndPoints.find(({ genesisHashRelay, paraId }) =>
|
||||
paraId === 1000 && genesisHashRelay === api.genesisHash.toHex()
|
||||
)?.providers || [];
|
||||
}
|
||||
|
||||
return apiEndpoint?.providers || [];
|
||||
}, [api, apiEndpoint, isRelayChain]);
|
||||
|
||||
useEffect(() => {
|
||||
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, isRelayChain, rcEndPoints]);
|
||||
|
||||
const state = useMemo(() => ({
|
||||
ahApi,
|
||||
ahEndPoints,
|
||||
isRelayChain,
|
||||
isStakingAsync,
|
||||
rcApi,
|
||||
rcEndPoints
|
||||
}), [ahApi, ahEndPoints, isRelayChain, isStakingAsync, rcApi, rcEndPoints]);
|
||||
|
||||
return <StakingAsyncApisCtx.Provider value={state}>
|
||||
{children}
|
||||
</StakingAsyncApisCtx.Provider>;
|
||||
};
|
||||
@ -7,4 +7,5 @@ export { BlockEventsCtxRoot } from './BlockEvents.js';
|
||||
export { KeyringCtxRoot } from './Keyring.js';
|
||||
export { PayWithAssetCtxRoot } from './PayWithAsset.js';
|
||||
export { QueueCtxRoot } from './Queue.js';
|
||||
export { StakingAsyncApisCtxRoot } from './StakingAsync.js';
|
||||
export { WindowSizeCtxRoot } from './WindowSize.js';
|
||||
|
||||
@ -114,3 +114,12 @@ export interface WindowSize {
|
||||
height: number;
|
||||
width: number;
|
||||
}
|
||||
|
||||
export interface StakingAsyncApis {
|
||||
rcApi?: ApiPromise,
|
||||
ahApi?: ApiPromise,
|
||||
ahEndPoints: string[],
|
||||
isRelayChain: boolean,
|
||||
isStakingAsync: boolean,
|
||||
rcEndPoints: string[]
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ export { useAssetInfos } from './useAssetInfos.js';
|
||||
export { useAvailableSlashes } from './useAvailableSlashes.js';
|
||||
export { useBalancesAll } from './useBalancesAll.js';
|
||||
export { useBestHash } from './useBestHash.js';
|
||||
export { useBestNumber } from './useBestNumber.js';
|
||||
export { useBestNumber, useBestNumberRelay } from './useBestNumber.js';
|
||||
export { useBlockAuthors } from './useBlockAuthors.js';
|
||||
export { useBlockEvents } from './useBlockEvents.js';
|
||||
export { useBlockInterval } from './useBlockInterval.js';
|
||||
|
||||
@ -6,6 +6,7 @@ import type { BlockNumber } from '@polkadot/types/interfaces';
|
||||
import { createNamedHook } from './createNamedHook.js';
|
||||
import { useApi } from './useApi.js';
|
||||
import { useCall } from './useCall.js';
|
||||
import { useStakingAsyncApis } from './useStakingAsyncApis.js';
|
||||
|
||||
function useBestNumberImpl (): BlockNumber | undefined {
|
||||
const { api } = useApi();
|
||||
@ -13,4 +14,12 @@ function useBestNumberImpl (): BlockNumber | undefined {
|
||||
return useCall<BlockNumber>(api.derive.chain.bestNumber);
|
||||
}
|
||||
|
||||
function useBestNumberRelayImpl (): BlockNumber | undefined {
|
||||
const { api } = useApi();
|
||||
const { isStakingAsync, rcApi } = useStakingAsyncApis();
|
||||
|
||||
return useCall<BlockNumber>((isStakingAsync ? rcApi : api)?.derive.chain.bestNumber);
|
||||
}
|
||||
|
||||
export const useBestNumber = createNamedHook('useBestNumber', useBestNumberImpl);
|
||||
export const useBestNumberRelay = createNamedHook('useBestNumberRelay', useBestNumberRelayImpl);
|
||||
|
||||
@ -1,93 +1,15 @@
|
||||
// Copyright 2017-2025 @polkadot/react-hooks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import type { StakingAsyncApis } from './ctx/types.js';
|
||||
|
||||
import { ApiPromise, WsProvider } from '@polkadot/api';
|
||||
import { createWsEndpoints } from '@polkadot/apps-config';
|
||||
import { createNamedHook, useApi } from '@polkadot/react-hooks';
|
||||
import { useContext } from 'react';
|
||||
|
||||
export const getApi = async (url: string[]|string) => {
|
||||
const api = await ApiPromise.create({
|
||||
provider: new WsProvider(url)
|
||||
});
|
||||
import { StakingAsyncApisCtx } from './ctx/StakingAsync.js';
|
||||
import { createNamedHook } from './createNamedHook.js';
|
||||
|
||||
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
|
||||
};
|
||||
function useStakingAsyncApisImpl (): StakingAsyncApis {
|
||||
return useContext(StakingAsyncApisCtx);
|
||||
}
|
||||
|
||||
export const useStakingAsyncApis = createNamedHook('useStakingAsyncApis', useStakingAsyncApisImpl);
|
||||
|
||||
@ -94,7 +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 { isStakingAsync, rcApi } = useStakingAsyncApis();
|
||||
|
||||
const { currentItem, isRpc, isVisible, queueSize, requestAddress } = useMemo(
|
||||
() => extractCurrent(txqueue),
|
||||
@ -107,14 +107,14 @@ function Signer ({ children, className = '' }: Props): React.ReactElement<Props>
|
||||
|
||||
useEffect((): void => {
|
||||
if (isRpc && currentItem) {
|
||||
const apiForCall = isStakingAsyncPage
|
||||
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, isStakingAsyncPage, queueSetTxStatus, rcApi]);
|
||||
}, [api, currentItem, isRpc, isStakingAsync, queueSetTxStatus, rcApi]);
|
||||
|
||||
const _onCancel = useCallback(
|
||||
(): void => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user