diff --git a/CHANGELOG.md b/CHANGELOG.md index 684db2aae5..85120c5f79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Add `feegrant` and `authz` messages ([\#481](https://github.com/forbole/big-dipper-2.0-cosmos/issues/481)) - Add `vesting` messages ([\#538](https://github.com/forbole/big-dipper-2.0-cosmos/issues/538)) - Add status row in `/validators` ([\#556](https://github.com/forbole/big-dipper-2.0-cosmos/issues/556)) +- Show who the top 34% validators are ([\#506](https://github.com/forbole/big-dipper-2.0-cosmos/issues/506)) ## Bug fixes - Fix validator searchbar ([\#540](https://github.com/forbole/big-dipper-2.0-cosmos/issues/540)) diff --git a/public/locales/en/validators.json b/public/locales/en/validators.json index 84a515e1b6..71c4c7bc74 100644 --- a/public/locales/en/validators.json +++ b/public/locales/en/validators.json @@ -54,5 +54,6 @@ "missedBlockCounter": "Missed Block Counter: {{amount}}", "signedBlockWindow": "Signed Block Window: {{amount}}", "lastSeen": "Last Seen", - "status": "Status" + "status": "Status", + "votingPowerExplanation": "As the top 34% voting power can easily <0>decrease network security and <0>halt the network they will be highlighted differently in order to educate and encourage decentralization" } diff --git a/src/components/info_popover/index.tsx b/src/components/info_popover/index.tsx index be8fc0200a..8d9656ef7c 100644 --- a/src/components/info_popover/index.tsx +++ b/src/components/info_popover/index.tsx @@ -33,6 +33,7 @@ const InfoPopover: React.FC<{ aria-haspopup="true" onMouseEnter={handlePopoverOpen} onMouseLeave={handlePopoverClose} + className={classes.root} > {display || ( { const styles = makeStyles( (theme) => { return ({ + root: { + display: 'flex', + alignItems: 'center', + }, icon: { display: 'inline-block', fontSize: '1rem', diff --git a/src/screens/validators/components/list/components/desktop/index.tsx b/src/screens/validators/components/list/components/desktop/index.tsx index c3cc062ece..7ebf424883 100644 --- a/src/screens/validators/components/list/components/desktop/index.tsx +++ b/src/screens/validators/components/list/components/desktop/index.tsx @@ -9,6 +9,7 @@ import { useGrid } from '@hooks'; import { SortArrows, AvatarName, + InfoPopover, } from '@components'; import { getValidatorConditionClass } from '@utils/get_validator_condition'; import { getValidatorStatus } from '@utils/get_validator_status'; @@ -16,7 +17,9 @@ import { useStyles } from './styles'; import { fetchColumns } from './utils'; import { ItemType } from '../../types'; import { - Condition, VotingPower, + Condition, + VotingPower, + VotingPowerExplanation, } from '..'; const Desktop: React.FC<{ @@ -63,6 +66,7 @@ const Desktop: React.FC<{ percentDisplay={percentDisplay} percentage={x.votingPowerPercent} content={votingPower} + topVotingPower={x.topVotingPower} /> ), status: ( @@ -104,6 +108,26 @@ const Desktop: React.FC<{ sortKey: sortingKey, } = columns[columnIndex]; + let formattedComponent = component; + + if (key === 'votingPower') { + formattedComponent = ( + + {t('votingPower')} + } + /> + {!!sort && ( + + )} + + ); + } + return (
(sort ? props.handleSort(sortingKey) : null)} role="button" > - {component || ( + {formattedComponent || ( ), status, diff --git a/src/screens/validators/components/list/components/voting_power/index.tsx b/src/screens/validators/components/list/components/voting_power/index.tsx index f156cdc8d1..827485c3e5 100644 --- a/src/screens/validators/components/list/components/voting_power/index.tsx +++ b/src/screens/validators/components/list/components/voting_power/index.tsx @@ -8,10 +8,11 @@ const VotingPower: React.FC<{ percentage: number; percentDisplay: string; content: string; + topVotingPower: boolean; }> = ({ - className, percentage, content, percentDisplay, + className, percentage, content, percentDisplay, topVotingPower, }) => { - const classes = useStyles(percentage); + const classes = useStyles(percentage, topVotingPower); return (
diff --git a/src/screens/validators/components/list/components/voting_power/styles.ts b/src/screens/validators/components/list/components/voting_power/styles.ts index 9aaab6d6ee..c51cb00960 100644 --- a/src/screens/validators/components/list/components/voting_power/styles.ts +++ b/src/screens/validators/components/list/components/voting_power/styles.ts @@ -1,25 +1,37 @@ import { makeStyles } from '@material-ui/core/styles'; import Color from 'color'; -export const useStyles = (percentage: number) => { +export const useStyles = (percentage: number, topVotingPower:boolean) => { const styles = makeStyles( (theme) => { return ({ root: { '& .MuiTypography-body1': { - color: theme.palette.custom.fonts.fontTwo, + color: ( + topVotingPower + ? theme.palette.custom.fonts.fontFour + : theme.palette.custom.fonts.fontTwo + ), }, }, chart: { display: 'flex', height: '2px', borderRadius: theme.shape.borderRadius, - background: Color(theme.palette.custom.primaryData.three).alpha(0.2).string(), + background: ( + topVotingPower + ? Color(theme.palette.custom.fonts.fontFour).alpha(0.2).string() + : Color(theme.palette.custom.primaryData.three).alpha(0.2).string() + ), overflow: 'hidden', }, active: { width: `${percentage}%`, - background: theme.palette.custom.primaryData.three, + background: ( + topVotingPower + ? theme.palette.custom.fonts.fontFour + : theme.palette.custom.primaryData.three + ), }, content: { display: 'flex', @@ -27,7 +39,11 @@ export const useStyles = (percentage: number) => { justifyContent: 'space-between', marginBottom: theme.spacing(1), '& .percentage': { - color: theme.palette.custom.primaryData.three, + color: ( + topVotingPower + ? theme.palette.custom.fonts.fontFour + : theme.palette.custom.primaryData.three + ), }, [theme.breakpoints.up('lg')]: { marginBottom: 0, diff --git a/src/screens/validators/components/list/components/voting_power_explanation/index.tsx b/src/screens/validators/components/list/components/voting_power_explanation/index.tsx new file mode 100644 index 0000000000..5dca379845 --- /dev/null +++ b/src/screens/validators/components/list/components/voting_power_explanation/index.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import Trans from 'next-translate/Trans'; +import { Typography } from '@material-ui/core'; +import { useStyles } from './styles'; + +const VotingPowerExplanation = () => { + const classes = useStyles(); + + return ( +
+ + , + ]} + /> + +
+ ); +}; + +export default VotingPowerExplanation; diff --git a/src/screens/validators/components/list/components/voting_power_explanation/styles.ts b/src/screens/validators/components/list/components/voting_power_explanation/styles.ts new file mode 100644 index 0000000000..310137787b --- /dev/null +++ b/src/screens/validators/components/list/components/voting_power_explanation/styles.ts @@ -0,0 +1,15 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = () => { + const styles = makeStyles( + () => { + return ({ + root: { + height: '100%', + }, + }); + }, + )(); + + return styles; +}; diff --git a/src/screens/validators/components/list/hooks.ts b/src/screens/validators/components/list/hooks.ts index 8ced522b36..92d73ccb21 100644 --- a/src/screens/validators/components/list/hooks.ts +++ b/src/screens/validators/components/list/hooks.ts @@ -1,4 +1,5 @@ import { useState } from 'react'; +import Big from 'big.js'; import * as R from 'ramda'; import numeral from 'numeral'; import { @@ -12,7 +13,9 @@ import { SlashingParams, } from '@models'; import { - ValidatorsState, ItemType, + ValidatorsState, + ItemType, + ValidatorType, } from './types'; export const useValidators = () => { @@ -56,7 +59,7 @@ export const useValidators = () => { const { signedBlockWindow } = slashingParams; - const formattedItems = data.validator.filter((x) => x.validatorInfo).map((x) => { + let formattedItems: ValidatorType[] = data.validator.filter((x) => x.validatorInfo).map((x) => { const votingPower = R.pathOr(0, ['validatorVotingPowers', 0, 'votingPower'], x); const votingPowerPercent = numeral((votingPower / votingPowerOverall) * 100).value(); const totalDelegations = x.delegations.reduce((a, b) => { @@ -88,6 +91,28 @@ export const useValidators = () => { }); }); + // get the top 34% validators + formattedItems = formattedItems.filter((x) => x.status === 3).sort((a, b) => { + return a.votingPower > b.votingPower ? -1 : 1; + }); + + // add key to indicate they are part of top 34% + let cumulativeVotingPower = Big(0); + let reached = false; + formattedItems.forEach((x) => { + const totalVp = cumulativeVotingPower.add(x.votingPowerPercent); + if (totalVp.lte(34) && !reached) { + x.topVotingPower = true; + } + + if (totalVp.gt(34) && !reached) { + x.topVotingPower = true; + reached = true; + } + + cumulativeVotingPower = totalVp; + }); + return { votingPowerOverall, items: formattedItems, diff --git a/src/screens/validators/components/list/types.ts b/src/screens/validators/components/list/types.ts index a104b1fbaf..57202aa18c 100644 --- a/src/screens/validators/components/list/types.ts +++ b/src/screens/validators/components/list/types.ts @@ -8,6 +8,7 @@ export type ValidatorType = { status: number; jailed: boolean; delegators: number; + topVotingPower?: boolean; // top 34% VP } export type ValidatorsState = {