From 2fc74956d248a2fe7bed3b7a60796b1127f8c71f Mon Sep 17 00:00:00 2001 From: wirednkod Date: Wed, 16 Mar 2022 19:23:42 +0200 Subject: [PATCH 01/26] add minExposedThreshold into useSortedTarget --- packages/page-staking/src/types.ts | 1 + packages/page-staking/src/useSortedTargets.ts | 34 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/page-staking/src/types.ts b/packages/page-staking/src/types.ts index 6b0a6bc2b17d..52e44f909873 100644 --- a/packages/page-staking/src/types.ts +++ b/packages/page-staking/src/types.ts @@ -90,4 +90,5 @@ export interface SortedTargets { validators?: ValidatorInfo[]; validatorIds?: string[]; waitingIds?: string[]; + minExposedThreshold?: Promise } diff --git a/packages/page-staking/src/useSortedTargets.ts b/packages/page-staking/src/useSortedTargets.ts index 2861c0d82bc4..6f167919400f 100644 --- a/packages/page-staking/src/useSortedTargets.ts +++ b/packages/page-staking/src/useSortedTargets.ts @@ -10,6 +10,8 @@ import type { SortedTargets, TargetSortBy, ValidatorInfo } from './types'; import { useMemo } from 'react'; import { createNamedHook, useAccounts, useApi, useCall, useCallMulti, useInflation } from '@polkadot/react-hooks'; +import { AccountId32 } from '@polkadot/types/interfaces'; +import { PalletStakingIndividualExposure } from '@polkadot/types/lookup'; import { arrayFlatten, BN, BN_HUNDRED, BN_MAX_INTEGER, BN_ONE, BN_ZERO } from '@polkadot/util'; interface LastEra { @@ -304,6 +306,35 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted [baseInfo, inflation] ); + const minExposedThreshold = useMemo(async () => { + const b = (x: BN): string => api.createType('Balance', x).toHuman(); + + const asyncFunc = async () => { + const assignments: Map = new Map(); + const currentEra = (await api.query.staking.currentEra()).unwrap(); + const stakers = await api.query.staking.erasStakers.entries(currentEra); + + stakers.sort((a, b) => a[1].total.toBn().cmp(b[1].total.toBn())); + + stakers.map((x) => x[1].others).flat(1).forEach((x) => { + const nominator = (x as PalletStakingIndividualExposure).who; + const amount = (x as PalletStakingIndividualExposure).value; + const val = assignments.get(nominator); + + assignments.set(nominator, val ? amount.toBn().add(val) : amount.toBn()); + }); + + const nominatorStakes = Array.from(assignments.values()); + + nominatorStakes.sort((a, b) => a.cmp(b)); + const minExposedThreshold = nominatorStakes[0]; + + return b(minExposedThreshold) || ''; + }; + + return asyncFunc(); + }, [api]); + return { counterForNominators, counterForValidators, @@ -314,7 +345,8 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted minNominated: BN_ZERO, minNominatorBond, minValidatorBond, - ...partial + ...partial, + minExposedThreshold }; } From f9eb19c4ebd74fae010e72f9dc9d0f505dd5a366 Mon Sep 17 00:00:00 2001 From: wirednkod Date: Wed, 16 Mar 2022 23:35:44 +0200 Subject: [PATCH 02/26] Fix the minExposedThreshold scripts/hook --- .../page-staking/src/Overview/Summary.tsx | 39 ++---------- packages/page-staking/src/types.ts | 2 +- packages/page-staking/src/useSortedTargets.ts | 59 ++++++++++--------- 3 files changed, 37 insertions(+), 63 deletions(-) diff --git a/packages/page-staking/src/Overview/Summary.tsx b/packages/page-staking/src/Overview/Summary.tsx index 3ea4fb51e906..a3df9c9507df 100644 --- a/packages/page-staking/src/Overview/Summary.tsx +++ b/packages/page-staking/src/Overview/Summary.tsx @@ -21,46 +21,17 @@ interface Props { targets: SortedTargets; } -function Summary ({ className = '', isVisible, stakingOverview, targets: { counterForNominators, inflation: { idealStake, inflation, stakedFraction }, nominators, waitingIds } }: Props): React.ReactElement { +function Summary ({ className = '', isVisible, stakingOverview, targets: { counterForNominators, inflation: { idealStake, inflation, stakedFraction }, minExposedThreshold, nominators, waitingIds } }: Props): React.ReactElement { const { t } = useTranslation(); return (
- ('validators')}> - {stakingOverview - ? <>{formatNumber(stakingOverview.validators.length)} / {formatNumber(stakingOverview.validatorCount)} - : - } - - ('waiting')} - > - {waitingIds - ? formatNumber(waitingIds.length) - : - } + ('nominators')}> + 124,19 DOT - ('active / nominators') - : t('nominators') - } - > - {nominators - ? ( - <> - {formatNumber(nominators.length)} - {counterForNominators && ( - <> / {formatNumber(counterForNominators)} - )} - - ) - : - } + ('validators')}> + {minExposedThreshold === '0' ? : minExposedThreshold}
diff --git a/packages/page-staking/src/types.ts b/packages/page-staking/src/types.ts index 52e44f909873..db0a9d08411b 100644 --- a/packages/page-staking/src/types.ts +++ b/packages/page-staking/src/types.ts @@ -90,5 +90,5 @@ export interface SortedTargets { validators?: ValidatorInfo[]; validatorIds?: string[]; waitingIds?: string[]; - minExposedThreshold?: Promise + minExposedThreshold?: string; } diff --git a/packages/page-staking/src/useSortedTargets.ts b/packages/page-staking/src/useSortedTargets.ts index 6f167919400f..0f077843979b 100644 --- a/packages/page-staking/src/useSortedTargets.ts +++ b/packages/page-staking/src/useSortedTargets.ts @@ -4,10 +4,11 @@ import type { ApiPromise } from '@polkadot/api'; import type { DeriveSessionInfo, DeriveStakingElected, DeriveStakingWaiting } from '@polkadot/api-derive/types'; import type { Inflation } from '@polkadot/react-hooks/types'; -import type { Option, u32 } from '@polkadot/types'; +import type { Option, StorageKey, u32 } from '@polkadot/types'; +import type { Codec } from '@polkadot/types/types'; import type { SortedTargets, TargetSortBy, ValidatorInfo } from './types'; -import { useMemo } from 'react'; +import { useMemo, useState } from 'react'; import { createNamedHook, useAccounts, useApi, useCall, useCallMulti, useInflation } from '@polkadot/react-hooks'; import { AccountId32 } from '@polkadot/types/interfaces'; @@ -246,6 +247,27 @@ function extractBaseInfo (api: ApiPromise, allAccounts: string[], electedDerive: }; } +function getMinExposedThreshold (stakers: [StorageKey<[u32, AccountId32]>, Codec][]) { + const assignments: Map = new Map(); + const b = (x: BN): string => api.createType('Balance', x).toHuman(); + + stakers.sort((a, b) => a[1].total.toBn().cmp(b[1].total.toBn())); + + stakers.map((x) => x[1].others).flat(1).forEach((x) => { + const nominator = (x as PalletStakingIndividualExposure).who; + const amount = (x as PalletStakingIndividualExposure).value; + const val = assignments.get(nominator); + + assignments.set(nominator, val ? amount.toBn().add(val) : amount.toBn()); + }); + + const nominatorStakes = Array.from(assignments.values()); + + nominatorStakes.sort((a, b) => a.cmp(b)); + + return b(nominatorStakes[0]); +} + const transformEra = { transform: ({ activeEra, eraLength, sessionLength }: DeriveSessionInfo): LastEra => ({ activeEra, @@ -289,6 +311,7 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted const electedInfo = useCall(api.derive.staking.electedInfo, [{ ...DEFAULT_FLAGS_ELECTED, withLedger }]); const waitingInfo = useCall(api.derive.staking.waitingInfo, [{ ...DEFAULT_FLAGS_WAITING, withLedger }]); const lastEraInfo = useCall(api.derive.session.info, undefined, transformEra); + const [stakers, setStakers] = useState<[StorageKey<[u32, AccountId32]>, Codec][]>([]); const baseInfo = useMemo( () => electedInfo && lastEraInfo && totalIssuance && waitingInfo @@ -306,34 +329,14 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted [baseInfo, inflation] ); - const minExposedThreshold = useMemo(async () => { - const b = (x: BN): string => api.createType('Balance', x).toHuman(); - - const asyncFunc = async () => { - const assignments: Map = new Map(); - const currentEra = (await api.query.staking.currentEra()).unwrap(); - const stakers = await api.query.staking.erasStakers.entries(currentEra); - - stakers.sort((a, b) => a[1].total.toBn().cmp(b[1].total.toBn())); - - stakers.map((x) => x[1].others).flat(1).forEach((x) => { - const nominator = (x as PalletStakingIndividualExposure).who; - const amount = (x as PalletStakingIndividualExposure).value; - const val = assignments.get(nominator); + const curEra = useCall>(api.query.staking.currentEra); - assignments.set(nominator, val ? amount.toBn().add(val) : amount.toBn()); - }); - - const nominatorStakes = Array.from(assignments.values()); - - nominatorStakes.sort((a, b) => a.cmp(b)); - const minExposedThreshold = nominatorStakes[0]; - - return b(minExposedThreshold) || ''; - }; + const getStakers = useMemo(() => async (currentEra: u32) => { + setStakers(await api.query.staking.erasStakers.entries(currentEra)); + }, [api.query.staking.erasStakers]); - return asyncFunc(); - }, [api]); + curEra && getStakers(curEra?.unwrap()); + const minExposedThreshold = getMinExposedThreshold(stakers); return { counterForNominators, From 42359e869a9209b8b9167aad801811a483e60bad Mon Sep 17 00:00:00 2001 From: wirednkod Date: Thu, 17 Mar 2022 11:59:13 +0200 Subject: [PATCH 03/26] fix missing api error --- packages/page-staking/src/useSortedTargets.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/page-staking/src/useSortedTargets.ts b/packages/page-staking/src/useSortedTargets.ts index 0f077843979b..e0e3c07cb444 100644 --- a/packages/page-staking/src/useSortedTargets.ts +++ b/packages/page-staking/src/useSortedTargets.ts @@ -247,7 +247,7 @@ function extractBaseInfo (api: ApiPromise, allAccounts: string[], electedDerive: }; } -function getMinExposedThreshold (stakers: [StorageKey<[u32, AccountId32]>, Codec][]) { +function getMinExposedThreshold (api: ApiPromise, stakers: [StorageKey<[u32, AccountId32]>, Codec][]) { const assignments: Map = new Map(); const b = (x: BN): string => api.createType('Balance', x).toHuman(); @@ -336,7 +336,7 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted }, [api.query.staking.erasStakers]); curEra && getStakers(curEra?.unwrap()); - const minExposedThreshold = getMinExposedThreshold(stakers); + const minExposedThreshold = getMinExposedThreshold(api, stakers); return { counterForNominators, From 4be121aaf289719fa87e6b9bc10ac09165116906 Mon Sep 17 00:00:00 2001 From: wirednkod Date: Fri, 18 Mar 2022 00:24:05 +0200 Subject: [PATCH 04/26] add implementation based on issue #6918 --- .../page-staking/src/Overview/Summary.tsx | 17 ++++++------- packages/page-staking/src/types.ts | 3 ++- packages/page-staking/src/useSortedTargets.ts | 25 ++++++++++++++----- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/packages/page-staking/src/Overview/Summary.tsx b/packages/page-staking/src/Overview/Summary.tsx index a3df9c9507df..fcaceca75990 100644 --- a/packages/page-staking/src/Overview/Summary.tsx +++ b/packages/page-staking/src/Overview/Summary.tsx @@ -9,7 +9,6 @@ import styled from 'styled-components'; import SummarySession from '@polkadot/app-explorer/SummarySession'; import { CardSummary, Spinner, SummaryBox } from '@polkadot/react-components'; -import { formatNumber } from '@polkadot/util'; import { useTranslation } from '../translate'; @@ -21,20 +20,19 @@ interface Props { targets: SortedTargets; } -function Summary ({ className = '', isVisible, stakingOverview, targets: { counterForNominators, inflation: { idealStake, inflation, stakedFraction }, minExposedThreshold, nominators, waitingIds } }: Props): React.ReactElement { +function Summary ({ className = '', isVisible, targets: { inflation: { idealStake, inflation, stakedFraction }, nominatorMinActiveThreshold, validatorMinActiveThreshold } }: Props): React.ReactElement { const { t } = useTranslation(); return (
- ('nominators')}> - 124,19 DOT + {t('nominators')}
{t('min active stake')}
}> + {nominatorMinActiveThreshold === '0' ? : nominatorMinActiveThreshold}
- ('validators')}> - {minExposedThreshold === '0' ? : minExposedThreshold} + {t('validators')}
{t('min active stake')}
}> + {validatorMinActiveThreshold === '' ? : validatorMinActiveThreshold}
-
-
+
{(idealStake > 0) && Number.isFinite(idealStake) && ( {inflation.toFixed(1)}% )} -
-
+
diff --git a/packages/page-staking/src/types.ts b/packages/page-staking/src/types.ts index db0a9d08411b..5ee02e22e170 100644 --- a/packages/page-staking/src/types.ts +++ b/packages/page-staking/src/types.ts @@ -90,5 +90,6 @@ export interface SortedTargets { validators?: ValidatorInfo[]; validatorIds?: string[]; waitingIds?: string[]; - minExposedThreshold?: string; + nominatorMinActiveThreshold?: string; + validatorMinActiveThreshold?: string; } diff --git a/packages/page-staking/src/useSortedTargets.ts b/packages/page-staking/src/useSortedTargets.ts index e0e3c07cb444..77912e82475d 100644 --- a/packages/page-staking/src/useSortedTargets.ts +++ b/packages/page-staking/src/useSortedTargets.ts @@ -8,7 +8,7 @@ import type { Option, StorageKey, u32 } from '@polkadot/types'; import type { Codec } from '@polkadot/types/types'; import type { SortedTargets, TargetSortBy, ValidatorInfo } from './types'; -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { createNamedHook, useAccounts, useApi, useCall, useCallMulti, useInflation } from '@polkadot/react-hooks'; import { AccountId32 } from '@polkadot/types/interfaces'; @@ -247,9 +247,10 @@ function extractBaseInfo (api: ApiPromise, allAccounts: string[], electedDerive: }; } -function getMinExposedThreshold (api: ApiPromise, stakers: [StorageKey<[u32, AccountId32]>, Codec][]) { +const b = (x: BN, api: ApiPromise): string => api.createType('Balance', x).toHuman(); + +function getMinActiveThreshold (api: ApiPromise, stakers: [StorageKey<[u32, AccountId32]>, Codec][]) { const assignments: Map = new Map(); - const b = (x: BN): string => api.createType('Balance', x).toHuman(); stakers.sort((a, b) => a[1].total.toBn().cmp(b[1].total.toBn())); @@ -265,7 +266,7 @@ function getMinExposedThreshold (api: ApiPromise, stakers: [StorageKey<[u32, Acc nominatorStakes.sort((a, b) => a.cmp(b)); - return b(nominatorStakes[0]); + return b(nominatorStakes[0], api); } const transformEra = { @@ -312,6 +313,13 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted const waitingInfo = useCall(api.derive.staking.waitingInfo, [{ ...DEFAULT_FLAGS_WAITING, withLedger }]); const lastEraInfo = useCall(api.derive.session.info, undefined, transformEra); const [stakers, setStakers] = useState<[StorageKey<[u32, AccountId32]>, Codec][]>([]); + const [stakersTotal, setStakersTotal] = useState(); + + useEffect(() => { + if (stakers[0] && stakers[0][1]) { + setStakersTotal(stakers[0][1].total.toBn() as BN); + } + }, [stakers]); const baseInfo = useMemo( () => electedInfo && lastEraInfo && totalIssuance && waitingInfo @@ -336,7 +344,11 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted }, [api.query.staking.erasStakers]); curEra && getStakers(curEra?.unwrap()); - const minExposedThreshold = getMinExposedThreshold(api, stakers); + const nominatorMinActiveThreshold = getMinActiveThreshold(api, stakers); + const validatorMinActiveThreshold = stakersTotal ? b(stakersTotal, api) : ''; + + console.log('nominatorMinActiveThreshold', nominatorMinActiveThreshold); + console.log('validatorMinActiveThreshold', validatorMinActiveThreshold); return { counterForNominators, @@ -349,7 +361,8 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted minNominatorBond, minValidatorBond, ...partial, - minExposedThreshold + nominatorMinActiveThreshold, + validatorMinActiveThreshold }; } From cddbd7eb04c5a8d473f04ff449d5b5323ca282e5 Mon Sep 17 00:00:00 2001 From: wirednkod Date: Fri, 18 Mar 2022 00:24:40 +0200 Subject: [PATCH 05/26] add translation strings --- packages/apps/public/locales/en/app-staking.json | 3 ++- packages/apps/public/locales/it/translation.json | 3 ++- packages/apps/public/locales/ru/translation.json | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/apps/public/locales/en/app-staking.json b/packages/apps/public/locales/en/app-staking.json index 0bb337fc9ca2..d3ddd77b592b 100644 --- a/packages/apps/public/locales/en/app-staking.json +++ b/packages/apps/public/locales/en/app-staking.json @@ -208,6 +208,7 @@ "last #": "last #", "last reward": "last reward", "lowest / avg staked": "lowest / avg staked", + "min active stake": "min active stake", "min nominated": "min nominated", "min nominated / threshold": "min nominated / threshold", "next": "next", @@ -274,4 +275,4 @@ "{{currency}} slashed": "{{currency}} slashed", "{{currency}} total": "{{currency}} total", "{{days}} days": "{{days}} days" -} \ No newline at end of file +} diff --git a/packages/apps/public/locales/it/translation.json b/packages/apps/public/locales/it/translation.json index 5f6b403be5a2..4b84efd53a40 100644 --- a/packages/apps/public/locales/it/translation.json +++ b/packages/apps/public/locales/it/translation.json @@ -1466,6 +1466,7 @@ "https://example.com": "https://example.com", "id": "id", "ideal staked": "", + "min active stake": "", "identity": "identità", "imminent preimage (proposal already passed)": "proposta imminente (già approvate)", "immortal": "perpetua", @@ -2135,4 +2136,4 @@ "{{threshold}}, passing": "{{threshold}}, approvato", "{{type}} copied": "{{type}} copiato", "{{value}}x voting balance, locked for {{lock}}x enactment ({{period}} days)": "{{value}} x moltiplicatore di voto, fondi bloccati per {{lock}}x = {{period}} giorni" -} \ No newline at end of file +} diff --git a/packages/apps/public/locales/ru/translation.json b/packages/apps/public/locales/ru/translation.json index 1e9e214bd68d..f899a9c1131f 100644 --- a/packages/apps/public/locales/ru/translation.json +++ b/packages/apps/public/locales/ru/translation.json @@ -1492,6 +1492,7 @@ "https://example.com": "https://primer.com", "id": "", "ideal staked": "", + "min active stake": "", "identity": "личность", "imminent preimage (proposal already passed)": "неизбежный прообраз (предложение уже одобрено)", "immortal": "бессмертный", @@ -2215,4 +2216,4 @@ "{{threshold}}, passing": "{{threshold}}, проходит", "{{type}} copied": "", "{{value}}x voting balance, locked for {{lock}}x enactment ({{period}} days)": "{{value}}x баланс голосования, заблокировано на {{lock}}x ({{period}} дней)" -} \ No newline at end of file +} From deaa75e694162ce35a1cf963d282684db3d0c464 Mon Sep 17 00:00:00 2001 From: wirednkod Date: Fri, 18 Mar 2022 00:27:07 +0200 Subject: [PATCH 06/26] remove stray logs --- packages/page-staking/src/useSortedTargets.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/page-staking/src/useSortedTargets.ts b/packages/page-staking/src/useSortedTargets.ts index 77912e82475d..f706b1ea8502 100644 --- a/packages/page-staking/src/useSortedTargets.ts +++ b/packages/page-staking/src/useSortedTargets.ts @@ -347,9 +347,6 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted const nominatorMinActiveThreshold = getMinActiveThreshold(api, stakers); const validatorMinActiveThreshold = stakersTotal ? b(stakersTotal, api) : ''; - console.log('nominatorMinActiveThreshold', nominatorMinActiveThreshold); - console.log('validatorMinActiveThreshold', validatorMinActiveThreshold); - return { counterForNominators, counterForValidators, From e60c7b30d79e35a73b5c8af6930ab788ee5fd067 Mon Sep 17 00:00:00 2001 From: wirednkod Date: Mon, 21 Mar 2022 21:15:03 +0200 Subject: [PATCH 07/26] revert formatBalance --- packages/page-staking/src/useSortedTargets.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/page-staking/src/useSortedTargets.ts b/packages/page-staking/src/useSortedTargets.ts index f706b1ea8502..6984c4570302 100644 --- a/packages/page-staking/src/useSortedTargets.ts +++ b/packages/page-staking/src/useSortedTargets.ts @@ -13,7 +13,7 @@ import { useEffect, useMemo, useState } from 'react'; import { createNamedHook, useAccounts, useApi, useCall, useCallMulti, useInflation } from '@polkadot/react-hooks'; import { AccountId32 } from '@polkadot/types/interfaces'; import { PalletStakingIndividualExposure } from '@polkadot/types/lookup'; -import { arrayFlatten, BN, BN_HUNDRED, BN_MAX_INTEGER, BN_ONE, BN_ZERO } from '@polkadot/util'; +import { arrayFlatten, BN, BN_HUNDRED, BN_MAX_INTEGER, BN_ONE, BN_ZERO, formatBalance } from '@polkadot/util'; interface LastEra { activeEra: BN; @@ -247,7 +247,7 @@ function extractBaseInfo (api: ApiPromise, allAccounts: string[], electedDerive: }; } -const b = (x: BN, api: ApiPromise): string => api.createType('Balance', x).toHuman(); +const b = (x: BN, api: ApiPromise): string => formatBalance(api.createType('Balance', x)); function getMinActiveThreshold (api: ApiPromise, stakers: [StorageKey<[u32, AccountId32]>, Codec][]) { const assignments: Map = new Map(); From 40191388bfb82effaf64f35f35fdf37a11e96f20 Mon Sep 17 00:00:00 2001 From: wirednkod Date: Mon, 21 Mar 2022 21:46:23 +0200 Subject: [PATCH 08/26] fix code to show correctly the min active nominator stake --- packages/page-staking/src/Overview/Summary.tsx | 2 +- packages/page-staking/src/useSortedTargets.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/page-staking/src/Overview/Summary.tsx b/packages/page-staking/src/Overview/Summary.tsx index fcaceca75990..999297f487ae 100644 --- a/packages/page-staking/src/Overview/Summary.tsx +++ b/packages/page-staking/src/Overview/Summary.tsx @@ -27,7 +27,7 @@ function Summary ({ className = '', isVisible, targets: { inflation: { idealStak
{t('nominators')}
{t('min active stake')}
}> - {nominatorMinActiveThreshold === '0' ? : nominatorMinActiveThreshold} + {!nominatorMinActiveThreshold ? : nominatorMinActiveThreshold}
{t('validators')}
{t('min active stake')}
}> {validatorMinActiveThreshold === '' ? : validatorMinActiveThreshold} diff --git a/packages/page-staking/src/useSortedTargets.ts b/packages/page-staking/src/useSortedTargets.ts index 6984c4570302..5a8d1ee74663 100644 --- a/packages/page-staking/src/useSortedTargets.ts +++ b/packages/page-staking/src/useSortedTargets.ts @@ -250,23 +250,23 @@ function extractBaseInfo (api: ApiPromise, allAccounts: string[], electedDerive: const b = (x: BN, api: ApiPromise): string => formatBalance(api.createType('Balance', x)); function getMinActiveThreshold (api: ApiPromise, stakers: [StorageKey<[u32, AccountId32]>, Codec][]) { - const assignments: Map = new Map(); + const assignments: Map = new Map(); stakers.sort((a, b) => a[1].total.toBn().cmp(b[1].total.toBn())); stakers.map((x) => x[1].others).flat(1).forEach((x) => { - const nominator = (x as PalletStakingIndividualExposure).who; + const nominator = (x as PalletStakingIndividualExposure).who.toString(); const amount = (x as PalletStakingIndividualExposure).value; const val = assignments.get(nominator); assignments.set(nominator, val ? amount.toBn().add(val) : amount.toBn()); }); - const nominatorStakes = Array.from(assignments.values()); + const nominatorStakes = Array.from(assignments); - nominatorStakes.sort((a, b) => a.cmp(b)); + nominatorStakes.sort((a, b) => a[1].cmp(b[1])); - return b(nominatorStakes[0], api); + return nominatorStakes[0] && b(nominatorStakes[0][1], api); } const transformEra = { From 0fa8264db6aacadffaf51956eafdbb893e5b4b06 Mon Sep 17 00:00:00 2001 From: wirednkod Date: Mon, 21 Mar 2022 22:56:36 +0200 Subject: [PATCH 09/26] Add tooltip; Fix breaking lint --- .../apps/public/locales/en/app-staking.json | 2 ++ .../page-staking/src/Overview/Summary.tsx | 26 ++++++++++++++++--- packages/page-staking/src/useSortedTargets.ts | 11 ++++---- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/packages/apps/public/locales/en/app-staking.json b/packages/apps/public/locales/en/app-staking.json index d3ddd77b592b..3721bd7b2246 100644 --- a/packages/apps/public/locales/en/app-staking.json +++ b/packages/apps/public/locales/en/app-staking.json @@ -43,6 +43,8 @@ "Inject session keys (advanced)": "Inject session keys (advanced)", "Keys from rotateKeys": "Keys from rotateKeys", "Max, {{eras}} eras": "Max, {{eras}} eras", + "Minimum stake among the active nominators": "Minimum stake among the active nominators", + "Minimum (total) stake among the active validators.": "Minimum (total) stake among the active validators.", "Most profitable": "Most profitable", "Next session": "Next session", "No active validators found": "No active validators found", diff --git a/packages/page-staking/src/Overview/Summary.tsx b/packages/page-staking/src/Overview/Summary.tsx index 999297f487ae..42887e6af449 100644 --- a/packages/page-staking/src/Overview/Summary.tsx +++ b/packages/page-staking/src/Overview/Summary.tsx @@ -26,10 +26,26 @@ function Summary ({ className = '', isVisible, targets: { inflation: { idealStak return (
- {t('nominators')}
{t('min active stake')}
}> - {!nominatorMinActiveThreshold ? : nominatorMinActiveThreshold} + ('Minimum stake among the active nominators.')} + label={ +
+ {t('nominators')} +
{t('min active stake')}
+
+ } + > + {nominatorMinActiveThreshold === '' ? : nominatorMinActiveThreshold}
- {t('validators')}
{t('min active stake')}
}> + ('Minimum (total) stake among the active validators.')} + label={ +
+ {t('validators')} +
{t('min active stake')}
+
+ } + > {validatorMinActiveThreshold === '' ? : validatorMinActiveThreshold}
@@ -65,6 +81,10 @@ function Summary ({ className = '', isVisible, targets: { inflation: { idealStak } export default React.memo(styled(Summary)` + .label-floater { + float: left; + margin-bottom: 0.75rem; + } .validator--Account-block-icon { display: inline-block; margin-right: 0.75rem; diff --git a/packages/page-staking/src/useSortedTargets.ts b/packages/page-staking/src/useSortedTargets.ts index 5a8d1ee74663..3b9596110e6f 100644 --- a/packages/page-staking/src/useSortedTargets.ts +++ b/packages/page-staking/src/useSortedTargets.ts @@ -5,14 +5,13 @@ import type { ApiPromise } from '@polkadot/api'; import type { DeriveSessionInfo, DeriveStakingElected, DeriveStakingWaiting } from '@polkadot/api-derive/types'; import type { Inflation } from '@polkadot/react-hooks/types'; import type { Option, StorageKey, u32 } from '@polkadot/types'; -import type { Codec } from '@polkadot/types/types'; import type { SortedTargets, TargetSortBy, ValidatorInfo } from './types'; import { useEffect, useMemo, useState } from 'react'; import { createNamedHook, useAccounts, useApi, useCall, useCallMulti, useInflation } from '@polkadot/react-hooks'; import { AccountId32 } from '@polkadot/types/interfaces'; -import { PalletStakingIndividualExposure } from '@polkadot/types/lookup'; +import { PalletStakingExposure, PalletStakingIndividualExposure } from '@polkadot/types/lookup'; import { arrayFlatten, BN, BN_HUNDRED, BN_MAX_INTEGER, BN_ONE, BN_ZERO, formatBalance } from '@polkadot/util'; interface LastEra { @@ -249,7 +248,7 @@ function extractBaseInfo (api: ApiPromise, allAccounts: string[], electedDerive: const b = (x: BN, api: ApiPromise): string => formatBalance(api.createType('Balance', x)); -function getMinActiveThreshold (api: ApiPromise, stakers: [StorageKey<[u32, AccountId32]>, Codec][]) { +function getMinActiveThreshold (api: ApiPromise, stakers: [StorageKey<[u32, AccountId32]>, PalletStakingExposure][]) { const assignments: Map = new Map(); stakers.sort((a, b) => a[1].total.toBn().cmp(b[1].total.toBn())); @@ -266,7 +265,7 @@ function getMinActiveThreshold (api: ApiPromise, stakers: [StorageKey<[u32, Acco nominatorStakes.sort((a, b) => a[1].cmp(b[1])); - return nominatorStakes[0] && b(nominatorStakes[0][1], api); + return nominatorStakes[0] ? b(nominatorStakes[0][1], api) : ''; } const transformEra = { @@ -312,12 +311,12 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted const electedInfo = useCall(api.derive.staking.electedInfo, [{ ...DEFAULT_FLAGS_ELECTED, withLedger }]); const waitingInfo = useCall(api.derive.staking.waitingInfo, [{ ...DEFAULT_FLAGS_WAITING, withLedger }]); const lastEraInfo = useCall(api.derive.session.info, undefined, transformEra); - const [stakers, setStakers] = useState<[StorageKey<[u32, AccountId32]>, Codec][]>([]); + const [stakers, setStakers] = useState<[StorageKey<[u32, AccountId32]>, PalletStakingExposure][]>([]); const [stakersTotal, setStakersTotal] = useState(); useEffect(() => { if (stakers[0] && stakers[0][1]) { - setStakersTotal(stakers[0][1].total.toBn() as BN); + setStakersTotal(stakers[0][1].total.toBn()); } }, [stakers]); From 6ee2b5bb7c0d391f799c38cc741be23af3a2afcd Mon Sep 17 00:00:00 2001 From: wirednkod Date: Thu, 14 Apr 2022 11:42:56 +0300 Subject: [PATCH 10/26] Fix performance issue --- packages/page-staking/src/useSortedTargets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/page-staking/src/useSortedTargets.ts b/packages/page-staking/src/useSortedTargets.ts index 3b9596110e6f..82148dfa592b 100644 --- a/packages/page-staking/src/useSortedTargets.ts +++ b/packages/page-staking/src/useSortedTargets.ts @@ -343,7 +343,7 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted }, [api.query.staking.erasStakers]); curEra && getStakers(curEra?.unwrap()); - const nominatorMinActiveThreshold = getMinActiveThreshold(api, stakers); + const nominatorMinActiveThreshold = useMemo(() => getMinActiveThreshold(api, stakers), [api, stakers]); const validatorMinActiveThreshold = stakersTotal ? b(stakersTotal, api) : ''; return { From b635be395bc5866f0ec8ac4763c2d2bcbaef3f4e Mon Sep 17 00:00:00 2001 From: wirednkod Date: Thu, 14 Apr 2022 18:04:41 +0300 Subject: [PATCH 11/26] readdress performance --- packages/page-staking/src/useSortedTargets.ts | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/packages/page-staking/src/useSortedTargets.ts b/packages/page-staking/src/useSortedTargets.ts index 82148dfa592b..1c21c5ec3444 100644 --- a/packages/page-staking/src/useSortedTargets.ts +++ b/packages/page-staking/src/useSortedTargets.ts @@ -248,26 +248,6 @@ function extractBaseInfo (api: ApiPromise, allAccounts: string[], electedDerive: const b = (x: BN, api: ApiPromise): string => formatBalance(api.createType('Balance', x)); -function getMinActiveThreshold (api: ApiPromise, stakers: [StorageKey<[u32, AccountId32]>, PalletStakingExposure][]) { - const assignments: Map = new Map(); - - stakers.sort((a, b) => a[1].total.toBn().cmp(b[1].total.toBn())); - - stakers.map((x) => x[1].others).flat(1).forEach((x) => { - const nominator = (x as PalletStakingIndividualExposure).who.toString(); - const amount = (x as PalletStakingIndividualExposure).value; - const val = assignments.get(nominator); - - assignments.set(nominator, val ? amount.toBn().add(val) : amount.toBn()); - }); - - const nominatorStakes = Array.from(assignments); - - nominatorStakes.sort((a, b) => a[1].cmp(b[1])); - - return nominatorStakes[0] ? b(nominatorStakes[0][1], api) : ''; -} - const transformEra = { transform: ({ activeEra, eraLength, sessionLength }: DeriveSessionInfo): LastEra => ({ activeEra, @@ -313,6 +293,8 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted const lastEraInfo = useCall(api.derive.session.info, undefined, transformEra); const [stakers, setStakers] = useState<[StorageKey<[u32, AccountId32]>, PalletStakingExposure][]>([]); const [stakersTotal, setStakersTotal] = useState(); + const [nominatorMinActiveThreshold, setNominatorMinActiveThreshold] = useState(''); + const [calcStakers, setCalcStakers] = useState(false); useEffect(() => { if (stakers[0] && stakers[0][1]) { @@ -320,6 +302,27 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted } }, [stakers]); + useEffect(() => { + if (stakers.length && !calcStakers) { + const assignments: Map = new Map(); + + stakers.sort((a, b) => a[1].total.toBn().cmp(b[1].total.toBn())).map((x) => x[1].others).flat(1).forEach((x) => { + const nominator = (x as PalletStakingIndividualExposure).who.toString(); + const amount = (x as PalletStakingIndividualExposure).value; + const val = assignments.get(nominator); + + assignments.set(nominator, val ? amount.toBn().add(val) : amount.toBn()); + }); + + const nominatorStakes = Array.from(assignments); + + nominatorStakes.sort((a, b) => a[1].cmp(b[1])); + + setNominatorMinActiveThreshold(nominatorStakes[0] ? b(nominatorStakes[0][1], api) : ''); + setCalcStakers(true); + } + }, [api, calcStakers, stakers]); + const baseInfo = useMemo( () => electedInfo && lastEraInfo && totalIssuance && waitingInfo ? extractBaseInfo(api, allAccounts, electedInfo, waitingInfo, favorites, totalIssuance, lastEraInfo, historyDepth) @@ -343,7 +346,7 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted }, [api.query.staking.erasStakers]); curEra && getStakers(curEra?.unwrap()); - const nominatorMinActiveThreshold = useMemo(() => getMinActiveThreshold(api, stakers), [api, stakers]); + const validatorMinActiveThreshold = stakersTotal ? b(stakersTotal, api) : ''; return { From c80183903fb8d332f4768b15422930ca35c11b72 Mon Sep 17 00:00:00 2001 From: wirednkod Date: Tue, 19 Apr 2022 14:50:28 +0300 Subject: [PATCH 12/26] Seperate pages Overview/Validator --- .../src/Overview/ActionsBanner.tsx | 21 ++ .../src/Overview/Address/Favorite.tsx | 34 +++ .../src/Overview/Address/NominatedBy.tsx | 81 +++++++ .../src/Overview/Address/StakeOther.tsx | 89 ++++++++ .../src/Overview/Address/Status.tsx | 87 ++++++++ .../src/Overview/Address/index.tsx | 192 +++++++++++++++++ .../src/Overview/Address/types.ts | 9 + .../page-staking/src/Overview/CurrentList.tsx | 204 ++++++++++++++++++ .../src/{Validators => Overview}/Summary.tsx | 0 packages/page-staking/src/Overview/index.tsx | 44 ++++ packages/page-staking/src/Overview/types.ts | 7 + .../page-staking/src/Validators/index.tsx | 5 - packages/page-staking/src/index.tsx | 21 +- 13 files changed, 788 insertions(+), 6 deletions(-) create mode 100644 packages/page-staking/src/Overview/ActionsBanner.tsx create mode 100644 packages/page-staking/src/Overview/Address/Favorite.tsx create mode 100644 packages/page-staking/src/Overview/Address/NominatedBy.tsx create mode 100644 packages/page-staking/src/Overview/Address/StakeOther.tsx create mode 100644 packages/page-staking/src/Overview/Address/Status.tsx create mode 100644 packages/page-staking/src/Overview/Address/index.tsx create mode 100644 packages/page-staking/src/Overview/Address/types.ts create mode 100644 packages/page-staking/src/Overview/CurrentList.tsx rename packages/page-staking/src/{Validators => Overview}/Summary.tsx (100%) create mode 100644 packages/page-staking/src/Overview/index.tsx create mode 100644 packages/page-staking/src/Overview/types.ts diff --git a/packages/page-staking/src/Overview/ActionsBanner.tsx b/packages/page-staking/src/Overview/ActionsBanner.tsx new file mode 100644 index 000000000000..a03bd800b609 --- /dev/null +++ b/packages/page-staking/src/Overview/ActionsBanner.tsx @@ -0,0 +1,21 @@ +// Copyright 2017-2022 @polkadot/app-staking authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import { MarkWarning } from '@polkadot/react-components'; + +import { useTranslation } from '../translate'; + +function ActionsBanner (): React.ReactElement | null { + const { t } = useTranslation(); + + return ( + ('Use the account actions to create a new validator/nominator stash and bond it to participate in staking. Do not send funds directly via a transfer to a validator.')} + /> + ); +} + +export default React.memo(ActionsBanner); diff --git a/packages/page-staking/src/Overview/Address/Favorite.tsx b/packages/page-staking/src/Overview/Address/Favorite.tsx new file mode 100644 index 000000000000..e9e31d790e01 --- /dev/null +++ b/packages/page-staking/src/Overview/Address/Favorite.tsx @@ -0,0 +1,34 @@ +// Copyright 2017-2022 @polkadot/app-staking authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React, { useCallback } from 'react'; +import styled from 'styled-components'; + +import { Icon } from '@polkadot/react-components'; + +interface Props { + address: string; + className?: string; + isFavorite: boolean; + toggleFavorite: (accountId: string) => void; +} + +function Favorite ({ address, className, isFavorite, toggleFavorite }: Props): React.ReactElement { + const _onFavorite = useCallback( + () => toggleFavorite(address), + [address, toggleFavorite] + ); + + return ( + + ); +} + +export default React.memo(styled(Favorite)` + margin-right: 1rem; +`); diff --git a/packages/page-staking/src/Overview/Address/NominatedBy.tsx b/packages/page-staking/src/Overview/Address/NominatedBy.tsx new file mode 100644 index 000000000000..374d2941bb99 --- /dev/null +++ b/packages/page-staking/src/Overview/Address/NominatedBy.tsx @@ -0,0 +1,81 @@ +// Copyright 2017-2022 @polkadot/app-staking authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { SlashingSpans } from '@polkadot/types/interfaces'; +import type { NominatedBy as NominatedByType } from '../../types'; + +import React, { useMemo } from 'react'; + +import { AddressMini, Expander } from '@polkadot/react-components'; +import { formatNumber } from '@polkadot/util'; + +import { useTranslation } from '../../translate'; + +interface Props { + nominators?: NominatedByType[]; + slashingSpans?: SlashingSpans | null; +} + +interface Chilled { + active: null | [number, () => React.ReactNode[]]; + chilled: null | [number, () => React.ReactNode[]]; +} + +function extractFunction (all: string[]): null | [number, () => React.ReactNode[]] { + return all.length + ? [ + all.length, + () => all.map((who): React.ReactNode => + + ) + ] + : null; +} + +function extractChilled (nominators: NominatedByType[] = [], slashingSpans?: SlashingSpans | null): Chilled { + const chilled = slashingSpans + ? nominators + .filter(({ submittedIn }) => !slashingSpans.lastNonzeroSlash.isZero() && slashingSpans.lastNonzeroSlash.gte(submittedIn)) + .map(({ nominatorId }) => nominatorId) + : []; + + return { + active: extractFunction( + nominators + .filter(({ nominatorId }) => !chilled.includes(nominatorId)) + .map(({ nominatorId }) => nominatorId) + ), + chilled: extractFunction(chilled) + }; +} + +function NominatedBy ({ nominators, slashingSpans }: Props): React.ReactElement { + const { t } = useTranslation(); + + const { active, chilled } = useMemo( + () => extractChilled(nominators, slashingSpans), + [nominators, slashingSpans] + ); + + return ( + + {active && ( + ('Nominations ({{count}})', { replace: { count: formatNumber(active[0]) } })} + /> + )} + {chilled && ( + ('Renomination required ({{count}})', { replace: { count: formatNumber(chilled[0]) } })} + /> + )} + + ); +} + +export default React.memo(NominatedBy); diff --git a/packages/page-staking/src/Overview/Address/StakeOther.tsx b/packages/page-staking/src/Overview/Address/StakeOther.tsx new file mode 100644 index 000000000000..73be1dee00c6 --- /dev/null +++ b/packages/page-staking/src/Overview/Address/StakeOther.tsx @@ -0,0 +1,89 @@ +// Copyright 2017-2022 @polkadot/app-staking authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { NominatorValue } from './types'; + +import React, { useMemo } from 'react'; + +import { AddressMini, Expander } from '@polkadot/react-components'; +import { useApi } from '@polkadot/react-hooks'; +import { FormatBalance } from '@polkadot/react-query'; +import { BN, BN_ZERO } from '@polkadot/util'; + +interface Props { + stakeOther?: BN; + nominators: NominatorValue[]; +} + +function extractFunction (all: NominatorValue[]): null | [number, () => React.ReactNode[]] { + return all.length + ? [ + all.length, + () => all.map(({ nominatorId, value }): React.ReactNode => + + ) + ] + : null; +} + +function extractTotals (maxPaid: BN | undefined, nominators: NominatorValue[], stakeOther?: BN): [null | [number, () => React.ReactNode[]], BN, null | [number, () => React.ReactNode[]], BN] { + const sorted = nominators.sort((a, b) => b.value.cmp(a.value)); + + if (!maxPaid || maxPaid.gtn(sorted.length)) { + return [extractFunction(sorted), stakeOther || BN_ZERO, null, BN_ZERO]; + } + + const max = maxPaid.toNumber(); + const rewarded = sorted.slice(0, max); + const rewardedTotal = rewarded.reduce((total, { value }) => total.iadd(value), new BN(0)); + const unrewarded = sorted.slice(max); + const unrewardedTotal = unrewarded.reduce((total, { value }) => total.iadd(value), new BN(0)); + + return [extractFunction(rewarded), rewardedTotal, extractFunction(unrewarded), unrewardedTotal]; +} + +function StakeOther ({ nominators, stakeOther }: Props): React.ReactElement { + const { api } = useApi(); + + const [rewarded, rewardedTotal, unrewarded, unrewardedTotal] = useMemo( + () => extractTotals(api.consts.staking?.maxNominatorRewardedPerValidator, nominators, stakeOther), + [api, nominators, stakeOther] + ); + + return ( + + {rewarded && ( + <> + + } + /> + {unrewarded && ( + + } + /> + )} + + )} + + ); +} + +export default React.memo(StakeOther); diff --git a/packages/page-staking/src/Overview/Address/Status.tsx b/packages/page-staking/src/Overview/Address/Status.tsx new file mode 100644 index 000000000000..33bddcafe3c3 --- /dev/null +++ b/packages/page-staking/src/Overview/Address/Status.tsx @@ -0,0 +1,87 @@ +// Copyright 2017-2022 @polkadot/app-staking authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { BN } from '@polkadot/util'; + +import React, { useMemo } from 'react'; + +import { Badge, Icon } from '@polkadot/react-components'; +import { useAccounts } from '@polkadot/react-hooks'; + +import MaxBadge from '../../MaxBadge'; + +interface Props { + isChilled?: boolean; + isElected: boolean; + isMain?: boolean; + isPara?: boolean; + isRelay?: boolean; + nominators?: { nominatorId: string }[]; + onlineCount?: false | BN; + onlineMessage?: boolean; +} + +const NO_NOMS: { nominatorId: string }[] = []; + +function Status ({ isChilled, isElected, isMain, isPara, isRelay, nominators = NO_NOMS, onlineCount, onlineMessage }: Props): React.ReactElement { + const { allAccounts } = useAccounts(); + const blockCount = onlineCount && onlineCount.toNumber(); + + const isNominating = useMemo( + () => nominators.some(({ nominatorId }) => allAccounts.includes(nominatorId)), + [allAccounts, nominators] + ); + + return ( + <> + {isNominating + ? ( + + ) + : + } + {isRelay && ( + isPara + ? ( + + ) + : + )} + {isChilled + ? ( + + ) + : isElected + ? ( + + ) + : + } + {isMain && ( + blockCount || onlineMessage + ? ( + } + /> + ) + : + )} + + + ); +} + +export default React.memo(Status); diff --git a/packages/page-staking/src/Overview/Address/index.tsx b/packages/page-staking/src/Overview/Address/index.tsx new file mode 100644 index 000000000000..73cd2f9aac71 --- /dev/null +++ b/packages/page-staking/src/Overview/Address/index.tsx @@ -0,0 +1,192 @@ +// Copyright 2017-2022 @polkadot/app-staking authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { DeriveHeartbeatAuthor } from '@polkadot/api-derive/types'; +import type { Option } from '@polkadot/types'; +import type { SlashingSpans, ValidatorPrefs } from '@polkadot/types/interfaces'; +import type { BN } from '@polkadot/util'; +import type { NominatedBy as NominatedByType, ValidatorInfo } from '../../types'; +import type { NominatorValue } from './types'; + +import React, { useMemo } from 'react'; + +import { ApiPromise } from '@polkadot/api'; +import { AddressSmall, Icon, LinkExternal } from '@polkadot/react-components'; +import { checkVisibility } from '@polkadot/react-components/util'; +import { useApi, useCall, useDeriveAccountInfo } from '@polkadot/react-hooks'; +import { FormatBalance } from '@polkadot/react-query'; +import { BN_ZERO } from '@polkadot/util'; + +import Favorite from './Favorite'; +import NominatedBy from './NominatedBy'; +import StakeOther from './StakeOther'; +import Status from './Status'; + +interface Props { + address: string; + className?: string; + filterName: string; + hasQueries: boolean; + isElected: boolean; + isFavorite: boolean; + isMain?: boolean; + isPara?: boolean; + lastBlock?: string; + minCommission?: BN; + nominatedBy?: NominatedByType[]; + points?: string; + recentlyOnline?: DeriveHeartbeatAuthor; + toggleFavorite: (accountId: string) => void; + validatorInfo?: ValidatorInfo; + withIdentity?: boolean; +} + +interface StakingState { + isChilled?: boolean; + commission?: string; + nominators: NominatorValue[]; + stakeTotal?: BN; + stakeOther?: BN; + stakeOwn?: BN; +} + +function expandInfo ({ exposure, validatorPrefs }: ValidatorInfo, minCommission?: BN): StakingState { + let nominators: NominatorValue[] = []; + let stakeTotal: BN | undefined; + let stakeOther: BN | undefined; + let stakeOwn: BN | undefined; + + if (exposure && exposure.total) { + nominators = exposure.others.map(({ value, who }) => ({ nominatorId: who.toString(), value: value.unwrap() })); + stakeTotal = exposure.total?.unwrap() || BN_ZERO; + stakeOwn = exposure.own.unwrap(); + stakeOther = stakeTotal.sub(stakeOwn); + } + + const commission = (validatorPrefs as ValidatorPrefs)?.commission?.unwrap(); + + return { + commission: commission?.toHuman(), + isChilled: commission && minCommission && commission.isZero() && commission.lt(minCommission), + nominators, + stakeOther, + stakeOwn, + stakeTotal + }; +} + +const transformSlashes = { + transform: (opt: Option) => opt.unwrapOr(null) +}; + +function useAddressCalls (api: ApiPromise, address: string, isMain?: boolean) { + const params = useMemo(() => [address], [address]); + const accountInfo = useDeriveAccountInfo(address); + const slashingSpans = useCall(!isMain && api.query.staking.slashingSpans, params, transformSlashes); + + return { accountInfo, slashingSpans }; +} + +function Address ({ address, className = '', filterName, hasQueries, isElected, isFavorite, isMain, isPara, lastBlock, minCommission, nominatedBy, points, recentlyOnline, toggleFavorite, validatorInfo, withIdentity }: Props): React.ReactElement | null { + const { api } = useApi(); + const { accountInfo, slashingSpans } = useAddressCalls(api, address, isMain); + + const { commission, isChilled, nominators, stakeOther, stakeOwn } = useMemo( + () => validatorInfo + ? expandInfo(validatorInfo, minCommission) + : { nominators: [] }, + [minCommission, validatorInfo] + ); + + const isVisible = useMemo( + () => accountInfo ? checkVisibility(api, address, accountInfo, filterName, withIdentity) : true, + [api, accountInfo, address, filterName, withIdentity] + ); + + const statsLink = useMemo( + () => `#/staking/query/${address}`, + [address] + ); + + if (!isVisible) { + return null; + } + + return ( + + + + + + + + + {isMain + ? ( + + ) + : ( + + ) + } + {isMain && ( + + {stakeOwn?.gtn(0) && ( + + )} + + )} + + {commission} + + {isMain && ( + <> + + {points} + + + {lastBlock} + + + )} + + {hasQueries && ( + + + + )} + + + + + + ); +} + +export default React.memo(Address); diff --git a/packages/page-staking/src/Overview/Address/types.ts b/packages/page-staking/src/Overview/Address/types.ts new file mode 100644 index 000000000000..185812537800 --- /dev/null +++ b/packages/page-staking/src/Overview/Address/types.ts @@ -0,0 +1,9 @@ +// Copyright 2017-2022 @polkadot/app-staking authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { Balance } from '@polkadot/types/interfaces'; + +export interface NominatorValue { + nominatorId: string; + value: Balance; +} diff --git a/packages/page-staking/src/Overview/CurrentList.tsx b/packages/page-staking/src/Overview/CurrentList.tsx new file mode 100644 index 000000000000..3d7a4ef5fbf4 --- /dev/null +++ b/packages/page-staking/src/Overview/CurrentList.tsx @@ -0,0 +1,204 @@ +// Copyright 2017-2022 @polkadot/app-staking authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { DeriveHeartbeats, DeriveStakingOverview } from '@polkadot/api-derive/types'; +import type { Authors } from '@polkadot/react-query/BlockAuthors'; +import type { AccountId } from '@polkadot/types/interfaces'; +import type { BN } from '@polkadot/util'; +import type { NominatedByMap, SortedTargets, ValidatorInfo } from '../types'; + +import React, { useContext, useMemo, useRef, useState } from 'react'; + +import { Table } from '@polkadot/react-components'; +import { useApi, useLoadingDelay } from '@polkadot/react-hooks'; +import { BlockAuthorsContext } from '@polkadot/react-query'; + +import Filtering from '../Filtering'; +import Legend from '../Legend'; +import { useTranslation } from '../translate'; +import Address from './Address'; + +interface Props { + className?: string; + favorites: string[]; + hasQueries: boolean; + isIntentions?: boolean; + isIntentionsTrigger?: boolean; + isOwn: boolean; + minCommission?: BN; + nominatedBy?: NominatedByMap; + ownStashIds?: string[]; + paraValidators?: Record; + recentlyOnline?: DeriveHeartbeats; + setNominators?: (nominators: string[]) => void; + stakingOverview?: DeriveStakingOverview; + targets: SortedTargets; + toggleFavorite: (address: string) => void; +} + +type AccountExtend = [string, boolean, boolean]; + +interface Filtered { + validators?: AccountExtend[]; + waiting?: AccountExtend[]; +} + +const EmptyAuthorsContext: React.Context = React.createContext({ byAuthor: {}, eraPoints: {}, lastBlockAuthors: [], lastHeaders: [] }); + +function filterAccounts (isOwn: boolean, accounts: string[] = [], ownStashIds: string[] = [], elected: string[], favorites: string[], without: string[]): AccountExtend[] { + return accounts + .filter((accountId) => + !without.includes(accountId) && ( + !isOwn || + ownStashIds.includes(accountId) + ) + ) + .map((accountId): AccountExtend => [ + accountId, + elected.includes(accountId), + favorites.includes(accountId) + ]) + .sort(([accA,, isFavA]: AccountExtend, [accB,, isFavB]: AccountExtend): number => { + const isStashA = ownStashIds.includes(accA); + const isStashB = ownStashIds.includes(accB); + + return isFavA === isFavB + ? isStashA === isStashB + ? 0 + : (isStashA ? -1 : 1) + : (isFavA ? -1 : 1); + }); +} + +function accountsToString (accounts: AccountId[]): string[] { + return accounts.map((a) => a.toString()); +} + +function getFiltered (isOwn: boolean, stakingOverview: DeriveStakingOverview | undefined, favorites: string[], next?: string[], ownStashIds?: string[]): Filtered { + if (!stakingOverview) { + return {}; + } + + const allElected = accountsToString(stakingOverview.nextElected); + const validatorIds = accountsToString(stakingOverview.validators); + + return { + validators: filterAccounts(isOwn, validatorIds, ownStashIds, allElected, favorites, []), + waiting: filterAccounts(isOwn, allElected, ownStashIds, allElected, favorites, validatorIds).concat( + filterAccounts(isOwn, next, ownStashIds, [], favorites, allElected) + ) + }; +} + +const DEFAULT_PARAS = {}; + +function CurrentList ({ className, favorites, hasQueries, isIntentions, isOwn, minCommission, nominatedBy, ownStashIds, paraValidators = DEFAULT_PARAS, recentlyOnline, stakingOverview, targets, toggleFavorite }: Props): React.ReactElement | null { + const { t } = useTranslation(); + const { api } = useApi(); + const { byAuthor, eraPoints } = useContext(isIntentions ? EmptyAuthorsContext : BlockAuthorsContext); + const [nameFilter, setNameFilter] = useState(''); + + // we have a very large list, so we use a loading delay + const isLoading = useLoadingDelay(); + + const { validators, waiting } = useMemo( + () => getFiltered(isOwn, stakingOverview, favorites, targets.waitingIds, ownStashIds), + [favorites, isOwn, ownStashIds, stakingOverview, targets] + ); + + const list = useMemo( + () => isLoading + ? undefined + : isIntentions + ? nominatedBy && waiting + : validators, + [isIntentions, isLoading, nominatedBy, validators, waiting] + ); + + const infoMap = useMemo( + () => targets.validators && targets.validators.reduce>((result, info) => { + result[info.key] = info; + + return result; + }, {}), + [targets] + ); + + const headerRef = useRef( + isIntentions + ? [ + [t('intentions'), 'start', 2], + [t('nominators'), 'expand'], + [t('commission'), 'number'], + [], + [] + ] + : [ + [t('validators'), 'start', 2], + [t('other stake'), 'expand'], + [t('own stake'), 'media--1100'], + [t('commission')], + [t('points')], + [t('last #')], + [], + [undefined, 'media--1200'] + ] + ); + + return ( + ('No waiting validators found') + : list && recentlyOnline && infoMap && t('No active validators found') + } + emptySpinner={ + <> + {!waiting &&
{t('Retrieving validators')}
} + {!infoMap &&
{t('Retrieving validator info')}
} + {isIntentions + ? !nominatedBy &&
{t('Retrieving nominators')}
+ : !recentlyOnline &&
{t('Retrieving online status')}
+ } + {!list &&
{t('Preparing validator list')}
} + + } + filter={ + + } + header={headerRef.current} + legend={ + + } + > + {list && list.map(([address, isElected, isFavorite]): React.ReactNode => ( +
+ ))} +
+ ); +} + +export default React.memo(CurrentList); diff --git a/packages/page-staking/src/Validators/Summary.tsx b/packages/page-staking/src/Overview/Summary.tsx similarity index 100% rename from packages/page-staking/src/Validators/Summary.tsx rename to packages/page-staking/src/Overview/Summary.tsx diff --git a/packages/page-staking/src/Overview/index.tsx b/packages/page-staking/src/Overview/index.tsx new file mode 100644 index 000000000000..f33da4e7ba3c --- /dev/null +++ b/packages/page-staking/src/Overview/index.tsx @@ -0,0 +1,44 @@ +// Copyright 2017-2022 @polkadot/app-staking authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { DeriveStakingOverview } from '@polkadot/api-derive/types'; +import type { StakerState } from '@polkadot/react-hooks/types'; +import type { BN } from '@polkadot/util'; +import type { NominatedByMap, SortedTargets } from '../types'; + +import React, { useEffect } from 'react'; + +import Summary from './Summary'; + +interface Props { + className?: string; + favorites: string[]; + hasAccounts: boolean; + hasQueries: boolean; + minCommission?: BN; + nominatedBy?: NominatedByMap; + ownStashes?: StakerState[]; + paraValidators?: Record; + stakingOverview?: DeriveStakingOverview; + targets: SortedTargets; + toggleFavorite: (address: string) => void; + toggleLedger?: () => void; + toggleNominatedBy: () => void; +} + +function Overview ({ className = '', stakingOverview, targets, toggleLedger }: Props): React.ReactElement { + useEffect((): void => { + toggleLedger && toggleLedger(); + }, [toggleLedger]); + + return ( +
+ +
+ ); +} + +export default React.memo(Overview); diff --git a/packages/page-staking/src/Overview/types.ts b/packages/page-staking/src/Overview/types.ts new file mode 100644 index 000000000000..88fb59b67588 --- /dev/null +++ b/packages/page-staking/src/Overview/types.ts @@ -0,0 +1,7 @@ +// Copyright 2017-2022 @polkadot/app-staking authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +export interface AddressDetails { + address: string; + points?: number; +} diff --git a/packages/page-staking/src/Validators/index.tsx b/packages/page-staking/src/Validators/index.tsx index f0f7d99d8df4..085d0d34005e 100644 --- a/packages/page-staking/src/Validators/index.tsx +++ b/packages/page-staking/src/Validators/index.tsx @@ -14,7 +14,6 @@ import { useApi, useCall } from '@polkadot/react-hooks'; import { useTranslation } from '../translate'; import ActionsBanner from './ActionsBanner'; import CurrentList from './CurrentList'; -import Summary from './Summary'; interface Props { className?: string; @@ -70,10 +69,6 @@ function Overview ({ className = '', favorites, hasAccounts, hasQueries, minComm return (
- {hasAccounts && (ownStashes?.length === 0) && ( )} diff --git a/packages/page-staking/src/index.tsx b/packages/page-staking/src/index.tsx index aa8c8129d5ab..5a97c0909e9b 100644 --- a/packages/page-staking/src/index.tsx +++ b/packages/page-staking/src/index.tsx @@ -19,6 +19,7 @@ import basicMd from './md/basic.md'; import Actions from './Actions'; import Bags from './Bags'; import { STORE_FAVS_BASE } from './constants'; +import Overview from './Overview'; import Payouts from './Payouts'; import Pools from './Pools'; import Query from './Query'; @@ -95,6 +96,10 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement('Overview') }, + { + name: 'validators', + text: t('Validators') + }, { name: 'actions', text: t('Accounts') @@ -175,6 +180,20 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement + Date: Tue, 19 Apr 2022 15:21:56 +0300 Subject: [PATCH 13/26] Remove not-necessery components; Revert changes of SummaryBox --- .../src/Overview/ActionsBanner.tsx | 21 -- .../src/Overview/Address/Favorite.tsx | 34 --- .../src/Overview/Address/NominatedBy.tsx | 81 ------- .../src/Overview/Address/StakeOther.tsx | 89 -------- .../src/Overview/Address/Status.tsx | 87 -------- .../src/Overview/Address/index.tsx | 192 ----------------- .../src/Overview/Address/types.ts | 9 - .../page-staking/src/Overview/CurrentList.tsx | 204 ------------------ .../page-staking/src/Overview/Summary.tsx | 99 --------- packages/page-staking/src/Overview/index.tsx | 147 ++++++++++--- packages/page-staking/src/Overview/types.ts | 7 - 11 files changed, 123 insertions(+), 847 deletions(-) delete mode 100644 packages/page-staking/src/Overview/ActionsBanner.tsx delete mode 100644 packages/page-staking/src/Overview/Address/Favorite.tsx delete mode 100644 packages/page-staking/src/Overview/Address/NominatedBy.tsx delete mode 100644 packages/page-staking/src/Overview/Address/StakeOther.tsx delete mode 100644 packages/page-staking/src/Overview/Address/Status.tsx delete mode 100644 packages/page-staking/src/Overview/Address/index.tsx delete mode 100644 packages/page-staking/src/Overview/Address/types.ts delete mode 100644 packages/page-staking/src/Overview/CurrentList.tsx delete mode 100644 packages/page-staking/src/Overview/Summary.tsx delete mode 100644 packages/page-staking/src/Overview/types.ts diff --git a/packages/page-staking/src/Overview/ActionsBanner.tsx b/packages/page-staking/src/Overview/ActionsBanner.tsx deleted file mode 100644 index a03bd800b609..000000000000 --- a/packages/page-staking/src/Overview/ActionsBanner.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2017-2022 @polkadot/app-staking authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import React from 'react'; - -import { MarkWarning } from '@polkadot/react-components'; - -import { useTranslation } from '../translate'; - -function ActionsBanner (): React.ReactElement | null { - const { t } = useTranslation(); - - return ( - ('Use the account actions to create a new validator/nominator stash and bond it to participate in staking. Do not send funds directly via a transfer to a validator.')} - /> - ); -} - -export default React.memo(ActionsBanner); diff --git a/packages/page-staking/src/Overview/Address/Favorite.tsx b/packages/page-staking/src/Overview/Address/Favorite.tsx deleted file mode 100644 index e9e31d790e01..000000000000 --- a/packages/page-staking/src/Overview/Address/Favorite.tsx +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2017-2022 @polkadot/app-staking authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import React, { useCallback } from 'react'; -import styled from 'styled-components'; - -import { Icon } from '@polkadot/react-components'; - -interface Props { - address: string; - className?: string; - isFavorite: boolean; - toggleFavorite: (accountId: string) => void; -} - -function Favorite ({ address, className, isFavorite, toggleFavorite }: Props): React.ReactElement { - const _onFavorite = useCallback( - () => toggleFavorite(address), - [address, toggleFavorite] - ); - - return ( - - ); -} - -export default React.memo(styled(Favorite)` - margin-right: 1rem; -`); diff --git a/packages/page-staking/src/Overview/Address/NominatedBy.tsx b/packages/page-staking/src/Overview/Address/NominatedBy.tsx deleted file mode 100644 index 374d2941bb99..000000000000 --- a/packages/page-staking/src/Overview/Address/NominatedBy.tsx +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2017-2022 @polkadot/app-staking authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import type { SlashingSpans } from '@polkadot/types/interfaces'; -import type { NominatedBy as NominatedByType } from '../../types'; - -import React, { useMemo } from 'react'; - -import { AddressMini, Expander } from '@polkadot/react-components'; -import { formatNumber } from '@polkadot/util'; - -import { useTranslation } from '../../translate'; - -interface Props { - nominators?: NominatedByType[]; - slashingSpans?: SlashingSpans | null; -} - -interface Chilled { - active: null | [number, () => React.ReactNode[]]; - chilled: null | [number, () => React.ReactNode[]]; -} - -function extractFunction (all: string[]): null | [number, () => React.ReactNode[]] { - return all.length - ? [ - all.length, - () => all.map((who): React.ReactNode => - - ) - ] - : null; -} - -function extractChilled (nominators: NominatedByType[] = [], slashingSpans?: SlashingSpans | null): Chilled { - const chilled = slashingSpans - ? nominators - .filter(({ submittedIn }) => !slashingSpans.lastNonzeroSlash.isZero() && slashingSpans.lastNonzeroSlash.gte(submittedIn)) - .map(({ nominatorId }) => nominatorId) - : []; - - return { - active: extractFunction( - nominators - .filter(({ nominatorId }) => !chilled.includes(nominatorId)) - .map(({ nominatorId }) => nominatorId) - ), - chilled: extractFunction(chilled) - }; -} - -function NominatedBy ({ nominators, slashingSpans }: Props): React.ReactElement { - const { t } = useTranslation(); - - const { active, chilled } = useMemo( - () => extractChilled(nominators, slashingSpans), - [nominators, slashingSpans] - ); - - return ( - - {active && ( - ('Nominations ({{count}})', { replace: { count: formatNumber(active[0]) } })} - /> - )} - {chilled && ( - ('Renomination required ({{count}})', { replace: { count: formatNumber(chilled[0]) } })} - /> - )} - - ); -} - -export default React.memo(NominatedBy); diff --git a/packages/page-staking/src/Overview/Address/StakeOther.tsx b/packages/page-staking/src/Overview/Address/StakeOther.tsx deleted file mode 100644 index 73be1dee00c6..000000000000 --- a/packages/page-staking/src/Overview/Address/StakeOther.tsx +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2017-2022 @polkadot/app-staking authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import type { NominatorValue } from './types'; - -import React, { useMemo } from 'react'; - -import { AddressMini, Expander } from '@polkadot/react-components'; -import { useApi } from '@polkadot/react-hooks'; -import { FormatBalance } from '@polkadot/react-query'; -import { BN, BN_ZERO } from '@polkadot/util'; - -interface Props { - stakeOther?: BN; - nominators: NominatorValue[]; -} - -function extractFunction (all: NominatorValue[]): null | [number, () => React.ReactNode[]] { - return all.length - ? [ - all.length, - () => all.map(({ nominatorId, value }): React.ReactNode => - - ) - ] - : null; -} - -function extractTotals (maxPaid: BN | undefined, nominators: NominatorValue[], stakeOther?: BN): [null | [number, () => React.ReactNode[]], BN, null | [number, () => React.ReactNode[]], BN] { - const sorted = nominators.sort((a, b) => b.value.cmp(a.value)); - - if (!maxPaid || maxPaid.gtn(sorted.length)) { - return [extractFunction(sorted), stakeOther || BN_ZERO, null, BN_ZERO]; - } - - const max = maxPaid.toNumber(); - const rewarded = sorted.slice(0, max); - const rewardedTotal = rewarded.reduce((total, { value }) => total.iadd(value), new BN(0)); - const unrewarded = sorted.slice(max); - const unrewardedTotal = unrewarded.reduce((total, { value }) => total.iadd(value), new BN(0)); - - return [extractFunction(rewarded), rewardedTotal, extractFunction(unrewarded), unrewardedTotal]; -} - -function StakeOther ({ nominators, stakeOther }: Props): React.ReactElement { - const { api } = useApi(); - - const [rewarded, rewardedTotal, unrewarded, unrewardedTotal] = useMemo( - () => extractTotals(api.consts.staking?.maxNominatorRewardedPerValidator, nominators, stakeOther), - [api, nominators, stakeOther] - ); - - return ( - - {rewarded && ( - <> - - } - /> - {unrewarded && ( - - } - /> - )} - - )} - - ); -} - -export default React.memo(StakeOther); diff --git a/packages/page-staking/src/Overview/Address/Status.tsx b/packages/page-staking/src/Overview/Address/Status.tsx deleted file mode 100644 index 33bddcafe3c3..000000000000 --- a/packages/page-staking/src/Overview/Address/Status.tsx +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2017-2022 @polkadot/app-staking authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import type { BN } from '@polkadot/util'; - -import React, { useMemo } from 'react'; - -import { Badge, Icon } from '@polkadot/react-components'; -import { useAccounts } from '@polkadot/react-hooks'; - -import MaxBadge from '../../MaxBadge'; - -interface Props { - isChilled?: boolean; - isElected: boolean; - isMain?: boolean; - isPara?: boolean; - isRelay?: boolean; - nominators?: { nominatorId: string }[]; - onlineCount?: false | BN; - onlineMessage?: boolean; -} - -const NO_NOMS: { nominatorId: string }[] = []; - -function Status ({ isChilled, isElected, isMain, isPara, isRelay, nominators = NO_NOMS, onlineCount, onlineMessage }: Props): React.ReactElement { - const { allAccounts } = useAccounts(); - const blockCount = onlineCount && onlineCount.toNumber(); - - const isNominating = useMemo( - () => nominators.some(({ nominatorId }) => allAccounts.includes(nominatorId)), - [allAccounts, nominators] - ); - - return ( - <> - {isNominating - ? ( - - ) - : - } - {isRelay && ( - isPara - ? ( - - ) - : - )} - {isChilled - ? ( - - ) - : isElected - ? ( - - ) - : - } - {isMain && ( - blockCount || onlineMessage - ? ( - } - /> - ) - : - )} - - - ); -} - -export default React.memo(Status); diff --git a/packages/page-staking/src/Overview/Address/index.tsx b/packages/page-staking/src/Overview/Address/index.tsx deleted file mode 100644 index 73cd2f9aac71..000000000000 --- a/packages/page-staking/src/Overview/Address/index.tsx +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright 2017-2022 @polkadot/app-staking authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import type { DeriveHeartbeatAuthor } from '@polkadot/api-derive/types'; -import type { Option } from '@polkadot/types'; -import type { SlashingSpans, ValidatorPrefs } from '@polkadot/types/interfaces'; -import type { BN } from '@polkadot/util'; -import type { NominatedBy as NominatedByType, ValidatorInfo } from '../../types'; -import type { NominatorValue } from './types'; - -import React, { useMemo } from 'react'; - -import { ApiPromise } from '@polkadot/api'; -import { AddressSmall, Icon, LinkExternal } from '@polkadot/react-components'; -import { checkVisibility } from '@polkadot/react-components/util'; -import { useApi, useCall, useDeriveAccountInfo } from '@polkadot/react-hooks'; -import { FormatBalance } from '@polkadot/react-query'; -import { BN_ZERO } from '@polkadot/util'; - -import Favorite from './Favorite'; -import NominatedBy from './NominatedBy'; -import StakeOther from './StakeOther'; -import Status from './Status'; - -interface Props { - address: string; - className?: string; - filterName: string; - hasQueries: boolean; - isElected: boolean; - isFavorite: boolean; - isMain?: boolean; - isPara?: boolean; - lastBlock?: string; - minCommission?: BN; - nominatedBy?: NominatedByType[]; - points?: string; - recentlyOnline?: DeriveHeartbeatAuthor; - toggleFavorite: (accountId: string) => void; - validatorInfo?: ValidatorInfo; - withIdentity?: boolean; -} - -interface StakingState { - isChilled?: boolean; - commission?: string; - nominators: NominatorValue[]; - stakeTotal?: BN; - stakeOther?: BN; - stakeOwn?: BN; -} - -function expandInfo ({ exposure, validatorPrefs }: ValidatorInfo, minCommission?: BN): StakingState { - let nominators: NominatorValue[] = []; - let stakeTotal: BN | undefined; - let stakeOther: BN | undefined; - let stakeOwn: BN | undefined; - - if (exposure && exposure.total) { - nominators = exposure.others.map(({ value, who }) => ({ nominatorId: who.toString(), value: value.unwrap() })); - stakeTotal = exposure.total?.unwrap() || BN_ZERO; - stakeOwn = exposure.own.unwrap(); - stakeOther = stakeTotal.sub(stakeOwn); - } - - const commission = (validatorPrefs as ValidatorPrefs)?.commission?.unwrap(); - - return { - commission: commission?.toHuman(), - isChilled: commission && minCommission && commission.isZero() && commission.lt(minCommission), - nominators, - stakeOther, - stakeOwn, - stakeTotal - }; -} - -const transformSlashes = { - transform: (opt: Option) => opt.unwrapOr(null) -}; - -function useAddressCalls (api: ApiPromise, address: string, isMain?: boolean) { - const params = useMemo(() => [address], [address]); - const accountInfo = useDeriveAccountInfo(address); - const slashingSpans = useCall(!isMain && api.query.staking.slashingSpans, params, transformSlashes); - - return { accountInfo, slashingSpans }; -} - -function Address ({ address, className = '', filterName, hasQueries, isElected, isFavorite, isMain, isPara, lastBlock, minCommission, nominatedBy, points, recentlyOnline, toggleFavorite, validatorInfo, withIdentity }: Props): React.ReactElement | null { - const { api } = useApi(); - const { accountInfo, slashingSpans } = useAddressCalls(api, address, isMain); - - const { commission, isChilled, nominators, stakeOther, stakeOwn } = useMemo( - () => validatorInfo - ? expandInfo(validatorInfo, minCommission) - : { nominators: [] }, - [minCommission, validatorInfo] - ); - - const isVisible = useMemo( - () => accountInfo ? checkVisibility(api, address, accountInfo, filterName, withIdentity) : true, - [api, accountInfo, address, filterName, withIdentity] - ); - - const statsLink = useMemo( - () => `#/staking/query/${address}`, - [address] - ); - - if (!isVisible) { - return null; - } - - return ( - - - - - - - - - {isMain - ? ( - - ) - : ( - - ) - } - {isMain && ( - - {stakeOwn?.gtn(0) && ( - - )} - - )} - - {commission} - - {isMain && ( - <> - - {points} - - - {lastBlock} - - - )} - - {hasQueries && ( - - - - )} - - - - - - ); -} - -export default React.memo(Address); diff --git a/packages/page-staking/src/Overview/Address/types.ts b/packages/page-staking/src/Overview/Address/types.ts deleted file mode 100644 index 185812537800..000000000000 --- a/packages/page-staking/src/Overview/Address/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2017-2022 @polkadot/app-staking authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import type { Balance } from '@polkadot/types/interfaces'; - -export interface NominatorValue { - nominatorId: string; - value: Balance; -} diff --git a/packages/page-staking/src/Overview/CurrentList.tsx b/packages/page-staking/src/Overview/CurrentList.tsx deleted file mode 100644 index 3d7a4ef5fbf4..000000000000 --- a/packages/page-staking/src/Overview/CurrentList.tsx +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright 2017-2022 @polkadot/app-staking authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import type { DeriveHeartbeats, DeriveStakingOverview } from '@polkadot/api-derive/types'; -import type { Authors } from '@polkadot/react-query/BlockAuthors'; -import type { AccountId } from '@polkadot/types/interfaces'; -import type { BN } from '@polkadot/util'; -import type { NominatedByMap, SortedTargets, ValidatorInfo } from '../types'; - -import React, { useContext, useMemo, useRef, useState } from 'react'; - -import { Table } from '@polkadot/react-components'; -import { useApi, useLoadingDelay } from '@polkadot/react-hooks'; -import { BlockAuthorsContext } from '@polkadot/react-query'; - -import Filtering from '../Filtering'; -import Legend from '../Legend'; -import { useTranslation } from '../translate'; -import Address from './Address'; - -interface Props { - className?: string; - favorites: string[]; - hasQueries: boolean; - isIntentions?: boolean; - isIntentionsTrigger?: boolean; - isOwn: boolean; - minCommission?: BN; - nominatedBy?: NominatedByMap; - ownStashIds?: string[]; - paraValidators?: Record; - recentlyOnline?: DeriveHeartbeats; - setNominators?: (nominators: string[]) => void; - stakingOverview?: DeriveStakingOverview; - targets: SortedTargets; - toggleFavorite: (address: string) => void; -} - -type AccountExtend = [string, boolean, boolean]; - -interface Filtered { - validators?: AccountExtend[]; - waiting?: AccountExtend[]; -} - -const EmptyAuthorsContext: React.Context = React.createContext({ byAuthor: {}, eraPoints: {}, lastBlockAuthors: [], lastHeaders: [] }); - -function filterAccounts (isOwn: boolean, accounts: string[] = [], ownStashIds: string[] = [], elected: string[], favorites: string[], without: string[]): AccountExtend[] { - return accounts - .filter((accountId) => - !without.includes(accountId) && ( - !isOwn || - ownStashIds.includes(accountId) - ) - ) - .map((accountId): AccountExtend => [ - accountId, - elected.includes(accountId), - favorites.includes(accountId) - ]) - .sort(([accA,, isFavA]: AccountExtend, [accB,, isFavB]: AccountExtend): number => { - const isStashA = ownStashIds.includes(accA); - const isStashB = ownStashIds.includes(accB); - - return isFavA === isFavB - ? isStashA === isStashB - ? 0 - : (isStashA ? -1 : 1) - : (isFavA ? -1 : 1); - }); -} - -function accountsToString (accounts: AccountId[]): string[] { - return accounts.map((a) => a.toString()); -} - -function getFiltered (isOwn: boolean, stakingOverview: DeriveStakingOverview | undefined, favorites: string[], next?: string[], ownStashIds?: string[]): Filtered { - if (!stakingOverview) { - return {}; - } - - const allElected = accountsToString(stakingOverview.nextElected); - const validatorIds = accountsToString(stakingOverview.validators); - - return { - validators: filterAccounts(isOwn, validatorIds, ownStashIds, allElected, favorites, []), - waiting: filterAccounts(isOwn, allElected, ownStashIds, allElected, favorites, validatorIds).concat( - filterAccounts(isOwn, next, ownStashIds, [], favorites, allElected) - ) - }; -} - -const DEFAULT_PARAS = {}; - -function CurrentList ({ className, favorites, hasQueries, isIntentions, isOwn, minCommission, nominatedBy, ownStashIds, paraValidators = DEFAULT_PARAS, recentlyOnline, stakingOverview, targets, toggleFavorite }: Props): React.ReactElement | null { - const { t } = useTranslation(); - const { api } = useApi(); - const { byAuthor, eraPoints } = useContext(isIntentions ? EmptyAuthorsContext : BlockAuthorsContext); - const [nameFilter, setNameFilter] = useState(''); - - // we have a very large list, so we use a loading delay - const isLoading = useLoadingDelay(); - - const { validators, waiting } = useMemo( - () => getFiltered(isOwn, stakingOverview, favorites, targets.waitingIds, ownStashIds), - [favorites, isOwn, ownStashIds, stakingOverview, targets] - ); - - const list = useMemo( - () => isLoading - ? undefined - : isIntentions - ? nominatedBy && waiting - : validators, - [isIntentions, isLoading, nominatedBy, validators, waiting] - ); - - const infoMap = useMemo( - () => targets.validators && targets.validators.reduce>((result, info) => { - result[info.key] = info; - - return result; - }, {}), - [targets] - ); - - const headerRef = useRef( - isIntentions - ? [ - [t('intentions'), 'start', 2], - [t('nominators'), 'expand'], - [t('commission'), 'number'], - [], - [] - ] - : [ - [t('validators'), 'start', 2], - [t('other stake'), 'expand'], - [t('own stake'), 'media--1100'], - [t('commission')], - [t('points')], - [t('last #')], - [], - [undefined, 'media--1200'] - ] - ); - - return ( - ('No waiting validators found') - : list && recentlyOnline && infoMap && t('No active validators found') - } - emptySpinner={ - <> - {!waiting &&
{t('Retrieving validators')}
} - {!infoMap &&
{t('Retrieving validator info')}
} - {isIntentions - ? !nominatedBy &&
{t('Retrieving nominators')}
- : !recentlyOnline &&
{t('Retrieving online status')}
- } - {!list &&
{t('Preparing validator list')}
} - - } - filter={ - - } - header={headerRef.current} - legend={ - - } - > - {list && list.map(([address, isElected, isFavorite]): React.ReactNode => ( -
- ))} -
- ); -} - -export default React.memo(CurrentList); diff --git a/packages/page-staking/src/Overview/Summary.tsx b/packages/page-staking/src/Overview/Summary.tsx deleted file mode 100644 index 597c829393bc..000000000000 --- a/packages/page-staking/src/Overview/Summary.tsx +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2017-2022 @polkadot/app-staking authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import type { DeriveStakingOverview } from '@polkadot/api-derive/types'; -import type { SortedTargets } from '../types'; - -import React from 'react'; -import styled from 'styled-components'; - -import SummarySession from '@polkadot/app-explorer/SummarySession'; -import { CardSummary, Spinner, SummaryBox } from '@polkadot/react-components'; - -import { useTranslation } from '../translate'; - -interface Props { - className?: string; - nominators?: string[]; - stakingOverview?: DeriveStakingOverview; - targets: SortedTargets; -} - -function Summary ({ className = '', targets: { inflation: { idealStake, inflation, stakedFraction }, nominatorMinActiveThreshold, validatorMinActiveThreshold } }: Props): React.ReactElement { - const { t } = useTranslation(); - - return ( - -
- ('Minimum stake among the active nominators.')} - label={ -
- {t('nominators')} -
{t('min active stake')}
-
- } - > - {nominatorMinActiveThreshold === '' ? : nominatorMinActiveThreshold} -
- ('Minimum (total) stake among the active validators.')} - label={ -
- {t('validators')} -
{t('min active stake')}
-
- } - > - {validatorMinActiveThreshold === '' ? : validatorMinActiveThreshold} -
-
- {(idealStake > 0) && Number.isFinite(idealStake) && ( - ('ideal staked')} - > - <>{(idealStake * 100).toFixed(1)}% - - )} - {(stakedFraction > 0) && ( - ('staked')} - > - <>{(stakedFraction * 100).toFixed(1)}% - - )} - {(inflation > 0) && Number.isFinite(inflation) && ( - ('inflation')} - > - <>{inflation.toFixed(1)}% - - )} -
- -
-
- ); -} - -export default React.memo(styled(Summary)` - .label-floater { - float: left; - margin-bottom: 0.75rem; - } - .validator--Account-block-icon { - display: inline-block; - margin-right: 0.75rem; - margin-top: -0.25rem; - vertical-align: middle; - } - - .validator--Summary-authors { - .validator--Account-block-icon+.validator--Account-block-icon { - margin-left: -1.5rem; - } - } -`); diff --git a/packages/page-staking/src/Overview/index.tsx b/packages/page-staking/src/Overview/index.tsx index f33da4e7ba3c..910298d68fc5 100644 --- a/packages/page-staking/src/Overview/index.tsx +++ b/packages/page-staking/src/Overview/index.tsx @@ -2,43 +2,142 @@ // SPDX-License-Identifier: Apache-2.0 import type { DeriveStakingOverview } from '@polkadot/api-derive/types'; -import type { StakerState } from '@polkadot/react-hooks/types'; -import type { BN } from '@polkadot/util'; -import type { NominatedByMap, SortedTargets } from '../types'; +import type { SortedTargets } from '../types'; -import React, { useEffect } from 'react'; +import React from 'react'; +import styled from 'styled-components'; -import Summary from './Summary'; +import SummarySession from '@polkadot/app-explorer/SummarySession'; +import { CardSummary, Spinner, SummaryBox } from '@polkadot/react-components'; +import { formatNumber } from '@polkadot/util'; + +import { useTranslation } from '../translate'; interface Props { className?: string; - favorites: string[]; - hasAccounts: boolean; - hasQueries: boolean; - minCommission?: BN; - nominatedBy?: NominatedByMap; - ownStashes?: StakerState[]; - paraValidators?: Record; + nominators?: string[]; stakingOverview?: DeriveStakingOverview; targets: SortedTargets; - toggleFavorite: (address: string) => void; - toggleLedger?: () => void; - toggleNominatedBy: () => void; } -function Overview ({ className = '', stakingOverview, targets, toggleLedger }: Props): React.ReactElement { - useEffect((): void => { - toggleLedger && toggleLedger(); - }, [toggleLedger]); +function Overview ({ className = '', stakingOverview, targets: { counterForNominators, inflation: { idealStake, inflation, stakedFraction }, nominatorMinActiveThreshold, nominators, validatorMinActiveThreshold, waitingIds } }: Props): React.ReactElement { + const { t } = useTranslation(); return (
- + +
+ ('validators')}> + {stakingOverview + ? <>{formatNumber(stakingOverview.validators.length)} / {formatNumber(stakingOverview.validatorCount)} + : + } + + ('waiting')} + > + {waitingIds + ? formatNumber(waitingIds.length) + : + } + + ('active / nominators') + : t('nominators') + } + > + {nominators + ? ( + <> + {formatNumber(nominators.length)} + {counterForNominators && ( + <> / {formatNumber(counterForNominators)} + )} + + ) + : + } + +
+
+ {(idealStake > 0) && Number.isFinite(idealStake) && ( + ('ideal staked')} + > + <>{(idealStake * 100).toFixed(1)}% + + )} + {(stakedFraction > 0) && ( + ('staked')} + > + <>{(stakedFraction * 100).toFixed(1)}% + + )} + {(inflation > 0) && Number.isFinite(inflation) && ( + ('inflation')} + > + <>{inflation.toFixed(1)}% + + )} +
+
+ +
+
+ +
+ ('Minimum stake among the active nominators.')} + label={ +
+ {t('nominators')} +
{t('min active stake')}
+
+ } + > + {nominatorMinActiveThreshold === '' ? : nominatorMinActiveThreshold} +
+ ('Minimum (total) stake among the active validators.')} + label={ +
+ {t('validators')} +
{t('min active stake')}
+
+ } + > + {validatorMinActiveThreshold === '' ? : validatorMinActiveThreshold} +
+
+
); } -export default React.memo(Overview); +export default React.memo(styled(Overview)` + .label-floater { + float: left; + margin-bottom: 0.75rem; + } + .validator--Account-block-icon { + display: inline-block; + margin-right: 0.75rem; + margin-top: -0.25rem; + vertical-align: middle; + } + + .validator--Summary-authors { + .validator--Account-block-icon+.validator--Account-block-icon { + margin-left: -1.5rem; + } + } +`); diff --git a/packages/page-staking/src/Overview/types.ts b/packages/page-staking/src/Overview/types.ts deleted file mode 100644 index 88fb59b67588..000000000000 --- a/packages/page-staking/src/Overview/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2017-2022 @polkadot/app-staking authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -export interface AddressDetails { - address: string; - points?: number; -} From 8099177de099a63254564758953fe82f09b11c1c Mon Sep 17 00:00:00 2001 From: wirednkod Date: Thu, 21 Apr 2022 17:27:33 +0300 Subject: [PATCH 14/26] Finalize Overview page by concentrating Summaries --- packages/page-staking/src/Bags/Summary.tsx | 57 ------- packages/page-staking/src/Bags/index.tsx | 5 - .../page-staking/src/Overview/SpinnerWrap.tsx | 22 +++ .../page-staking/src/Overview/SummaryBags.tsx | 59 +++++++ .../src/Overview/SummaryGeneral.tsx | 100 ++++++++++++ .../src/Overview/SummaryNominators.tsx | 93 +++++++++++ .../src/Overview/SummaryValidators.tsx | 105 +++++++++++++ packages/page-staking/src/Overview/index.tsx | 133 ++++------------ packages/page-staking/src/Targets/Summary.tsx | 147 ------------------ packages/page-staking/src/Targets/index.tsx | 13 -- packages/page-staking/src/types.ts | 6 + packages/page-staking/src/useSortedTargets.ts | 10 ++ 12 files changed, 423 insertions(+), 327 deletions(-) delete mode 100644 packages/page-staking/src/Bags/Summary.tsx create mode 100644 packages/page-staking/src/Overview/SpinnerWrap.tsx create mode 100644 packages/page-staking/src/Overview/SummaryBags.tsx create mode 100644 packages/page-staking/src/Overview/SummaryGeneral.tsx create mode 100644 packages/page-staking/src/Overview/SummaryNominators.tsx create mode 100644 packages/page-staking/src/Overview/SummaryValidators.tsx delete mode 100644 packages/page-staking/src/Targets/Summary.tsx diff --git a/packages/page-staking/src/Bags/Summary.tsx b/packages/page-staking/src/Bags/Summary.tsx deleted file mode 100644 index 089b852687d3..000000000000 --- a/packages/page-staking/src/Bags/Summary.tsx +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2017-2022 @polkadot/app-staking authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import type { BN } from '@polkadot/util'; -import type { BagMap } from './types'; - -import React, { useMemo } from 'react'; - -import { CardSummary, Spinner, SummaryBox } from '@polkadot/react-components'; -import { useApi, useCall } from '@polkadot/react-hooks'; -import { formatNumber, isNumber } from '@polkadot/util'; - -import { useTranslation } from '../translate'; - -interface Props { - bags?: unknown[]; - className?: string; - mapOwn?: BagMap; -} - -function Summary ({ bags, className = '', mapOwn }: Props): React.ReactElement { - const { t } = useTranslation(); - const { api } = useApi(); - const total = useCall(api.query.bagsList.counterForListNodes); - - const myCount = useMemo( - () => mapOwn && Object.values(mapOwn).reduce((count, n) => count + n.length, 0), - [mapOwn] - ); - - return ( - - ('total bags')}> - {bags - ? formatNumber(bags.length) - : - } - -
- ('total nodes')}> - {mapOwn - ? formatNumber(total) - : - } - - ('my nodes')}> - {isNumber(myCount) - ? formatNumber(myCount) - : '-' - } - -
-
- ); -} - -export default React.memo(Summary); diff --git a/packages/page-staking/src/Bags/index.tsx b/packages/page-staking/src/Bags/index.tsx index 52d157c719cd..5db6d0612204 100644 --- a/packages/page-staking/src/Bags/index.tsx +++ b/packages/page-staking/src/Bags/index.tsx @@ -10,7 +10,6 @@ import { Button, Table, ToggleGroup } from '@polkadot/react-components'; import { useTranslation } from '../translate'; import Bag from './Bag'; -import Summary from './Summary'; import useBagsList from './useBagsList'; import useBagsNodes from './useBagsNodes'; @@ -61,10 +60,6 @@ function Bags ({ ownStashes }: Props): React.ReactElement { return ( <> - > +} + +function SpinnerWrap ({ check, children }: Props): React.ReactElement { + return check + ? children + : (); +} + +export default React.memo(styled(SpinnerWrap)` + margin-top: 1rem; +`); diff --git a/packages/page-staking/src/Overview/SummaryBags.tsx b/packages/page-staking/src/Overview/SummaryBags.tsx new file mode 100644 index 000000000000..4936e8cdfb04 --- /dev/null +++ b/packages/page-staking/src/Overview/SummaryBags.tsx @@ -0,0 +1,59 @@ +// Copyright 2017-2022 @polkadot/app-staking authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { BN } from '@polkadot/util'; + +import React, { useMemo } from 'react'; + +import { Card, CardSummary, SummaryBox } from '@polkadot/react-components'; +import { useApi, useCall } from '@polkadot/react-hooks'; +import { StakerState } from '@polkadot/react-hooks/types'; +import { formatNumber } from '@polkadot/util'; + +import useBagsList from '../Bags/useBagsList'; +import useBagsNodes from '../Bags/useBagsNodes'; +import { useTranslation } from '../translate'; +import { Section, Title } from './index'; +import SpinnerWrap from './SpinnerWrap'; + +interface Props { + ownStashes?: StakerState[]; +} + +function SummaryBags ({ ownStashes }: Props) { + const { t } = useTranslation(); + const { api } = useApi(); + const stashIds = useMemo( + () => ownStashes + ? ownStashes.map(({ stashId }) => stashId) + : [], + [ownStashes] + ); + const mapOwn = useBagsNodes(stashIds); + const bags = useBagsList(); + const total = useCall(api.query.bagsList.counterForListNodes); + + return ( + + {t<string>('bags')} + +
+ ('total bags')}> + + {formatNumber(bags?.length)} + + +
+
+ ('total nodes')}> + + {formatNumber(total)} + + +
+
+
+ ); +} + +export default SummaryBags; diff --git a/packages/page-staking/src/Overview/SummaryGeneral.tsx b/packages/page-staking/src/Overview/SummaryGeneral.tsx new file mode 100644 index 000000000000..2d2b92f25dc6 --- /dev/null +++ b/packages/page-staking/src/Overview/SummaryGeneral.tsx @@ -0,0 +1,100 @@ +// Copyright 2017-2022 @polkadot/app-staking authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { BN } from '@polkadot/util'; + +import React, { useMemo } from 'react'; + +import SummarySession from '@polkadot/app-explorer/SummarySession'; +import { Card, CardSummary, SummaryBox } from '@polkadot/react-components'; +import { FormatBalance } from '@polkadot/react-query'; + +import { useTranslation } from '../translate'; +import { SortedTargets } from '../types'; +import { Section } from './index'; +import SpinnerWrap from './SpinnerWrap'; + +interface Props { + targets: SortedTargets; +} + +interface ProgressInfo { + hideValue: true; + total: BN; + value: BN; +} + +function getProgressInfo (value?: BN, total?: BN): ProgressInfo | undefined { + return value && total && !total.isZero() + ? { + hideValue: true, + total, + value + } + : undefined; +} + +function SummaryGeneral ({ targets: { inflation: { idealStake, + inflation, + stakedReturn }, +totalIssuance, +totalStaked } }: Props) { + const { t } = useTranslation(); + + const progressStake = useMemo( + () => getProgressInfo(totalStaked, totalIssuance), + [totalIssuance, totalStaked] + ); + + return ( + + +
+ +
+
+ ('total staked')} + progress={progressStake} + > + + + + +
+
+ ('ideal staked')} + > + 0) && Number.isFinite(idealStake)} + > + {(idealStake * 100).toFixed(1)}% + + + ('inflation')} + > + 0) && Number.isFinite(inflation)} + >{inflation.toFixed(1)}% + + ('returns')}> + 0) && Number.isFinite(stakedReturn)} + > + {stakedReturn.toFixed(1)}% + + +
+
+
+ ); +} + +export default SummaryGeneral; diff --git a/packages/page-staking/src/Overview/SummaryNominators.tsx b/packages/page-staking/src/Overview/SummaryNominators.tsx new file mode 100644 index 000000000000..0ab675335d2f --- /dev/null +++ b/packages/page-staking/src/Overview/SummaryNominators.tsx @@ -0,0 +1,93 @@ +// Copyright 2017-2022 @polkadot/app-staking authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import { Card, CardSummary, SummaryBox } from '@polkadot/react-components'; +import { FormatBalance } from '@polkadot/react-query'; +import { formatNumber } from '@polkadot/util'; + +import { useTranslation } from '../translate'; +import { SortedTargets } from '../types'; +import { Section, Title } from './index'; +import SpinnerWrap from './SpinnerWrap'; + +interface Props { + targets: SortedTargets; +} + +function SummaryNominators ({ targets: { maxNominatorsCount, + minNominatorBond, + nominatorActiveCount, + nominatorElectingCount, + nominatorMinActiveThreshold } }: Props) { + const { t } = useTranslation(); + + return ( + + {t<string>('nominators')} + +
+ ('Total count of nominators.')} + label={t('total')} + > + + {formatNumber(maxNominatorsCount?.toNumber())} + + +
+
+ ('Electing count of nominators.')} + label={t('electing')} + > + + {formatNumber(nominatorElectingCount)} + + +
+
+ ('Active count of nominators.')} + label={t('active')} + > + + {formatNumber(nominatorActiveCount)} + + +
+
+ +
+ ('Threshold stake among intended nominators.')} + label={t('intention thrs')} + > + + + + +
+
+ ('Minimum threshold stake among active nominators.')} + label={t('min active thrs')} + > + + {nominatorMinActiveThreshold} + + +
+
+ {/** Average Stake of Active Nominators? */} +
+
+
+ ); +} + +export default SummaryNominators; diff --git a/packages/page-staking/src/Overview/SummaryValidators.tsx b/packages/page-staking/src/Overview/SummaryValidators.tsx new file mode 100644 index 000000000000..20508221be1f --- /dev/null +++ b/packages/page-staking/src/Overview/SummaryValidators.tsx @@ -0,0 +1,105 @@ +// Copyright 2017-2022 @polkadot/app-staking authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import { Card, CardSummary, SummaryBox } from '@polkadot/react-components'; +import { FormatBalance } from '@polkadot/react-query'; +import { formatNumber } from '@polkadot/util'; + +import { useTranslation } from '../translate'; +import { SortedTargets } from '../types'; +import { Section, Title } from './index'; +import SpinnerWrap from './SpinnerWrap'; + +interface Props { + targets: SortedTargets; +} + +function SummaryValidators ({ targets: + { avgStaked, + maxValidatorsCount, + minValidatorBond, + validatorActiveCount, + validatorMinActiveThreshold, + waitingIds } }: Props) { + const { t } = useTranslation(); + + return ( + + {t<string>('validators')} + +
+ ('Count of total validators.')} + label={t('total')} + > + + {maxValidatorsCount?.toNumber()} + + +
+
+ ('Count of waiting validators.')} + label={t('waiting')} + > + + {formatNumber(waitingIds?.length)} + + +
+
+ ('Count of active validators.')} + label={t('active')} + > + + {validatorActiveCount} + + +
+
+ +
+ ('Threshold stake among intended validators.')} + label={t('intention thrs')} + > + + + + +
+
+ ('Minimum threshold stake among active validators.')} + label={t('min active thrs')} + > + + {validatorMinActiveThreshold} + + +
+
+ ('Average stake among active validators.')} + label={t('average active stake')} + > + + + + +
+
+
+ ); +} + +export default SummaryValidators; diff --git a/packages/page-staking/src/Overview/index.tsx b/packages/page-staking/src/Overview/index.tsx index 910298d68fc5..cc1f03615708 100644 --- a/packages/page-staking/src/Overview/index.tsx +++ b/packages/page-staking/src/Overview/index.tsx @@ -2,131 +2,54 @@ // SPDX-License-Identifier: Apache-2.0 import type { DeriveStakingOverview } from '@polkadot/api-derive/types'; +import type { StakerState } from '@polkadot/react-hooks/types'; import type { SortedTargets } from '../types'; import React from 'react'; import styled from 'styled-components'; -import SummarySession from '@polkadot/app-explorer/SummarySession'; -import { CardSummary, Spinner, SummaryBox } from '@polkadot/react-components'; -import { formatNumber } from '@polkadot/util'; - -import { useTranslation } from '../translate'; +import SummaryBags from './SummaryBags'; +import SummaryGeneral from './SummaryGeneral'; +import SummaryNominators from './SummaryNominators'; +import SummaryValidators from './SummaryValidators'; interface Props { className?: string; nominators?: string[]; + ownStashes?: StakerState[]; stakingOverview?: DeriveStakingOverview; targets: SortedTargets; } -function Overview ({ className = '', stakingOverview, targets: { counterForNominators, inflation: { idealStake, inflation, stakedFraction }, nominatorMinActiveThreshold, nominators, validatorMinActiveThreshold, waitingIds } }: Props): React.ReactElement { - const { t } = useTranslation(); - +function Overview ({ className = '', + ownStashes, + targets }: Props): React.ReactElement { return (
- -
- ('validators')}> - {stakingOverview - ? <>{formatNumber(stakingOverview.validators.length)} / {formatNumber(stakingOverview.validatorCount)} - : - } - - ('waiting')} - > - {waitingIds - ? formatNumber(waitingIds.length) - : - } - - ('active / nominators') - : t('nominators') - } - > - {nominators - ? ( - <> - {formatNumber(nominators.length)} - {counterForNominators && ( - <> / {formatNumber(counterForNominators)} - )} - - ) - : - } - -
-
- {(idealStake > 0) && Number.isFinite(idealStake) && ( - ('ideal staked')} - > - <>{(idealStake * 100).toFixed(1)}% - - )} - {(stakedFraction > 0) && ( - ('staked')} - > - <>{(stakedFraction * 100).toFixed(1)}% - - )} - {(inflation > 0) && Number.isFinite(inflation) && ( - ('inflation')} - > - <>{inflation.toFixed(1)}% - - )} -
-
- -
-
- -
- ('Minimum stake among the active nominators.')} - label={ -
- {t('nominators')} -
{t('min active stake')}
-
- } - > - {nominatorMinActiveThreshold === '' ? : nominatorMinActiveThreshold} -
- ('Minimum (total) stake among the active validators.')} - label={ -
- {t('validators')} -
{t('min active stake')}
-
- } - > - {validatorMinActiveThreshold === '' ? : validatorMinActiveThreshold} -
-
-
+ + + +
); } +export const Title = styled.div` + text-align: left; + text-transform: lowercase; + margin: 0 0 20px; + font-weight: 400; + font-size: 15px; +`; + +export const Section = styled.section` + width: 33%; + justify-content: center; +`; + export default React.memo(styled(Overview)` - .label-floater { - float: left; - margin-bottom: 0.75rem; + article { + justify-content: center; } .validator--Account-block-icon { display: inline-block; diff --git a/packages/page-staking/src/Targets/Summary.tsx b/packages/page-staking/src/Targets/Summary.tsx deleted file mode 100644 index 3cfbe7c6d907..000000000000 --- a/packages/page-staking/src/Targets/Summary.tsx +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2017-2022 @polkadot/app-staking authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import type { Option } from '@polkadot/types'; -import type { Balance } from '@polkadot/types/interfaces'; -import type { BN } from '@polkadot/util'; - -import React, { useMemo } from 'react'; - -import { CardSummary, SummaryBox } from '@polkadot/react-components'; -import { useApi, useCall } from '@polkadot/react-hooks'; -import { FormatBalance } from '@polkadot/react-query'; -import { BN_ZERO } from '@polkadot/util'; - -import { useTranslation } from '../translate'; - -interface Props { - avgStaked?: BN; - lastEra?: BN; - lowStaked?: BN; - minNominated?: BN; - minNominatorBond?: BN; - numNominators?: number; - numValidators?: number; - stakedReturn: number; - totalIssuance?: BN; - totalStaked?: BN; -} - -interface ProgressInfo { - hideValue: true; - total: BN; - value: BN; -} - -const OPT_REWARD = { - transform: (optBalance: Option) => - optBalance.unwrapOrDefault() -}; - -function getProgressInfo (value?: BN, total?: BN): ProgressInfo | undefined { - return value && total && !total.isZero() - ? { - hideValue: true, - total, - value - } - : undefined; -} - -function Summary ({ avgStaked, lastEra, lowStaked, minNominated, minNominatorBond, stakedReturn, totalIssuance, totalStaked }: Props): React.ReactElement { - const { t } = useTranslation(); - const { api } = useApi(); - const lastReward = useCall(lastEra && api.query.staking.erasValidatorReward, [lastEra], OPT_REWARD); - - const progressStake = useMemo( - () => getProgressInfo(totalStaked, totalIssuance), - [totalIssuance, totalStaked] - ); - - const progressAvg = useMemo( - () => getProgressInfo(lowStaked, avgStaked), - [avgStaked, lowStaked] - ); - - return ( - -
- {progressStake && ( - ('total staked')} - progress={progressStake} - > - - - )} -
-
- {totalIssuance && (stakedReturn > 0) && Number.isFinite(stakedReturn) && ( - ('returns')}> - {stakedReturn.toFixed(1)}% - - )} -
-
- {progressAvg && ( - ('lowest / avg staked')}`} - progress={progressAvg} - > - -  /  - - - )} -
-
- {minNominated?.gt(BN_ZERO) && ( - ('min nominated / threshold') - : t('min nominated')} - > - - {minNominatorBond && ( - <> -  /  - - - )} - - )} -
-
- {lastReward?.gt(BN_ZERO) && ( - ('last reward')}> - - - )} -
-
- ); -} - -export default React.memo(Summary); diff --git a/packages/page-staking/src/Targets/index.tsx b/packages/page-staking/src/Targets/index.tsx index c369e70df312..26a5f17863fc 100644 --- a/packages/page-staking/src/Targets/index.tsx +++ b/packages/page-staking/src/Targets/index.tsx @@ -21,7 +21,6 @@ import Legend from '../Legend'; import { useTranslation } from '../translate'; import useIdentities from '../useIdentities'; import Nominate from './Nominate'; -import Summary from './Summary'; import useOwnNominators from './useOwnNominators'; import Validator from './Validator'; @@ -360,18 +359,6 @@ function Targets ({ className = '', isInElection, nominatedBy, ownStashes, targe return (
-
+ ); } From 0af248ee0caf39494d47389f4e03dac074f40e11 Mon Sep 17 00:00:00 2001 From: wirednkod Date: Fri, 22 Apr 2022 10:38:06 +0300 Subject: [PATCH 16/26] Add condition rendering for bags Card in Overview --- packages/page-staking/src/Overview/index.tsx | 9 ++++++++- packages/page-staking/src/index.tsx | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/page-staking/src/Overview/index.tsx b/packages/page-staking/src/Overview/index.tsx index cc1f03615708..4d160d7208d6 100644 --- a/packages/page-staking/src/Overview/index.tsx +++ b/packages/page-staking/src/Overview/index.tsx @@ -8,6 +8,9 @@ import type { SortedTargets } from '../types'; import React from 'react'; import styled from 'styled-components'; +import { useApi } from '@polkadot/react-hooks'; +import { isFunction } from '@polkadot/util'; + import SummaryBags from './SummaryBags'; import SummaryGeneral from './SummaryGeneral'; import SummaryNominators from './SummaryNominators'; @@ -19,17 +22,21 @@ interface Props { ownStashes?: StakerState[]; stakingOverview?: DeriveStakingOverview; targets: SortedTargets; + hasStashes?: boolean } function Overview ({ className = '', + hasStashes, ownStashes, targets }: Props): React.ReactElement { + const { api } = useApi(); + return (
- + {hasStashes && isFunction(api.query.bagsList?.counterForListNodes) && }
); } diff --git a/packages/page-staking/src/index.tsx b/packages/page-staking/src/index.tsx index 5a97c0909e9b..70fabdd3ea11 100644 --- a/packages/page-staking/src/index.tsx +++ b/packages/page-staking/src/index.tsx @@ -185,6 +185,7 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement Date: Tue, 17 May 2022 12:32:34 +0300 Subject: [PATCH 17/26] Update translation string --- packages/page-staking/src/Overview/SummaryNominators.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/page-staking/src/Overview/SummaryNominators.tsx b/packages/page-staking/src/Overview/SummaryNominators.tsx index 0ab675335d2f..d9897befeb21 100644 --- a/packages/page-staking/src/Overview/SummaryNominators.tsx +++ b/packages/page-staking/src/Overview/SummaryNominators.tsx @@ -29,8 +29,8 @@ function SummaryNominators ({ targets: { maxNominatorsCount,
('Total count of nominators.')} - label={t('total')} + help={t('Maximum number of nominator intentions.')} + label={t('maximum')} > {formatNumber(maxNominatorsCount?.toNumber())} @@ -39,7 +39,7 @@ function SummaryNominators ({ targets: { maxNominatorsCount,
('Electing count of nominators.')} + help={t('Number of electing nominators.')} label={t('electing')} > From e7038c83e7229f78c80e2d8a0880cad4ed42ace7 Mon Sep 17 00:00:00 2001 From: wirednkod Date: Tue, 17 May 2022 12:48:08 +0300 Subject: [PATCH 18/26] Address more PR comments --- packages/page-staking/src/Overview/SummaryNominators.tsx | 4 ++-- packages/page-staking/src/Overview/SummaryValidators.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/page-staking/src/Overview/SummaryNominators.tsx b/packages/page-staking/src/Overview/SummaryNominators.tsx index d9897befeb21..822c340d4c89 100644 --- a/packages/page-staking/src/Overview/SummaryNominators.tsx +++ b/packages/page-staking/src/Overview/SummaryNominators.tsx @@ -49,7 +49,7 @@ function SummaryNominators ({ targets: { maxNominatorsCount,
('Active count of nominators.')} + help={t('Number of nominators backing active validators in the current era.')} label={t('active')} > @@ -61,7 +61,7 @@ function SummaryNominators ({ targets: { maxNominatorsCount,
('Threshold stake among intended nominators.')} + help={t('Threshold stake to intend nomination.')} label={t('intention thrs')} > diff --git a/packages/page-staking/src/Overview/SummaryValidators.tsx b/packages/page-staking/src/Overview/SummaryValidators.tsx index 20508221be1f..18934cc7f310 100644 --- a/packages/page-staking/src/Overview/SummaryValidators.tsx +++ b/packages/page-staking/src/Overview/SummaryValidators.tsx @@ -31,8 +31,8 @@ function SummaryValidators ({ targets:
('Count of total validators.')} - label={t('total')} + help={t('Maximum number of validator intentions.')} + label={t('max intention')} > {maxValidatorsCount?.toNumber()} From fc5ba1529067e318cb07840cbf8144bcbb7f80cc Mon Sep 17 00:00:00 2001 From: wirednkod Date: Tue, 17 May 2022 16:36:06 +0300 Subject: [PATCH 19/26] Correct the max electing nominators --- packages/page-staking/src/Overview/SummaryNominators.tsx | 6 +++--- packages/page-staking/src/types.ts | 1 + packages/page-staking/src/useSortedTargets.ts | 4 ++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/page-staking/src/Overview/SummaryNominators.tsx b/packages/page-staking/src/Overview/SummaryNominators.tsx index 822c340d4c89..f65a68c76127 100644 --- a/packages/page-staking/src/Overview/SummaryNominators.tsx +++ b/packages/page-staking/src/Overview/SummaryNominators.tsx @@ -19,7 +19,7 @@ interface Props { function SummaryNominators ({ targets: { maxNominatorsCount, minNominatorBond, nominatorActiveCount, - nominatorElectingCount, + nominatorMaxElectingCount, nominatorMinActiveThreshold } }: Props) { const { t } = useTranslation(); @@ -42,8 +42,8 @@ function SummaryNominators ({ targets: { maxNominatorsCount, help={t('Number of electing nominators.')} label={t('electing')} > - - {formatNumber(nominatorElectingCount)} + + {formatNumber(nominatorMaxElectingCount)}
diff --git a/packages/page-staking/src/types.ts b/packages/page-staking/src/types.ts index 54ac9c699941..01f0b7a390ca 100644 --- a/packages/page-staking/src/types.ts +++ b/packages/page-staking/src/types.ts @@ -102,6 +102,7 @@ export interface SortedTargets { validatorIntentionCount?: number; validatorWaitingCount?: number; nominatorMinActiveThreshold?: string; + nominatorMaxElectingCount?: u32; validatorMinActiveThreshold?: string; } diff --git a/packages/page-staking/src/useSortedTargets.ts b/packages/page-staking/src/useSortedTargets.ts index 8d2f099ba8d3..9091eb536c0a 100644 --- a/packages/page-staking/src/useSortedTargets.ts +++ b/packages/page-staking/src/useSortedTargets.ts @@ -297,6 +297,7 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted const [stakers, setStakers] = useState<[StorageKey<[u32, AccountId32]>, PalletStakingExposure][]>([]); const [stakersTotal, setStakersTotal] = useState(); const [nominatorMinActiveThreshold, setNominatorMinActiveThreshold] = useState(''); + const [nominatorMaxElectingCount, setNominatorMaxElectingCount] = useState(); const [nominatorElectingCount, setNominatorElectingCount] = useState(); const [nominatorActiveCount, setNominatorActiveCount] = useState(); const [validatorActiveCount, setValidatorActiveCount] = useState(); @@ -325,6 +326,8 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted nominatorStakes.sort((a, b) => a[1].cmp(b[1])); + setNominatorMaxElectingCount(api.consts.electionProviderMultiPhase.maxElectingVoters); + setNominatorElectingCount(assignments.size); setNominatorActiveCount(assignments.size); setNominatorMinActiveThreshold(nominatorStakes[0] ? b(nominatorStakes[0][1], api) : ''); @@ -372,6 +375,7 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted ...partial, nominatorActiveCount, nominatorElectingCount, + nominatorMaxElectingCount, nominatorMinActiveThreshold, validatorActiveCount, validatorMinActiveThreshold From 7347707dc49891ce66e514ea578dbde18c6c29a4 Mon Sep 17 00:00:00 2001 From: wirednkod Date: Tue, 17 May 2022 21:20:32 +0300 Subject: [PATCH 20/26] Add notice for nominators --- .../apps/public/locales/it/translation.json | 1 + .../apps/public/locales/ru/translation.json | 1 + .../src/Overview/SummaryNominators.tsx | 140 ++++++++++-------- 3 files changed, 78 insertions(+), 64 deletions(-) diff --git a/packages/apps/public/locales/it/translation.json b/packages/apps/public/locales/it/translation.json index 9397632d09a2..e11c25c446d0 100644 --- a/packages/apps/public/locales/it/translation.json +++ b/packages/apps/public/locales/it/translation.json @@ -320,6 +320,7 @@ "Image": "Carica codice", "Important notice": "Notifica importante", "In calculating the election outcome, this prioritized vote ordering will be used to determine the final score for the candidates.": "In fase di calcolo dell'esito dell'elezione, la priorità delle preferenze assegnata sarà usata per determinare il punteggio finale di ogni candidato", + "In order to receive staking rewards, and be exposed with an active validator to slash, a nominator needs to be 'active'. Compare the below thresholds with your active stake to ensure you are in the correct set between 'active' and 'intention'.": "", "Inactive": "Inattivo", "Inactive nominations ({{count}})": "Nomine inattive ({{count}})", "Include an optional tip for faster processing": "Includi una mancia per velocizzare la finalizzazione", diff --git a/packages/apps/public/locales/ru/translation.json b/packages/apps/public/locales/ru/translation.json index eec8bc575767..af41b7d671f8 100644 --- a/packages/apps/public/locales/ru/translation.json +++ b/packages/apps/public/locales/ru/translation.json @@ -318,6 +318,7 @@ "Image": "", "Important notice": "Важное уведомление", "In calculating the election outcome, this prioritized vote ordering will be used to determine the final score for the candidates.": "Для подсчета результатов голосования будет использовано приоритезированное голосование", + "In order to receive staking rewards, and be exposed with an active validator to slash, a nominator needs to be 'active'. Compare the below thresholds with your active stake to ensure you are in the correct set between 'active' and 'intention'.": "", "Inactive": "", "Inactive nominations ({{count}})": "Неактивные номинации ({{count}})", "Include an optional tip for faster processing": "Добавить чаевые для приоритетной обработки", diff --git a/packages/page-staking/src/Overview/SummaryNominators.tsx b/packages/page-staking/src/Overview/SummaryNominators.tsx index f65a68c76127..582a44ae5e55 100644 --- a/packages/page-staking/src/Overview/SummaryNominators.tsx +++ b/packages/page-staking/src/Overview/SummaryNominators.tsx @@ -3,7 +3,7 @@ import React from 'react'; -import { Card, CardSummary, SummaryBox } from '@polkadot/react-components'; +import { Card, CardSummary, MarkWarning, SummaryBox } from '@polkadot/react-components'; import { FormatBalance } from '@polkadot/react-query'; import { formatNumber } from '@polkadot/util'; @@ -24,69 +24,81 @@ function SummaryNominators ({ targets: { maxNominatorsCount, const { t } = useTranslation(); return ( - - {t<string>('nominators')} - -
- ('Maximum number of nominator intentions.')} - label={t('maximum')} - > - - {formatNumber(maxNominatorsCount?.toNumber())} - - -
-
- ('Number of electing nominators.')} - label={t('electing')} - > - - {formatNumber(nominatorMaxElectingCount)} - - -
-
- ('Number of nominators backing active validators in the current era.')} - label={t('active')} - > - - {formatNumber(nominatorActiveCount)} - - -
-
- -
- ('Threshold stake to intend nomination.')} - label={t('intention thrs')} - > - - - - -
-
- ('Minimum threshold stake among active nominators.')} - label={t('min active thrs')} - > - - {nominatorMinActiveThreshold} - - -
-
- {/** Average Stake of Active Nominators? */} -
-
-
+ <> + ('In order to receive staking rewards, and be exposed with an active validator to slash, a nominator needs to be "active". Compare the below thresholds with your active stake to ensure you are in the correct set between "active" and "intention".')} + withIcon={false} + > + Learn More + + + {t<string>('nominators')} + +
+ ('Maximum number of nominator intentions.')} + label={t('maximum')} + > + + {formatNumber(maxNominatorsCount?.toNumber())} + + +
+
+ ('Number of electing nominators.')} + label={t('electing')} + > + + {formatNumber(nominatorMaxElectingCount)} + + +
+
+ ('Number of nominators backing active validators in the current era.')} + label={t('active')} + > + + {formatNumber(nominatorActiveCount)} + + +
+
+ +
+ ('Threshold stake to intend nomination.')} + label={t('intention thrs')} + > + + + + +
+
+ ('Minimum threshold stake among active nominators.')} + label={t('min active thrs')} + > + + {nominatorMinActiveThreshold} + + +
+
+ {/** Average Stake of Active Nominators? */} +
+
+
+ ); } From 157b58bd6f3468dafc986bdd3c4d085e64ee3ef3 Mon Sep 17 00:00:00 2001 From: Nikos Kontakis Date: Tue, 24 May 2022 12:55:20 +0300 Subject: [PATCH 21/26] move notification inside nominator's panel --- .../src/Overview/SummaryNominators.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/page-staking/src/Overview/SummaryNominators.tsx b/packages/page-staking/src/Overview/SummaryNominators.tsx index 582a44ae5e55..b60b525d5684 100644 --- a/packages/page-staking/src/Overview/SummaryNominators.tsx +++ b/packages/page-staking/src/Overview/SummaryNominators.tsx @@ -25,16 +25,6 @@ function SummaryNominators ({ targets: { maxNominatorsCount, return ( <> - ('In order to receive staking rewards, and be exposed with an active validator to slash, a nominator needs to be "active". Compare the below thresholds with your active stake to ensure you are in the correct set between "active" and "intention".')} - withIcon={false} - > - Learn More - {t<string>('nominators')} @@ -97,6 +87,16 @@ function SummaryNominators ({ targets: { maxNominatorsCount, {/** Average Stake of Active Nominators? */}
+ ('In order to receive staking rewards, and be exposed with an active validator to slash, a nominator needs to be "active". Compare the below thresholds with your active stake to ensure you are in the correct set between "active" and "intention".')} + withIcon={false} + > + Learn More + ); From 113eb7d8b7b0d1b183b4d6e3fa5ecd028ccafbc8 Mon Sep 17 00:00:00 2001 From: Nikos Kontakis Date: Tue, 24 May 2022 13:04:38 +0300 Subject: [PATCH 22/26] satisfy linter --- packages/page-staking/src/Targets/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/page-staking/src/Targets/index.tsx b/packages/page-staking/src/Targets/index.tsx index 26a5f17863fc..132f8c36d0f2 100644 --- a/packages/page-staking/src/Targets/index.tsx +++ b/packages/page-staking/src/Targets/index.tsx @@ -191,7 +191,7 @@ const DEFAULT_NAME = { isQueryFiltered: false, nameFilter: '' }; const DEFAULT_SORT: SortState = { sortBy: 'rankOverall', sortFromMax: true }; -function Targets ({ className = '', isInElection, nominatedBy, ownStashes, targets: { avgStaked, inflation: { stakedReturn }, lastEra, lowStaked, medianComm, minNominated, minNominatorBond, nominators, totalIssuance, totalStaked, validatorIds, validators }, toggleFavorite, toggleLedger, toggleNominatedBy }: Props): React.ReactElement { +function Targets ({ className = '', isInElection, nominatedBy, ownStashes, targets: { medianComm, validatorIds, validators }, toggleFavorite, toggleLedger, toggleNominatedBy }: Props): React.ReactElement { const { t } = useTranslation(); const { api } = useApi(); const allSlashes = useAvailableSlashes(); From b046c484f1bba322874e9899ab3f993c7d29e9ff Mon Sep 17 00:00:00 2001 From: Nikos Kontakis Date: Tue, 24 May 2022 13:13:43 +0300 Subject: [PATCH 23/26] satisfy linter --- .../src/Overview/SummaryNominators.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/page-staking/src/Overview/SummaryNominators.tsx b/packages/page-staking/src/Overview/SummaryNominators.tsx index b60b525d5684..af71f20dd48d 100644 --- a/packages/page-staking/src/Overview/SummaryNominators.tsx +++ b/packages/page-staking/src/Overview/SummaryNominators.tsx @@ -88,15 +88,15 @@ function SummaryNominators ({ targets: { maxNominatorsCount,
('In order to receive staking rewards, and be exposed with an active validator to slash, a nominator needs to be "active". Compare the below thresholds with your active stake to ensure you are in the correct set between "active" and "intention".')} - withIcon={false} - > - Learn More - + content={t('In order to receive staking rewards, and be exposed with an active validator to slash, a nominator needs to be "active". Compare the below thresholds with your active stake to ensure you are in the correct set between "active" and "intention".')} + withIcon={false} + > + Learn More + ); From cada3daaaddea80dc82c182e66865476d3e808c2 Mon Sep 17 00:00:00 2001 From: wirednkod Date: Wed, 15 Jun 2022 11:33:49 +0300 Subject: [PATCH 24/26] Correct staking page for older staking networks --- packages/page-staking/src/Overview/SummaryNominators.tsx | 8 +++++--- packages/page-staking/src/useSortedTargets.ts | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/page-staking/src/Overview/SummaryNominators.tsx b/packages/page-staking/src/Overview/SummaryNominators.tsx index 582a44ae5e55..68e295196420 100644 --- a/packages/page-staking/src/Overview/SummaryNominators.tsx +++ b/packages/page-staking/src/Overview/SummaryNominators.tsx @@ -53,9 +53,11 @@ function SummaryNominators ({ targets: { maxNominatorsCount, help={t('Number of electing nominators.')} label={t('electing')} > - - {formatNumber(nominatorMaxElectingCount)} - + {nominatorMaxElectingCount === null + ? <>- + : + {formatNumber(nominatorMaxElectingCount)} + }
diff --git a/packages/page-staking/src/useSortedTargets.ts b/packages/page-staking/src/useSortedTargets.ts index 9091eb536c0a..9722684a1894 100644 --- a/packages/page-staking/src/useSortedTargets.ts +++ b/packages/page-staking/src/useSortedTargets.ts @@ -297,7 +297,7 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted const [stakers, setStakers] = useState<[StorageKey<[u32, AccountId32]>, PalletStakingExposure][]>([]); const [stakersTotal, setStakersTotal] = useState(); const [nominatorMinActiveThreshold, setNominatorMinActiveThreshold] = useState(''); - const [nominatorMaxElectingCount, setNominatorMaxElectingCount] = useState(); + const [nominatorMaxElectingCount, setNominatorMaxElectingCount] = useState(); const [nominatorElectingCount, setNominatorElectingCount] = useState(); const [nominatorActiveCount, setNominatorActiveCount] = useState(); const [validatorActiveCount, setValidatorActiveCount] = useState(); @@ -326,7 +326,7 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted nominatorStakes.sort((a, b) => a[1].cmp(b[1])); - setNominatorMaxElectingCount(api.consts.electionProviderMultiPhase.maxElectingVoters); + setNominatorMaxElectingCount(api.consts.electionProviderMultiPhase?.maxElectingVoters || null); setNominatorElectingCount(assignments.size); setNominatorActiveCount(assignments.size); From c3122b2a0ca909796f77f0574f4a7333ffc3a8de Mon Sep 17 00:00:00 2001 From: wirednkod Date: Wed, 22 Jun 2022 11:04:41 +0300 Subject: [PATCH 25/26] Address issues on Docker PoS Mainnet --- .../src/Overview/SummaryNominators.tsx | 37 ++++++++++++------- .../src/Overview/SummaryValidators.tsx | 27 +++++++++----- packages/page-staking/src/useSortedTargets.ts | 5 ++- 3 files changed, 45 insertions(+), 24 deletions(-) diff --git a/packages/page-staking/src/Overview/SummaryNominators.tsx b/packages/page-staking/src/Overview/SummaryNominators.tsx index 4546484bad24..e0b2398f3f0d 100644 --- a/packages/page-staking/src/Overview/SummaryNominators.tsx +++ b/packages/page-staking/src/Overview/SummaryNominators.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { Card, CardSummary, MarkWarning, SummaryBox } from '@polkadot/react-components'; +import { useApi } from '@polkadot/react-hooks'; import { FormatBalance } from '@polkadot/react-query'; import { formatNumber } from '@polkadot/util'; @@ -23,6 +24,12 @@ function SummaryNominators ({ targets: { maxNominatorsCount, nominatorMinActiveThreshold } }: Props) { const { t } = useTranslation(); + const { api } = useApi(); + + const maxElectingVotersDefined = !!api.consts.electionProviderMultiPhase?.maxElectingVoters; + const maxNominatorDefined = !!api.query.staking.maxNominatorsCount; + const minNominatorBondDefined = !!api.query.staking.minNominatorBond; + return ( <> @@ -33,9 +40,11 @@ function SummaryNominators ({ targets: { maxNominatorsCount, help={t('Maximum number of nominator intentions.')} label={t('maximum')} > - - {formatNumber(maxNominatorsCount?.toNumber())} - + {maxNominatorDefined + ? + {formatNumber(maxNominatorsCount?.toNumber())} + + : '-'}
@@ -43,11 +52,11 @@ function SummaryNominators ({ targets: { maxNominatorsCount, help={t('Number of electing nominators.')} label={t('electing')} > - {nominatorMaxElectingCount === null - ? <>- - : + {maxElectingVotersDefined + ? {formatNumber(nominatorMaxElectingCount)} - } + + : '-'}
@@ -67,12 +76,14 @@ function SummaryNominators ({ targets: { maxNominatorsCount, help={t('Threshold stake to intend nomination.')} label={t('intention thrs')} > - - - + {minNominatorBondDefined + ? + + + : '-'}
diff --git a/packages/page-staking/src/Overview/SummaryValidators.tsx b/packages/page-staking/src/Overview/SummaryValidators.tsx index 18934cc7f310..b2517cca25e2 100644 --- a/packages/page-staking/src/Overview/SummaryValidators.tsx +++ b/packages/page-staking/src/Overview/SummaryValidators.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { Card, CardSummary, SummaryBox } from '@polkadot/react-components'; +import { useApi } from '@polkadot/react-hooks'; import { FormatBalance } from '@polkadot/react-query'; import { formatNumber } from '@polkadot/util'; @@ -24,6 +25,10 @@ function SummaryValidators ({ targets: validatorMinActiveThreshold, waitingIds } }: Props) { const { t } = useTranslation(); + const { api } = useApi(); + + const maxValidatorDefined = !!api.query.staking.maxValidatorsCount; + const minValidatorBondDefined = !!api.query.staking.minValidatorBond; return ( @@ -34,9 +39,11 @@ function SummaryValidators ({ targets: help={t('Maximum number of validator intentions.')} label={t('max intention')} > - - {maxValidatorsCount?.toNumber()} - + {maxValidatorDefined + ? + {maxValidatorsCount?.toNumber()} + + : '-'}
@@ -66,12 +73,14 @@ function SummaryValidators ({ targets: help={t('Threshold stake among intended validators.')} label={t('intention thrs')} > - - - + {minValidatorBondDefined + ? + + + : '-'}
diff --git a/packages/page-staking/src/useSortedTargets.ts b/packages/page-staking/src/useSortedTargets.ts index 38cee952f7b9..a6151a1f9b9c 100644 --- a/packages/page-staking/src/useSortedTargets.ts +++ b/packages/page-staking/src/useSortedTargets.ts @@ -288,13 +288,14 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted api.query.staking.minValidatorBond, api.query.balances?.totalIssuance ], OPT_MULTI); + const electedInfo = useCall(api.derive.staking.electedInfo, [{ ...DEFAULT_FLAGS_ELECTED, withLedger }]); const waitingInfo = useCall(api.derive.staking.waitingInfo, [{ ...DEFAULT_FLAGS_WAITING, withLedger }]); const lastEraInfo = useCall(api.derive.session.info, undefined, OPT_ERA); const [stakers, setStakers] = useState<[StorageKey<[u32, AccountId32]>, PalletStakingExposure][]>([]); const [stakersTotal, setStakersTotal] = useState(); const [nominatorMinActiveThreshold, setNominatorMinActiveThreshold] = useState(''); - const [nominatorMaxElectingCount, setNominatorMaxElectingCount] = useState(); + const [nominatorMaxElectingCount, setNominatorMaxElectingCount] = useState(); const [nominatorElectingCount, setNominatorElectingCount] = useState(); const [nominatorActiveCount, setNominatorActiveCount] = useState(); const [validatorActiveCount, setValidatorActiveCount] = useState(); @@ -323,7 +324,7 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted nominatorStakes.sort((a, b) => a[1].cmp(b[1])); - setNominatorMaxElectingCount(api.consts.electionProviderMultiPhase?.maxElectingVoters || null); + setNominatorMaxElectingCount(api.consts.electionProviderMultiPhase?.maxElectingVoters); setNominatorElectingCount(assignments.size); setNominatorActiveCount(assignments.size); From b2006584de08a68c838b91166fd581c76da92672 Mon Sep 17 00:00:00 2001 From: wirednkod Date: Wed, 13 Jul 2022 00:27:04 +0300 Subject: [PATCH 26/26] Address issue of Automata --- .../src/Overview/SummaryGeneral.tsx | 15 ++++++++----- .../src/Overview/SummaryNominators.tsx | 22 ++++++++++++------- .../src/Overview/SummaryValidators.tsx | 22 ++++++++++++------- packages/page-staking/src/useSortedTargets.ts | 8 +++---- 4 files changed, 42 insertions(+), 25 deletions(-) diff --git a/packages/page-staking/src/Overview/SummaryGeneral.tsx b/packages/page-staking/src/Overview/SummaryGeneral.tsx index 2d2b92f25dc6..feee43ef8a35 100644 --- a/packages/page-staking/src/Overview/SummaryGeneral.tsx +++ b/packages/page-staking/src/Overview/SummaryGeneral.tsx @@ -46,6 +46,15 @@ totalStaked } }: Props) { [totalIssuance, totalStaked] ); + const returnsCheck: string = useMemo( + () => { + if (totalIssuance && stakedReturn > 0 && Number.isFinite(stakedReturn)) { + return (stakedReturn.toFixed(1) + '%'); + } + + return '0%'; + }, [totalIssuance, stakedReturn]); + return ( @@ -85,11 +94,7 @@ totalStaked } }: Props) { >{inflation.toFixed(1)}% ('returns')}> - 0) && Number.isFinite(stakedReturn)} - > - {stakedReturn.toFixed(1)}% - + {returnsCheck}
diff --git a/packages/page-staking/src/Overview/SummaryNominators.tsx b/packages/page-staking/src/Overview/SummaryNominators.tsx index e0b2398f3f0d..87f945beb4fd 100644 --- a/packages/page-staking/src/Overview/SummaryNominators.tsx +++ b/packages/page-staking/src/Overview/SummaryNominators.tsx @@ -27,8 +27,10 @@ function SummaryNominators ({ targets: { maxNominatorsCount, const { api } = useApi(); const maxElectingVotersDefined = !!api.consts.electionProviderMultiPhase?.maxElectingVoters; - const maxNominatorDefined = !!api.query.staking.maxNominatorsCount; - const minNominatorBondDefined = !!api.query.staking.minNominatorBond; + const maxNominatorDefined = !!api.query.staking.maxNominatorsCount && maxNominatorsCount !== undefined; + const minNominatorBondDefined = !!api.query.staking.minNominatorBond && minNominatorBond !== undefined; + const nominatorMinActiveThresholdDefined = !!api.query.staking.erasStakers && nominatorMinActiveThreshold !== undefined; + const nominatorActiveCountDefined = !!api.query.erasStakers && nominatorActiveCount !== undefined; return ( <> @@ -64,9 +66,11 @@ function SummaryNominators ({ targets: { maxNominatorsCount, help={t('Number of nominators backing active validators in the current era.')} label={t('active')} > - - {formatNumber(nominatorActiveCount)} - + {nominatorActiveCountDefined + ? + {formatNumber(nominatorActiveCount)} + + : '-'}
@@ -91,9 +95,11 @@ function SummaryNominators ({ targets: { maxNominatorsCount, help={t('Minimum threshold stake among active nominators.')} label={t('min active thrs')} > - - {nominatorMinActiveThreshold} - + {nominatorMinActiveThresholdDefined + ? + {nominatorMinActiveThreshold} + + : '-'}
diff --git a/packages/page-staking/src/Overview/SummaryValidators.tsx b/packages/page-staking/src/Overview/SummaryValidators.tsx index b2517cca25e2..69d3aa7afc05 100644 --- a/packages/page-staking/src/Overview/SummaryValidators.tsx +++ b/packages/page-staking/src/Overview/SummaryValidators.tsx @@ -27,8 +27,10 @@ function SummaryValidators ({ targets: const { t } = useTranslation(); const { api } = useApi(); - const maxValidatorDefined = !!api.query.staking.maxValidatorsCount; - const minValidatorBondDefined = !!api.query.staking.minValidatorBond; + const maxValidatorDefined = !!api.query.staking.maxValidatorsCount && maxValidatorsCount !== undefined; + const minValidatorBondDefined = !!api.query.staking.minValidatorBond && minValidatorBond !== undefined; + const validatorMinActiveThresholdDefined = !!api.query.staking.erasStakers && validatorMinActiveThreshold !== undefined; + const validatorActiveCountDefined = !!api.query.staking.erasStakers && validatorActiveCount !== undefined; return ( @@ -61,9 +63,11 @@ function SummaryValidators ({ targets: help={t('Count of active validators.')} label={t('active')} > - - {validatorActiveCount} - + {validatorActiveCountDefined + ? + {validatorActiveCount} + + : '-'}
@@ -88,9 +92,11 @@ function SummaryValidators ({ targets: help={t('Minimum threshold stake among active validators.')} label={t('min active thrs')} > - - {validatorMinActiveThreshold} - + {validatorMinActiveThresholdDefined + ? + {validatorMinActiveThreshold} + + : '-'}
diff --git a/packages/page-staking/src/useSortedTargets.ts b/packages/page-staking/src/useSortedTargets.ts index 4e8833ebe1cd..fcb5641fd78f 100644 --- a/packages/page-staking/src/useSortedTargets.ts +++ b/packages/page-staking/src/useSortedTargets.ts @@ -28,7 +28,7 @@ interface MultiResult { maxNominatorsCount?: BN; maxValidatorsCount?: BN; minNominatorBond?: BN; - minValidatorBond?: BN; + minValidatorBond?: BN | undefined; totalIssuance?: BN; } @@ -293,8 +293,8 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted const waitingInfo = useCall(api.derive.staking.waitingInfo, [{ ...DEFAULT_FLAGS_WAITING, withLedger }]); const lastEraInfo = useCall(api.derive.session.info, undefined, OPT_ERA); const [stakers, setStakers] = useState<[StorageKey<[u32, AccountId32]>, PalletStakingExposure][]>([]); - const [stakersTotal, setStakersTotal] = useState(); - const [nominatorMinActiveThreshold, setNominatorMinActiveThreshold] = useState(''); + const [stakersTotal, setStakersTotal] = useState(undefined); + const [nominatorMinActiveThreshold, setNominatorMinActiveThreshold] = useState(); const [nominatorMaxElectingCount, setNominatorMaxElectingCount] = useState(); const [nominatorElectingCount, setNominatorElectingCount] = useState(); const [nominatorActiveCount, setNominatorActiveCount] = useState(); @@ -351,7 +351,7 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted curEra && getStakers(curEra?.unwrap()); - const validatorMinActiveThreshold = stakersTotal ? b(stakersTotal, api) : ''; + const validatorMinActiveThreshold = stakersTotal !== undefined ? b(stakersTotal, api) : undefined; return useMemo( (): SortedTargets => ({