diff --git a/packages/page-staking/src/Actions/index.tsx b/packages/page-staking/src/Actions/index.tsx index 6f8038d512..a68817fd12 100644 --- a/packages/page-staking/src/Actions/index.tsx +++ b/packages/page-staking/src/Actions/index.tsx @@ -77,6 +77,7 @@ function Actions ({ className = '', isInElection, ownStashes, targets }: Props): return (
+ - ('No funds staked yet. Bond funds to validate or nominate a validator')} footer={footer} diff --git a/packages/page-staking/src/Payouts/PayToggle.tsx b/packages/page-staking/src/Payouts/PayToggle.tsx index 14b9e98e6a..7eece82043 100644 --- a/packages/page-staking/src/Payouts/PayToggle.tsx +++ b/packages/page-staking/src/Payouts/PayToggle.tsx @@ -9,27 +9,31 @@ import { Button } from '@polkadot/react-components'; interface Props { className?: string; onChange: (index: number) => void; - options: { text: string, value: number }[]; + options: { isDisabled?: boolean, text: string, value: number }[]; selected: number; } interface ToggleProps { index: number; + isDisabled?: boolean; isSelected: boolean; onChange: (index: number) => void; text: string; } -function ToggleIndex ({ index, isSelected, onChange, text }: ToggleProps): React.ReactElement { +function ToggleIndex ({ index, isDisabled, isSelected, onChange, text }: ToggleProps): React.ReactElement { const _onClick = useCallback( - () => onChange(index), - [index, onChange] + (): void => { + !isDisabled && onChange(index); + }, + [isDisabled, index, onChange] ); return (
@@ -185,8 +194,14 @@ function Payouts ({ className = '', isInElection }: Props): React.ReactElement

+ {api.tx.staking.payoutStakers && ( + )} - ('No pending payouts for your stashes')} emptySpinner={t('Retrieving info for the selected eras, this will take some time')} @@ -216,7 +230,7 @@ function Payouts ({ className = '', isInElection }: Props): React.ReactElement

))}

