Fix: Staking summary and Schedular remaining time (#11903)
* feat: enhance staking overview by combining data from multiple sources and add SummarySession component * feat: integrate staking async APIs into Scheduled and Scheduler components * fix: build * feat: integrate staking async APIs into StakingUnbonding component * feat: enhance SummarySession component with era progress calculation and improved API integration * fix: chart timeline * chore: comment
This commit is contained in:
parent
9b671cb7c2
commit
68c45b91b6
@ -9,7 +9,7 @@ import type { CurveGraph, ReferendumProps as Props } from '../types.js';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { Chart, Columar, LinkExternal, styled, Table } from '@polkadot/react-components';
|
||||
import { useBestNumber, useBlockInterval, useToggle } from '@polkadot/react-hooks';
|
||||
import { useBestNumberRelay, useBlockInterval, useStakingAsyncApis, useToggle } from '@polkadot/react-hooks';
|
||||
import { calcBlockTime } from '@polkadot/react-hooks/useBlockTime';
|
||||
import { BN_MILLION, BN_THOUSAND, bnMax, bnToBn, formatNumber, objectSpread } from '@polkadot/util';
|
||||
|
||||
@ -347,8 +347,9 @@ function extractInfo (info: PalletReferendaReferendumInfoConvictionVotingTally |
|
||||
|
||||
function Referendum (props: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const bestNumber = useBestNumber();
|
||||
const blockInterval = useBlockInterval();
|
||||
const { isStakingAsync, rcApi } = useStakingAsyncApis();
|
||||
const bestNumber = useBestNumberRelay();
|
||||
const blockInterval = useBlockInterval(isStakingAsync ? rcApi : undefined);
|
||||
const { activeIssuance, className = '', palletReferenda, value: { id, info, isConvictionVote, track, trackGraph } } = props;
|
||||
const [isExpanded, toggleExpanded] = useToggle(false);
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import type { ScheduledExt } from './types.js';
|
||||
import React from 'react';
|
||||
|
||||
import Hash from '@polkadot/app-preimages/Preimages/Hash';
|
||||
import { usePreimage } from '@polkadot/react-hooks';
|
||||
import { usePreimage, useStakingAsyncApis } from '@polkadot/react-hooks';
|
||||
import { CallExpander } from '@polkadot/react-params';
|
||||
import { BlockToTime } from '@polkadot/react-query';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
@ -19,6 +19,7 @@ interface Props {
|
||||
}
|
||||
|
||||
function Scheduled ({ bestNumber, className = '', value: { blockNumber, call, maybeId, maybePeriodic, preimageHash } }: Props): React.ReactElement<Props> {
|
||||
const { isStakingAsync, rcApi } = useStakingAsyncApis();
|
||||
const preimage = usePreimage(preimageHash);
|
||||
const period = maybePeriodic.unwrapOr(null);
|
||||
const name = maybeId.unwrapOr(null);
|
||||
@ -37,7 +38,10 @@ function Scheduled ({ bestNumber, className = '', value: { blockNumber, call, ma
|
||||
<td className='number together'>
|
||||
{bestNumber && (
|
||||
<>
|
||||
<BlockToTime value={blockNumber.sub(bestNumber)} />
|
||||
<BlockToTime
|
||||
api={isStakingAsync ? rcApi : undefined}
|
||||
value={blockNumber.sub(bestNumber)}
|
||||
/>
|
||||
#{formatNumber(blockNumber)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -11,7 +11,7 @@ import type { ScheduledExt } from './types.js';
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
|
||||
import { Table } from '@polkadot/react-components';
|
||||
import { useApi, useBestNumber, useCall } from '@polkadot/react-hooks';
|
||||
import { useApi, useBestNumberRelay, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
import ScheduledView from './Scheduled.js';
|
||||
import { useTranslation } from './translate.js';
|
||||
@ -81,7 +81,7 @@ const OPT_SCHED = {
|
||||
function Schedule ({ className = '' }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const bestNumber = useBestNumber();
|
||||
const bestNumber = useBestNumberRelay();
|
||||
const items = useCall<ScheduledExt[]>(api.query.scheduler.agenda.entries, undefined, OPT_SCHED);
|
||||
|
||||
const filtered = useMemo(
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { DeriveStakingOverview } from '@polkadot/api-derive/types';
|
||||
import type { DeriveStakingOverview, DeriveStakingValidators } from '@polkadot/api-derive/types';
|
||||
import type { AppProps } from '@polkadot/react-components/types';
|
||||
import type { ElectionStatus, ParaValidatorIndex, ValidatorId } from '@polkadot/types/interfaces';
|
||||
import type { BN } from '@polkadot/util';
|
||||
@ -71,7 +71,17 @@ function StakingApp ({ ahApi, ahEndPoints, basePath, isRelayChain, rcApi, rcEndP
|
||||
], OPT_MULTI);
|
||||
const nominatedBy = useNominations(loadNominations);
|
||||
const ownPools = useOwnPools();
|
||||
const stakingOverview = useCall<DeriveStakingOverview>(rcApi?.derive.staking.overview);
|
||||
const ahStakingOverview = useCall<DeriveStakingOverview>(api?.derive.staking.overview);
|
||||
const validatorsInfo = useCall<DeriveStakingValidators>(rcApi?.derive.staking.validators);
|
||||
|
||||
const stakingOverview = useMemo(() => (
|
||||
!!ahStakingOverview && !!validatorsInfo
|
||||
? {
|
||||
...ahStakingOverview,
|
||||
...validatorsInfo
|
||||
}
|
||||
: undefined
|
||||
), [ahStakingOverview, validatorsInfo]);
|
||||
|
||||
const toggleNominatedBy = useCallback(
|
||||
() => setLoadNominations(true),
|
||||
@ -201,7 +211,6 @@ function StakingApp ({ ahApi, ahEndPoints, basePath, isRelayChain, rcApi, rcEndP
|
||||
isInElection={isInElection}
|
||||
nominatedBy={nominatedBy}
|
||||
ownStashes={ownStashes}
|
||||
stakingOverview={stakingOverview}
|
||||
targets={targets}
|
||||
toggleFavorite={toggleFavorite}
|
||||
toggleLedger={toggleLedger}
|
||||
|
||||
@ -8,12 +8,12 @@ import type { BN } from '@polkadot/util';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import SummarySession from '@polkadot/app-explorer/SummarySession';
|
||||
import { CardSummary, styled, SummaryBox } from '@polkadot/react-components';
|
||||
import { useApi, useCall } from '@polkadot/react-hooks';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import SummarySession from './SummarySession.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
|
||||
117
packages/page-staking-async/src/Validators/SummarySession.tsx
Normal file
117
packages/page-staking-async/src/Validators/SummarySession.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
// Copyright 2017-2025 @polkadot/app-staking-async authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DeriveSessionProgress } from '@polkadot/api-derive/types';
|
||||
import type { Forcing } from '@polkadot/types/interfaces';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { CardSummary } from '@polkadot/react-components';
|
||||
import { useApi, useBlockInterval, useCall, useStakingAsyncApis } from '@polkadot/react-hooks';
|
||||
import { BN, BN_THREE, BN_TWO, BN_ZERO, formatNumber } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
withEra?: boolean;
|
||||
withSession?: boolean;
|
||||
}
|
||||
|
||||
function SummarySession ({ className, withEra = true, withSession = true }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi(); // Asset Hub API
|
||||
const blockTime = useBlockInterval();
|
||||
const { rcApi } = useStakingAsyncApis();
|
||||
const sessionInfo = useCall<DeriveSessionProgress>(rcApi?.derive.session?.progress);
|
||||
const ahSessionInfo = useCall<DeriveSessionProgress>(api?.derive.session?.progress);
|
||||
const forcing = useCall<Forcing>(rcApi?.query.staking?.forceEra);
|
||||
|
||||
const eraLabel = t('era');
|
||||
const sessionLabel = rcApi?.query.babe
|
||||
? t('epoch')
|
||||
: t('session');
|
||||
const activeEraStart = sessionInfo?.activeEraStart.unwrapOr(null);
|
||||
|
||||
const eraProgress = useMemo(() => {
|
||||
if (!ahSessionInfo) {
|
||||
return BN_ZERO;
|
||||
}
|
||||
|
||||
const currentEraStart = ahSessionInfo.activeEraStart.unwrapOrDefault();
|
||||
|
||||
if (currentEraStart.isZero()) {
|
||||
return BN_ZERO;
|
||||
}
|
||||
|
||||
const currentTimestamp = new BN(Math.floor(Date.now()));
|
||||
|
||||
const elapsed = currentTimestamp.sub(currentEraStart);
|
||||
|
||||
return elapsed.div(blockTime);
|
||||
}, [ahSessionInfo, blockTime]);
|
||||
|
||||
const eraDuration = useMemo(
|
||||
() => {
|
||||
// default to 1800 blocks i.e. 6 hours
|
||||
return new BN(6 * 60 * 60 * 1000).div(blockTime);
|
||||
},
|
||||
[blockTime]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{rcApi?.derive.session && (
|
||||
<>
|
||||
{withSession && (
|
||||
rcApi?.query.babe
|
||||
? (
|
||||
<CardSummary
|
||||
apiOverride={rcApi}
|
||||
className={className}
|
||||
label={sessionLabel}
|
||||
progress={{
|
||||
isBlurred: !sessionInfo,
|
||||
total: sessionInfo?.sessionLength || BN_THREE,
|
||||
value: sessionInfo?.sessionProgress || BN_TWO,
|
||||
withTime: true
|
||||
}}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<CardSummary
|
||||
apiOverride={rcApi}
|
||||
label={sessionLabel}
|
||||
>
|
||||
#{sessionInfo
|
||||
? formatNumber(sessionInfo.currentIndex)
|
||||
: <span className='--tmp'>123</span>}
|
||||
{withEra && activeEraStart && <div className='isSecondary'> </div>}
|
||||
</CardSummary>
|
||||
)
|
||||
)}
|
||||
<CardSummary
|
||||
className={className}
|
||||
label={eraLabel}
|
||||
progress={{
|
||||
isBlurred: !(sessionInfo && forcing),
|
||||
total: sessionInfo && forcing
|
||||
? forcing.isForceAlways
|
||||
? sessionInfo.sessionLength
|
||||
: eraDuration
|
||||
: BN_THREE,
|
||||
value: sessionInfo && forcing
|
||||
? forcing.isForceAlways
|
||||
? sessionInfo.sessionProgress
|
||||
: eraProgress
|
||||
: BN_TWO,
|
||||
withTime: true
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(SummarySession);
|
||||
@ -1,7 +1,7 @@
|
||||
// Copyright 2017-2025 @polkadot/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DeriveHasIdentity, DeriveStakingOverview } from '@polkadot/api-derive/types';
|
||||
import type { DeriveHasIdentity } from '@polkadot/api-derive/types';
|
||||
import type { StakerState } from '@polkadot/react-hooks/types';
|
||||
import type { u32 } from '@polkadot/types-codec';
|
||||
import type { BN } from '@polkadot/util';
|
||||
@ -29,7 +29,6 @@ interface Props {
|
||||
isInElection: boolean;
|
||||
nominatedBy?: NominatedByMap;
|
||||
ownStashes?: StakerState[];
|
||||
stakingOverview?: DeriveStakingOverview;
|
||||
targets: SortedTargets;
|
||||
toggleFavorite: (address: string) => void;
|
||||
toggleLedger: () => void;
|
||||
|
||||
@ -185,7 +185,6 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
|
||||
isInElection={isInElection}
|
||||
nominatedBy={nominatedBy}
|
||||
ownStashes={ownStashes}
|
||||
stakingOverview={stakingOverview}
|
||||
targets={targets}
|
||||
toggleFavorite={toggleFavorite}
|
||||
toggleLedger={toggleLedger}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// Copyright 2017-2025 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { UInt } from '@polkadot/types';
|
||||
import type { BN } from '@polkadot/util';
|
||||
|
||||
@ -28,9 +29,10 @@ interface Props {
|
||||
className?: string;
|
||||
label: React.ReactNode;
|
||||
progress?: ProgressProps;
|
||||
apiOverride?: ApiPromise;
|
||||
}
|
||||
|
||||
function CardSummary ({ children, className = '', label, progress }: Props): React.ReactElement<Props> | null {
|
||||
function CardSummary ({ apiOverride, children, className = '', label, progress }: Props): React.ReactElement<Props> | null {
|
||||
const value = progress?.value;
|
||||
const total = progress?.total;
|
||||
const left = progress && !isUndefined(value) && !isUndefined(total) && value.gten(0) && total.gtn(0)
|
||||
@ -73,6 +75,7 @@ function CardSummary ({ children, className = '', label, progress }: Props): Rea
|
||||
<>
|
||||
{isTimed && !children && (
|
||||
<BlockToTime
|
||||
api={apiOverride}
|
||||
className={progress.isBlurred ? '--tmp' : ''}
|
||||
value={progress.total}
|
||||
/>
|
||||
@ -88,6 +91,7 @@ function CardSummary ({ children, className = '', label, progress }: Props): Rea
|
||||
}`
|
||||
: (
|
||||
<BlockToTime
|
||||
api={apiOverride}
|
||||
className={`${progress.isBlurred ? '--tmp' : ''} timer`}
|
||||
value={progress.total.sub(progress.value)}
|
||||
/>
|
||||
|
||||
@ -5,7 +5,7 @@ import type { DeriveSessionProgress, DeriveStakingAccount } from '@polkadot/api-
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { useApi, useCall } from '@polkadot/react-hooks';
|
||||
import { useApi, useCall, useStakingAsyncApis } from '@polkadot/react-hooks';
|
||||
import { BlockToTime, FormatBalance } from '@polkadot/react-query';
|
||||
import { BN, BN_ONE, BN_ZERO, formatBalance, formatNumber } from '@polkadot/util';
|
||||
|
||||
@ -66,8 +66,10 @@ function extractTotals (stakingInfo?: DeriveStakingAccountPartial, progress?: De
|
||||
}
|
||||
|
||||
function StakingUnbonding ({ className = '', iconPosition = 'left', stakingInfo }: Props): React.ReactElement<Props> | null {
|
||||
const { api } = useApi();
|
||||
const progress = useCall<DeriveSessionProgress>(api.derive.session.progress);
|
||||
const { api: connectedApi } = useApi();
|
||||
const { isStakingAsync, rcApi } = useStakingAsyncApis();
|
||||
const api = useMemo(() => (isStakingAsync ? rcApi : connectedApi), [connectedApi, isStakingAsync, rcApi]);
|
||||
const progress = useCall<DeriveSessionProgress>(api?.derive.session.progress);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [mapped, total, isStalled] = useMemo(
|
||||
@ -99,9 +101,10 @@ function StakingUnbonding ({ className = '', iconPosition = 'left', stakingInfo
|
||||
>
|
||||
<div>{t('Unbonding {{value}}', { replace: { value: formatBalance(value, { forceUnit: '-' }) } })}</div>
|
||||
<div className='faded'>
|
||||
{api.consts.babe?.epochDuration
|
||||
{api?.consts.babe?.epochDuration
|
||||
? (
|
||||
<BlockToTime
|
||||
api={api}
|
||||
label={`${t('{{blocks}} blocks', { replace: { blocks: formatNumber(blocks) } })}, `}
|
||||
value={blocks}
|
||||
/>
|
||||
|
||||
@ -56,7 +56,7 @@ export const StakingAsyncProvider = ({ children }: PropsWithChildren) => {
|
||||
const rcEndPoints = useMemo(() => {
|
||||
return (isRelayChain
|
||||
? apiEndpoint?.providers
|
||||
: apiEndpoint?.valueRelay) || [];
|
||||
: apiEndpoint?.valueRelay)?.filter((e) => e.startsWith('wss://')) || [];
|
||||
}, [apiEndpoint, isRelayChain]);
|
||||
|
||||
const ahEndPoints: string[] = useMemo(() => {
|
||||
@ -66,20 +66,24 @@ export const StakingAsyncProvider = ({ children }: PropsWithChildren) => {
|
||||
)?.providers || [];
|
||||
}
|
||||
|
||||
return apiEndpoint?.providers || [];
|
||||
return apiEndpoint?.providers?.filter((e) => e.startsWith('wss://')) || [];
|
||||
}, [api, apiEndpoint, isRelayChain]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isRelayChain) {
|
||||
const ahUrl = ahEndPoints.at(Math.floor(Math.random() * ahEndPoints.length));
|
||||
|
||||
setRcApi(api);
|
||||
|
||||
!!ahUrl && getApi(ahUrl).then(setAhApi).catch(console.error);
|
||||
} else {
|
||||
const rcUrl = rcEndPoints.at(Math.floor(Math.random() * rcEndPoints.length));
|
||||
|
||||
setAhApi(api);
|
||||
|
||||
!!rcUrl && getApi(rcUrl).then(setRcApi).catch(console.error);
|
||||
}
|
||||
}, [ahEndPoints, isRelayChain, rcEndPoints]);
|
||||
}, [ahEndPoints, api, isRelayChain, rcEndPoints]);
|
||||
|
||||
const state = useMemo(() => ({
|
||||
ahApi,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user