From 62176288ad098883dca026c0eacc91099b8418e6 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Thu, 3 Apr 2025 13:21:03 +0530
Subject: [PATCH 01/38] feat: initialized staking-next page
---
packages/apps-routing/src/index.ts | 3 +
packages/apps-routing/src/staking-next.ts | 20 +++++
packages/apps-routing/tsconfig.build.json | 1 +
.../apps/public/locales/en/apps-routing.json | 1 +
packages/page-staking-next/.skip-build | 0
packages/page-staking-next/.skip-npm | 0
packages/page-staking-next/README.md | 3 +
packages/page-staking-next/package.json | 23 ++++++
packages/page-staking-next/src/index.tsx | 78 +++++++++++++++++++
.../page-staking-next/tsconfig.build.json | 12 +++
packages/react-components/src/i18n/index.ts | 1 +
tsconfig.base.json | 1 +
tsconfig.build.json | 1 +
yarn.lock | 10 +++
14 files changed, 154 insertions(+)
create mode 100644 packages/apps-routing/src/staking-next.ts
create mode 100644 packages/page-staking-next/.skip-build
create mode 100644 packages/page-staking-next/.skip-npm
create mode 100644 packages/page-staking-next/README.md
create mode 100644 packages/page-staking-next/package.json
create mode 100644 packages/page-staking-next/src/index.tsx
create mode 100644 packages/page-staking-next/tsconfig.build.json
diff --git a/packages/apps-routing/src/index.ts b/packages/apps-routing/src/index.ts
index 10624d4b1c73..65ba15b671f8 100644
--- a/packages/apps-routing/src/index.ts
+++ b/packages/apps-routing/src/index.ts
@@ -38,6 +38,7 @@ import settings from './settings.js';
import signing from './signing.js';
import society from './society.js';
import staking from './staking.js';
+import stakingNext from './staking-next.js';
import staking2 from './staking2.js';
import stakingLegacy from './stakingLegacy.js';
import storage from './storage.js';
@@ -58,6 +59,8 @@ export default function create (t: TFunction): Routes {
poll(t),
transfer(t),
teleport(t),
+ // Staking for AssetHub Migration
+ stakingNext(t),
staking(t),
staking2(t),
// Legacy staking Pre v14 pallet version.
diff --git a/packages/apps-routing/src/staking-next.ts b/packages/apps-routing/src/staking-next.ts
new file mode 100644
index 000000000000..7945baee3461
--- /dev/null
+++ b/packages/apps-routing/src/staking-next.ts
@@ -0,0 +1,20 @@
+// Copyright 2017-2025 @polkadot/apps-routing authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+import type { Route, TFunction } from './types.js';
+
+import Component from '@polkadot/app-staking-next';
+
+export default function create (t: TFunction): Route {
+ return {
+ Component,
+ display: {
+ // TODO: Add check when to show this page
+ needsApi: []
+ },
+ group: 'network',
+ icon: 'certificate',
+ name: 'staking-next',
+ text: t('nav.staking-next', 'Staking Next', { ns: 'apps-routing' })
+ };
+}
diff --git a/packages/apps-routing/tsconfig.build.json b/packages/apps-routing/tsconfig.build.json
index b1f8b42f3575..325e6e3a6e59 100644
--- a/packages/apps-routing/tsconfig.build.json
+++ b/packages/apps-routing/tsconfig.build.json
@@ -41,6 +41,7 @@
{ "path": "../page-signing/tsconfig.build.json" },
{ "path": "../page-society/tsconfig.build.json" },
{ "path": "../page-staking/tsconfig.build.json" },
+ { "path": "../page-staking-next/tsconfig.build.json" },
{ "path": "../page-staking2/tsconfig.build.json" },
{ "path": "../page-staking-legacy/tsconfig.build.json" },
{ "path": "../page-storage/tsconfig.build.json" },
diff --git a/packages/apps/public/locales/en/apps-routing.json b/packages/apps/public/locales/en/apps-routing.json
index c5e93800442b..f60995dedb6c 100644
--- a/packages/apps/public/locales/en/apps-routing.json
+++ b/packages/apps/public/locales/en/apps-routing.json
@@ -34,6 +34,7 @@
"nav.signing": "Sign and verify",
"nav.society": "Society",
"nav.staking": "Staking",
+ "nav.staking-next": "Staking Next",
"nav.storage": "Chain state",
"nav.sudo": "Sudo",
"nav.tech-comm": "Tech. comm.",
diff --git a/packages/page-staking-next/.skip-build b/packages/page-staking-next/.skip-build
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/page-staking-next/.skip-npm b/packages/page-staking-next/.skip-npm
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/page-staking-next/README.md b/packages/page-staking-next/README.md
new file mode 100644
index 000000000000..bcee3ef8047f
--- /dev/null
+++ b/packages/page-staking-next/README.md
@@ -0,0 +1,3 @@
+# @polkadot/app-staking-next
+
+More Info can be found here - https://hackmd.io/7PiBrGxxRG2ib-WRZYJZhQ
\ No newline at end of file
diff --git a/packages/page-staking-next/package.json b/packages/page-staking-next/package.json
new file mode 100644
index 000000000000..484e3377ca76
--- /dev/null
+++ b/packages/page-staking-next/package.json
@@ -0,0 +1,23 @@
+{
+ "bugs": "https://github.com/polkadot-js/apps/issues",
+ "engines": {
+ "node": ">=18"
+ },
+ "homepage": "https://github.com/polkadot-js/apps/tree/master/packages/page-staking-next#readme",
+ "license": "Apache-2.0",
+ "name": "@polkadot/app-staking-next",
+ "private": true,
+ "repository": {
+ "directory": "packages/page-staking-next",
+ "type": "git",
+ "url": "https://github.com/polkadot-js/apps.git"
+ },
+ "sideEffects": false,
+ "type": "module",
+ "version": "0.152.2-11-x",
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*",
+ "react-is": "*"
+ }
+}
diff --git a/packages/page-staking-next/src/index.tsx b/packages/page-staking-next/src/index.tsx
new file mode 100644
index 000000000000..cbcd5148da63
--- /dev/null
+++ b/packages/page-staking-next/src/index.tsx
@@ -0,0 +1,78 @@
+// Copyright 2017-2025 @polkadot/app-staking-next authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+import type { AppProps as Props } from '@polkadot/react-components/types';
+
+import React from 'react';
+import { Route, Routes } from 'react-router';
+
+import { styled, Tabs } from '@polkadot/react-components';
+import { useAccounts } from '@polkadot/react-hooks';
+
+const HIDDEN_ACC = ['actions', 'payout'];
+
+function StakingApp ({ basePath, className = '' }: Props): React.ReactElement {
+ const { areAccountsLoaded, hasAccounts } = useAccounts();
+
+ return (
+
+
+
+
+ Staking Next Page
+ //
+ }
+ index
+ // path='bags'
+ />
+
+
+
+
+
+ );
+}
+
+const StyledMain = styled.main`
+ .staking--Chart {
+ margin-top: 1.5rem;
+
+ h1 {
+ margin-bottom: 0.5rem;
+ }
+
+ .ui--Spinner {
+ margin: 2.5rem auto;
+ }
+ }
+
+ .staking--optionsBar {
+ margin: 0.5rem 0 1rem;
+ text-align: center;
+ white-space: normal;
+
+ .staking--buttonToggle {
+ display: inline-block;
+ margin-right: 1rem;
+ margin-top: 0.5rem;
+ }
+ }
+
+ .ui--Expander.stakeOver {
+ .ui--Expander-summary {
+ color: var(--color-error);
+ }
+ }
+`;
+
+export default React.memo(StakingApp);
diff --git a/packages/page-staking-next/tsconfig.build.json b/packages/page-staking-next/tsconfig.build.json
new file mode 100644
index 000000000000..9da3e5471bbe
--- /dev/null
+++ b/packages/page-staking-next/tsconfig.build.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "baseUrl": "..",
+ "outDir": "./build",
+ "rootDir": "./src"
+ },
+ "references": [
+ { "path": "../react-components/tsconfig.build.json" },
+ { "path": "../react-hooks/tsconfig.build.json" }
+ ]
+}
diff --git a/packages/react-components/src/i18n/index.ts b/packages/react-components/src/i18n/index.ts
index a97f75d0d51f..08f4d352606c 100644
--- a/packages/react-components/src/i18n/index.ts
+++ b/packages/react-components/src/i18n/index.ts
@@ -68,6 +68,7 @@ i18next
'app-signing',
'app-society',
'app-staking',
+ 'app-staking-next',
'app-staking-legacy',
'app-storage',
'app-sudo',
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 158c1f661e9a..d1c994eb3a21 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -70,6 +70,7 @@
"@polkadot/app-signing": ["page-signing/src/index.tsx"],
"@polkadot/app-society": ["page-society/src/index.tsx"],
"@polkadot/app-staking": ["page-staking/src/index.tsx"],
+ "@polkadot/app-staking-next": ["page-staking-next/src/index.tsx"],
"@polkadot/app-staking2": ["page-staking2/src/index.tsx"],
"@polkadot/app-staking2/Legend": ["page-staking2/src/Legend.tsx"],
"@polkadot/app-staking2/Pools": ["page-staking2/src/Pools/index.tsx"],
diff --git a/tsconfig.build.json b/tsconfig.build.json
index 53428eb1a1a0..46fde44d1458 100644
--- a/tsconfig.build.json
+++ b/tsconfig.build.json
@@ -51,6 +51,7 @@
{ "path": "./packages/page-signing/tsconfig.build.json" },
{ "path": "./packages/page-society/tsconfig.build.json" },
{ "path": "./packages/page-staking/tsconfig.build.json" },
+ { "path": "./packages/page-staking-next/tsconfig.build.json" },
{ "path": "./packages/page-staking2/tsconfig.build.json" },
{ "path": "./packages/page-staking-legacy/tsconfig.build.json" },
{ "path": "./packages/page-storage/tsconfig.build.json" },
diff --git a/yarn.lock b/yarn.lock
index ef48e56adb51..85c2bb770ca2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2048,6 +2048,16 @@ __metadata:
languageName: unknown
linkType: soft
+"@polkadot/app-staking-next@workspace:packages/page-staking-next":
+ version: 0.0.0-use.local
+ resolution: "@polkadot/app-staking-next@workspace:packages/page-staking-next"
+ peerDependencies:
+ react: "*"
+ react-dom: "*"
+ react-is: "*"
+ languageName: unknown
+ linkType: soft
+
"@polkadot/app-staking2@workspace:packages/page-staking2":
version: 0.0.0-use.local
resolution: "@polkadot/app-staking2@workspace:packages/page-staking2"
From a2d53d92979031600c472642c1f41cdd960f0cdb Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Thu, 3 Apr 2025 14:02:29 +0530
Subject: [PATCH 02/38] feat: enhance staking-next page with dynamic tabs and
translation support
---
packages/page-staking-next/src/index.tsx | 85 ++++++++++++++++-----
packages/page-staking-next/src/translate.ts | 8 ++
2 files changed, 73 insertions(+), 20 deletions(-)
create mode 100644 packages/page-staking-next/src/translate.ts
diff --git a/packages/page-staking-next/src/index.tsx b/packages/page-staking-next/src/index.tsx
index cbcd5148da63..caf70353b809 100644
--- a/packages/page-staking-next/src/index.tsx
+++ b/packages/page-staking-next/src/index.tsx
@@ -3,16 +3,67 @@
import type { AppProps as Props } from '@polkadot/react-components/types';
-import React from 'react';
+import React, { useMemo } from 'react';
import { Route, Routes } from 'react-router';
import { styled, Tabs } from '@polkadot/react-components';
-import { useAccounts } from '@polkadot/react-hooks';
+import { useAccounts, useApi, useAvailableSlashes, useOwnStashInfos } from '@polkadot/react-hooks';
+import { isFunction } from '@polkadot/util';
+
+import { useTranslation } from './translate.js';
const HIDDEN_ACC = ['actions', 'payout'];
function StakingApp ({ basePath, className = '' }: Props): React.ReactElement {
+ const { t } = useTranslation();
+ const { api } = useApi();
const { areAccountsLoaded, hasAccounts } = useAccounts();
+ const ownStashes = useOwnStashInfos();
+ const slashes = useAvailableSlashes();
+
+ const hasStashes = useMemo(
+ () => hasAccounts && !!ownStashes && (ownStashes.length !== 0),
+ [hasAccounts, ownStashes]
+ );
+
+ const items = useMemo(() => [
+ {
+ isRoot: true,
+ name: 'overview',
+ text: t('Overview')
+ },
+ {
+ name: 'actions',
+ text: t('Accounts')
+ },
+ hasStashes && isFunction(api.query.staking.activeEra) && {
+ name: 'payout',
+ text: t('Payouts')
+ },
+ isFunction(api.query.nominationPools?.minCreateBond) && {
+ name: 'pools',
+ text: t('Pools')
+ },
+ {
+ alias: 'returns',
+ name: 'targets',
+ text: t('Targets')
+ },
+ hasStashes && isFunction((api.query.voterBagsList || api.query.bagsList || api.query.voterList)?.counterForListNodes) && {
+ name: 'bags',
+ text: t('Bags')
+ },
+ {
+ count: slashes.reduce((count, [, unapplied]) => count + unapplied.length, 0),
+ name: 'slashes',
+ text: t('Slashes')
+ },
+ {
+ hasParams: true,
+ name: 'query',
+ text: t('Validator stats')
+ }
+ ].filter((q): q is { name: string; text: string } => !!q), [api, hasStashes, slashes, t]);
return (
@@ -23,24 +74,18 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement
-
-
- Staking Next Page
- //
- }
- index
- // path='bags'
- />
-
-
-
-
-
- );
+ items={items}
+ />
+
+
+ Root Page}
+ index
+ />
+
+
+
+ );
}
const StyledMain = styled.main`
diff --git a/packages/page-staking-next/src/translate.ts b/packages/page-staking-next/src/translate.ts
new file mode 100644
index 000000000000..09db304c3e3a
--- /dev/null
+++ b/packages/page-staking-next/src/translate.ts
@@ -0,0 +1,8 @@
+// Copyright 2017-2025 @polkadot/app-staking-next authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+import { useTranslation as useTranslationBase } from 'react-i18next';
+
+export function useTranslation (): { t: (key: string, options?: { replace: Record }) => string } {
+ return useTranslationBase('app-staking-next');
+}
From d31da2bf4d34e4c1d253f840d6e1fb73ad2370a8 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Thu, 10 Apr 2025 11:21:00 +0530
Subject: [PATCH 03/38] feat: enabled payouts page for staking-next
---
packages/page-staking-next/src/constants.ts | 4 ++
packages/page-staking-next/src/index.tsx | 49 ++++++++++++++++++++-
tsconfig.base.json | 2 +
3 files changed, 53 insertions(+), 2 deletions(-)
create mode 100644 packages/page-staking-next/src/constants.ts
diff --git a/packages/page-staking-next/src/constants.ts b/packages/page-staking-next/src/constants.ts
new file mode 100644
index 000000000000..fcb9fadb8a46
--- /dev/null
+++ b/packages/page-staking-next/src/constants.ts
@@ -0,0 +1,4 @@
+// Copyright 2017-2025 @polkadot/app-staking-next authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+export const STORE_FAVS_BASE = 'staking:favorites';
diff --git a/packages/page-staking-next/src/index.tsx b/packages/page-staking-next/src/index.tsx
index caf70353b809..3e6eb2422494 100644
--- a/packages/page-staking-next/src/index.tsx
+++ b/packages/page-staking-next/src/index.tsx
@@ -2,30 +2,64 @@
// SPDX-License-Identifier: Apache-2.0
import type { AppProps as Props } from '@polkadot/react-components/types';
+import type { ElectionStatus, ParaValidatorIndex, ValidatorId } from '@polkadot/types/interfaces';
+import type { BN } from '@polkadot/util';
-import React, { useMemo } from 'react';
+import React, { useMemo, useState } from 'react';
import { Route, Routes } from 'react-router';
+import Payouts from '@polkadot/app-staking/Payouts';
+import useSortedTargets from '@polkadot/app-staking/useSortedTargets';
+import useOwnPools from '@polkadot/app-staking2/Pools/useOwnPools';
import { styled, Tabs } from '@polkadot/react-components';
-import { useAccounts, useApi, useAvailableSlashes, useOwnStashInfos } from '@polkadot/react-hooks';
+import { useAccounts, useApi, useAvailableSlashes, useCallMulti, useFavorites, useOwnStashInfos } from '@polkadot/react-hooks';
import { isFunction } from '@polkadot/util';
+import { STORE_FAVS_BASE } from './constants.js';
import { useTranslation } from './translate.js';
const HIDDEN_ACC = ['actions', 'payout'];
+const OPT_MULTI = {
+ defaultValue: [false, undefined, {}] as [boolean, BN | undefined, Record],
+ transform: ([eraElectionStatus, minValidatorBond, validators, activeValidatorIndices]: [ElectionStatus | null, BN | undefined, ValidatorId[] | null, ParaValidatorIndex[] | null]): [boolean, BN | undefined, Record] => [
+ !!eraElectionStatus && eraElectionStatus.isOpen,
+ minValidatorBond && !minValidatorBond.isZero()
+ ? minValidatorBond
+ : undefined,
+ validators && activeValidatorIndices
+ ? activeValidatorIndices.reduce((all, index) => ({ ...all, [validators[index.toNumber()].toString()]: true }), {})
+ : {}
+ ]
+};
+
function StakingApp ({ basePath, className = '' }: Props): React.ReactElement {
const { t } = useTranslation();
const { api } = useApi();
+ const [withLedger, setWithLedger] = useState(false);
+ const [favorites, toggleFavorite] = useFavorites(STORE_FAVS_BASE);
const { areAccountsLoaded, hasAccounts } = useAccounts();
const ownStashes = useOwnStashInfos();
const slashes = useAvailableSlashes();
+ const targets = useSortedTargets(favorites, withLedger);
+ const [isInElection, minCommission, paraValidators] = useCallMulti<[boolean, BN | undefined, Record]>([
+ api.query.staking.eraElectionStatus,
+ api.query.staking.minCommission,
+ api.query.session.validators,
+ (api.query.parasShared || api.query.shared)?.activeValidatorIndices
+ ], OPT_MULTI);
+ const ownPools = useOwnPools();
const hasStashes = useMemo(
() => hasAccounts && !!ownStashes && (ownStashes.length !== 0),
[hasAccounts, ownStashes]
);
+ const ownValidators = useMemo(
+ () => (ownStashes || []).filter(({ isStashValidating }) => isStashValidating),
+ [ownStashes]
+ );
+
const items = useMemo(() => [
{
isRoot: true,
@@ -78,6 +112,17 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement
+
+ }
+ path='payout'
+ />
Root Page}
index
diff --git a/tsconfig.base.json b/tsconfig.base.json
index d1c994eb3a21..7e6a3bb4f335 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -70,6 +70,8 @@
"@polkadot/app-signing": ["page-signing/src/index.tsx"],
"@polkadot/app-society": ["page-society/src/index.tsx"],
"@polkadot/app-staking": ["page-staking/src/index.tsx"],
+ "@polkadot/app-staking/Payouts": ["page-staking/src/Payouts/index.tsx"],
+ "@polkadot/app-staking/*": ["page-staking/src/*.ts"],
"@polkadot/app-staking-next": ["page-staking-next/src/index.tsx"],
"@polkadot/app-staking2": ["page-staking2/src/index.tsx"],
"@polkadot/app-staking2/Legend": ["page-staking2/src/Legend.tsx"],
From 739064bc63ffb8f84cd3b040fafe89b98cb33b04 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Thu, 10 Apr 2025 11:52:10 +0530
Subject: [PATCH 04/38] feat: enabled pools page for staking-next
---
packages/page-staking-next/src/index.tsx | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/packages/page-staking-next/src/index.tsx b/packages/page-staking-next/src/index.tsx
index 3e6eb2422494..d009217afc1a 100644
--- a/packages/page-staking-next/src/index.tsx
+++ b/packages/page-staking-next/src/index.tsx
@@ -10,6 +10,7 @@ import { Route, Routes } from 'react-router';
import Payouts from '@polkadot/app-staking/Payouts';
import useSortedTargets from '@polkadot/app-staking/useSortedTargets';
+import Pools from '@polkadot/app-staking2/Pools';
import useOwnPools from '@polkadot/app-staking2/Pools/useOwnPools';
import { styled, Tabs } from '@polkadot/react-components';
import { useAccounts, useApi, useAvailableSlashes, useCallMulti, useFavorites, useOwnStashInfos } from '@polkadot/react-hooks';
@@ -112,6 +113,12 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement
+
+ }
+ path='pools'
+ />
Date: Thu, 10 Apr 2025 12:03:46 +0530
Subject: [PATCH 05/38] feat: enabled bags page for staking-next
---
packages/page-staking-next/src/index.tsx | 11 +++++++++--
tsconfig.base.json | 1 +
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/packages/page-staking-next/src/index.tsx b/packages/page-staking-next/src/index.tsx
index d009217afc1a..f10fe6590989 100644
--- a/packages/page-staking-next/src/index.tsx
+++ b/packages/page-staking-next/src/index.tsx
@@ -8,6 +8,7 @@ import type { BN } from '@polkadot/util';
import React, { useMemo, useState } from 'react';
import { Route, Routes } from 'react-router';
+import Bags from '@polkadot/app-staking/Bags';
import Payouts from '@polkadot/app-staking/Payouts';
import useSortedTargets from '@polkadot/app-staking/useSortedTargets';
import Pools from '@polkadot/app-staking2/Pools';
@@ -115,9 +116,9 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement
+
}
- path='pools'
+ path='bags'
/>
+
+ }
+ path='pools'
+ />
Root Page}
index
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 7e6a3bb4f335..2ae1a2937f99 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -71,6 +71,7 @@
"@polkadot/app-society": ["page-society/src/index.tsx"],
"@polkadot/app-staking": ["page-staking/src/index.tsx"],
"@polkadot/app-staking/Payouts": ["page-staking/src/Payouts/index.tsx"],
+ "@polkadot/app-staking/Bags": ["page-staking/src/Bags/index.tsx"],
"@polkadot/app-staking/*": ["page-staking/src/*.ts"],
"@polkadot/app-staking-next": ["page-staking-next/src/index.tsx"],
"@polkadot/app-staking2": ["page-staking2/src/index.tsx"],
From 5df46e46ed13507e779742ffcd06c24ecf1695d5 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Thu, 10 Apr 2025 12:04:01 +0530
Subject: [PATCH 06/38] fix: build errors
---
packages/page-staking-next/tsconfig.build.json | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/page-staking-next/tsconfig.build.json b/packages/page-staking-next/tsconfig.build.json
index 9da3e5471bbe..fca1f4c867fe 100644
--- a/packages/page-staking-next/tsconfig.build.json
+++ b/packages/page-staking-next/tsconfig.build.json
@@ -7,6 +7,8 @@
},
"references": [
{ "path": "../react-components/tsconfig.build.json" },
- { "path": "../react-hooks/tsconfig.build.json" }
+ { "path": "../react-hooks/tsconfig.build.json" },
+ { "path": "../page-staking2/tsconfig.build.json" },
+ { "path": "../page-staking/tsconfig.build.json" },
]
}
From a18b2b804af692d5a10befdceaba7fcee1361a16 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Thu, 10 Apr 2025 12:04:15 +0530
Subject: [PATCH 07/38] chore: added translation files
---
packages/apps/public/locales/en/app-staking-next.json | 10 ++++++++++
packages/apps/public/locales/en/index.json | 1 +
2 files changed, 11 insertions(+)
create mode 100644 packages/apps/public/locales/en/app-staking-next.json
diff --git a/packages/apps/public/locales/en/app-staking-next.json b/packages/apps/public/locales/en/app-staking-next.json
new file mode 100644
index 000000000000..aff4523c2e4b
--- /dev/null
+++ b/packages/apps/public/locales/en/app-staking-next.json
@@ -0,0 +1,10 @@
+{
+ "Accounts": "Accounts",
+ "Bags": "Bags",
+ "Overview": "Overview",
+ "Payouts": "Payouts",
+ "Pools": "Pools",
+ "Slashes": "Slashes",
+ "Targets": "Targets",
+ "Validator stats": "Validator stats"
+}
\ No newline at end of file
diff --git a/packages/apps/public/locales/en/index.json b/packages/apps/public/locales/en/index.json
index afeac98eba19..dcba0639aad2 100644
--- a/packages/apps/public/locales/en/index.json
+++ b/packages/apps/public/locales/en/index.json
@@ -31,6 +31,7 @@
"app-signing.json",
"app-society.json",
"app-staking-legacy.json",
+ "app-staking-next.json",
"app-staking.json",
"app-staking2.json",
"app-storage.json",
From cea2d33fae63907010d137402d37ba37770ad85c Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Fri, 11 Apr 2025 10:12:49 +0530
Subject: [PATCH 08/38] refactor: relay and system staking page
---
packages/page-staking-next/src/index.tsx | 152 +++---------------
.../page-staking-next/src/relay/index.tsx | 12 ++
.../page-staking-next/src/system/index.tsx | 150 +++++++++++++++++
3 files changed, 183 insertions(+), 131 deletions(-)
create mode 100644 packages/page-staking-next/src/relay/index.tsx
create mode 100644 packages/page-staking-next/src/system/index.tsx
diff --git a/packages/page-staking-next/src/index.tsx b/packages/page-staking-next/src/index.tsx
index f10fe6590989..ce7d61b203d2 100644
--- a/packages/page-staking-next/src/index.tsx
+++ b/packages/page-staking-next/src/index.tsx
@@ -2,147 +2,37 @@
// SPDX-License-Identifier: Apache-2.0
import type { AppProps as Props } from '@polkadot/react-components/types';
-import type { ElectionStatus, ParaValidatorIndex, ValidatorId } from '@polkadot/types/interfaces';
-import type { BN } from '@polkadot/util';
-import React, { useMemo, useState } from 'react';
-import { Route, Routes } from 'react-router';
+import React from 'react';
-import Bags from '@polkadot/app-staking/Bags';
-import Payouts from '@polkadot/app-staking/Payouts';
-import useSortedTargets from '@polkadot/app-staking/useSortedTargets';
-import Pools from '@polkadot/app-staking2/Pools';
-import useOwnPools from '@polkadot/app-staking2/Pools/useOwnPools';
-import { styled, Tabs } from '@polkadot/react-components';
-import { useAccounts, useApi, useAvailableSlashes, useCallMulti, useFavorites, useOwnStashInfos } from '@polkadot/react-hooks';
-import { isFunction } from '@polkadot/util';
+import { styled } from '@polkadot/react-components';
+import { useApi } from '@polkadot/react-hooks';
-import { STORE_FAVS_BASE } from './constants.js';
-import { useTranslation } from './translate.js';
+import StakingRelayApp from './relay/index.js';
+import StakingSystemApp from './system/index.js';
-const HIDDEN_ACC = ['actions', 'payout'];
+function StakingApp ({ basePath, className = '', onStatusChange }: Props): React.ReactElement {
+ const { api, apiEndpoint } = useApi();
-const OPT_MULTI = {
- defaultValue: [false, undefined, {}] as [boolean, BN | undefined, Record],
- transform: ([eraElectionStatus, minValidatorBond, validators, activeValidatorIndices]: [ElectionStatus | null, BN | undefined, ValidatorId[] | null, ParaValidatorIndex[] | null]): [boolean, BN | undefined, Record] => [
- !!eraElectionStatus && eraElectionStatus.isOpen,
- minValidatorBond && !minValidatorBond.isZero()
- ? minValidatorBond
- : undefined,
- validators && activeValidatorIndices
- ? activeValidatorIndices.reduce((all, index) => ({ ...all, [validators[index.toNumber()].toString()]: true }), {})
- : {}
- ]
-};
-
-function StakingApp ({ basePath, className = '' }: Props): React.ReactElement {
- const { t } = useTranslation();
- const { api } = useApi();
- const [withLedger, setWithLedger] = useState(false);
- const [favorites, toggleFavorite] = useFavorites(STORE_FAVS_BASE);
- const { areAccountsLoaded, hasAccounts } = useAccounts();
- const ownStashes = useOwnStashInfos();
- const slashes = useAvailableSlashes();
- const targets = useSortedTargets(favorites, withLedger);
- const [isInElection, minCommission, paraValidators] = useCallMulti<[boolean, BN | undefined, Record]>([
- api.query.staking.eraElectionStatus,
- api.query.staking.minCommission,
- api.query.session.validators,
- (api.query.parasShared || api.query.shared)?.activeValidatorIndices
- ], OPT_MULTI);
- const ownPools = useOwnPools();
-
- const hasStashes = useMemo(
- () => hasAccounts && !!ownStashes && (ownStashes.length !== 0),
- [hasAccounts, ownStashes]
- );
-
- const ownValidators = useMemo(
- () => (ownStashes || []).filter(({ isStashValidating }) => isStashValidating),
- [ownStashes]
- );
-
- const items = useMemo(() => [
- {
- isRoot: true,
- name: 'overview',
- text: t('Overview')
- },
- {
- name: 'actions',
- text: t('Accounts')
- },
- hasStashes && isFunction(api.query.staking.activeEra) && {
- name: 'payout',
- text: t('Payouts')
- },
- isFunction(api.query.nominationPools?.minCreateBond) && {
- name: 'pools',
- text: t('Pools')
- },
- {
- alias: 'returns',
- name: 'targets',
- text: t('Targets')
- },
- hasStashes && isFunction((api.query.voterBagsList || api.query.bagsList || api.query.voterList)?.counterForListNodes) && {
- name: 'bags',
- text: t('Bags')
- },
- {
- count: slashes.reduce((count, [, unapplied]) => count + unapplied.length, 0),
- name: 'slashes',
- text: t('Slashes')
- },
- {
- hasParams: true,
- name: 'query',
- text: t('Validator stats')
- }
- ].filter((q): q is { name: string; text: string } => !!q), [api, hasStashes, slashes, t]);
+ // TODO: Must be removed in production
+ const isRelayGenesis = (api.genesisHash.toHex()) === '0x0e268177d92e92c5fa1a2374e4224da33bc9600c0318a9c25389a35f91238986';
return (
-
-
-
-
- }
- path='bags'
- />
-
- }
- path='payout'
- />
-
- }
- path='pools'
+ {apiEndpoint?.isRelay || isRelayGenesis
+ ? (
+
- Root Page}
- index
+ )
+ : (
+
-
-
+ )
+ }
);
}
diff --git a/packages/page-staking-next/src/relay/index.tsx b/packages/page-staking-next/src/relay/index.tsx
new file mode 100644
index 000000000000..f8c1307fa643
--- /dev/null
+++ b/packages/page-staking-next/src/relay/index.tsx
@@ -0,0 +1,12 @@
+// Copyright 2017-2025 @polkadot/app-staking-next authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+import type { AppProps as Props } from '@polkadot/react-components/types';
+
+import React from 'react';
+
+function StakingApp ({ basePath, className = '' }: Props): React.ReactElement {
+ return Relay Staking Page
;
+}
+
+export default React.memo(StakingApp);
diff --git a/packages/page-staking-next/src/system/index.tsx b/packages/page-staking-next/src/system/index.tsx
new file mode 100644
index 000000000000..3ace9a917175
--- /dev/null
+++ b/packages/page-staking-next/src/system/index.tsx
@@ -0,0 +1,150 @@
+// Copyright 2017-2025 @polkadot/app-staking-next authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+import type { AppProps as Props } from '@polkadot/react-components/types';
+import type { ElectionStatus, ParaValidatorIndex, ValidatorId } from '@polkadot/types/interfaces';
+import type { BN } from '@polkadot/util';
+
+import React, { useMemo, useState } from 'react';
+import { Route, Routes } from 'react-router';
+
+import Bags from '@polkadot/app-staking/Bags';
+import Payouts from '@polkadot/app-staking/Payouts';
+import useSortedTargets from '@polkadot/app-staking/useSortedTargets';
+import Pools from '@polkadot/app-staking2/Pools';
+import useOwnPools from '@polkadot/app-staking2/Pools/useOwnPools';
+import { Tabs } from '@polkadot/react-components';
+import { useAccounts, useApi, useAvailableSlashes, useCallMulti, useFavorites, useOwnStashInfos } from '@polkadot/react-hooks';
+import { isFunction } from '@polkadot/util';
+
+import { STORE_FAVS_BASE } from '../constants.js';
+import { useTranslation } from '../translate.js';
+
+const HIDDEN_ACC = ['actions', 'payout'];
+
+const OPT_MULTI = {
+ defaultValue: [false, undefined, {}] as [boolean, BN | undefined, Record],
+ transform: ([eraElectionStatus, minValidatorBond, validators, activeValidatorIndices]: [ElectionStatus | null, BN | undefined, ValidatorId[] | null, ParaValidatorIndex[] | null]): [boolean, BN | undefined, Record] => [
+ !!eraElectionStatus && eraElectionStatus.isOpen,
+ minValidatorBond && !minValidatorBond.isZero()
+ ? minValidatorBond
+ : undefined,
+ validators && activeValidatorIndices
+ ? activeValidatorIndices.reduce((all, index) => ({ ...all, [validators[index.toNumber()].toString()]: true }), {})
+ : {}
+ ]
+};
+
+function StakingApp ({ basePath, className = '' }: Props): React.ReactElement {
+ const { t } = useTranslation();
+ const { api } = useApi();
+ const [withLedger, setWithLedger] = useState(false);
+ const [favorites, toggleFavorite] = useFavorites(STORE_FAVS_BASE);
+ const { areAccountsLoaded, hasAccounts } = useAccounts();
+ const ownStashes = useOwnStashInfos();
+ const slashes = useAvailableSlashes();
+ const targets = useSortedTargets(favorites, withLedger);
+ const [isInElection, minCommission, paraValidators] = useCallMulti<[boolean, BN | undefined, Record]>([
+ api.query.staking.eraElectionStatus,
+ api.query.staking.minCommission,
+ api.query.session.validators,
+ (api.query.parasShared || api.query.shared)?.activeValidatorIndices
+ ], OPT_MULTI);
+ const ownPools = useOwnPools();
+
+ const hasStashes = useMemo(
+ () => hasAccounts && !!ownStashes && (ownStashes.length !== 0),
+ [hasAccounts, ownStashes]
+ );
+
+ const ownValidators = useMemo(
+ () => (ownStashes || []).filter(({ isStashValidating }) => isStashValidating),
+ [ownStashes]
+ );
+
+ const items = useMemo(() => [
+ {
+ isRoot: true,
+ name: 'overview',
+ text: t('Overview')
+ },
+ {
+ name: 'actions',
+ text: t('Accounts')
+ },
+ hasStashes && isFunction(api.query.staking.activeEra) && {
+ name: 'payout',
+ text: t('Payouts')
+ },
+ isFunction(api.query.nominationPools?.minCreateBond) && {
+ name: 'pools',
+ text: t('Pools')
+ },
+ {
+ alias: 'returns',
+ name: 'targets',
+ text: t('Targets')
+ },
+ hasStashes && isFunction((api.query.voterBagsList || api.query.bagsList || api.query.voterList)?.counterForListNodes) && {
+ name: 'bags',
+ text: t('Bags')
+ },
+ {
+ count: slashes.reduce((count, [, unapplied]) => count + unapplied.length, 0),
+ name: 'slashes',
+ text: t('Slashes')
+ },
+ {
+ hasParams: true,
+ name: 'query',
+ text: t('Validator stats')
+ }
+ ].filter((q): q is { name: string; text: string } => !!q), [api, hasStashes, slashes, t]);
+
+ return (
+ <>
+
+
+
+
+ }
+ path='bags'
+ />
+
+ }
+ path='payout'
+ />
+
+ }
+ path='pools'
+ />
+ Root Page}
+ index
+ />
+
+
+ >
+ );
+}
+
+export default React.memo(StakingApp);
From fdce3a43392cbba91d2cce135ad9d6b30a80623b Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Fri, 11 Apr 2025 10:27:50 +0530
Subject: [PATCH 09/38] feat: enabled overview page for staking-next
---
.../page-staking-next/src/system/index.tsx | 36 +++++++++++++++++--
tsconfig.base.json | 1 +
2 files changed, 34 insertions(+), 3 deletions(-)
diff --git a/packages/page-staking-next/src/system/index.tsx b/packages/page-staking-next/src/system/index.tsx
index 3ace9a917175..cec9f873df52 100644
--- a/packages/page-staking-next/src/system/index.tsx
+++ b/packages/page-staking-next/src/system/index.tsx
@@ -1,20 +1,23 @@
// Copyright 2017-2025 @polkadot/app-staking-next authors & contributors
// SPDX-License-Identifier: Apache-2.0
+import type { DeriveStakingOverview } from '@polkadot/api-derive/types';
import type { AppProps as Props } from '@polkadot/react-components/types';
import type { ElectionStatus, ParaValidatorIndex, ValidatorId } from '@polkadot/types/interfaces';
import type { BN } from '@polkadot/util';
-import React, { useMemo, useState } from 'react';
+import React, { useCallback, useMemo, useState } from 'react';
import { Route, Routes } from 'react-router';
import Bags from '@polkadot/app-staking/Bags';
import Payouts from '@polkadot/app-staking/Payouts';
+import useNominations from '@polkadot/app-staking/useNominations';
import useSortedTargets from '@polkadot/app-staking/useSortedTargets';
+import Validators from '@polkadot/app-staking/Validators';
import Pools from '@polkadot/app-staking2/Pools';
import useOwnPools from '@polkadot/app-staking2/Pools/useOwnPools';
import { Tabs } from '@polkadot/react-components';
-import { useAccounts, useApi, useAvailableSlashes, useCallMulti, useFavorites, useOwnStashInfos } from '@polkadot/react-hooks';
+import { useAccounts, useApi, useAvailableSlashes, useCall, useCallMulti, useFavorites, useOwnStashInfos } from '@polkadot/react-hooks';
import { isFunction } from '@polkadot/util';
import { STORE_FAVS_BASE } from '../constants.js';
@@ -40,6 +43,7 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement(api.derive.staking.overview);
+
+ const toggleNominatedBy = useCallback(
+ () => setLoadNominations(true),
+ []
+ );
+
+ const hasQueries = useMemo(
+ () => hasAccounts && !!(api.query.imOnline?.authoredBlocks) && !!(api.query.staking.activeEra),
+ [api, hasAccounts]
+ );
const hasStashes = useMemo(
() => hasAccounts && !!ownStashes && (ownStashes.length !== 0),
@@ -138,7 +154,21 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement
Root Page}
+ element={
+
+ }
index
/>
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 2ae1a2937f99..bf368a92a884 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -72,6 +72,7 @@
"@polkadot/app-staking": ["page-staking/src/index.tsx"],
"@polkadot/app-staking/Payouts": ["page-staking/src/Payouts/index.tsx"],
"@polkadot/app-staking/Bags": ["page-staking/src/Bags/index.tsx"],
+ "@polkadot/app-staking/Validators": ["page-staking/src/Validators/index.tsx"],
"@polkadot/app-staking/*": ["page-staking/src/*.ts"],
"@polkadot/app-staking-next": ["page-staking-next/src/index.tsx"],
"@polkadot/app-staking2": ["page-staking2/src/index.tsx"],
From 723e46dd7c2297b4931557146f2ce986e5768e84 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Fri, 11 Apr 2025 10:31:16 +0530
Subject: [PATCH 10/38] feat: enabled actions page for staking-next
---
packages/page-staking-next/src/system/index.tsx | 13 +++++++++++++
tsconfig.base.json | 1 +
2 files changed, 14 insertions(+)
diff --git a/packages/page-staking-next/src/system/index.tsx b/packages/page-staking-next/src/system/index.tsx
index cec9f873df52..60383d3d210c 100644
--- a/packages/page-staking-next/src/system/index.tsx
+++ b/packages/page-staking-next/src/system/index.tsx
@@ -9,6 +9,7 @@ import type { BN } from '@polkadot/util';
import React, { useCallback, useMemo, useState } from 'react';
import { Route, Routes } from 'react-router';
+import Actions from '@polkadot/app-staking/Actions';
import Bags from '@polkadot/app-staking/Bags';
import Payouts from '@polkadot/app-staking/Payouts';
import useNominations from '@polkadot/app-staking/useNominations';
@@ -153,6 +154,18 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement
+
+ }
+ path='actions'
+ />
Date: Fri, 11 Apr 2025 10:37:06 +0530
Subject: [PATCH 11/38] feat: enabled targets page for staking-next
---
.../page-staking-next/src/system/index.tsx | 23 ++++++++++++++++++-
tsconfig.base.json | 1 +
2 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/packages/page-staking-next/src/system/index.tsx b/packages/page-staking-next/src/system/index.tsx
index 60383d3d210c..f6fc1352c1e2 100644
--- a/packages/page-staking-next/src/system/index.tsx
+++ b/packages/page-staking-next/src/system/index.tsx
@@ -12,6 +12,7 @@ import { Route, Routes } from 'react-router';
import Actions from '@polkadot/app-staking/Actions';
import Bags from '@polkadot/app-staking/Bags';
import Payouts from '@polkadot/app-staking/Payouts';
+import Targets from '@polkadot/app-staking/Targets';
import useNominations from '@polkadot/app-staking/useNominations';
import useSortedTargets from '@polkadot/app-staking/useSortedTargets';
import Validators from '@polkadot/app-staking/Validators';
@@ -39,7 +40,7 @@ const OPT_MULTI = {
]
};
-function StakingApp ({ basePath, className = '' }: Props): React.ReactElement {
+function StakingApp ({ basePath }: Props): React.ReactElement {
const { t } = useTranslation();
const { api } = useApi();
const [withLedger, setWithLedger] = useState(false);
@@ -64,6 +65,11 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement setWithLedger(true),
+ []
+ );
+
const hasQueries = useMemo(
() => hasAccounts && !!(api.query.imOnline?.authoredBlocks) && !!(api.query.staking.activeEra),
[api, hasAccounts]
@@ -166,6 +172,21 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement
+
+ }
+ path='targets'
+ />
Date: Fri, 11 Apr 2025 10:46:20 +0530
Subject: [PATCH 12/38] feat: enabled query page for staking-next
---
packages/page-staking-next/src/system/index.tsx | 7 +++++++
packages/page-staking/src/Query/index.tsx | 11 ++++++-----
packages/page-staking/src/index.tsx | 2 +-
tsconfig.base.json | 1 +
4 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/packages/page-staking-next/src/system/index.tsx b/packages/page-staking-next/src/system/index.tsx
index f6fc1352c1e2..77189cd74496 100644
--- a/packages/page-staking-next/src/system/index.tsx
+++ b/packages/page-staking-next/src/system/index.tsx
@@ -12,6 +12,7 @@ import { Route, Routes } from 'react-router';
import Actions from '@polkadot/app-staking/Actions';
import Bags from '@polkadot/app-staking/Bags';
import Payouts from '@polkadot/app-staking/Payouts';
+import Query from '@polkadot/app-staking/Query';
import Targets from '@polkadot/app-staking/Targets';
import useNominations from '@polkadot/app-staking/useNominations';
import useSortedTargets from '@polkadot/app-staking/useSortedTargets';
@@ -160,6 +161,12 @@ function StakingApp ({ basePath }: Props): React.ReactElement {
}
path='pools'
/>
+
+ }
+ path='query/:value?'
+ />
{
+function Query ({ basePath, className }: Props): React.ReactElement {
const { t } = useTranslation();
const { api } = useApi();
const { value } = useParams<{ value: string }>();
@@ -35,8 +36,8 @@ function Query ({ className }: Props): React.ReactElement {
);
const _onQuery = useCallback(
- () => doQuery(validatorId),
- [validatorId]
+ () => doQuery(basePath, validatorId),
+ [basePath, validatorId]
);
if (!labels) {
diff --git a/packages/page-staking/src/index.tsx b/packages/page-staking/src/index.tsx
index 32994f220d80..aee52d1277b0 100644
--- a/packages/page-staking/src/index.tsx
+++ b/packages/page-staking/src/index.tsx
@@ -168,7 +168,7 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement
+
}
path='query/:value?'
/>
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 227ca1e14e02..719d1c53b72a 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -75,6 +75,7 @@
"@polkadot/app-staking/Validators": ["page-staking/src/Validators/index.tsx"],
"@polkadot/app-staking/Actions": ["page-staking/src/Actions/index.tsx"],
"@polkadot/app-staking/Targets": ["page-staking/src/Targets/index.tsx"],
+ "@polkadot/app-staking/Query": ["page-staking/src/Query/index.tsx"],
"@polkadot/app-staking/*": ["page-staking/src/*.ts"],
"@polkadot/app-staking-next": ["page-staking-next/src/index.tsx"],
"@polkadot/app-staking2": ["page-staking2/src/index.tsx"],
From 78cb444abad28b20ca2260cd4b9379dbaff0a5bc Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Mon, 14 Apr 2025 12:27:31 +0530
Subject: [PATCH 13/38] feat: add Command Center component and integrate with
Relay Staking Page
---
.../src/CommandCenter/index.tsx | 307 ++++++++++++++++++
packages/page-staking-next/src/index.tsx | 4 +-
.../page-staking-next/src/relay/index.tsx | 34 +-
3 files changed, 340 insertions(+), 5 deletions(-)
create mode 100644 packages/page-staking-next/src/CommandCenter/index.tsx
diff --git a/packages/page-staking-next/src/CommandCenter/index.tsx b/packages/page-staking-next/src/CommandCenter/index.tsx
new file mode 100644
index 000000000000..8b45b96444a9
--- /dev/null
+++ b/packages/page-staking-next/src/CommandCenter/index.tsx
@@ -0,0 +1,307 @@
+// Copyright 2017-2025 @polkadot/app-staking-next authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+import type { ApiPromise } from '@polkadot/api';
+import type { AccountId32, Event } from '@polkadot/types/interfaces';
+import type { IEventData } from '@polkadot/types/types';
+
+import React, { useEffect, useMemo, useState } from 'react';
+import { Link } from 'react-router-dom';
+
+import { CardSummary, Expander, MarkWarning, styled } from '@polkadot/react-components';
+import { useApi } from '@polkadot/react-hooks';
+import { Event as EventDisplay } from '@polkadot/react-params';
+import { formatNumber } from '@polkadot/util';
+
+import { useTranslation } from '../translate.js';
+
+// const getApi = async (url: string) => {
+// const api = await ApiPromise.create({
+// provider: new WsProvider(url)
+// });
+
+// await api.isReadyOrError;
+
+// return api;
+// };
+
+const commandCenterHandler = async (rcApi: ApiPromise, setRcOutout: React.Dispatch>): Promise => {
+ // const rcApi = await getApi('ws://127.0.0.1:54169');
+ // const ahApi = await getApi('ws://127.0.0.1:54175');
+
+ // const manager = UpdateManager.getInstance();
+ // manager.hook();
+
+ // let ahOutput: string[] = [];
+ // const rcEvents: string[] = [];
+ // const ahEvents: string[] = [];
+
+ await rcApi.rpc.chain.subscribeFinalizedHeads(async (header) => {
+ // --- RC:
+ // current session index
+ const index = await rcApi.query.session.currentIndex();
+ // whether the session pallet has a queued validator set within it
+ const hasQueuedInSession = await rcApi.query.session.queuedChanged();
+ // the range of historical session data that we have in the RC.
+ const historicalRange = await rcApi.query.historical.storedRange();
+
+ // whether there is a validator set queued in ah-client. for this we need to display only the id and the length of the set.
+ const hasQueuedInClient =
+ await rcApi.query.stakingNextAhClient.validatorSet();
+ // whether we have already passed a new validator set to session, and therefore in the next session rotation we want to pass this id to AH.
+ const hasNextActiveId =
+ await rcApi.query.stakingNextAhClient.nextSessionChangesValidators();
+ // whether the AhClient pallet is blocked or not, useful for migration signal from the fellowship.
+ const isBlocked = await rcApi.query.stakingNextAhClient.isBlocked();
+
+ // Events that we are interested in from RC:
+ const eventsOfInterest = (await rcApi.query.system.events())
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ .map((e) => e.event)
+ .filter((e) => {
+ const ahClientEvents = (e: IEventData) =>
+ e.section === 'stakingNextAhClient';
+ const sessionEvents = (e: IEventData) =>
+ e.section === 'session' || e.section === 'historical';
+
+ return ahClientEvents(e.data) || sessionEvents(e.data);
+ });
+ // .map(
+ // (e) =>
+ // `${e.section.toString()}::${e.method.toString()}(${e.data.toString()})`
+ // );
+
+ // rcEvents.push(...eventsOfInterest);
+
+ setRcOutout((prev) => {
+ const parsedHasQueuedInClient = rcApi.createType('Option<(u32,Vec)>', hasQueuedInClient);
+
+ return [
+ {
+ events: eventsOfInterest,
+ finalizedBlock: header.number.toNumber(),
+ session: {
+ hasQueuedInSession: hasQueuedInSession.isTrue,
+ historicalRange: historicalRange.isSome ? [historicalRange.unwrap()[0].toNumber(), historicalRange.unwrap()[1].toNumber()] : undefined,
+ index: index.toNumber()
+ },
+ stakingNextAhClient: {
+ hasNextActiveId: hasNextActiveId.isEmpty ? undefined : rcApi.createType('Option', hasNextActiveId).unwrap().toNumber(),
+ hasQueuedInClient: parsedHasQueuedInClient.isNone ? undefined : [parsedHasQueuedInClient.unwrap()[0].toNumber(), parsedHasQueuedInClient.unwrap()[1]],
+ isBlocked: isBlocked.toHuman() !== 'Not'
+ }
+ },
+ ...prev];
+ });
+
+ // rcOutput = [
+ // 'RC:',
+ // `finalized block ${header.number.toNumber()}`,
+ // `RC.session: index=${index.toNumber()}, hasQueuedInSession=${hasQueuedInSession.toString()}, historicalRange=${historicalRange.toString()}`,
+ // `RC.stakingNextAhClient: hasQueuedInClient=${hasQueuedInClient.toString()}, hasNextActiveId=${hasNextActiveId.toString()}, isBlocked=${isBlocked.toString()}`,
+ // `RC.events: ${JSON.stringify(rcEvents)}`,
+ // '----'
+ // ];
+
+ // manager.update(rcOutput.concat(ahOutput));
+ // rcOutput.concat(ahOutput);
+ });
+
+ // AH:
+ // await ahApi.rpc.chain.subscribeFinalizedHeads(async (header) => {
+ // // the current planned era
+ // const currentEra = await ahApi.query.staking.currentEra();
+ // // the active era
+ // const activeEra = await ahApi.query.staking.activeEra();
+ // // the starting index of the active era
+ // const erasStartSessionIndex =
+ // // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // // @ts-ignore
+ // await ahApi.query.staking.erasStartSessionIndex(activeEra.unwrap().index);
+
+ // // the basic state of the election provider
+ // const phase = await ahApi.query.multiBlock.currentPhase();
+ // const round = await ahApi.query.multiBlock.round();
+ // const snapshotRange = (
+ // await ahApi.query.multiBlock.pagedVoterSnapshotHash.entries()
+ // )
+ // .map(([k]) => k.args[0])
+ // .sort();
+ // const queuedScore =
+ // await ahApi.query.multiBlockVerifier.queuedSolutionScore();
+ // const signedSubmissions =
+ // await ahApi.query.multiBlockSigned.sortedScores(round);
+
+ // // Events that we are interested in from RC:
+ // const eventsOfInterest = (await ahApi.query.system.events())
+ // // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // // @ts-ignore
+ // .map((e) => e.event)
+ // .filter((e) => {
+ // const election = (e: IEventData) =>
+ // e.section === 'multiBlock' ||
+ // e.section === 'multiBlockVerifier' ||
+ // e.section === 'multiBlockSigned' ||
+ // e.section === 'multiBlockUnsigned';
+ // const rcClient = (e: IEventData) => e.section === 'stakingNextRcClient';
+ // const staking = (e: IEventData) =>
+ // e.section === 'staking' &&
+ // (e.method === 'EraPaid' ||
+ // e.method === 'SessionRotated' ||
+ // e.method === 'PagedElectionProceeded');
+
+ // return election(e.data) || rcClient(e.data) || staking(e.data);
+ // })
+ // .map(
+ // (e) =>
+ // `${e.section.toString()}::${e.method.toString()}(${e.data.toString()})`
+ // );
+
+ // ahEvents.push(...eventsOfInterest);
+
+ // ahOutput = [
+ // 'AH:',
+ // `finalized block ${header.number.toNumber()}`,
+ // // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // // @ts-ignore
+ // `AH.staking: currentEra=${currentEra.unwrap().toNumber()}, activeEra=${activeEra.unwrap().toString()}, erasStartSessionIndex(${activeEra.unwrap().index.toNumber()})=${erasStartSessionIndex.unwrap().toNumber()}`,
+ // `multiBlock: phase=${phase.toString()}, round=${round.toString()}, snapshotRange=${JSON.stringify(snapshotRange)}, queuedScore=${queuedScore.toString()}, signedSubmissions=${signedSubmissions.toString()}`,
+ // `AH.events: ${JSON.stringify(ahEvents)}`,
+ // '----'
+ // ];
+
+ // // manager.update(rcOutput.concat(ahOutput));
+ // rcOutput.concat(ahOutput);
+ // });
+};
+
+interface IRcOutput {
+ finalizedBlock: number,
+ session: {
+ index: number,
+ hasQueuedInSession: boolean,
+ historicalRange?: [number, number]
+ },
+ stakingNextAhClient: {
+ isBlocked: boolean
+ hasNextActiveId?: number,
+ hasQueuedInClient?: [number, AccountId32[]]
+ },
+ events: Event[]
+}
+
+function CommandCenter () {
+ const { t } = useTranslation();
+ const { api } = useApi();
+ const [rcOutput, setRcOutput] = useState([]);
+
+ const rcApi = useMemo(() => api, [api]);
+
+ useEffect(() => {
+ commandCenterHandler(rcApi, setRcOutput).catch((e) => console.log(e));
+ }, [rcApi]);
+
+ return <>
+
+
+ {t('Relay chain')}
+
+
+ {rcOutput.map((rc) => {
+ return (
+
+
+
+ #{formatNumber(rc.finalizedBlock)}
+
+
+
+ #{formatNumber(rc.session.index)}
+
+ {rc.session.historicalRange &&
+
+ [{rc.session.historicalRange?.[0]}, {rc.session.historicalRange?.[1]}]
+
+ }
+
+
+
+
{t('events')}
+ {rc.events.map((event) => {
+ const eventName = `${event.section}.${event.method}`;
+
+ return (
+
+
+
+
+ );
+ })}
+ {rc.events.length === 0 && }
+
+
+ );
+ })}
+
+
+ >;
+}
+
+const StyledSection = styled.section`
+ margin-block: 1rem;
+ max-height: 40vh;
+ overflow: auto;
+
+ .relay__chain {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ background: var(--bg-table);
+ margin-bottom: 0.35rem;
+ padding: 0.8rem 1rem;
+ border-radius: 0.5rem;
+
+ > div {
+ &:first-child{
+ display: flex;
+ align-items: center;
+ }
+ }
+
+ h4 {
+ font-weight: 400;
+ font-size: medium;
+ }
+
+ .session__summary {
+ display: flex;
+
+ .ui--Labelled-content {
+ font-size: var(--font-size-h3);
+ font-weight: var(--font-weight-normal);
+ }
+ }
+
+ .events__summary {
+ h3 {
+ font-weight: 500;
+ font-size: var(--font-size-h2);
+ }
+ }
+ }
+`;
+
+export default React.memo(CommandCenter);
diff --git a/packages/page-staking-next/src/index.tsx b/packages/page-staking-next/src/index.tsx
index ce7d61b203d2..27b4eeda9e60 100644
--- a/packages/page-staking-next/src/index.tsx
+++ b/packages/page-staking-next/src/index.tsx
@@ -8,8 +8,8 @@ import React from 'react';
import { styled } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
-import StakingRelayApp from './relay/index.js';
-import StakingSystemApp from './system/index.js';
+import StakingRelayApp from './Relay/index.js';
+import StakingSystemApp from './System/index.js';
function StakingApp ({ basePath, className = '', onStatusChange }: Props): React.ReactElement {
const { api, apiEndpoint } = useApi();
diff --git a/packages/page-staking-next/src/relay/index.tsx b/packages/page-staking-next/src/relay/index.tsx
index f8c1307fa643..a9ba48e8de0a 100644
--- a/packages/page-staking-next/src/relay/index.tsx
+++ b/packages/page-staking-next/src/relay/index.tsx
@@ -3,10 +3,38 @@
import type { AppProps as Props } from '@polkadot/react-components/types';
-import React from 'react';
+import React, { useMemo } from 'react';
+import { Route, Routes } from 'react-router-dom';
-function StakingApp ({ basePath, className = '' }: Props): React.ReactElement {
- return Relay Staking Page
;
+import { Tabs } from '@polkadot/react-components';
+
+import CommandCenter from '../CommandCenter/index.js';
+import { useTranslation } from '../translate.js';
+
+function StakingApp ({ basePath }: Props): React.ReactElement {
+ const { t } = useTranslation();
+
+ const items = useMemo(() => [
+ {
+ isRoot: true,
+ name: 'command-center',
+ text: t('Command Center')
+ }], [t]);
+
+ return <>
+
+
+
+ }
+ index
+ />
+
+
+ >;
}
export default React.memo(StakingApp);
From 20aa25c6f1d85700c0cdbf0cd32e68c7ee709714 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Mon, 14 Apr 2025 12:35:06 +0530
Subject: [PATCH 14/38] feat: limit event display to a maximum of 5 and adjust
styling in Command Center
---
packages/page-staking-next/src/CommandCenter/index.tsx | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/packages/page-staking-next/src/CommandCenter/index.tsx b/packages/page-staking-next/src/CommandCenter/index.tsx
index 8b45b96444a9..df8cef3140c6 100644
--- a/packages/page-staking-next/src/CommandCenter/index.tsx
+++ b/packages/page-staking-next/src/CommandCenter/index.tsx
@@ -15,6 +15,8 @@ import { formatNumber } from '@polkadot/util';
import { useTranslation } from '../translate.js';
+const MAX_EVENTS = 5;
+
// const getApi = async (url: string) => {
// const api = await ApiPromise.create({
// provider: new WsProvider(url)
@@ -92,7 +94,7 @@ const commandCenterHandler = async (rcApi: ApiPromise, setRcOutout: React.Dispat
isBlocked: isBlocked.toHuman() !== 'Not'
}
},
- ...prev];
+ ...prev.slice(0, MAX_EVENTS - 1)];
});
// rcOutput = [
@@ -263,8 +265,8 @@ function CommandCenter () {
const StyledSection = styled.section`
margin-block: 1rem;
- max-height: 40vh;
- overflow: auto;
+ // max-height: 40vh;
+ // overflow: auto;
.relay__chain {
display: grid;
From 728e73bdc6755f61484cbbc3ee3d8f97db222fce Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Mon, 14 Apr 2025 13:28:30 +0530
Subject: [PATCH 15/38] feat: display more info for relay command center
---
.../src/CommandCenter/index.tsx | 69 +++++++++++++------
1 file changed, 47 insertions(+), 22 deletions(-)
diff --git a/packages/page-staking-next/src/CommandCenter/index.tsx b/packages/page-staking-next/src/CommandCenter/index.tsx
index df8cef3140c6..f6479c0973ca 100644
--- a/packages/page-staking-next/src/CommandCenter/index.tsx
+++ b/packages/page-staking-next/src/CommandCenter/index.tsx
@@ -15,7 +15,7 @@ import { formatNumber } from '@polkadot/util';
import { useTranslation } from '../translate.js';
-const MAX_EVENTS = 5;
+const MAX_EVENTS = 10;
// const getApi = async (url: string) => {
// const api = await ApiPromise.create({
@@ -57,7 +57,7 @@ const commandCenterHandler = async (rcApi: ApiPromise, setRcOutout: React.Dispat
const isBlocked = await rcApi.query.stakingNextAhClient.isBlocked();
// Events that we are interested in from RC:
- const eventsOfInterest = (await rcApi.query.system.events())
+ const eventsOfInterest = (await (await rcApi.at(header.hash.toHex())).query.system.events())
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
.map((e) => e.event)
@@ -216,11 +216,11 @@ function CommandCenter () {
className='relay__chain'
key={rc.finalizedBlock}
>
-
-
- #{formatNumber(rc.finalizedBlock)}
-
+
+
+ #{formatNumber(rc.finalizedBlock)}
+
#{formatNumber(rc.session.index)}
@@ -230,6 +230,19 @@ function CommandCenter () {
}
+
+ {rc.stakingNextAhClient.isBlocked &&
}
+ {rc.stakingNextAhClient.hasQueuedInClient &&
+
+
+
+ {rc.stakingNextAhClient.hasQueuedInClient[0]}
+
+
+ {rc.stakingNextAhClient.hasQueuedInClient[1].length}
+
+
}
+
{t('events')}
@@ -265,8 +278,18 @@ function CommandCenter () {
const StyledSection = styled.section`
margin-block: 1rem;
- // max-height: 40vh;
- // overflow: auto;
+ max-height: 40vh;
+ overflow: auto;
+
+ .warning {
+ max-width: fit-content;
+ margin-left: 0;
+ }
+
+ .ui--Labelled-content {
+ font-size: var(--font-size-h3);
+ font-weight: var(--font-weight-normal);
+ }
.relay__chain {
display: grid;
@@ -276,24 +299,26 @@ const StyledSection = styled.section`
padding: 0.8rem 1rem;
border-radius: 0.5rem;
- > div {
- &:first-child{
+ .details {
+ display: flex;
+ flex-direction: column;
+ gap: 0.8rem;
+
+ .session__summary {
display: flex;
align-items: center;
- }
- }
-
- h4 {
- font-weight: 400;
- font-size: medium;
- }
- .session__summary {
- display: flex;
+ h4 {
+ font-weight: 400;
+ font-size: medium;
+ }
+ }
- .ui--Labelled-content {
- font-size: var(--font-size-h3);
- font-weight: var(--font-weight-normal);
+ .stakingNextAhClient__summary {
+ .stakingNextAhClient__hasQueuedInClient {
+ display: flex;
+ justify-content: space-evenly;
+ }
}
}
From f0615b055cdd613c7251244d9fccd7c49fa2f8d7 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Tue, 15 Apr 2025 10:05:55 +0530
Subject: [PATCH 16/38] chore: added translations
---
.../apps/public/locales/en/app-staking-next.json | 12 +++++++++++-
packages/apps/public/locales/en/translation.json | 5 +++++
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/packages/apps/public/locales/en/app-staking-next.json b/packages/apps/public/locales/en/app-staking-next.json
index aff4523c2e4b..23a69fbed65b 100644
--- a/packages/apps/public/locales/en/app-staking-next.json
+++ b/packages/apps/public/locales/en/app-staking-next.json
@@ -1,10 +1,20 @@
{
"Accounts": "Accounts",
+ "Asset Hub client pallet(AhClient) is blocked currently, useful for migration signal from the fellowship.": "Asset Hub client pallet(AhClient) is blocked currently, useful for migration signal from the fellowship.",
"Bags": "Bags",
+ "Command Center": "Command Center",
+ "No events available": "No events available",
"Overview": "Overview",
"Payouts": "Payouts",
"Pools": "Pools",
+ "Relay chain": "Relay chain",
"Slashes": "Slashes",
"Targets": "Targets",
- "Validator stats": "Validator stats"
+ "There is a validator set queued in ah-client.": "There is a validator set queued in ah-client.",
+ "Validator stats": "Validator stats",
+ "events": "events",
+ "historical range": "historical range",
+ "id": "id",
+ "number of validators": "number of validators",
+ "session": "session"
}
\ No newline at end of file
diff --git a/packages/apps/public/locales/en/translation.json b/packages/apps/public/locales/en/translation.json
index 63fe62a39d45..13f47284421c 100644
--- a/packages/apps/public/locales/en/translation.json
+++ b/packages/apps/public/locales/en/translation.json
@@ -102,6 +102,7 @@
"Approving of all or none of the options is equivalent and will not affect the outcome of the poll.": "",
"As a council member, you can suggest an initial value for the tip, each other council member can suggest their own.": "",
"As such it is recommended that you setup a proxy to control operations via the stash.": "",
+ "Asset Hub client pallet(AhClient) is blocked currently, useful for migration signal from the fellowship.": "",
"At block": "",
"Auctions": "",
"Auctions will be deprecated in favor of Coretime. When Coretime is active in Polkadot, this page will be removed.": "",
@@ -175,6 +176,7 @@
"Close deadline": "",
"Close proposal": "",
"Color": "",
+ "Command Center": "",
"Committee prime member, default voting": "",
"Completed": "",
"Confirm ABI removal": "",
@@ -953,6 +955,7 @@
"There are no pending proposals": "",
"There are no registered parachains": "",
"There are no unapplied/pending slashes": "",
+ "There is a validator set queued in ah-client.": "",
"There is an existing reference count on the sender account. As such the account cannot be reaped from the state.": "",
"There is currently an ongoing election for new validator candidates. As such staking operations are not permitted.": "",
"There is no on-chain attestation statement associated with the Ethereum account {{ethereumAddress}}": "",
@@ -1473,6 +1476,7 @@
"hex-encoded call": "",
"hex-encoded storage key": "",
"historic results": "",
+ "historical range": "",
"holders": "",
"https://example.com": "",
"id": "",
@@ -1603,6 +1607,7 @@
"nominators": "",
"nominators to be removed": "",
"none": "",
+ "number of validators": "",
"of {{locked}} vested": "",
"on-chain bonding duration": "",
"on-chain multisig accounts": "",
From d4e9e31c8b8105ebf5bb324bc8e10ac112bb9b3e Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Tue, 15 Apr 2025 10:13:44 +0530
Subject: [PATCH 17/38] refactor: relay section for command center
---
.../src/CommandCenter/index.tsx | 139 +----------------
.../src/CommandCenter/relay.tsx | 144 ++++++++++++++++++
2 files changed, 151 insertions(+), 132 deletions(-)
create mode 100644 packages/page-staking-next/src/CommandCenter/relay.tsx
diff --git a/packages/page-staking-next/src/CommandCenter/index.tsx b/packages/page-staking-next/src/CommandCenter/index.tsx
index f6479c0973ca..9c96585e8496 100644
--- a/packages/page-staking-next/src/CommandCenter/index.tsx
+++ b/packages/page-staking-next/src/CommandCenter/index.tsx
@@ -6,14 +6,10 @@ import type { AccountId32, Event } from '@polkadot/types/interfaces';
import type { IEventData } from '@polkadot/types/types';
import React, { useEffect, useMemo, useState } from 'react';
-import { Link } from 'react-router-dom';
-import { CardSummary, Expander, MarkWarning, styled } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
-import { Event as EventDisplay } from '@polkadot/react-params';
-import { formatNumber } from '@polkadot/util';
-import { useTranslation } from '../translate.js';
+import RelaySection from './relay.js';
const MAX_EVENTS = 10;
@@ -178,7 +174,7 @@ const commandCenterHandler = async (rcApi: ApiPromise, setRcOutout: React.Dispat
// });
};
-interface IRcOutput {
+export interface IRcOutput {
finalizedBlock: number,
session: {
index: number,
@@ -194,7 +190,6 @@ interface IRcOutput {
}
function CommandCenter () {
- const { t } = useTranslation();
const { api } = useApi();
const [rcOutput, setRcOutput] = useState([]);
@@ -204,131 +199,11 @@ function CommandCenter () {
commandCenterHandler(rcApi, setRcOutput).catch((e) => console.log(e));
}, [rcApi]);
- return <>
-
-
- {t('Relay chain')}
-
-
- {rcOutput.map((rc) => {
- return (
-
-
-
-
- #{formatNumber(rc.finalizedBlock)}
-
-
- #{formatNumber(rc.session.index)}
-
- {rc.session.historicalRange &&
-
- [{rc.session.historicalRange?.[0]}, {rc.session.historicalRange?.[1]}]
-
- }
-
-
- {rc.stakingNextAhClient.isBlocked &&
}
- {rc.stakingNextAhClient.hasQueuedInClient &&
-
-
-
- {rc.stakingNextAhClient.hasQueuedInClient[0]}
-
-
- {rc.stakingNextAhClient.hasQueuedInClient[1].length}
-
-
}
-
-
-
-
{t('events')}
- {rc.events.map((event) => {
- const eventName = `${event.section}.${event.method}`;
-
- return (
-
-
-
-
- );
- })}
- {rc.events.length === 0 && }
-
-
- );
- })}
-
-
- >;
+ return (
+ <>
+
+ >
+ );
}
-const StyledSection = styled.section`
- margin-block: 1rem;
- max-height: 40vh;
- overflow: auto;
-
- .warning {
- max-width: fit-content;
- margin-left: 0;
- }
-
- .ui--Labelled-content {
- font-size: var(--font-size-h3);
- font-weight: var(--font-weight-normal);
- }
-
- .relay__chain {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- background: var(--bg-table);
- margin-bottom: 0.35rem;
- padding: 0.8rem 1rem;
- border-radius: 0.5rem;
-
- .details {
- display: flex;
- flex-direction: column;
- gap: 0.8rem;
-
- .session__summary {
- display: flex;
- align-items: center;
-
- h4 {
- font-weight: 400;
- font-size: medium;
- }
- }
-
- .stakingNextAhClient__summary {
- .stakingNextAhClient__hasQueuedInClient {
- display: flex;
- justify-content: space-evenly;
- }
- }
- }
-
- .events__summary {
- h3 {
- font-weight: 500;
- font-size: var(--font-size-h2);
- }
- }
- }
-`;
-
export default React.memo(CommandCenter);
diff --git a/packages/page-staking-next/src/CommandCenter/relay.tsx b/packages/page-staking-next/src/CommandCenter/relay.tsx
new file mode 100644
index 000000000000..66703247c6a7
--- /dev/null
+++ b/packages/page-staking-next/src/CommandCenter/relay.tsx
@@ -0,0 +1,144 @@
+// Copyright 2017-2025 @polkadot/app-staking-next authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+import type { IRcOutput } from './index.js';
+
+import React from 'react';
+import { Link } from 'react-router-dom';
+
+import { CardSummary, Expander, MarkWarning, styled } from '@polkadot/react-components';
+import { Event as EventDisplay } from '@polkadot/react-params';
+import { formatNumber } from '@polkadot/util';
+
+import { useTranslation } from '../translate.js';
+
+function RelaySection ({ rcOutput }: {rcOutput: IRcOutput[]}) {
+ const { t } = useTranslation();
+
+ return (
+
+
+ {t('Relay chain')}
+
+
+ {rcOutput.map((rc) => {
+ return (
+
+
+
+
+ #{formatNumber(rc.finalizedBlock)}
+
+
+ #{formatNumber(rc.session.index)}
+
+ {rc.session.historicalRange &&
+
+ [{rc.session.historicalRange?.[0]}, {rc.session.historicalRange?.[1]}]
+
+ }
+
+
+ {rc.stakingNextAhClient.isBlocked &&
}
+ {rc.stakingNextAhClient.hasQueuedInClient &&
+
+
+
+ {rc.stakingNextAhClient.hasQueuedInClient[0]}
+
+
+ {rc.stakingNextAhClient.hasQueuedInClient[1].length}
+
+
}
+
+
+
+
{t('events')}
+ {rc.events.map((event) => {
+ const eventName = `${event.section}.${event.method}`;
+
+ return (
+
+
+
+
+ );
+ })}
+ {rc.events.length === 0 && }
+
+
+ );
+ })}
+
+
);
+}
+
+const StyledSection = styled.section`
+ margin-block: 1rem;
+ max-height: 40vh;
+ overflow: auto;
+
+ .warning {
+ max-width: fit-content;
+ margin-left: 0;
+ }
+
+ .ui--Labelled-content {
+ font-size: var(--font-size-h3);
+ font-weight: var(--font-weight-normal);
+ }
+
+ .relay__chain {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ background: var(--bg-table);
+ margin-bottom: 0.35rem;
+ padding: 0.8rem 1rem;
+ border-radius: 0.5rem;
+
+ .details {
+ display: flex;
+ flex-direction: column;
+ gap: 0.8rem;
+
+ .session__summary {
+ display: flex;
+ align-items: center;
+
+ h4 {
+ font-weight: 400;
+ font-size: medium;
+ }
+ }
+
+ .stakingNextAhClient__summary {
+ .stakingNextAhClient__hasQueuedInClient {
+ display: flex;
+ justify-content: space-evenly;
+ }
+ }
+ }
+
+ .events__summary {
+ h3 {
+ font-weight: 500;
+ font-size: var(--font-size-h2);
+ }
+ }
+ }
+`;
+
+export default React.memo(RelaySection);
From 11e1691de89de3cd79074f55cda81acb05f29ea0 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Tue, 15 Apr 2025 10:44:31 +0530
Subject: [PATCH 18/38] feat: storage fetch for ah api
---
.../src/CommandCenter/index.tsx | 211 ++++++++++--------
1 file changed, 124 insertions(+), 87 deletions(-)
diff --git a/packages/page-staking-next/src/CommandCenter/index.tsx b/packages/page-staking-next/src/CommandCenter/index.tsx
index 9c96585e8496..31d485c7aead 100644
--- a/packages/page-staking-next/src/CommandCenter/index.tsx
+++ b/packages/page-staking-next/src/CommandCenter/index.tsx
@@ -1,39 +1,34 @@
// Copyright 2017-2025 @polkadot/app-staking-next authors & contributors
// SPDX-License-Identifier: Apache-2.0
-import type { ApiPromise } from '@polkadot/api';
import type { AccountId32, Event } from '@polkadot/types/interfaces';
import type { IEventData } from '@polkadot/types/types';
import React, { useEffect, useMemo, useState } from 'react';
+import { ApiPromise, WsProvider } from '@polkadot/api';
import { useApi } from '@polkadot/react-hooks';
import RelaySection from './relay.js';
const MAX_EVENTS = 10;
-// const getApi = async (url: string) => {
-// const api = await ApiPromise.create({
-// provider: new WsProvider(url)
-// });
-
-// await api.isReadyOrError;
-
-// return api;
-// };
-
-const commandCenterHandler = async (rcApi: ApiPromise, setRcOutout: React.Dispatch>): Promise => {
- // const rcApi = await getApi('ws://127.0.0.1:54169');
- // const ahApi = await getApi('ws://127.0.0.1:54175');
+const getApi = async (url: string) => {
+ const api = await ApiPromise.create({
+ provider: new WsProvider(url)
+ });
- // const manager = UpdateManager.getInstance();
- // manager.hook();
+ await api.isReadyOrError;
- // let ahOutput: string[] = [];
- // const rcEvents: string[] = [];
- // const ahEvents: string[] = [];
+ return api;
+};
+const commandCenterHandler = async (
+ rcApi: ApiPromise,
+ ahApi: ApiPromise,
+ setRcOutout: React.Dispatch>,
+ setAhOutout: React.Dispatch>
+): Promise => {
await rcApi.rpc.chain.subscribeFinalizedHeads(async (header) => {
// --- RC:
// current session index
@@ -106,72 +101,90 @@ const commandCenterHandler = async (rcApi: ApiPromise, setRcOutout: React.Dispat
// rcOutput.concat(ahOutput);
});
- // AH:
- // await ahApi.rpc.chain.subscribeFinalizedHeads(async (header) => {
- // // the current planned era
- // const currentEra = await ahApi.query.staking.currentEra();
- // // the active era
- // const activeEra = await ahApi.query.staking.activeEra();
- // // the starting index of the active era
- // const erasStartSessionIndex =
- // // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // // @ts-ignore
- // await ahApi.query.staking.erasStartSessionIndex(activeEra.unwrap().index);
-
- // // the basic state of the election provider
- // const phase = await ahApi.query.multiBlock.currentPhase();
- // const round = await ahApi.query.multiBlock.round();
- // const snapshotRange = (
- // await ahApi.query.multiBlock.pagedVoterSnapshotHash.entries()
- // )
- // .map(([k]) => k.args[0])
- // .sort();
- // const queuedScore =
- // await ahApi.query.multiBlockVerifier.queuedSolutionScore();
- // const signedSubmissions =
- // await ahApi.query.multiBlockSigned.sortedScores(round);
-
- // // Events that we are interested in from RC:
- // const eventsOfInterest = (await ahApi.query.system.events())
- // // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // // @ts-ignore
- // .map((e) => e.event)
- // .filter((e) => {
- // const election = (e: IEventData) =>
- // e.section === 'multiBlock' ||
- // e.section === 'multiBlockVerifier' ||
- // e.section === 'multiBlockSigned' ||
- // e.section === 'multiBlockUnsigned';
- // const rcClient = (e: IEventData) => e.section === 'stakingNextRcClient';
- // const staking = (e: IEventData) =>
- // e.section === 'staking' &&
- // (e.method === 'EraPaid' ||
- // e.method === 'SessionRotated' ||
- // e.method === 'PagedElectionProceeded');
-
- // return election(e.data) || rcClient(e.data) || staking(e.data);
- // })
- // .map(
- // (e) =>
- // `${e.section.toString()}::${e.method.toString()}(${e.data.toString()})`
- // );
-
- // ahEvents.push(...eventsOfInterest);
-
- // ahOutput = [
- // 'AH:',
- // `finalized block ${header.number.toNumber()}`,
- // // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // // @ts-ignore
- // `AH.staking: currentEra=${currentEra.unwrap().toNumber()}, activeEra=${activeEra.unwrap().toString()}, erasStartSessionIndex(${activeEra.unwrap().index.toNumber()})=${erasStartSessionIndex.unwrap().toNumber()}`,
- // `multiBlock: phase=${phase.toString()}, round=${round.toString()}, snapshotRange=${JSON.stringify(snapshotRange)}, queuedScore=${queuedScore.toString()}, signedSubmissions=${signedSubmissions.toString()}`,
- // `AH.events: ${JSON.stringify(ahEvents)}`,
- // '----'
- // ];
-
- // // manager.update(rcOutput.concat(ahOutput));
- // rcOutput.concat(ahOutput);
- // });
+ await ahApi.rpc.chain.subscribeFinalizedHeads(async (header) => {
+ // the current planned era
+ const currentEra = await ahApi.query.staking.currentEra();
+ // the active era
+ const activeEra = await ahApi.query.staking.activeEra();
+ // the starting index of the active era
+ const erasStartSessionIndex =
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ await ahApi.query.staking.erasStartSessionIndex(activeEra.unwrap().index);
+
+ // the basic state of the election provider
+ const phase = await ahApi.query.multiBlock.currentPhase();
+ const round = await ahApi.query.multiBlock.round();
+ const snapshotRange = (
+ await ahApi.query.multiBlock.pagedVoterSnapshotHash.entries()
+ )
+ .map(([k]) => k.args[0])
+ .sort();
+ const queuedScore =
+ await ahApi.query.multiBlockVerifier.queuedSolutionScore();
+ const signedSubmissions =
+ await ahApi.query.multiBlockSigned.sortedScores(round);
+
+ // Events that we are interested in from RC:
+ const eventsOfInterest = (await (await ahApi.at(header.hash.toHex())).query.system.events())
+ .map((e) => e.event)
+ .filter((e) => {
+ const election = (e: IEventData) =>
+ e.section === 'multiBlock' ||
+ e.section === 'multiBlockVerifier' ||
+ e.section === 'multiBlockSigned' ||
+ e.section === 'multiBlockUnsigned';
+ const rcClient = (e: IEventData) => e.section === 'stakingNextRcClient';
+ const staking = (e: IEventData) =>
+ e.section === 'staking' &&
+ (e.method === 'EraPaid' ||
+ e.method === 'SessionRotated' ||
+ e.method === 'PagedElectionProceeded');
+
+ return election(e.data) || rcClient(e.data) || staking(e.data);
+ });
+
+ setAhOutout((prev) => {
+ return [
+ {
+ events: eventsOfInterest,
+ finalizedBlock: header.number.toNumber(),
+ multiblock: {
+ phase: phase.toString(),
+ queuedScore: ahApi.createType('Option', queuedScore).unwrap().toString(),
+ round: ahApi.createType('u32', round).toNumber(),
+ signedSubmissions: ahApi.createType('Vec<(AccountId32,SpNposElectionsElectionScore)>', signedSubmissions).length,
+ snapshotRange: snapshotRange.map((a) => a.toString())
+ },
+ staking: {
+ activeEra: {
+ index: activeEra.unwrap().index.toNumber(),
+ start: activeEra.unwrap().toString()
+ },
+ currentEra: currentEra.unwrap().toNumber(),
+ erasStartSessionIndex: erasStartSessionIndex.unwrap().toNumber()
+ }
+ },
+ ...prev.slice(0, MAX_EVENTS - 1)
+ ];
+ });
+
+ // ahEvents.push(...eventsOfInterest);
+
+ // ahOutput = [
+ // 'AH:',
+ // `finalized block ${header.number.toNumber()}`,
+ // // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // // @ts-ignore
+ // `AH.staking: currentEra=${currentEra.unwrap().toNumber()}, activeEra=${activeEra.unwrap().toString()}, erasStartSessionIndex(${activeEra.unwrap().index.toNumber()})=${erasStartSessionIndex.unwrap().toNumber()}`,
+ // `multiBlock: phase=${phase.toString()}, round=${round.toString()}, snapshotRange=${JSON.stringify(snapshotRange)}, queuedScore=${queuedScore.toString()}, signedSubmissions=${signedSubmissions.toString()}`,
+ // `AH.events: ${JSON.stringify(ahEvents)}`,
+ // '----'
+ // ];
+
+ // manager.update(rcOutput.concat(ahOutput));
+ // rcOutput.concat(ahOutput);
+ });
};
export interface IRcOutput {
@@ -189,15 +202,39 @@ export interface IRcOutput {
events: Event[]
}
+export interface IAhOutput {
+ finalizedBlock: number,
+ staking: {
+ currentEra: number,
+ activeEra: {index: number, start: string},
+ erasStartSessionIndex: number
+ },
+ multiblock: {
+ phase: string,
+ round: number,
+ snapshotRange: string[]
+ queuedScore: string,
+ signedSubmissions: number
+ },
+ events: Event[]
+}
+
function CommandCenter () {
const { api } = useApi();
const [rcOutput, setRcOutput] = useState([]);
+ const [_ahOutput, setAhOutput] = useState([]);
const rcApi = useMemo(() => api, [api]);
+ const [ahApi, setAhApi] = useState();
+
+ useEffect(() => {
+ getApi('ws://127.0.0.1:57357').then((ahApi) => setAhApi(ahApi)).catch((e) => console.log(e));
+ }, []);
+
useEffect(() => {
- commandCenterHandler(rcApi, setRcOutput).catch((e) => console.log(e));
- }, [rcApi]);
+ ahApi && commandCenterHandler(rcApi, ahApi, setRcOutput, setAhOutput).catch((e) => console.log(e));
+ }, [ahApi, rcApi]);
return (
<>
From ad9ad082ed42967701a33d7bedfed3ab28828db0 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Tue, 15 Apr 2025 11:55:46 +0530
Subject: [PATCH 19/38] feat: integrate Command Center into StakingApp routing
---
.../src/CommandCenter/index.tsx | 38 +++++++++++++------
.../page-staking-next/src/system/index.tsx | 9 +++++
2 files changed, 35 insertions(+), 12 deletions(-)
diff --git a/packages/page-staking-next/src/CommandCenter/index.tsx b/packages/page-staking-next/src/CommandCenter/index.tsx
index 31d485c7aead..ecc650123022 100644
--- a/packages/page-staking-next/src/CommandCenter/index.tsx
+++ b/packages/page-staking-next/src/CommandCenter/index.tsx
@@ -4,16 +4,18 @@
import type { AccountId32, Event } from '@polkadot/types/interfaces';
import type { IEventData } from '@polkadot/types/types';
-import React, { useEffect, useMemo, useState } from 'react';
+import React, { useEffect, useState } from 'react';
import { ApiPromise, WsProvider } from '@polkadot/api';
+import { prodParasPolkadotCommon, prodRelayPolkadot } from '@polkadot/apps-config';
import { useApi } from '@polkadot/react-hooks';
+import AssetHubSection from './ah.js';
import RelaySection from './relay.js';
const MAX_EVENTS = 10;
-const getApi = async (url: string) => {
+const getApi = async (url: string[]) => {
const api = await ApiPromise.create({
provider: new WsProvider(url)
});
@@ -145,13 +147,15 @@ const commandCenterHandler = async (
});
setAhOutout((prev) => {
+ const parsedQueuedScore = ahApi.createType('Option', queuedScore);
+
return [
{
events: eventsOfInterest,
finalizedBlock: header.number.toNumber(),
multiblock: {
phase: phase.toString(),
- queuedScore: ahApi.createType('Option', queuedScore).unwrap().toString(),
+ queuedScore: parsedQueuedScore.isSome ? parsedQueuedScore.unwrap().toString() : null,
round: ahApi.createType('u32', round).toNumber(),
signedSubmissions: ahApi.createType('Vec<(AccountId32,SpNposElectionsElectionScore)>', signedSubmissions).length,
snapshotRange: snapshotRange.map((a) => a.toString())
@@ -213,32 +217,42 @@ export interface IAhOutput {
phase: string,
round: number,
snapshotRange: string[]
- queuedScore: string,
+ queuedScore: string|null,
signedSubmissions: number
},
events: Event[]
}
function CommandCenter () {
- const { api } = useApi();
+ const { api, apiEndpoint } = useApi();
const [rcOutput, setRcOutput] = useState([]);
- const [_ahOutput, setAhOutput] = useState([]);
-
- const rcApi = useMemo(() => api, [api]);
-
+ const [ahOutput, setAhOutput] = useState([]);
const [ahApi, setAhApi] = useState();
+ const [rcApi, setRcApi] = useState();
useEffect(() => {
- getApi('ws://127.0.0.1:57357').then((ahApi) => setAhApi(ahApi)).catch((e) => console.log(e));
- }, []);
+ // Check if it is relay chain
+ if (api.tx.stakingNextAhClient) {
+ setRcApi(api);
+ const ahEndpoint = Object.values(prodParasPolkadotCommon[0].providers) as string[];
+
+ getApi(ahEndpoint).then((ahApi) => setAhApi(ahApi)).catch((e) => console.log(e));
+ } else if (api.tx.staking && api.tx.stakingNextRcClient) { // Check if Asset Hub chain
+ setAhApi(api);
+ const rcEndpoint = Object.values(prodRelayPolkadot.providers).filter((e) => e.startsWith('ws'));
+
+ getApi(rcEndpoint).then((rcApi) => setRcApi(rcApi)).catch((e) => console.log(e));
+ }
+ }, [api, apiEndpoint?.paraId]);
useEffect(() => {
- ahApi && commandCenterHandler(rcApi, ahApi, setRcOutput, setAhOutput).catch((e) => console.log(e));
+ ahApi && rcApi && commandCenterHandler(rcApi, ahApi, setRcOutput, setAhOutput).catch((e) => console.log(e));
}, [ahApi, rcApi]);
return (
<>
+
>
);
}
diff --git a/packages/page-staking-next/src/system/index.tsx b/packages/page-staking-next/src/system/index.tsx
index 77189cd74496..052e18359a89 100644
--- a/packages/page-staking-next/src/system/index.tsx
+++ b/packages/page-staking-next/src/system/index.tsx
@@ -23,6 +23,7 @@ import { Tabs } from '@polkadot/react-components';
import { useAccounts, useApi, useAvailableSlashes, useCall, useCallMulti, useFavorites, useOwnStashInfos } from '@polkadot/react-hooks';
import { isFunction } from '@polkadot/util';
+import CommandCenter from '../CommandCenter/index.js';
import { STORE_FAVS_BASE } from '../constants.js';
import { useTranslation } from '../translate.js';
@@ -122,6 +123,10 @@ function StakingApp ({ basePath }: Props): React.ReactElement {
hasParams: true,
name: 'query',
text: t('Validator stats')
+ },
+ {
+ name: 'command-center',
+ text: t('Command Center')
}
].filter((q): q is { name: string; text: string } => !!q), [api, hasStashes, slashes, t]);
@@ -194,6 +199,10 @@ function StakingApp ({ basePath }: Props): React.ReactElement {
}
path='targets'
/>
+ }
+ path='command-center'
+ />
Date: Tue, 15 Apr 2025 12:15:29 +0530
Subject: [PATCH 20/38] feat: add AssetHubSection component and update
RelaySection styling
---
.../src/CommandCenter/ah.tsx | 127 ++++++++++++++++++
.../src/CommandCenter/index.tsx | 41 +-----
.../src/CommandCenter/relay.tsx | 2 +-
3 files changed, 129 insertions(+), 41 deletions(-)
create mode 100644 packages/page-staking-next/src/CommandCenter/ah.tsx
diff --git a/packages/page-staking-next/src/CommandCenter/ah.tsx b/packages/page-staking-next/src/CommandCenter/ah.tsx
new file mode 100644
index 000000000000..2253d5020fde
--- /dev/null
+++ b/packages/page-staking-next/src/CommandCenter/ah.tsx
@@ -0,0 +1,127 @@
+// Copyright 2017-2025 @polkadot/app-staking-next authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+import type { IAhOutput } from './index.js';
+
+import React from 'react';
+import { Link } from 'react-router-dom';
+
+import { CardSummary, Expander, MarkWarning, styled } from '@polkadot/react-components';
+import { Event as EventDisplay } from '@polkadot/react-params';
+import { formatNumber } from '@polkadot/util';
+
+import { useTranslation } from '../translate.js';
+
+function AssetHubSection ({ ahOutput }: {ahOutput: IAhOutput[]}) {
+ const { t } = useTranslation();
+
+ return (
+
+
+ {t('Asset Hub chain')}
+
+
+ {ahOutput.map((ah) => {
+ return (
+
+
+
+
+ #{formatNumber(ah.finalizedBlock)}
+
+
+
+
+
+
+
+
+
{t('events')}
+ {ah.events.map((event) => {
+ const eventName = `${event.section}.${event.method}`;
+
+ return (
+
+
+
+
+ );
+ })}
+ {ah.events.length === 0 && }
+
+
+ );
+ })}
+
+
);
+}
+
+const StyledSection = styled.section`
+ margin-block: 1rem;
+ max-height: 40vh;
+ overflow: auto;
+
+ .warning {
+ max-width: fit-content;
+ margin-left: 0;
+ }
+
+ .ui--Labelled-content {
+ font-size: var(--font-size-h3);
+ font-weight: var(--font-weight-normal);
+ }
+
+ .relay__chain {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ background: var(--bg-table);
+ margin-bottom: 0.35rem;
+ padding: 0.8rem 1rem;
+ border-radius: 0.5rem;
+
+ .details {
+ display: flex;
+ flex-direction: column;
+ gap: 0.8rem;
+
+ .session__summary {
+ display: flex;
+ align-items: center;
+
+ h4 {
+ font-weight: 400;
+ font-size: medium;
+ }
+ }
+
+ .stakingNextAhClient__summary {
+ .stakingNextAhClient__hasQueuedInClient {
+ display: flex;
+ justify-content: space-evenly;
+ }
+ }
+ }
+
+ .events__summary {
+ h3 {
+ font-weight: 500;
+ font-size: var(--font-size-h2);
+ }
+ }
+ }
+`;
+
+export default React.memo(AssetHubSection);
diff --git a/packages/page-staking-next/src/CommandCenter/index.tsx b/packages/page-staking-next/src/CommandCenter/index.tsx
index ecc650123022..353fae1d52b8 100644
--- a/packages/page-staking-next/src/CommandCenter/index.tsx
+++ b/packages/page-staking-next/src/CommandCenter/index.tsx
@@ -51,8 +51,6 @@ const commandCenterHandler = async (
// Events that we are interested in from RC:
const eventsOfInterest = (await (await rcApi.at(header.hash.toHex())).query.system.events())
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
.map((e) => e.event)
.filter((e) => {
const ahClientEvents = (e: IEventData) =>
@@ -62,12 +60,6 @@ const commandCenterHandler = async (
return ahClientEvents(e.data) || sessionEvents(e.data);
});
- // .map(
- // (e) =>
- // `${e.section.toString()}::${e.method.toString()}(${e.data.toString()})`
- // );
-
- // rcEvents.push(...eventsOfInterest);
setRcOutout((prev) => {
const parsedHasQueuedInClient = rcApi.createType('Option<(u32,Vec)>', hasQueuedInClient);
@@ -89,18 +81,6 @@ const commandCenterHandler = async (
},
...prev.slice(0, MAX_EVENTS - 1)];
});
-
- // rcOutput = [
- // 'RC:',
- // `finalized block ${header.number.toNumber()}`,
- // `RC.session: index=${index.toNumber()}, hasQueuedInSession=${hasQueuedInSession.toString()}, historicalRange=${historicalRange.toString()}`,
- // `RC.stakingNextAhClient: hasQueuedInClient=${hasQueuedInClient.toString()}, hasNextActiveId=${hasNextActiveId.toString()}, isBlocked=${isBlocked.toString()}`,
- // `RC.events: ${JSON.stringify(rcEvents)}`,
- // '----'
- // ];
-
- // manager.update(rcOutput.concat(ahOutput));
- // rcOutput.concat(ahOutput);
});
await ahApi.rpc.chain.subscribeFinalizedHeads(async (header) => {
@@ -109,10 +89,7 @@ const commandCenterHandler = async (
// the active era
const activeEra = await ahApi.query.staking.activeEra();
// the starting index of the active era
- const erasStartSessionIndex =
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- await ahApi.query.staking.erasStartSessionIndex(activeEra.unwrap().index);
+ const erasStartSessionIndex = await ahApi.query.staking.erasStartSessionIndex(activeEra.unwrap().index);
// the basic state of the election provider
const phase = await ahApi.query.multiBlock.currentPhase();
@@ -172,22 +149,6 @@ const commandCenterHandler = async (
...prev.slice(0, MAX_EVENTS - 1)
];
});
-
- // ahEvents.push(...eventsOfInterest);
-
- // ahOutput = [
- // 'AH:',
- // `finalized block ${header.number.toNumber()}`,
- // // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // // @ts-ignore
- // `AH.staking: currentEra=${currentEra.unwrap().toNumber()}, activeEra=${activeEra.unwrap().toString()}, erasStartSessionIndex(${activeEra.unwrap().index.toNumber()})=${erasStartSessionIndex.unwrap().toNumber()}`,
- // `multiBlock: phase=${phase.toString()}, round=${round.toString()}, snapshotRange=${JSON.stringify(snapshotRange)}, queuedScore=${queuedScore.toString()}, signedSubmissions=${signedSubmissions.toString()}`,
- // `AH.events: ${JSON.stringify(ahEvents)}`,
- // '----'
- // ];
-
- // manager.update(rcOutput.concat(ahOutput));
- // rcOutput.concat(ahOutput);
});
};
diff --git a/packages/page-staking-next/src/CommandCenter/relay.tsx b/packages/page-staking-next/src/CommandCenter/relay.tsx
index 66703247c6a7..e41b046be510 100644
--- a/packages/page-staking-next/src/CommandCenter/relay.tsx
+++ b/packages/page-staking-next/src/CommandCenter/relay.tsx
@@ -17,7 +17,7 @@ function RelaySection ({ rcOutput }: {rcOutput: IRcOutput[]}) {
return (
-
+
{t('Relay chain')}
From 103ca72a55f3a8953a72443e8c345c0042e06583 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Tue, 15 Apr 2025 12:18:06 +0530
Subject: [PATCH 21/38] chore: added translation
---
packages/apps/public/locales/en/app-staking-next.json | 1 +
packages/apps/public/locales/en/translation.json | 1 +
2 files changed, 2 insertions(+)
diff --git a/packages/apps/public/locales/en/app-staking-next.json b/packages/apps/public/locales/en/app-staking-next.json
index 23a69fbed65b..eb92f1dd0129 100644
--- a/packages/apps/public/locales/en/app-staking-next.json
+++ b/packages/apps/public/locales/en/app-staking-next.json
@@ -1,5 +1,6 @@
{
"Accounts": "Accounts",
+ "Asset Hub chain": "Asset Hub chain",
"Asset Hub client pallet(AhClient) is blocked currently, useful for migration signal from the fellowship.": "Asset Hub client pallet(AhClient) is blocked currently, useful for migration signal from the fellowship.",
"Bags": "Bags",
"Command Center": "Command Center",
diff --git a/packages/apps/public/locales/en/translation.json b/packages/apps/public/locales/en/translation.json
index 13f47284421c..14ee04037fd0 100644
--- a/packages/apps/public/locales/en/translation.json
+++ b/packages/apps/public/locales/en/translation.json
@@ -102,6 +102,7 @@
"Approving of all or none of the options is equivalent and will not affect the outcome of the poll.": "",
"As a council member, you can suggest an initial value for the tip, each other council member can suggest their own.": "",
"As such it is recommended that you setup a proxy to control operations via the stash.": "",
+ "Asset Hub chain": "",
"Asset Hub client pallet(AhClient) is blocked currently, useful for migration signal from the fellowship.": "",
"At block": "",
"Auctions": "",
From 477159d52bdd79234639938b29e1de58d4a5151a Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Tue, 15 Apr 2025 12:34:15 +0530
Subject: [PATCH 22/38] feat: implement API check for staking-next route
---
packages/apps-routing/src/staking-next.ts | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/packages/apps-routing/src/staking-next.ts b/packages/apps-routing/src/staking-next.ts
index 7945baee3461..44842e42b650 100644
--- a/packages/apps-routing/src/staking-next.ts
+++ b/packages/apps-routing/src/staking-next.ts
@@ -1,16 +1,25 @@
// Copyright 2017-2025 @polkadot/apps-routing authors & contributors
// SPDX-License-Identifier: Apache-2.0
+import type { ApiPromise } from '@polkadot/api';
import type { Route, TFunction } from './types.js';
import Component from '@polkadot/app-staking-next';
+function needsApiCheck (api: ApiPromise): boolean {
+ try {
+ return !!((api.tx.stakingNextAhClient) || (api.tx.staking && api.tx.stakingNextRcClient));
+ } catch {
+ return false;
+ }
+}
+
export default function create (t: TFunction): Route {
return {
Component,
display: {
- // TODO: Add check when to show this page
- needsApi: []
+ needsApi: [],
+ needsApiCheck
},
group: 'network',
icon: 'certificate',
From 5ad5cfa96560a07e37256c2c696ace190f537df2 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Tue, 15 Apr 2025 13:55:06 +0530
Subject: [PATCH 23/38] feat: enhance AssetHubSection with detailed multiblock
and staking summaries
---
.../src/CommandCenter/ah.tsx | 47 +++++++++++---
.../src/CommandCenter/index.tsx | 64 +++++++++----------
2 files changed, 70 insertions(+), 41 deletions(-)
diff --git a/packages/page-staking-next/src/CommandCenter/ah.tsx b/packages/page-staking-next/src/CommandCenter/ah.tsx
index 2253d5020fde..f29bcd090737 100644
--- a/packages/page-staking-next/src/CommandCenter/ah.tsx
+++ b/packages/page-staking-next/src/CommandCenter/ah.tsx
@@ -32,10 +32,35 @@ function AssetHubSection ({ ahOutput }: {ahOutput: IAhOutput[]}) {
#{formatNumber(ah.finalizedBlock)}
-
-
-
+
+
+
+ {ah.staking.currentEra}
+
+
+ {ah.staking.erasStartSessionIndex}
+
+
+ {ah.staking.activeEra.index}
+
+
+
+
+ {ah.multiblock.phase}
+
+ {ah.multiblock.queuedScore &&
+
+ {ah.multiblock.queuedScore}
+ }
+
+ {ah.multiblock.signedSubmissions}
+
+ {!!ah.multiblock.snapshotRange.length &&
+
+ {ah.multiblock.snapshotRange}
+ }
+
@@ -94,7 +119,7 @@ const StyledSection = styled.section`
.details {
display: flex;
- flex-direction: column;
+ align-items: center;
gap: 0.8rem;
.session__summary {
@@ -107,11 +132,15 @@ const StyledSection = styled.section`
}
}
- .stakingNextAhClient__summary {
- .stakingNextAhClient__hasQueuedInClient {
- display: flex;
- justify-content: space-evenly;
- }
+ .staking__summary {
+ display: flex;
+ justify-content: end;
+ }
+
+ .multiblock__summary {
+ display: flex;
+ justify-content: end;
+ margin-top: 1.5rem;
}
}
diff --git a/packages/page-staking-next/src/CommandCenter/index.tsx b/packages/page-staking-next/src/CommandCenter/index.tsx
index 353fae1d52b8..f73f557b5a45 100644
--- a/packages/page-staking-next/src/CommandCenter/index.tsx
+++ b/packages/page-staking-next/src/CommandCenter/index.tsx
@@ -25,6 +25,38 @@ const getApi = async (url: string[]) => {
return api;
};
+export interface IRcOutput {
+ finalizedBlock: number,
+ session: {
+ index: number,
+ hasQueuedInSession: boolean,
+ historicalRange?: [number, number]
+ },
+ stakingNextAhClient: {
+ isBlocked: boolean
+ hasNextActiveId?: number,
+ hasQueuedInClient?: [number, AccountId32[]]
+ },
+ events: Event[]
+}
+
+export interface IAhOutput {
+ finalizedBlock: number,
+ staking: {
+ currentEra: number,
+ activeEra: {index: number, start: string},
+ erasStartSessionIndex: number
+ },
+ multiblock: {
+ phase: string,
+ round: number,
+ snapshotRange: string[]
+ queuedScore: string|null,
+ signedSubmissions: number
+ },
+ events: Event[]
+}
+
const commandCenterHandler = async (
rcApi: ApiPromise,
ahApi: ApiPromise,
@@ -152,38 +184,6 @@ const commandCenterHandler = async (
});
};
-export interface IRcOutput {
- finalizedBlock: number,
- session: {
- index: number,
- hasQueuedInSession: boolean,
- historicalRange?: [number, number]
- },
- stakingNextAhClient: {
- isBlocked: boolean
- hasNextActiveId?: number,
- hasQueuedInClient?: [number, AccountId32[]]
- },
- events: Event[]
-}
-
-export interface IAhOutput {
- finalizedBlock: number,
- staking: {
- currentEra: number,
- activeEra: {index: number, start: string},
- erasStartSessionIndex: number
- },
- multiblock: {
- phase: string,
- round: number,
- snapshotRange: string[]
- queuedScore: string|null,
- signedSubmissions: number
- },
- events: Event[]
-}
-
function CommandCenter () {
const { api, apiEndpoint } = useApi();
const [rcOutput, setRcOutput] = useState
([]);
From 822f7cdd2650fbb9f162aacfd665c77ea46cc573 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Wed, 16 Apr 2025 10:11:26 +0530
Subject: [PATCH 24/38] chore: added translations
---
packages/apps/public/locales/en/app-staking-next.json | 9 ++++++++-
packages/apps/public/locales/en/translation.json | 7 +++++++
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/packages/apps/public/locales/en/app-staking-next.json b/packages/apps/public/locales/en/app-staking-next.json
index eb92f1dd0129..fb8bde998aac 100644
--- a/packages/apps/public/locales/en/app-staking-next.json
+++ b/packages/apps/public/locales/en/app-staking-next.json
@@ -13,9 +13,16 @@
"Targets": "Targets",
"There is a validator set queued in ah-client.": "There is a validator set queued in ah-client.",
"Validator stats": "Validator stats",
+ "active era": "active era",
+ "current era": "current era",
+ "era session index": "era session index",
"events": "events",
"historical range": "historical range",
"id": "id",
+ "multiblock phase": "multiblock phase",
+ "multiblock queued score": "multiblock queued score",
"number of validators": "number of validators",
- "session": "session"
+ "session": "session",
+ "signed submissions": "signed submissions",
+ "snapshot range": "snapshot range"
}
\ No newline at end of file
diff --git a/packages/apps/public/locales/en/translation.json b/packages/apps/public/locales/en/translation.json
index 14ee04037fd0..9067849ee9e4 100644
--- a/packages/apps/public/locales/en/translation.json
+++ b/packages/apps/public/locales/en/translation.json
@@ -1166,6 +1166,7 @@
"activate": "",
"active": "",
"active / nominators": "",
+ "active era": "",
"active issuance": "",
"active raised / cap": "",
"active total": "",
@@ -1333,6 +1334,7 @@
"current approval (failing)": "",
"current approval (passing)": "",
"current curator": "",
+ "current era": "",
"current lease": "",
"current price": "",
"current range winning bid": "",
@@ -1413,6 +1415,7 @@
"epoch": "",
"era": "",
"era points": "",
+ "era session index": "",
"era {{era}}": "",
"era {{era}}, {{count}} slashes": "",
"era {{era}}/unapplied": "",
@@ -1578,6 +1581,8 @@
"mnemonic seed": "",
"mortal, valid from #{{startAt}} to #{{endsAt}}": "",
"motions": "",
+ "multiblock phase": "",
+ "multiblock queued score": "",
"multisig": "",
"multisig call data": "",
"multisig name": "",
@@ -1775,7 +1780,9 @@
"signature crypto type": "",
"signature of supplied data": "",
"signature {{type}}": "",
+ "signed submissions": "",
"skeptic": "",
+ "snapshot range": "",
"society head": "",
"sold/offered": "",
"somebody@example.com": "",
From 4a8ca8c5cea0c7bbe6fc7d6a6fd7f885572f4ede Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Wed, 16 Apr 2025 12:07:20 +0530
Subject: [PATCH 25/38] chore: delete folder
---
.../page-staking-next/src/relay/index.tsx | 40 ---
.../page-staking-next/src/system/index.tsx | 230 ------------------
2 files changed, 270 deletions(-)
delete mode 100644 packages/page-staking-next/src/relay/index.tsx
delete mode 100644 packages/page-staking-next/src/system/index.tsx
diff --git a/packages/page-staking-next/src/relay/index.tsx b/packages/page-staking-next/src/relay/index.tsx
deleted file mode 100644
index a9ba48e8de0a..000000000000
--- a/packages/page-staking-next/src/relay/index.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2017-2025 @polkadot/app-staking-next authors & contributors
-// SPDX-License-Identifier: Apache-2.0
-
-import type { AppProps as Props } from '@polkadot/react-components/types';
-
-import React, { useMemo } from 'react';
-import { Route, Routes } from 'react-router-dom';
-
-import { Tabs } from '@polkadot/react-components';
-
-import CommandCenter from '../CommandCenter/index.js';
-import { useTranslation } from '../translate.js';
-
-function StakingApp ({ basePath }: Props): React.ReactElement {
- const { t } = useTranslation();
-
- const items = useMemo(() => [
- {
- isRoot: true,
- name: 'command-center',
- text: t('Command Center')
- }], [t]);
-
- return <>
-
-
-
- }
- index
- />
-
-
- >;
-}
-
-export default React.memo(StakingApp);
diff --git a/packages/page-staking-next/src/system/index.tsx b/packages/page-staking-next/src/system/index.tsx
deleted file mode 100644
index 052e18359a89..000000000000
--- a/packages/page-staking-next/src/system/index.tsx
+++ /dev/null
@@ -1,230 +0,0 @@
-// Copyright 2017-2025 @polkadot/app-staking-next authors & contributors
-// SPDX-License-Identifier: Apache-2.0
-
-import type { DeriveStakingOverview } from '@polkadot/api-derive/types';
-import type { AppProps as Props } from '@polkadot/react-components/types';
-import type { ElectionStatus, ParaValidatorIndex, ValidatorId } from '@polkadot/types/interfaces';
-import type { BN } from '@polkadot/util';
-
-import React, { useCallback, useMemo, useState } from 'react';
-import { Route, Routes } from 'react-router';
-
-import Actions from '@polkadot/app-staking/Actions';
-import Bags from '@polkadot/app-staking/Bags';
-import Payouts from '@polkadot/app-staking/Payouts';
-import Query from '@polkadot/app-staking/Query';
-import Targets from '@polkadot/app-staking/Targets';
-import useNominations from '@polkadot/app-staking/useNominations';
-import useSortedTargets from '@polkadot/app-staking/useSortedTargets';
-import Validators from '@polkadot/app-staking/Validators';
-import Pools from '@polkadot/app-staking2/Pools';
-import useOwnPools from '@polkadot/app-staking2/Pools/useOwnPools';
-import { Tabs } from '@polkadot/react-components';
-import { useAccounts, useApi, useAvailableSlashes, useCall, useCallMulti, useFavorites, useOwnStashInfos } from '@polkadot/react-hooks';
-import { isFunction } from '@polkadot/util';
-
-import CommandCenter from '../CommandCenter/index.js';
-import { STORE_FAVS_BASE } from '../constants.js';
-import { useTranslation } from '../translate.js';
-
-const HIDDEN_ACC = ['actions', 'payout'];
-
-const OPT_MULTI = {
- defaultValue: [false, undefined, {}] as [boolean, BN | undefined, Record],
- transform: ([eraElectionStatus, minValidatorBond, validators, activeValidatorIndices]: [ElectionStatus | null, BN | undefined, ValidatorId[] | null, ParaValidatorIndex[] | null]): [boolean, BN | undefined, Record] => [
- !!eraElectionStatus && eraElectionStatus.isOpen,
- minValidatorBond && !minValidatorBond.isZero()
- ? minValidatorBond
- : undefined,
- validators && activeValidatorIndices
- ? activeValidatorIndices.reduce((all, index) => ({ ...all, [validators[index.toNumber()].toString()]: true }), {})
- : {}
- ]
-};
-
-function StakingApp ({ basePath }: Props): React.ReactElement {
- const { t } = useTranslation();
- const { api } = useApi();
- const [withLedger, setWithLedger] = useState(false);
- const [favorites, toggleFavorite] = useFavorites(STORE_FAVS_BASE);
- const [loadNominations, setLoadNominations] = useState(false);
- const { areAccountsLoaded, hasAccounts } = useAccounts();
- const ownStashes = useOwnStashInfos();
- const slashes = useAvailableSlashes();
- const targets = useSortedTargets(favorites, withLedger);
- const [isInElection, minCommission, paraValidators] = useCallMulti<[boolean, BN | undefined, Record]>([
- api.query.staking.eraElectionStatus,
- api.query.staking.minCommission,
- api.query.session.validators,
- (api.query.parasShared || api.query.shared)?.activeValidatorIndices
- ], OPT_MULTI);
- const nominatedBy = useNominations(loadNominations);
- const ownPools = useOwnPools();
- const stakingOverview = useCall(api.derive.staking.overview);
-
- const toggleNominatedBy = useCallback(
- () => setLoadNominations(true),
- []
- );
-
- const toggleLedger = useCallback(
- () => setWithLedger(true),
- []
- );
-
- const hasQueries = useMemo(
- () => hasAccounts && !!(api.query.imOnline?.authoredBlocks) && !!(api.query.staking.activeEra),
- [api, hasAccounts]
- );
-
- const hasStashes = useMemo(
- () => hasAccounts && !!ownStashes && (ownStashes.length !== 0),
- [hasAccounts, ownStashes]
- );
-
- const ownValidators = useMemo(
- () => (ownStashes || []).filter(({ isStashValidating }) => isStashValidating),
- [ownStashes]
- );
-
- const items = useMemo(() => [
- {
- isRoot: true,
- name: 'overview',
- text: t('Overview')
- },
- {
- name: 'actions',
- text: t('Accounts')
- },
- hasStashes && isFunction(api.query.staking.activeEra) && {
- name: 'payout',
- text: t('Payouts')
- },
- isFunction(api.query.nominationPools?.minCreateBond) && {
- name: 'pools',
- text: t('Pools')
- },
- {
- alias: 'returns',
- name: 'targets',
- text: t('Targets')
- },
- hasStashes && isFunction((api.query.voterBagsList || api.query.bagsList || api.query.voterList)?.counterForListNodes) && {
- name: 'bags',
- text: t('Bags')
- },
- {
- count: slashes.reduce((count, [, unapplied]) => count + unapplied.length, 0),
- name: 'slashes',
- text: t('Slashes')
- },
- {
- hasParams: true,
- name: 'query',
- text: t('Validator stats')
- },
- {
- name: 'command-center',
- text: t('Command Center')
- }
- ].filter((q): q is { name: string; text: string } => !!q), [api, hasStashes, slashes, t]);
-
- return (
- <>
-
-
-
-
- }
- path='bags'
- />
-
- }
- path='payout'
- />
-
- }
- path='pools'
- />
-
- }
- path='query/:value?'
- />
-
- }
- path='actions'
- />
-
- }
- path='targets'
- />
- }
- path='command-center'
- />
-
- }
- index
- />
-
-
- >
- );
-}
-
-export default React.memo(StakingApp);
From d4e01f4a231502881cdb9296739ab0ea1968c701 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Wed, 16 Apr 2025 12:11:30 +0530
Subject: [PATCH 26/38] chore: added folders
---
.../page-staking-next/src/Relay/index.tsx | 40 +++
.../page-staking-next/src/System/index.tsx | 230 ++++++++++++++++++
2 files changed, 270 insertions(+)
create mode 100644 packages/page-staking-next/src/Relay/index.tsx
create mode 100644 packages/page-staking-next/src/System/index.tsx
diff --git a/packages/page-staking-next/src/Relay/index.tsx b/packages/page-staking-next/src/Relay/index.tsx
new file mode 100644
index 000000000000..a9ba48e8de0a
--- /dev/null
+++ b/packages/page-staking-next/src/Relay/index.tsx
@@ -0,0 +1,40 @@
+// Copyright 2017-2025 @polkadot/app-staking-next authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+import type { AppProps as Props } from '@polkadot/react-components/types';
+
+import React, { useMemo } from 'react';
+import { Route, Routes } from 'react-router-dom';
+
+import { Tabs } from '@polkadot/react-components';
+
+import CommandCenter from '../CommandCenter/index.js';
+import { useTranslation } from '../translate.js';
+
+function StakingApp ({ basePath }: Props): React.ReactElement {
+ const { t } = useTranslation();
+
+ const items = useMemo(() => [
+ {
+ isRoot: true,
+ name: 'command-center',
+ text: t('Command Center')
+ }], [t]);
+
+ return <>
+
+
+
+ }
+ index
+ />
+
+
+ >;
+}
+
+export default React.memo(StakingApp);
diff --git a/packages/page-staking-next/src/System/index.tsx b/packages/page-staking-next/src/System/index.tsx
new file mode 100644
index 000000000000..052e18359a89
--- /dev/null
+++ b/packages/page-staking-next/src/System/index.tsx
@@ -0,0 +1,230 @@
+// Copyright 2017-2025 @polkadot/app-staking-next authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+import type { DeriveStakingOverview } from '@polkadot/api-derive/types';
+import type { AppProps as Props } from '@polkadot/react-components/types';
+import type { ElectionStatus, ParaValidatorIndex, ValidatorId } from '@polkadot/types/interfaces';
+import type { BN } from '@polkadot/util';
+
+import React, { useCallback, useMemo, useState } from 'react';
+import { Route, Routes } from 'react-router';
+
+import Actions from '@polkadot/app-staking/Actions';
+import Bags from '@polkadot/app-staking/Bags';
+import Payouts from '@polkadot/app-staking/Payouts';
+import Query from '@polkadot/app-staking/Query';
+import Targets from '@polkadot/app-staking/Targets';
+import useNominations from '@polkadot/app-staking/useNominations';
+import useSortedTargets from '@polkadot/app-staking/useSortedTargets';
+import Validators from '@polkadot/app-staking/Validators';
+import Pools from '@polkadot/app-staking2/Pools';
+import useOwnPools from '@polkadot/app-staking2/Pools/useOwnPools';
+import { Tabs } from '@polkadot/react-components';
+import { useAccounts, useApi, useAvailableSlashes, useCall, useCallMulti, useFavorites, useOwnStashInfos } from '@polkadot/react-hooks';
+import { isFunction } from '@polkadot/util';
+
+import CommandCenter from '../CommandCenter/index.js';
+import { STORE_FAVS_BASE } from '../constants.js';
+import { useTranslation } from '../translate.js';
+
+const HIDDEN_ACC = ['actions', 'payout'];
+
+const OPT_MULTI = {
+ defaultValue: [false, undefined, {}] as [boolean, BN | undefined, Record],
+ transform: ([eraElectionStatus, minValidatorBond, validators, activeValidatorIndices]: [ElectionStatus | null, BN | undefined, ValidatorId[] | null, ParaValidatorIndex[] | null]): [boolean, BN | undefined, Record] => [
+ !!eraElectionStatus && eraElectionStatus.isOpen,
+ minValidatorBond && !minValidatorBond.isZero()
+ ? minValidatorBond
+ : undefined,
+ validators && activeValidatorIndices
+ ? activeValidatorIndices.reduce((all, index) => ({ ...all, [validators[index.toNumber()].toString()]: true }), {})
+ : {}
+ ]
+};
+
+function StakingApp ({ basePath }: Props): React.ReactElement {
+ const { t } = useTranslation();
+ const { api } = useApi();
+ const [withLedger, setWithLedger] = useState(false);
+ const [favorites, toggleFavorite] = useFavorites(STORE_FAVS_BASE);
+ const [loadNominations, setLoadNominations] = useState(false);
+ const { areAccountsLoaded, hasAccounts } = useAccounts();
+ const ownStashes = useOwnStashInfos();
+ const slashes = useAvailableSlashes();
+ const targets = useSortedTargets(favorites, withLedger);
+ const [isInElection, minCommission, paraValidators] = useCallMulti<[boolean, BN | undefined, Record]>([
+ api.query.staking.eraElectionStatus,
+ api.query.staking.minCommission,
+ api.query.session.validators,
+ (api.query.parasShared || api.query.shared)?.activeValidatorIndices
+ ], OPT_MULTI);
+ const nominatedBy = useNominations(loadNominations);
+ const ownPools = useOwnPools();
+ const stakingOverview = useCall(api.derive.staking.overview);
+
+ const toggleNominatedBy = useCallback(
+ () => setLoadNominations(true),
+ []
+ );
+
+ const toggleLedger = useCallback(
+ () => setWithLedger(true),
+ []
+ );
+
+ const hasQueries = useMemo(
+ () => hasAccounts && !!(api.query.imOnline?.authoredBlocks) && !!(api.query.staking.activeEra),
+ [api, hasAccounts]
+ );
+
+ const hasStashes = useMemo(
+ () => hasAccounts && !!ownStashes && (ownStashes.length !== 0),
+ [hasAccounts, ownStashes]
+ );
+
+ const ownValidators = useMemo(
+ () => (ownStashes || []).filter(({ isStashValidating }) => isStashValidating),
+ [ownStashes]
+ );
+
+ const items = useMemo(() => [
+ {
+ isRoot: true,
+ name: 'overview',
+ text: t('Overview')
+ },
+ {
+ name: 'actions',
+ text: t('Accounts')
+ },
+ hasStashes && isFunction(api.query.staking.activeEra) && {
+ name: 'payout',
+ text: t('Payouts')
+ },
+ isFunction(api.query.nominationPools?.minCreateBond) && {
+ name: 'pools',
+ text: t('Pools')
+ },
+ {
+ alias: 'returns',
+ name: 'targets',
+ text: t('Targets')
+ },
+ hasStashes && isFunction((api.query.voterBagsList || api.query.bagsList || api.query.voterList)?.counterForListNodes) && {
+ name: 'bags',
+ text: t('Bags')
+ },
+ {
+ count: slashes.reduce((count, [, unapplied]) => count + unapplied.length, 0),
+ name: 'slashes',
+ text: t('Slashes')
+ },
+ {
+ hasParams: true,
+ name: 'query',
+ text: t('Validator stats')
+ },
+ {
+ name: 'command-center',
+ text: t('Command Center')
+ }
+ ].filter((q): q is { name: string; text: string } => !!q), [api, hasStashes, slashes, t]);
+
+ return (
+ <>
+
+
+
+
+ }
+ path='bags'
+ />
+
+ }
+ path='payout'
+ />
+
+ }
+ path='pools'
+ />
+
+ }
+ path='query/:value?'
+ />
+
+ }
+ path='actions'
+ />
+
+ }
+ path='targets'
+ />
+ }
+ path='command-center'
+ />
+
+ }
+ index
+ />
+
+
+ >
+ );
+}
+
+export default React.memo(StakingApp);
From 18146d775b08b63f0acd57c55b1d66bcbd2ec30c Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Wed, 7 May 2025 13:13:47 +0530
Subject: [PATCH 27/38] feat: update layout and styling in CommandCenter
components
---
.../page-staking-next/src/CommandCenter/ah.tsx | 4 ++--
.../src/CommandCenter/index.tsx | 17 ++++++++++++++---
.../src/CommandCenter/relay.tsx | 1 -
3 files changed, 16 insertions(+), 6 deletions(-)
diff --git a/packages/page-staking-next/src/CommandCenter/ah.tsx b/packages/page-staking-next/src/CommandCenter/ah.tsx
index f29bcd090737..a2224015b59a 100644
--- a/packages/page-staking-next/src/CommandCenter/ah.tsx
+++ b/packages/page-staking-next/src/CommandCenter/ah.tsx
@@ -16,7 +16,7 @@ function AssetHubSection ({ ahOutput }: {ahOutput: IAhOutput[]}) {
const { t } = useTranslation();
return (
-
+
{t('Asset Hub chain')}
@@ -96,7 +96,6 @@ function AssetHubSection ({ ahOutput }: {ahOutput: IAhOutput[]}) {
const StyledSection = styled.section`
margin-block: 1rem;
- max-height: 40vh;
overflow: auto;
.warning {
@@ -145,6 +144,7 @@ const StyledSection = styled.section`
}
.events__summary {
+ justify-self: center;
h3 {
font-weight: 500;
font-size: var(--font-size-h2);
diff --git a/packages/page-staking-next/src/CommandCenter/index.tsx b/packages/page-staking-next/src/CommandCenter/index.tsx
index f73f557b5a45..a372fe4a7348 100644
--- a/packages/page-staking-next/src/CommandCenter/index.tsx
+++ b/packages/page-staking-next/src/CommandCenter/index.tsx
@@ -8,12 +8,13 @@ import React, { useEffect, useState } from 'react';
import { ApiPromise, WsProvider } from '@polkadot/api';
import { prodParasPolkadotCommon, prodRelayPolkadot } from '@polkadot/apps-config';
+import { styled } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import AssetHubSection from './ah.js';
import RelaySection from './relay.js';
-const MAX_EVENTS = 10;
+const MAX_EVENTS = 25;
const getApi = async (url: string[]) => {
const api = await ApiPromise.create({
@@ -211,11 +212,21 @@ function CommandCenter () {
}, [ahApi, rcApi]);
return (
- <>
+
- >
+
);
}
+const StyledDiv = styled.div`
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 1rem;
+
+ @media screen and (max-width: 1200px){
+ grid-template-columns: repeat(1, 1fr);
+ }
+`;
+
export default React.memo(CommandCenter);
diff --git a/packages/page-staking-next/src/CommandCenter/relay.tsx b/packages/page-staking-next/src/CommandCenter/relay.tsx
index e41b046be510..8c9a35af34a7 100644
--- a/packages/page-staking-next/src/CommandCenter/relay.tsx
+++ b/packages/page-staking-next/src/CommandCenter/relay.tsx
@@ -88,7 +88,6 @@ function RelaySection ({ rcOutput }: {rcOutput: IRcOutput[]}) {
const StyledSection = styled.section`
margin-block: 1rem;
- max-height: 40vh;
overflow: auto;
.warning {
From 49abc2d142b4054524fc28d468394b613ab4b1d5 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Wed, 7 May 2025 13:27:45 +0530
Subject: [PATCH 28/38] feat: improve layout of relay chain and events summary
in AssetHubSection
---
packages/page-staking-next/src/CommandCenter/ah.tsx | 3 +++
1 file changed, 3 insertions(+)
diff --git a/packages/page-staking-next/src/CommandCenter/ah.tsx b/packages/page-staking-next/src/CommandCenter/ah.tsx
index a2224015b59a..e8ed7bb571f9 100644
--- a/packages/page-staking-next/src/CommandCenter/ah.tsx
+++ b/packages/page-staking-next/src/CommandCenter/ah.tsx
@@ -111,6 +111,7 @@ const StyledSection = styled.section`
.relay__chain {
display: grid;
grid-template-columns: repeat(2, 1fr);
+ place-items: start;
background: var(--bg-table);
margin-bottom: 0.35rem;
padding: 0.8rem 1rem;
@@ -144,6 +145,8 @@ const StyledSection = styled.section`
}
.events__summary {
+ display: grid;
+ gap: 1rem;
justify-self: center;
h3 {
font-weight: 500;
From 6eb4020841adab0893be68967f2b9c32bda91aba Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Mon, 12 May 2025 13:42:46 +0530
Subject: [PATCH 29/38] feat: add Slashes component to StakingApp and update
TypeScript paths
---
packages/page-staking-next/src/System/index.tsx | 10 ++++++++++
packages/page-staking-next/src/index.tsx | 7 ++-----
tsconfig.base.json | 1 +
3 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/packages/page-staking-next/src/System/index.tsx b/packages/page-staking-next/src/System/index.tsx
index 052e18359a89..53afecf61cbd 100644
--- a/packages/page-staking-next/src/System/index.tsx
+++ b/packages/page-staking-next/src/System/index.tsx
@@ -13,6 +13,7 @@ import Actions from '@polkadot/app-staking/Actions';
import Bags from '@polkadot/app-staking/Bags';
import Payouts from '@polkadot/app-staking/Payouts';
import Query from '@polkadot/app-staking/Query';
+import Slashes from '@polkadot/app-staking/Slashes';
import Targets from '@polkadot/app-staking/Targets';
import useNominations from '@polkadot/app-staking/useNominations';
import useSortedTargets from '@polkadot/app-staking/useSortedTargets';
@@ -199,6 +200,15 @@ function StakingApp ({ basePath }: Props): React.ReactElement {
}
path='targets'
/>
+
+ }
+ path='slashes'
+ />
}
path='command-center'
diff --git a/packages/page-staking-next/src/index.tsx b/packages/page-staking-next/src/index.tsx
index 27b4eeda9e60..823679207ece 100644
--- a/packages/page-staking-next/src/index.tsx
+++ b/packages/page-staking-next/src/index.tsx
@@ -12,14 +12,11 @@ import StakingRelayApp from './Relay/index.js';
import StakingSystemApp from './System/index.js';
function StakingApp ({ basePath, className = '', onStatusChange }: Props): React.ReactElement {
- const { api, apiEndpoint } = useApi();
-
- // TODO: Must be removed in production
- const isRelayGenesis = (api.genesisHash.toHex()) === '0x0e268177d92e92c5fa1a2374e4224da33bc9600c0318a9c25389a35f91238986';
+ const { apiEndpoint } = useApi();
return (
- {apiEndpoint?.isRelay || isRelayGenesis
+ {apiEndpoint?.isRelay
? (
Date: Mon, 19 May 2025 10:17:08 +0530
Subject: [PATCH 30/38] feat: enhance CommandCenter with relay and para
endpoints integration
---
.../src/CommandCenter/index.tsx | 52 ++++++++++++++-----
1 file changed, 40 insertions(+), 12 deletions(-)
diff --git a/packages/page-staking-next/src/CommandCenter/index.tsx b/packages/page-staking-next/src/CommandCenter/index.tsx
index a372fe4a7348..88168179bf47 100644
--- a/packages/page-staking-next/src/CommandCenter/index.tsx
+++ b/packages/page-staking-next/src/CommandCenter/index.tsx
@@ -4,19 +4,19 @@
import type { AccountId32, Event } from '@polkadot/types/interfaces';
import type { IEventData } from '@polkadot/types/types';
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
import { ApiPromise, WsProvider } from '@polkadot/api';
-import { prodParasPolkadotCommon, prodRelayPolkadot } from '@polkadot/apps-config';
import { styled } from '@polkadot/react-components';
-import { useApi } from '@polkadot/react-hooks';
+import { useApi, useParaEndpoints } from '@polkadot/react-hooks';
+import { useRelayEndpoints } from '@polkadot/react-hooks/useParaEndpoints';
import AssetHubSection from './ah.js';
import RelaySection from './relay.js';
const MAX_EVENTS = 25;
-const getApi = async (url: string[]) => {
+const getApi = async (url: string[]|string) => {
const api = await ApiPromise.create({
provider: new WsProvider(url)
});
@@ -186,26 +186,54 @@ const commandCenterHandler = async (
};
function CommandCenter () {
- const { api, apiEndpoint } = useApi();
+ const { api, apiUrl } = useApi();
+ const rcEndPoints = useRelayEndpoints();
+ const ahEndPoints = useParaEndpoints(1000);
+
const [rcOutput, setRcOutput] = useState([]);
const [ahOutput, setAhOutput] = useState([]);
+
+ const [rcUrl, setRcUrl] = useState(undefined);
+ const [ahUrl, setAhUrl] = useState(undefined);
+
const [ahApi, setAhApi] = useState();
const [rcApi, setRcApi] = useState();
+ // Check if it is relay chain
+ const isRelayChain = useMemo(() => api.tx.stakingNextAhClient, [api.tx.stakingNextAhClient]);
+
+ useEffect(() => {
+ if (isRelayChain) {
+ setRcUrl(apiUrl);
+ const ahUrl = ahEndPoints.at(0)?.providers?.at(0);
+
+ setAhUrl(ahUrl);
+ } else {
+ setAhUrl(apiUrl);
+ const rcUrl = rcEndPoints.at(0)?.valueRelay?.at(0);
+
+ setRcUrl(rcUrl);
+ }
+ }, [ahEndPoints, apiUrl, isRelayChain, rcEndPoints]);
+
useEffect(() => {
- // Check if it is relay chain
- if (api.tx.stakingNextAhClient) {
+ setRcApi(undefined);
+ setAhApi(undefined);
+
+ if (isRelayChain) {
setRcApi(api);
- const ahEndpoint = Object.values(prodParasPolkadotCommon[0].providers) as string[];
- getApi(ahEndpoint).then((ahApi) => setAhApi(ahApi)).catch((e) => console.log(e));
+ if (ahUrl) {
+ getApi(ahUrl).then((ahApi) => setAhApi(ahApi)).catch((e) => console.log(e));
+ }
} else if (api.tx.staking && api.tx.stakingNextRcClient) { // Check if Asset Hub chain
setAhApi(api);
- const rcEndpoint = Object.values(prodRelayPolkadot.providers).filter((e) => e.startsWith('ws'));
- getApi(rcEndpoint).then((rcApi) => setRcApi(rcApi)).catch((e) => console.log(e));
+ if (rcUrl) {
+ getApi(rcUrl).then((rcApi) => setRcApi(rcApi)).catch((e) => console.log(e));
+ }
}
- }, [api, apiEndpoint?.paraId]);
+ }, [ahUrl, api, isRelayChain, rcUrl]);
useEffect(() => {
ahApi && rcApi && commandCenterHandler(rcApi, ahApi, setRcOutput, setAhOutput).catch((e) => console.log(e));
From 30707a9e8d321475ad6eb3ab4af5d8c4569e9f19 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Mon, 19 May 2025 10:37:11 +0530
Subject: [PATCH 31/38] feat: refactor CommandCenter to integrate relay and
para endpoints with improved state management
---
.../src/CommandCenter/index.tsx | 30 ++++++++++++++-----
1 file changed, 23 insertions(+), 7 deletions(-)
diff --git a/packages/page-staking-next/src/CommandCenter/index.tsx b/packages/page-staking-next/src/CommandCenter/index.tsx
index 88168179bf47..0950bda04fd0 100644
--- a/packages/page-staking-next/src/CommandCenter/index.tsx
+++ b/packages/page-staking-next/src/CommandCenter/index.tsx
@@ -7,13 +7,15 @@ import type { IEventData } from '@polkadot/types/types';
import React, { useEffect, useMemo, useState } from 'react';
import { ApiPromise, WsProvider } from '@polkadot/api';
+import { createWsEndpoints } from '@polkadot/apps-config';
import { styled } from '@polkadot/react-components';
-import { useApi, useParaEndpoints } from '@polkadot/react-hooks';
-import { useRelayEndpoints } from '@polkadot/react-hooks/useParaEndpoints';
+import { useApi } from '@polkadot/react-hooks';
import AssetHubSection from './ah.js';
import RelaySection from './relay.js';
+const allEndPoints = createWsEndpoints((k, v) => v?.toString() || k);
+
const MAX_EVENTS = 25;
const getApi = async (url: string[]|string) => {
@@ -186,9 +188,7 @@ const commandCenterHandler = async (
};
function CommandCenter () {
- const { api, apiUrl } = useApi();
- const rcEndPoints = useRelayEndpoints();
- const ahEndPoints = useParaEndpoints(1000);
+ const { api, apiEndpoint, apiUrl } = useApi();
const [rcOutput, setRcOutput] = useState([]);
const [ahOutput, setAhOutput] = useState([]);
@@ -202,15 +202,31 @@ function CommandCenter () {
// Check if it is relay chain
const isRelayChain = useMemo(() => api.tx.stakingNextAhClient, [api.tx.stakingNextAhClient]);
+ const rcEndPoints = useMemo(() => {
+ return (isRelayChain
+ ? apiEndpoint?.providers
+ : apiEndpoint?.valueRelay) || [];
+ }, [apiEndpoint?.providers, apiEndpoint?.valueRelay, isRelayChain]);
+
+ const ahEndPoints = useMemo(() => {
+ if (isRelayChain) {
+ return allEndPoints.filter(({ paraId }) =>
+ paraId === 1000
+ ).at(0)?.providers || [];
+ }
+
+ return apiEndpoint?.providers || [];
+ }, [apiEndpoint?.providers, isRelayChain]);
+
useEffect(() => {
if (isRelayChain) {
setRcUrl(apiUrl);
- const ahUrl = ahEndPoints.at(0)?.providers?.at(0);
+ const ahUrl = ahEndPoints.at(0);
setAhUrl(ahUrl);
} else {
setAhUrl(apiUrl);
- const rcUrl = rcEndPoints.at(0)?.valueRelay?.at(0);
+ const rcUrl = rcEndPoints.at(0);
setRcUrl(rcUrl);
}
From 9263093a9005bbd1340c898b25e0af87fa7c3358 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Mon, 19 May 2025 10:54:48 +0530
Subject: [PATCH 32/38] feat: enhance AssetHubSection and RelaySection with API
connection indicators
---
packages/page-staking-next/src/CommandCenter/ah.tsx | 6 ++++--
packages/page-staking-next/src/CommandCenter/index.tsx | 10 ++++++++--
packages/page-staking-next/src/CommandCenter/relay.tsx | 6 ++++--
3 files changed, 16 insertions(+), 6 deletions(-)
diff --git a/packages/page-staking-next/src/CommandCenter/ah.tsx b/packages/page-staking-next/src/CommandCenter/ah.tsx
index e8ed7bb571f9..dcc22a662f48 100644
--- a/packages/page-staking-next/src/CommandCenter/ah.tsx
+++ b/packages/page-staking-next/src/CommandCenter/ah.tsx
@@ -1,18 +1,19 @@
// Copyright 2017-2025 @polkadot/app-staking-next authors & contributors
// SPDX-License-Identifier: Apache-2.0
+import type { ApiPromise } from '@polkadot/api';
import type { IAhOutput } from './index.js';
import React from 'react';
import { Link } from 'react-router-dom';
-import { CardSummary, Expander, MarkWarning, styled } from '@polkadot/react-components';
+import { CardSummary, Expander, MarkWarning, Spinner, styled } from '@polkadot/react-components';
import { Event as EventDisplay } from '@polkadot/react-params';
import { formatNumber } from '@polkadot/util';
import { useTranslation } from '../translate.js';
-function AssetHubSection ({ ahOutput }: {ahOutput: IAhOutput[]}) {
+function AssetHubSection ({ ahApi, ahOutput }: { ahApi?: ApiPromise, ahOutput: IAhOutput[]}) {
const { t } = useTranslation();
return (
@@ -20,6 +21,7 @@ function AssetHubSection ({ ahOutput }: {ahOutput: IAhOutput[]}) {
{t('Asset Hub chain')}
+ {!ahApi && }
{ahOutput.map((ah) => {
return (
diff --git a/packages/page-staking-next/src/CommandCenter/index.tsx b/packages/page-staking-next/src/CommandCenter/index.tsx
index 0950bda04fd0..d049dac715d7 100644
--- a/packages/page-staking-next/src/CommandCenter/index.tsx
+++ b/packages/page-staking-next/src/CommandCenter/index.tsx
@@ -257,8 +257,14 @@ function CommandCenter () {
return (
-
-
+
+
);
}
diff --git a/packages/page-staking-next/src/CommandCenter/relay.tsx b/packages/page-staking-next/src/CommandCenter/relay.tsx
index 8c9a35af34a7..9b99dc58c211 100644
--- a/packages/page-staking-next/src/CommandCenter/relay.tsx
+++ b/packages/page-staking-next/src/CommandCenter/relay.tsx
@@ -1,18 +1,19 @@
// Copyright 2017-2025 @polkadot/app-staking-next authors & contributors
// SPDX-License-Identifier: Apache-2.0
+import type { ApiPromise } from '@polkadot/api';
import type { IRcOutput } from './index.js';
import React from 'react';
import { Link } from 'react-router-dom';
-import { CardSummary, Expander, MarkWarning, styled } from '@polkadot/react-components';
+import { CardSummary, Expander, MarkWarning, Spinner, styled } from '@polkadot/react-components';
import { Event as EventDisplay } from '@polkadot/react-params';
import { formatNumber } from '@polkadot/util';
import { useTranslation } from '../translate.js';
-function RelaySection ({ rcOutput }: {rcOutput: IRcOutput[]}) {
+function RelaySection ({ rcApi, rcOutput }: {rcApi?: ApiPromise, rcOutput: IRcOutput[]}) {
const { t } = useTranslation();
return (
@@ -20,6 +21,7 @@ function RelaySection ({ rcOutput }: {rcOutput: IRcOutput[]}) {
{t('Relay chain')}
+ {!rcApi && }
{rcOutput.map((rc) => {
return (
From 1505d359fd44a328c34531b11a13be80956f862e Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Mon, 19 May 2025 12:21:33 +0530
Subject: [PATCH 33/38] feat: enhance CommandCenter and RelaySection with
children prop and dropdown for API selection
---
.../src/CommandCenter/ah.tsx | 22 ++++++--
.../src/CommandCenter/index.tsx | 52 ++++++++++++++++---
.../src/CommandCenter/relay.tsx | 18 ++++++-
3 files changed, 78 insertions(+), 14 deletions(-)
diff --git a/packages/page-staking-next/src/CommandCenter/ah.tsx b/packages/page-staking-next/src/CommandCenter/ah.tsx
index dcc22a662f48..0f12280f742b 100644
--- a/packages/page-staking-next/src/CommandCenter/ah.tsx
+++ b/packages/page-staking-next/src/CommandCenter/ah.tsx
@@ -1,6 +1,7 @@
// Copyright 2017-2025 @polkadot/app-staking-next authors & contributors
// SPDX-License-Identifier: Apache-2.0
+import type { ReactNode } from 'react';
import type { ApiPromise } from '@polkadot/api';
import type { IAhOutput } from './index.js';
@@ -13,20 +14,33 @@ import { formatNumber } from '@polkadot/util';
import { useTranslation } from '../translate.js';
-function AssetHubSection ({ ahApi, ahOutput }: { ahApi?: ApiPromise, ahOutput: IAhOutput[]}) {
+interface Props {
+ children: ReactNode;
+ ahApi?: ApiPromise;
+ ahOutput: IAhOutput[];
+}
+
+function AssetHubSection ({ ahApi, ahOutput, children }: Props) {
const { t } = useTranslation();
return (
-
+
{t('Asset Hub chain')}
+ {children}
{!ahApi && }
{ahOutput.map((ah) => {
return (
@@ -110,7 +124,7 @@ const StyledSection = styled.section`
font-weight: var(--font-weight-normal);
}
- .relay__chain {
+ .assethub__chain {
display: grid;
grid-template-columns: repeat(2, 1fr);
place-items: start;
diff --git a/packages/page-staking-next/src/CommandCenter/index.tsx b/packages/page-staking-next/src/CommandCenter/index.tsx
index d049dac715d7..d50538731e43 100644
--- a/packages/page-staking-next/src/CommandCenter/index.tsx
+++ b/packages/page-staking-next/src/CommandCenter/index.tsx
@@ -4,11 +4,11 @@
import type { AccountId32, Event } from '@polkadot/types/interfaces';
import type { IEventData } from '@polkadot/types/types';
-import React, { useEffect, useMemo, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { ApiPromise, WsProvider } from '@polkadot/api';
import { createWsEndpoints } from '@polkadot/apps-config';
-import { styled } from '@polkadot/react-components';
+import { Dropdown, styled } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import AssetHubSection from './ah.js';
@@ -208,7 +208,7 @@ function CommandCenter () {
: apiEndpoint?.valueRelay) || [];
}, [apiEndpoint?.providers, apiEndpoint?.valueRelay, isRelayChain]);
- const ahEndPoints = useMemo(() => {
+ const ahEndPoints: string[] = useMemo(() => {
if (isRelayChain) {
return allEndPoints.filter(({ paraId }) =>
paraId === 1000
@@ -218,6 +218,21 @@ function CommandCenter () {
return apiEndpoint?.providers || [];
}, [apiEndpoint?.providers, isRelayChain]);
+ const rcEndPointOptions = useRef(rcEndPoints.map((e) => ({ text: e, value: e })));
+ const ahEndPointOptions = useRef(ahEndPoints.map((e) => ({ text: e, value: e })));
+
+ const _onSelectAhUrl = useCallback((newAhUrl: string) => {
+ if (newAhUrl !== ahUrl) {
+ setAhUrl(newAhUrl);
+ }
+ }, [ahUrl]);
+
+ const _onSelectRcUrl = useCallback((newRcUrl: string) => {
+ if (newRcUrl !== rcUrl) {
+ setRcUrl(newRcUrl);
+ }
+ }, [rcUrl]);
+
useEffect(() => {
if (isRelayChain) {
setRcUrl(apiUrl);
@@ -240,19 +255,19 @@ function CommandCenter () {
setRcApi(api);
if (ahUrl) {
- getApi(ahUrl).then((ahApi) => setAhApi(ahApi)).catch((e) => console.log(e));
+ getApi(ahUrl).then((ahApi) => setAhApi(ahApi)).catch(console.log);
}
} else if (api.tx.staking && api.tx.stakingNextRcClient) { // Check if Asset Hub chain
setAhApi(api);
if (rcUrl) {
- getApi(rcUrl).then((rcApi) => setRcApi(rcApi)).catch((e) => console.log(e));
+ getApi(rcUrl).then((rcApi) => setRcApi(rcApi)).catch(console.log);
}
}
}, [ahUrl, api, isRelayChain, rcUrl]);
useEffect(() => {
- ahApi && rcApi && commandCenterHandler(rcApi, ahApi, setRcOutput, setAhOutput).catch((e) => console.log(e));
+ ahApi && rcApi && commandCenterHandler(rcApi, ahApi, setRcOutput, setAhOutput).catch(console.log);
}, [ahApi, rcApi]);
return (
@@ -260,11 +275,27 @@ function CommandCenter () {
+ >
+
+
+ >
+
+
);
}
@@ -277,6 +308,11 @@ const StyledDiv = styled.div`
@media screen and (max-width: 1200px){
grid-template-columns: repeat(1, 1fr);
}
+
+ .ui {
+ margin-left: 0.5rem !important;
+ width: 20rem !important;
+ }
`;
export default React.memo(CommandCenter);
diff --git a/packages/page-staking-next/src/CommandCenter/relay.tsx b/packages/page-staking-next/src/CommandCenter/relay.tsx
index 9b99dc58c211..cb51ae541388 100644
--- a/packages/page-staking-next/src/CommandCenter/relay.tsx
+++ b/packages/page-staking-next/src/CommandCenter/relay.tsx
@@ -1,6 +1,7 @@
// Copyright 2017-2025 @polkadot/app-staking-next authors & contributors
// SPDX-License-Identifier: Apache-2.0
+import type { ReactNode } from 'react';
import type { ApiPromise } from '@polkadot/api';
import type { IRcOutput } from './index.js';
@@ -13,13 +14,26 @@ import { formatNumber } from '@polkadot/util';
import { useTranslation } from '../translate.js';
-function RelaySection ({ rcApi, rcOutput }: {rcApi?: ApiPromise, rcOutput: IRcOutput[]}) {
+interface Props {
+ children: ReactNode;
+ rcApi?: ApiPromise;
+ rcOutput: IRcOutput[];
+}
+
+function RelaySection ({ children, rcApi, rcOutput }: Props) {
const { t } = useTranslation();
return (
-
+
{t('Relay chain')}
+ {children}
{!rcApi && }
From 9f3214e1bae2ac976ce9319d8b321e37736baf44 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Mon, 19 May 2025 12:30:31 +0530
Subject: [PATCH 34/38] feat: improve API disconnection handling and reset
state in CommandCenter
---
.../page-staking-next/src/CommandCenter/index.tsx | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/packages/page-staking-next/src/CommandCenter/index.tsx b/packages/page-staking-next/src/CommandCenter/index.tsx
index d50538731e43..1cb2068adc63 100644
--- a/packages/page-staking-next/src/CommandCenter/index.tsx
+++ b/packages/page-staking-next/src/CommandCenter/index.tsx
@@ -223,15 +223,17 @@ function CommandCenter () {
const _onSelectAhUrl = useCallback((newAhUrl: string) => {
if (newAhUrl !== ahUrl) {
+ ahApi?.disconnect().catch(console.log);
setAhUrl(newAhUrl);
}
- }, [ahUrl]);
+ }, [ahApi, ahUrl]);
const _onSelectRcUrl = useCallback((newRcUrl: string) => {
if (newRcUrl !== rcUrl) {
+ rcApi?.disconnect().catch(console.log);
setRcUrl(newRcUrl);
}
- }, [rcUrl]);
+ }, [rcApi, rcUrl]);
useEffect(() => {
if (isRelayChain) {
@@ -250,6 +252,8 @@ function CommandCenter () {
useEffect(() => {
setRcApi(undefined);
setAhApi(undefined);
+ setRcOutput([]);
+ setAhOutput([]);
if (isRelayChain) {
setRcApi(api);
@@ -309,6 +313,10 @@ const StyledDiv = styled.div`
grid-template-columns: repeat(1, 1fr);
}
+ .ui--Spinner {
+ margin-top: 4rem;
+ }
+
.ui {
margin-left: 0.5rem !important;
width: 20rem !important;
From 4028536619a1423c891479198d79981efa2e98d1 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Tue, 20 May 2025 09:51:30 +0530
Subject: [PATCH 35/38] feat: improve command center API handling and enhance
relay section styling
---
.../page-staking-next/src/CommandCenter/index.tsx | 12 ++++++------
.../page-staking-next/src/CommandCenter/relay.tsx | 1 +
2 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/packages/page-staking-next/src/CommandCenter/index.tsx b/packages/page-staking-next/src/CommandCenter/index.tsx
index 1cb2068adc63..ffe8ca81023c 100644
--- a/packages/page-staking-next/src/CommandCenter/index.tsx
+++ b/packages/page-staking-next/src/CommandCenter/index.tsx
@@ -82,7 +82,7 @@ const commandCenterHandler = async (
const hasNextActiveId =
await rcApi.query.stakingNextAhClient.nextSessionChangesValidators();
// whether the AhClient pallet is blocked or not, useful for migration signal from the fellowship.
- const isBlocked = await rcApi.query.stakingNextAhClient.isBlocked();
+ const isBlocked = await rcApi.query.stakingNextAhClient?.isBlocked?.();
// Events that we are interested in from RC:
const eventsOfInterest = (await (await rcApi.at(header.hash.toHex())).query.system.events())
@@ -111,7 +111,7 @@ const commandCenterHandler = async (
stakingNextAhClient: {
hasNextActiveId: hasNextActiveId.isEmpty ? undefined : rcApi.createType('Option', hasNextActiveId).unwrap().toNumber(),
hasQueuedInClient: parsedHasQueuedInClient.isNone ? undefined : [parsedHasQueuedInClient.unwrap()[0].toNumber(), parsedHasQueuedInClient.unwrap()[1]],
- isBlocked: isBlocked.toHuman() !== 'Not'
+ isBlocked: isBlocked?.toHuman() !== 'Not'
}
},
...prev.slice(0, MAX_EVENTS - 1)];
@@ -210,13 +210,13 @@ function CommandCenter () {
const ahEndPoints: string[] = useMemo(() => {
if (isRelayChain) {
- return allEndPoints.filter(({ paraId }) =>
- paraId === 1000
- ).at(0)?.providers || [];
+ return allEndPoints.find(({ genesisHashRelay, paraId }) =>
+ paraId === 1000 && genesisHashRelay === api.genesisHash.toHex()
+ )?.providers || [];
}
return apiEndpoint?.providers || [];
- }, [apiEndpoint?.providers, isRelayChain]);
+ }, [api.genesisHash, apiEndpoint?.providers, isRelayChain]);
const rcEndPointOptions = useRef(rcEndPoints.map((e) => ({ text: e, value: e })));
const ahEndPointOptions = useRef(ahEndPoints.map((e) => ({ text: e, value: e })));
diff --git a/packages/page-staking-next/src/CommandCenter/relay.tsx b/packages/page-staking-next/src/CommandCenter/relay.tsx
index cb51ae541388..10c647793cfc 100644
--- a/packages/page-staking-next/src/CommandCenter/relay.tsx
+++ b/packages/page-staking-next/src/CommandCenter/relay.tsx
@@ -148,6 +148,7 @@ const StyledSection = styled.section`
}
.events__summary {
+ justify-self: center;
h3 {
font-weight: 500;
font-size: var(--font-size-h2);
From 19a75a29995c526b2866c8eb1b0e03d50a5d82c9 Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Wed, 21 May 2025 12:41:57 +0530
Subject: [PATCH 36/38] feat: add translations for Active Validators and All
Validators in locale files
---
packages/apps/public/locales/en/app-staking-next.json | 4 ++--
packages/apps/public/locales/en/translation.json | 2 ++
packages/page-staking-next/src/System/index.tsx | 8 ++++----
3 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/packages/apps/public/locales/en/app-staking-next.json b/packages/apps/public/locales/en/app-staking-next.json
index fb8bde998aac..fc0a795cdc03 100644
--- a/packages/apps/public/locales/en/app-staking-next.json
+++ b/packages/apps/public/locales/en/app-staking-next.json
@@ -1,16 +1,16 @@
{
"Accounts": "Accounts",
+ "Active Validators": "Active Validators",
+ "All Validators": "All Validators",
"Asset Hub chain": "Asset Hub chain",
"Asset Hub client pallet(AhClient) is blocked currently, useful for migration signal from the fellowship.": "Asset Hub client pallet(AhClient) is blocked currently, useful for migration signal from the fellowship.",
"Bags": "Bags",
"Command Center": "Command Center",
"No events available": "No events available",
- "Overview": "Overview",
"Payouts": "Payouts",
"Pools": "Pools",
"Relay chain": "Relay chain",
"Slashes": "Slashes",
- "Targets": "Targets",
"There is a validator set queued in ah-client.": "There is a validator set queued in ah-client.",
"Validator stats": "Validator stats",
"active era": "active era",
diff --git a/packages/apps/public/locales/en/translation.json b/packages/apps/public/locales/en/translation.json
index 9067849ee9e4..f7ddea4019fb 100644
--- a/packages/apps/public/locales/en/translation.json
+++ b/packages/apps/public/locales/en/translation.json
@@ -39,6 +39,7 @@
"Accounts": "",
"Accounts injected from any of these extensions will appear in this application and be available for use. The above list is updated as more extensions with external signing capability become available.": "",
"Active": "",
+ "Active Validators": "",
"Active nominations ({{count}})": "",
"Add": "",
"Add Bounty": "",
@@ -69,6 +70,7 @@
"Addresses": "",
"Advanced creation options": "",
"After delay": "",
+ "All Validators": "",
"All active/available cores": "",
"All active/available tracks": "",
"All available slices": "",
diff --git a/packages/page-staking-next/src/System/index.tsx b/packages/page-staking-next/src/System/index.tsx
index 53afecf61cbd..b8b4864313fe 100644
--- a/packages/page-staking-next/src/System/index.tsx
+++ b/packages/page-staking-next/src/System/index.tsx
@@ -91,8 +91,8 @@ function StakingApp ({ basePath }: Props): React.ReactElement {
const items = useMemo(() => [
{
isRoot: true,
- name: 'overview',
- text: t('Overview')
+ name: 'active-validators',
+ text: t('Active Validators')
},
{
name: 'actions',
@@ -108,8 +108,8 @@ function StakingApp ({ basePath }: Props): React.ReactElement {
},
{
alias: 'returns',
- name: 'targets',
- text: t('Targets')
+ name: 'all-validators',
+ text: t('All Validators')
},
hasStashes && isFunction((api.query.voterBagsList || api.query.bagsList || api.query.voterList)?.counterForListNodes) && {
name: 'bags',
From a7e6758cb28d75ad9bc1b9c3d3e410fffb9de4cf Mon Sep 17 00:00:00 2001
From: Arjun Porwal
Date: Wed, 21 May 2025 12:46:19 +0530
Subject: [PATCH 37/38] feat: update route path for targets to all-validators
in StakingApp
---
packages/apps/public/locales/en/app-staking-next.json | 2 +-
packages/apps/public/locales/en/translation.json | 2 +-
packages/page-staking-next/src/CommandCenter/relay.tsx | 2 +-
packages/page-staking-next/src/System/index.tsx | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/apps/public/locales/en/app-staking-next.json b/packages/apps/public/locales/en/app-staking-next.json
index fc0a795cdc03..f2c8a1777893 100644
--- a/packages/apps/public/locales/en/app-staking-next.json
+++ b/packages/apps/public/locales/en/app-staking-next.json
@@ -3,7 +3,7 @@
"Active Validators": "Active Validators",
"All Validators": "All Validators",
"Asset Hub chain": "Asset Hub chain",
- "Asset Hub client pallet(AhClient) is blocked currently, useful for migration signal from the fellowship.": "Asset Hub client pallet(AhClient) is blocked currently, useful for migration signal from the fellowship.",
+ "Asset Hub client pallet (AhClient) is blocked currently, useful for migration signal from the fellowship.": "Asset Hub client pallet (AhClient) is blocked currently, useful for migration signal from the fellowship.",
"Bags": "Bags",
"Command Center": "Command Center",
"No events available": "No events available",
diff --git a/packages/apps/public/locales/en/translation.json b/packages/apps/public/locales/en/translation.json
index f7ddea4019fb..01e492bc562c 100644
--- a/packages/apps/public/locales/en/translation.json
+++ b/packages/apps/public/locales/en/translation.json
@@ -105,7 +105,7 @@
"As a council member, you can suggest an initial value for the tip, each other council member can suggest their own.": "",
"As such it is recommended that you setup a proxy to control operations via the stash.": "",
"Asset Hub chain": "",
- "Asset Hub client pallet(AhClient) is blocked currently, useful for migration signal from the fellowship.": "",
+ "Asset Hub client pallet (AhClient) is blocked currently, useful for migration signal from the fellowship.": "",
"At block": "",
"Auctions": "",
"Auctions will be deprecated in favor of Coretime. When Coretime is active in Polkadot, this page will be removed.": "",
diff --git a/packages/page-staking-next/src/CommandCenter/relay.tsx b/packages/page-staking-next/src/CommandCenter/relay.tsx
index 10c647793cfc..58dd47666354 100644
--- a/packages/page-staking-next/src/CommandCenter/relay.tsx
+++ b/packages/page-staking-next/src/CommandCenter/relay.tsx
@@ -58,7 +58,7 @@ function RelaySection ({ children, rcApi, rcOutput }: Props) {
}
- {rc.stakingNextAhClient.isBlocked &&
}
+ {rc.stakingNextAhClient.isBlocked &&
}
{rc.stakingNextAhClient.hasQueuedInClient &&
diff --git a/packages/page-staking-next/src/System/index.tsx b/packages/page-staking-next/src/System/index.tsx
index b8b4864313fe..34997c8a2c81 100644
--- a/packages/page-staking-next/src/System/index.tsx
+++ b/packages/page-staking-next/src/System/index.tsx
@@ -198,7 +198,7 @@ function StakingApp ({ basePath }: Props): React.ReactElement
{
toggleNominatedBy={toggleNominatedBy}
/>
}
- path='targets'
+ path='all-validators'
/>
Date: Wed, 21 May 2025 12:49:42 +0530
Subject: [PATCH 38/38] feat: update README with details on Asset Hub migration
and staking workflow changes
---
packages/page-staking-next/README.md | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/packages/page-staking-next/README.md b/packages/page-staking-next/README.md
index bcee3ef8047f..6f910f1cf72b 100644
--- a/packages/page-staking-next/README.md
+++ b/packages/page-staking-next/README.md
@@ -1,3 +1,11 @@
# @polkadot/app-staking-next
+With Asset Hub migration, the workflow of the staking system will change. The "user interactions" (nominate, bond, etc) are by and large the same, yet some details outlined below will change:
+
+On the relay chain, the session pallet will rotate session (aka. epochs) at a fixed rate as it did before. It will send these to AH in the form of a SessionReport message. Possibly, it will also send messages about offences to AH so that they can be applied and actually slash staker balances in AH.
+
+`pallet-session` on the relay chain will only interact with `pallet-staking-next-ah-client`. `ah-client` could at any point, if it has one, return a validator set to session to be used for the next session.
+
+In any session change in pallet-session where a new validator set is activated, that SessionReport will contain two pieces of information:
+
More Info can be found here - https://hackmd.io/7PiBrGxxRG2ib-WRZYJZhQ
\ No newline at end of file