Skip to content

Commit

Permalink
feat: Order nominations by nomination status. (#1543)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ross Bulat authored Oct 20, 2023
1 parent dc453e4 commit 0ab87f4
Show file tree
Hide file tree
Showing 14 changed files with 249 additions and 188 deletions.
2 changes: 1 addition & 1 deletion src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const ListItemsPerPage = 50;
export const ListItemsPerBatch = 30;
export const MinBondPrecision = 3;
export const MaxPayoutDays = 60;
export const MaxEraRewardPointsEras = 10;
export const MaxEraRewardPointsEras = 14;

/*
* Third party API keys and endpoints
Expand Down
2 changes: 2 additions & 0 deletions src/contexts/Validators/ValidatorEntries/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type { EraPointsBoundaries, ValidatorsContextInterface } from '../types';
export const defaultValidatorsContext: ValidatorsContextInterface = {
fetchValidatorPrefs: async (a) => new Promise((resolve) => resolve(null)),
getValidatorEraPoints: (startEra, address) => ({}),
getNominated: (bondFor) => [],
injectValidatorListData: (entries) => [],
validators: [],
validatorIdentities: {},
validatorSupers: {},
Expand Down
43 changes: 40 additions & 3 deletions src/contexts/Validators/ValidatorEntries/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { greaterThanZero, rmCommas, shuffle } from '@polkadot-cloud/utils';
import BigNumber from 'bignumber.js';
import React, { useEffect, useRef, useState } from 'react';
import { ValidatorCommunity } from '@polkadot-cloud/assets/validators';
import type { AnyApi, AnyJson, Fn, Sync } from 'types';
import type { AnyApi, AnyJson, BondFor, Fn, Sync } from 'types';
import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks';
import { useBonded } from 'contexts/Bonded';
import { useNetworkMetrics } from 'contexts/NetworkMetrics';
Expand All @@ -14,6 +14,7 @@ import { useNetwork } from 'contexts/Network';
import { useApi } from 'contexts/Api';
import { useActiveAccounts } from 'contexts/ActiveAccounts';
import { MaxEraRewardPointsEras } from 'consts';
import { useStaking } from 'contexts/Staking';
import type {
EraPointsBoundaries,
EraRewardPoints,
Expand All @@ -22,6 +23,7 @@ import type {
Validator,
ValidatorAddresses,
ValidatorSuper,
ValidatorListEntry,
ValidatorsContextInterface,
} from '../types';
import {
Expand All @@ -38,6 +40,7 @@ export const ValidatorsProvider = ({
}) => {
const { network } = useNetwork();
const { isReady, api } = useApi();
const { stakers } = useStaking().eraStakers;
const { poolNominations } = useActivePools();
const { activeAccount } = useActiveAccounts();
const { activeEra, metrics } = useNetworkMetrics();
Expand Down Expand Up @@ -67,15 +70,17 @@ export const ValidatorsProvider = ({
const [sessionParaValidators, setSessionParaValidators] = useState<string[]>(
[]
);

// Stores unsub object for para session.
const sessionParaUnsub = useRef<Fn>();

// Stores the average network commission rate.
const [avgCommission, setAvgCommission] = useState(0);

// stores the user's nominated validators as list
// Stores the user's nominated validators as list
const [nominated, setNominated] = useState<Validator[] | null>(null);

// stores the nominated validators by the members pool's as list
// Stores the nominated validators by the members pool's as list
const [poolNominated, setPoolNominated] = useState<Validator[] | null>(null);

// Stores a randomised validator community dataset.
Expand Down Expand Up @@ -400,6 +405,36 @@ export const ValidatorsProvider = ({
});
};

// Gets either `nominated` or `poolNominated` depending on bondFor, and injects the validator
// status into the entries.
const getNominated = (bondFor: BondFor) =>
bondFor === 'nominator' ? nominated : poolNominated;

// Inject status into validator entries.
const injectValidatorListData = (
entries: Validator[]
): ValidatorListEntry[] => {
const injected: ValidatorListEntry[] =
entries.map((entry) => {
const inEra =
stakers.find(({ address }) => address === entry.address) || false;
let totalStake = new BigNumber(0);
if (inEra) {
const { others, own } = inEra;
if (own) totalStake = totalStake.plus(own);
others.forEach(({ value }) => {
totalStake = totalStake.plus(value);
});
}
return {
...entry,
totalStake,
validatorStatus: inEra ? 'active' : 'waiting',
};
}) || [];
return injected;
};

// Reset validator state data on network change.
useEffectIgnoreInitial(() => {
setValidatorsFetched('unsynced');
Expand Down Expand Up @@ -464,6 +499,8 @@ export const ValidatorsProvider = ({
value={{
fetchValidatorPrefs,
getValidatorEraPoints,
getNominated,
injectValidatorListData,
validators,
validatorIdentities,
validatorSupers,
Expand Down
9 changes: 8 additions & 1 deletion src/contexts/Validators/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
// SPDX-License-Identifier: GPL-3.0-only

import type BigNumber from 'bignumber.js';
import type { AnyJson, Sync } from 'types';
import type { AnyJson, BondFor, Sync } from 'types';

export interface ValidatorsContextInterface {
fetchValidatorPrefs: (a: ValidatorAddresses) => Promise<Validator[] | null>;
getValidatorEraPoints: (
startEra: BigNumber,
address: string
) => Record<string, BigNumber>;
getNominated: (bondFor: BondFor) => Validator[] | null;
injectValidatorListData: (entries: Validator[]) => ValidatorListEntry[];
validators: Validator[];
validatorIdentities: Record<string, Identity>;
validatorSupers: Record<string, AnyJson>;
Expand Down Expand Up @@ -73,3 +75,8 @@ export type EraPointsBoundaries = {
high: BigNumber;
low: BigNumber;
} | null;

export type ValidatorListEntry = Validator & {
validatorStatus: 'waiting' | 'active';
totalStake: BigNumber;
};
16 changes: 7 additions & 9 deletions src/library/Hooks/useNominationStatus/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useActivePools } from 'contexts/Pools/ActivePools';
import { useStaking } from 'contexts/Staking';
import { useUi } from 'contexts/UI';
import { useValidators } from 'contexts/Validators/ValidatorEntries';
import type { AnyJson, MaybeAddress } from 'types';
import type { AnyJson, BondFor, MaybeAddress } from 'types';
import { useNetwork } from 'contexts/Network';

export const useNominationStatus = () => {
Expand All @@ -18,23 +18,24 @@ export const useNominationStatus = () => {
const {
networkData: { units },
} = useNetwork();
const { validators } = useValidators();
const { poolNominations } = useActivePools();
const { getAccountNominations } = useBonded();
const {
inSetup,
eraStakers,
erasStakersSyncing,
getNominationsStatusFromTargets,
getLowestRewardFromStaker,
} = useStaking();
const { validators } = useValidators();
const { poolNominations } = useActivePools();
const { getAccountNominations } = useBonded();

// Utility to get an account's nominees alongside their status.
const getNomineesStatus = (who: MaybeAddress, type: 'nominator' | 'pool') => {
const getNomineesStatus = (who: MaybeAddress, type: BondFor) => {
const nominations =
type === 'nominator'
? getAccountNominations(who)
: poolNominations?.targets ?? [];

return getNominationsStatusFromTargets(who, nominations);
};

Expand All @@ -46,10 +47,7 @@ export const useNominationStatus = () => {

// Utility to get the status of the provided account's nominations, and whether they are earning
// reards.
const getNominationStatus = (
who: MaybeAddress,
type: 'nominator' | 'pool'
) => {
const getNominationStatus = (who: MaybeAddress, type: BondFor) => {
// Get the sets nominees from the provided account's targets.
const nominees = Object.entries(getNomineesStatus(who, type));
const activeNominees = getNomineesByStatus(nominees, 'active');
Expand Down
48 changes: 8 additions & 40 deletions src/library/ListItem/Labels/EraStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,29 @@
// SPDX-License-Identifier: GPL-3.0-only

import { capitalizeFirstLetter, planckToUnit } from '@polkadot-cloud/utils';
import BigNumber from 'bignumber.js';
import { useTranslation } from 'react-i18next';
import { useStaking } from 'contexts/Staking';
import { useUi } from 'contexts/UI';
import { ValidatorStatusWrapper } from 'library/ListItem/Wrappers';
import type { MaybeAddress } from 'types';
import { useNetwork } from 'contexts/Network';
import type { EraStatusProps } from '../types';

export const EraStatus = ({
address,
noMargin,
}: {
address: MaybeAddress;
noMargin: boolean;
}) => {
export const EraStatus = ({ noMargin, status, totalStake }: EraStatusProps) => {
const { t } = useTranslation('library');
const {
networkData: { unit, units },
} = useNetwork();
const {
eraStakers: { stakers },
erasStakersSyncing,
} = useStaking();
const { isSyncing } = useUi();
const { erasStakersSyncing } = useStaking();
const { unit, units } = useNetwork().networkData;

// is the validator in the active era
const validatorInEra = stakers.find((s) => s.address === address) || null;

// flag whether validator is active
const validatorStatus = isSyncing
? 'waiting'
: validatorInEra
? 'active'
: 'waiting';

let totalStakePlanck = new BigNumber(0);
if (validatorInEra) {
const { others, own } = validatorInEra;
others.forEach((o: any) => {
totalStakePlanck = totalStakePlanck.plus(o.value);
});
if (own) {
totalStakePlanck = totalStakePlanck.plus(own);
}
}

const totalStake = planckToUnit(totalStakePlanck, units);
// Fallback to `waiting` status if still syncing.
const validatorStatus = isSyncing ? 'waiting' : status;

return (
<ValidatorStatusWrapper $status={validatorStatus} $noMargin={noMargin}>
<h5>
{isSyncing || erasStakersSyncing
? t('syncing')
: validatorInEra
? `${t('listItemActive')} / ${totalStake
: validatorStatus !== 'waiting'
? `${t('listItemActive')} / ${planckToUnit(totalStake, units)
.integerValue()
.toFormat()} ${unit}`
: capitalizeFirstLetter(t(`${validatorStatus}`) ?? '')}
Expand Down
26 changes: 5 additions & 21 deletions src/library/ListItem/Labels/NominationStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,35 @@
import { greaterThanZero, planckToUnit } from '@polkadot-cloud/utils';
import BigNumber from 'bignumber.js';
import { useTranslation } from 'react-i18next';
import { useBondedPools } from 'contexts/Pools/BondedPools';
import { useStaking } from 'contexts/Staking';
import { ValidatorStatusWrapper } from 'library/ListItem/Wrappers';
import { useNominationStatus } from 'library/Hooks/useNominationStatus';
import { useNetwork } from 'contexts/Network';
import { useActiveAccounts } from 'contexts/ActiveAccounts';
import type { NominationStatusProps } from '../types';

export const NominationStatus = ({
address,
nominator,
bondFor,
noMargin = false,
status,
}: NominationStatusProps) => {
const { t } = useTranslation('library');
const {
networkData: { unit, units },
} = useNetwork();
const { activeAccount } = useActiveAccounts();
const { getPoolNominationStatus } = useBondedPools();
const { getNomineesStatus } = useNominationStatus();
const {
eraStakers: { activeAccountOwnStake, stakers },
erasStakersSyncing,
} = useStaking();

let nominationStatus;
if (bondFor === 'pool') {
// get nomination status from pool metadata
nominationStatus = getPoolNominationStatus(nominator, address);
} else {
// get all active account's nominations.
const nominationStatuses = getNomineesStatus(activeAccount, 'nominator');
// find the nominator status within the returned nominations.
nominationStatus = nominationStatuses[address];
}

// determine staked amount
let stakedAmount = new BigNumber(0);
if (bondFor === 'nominator') {
// bonded amount within the validator.
stakedAmount =
nominationStatus === 'active'
status === 'active'
? new BigNumber(
activeAccountOwnStake?.find((own: any) => own.address)?.value ?? 0
activeAccountOwnStake?.find((own) => own.address)?.value ?? 0
)
: new BigNumber(0);
} else {
Expand All @@ -60,9 +44,9 @@ export const NominationStatus = ({
}

return (
<ValidatorStatusWrapper $status={nominationStatus} $noMargin={noMargin}>
<ValidatorStatusWrapper $status={status || 'waiting'} $noMargin={noMargin}>
<h5>
{t(`${nominationStatus}`)}
{t(`${status || 'waiting'}`)}
{greaterThanZero(stakedAmount)
? ` / ${
erasStakersSyncing ? '...' : `${stakedAmount.toFormat()} ${unit}`
Expand Down
10 changes: 10 additions & 0 deletions src/library/ListItem/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import type React from 'react';
import type { BondedPool } from 'contexts/Pools/types';
import type { BondFor, MaybeAddress } from 'types';
import type { ValidatorPrefs } from 'contexts/Validators/types';
import type BigNumber from 'bignumber.js';
import type { NominationStatus } from 'library/ValidatorList/ValidatorItem/types';

export interface BlockedProps {
prefs: ValidatorPrefs;
Expand Down Expand Up @@ -37,6 +39,7 @@ export interface NominationStatusProps {
address: string;
bondFor: BondFor;
nominator: MaybeAddress;
status?: NominationStatus;
noMargin?: boolean;
}

Expand All @@ -53,3 +56,10 @@ export interface SelectProps {
export interface ParaValidatorProps {
address: MaybeAddress;
}

export interface EraStatusProps {
address: MaybeAddress;
noMargin: boolean;
totalStake: BigNumber;
status: 'waiting' | 'active';
}
4 changes: 2 additions & 2 deletions src/library/Nominations/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ export const Nominations = ({
modal: { openModal },
canvas: { openCanvas },
} = useOverlay();
const { getNominated } = useValidators();
const { isFastUnstaking } = useUnstaking();
const { activeAccount } = useActiveAccounts();
const { getAccountNominations } = useBonded();
const { isReadOnlyAccount } = useImportedAccounts();
const { nominated: nominatorNominated, poolNominated } = useValidators();

// Determine if pool or nominator.
const isPool = bondFor === 'pool';
Expand All @@ -54,7 +54,7 @@ export const Nominations = ({
const nominations = isPool
? poolNominations.targets
: getAccountNominations(nominator);
const nominated = isPool ? poolNominated : nominatorNominated;
const nominated = getNominated(bondFor);

// Determine if this nominator is actually nominating.
const isNominating = nominated?.length ?? false;
Expand Down
4 changes: 2 additions & 2 deletions src/library/Pool/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ export const Pool = ({ pool, batchKey, batchIndex }: PoolProps) => {
const targets = nominations[batchIndex]?.targets ?? [];

// extract validator entries from pool targets
const targetValidators = validators.filter((v: any) =>
targets.includes(v.address)
const targetValidators = validators.filter(({ address }) =>
targets.includes(address)
);

// configure floating menu position
Expand Down
Loading

0 comments on commit 0ab87f4

Please sign in to comment.