- {api.tx.staking.payoutStakers && !isLoadingRewards && validators && (validators.length !== 0) && ( + {api.tx.staking.payoutStakers && (myStashesIndex === 1) && !isLoadingRewards && validators && (validators.length !== 0) && ( +
('No active validators to check')} filter={filter} diff --git a/packages/react-hooks/src/useOwnEraRewards.ts b/packages/react-hooks/src/useOwnEraRewards.ts index e70a8452ca..3775948cb9 100644 --- a/packages/react-hooks/src/useOwnEraRewards.ts +++ b/packages/react-hooks/src/useOwnEraRewards.ts @@ -2,10 +2,13 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { DeriveStakerReward } from '@polkadot/api-derive/types'; +import { DeriveEraPoints, DeriveEraRewards, DeriveStakerReward } from '@polkadot/api-derive/types'; import { EraIndex } from '@polkadot/types/interfaces'; +import { StakerState } from './types'; import { useEffect, useState } from 'react'; +import { registry } from '@polkadot/react-api'; +import { BN_ZERO } from '@polkadot/util'; import useApi from './useApi'; import useCall from './useCall'; @@ -13,11 +16,21 @@ import useIsMountedRef from './useIsMountedRef'; import { useOwnStashIds } from './useOwnStashes'; interface OwnRewards { - allRewards?: Record; + allRewards?: Record | null; isLoadingRewards: boolean; rewardCount: number; } +interface ValidatorWithEras { + eras: EraIndex[]; + stashId: string; +} + +interface Filtered { + filteredEras: EraIndex[]; + validatorEras: ValidatorWithEras[]; +} + function getRewards ([[stashIds], available]: [[string[]], DeriveStakerReward[][]]): OwnRewards { const allRewards: Record = {}; @@ -32,30 +45,96 @@ function getRewards ([[stashIds], available]: [[string[]], DeriveStakerReward[][ }; } -export default function useOwnEraRewards (maxEras?: number): OwnRewards { +function getValRewards (validatorEras: ValidatorWithEras[], erasPoints: DeriveEraPoints[], erasRewards: DeriveEraRewards[]): OwnRewards { + const allRewards: Record = {}; + + validatorEras.forEach(({ eras, stashId }): void => { + eras.forEach((era): void => { + const eraPoints = erasPoints.find((p) => p.era.eq(era)); + const eraRewards = erasRewards.find((r) => r.era.eq(era)); + + if (eraPoints?.eraPoints.gt(BN_ZERO) && eraPoints?.validators[stashId] && eraRewards) { + const reward = eraPoints.validators[stashId].mul(eraRewards.eraReward).div(eraPoints.eraPoints); + + if (!reward.isZero()) { + const total = registry.createType('Balance', reward); + + if (!allRewards[stashId]) { + allRewards[stashId] = []; + } + + allRewards[stashId].push({ + era, + eraReward: eraRewards.eraReward, + isEmpty: false, + isValidator: true, + nominating: [], + validators: { + [stashId]: { + total, + value: total + } + } + }); + } + } + }); + }); + + return { + allRewards, + isLoadingRewards: false, + rewardCount: Object.values(allRewards).filter((rewards) => rewards.length !== 0).length + }; +} + +export default function useOwnEraRewards (maxEras?: number, ownValidators?: StakerState[]): OwnRewards { const { api } = useApi(); const mountedRef = useIsMountedRef(); const stashIds = useOwnStashIds(); const allEras = useCall(api.derive.staking?.erasHistoric, []); - const [filteredEras, setFilteredEras] = useState([]); - const available = useCall<[[string[]], DeriveStakerReward[][]]>(!!filteredEras.length && stashIds && api.derive.staking?.stakerRewardsMultiEras, [stashIds, filteredEras], { withParams: true }); + const [{ filteredEras, validatorEras }, setFiltered] = useState({ filteredEras: [], validatorEras: [] }); const [state, setState] = useState({ isLoadingRewards: true, rewardCount: 0 }); + const stakerRewards = useCall<[[string[]], DeriveStakerReward[][]]>(!ownValidators?.length && !!filteredEras.length && stashIds && api.derive.staking?.stakerRewardsMultiEras, [stashIds, filteredEras], { withParams: true }); + const erasPoints = useCall(!!validatorEras.length && !!filteredEras.length && api.derive.staking._erasPoints, [filteredEras, false]); + const erasRewards = useCall(!!validatorEras.length && !!filteredEras.length && api.derive.staking._erasRewards, [filteredEras, false]); useEffect((): void => { - setState({ isLoadingRewards: true, rewardCount: 0 }); - }, [maxEras]); + setState({ allRewards: null, isLoadingRewards: true, rewardCount: 0 }); + }, [maxEras, ownValidators]); useEffect((): void => { - allEras && maxEras && setFilteredEras( - allEras.slice(-1 * maxEras) + if (allEras && maxEras) { + const filteredEras = allEras.slice(-1 * maxEras); + const validatorEras: ValidatorWithEras[] = []; + + if (ownValidators?.length) { + ownValidators.forEach(({ stakingLedger, stashId }): void => { + if (stakingLedger) { + const eras = filteredEras.filter((era) => !stakingLedger.claimedRewards.some((c) => era.eq(c))); + + if (eras.length) { + validatorEras.push({ eras, stashId }); + } + } + }); + } + + setFiltered({ filteredEras, validatorEras }); + } + }, [allEras, maxEras, ownValidators]); + + useEffect((): void => { + mountedRef.current && stakerRewards && !ownValidators && setState( + getRewards(stakerRewards) ); - }, [allEras, maxEras]); + }, [mountedRef, ownValidators, stakerRewards]); useEffect((): void => { - mountedRef.current && available && setState( - getRewards(available) + mountedRef && erasPoints && erasRewards && ownValidators && setState( + getValRewards(validatorEras, erasPoints, erasRewards) ); - }, [available, mountedRef]); + }, [erasPoints, erasRewards, mountedRef, ownValidators, validatorEras]); return state; }