diff --git a/packages/page-staking/src/Targets/Summary.tsx b/packages/page-staking/src/Targets/Summary.tsx
index cda7416609..61a6527f0f 100644
--- a/packages/page-staking/src/Targets/Summary.tsx
+++ b/packages/page-staking/src/Targets/Summary.tsx
@@ -14,8 +14,8 @@ import { useTranslation } from '../translate';
interface Props {
lastReward?: BN;
- numNominators: number;
- numValidators: number;
+ numNominators?: number;
+ numValidators?: number;
totalStaked?: BN;
}
@@ -59,9 +59,11 @@ function Summary ({ lastReward, numNominators, numValidators, totalStaked }: Pro
{percentage}
)}
-
- {numValidators}/{numNominators}
-
+ {numValidators && numNominators && (
+
+ {numValidators}/{numNominators}
+
+ )}
{lastReward?.gtn(0) && (
a.bondTotal.gtn(0))
- .sort((a, b): number => b.commissionPer - a.commissionPer)
- .map((info, index): ValidatorInfo => {
- info.rankComm = index + 1;
-
- return info;
- })
- .sort((a, b): number => b.bondOther.cmp(a.bondOther))
- .map((info, index): ValidatorInfo => {
- info.rankBondOther = index + 1;
-
- return info;
- })
- .sort((a, b): number => b.bondOwn.cmp(a.bondOwn))
- .map((info, index): ValidatorInfo => {
- info.rankBondOwn = index + 1;
-
- return info;
- })
- .sort((a, b): number => b.bondTotal.cmp(a.bondTotal))
- .map((info, index): ValidatorInfo => {
- info.rankBondTotal = index + 1;
-
- return info;
- })
- .sort((a, b): number => b.validatorPayment.cmp(a.validatorPayment))
- .map((info, index): ValidatorInfo => {
- info.rankPayment = index + 1;
-
- return info;
- })
- .sort((a, b): number => a.rewardSplit.cmp(b.rewardSplit))
- .map((info, index): ValidatorInfo => {
- info.rankReward = index + 1;
-
- return info;
- })
- .sort((a, b): number => {
- const cmp = b.rewardPayout.cmp(a.rewardPayout);
-
- return cmp !== 0
- ? cmp
- : a.rankReward === b.rankReward
- ? a.rankPayment === b.rankPayment
- ? b.rankBondTotal - a.rankBondTotal
- : b.rankPayment - a.rankPayment
- : b.rankReward - a.rankReward;
- })
- .map((info, index): ValidatorInfo => {
- info.rankOverall = index + 1;
-
- return info;
- })
- .sort((a, b): number => a.isFavorite === b.isFavorite
- ? 0
- : (a.isFavorite ? -1 : 1)
- );
-}
-
-function extractInfo (allAccounts: string[], amount: BN = new BN(0), electedInfo: DeriveStakingElected, favorites: string[], lastReward = new BN(1)): AllInfo {
- const nominators: string[] = [];
- let totalStaked = new BN(0);
- const perValidatorReward = lastReward.divn(electedInfo.info.length);
- const validators = sortValidators(
- electedInfo.info.map(({ accountId, exposure: _exposure, validatorPrefs }): ValidatorInfo => {
- const exposure = _exposure || {
- others: registry.createType('Vec'),
- own: registry.createType('Compact'),
- total: registry.createType('Compact')
- };
- const prefs = (validatorPrefs as (ValidatorPrefs | ValidatorPrefsTo196)) || {
- commission: registry.createType('Compact')
- };
- const bondOwn = exposure.own.unwrap();
- const bondTotal = exposure.total.unwrap();
- const validatorPayment = (prefs as ValidatorPrefsTo196).validatorPayment
- ? (prefs as ValidatorPrefsTo196).validatorPayment.unwrap() as BN
- : (prefs as ValidatorPrefs).commission.unwrap().mul(perValidatorReward).div(PERBILL);
- const key = accountId.toString();
- const rewardSplit = perValidatorReward.sub(validatorPayment);
- const rewardPayout = rewardSplit.gtn(0)
- ? amount.mul(rewardSplit).div(amount.add(bondTotal))
- : new BN(0);
- const isNominating = exposure.others.reduce((isNominating, indv): boolean => {
- const nominator = indv.who.toString();
-
- if (!nominators.includes(nominator)) {
- nominators.push(nominator);
- }
-
- return isNominating || allAccounts.includes(nominator);
- }, allAccounts.includes(key));
-
- totalStaked = totalStaked.add(bondTotal);
-
- return {
- accountId,
- bondOther: bondTotal.sub(bondOwn),
- bondOwn,
- bondShare: 0,
- bondTotal,
- commissionPer: (((prefs as ValidatorPrefs).commission?.unwrap() || new BN(0)).toNumber() / 10_000_000),
- isCommission: !!(prefs as ValidatorPrefs).commission,
- isFavorite: favorites.includes(key),
- isNominating,
- key,
- numNominators: exposure.others.length,
- rankBondOther: 0,
- rankBondOwn: 0,
- rankBondTotal: 0,
- rankComm: 0,
- rankOverall: 0,
- rankPayment: 0,
- rankReward: 0,
- rewardPayout,
- rewardSplit,
- validatorPayment
- };
- })
- );
-
- return { nominators, totalStaked, validators };
-}
-
-function sort (sortBy: SortBy, sortFromMax: boolean, validators: ValidatorInfo[]): ValidatorInfo[] {
- return [...validators]
- .sort((a, b): number => sortFromMax
- ? a[sortBy] - b[sortBy]
- : b[sortBy] - a[sortBy]
+function sort (sortBy: SortBy, sortFromMax: boolean, validators: ValidatorInfo[]): number[] {
+ return [...Array(validators.length).keys()]
+ .sort((a, b) =>
+ sortFromMax
+ ? validators[a][sortBy] - validators[b][sortBy]
+ : validators[b][sortBy] - validators[a][sortBy]
)
- .sort((a, b): number => a.isFavorite === b.isFavorite
- ? 0
- : (a.isFavorite ? -1 : 1)
+ .sort((a, b) =>
+ validators[a].isFavorite === validators[b].isFavorite
+ ? 0
+ : (validators[a].isFavorite ? -1 : 1)
);
}
function Targets ({ className, ownStashes }: Props): React.ReactElement {
const { t } = useTranslation();
- const { api } = useApi();
- const { allAccounts } = useAccounts();
const ownNominators = useOwnNominators(ownStashes);
- const lastEra = useCall(api.derive.session.indexes, [], {
- transform: ({ activeEra }: DeriveSessionIndexes) => activeEra.gtn(0) ? activeEra.subn(1) : new BN(0)
- }) || new BN(0);
- const lastReward = useCall(api.query.staking.erasValidatorReward, [lastEra], {
- transform: (optBalance: Option) => optBalance.unwrapOrDefault()
- });
const [_amount, setAmount] = useState(new BN(1_000));
- const electedInfo = useCall(api.derive.staking.electedInfo, []);
- const [favorites, toggleFavorite] = useFavorites(STORE_FAVS_BASE);
- const [selected, setSelected] = useState([]);
- const [{ nominators, sorted, totalStaked, validators }, setWorkable] = useState({ nominators: [], validators: [] });
- const [{ sortBy, sortFromMax }, setSortBy] = useState<{ sortBy: SortBy; sortFromMax: boolean }>({ sortBy: 'rankOverall', sortFromMax: true });
const amount = useDebounce(_amount);
- const labels = useMemo(
- (): Record => ({
- rankBondOther: t('other stake'),
- rankBondOwn: t('own stake'),
- rankBondTotal: t('total stake'),
- rankComm: t('commission'),
- rankOverall: t('profit/era est')
- }),
- [t]
- );
+ const { lastReward, nominators, toggleFavorite, totalStaked, validators } = useSortedTargets(amount);
+ const [selected, setSelected] = useState([]);
+ const [sorted, setSorted] = useState();
+ const [{ sortBy, sortFromMax }, setSortBy] = useState<{ sortBy: SortBy; sortFromMax: boolean }>({ sortBy: 'rankOverall', sortFromMax: true });
+
+ useEffect((): void => {
+ validators && setSorted(
+ sort(sortBy, sortFromMax, validators)
+ );
+ }, [sortBy, sortFromMax, validators]);
const _sort = useCallback(
(newSortBy: SortBy) => setSortBy(({ sortBy, sortFromMax }) => ({
@@ -227,21 +78,23 @@ function Targets ({ className, ownStashes }: Props): React.ReactElement {
const _selectProfitable = useCallback(
() => setSelected(
- validators
+ (validators || [])
.filter((_, index) => index < MAX_NOMINATIONS)
.map(({ key }) => key)
),
[validators]
);
- useEffect((): void => {
- if (electedInfo) {
- const { nominators, totalStaked, validators } = extractInfo(allAccounts, amount, electedInfo, favorites, lastReward);
- const sorted = sort(sortBy, sortFromMax, validators);
-
- setWorkable({ nominators, sorted, totalStaked, validators });
- }
- }, [allAccounts, amount, electedInfo, favorites, lastReward, sortBy, sortFromMax]);
+ const labels = useMemo(
+ (): Record => ({
+ rankBondOther: t('other stake'),
+ rankBondOwn: t('own stake'),
+ rankBondTotal: t('total stake'),
+ rankComm: t('commission'),
+ rankOverall: t('profit/era est')
+ }),
+ [t]
+ );
const header = useMemo(() => [
[t('validators'), 'start', 3],
@@ -269,14 +122,14 @@ function Targets ({ className, ownStashes }: Props): React.ReactElement {
@@ -291,12 +144,12 @@ function Targets ({ className, ownStashes }: Props): React.ReactElement {
filter={filter}
header={header}
>
- {sorted?.map((info): React.ReactNode =>
+ {validators && sorted?.map((index): React.ReactNode =>
diff --git a/packages/page-staking/src/Targets/types.ts b/packages/page-staking/src/Targets/types.ts
index 1e62802ae1..4382633fe5 100644
--- a/packages/page-staking/src/Targets/types.ts
+++ b/packages/page-staking/src/Targets/types.ts
@@ -29,3 +29,11 @@ export interface ValidatorInfo {
rewardSplit: BN;
validatorPayment: BN;
}
+
+export interface SortedTargets {
+ lastReward?: BN;
+ nominators?: string[];
+ toggleFavorite: (accountId: string) => void;
+ totalStaked?: BN;
+ validators?: ValidatorInfo[];
+}
diff --git a/packages/page-staking/src/Targets/useSortedTargets.ts b/packages/page-staking/src/Targets/useSortedTargets.ts
new file mode 100644
index 0000000000..ca17c66ade
--- /dev/null
+++ b/packages/page-staking/src/Targets/useSortedTargets.ts
@@ -0,0 +1,167 @@
+// Copyright 2017-2020 @polkadot/app-staking authors & contributors
+// This software may be modified and distributed under the terms
+// of the Apache-2.0 license. See the LICENSE file for details.
+
+import { DeriveSessionIndexes, DeriveStakingElected } from '@polkadot/api-derive/types';
+import { Balance, ValidatorPrefs, ValidatorPrefsTo196 } from '@polkadot/types/interfaces';
+import { SortedTargets, ValidatorInfo } from './types';
+
+import BN from 'bn.js';
+import { useEffect, useState } from 'react';
+import { registry } from '@polkadot/react-api';
+import { useAccounts, useApi, useCall, useFavorites } from '@polkadot/react-hooks';
+import { Option } from '@polkadot/types';
+
+import { STORE_FAVS_BASE } from '../constants';
+
+const PERBILL = new BN(1_000_000_000);
+
+function sortValidators (list: ValidatorInfo[]): ValidatorInfo[] {
+ return list
+ .filter((a) => a.bondTotal.gtn(0))
+ .sort((a, b): number => b.commissionPer - a.commissionPer)
+ .map((info, index): ValidatorInfo => {
+ info.rankComm = index + 1;
+
+ return info;
+ })
+ .sort((a, b): number => b.bondOther.cmp(a.bondOther))
+ .map((info, index): ValidatorInfo => {
+ info.rankBondOther = index + 1;
+
+ return info;
+ })
+ .sort((a, b): number => b.bondOwn.cmp(a.bondOwn))
+ .map((info, index): ValidatorInfo => {
+ info.rankBondOwn = index + 1;
+
+ return info;
+ })
+ .sort((a, b): number => b.bondTotal.cmp(a.bondTotal))
+ .map((info, index): ValidatorInfo => {
+ info.rankBondTotal = index + 1;
+
+ return info;
+ })
+ .sort((a, b): number => b.validatorPayment.cmp(a.validatorPayment))
+ .map((info, index): ValidatorInfo => {
+ info.rankPayment = index + 1;
+
+ return info;
+ })
+ .sort((a, b): number => a.rewardSplit.cmp(b.rewardSplit))
+ .map((info, index): ValidatorInfo => {
+ info.rankReward = index + 1;
+
+ return info;
+ })
+ .sort((a, b): number => {
+ const cmp = b.rewardPayout.cmp(a.rewardPayout);
+
+ return cmp !== 0
+ ? cmp
+ : a.rankReward === b.rankReward
+ ? a.rankPayment === b.rankPayment
+ ? b.rankBondTotal - a.rankBondTotal
+ : b.rankPayment - a.rankPayment
+ : b.rankReward - a.rankReward;
+ })
+ .map((info, index): ValidatorInfo => {
+ info.rankOverall = index + 1;
+
+ return info;
+ })
+ .sort((a, b): number => a.isFavorite === b.isFavorite
+ ? 0
+ : (a.isFavorite ? -1 : 1)
+ );
+}
+
+function extractInfo (allAccounts: string[], amount: BN = new BN(0), electedInfo: DeriveStakingElected, favorites: string[], lastReward = new BN(1)): Partial {
+ const nominators: string[] = [];
+ let totalStaked = new BN(0);
+ const perValidatorReward = lastReward.divn(electedInfo.info.length);
+ const validators = sortValidators(
+ electedInfo.info.map(({ accountId, exposure: _exposure, validatorPrefs }): ValidatorInfo => {
+ const exposure = _exposure || {
+ others: registry.createType('Vec'),
+ own: registry.createType('Compact'),
+ total: registry.createType('Compact')
+ };
+ const prefs = (validatorPrefs as (ValidatorPrefs | ValidatorPrefsTo196)) || {
+ commission: registry.createType('Compact')
+ };
+ const bondOwn = exposure.own.unwrap();
+ const bondTotal = exposure.total.unwrap();
+ const validatorPayment = (prefs as ValidatorPrefsTo196).validatorPayment
+ ? (prefs as ValidatorPrefsTo196).validatorPayment.unwrap() as BN
+ : (prefs as ValidatorPrefs).commission.unwrap().mul(perValidatorReward).div(PERBILL);
+ const key = accountId.toString();
+ const rewardSplit = perValidatorReward.sub(validatorPayment);
+ const rewardPayout = rewardSplit.gtn(0)
+ ? amount.mul(rewardSplit).div(amount.add(bondTotal))
+ : new BN(0);
+ const isNominating = exposure.others.reduce((isNominating, indv): boolean => {
+ const nominator = indv.who.toString();
+
+ if (!nominators.includes(nominator)) {
+ nominators.push(nominator);
+ }
+
+ return isNominating || allAccounts.includes(nominator);
+ }, allAccounts.includes(key));
+
+ totalStaked = totalStaked.add(bondTotal);
+
+ return {
+ accountId,
+ bondOther: bondTotal.sub(bondOwn),
+ bondOwn,
+ bondShare: 0,
+ bondTotal,
+ commissionPer: (((prefs as ValidatorPrefs).commission?.unwrap() || new BN(0)).toNumber() / 10_000_000),
+ isCommission: !!(prefs as ValidatorPrefs).commission,
+ isFavorite: favorites.includes(key),
+ isNominating,
+ key,
+ numNominators: exposure.others.length,
+ rankBondOther: 0,
+ rankBondOwn: 0,
+ rankBondTotal: 0,
+ rankComm: 0,
+ rankOverall: 0,
+ rankPayment: 0,
+ rankReward: 0,
+ rewardPayout,
+ rewardSplit,
+ validatorPayment
+ };
+ })
+ );
+
+ return { nominators, totalStaked, validators };
+}
+
+export default function useSortedTargets (amount?: BN): SortedTargets {
+ const { api } = useApi();
+ const { allAccounts } = useAccounts();
+ const [favorites, toggleFavorite] = useFavorites(STORE_FAVS_BASE);
+ const electedInfo = useCall(api.derive.staking.electedInfo, []);
+ const lastEra = useCall(api.derive.session.indexes, [], {
+ transform: ({ activeEra }: DeriveSessionIndexes) => activeEra.gtn(0) ? activeEra.subn(1) : new BN(0)
+ });
+ const lastReward = useCall(lastEra && api.query.staking.erasValidatorReward, [lastEra], {
+ transform: (optBalance: Option) => optBalance.unwrapOrDefault()
+ });
+ const [state, setState] = useState({ toggleFavorite });
+
+ useEffect((): void => {
+ electedInfo && setState(({ toggleFavorite }) => ({
+ ...extractInfo(allAccounts, amount, electedInfo, favorites, lastReward),
+ lastReward,
+ toggleFavorite
+ }));
+ }, [allAccounts, amount, electedInfo, favorites, lastReward]);
+
+ return state;
+}
diff --git a/packages/page-staking/src/index.tsx b/packages/page-staking/src/index.tsx
index a662810231..32d6dee67c 100644
--- a/packages/page-staking/src/index.tsx
+++ b/packages/page-staking/src/index.tsx
@@ -120,9 +120,6 @@ function StakingApp ({ basePath, className }: Props): React.ReactElement
-
-
-
ownStashes={ownStashes}
validators={validators}
/>
+