feat(2025): validator table broken into sub-tables (#2290)
* feat(2025): validator table broken into sub-tables and showing all the new data * feat(2025): fixed test broken due to a refactor * feat(2025): removed unused redundancy in shared.tsx * feat(2025): tweak to types import location in consensus-validators-table.spec.tsx * feat(2025): removed tests for deleted validator table elements
This commit is contained in:
parent
cf2cfad746
commit
a30f2227c4
@ -12,8 +12,6 @@ const stakedByDelegates = '[data-testid="staked-by-delegates"]';
|
||||
const stakeShare = '[data-testid="stake-percentage"]';
|
||||
const epochCountDown = '[data-testid="epoch-countdown"]';
|
||||
const stakeNumberRegex = /^\d*\.?\d*$/;
|
||||
const ownStake = '[data-testid="own-stake"]';
|
||||
const nominatedStake = '[data-testid="nominated-stake"]';
|
||||
|
||||
context('Staking Page - verify elements on page', function () {
|
||||
before('navigate to staking page', function () {
|
||||
@ -173,16 +171,6 @@ context('Staking Page - verify elements on page', function () {
|
||||
});
|
||||
});
|
||||
|
||||
// 2001-STKE-013
|
||||
it('Should be able to see own stake this epoch', function () {
|
||||
cy.get(ownStake).invoke('text').should('match', stakeNumberRegex);
|
||||
});
|
||||
|
||||
// 2001-STKE-014
|
||||
it('Should be able to see nominated stake this epoch', function () {
|
||||
cy.get(nominatedStake).invoke('text').should('match', stakeNumberRegex);
|
||||
});
|
||||
|
||||
// 2001-STKE-011 2002-SINC-001 2002-SINC-002
|
||||
it('Should be able to see epoch information', function () {
|
||||
const epochTitle = 'h3';
|
||||
|
@ -537,7 +537,8 @@
|
||||
"unknownEthereumConnectionError": "An unknown error occurred. Check the console in your browser's web developer tools for more details",
|
||||
"stakingDescriptionTitle": "How does staking on Vega work?",
|
||||
"stakingDescription1": "1. VEGA is an ERC20 token. Associate it with a Vega wallet using the",
|
||||
"stakingDescription2": "2. Use this site and your Vega wallet to nominate a validator. View the validator profile pitches and discussion",
|
||||
"stakingDescription2a": "2. Use this site and your Vega wallet to nominate a validator.",
|
||||
"stakingDescription2b": "View the validator profile pitches and discussion",
|
||||
"stakingDescription3": "3. Earn a share of trading fees and treasury rewards for each full epoch staked",
|
||||
"stakingDescription4": "4. Move your stake if your validator is penalised",
|
||||
"stakingBridge": "staking bridge",
|
||||
|
@ -2,7 +2,7 @@ import { useEffect } from 'react';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { EpochCountdown } from '../../../components/epoch-countdown';
|
||||
import { useNodesQuery } from './__generated___/Nodes';
|
||||
import { usePreviousEpochQuery } from './__generated___/PreviousEpoch';
|
||||
import { usePreviousEpochQuery } from '../__generated___/PreviousEpoch';
|
||||
import { ValidatorTables } from './validator-tables';
|
||||
|
||||
export const EpochData = () => {
|
||||
|
@ -32,7 +32,7 @@ export const StakingIntro = () => {
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
{t('stakingDescription2')}{' '}
|
||||
{t('stakingDescription2a')} {t('stakingDescription2b')}{' '}
|
||||
<UTLink
|
||||
href={ExternalLinks.VALIDATOR_FORUM}
|
||||
target="_blank"
|
||||
|
@ -4,13 +4,13 @@ import { ConsensusValidatorsTable } from './consensus-validators-table';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { NodesDocument } from '../__generated___/Nodes';
|
||||
import { PreviousEpochDocument } from '../__generated___/PreviousEpoch';
|
||||
import { PreviousEpochDocument } from '../../__generated___/PreviousEpoch';
|
||||
import { Schema } from '@vegaprotocol/types';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import type { NodesFragmentFragment } from '../__generated___/Nodes';
|
||||
import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch';
|
||||
import type { PreviousEpochQuery } from '../../__generated___/PreviousEpoch';
|
||||
|
||||
const nodeFactory = (
|
||||
overrides?: PartialDeep<NodesFragmentFragment>
|
||||
|
@ -3,6 +3,8 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { AgGridDynamic as AgGrid, Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useAppState } from '../../../../contexts/app-state/app-state-context';
|
||||
import { BigNumber } from '../../../../lib/bignumber';
|
||||
import { normalisedVotingPower, rawValidatorScore } from '../../shared';
|
||||
import {
|
||||
defaultColDef,
|
||||
NODE_LIST_GRID_STYLES,
|
||||
@ -47,8 +49,8 @@ export const ConsensusValidatorsTable = ({
|
||||
|
||||
const canonisedNodes = data
|
||||
.sort((a, b) => {
|
||||
const aVotingPower = toBigNum(a.rankingScore.votingPower, 0);
|
||||
const bVotingPower = toBigNum(b.rankingScore.votingPower, 0);
|
||||
const aVotingPower = new BigNumber(a.rankingScore.votingPower);
|
||||
const bVotingPower = new BigNumber(b.rankingScore.votingPower);
|
||||
return bVotingPower.minus(aVotingPower).toNumber();
|
||||
})
|
||||
.map((node, index) => {
|
||||
@ -69,9 +71,6 @@ export const ConsensusValidatorsTable = ({
|
||||
pendingStake,
|
||||
votingPowerRanking,
|
||||
}) => {
|
||||
const normalisedVotingPower =
|
||||
toBigNum(votingPower, 0).dividedBy(100).dp(2).toString() + '%';
|
||||
|
||||
return {
|
||||
id,
|
||||
[ValidatorFields.RANKING_INDEX]: votingPowerRanking,
|
||||
@ -83,11 +82,11 @@ export const ConsensusValidatorsTable = ({
|
||||
toBigNum(stakedTotal, decimals),
|
||||
2
|
||||
),
|
||||
[ValidatorFields.NORMALISED_VOTING_POWER]: normalisedVotingPower,
|
||||
[ValidatorFields.NORMALISED_VOTING_POWER]:
|
||||
normalisedVotingPower(votingPower),
|
||||
[ValidatorFields.STAKE_SHARE]: stakedTotalPercentage(stakeScore),
|
||||
[ValidatorFields.TOTAL_PENALTIES]: totalPenalties(
|
||||
previousEpochData,
|
||||
id,
|
||||
rawValidatorScore(previousEpochData, id),
|
||||
performanceScore,
|
||||
stakedTotal,
|
||||
totalStake
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { rawValidatorScore } from '../../shared';
|
||||
import { stakedTotalPercentage, totalPenalties } from './shared';
|
||||
|
||||
const mockPreviousEpochData = {
|
||||
@ -27,19 +28,34 @@ describe('stakedTotalPercentage', () => {
|
||||
describe('totalPenalties', () => {
|
||||
it('should return the correct penalty based on arbitrary values, test 1', () => {
|
||||
expect(
|
||||
totalPenalties(mockPreviousEpochData, '0x123', '0.1', '5000', '100000')
|
||||
totalPenalties(
|
||||
rawValidatorScore(mockPreviousEpochData, '0x123'),
|
||||
'0.1',
|
||||
'5000',
|
||||
'100000'
|
||||
)
|
||||
).toBe('50%');
|
||||
});
|
||||
|
||||
it('should return the correct penalty based on lower performance score than first test', () => {
|
||||
expect(
|
||||
totalPenalties(mockPreviousEpochData, '0x123', '0.05', '5000', '100000')
|
||||
totalPenalties(
|
||||
rawValidatorScore(mockPreviousEpochData, '0x123'),
|
||||
'0.05',
|
||||
'5000',
|
||||
'100000'
|
||||
)
|
||||
).toBe('75%');
|
||||
});
|
||||
|
||||
it('should return the correct penalty based on higher amount of stake than other tests (great penalty due to anti-whaling)', () => {
|
||||
expect(
|
||||
totalPenalties(mockPreviousEpochData, '0x123', '0.1', '5000', '5500')
|
||||
totalPenalties(
|
||||
rawValidatorScore(mockPreviousEpochData, '0x123'),
|
||||
'0.1',
|
||||
'5000',
|
||||
'5500'
|
||||
)
|
||||
).toBe('97.25%');
|
||||
});
|
||||
});
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { removePaginationWrapper, toBigNum } from '@vegaprotocol/react-helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toBigNum } from '@vegaprotocol/react-helpers';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import type { NodesFragmentFragment } from '../__generated___/Nodes';
|
||||
import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { PreviousEpochQuery } from '../../__generated___/PreviousEpoch';
|
||||
|
||||
export enum ValidatorFields {
|
||||
RANKING_INDEX = 'rankingIndex',
|
||||
@ -37,19 +37,11 @@ export const stakedTotalPercentage = (stakeScore: string) =>
|
||||
toBigNum(stakeScore, 0).times(100).dp(2).toString() + '%';
|
||||
|
||||
export const totalPenalties = (
|
||||
previousEpochData: PreviousEpochQuery | undefined,
|
||||
id: string,
|
||||
rawValidatorScore: string | null | undefined,
|
||||
performanceScore: string,
|
||||
stakedTotal: string,
|
||||
totalStake: string
|
||||
) => {
|
||||
const rawValidatorScore = previousEpochData
|
||||
? removePaginationWrapper(
|
||||
previousEpochData.epoch?.validatorsConnection?.edges
|
||||
).find((validator) => validator?.id === id)?.rewardScore
|
||||
?.rawValidatorScore
|
||||
: null;
|
||||
|
||||
const totalPenaltiesCalc =
|
||||
rawValidatorScore !== null
|
||||
? 100 *
|
||||
|
@ -3,6 +3,8 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
||||
import { useAppState } from '../../../../contexts/app-state/app-state-context';
|
||||
import { BigNumber } from '../../../../lib/bignumber';
|
||||
import { rawValidatorScore } from '../../shared';
|
||||
import {
|
||||
defaultColDef,
|
||||
NODE_LIST_GRID_STYLES,
|
||||
@ -49,9 +51,9 @@ export const StandbyPendingValidatorsTable = ({
|
||||
let individualStakeNeededForPromotion = undefined;
|
||||
|
||||
if (stakeNeededForPromotion) {
|
||||
const stakedTotalBigNum = toBigNum(stakedTotal, 0);
|
||||
const stakeNeededBigNum = toBigNum(stakeNeededForPromotion, 0);
|
||||
const performanceScoreBigNum = toBigNum(performanceScore, 0);
|
||||
const stakedTotalBigNum = new BigNumber(stakedTotal);
|
||||
const stakeNeededBigNum = new BigNumber(stakeNeededForPromotion);
|
||||
const performanceScoreBigNum = new BigNumber(performanceScore);
|
||||
|
||||
const calc = stakeNeededBigNum
|
||||
.dividedBy(performanceScoreBigNum)
|
||||
@ -76,8 +78,7 @@ export const StandbyPendingValidatorsTable = ({
|
||||
individualStakeNeededForPromotion || t('n/a'),
|
||||
[ValidatorFields.STAKE_SHARE]: stakedTotalPercentage(stakeScore),
|
||||
[ValidatorFields.TOTAL_PENALTIES]: totalPenalties(
|
||||
previousEpochData,
|
||||
id,
|
||||
rawValidatorScore(previousEpochData, id),
|
||||
performanceScore,
|
||||
stakedTotal,
|
||||
totalStake
|
||||
|
@ -7,7 +7,7 @@ import type {
|
||||
NodesQuery,
|
||||
NodesFragmentFragment,
|
||||
} from '../__generated___/Nodes';
|
||||
import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch';
|
||||
import type { PreviousEpochQuery } from '../../__generated___/PreviousEpoch';
|
||||
import { formatNumber } from '../../../../lib/format-number';
|
||||
import {
|
||||
createDocsLinks,
|
||||
|
@ -2,6 +2,12 @@ import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
addDecimal,
|
||||
removePaginationWrapper,
|
||||
toBigNum,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { EpochCountdown } from '../../../components/epoch-countdown';
|
||||
import { BigNumber } from '../../../lib/bignumber';
|
||||
import { ConnectToVega } from '../../../components/connect-to-vega';
|
||||
@ -9,41 +15,39 @@ import { StakingForm } from './staking-form';
|
||||
import { ValidatorTable } from './validator-table';
|
||||
import { YourStake } from './your-stake';
|
||||
import NodeContainer from './nodes-container';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useAppState } from '../../../contexts/app-state/app-state-context';
|
||||
import {
|
||||
addDecimal,
|
||||
removePaginationWrapper,
|
||||
toBigNum,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import type { StakingQuery } from './__generated___/Staking';
|
||||
import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch';
|
||||
|
||||
interface StakingNodeProps {
|
||||
data?: StakingQuery;
|
||||
previousEpochData?: PreviousEpochQuery;
|
||||
}
|
||||
|
||||
export const StakingNode = ({ data }: StakingNodeProps) => {
|
||||
export const StakingNode = ({ data, previousEpochData }: StakingNodeProps) => {
|
||||
const { pubKey: vegaKey } = useVegaWallet();
|
||||
const {
|
||||
appState: { decimals },
|
||||
} = useAppState();
|
||||
const { node } = useParams<{ node: string }>();
|
||||
const { t } = useTranslation();
|
||||
const nodeInfo = React.useMemo(
|
||||
() =>
|
||||
removePaginationWrapper(data?.nodesConnection?.edges).find(
|
||||
|
||||
const { nodeInfo, currentEpoch, delegations } = React.useMemo(
|
||||
() => ({
|
||||
nodeInfo: removePaginationWrapper(data?.nodesConnection?.edges).find(
|
||||
({ id }) => id === node
|
||||
),
|
||||
[node, data]
|
||||
);
|
||||
|
||||
const currentEpoch = React.useMemo(() => {
|
||||
return data?.epoch.id;
|
||||
}, [data?.epoch.id]);
|
||||
|
||||
const delegations = React.useMemo(
|
||||
() => removePaginationWrapper(data?.party?.delegationsConnection?.edges),
|
||||
[data?.party?.delegationsConnection?.edges]
|
||||
currentEpoch: data?.epoch.id,
|
||||
delegations: removePaginationWrapper(
|
||||
data?.party?.delegationsConnection?.edges
|
||||
),
|
||||
}),
|
||||
[
|
||||
data?.epoch.id,
|
||||
data?.nodesConnection?.edges,
|
||||
data?.party?.delegationsConnection?.edges,
|
||||
node,
|
||||
]
|
||||
);
|
||||
|
||||
const stakeThisEpoch = React.useMemo(() => {
|
||||
@ -105,7 +109,7 @@ export const StakingNode = ({ data }: StakingNodeProps) => {
|
||||
<ValidatorTable
|
||||
node={nodeInfo}
|
||||
stakedTotal={addDecimal(data?.nodeData?.stakedTotal || '0', decimals)}
|
||||
stakeThisEpoch={stakeThisEpoch}
|
||||
previousEpochData={previousEpochData}
|
||||
/>
|
||||
</section>
|
||||
{data?.epoch.timestamps.start && data?.epoch.timestamps.expiry && (
|
||||
@ -148,6 +152,10 @@ export const StakingNode = ({ data }: StakingNodeProps) => {
|
||||
|
||||
export const Node = () => {
|
||||
return (
|
||||
<NodeContainer>{({ data }) => <StakingNode data={data} />}</NodeContainer>
|
||||
<NodeContainer>
|
||||
{({ data, previousEpochData }) => (
|
||||
<StakingNode data={data} previousEpochData={previousEpochData} />
|
||||
)}
|
||||
</NodeContainer>
|
||||
);
|
||||
};
|
||||
|
@ -5,7 +5,9 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useStakingQuery } from './__generated___/Staking';
|
||||
import { SplashLoader } from '../../../components/splash-loader';
|
||||
import { usePreviousEpochQuery } from '../__generated___/PreviousEpoch';
|
||||
import type { StakingQuery } from './__generated___/Staking';
|
||||
import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch';
|
||||
|
||||
// TODO should only request a single node. When migrating from deprecated APIs we should address this.
|
||||
|
||||
@ -14,14 +16,28 @@ const RPC_ERROR = 'rpc error: code = NotFound desc = NotFound error';
|
||||
export const NodeContainer = ({
|
||||
children,
|
||||
}: {
|
||||
children: ({ data }: { data?: StakingQuery }) => React.ReactElement;
|
||||
children: ({
|
||||
data,
|
||||
previousEpochData,
|
||||
}: {
|
||||
data?: StakingQuery;
|
||||
previousEpochData?: PreviousEpochQuery;
|
||||
}) => React.ReactElement;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { data, loading, error, refetch } = useStakingQuery({
|
||||
variables: { partyId: pubKey || '' },
|
||||
});
|
||||
const { data: previousEpochData } = usePreviousEpochQuery({
|
||||
variables: {
|
||||
epochId: (Number(data?.epoch.id) - 1).toString(),
|
||||
},
|
||||
skip: !data?.epoch.id,
|
||||
});
|
||||
|
||||
// @todo - this epoch querying needs to be hoisted as the validator tables rely
|
||||
// on it too
|
||||
React.useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
if (!data?.epoch.timestamps.expiry) return;
|
||||
@ -59,7 +75,7 @@ export const NodeContainer = ({
|
||||
);
|
||||
}
|
||||
|
||||
return children({ data });
|
||||
return children({ data, previousEpochData });
|
||||
};
|
||||
|
||||
export default NodeContainer;
|
||||
|
@ -1,15 +1,20 @@
|
||||
import React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||
import countryData from '../../../components/country-selector/country-data';
|
||||
import { Link as UTLink, Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
|
||||
import { BigNumber } from '../../../lib/bignumber';
|
||||
import { formatNumber } from '../../../lib/format-number';
|
||||
import type { StakingNodeFieldsFragment } from './__generated___/Staking';
|
||||
import { toBigNum } from '@vegaprotocol/react-helpers';
|
||||
import { ExternalLinks, toBigNum } from '@vegaprotocol/react-helpers';
|
||||
import { useAppState } from '../../../contexts/app-state/app-state-context';
|
||||
import { Schema } from '@vegaprotocol/types';
|
||||
import { totalPenalties } from '../home/validator-tables/shared';
|
||||
import { normalisedVotingPower, rawValidatorScore } from '../shared';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { StakingNodeFieldsFragment } from './__generated___/Staking';
|
||||
import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch';
|
||||
|
||||
const statuses = {
|
||||
[Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_ERSATZ]: 'status-ersatz',
|
||||
@ -25,10 +30,10 @@ const ValidatorTableCell = ({
|
||||
children,
|
||||
dataTestId,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
children: ReactNode;
|
||||
dataTestId?: string;
|
||||
}) => (
|
||||
<span datatest-id={dataTestId} className="break-words">
|
||||
<span data-testid={dataTestId} className="break-words">
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
@ -36,109 +41,214 @@ const ValidatorTableCell = ({
|
||||
export interface ValidatorTableProps {
|
||||
node: StakingNodeFieldsFragment;
|
||||
stakedTotal: string;
|
||||
stakeThisEpoch: BigNumber;
|
||||
stakedTotalAllNodes?: string;
|
||||
previousEpochData?: PreviousEpochQuery;
|
||||
}
|
||||
|
||||
export const ValidatorTable = ({
|
||||
node,
|
||||
stakedTotal,
|
||||
stakeThisEpoch,
|
||||
previousEpochData,
|
||||
}: ValidatorTableProps) => {
|
||||
const { ETHERSCAN_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
appState: { decimals },
|
||||
} = useAppState();
|
||||
const stakePercentage = React.useMemo(() => {
|
||||
const total = new BigNumber(stakedTotal);
|
||||
const stakedOnNode = toBigNum(node.stakedTotal, decimals);
|
||||
const stakedTotalPercentage =
|
||||
total.isEqualTo(0) || stakedOnNode.isEqualTo(0)
|
||||
? '-'
|
||||
: stakedOnNode.dividedBy(total).times(100).dp(2).toString() + '%';
|
||||
return stakedTotalPercentage;
|
||||
}, [decimals, node.stakedTotal, stakedTotal]);
|
||||
|
||||
const total = useMemo(() => new BigNumber(stakedTotal), [stakedTotal]);
|
||||
|
||||
const stakedOnNode = toBigNum(node.stakedTotal, decimals);
|
||||
|
||||
const location = countryData.find((c) => c.code === node.location)?.name;
|
||||
|
||||
const performanceScore = new BigNumber(node.rankingScore.performanceScore).dp(
|
||||
2
|
||||
);
|
||||
|
||||
const validatorScore = rawValidatorScore(previousEpochData, node.id);
|
||||
|
||||
const overstakedAmount = useMemo(() => {
|
||||
const amount = validatorScore
|
||||
? new BigNumber(validatorScore).times(total).minus(stakedOnNode).dp(2)
|
||||
: new BigNumber(0);
|
||||
|
||||
return amount.isNegative() ? new BigNumber(0) : amount;
|
||||
}, [stakedOnNode, total, validatorScore]);
|
||||
|
||||
const stakePercentage =
|
||||
total.isEqualTo(0) || stakedOnNode.isEqualTo(0)
|
||||
? '-'
|
||||
: stakedOnNode.dividedBy(total).times(100).dp(2).toString() + '%';
|
||||
|
||||
const performancePenalty =
|
||||
new BigNumber(1).minus(performanceScore).times(100).toString() + '%';
|
||||
|
||||
const totalPenaltiesAmount = totalPenalties(
|
||||
validatorScore,
|
||||
node.rankingScore.performanceScore,
|
||||
stakedOnNode.toString(),
|
||||
total.toString()
|
||||
);
|
||||
|
||||
const overstakingPenalty = useMemo(
|
||||
() =>
|
||||
overstakedAmount.dividedBy(stakedOnNode).times(100).dp(2).toString() +
|
||||
'%',
|
||||
[overstakedAmount, stakedOnNode]
|
||||
);
|
||||
|
||||
const unnormalisedVotingPower = validatorScore
|
||||
? new BigNumber(validatorScore).times(100).dp(2).toString() + '%'
|
||||
: null;
|
||||
|
||||
return (
|
||||
<KeyValueTable data-testid="validator-table">
|
||||
<KeyValueTableRow>
|
||||
<span>{t('id')}:</span>
|
||||
<ValidatorTableCell dataTestId="validator-id">
|
||||
{node.id}
|
||||
</ValidatorTableCell>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('VEGA ADDRESS / PUBLIC KEY')}</span>
|
||||
<ValidatorTableCell dataTestId="validator-public-key">
|
||||
{node.pubkey}
|
||||
</ValidatorTableCell>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('ABOUT THIS VALIDATOR')}</span>
|
||||
<span>
|
||||
<a href={node.infoUrl}>{node.infoUrl}</a>
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('STATUS')}</span>
|
||||
<span data-testid="validator-status">
|
||||
{t(statusTranslationKey(node.rankingScore.status))}
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('IP ADDRESS')}</span>
|
||||
<span>{node.location}</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('ETHEREUM ADDRESS')}</span>
|
||||
<span>
|
||||
<Link
|
||||
title={t('View on Etherscan (opens in a new tab)')}
|
||||
href={`${ETHERSCAN_URL}/address/${node.ethereumAddress}`}
|
||||
target="_blank"
|
||||
>
|
||||
{node.ethereumAddress}
|
||||
</Link>
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('TOTAL STAKE')}</span>
|
||||
<span data-testid="total-stake">
|
||||
{formatNumber(toBigNum(node.stakedTotal, decimals))}
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('PENDING STAKE')}</span>
|
||||
<span data-testid="pending-stake">
|
||||
{formatNumber(toBigNum(node.pendingStake, decimals))}
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('STAKED BY OPERATOR')}</span>
|
||||
<span data-testid="staked-by-operator">
|
||||
{formatNumber(toBigNum(node.stakedByOperator, decimals))}
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('STAKED BY DELEGATES')}</span>
|
||||
<span data-testid="staked-by-delegates">
|
||||
{formatNumber(toBigNum(node.stakedByDelegates, 18))}
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('STAKE SHARE')}</span>
|
||||
<span data-testid="stake-percentage">{stakePercentage}</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('OWN STAKE (THIS EPOCH)')}</span>
|
||||
<span data-testid="own-stake">{formatNumber(stakeThisEpoch)}</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('NOMINATED (THIS EPOCH)')}</span>
|
||||
<span data-testid="nominated-stake">
|
||||
{formatNumber(toBigNum(node.stakedByDelegates, decimals))}
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
<div className="mb-8" data-testid="validator-table">
|
||||
<KeyValueTable data-testid="validator-table-profile" title={t('PROFILE')}>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('id')}</span>
|
||||
<ValidatorTableCell dataTestId="validator-id">
|
||||
{node.id}
|
||||
</ValidatorTableCell>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('ABOUT THIS VALIDATOR')}</span>
|
||||
<span>
|
||||
<a href={node.infoUrl}>{node.infoUrl}</a>
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>
|
||||
<strong>{t('STATUS')}</strong>
|
||||
</span>
|
||||
<span data-testid="validator-status">
|
||||
<strong>{t(statusTranslationKey(node.rankingScore.status))}</strong>
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
|
||||
<div className="mb-6 text-sm">
|
||||
{t('stakingDescription2b')}{' '}
|
||||
<UTLink
|
||||
href={ExternalLinks.VALIDATOR_FORUM}
|
||||
target="_blank"
|
||||
data-testid="validator-forum-link"
|
||||
>
|
||||
{t('onTheForum')}
|
||||
</UTLink>
|
||||
</div>
|
||||
|
||||
<KeyValueTable data-testid="validator-table-address" title={t('ADDRESS')}>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('VEGA ADDRESS / PUBLIC KEY')}</span>
|
||||
<ValidatorTableCell dataTestId="validator-public-key">
|
||||
{node.pubkey}
|
||||
</ValidatorTableCell>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('SERVER LOCATION')}</span>
|
||||
<ValidatorTableCell>
|
||||
{location || t('not available')}
|
||||
</ValidatorTableCell>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('ETHEREUM ADDRESS')}</span>
|
||||
<span>
|
||||
<Link
|
||||
title={t('View on Etherscan (opens in a new tab)')}
|
||||
href={`${ETHERSCAN_URL}/address/${node.ethereumAddress}`}
|
||||
target="_blank"
|
||||
>
|
||||
{node.ethereumAddress}
|
||||
</Link>
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
|
||||
<KeyValueTable data-testid="validator-table-stake" title={t('STAKE')}>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('STAKED BY OPERATOR')}</span>
|
||||
<span data-testid="staked-by-operator">
|
||||
{formatNumber(toBigNum(node.stakedByOperator, decimals))}
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('STAKED BY DELEGATES')}</span>
|
||||
<span data-testid="staked-by-delegates">
|
||||
{formatNumber(toBigNum(node.stakedByDelegates, decimals))}
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>
|
||||
<strong>{t('TOTAL STAKE')}</strong>
|
||||
</span>
|
||||
<span data-testid="total-stake">
|
||||
<strong>
|
||||
{formatNumber(toBigNum(node.stakedTotal, decimals))}
|
||||
</strong>
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('PENDING STAKE')}</span>
|
||||
<span data-testid="pending-stake">
|
||||
{formatNumber(toBigNum(node.pendingStake, decimals))}
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('STAKE SHARE')}</span>
|
||||
<span data-testid="stake-percentage">{stakePercentage}</span>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
|
||||
<KeyValueTable
|
||||
data-testid="validator-table-penalties"
|
||||
title={t('PENALTIES')}
|
||||
>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('OVERSTAKED AMOUNT')}</span>
|
||||
<span>{overstakedAmount.toString()}</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('OVERSTAKED PENALTY')}</span>
|
||||
<span>{overstakingPenalty}</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('PERFORMANCE SCORE')}</span>
|
||||
<span>{performanceScore.toString()}</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('PERFORMANCE PENALITY')}</span>
|
||||
<span>{performancePenalty}</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>
|
||||
<strong>{t('TOTAL PENALTIES')}</strong>
|
||||
</span>
|
||||
<span>
|
||||
<strong>{totalPenaltiesAmount}</strong>
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
|
||||
<KeyValueTable
|
||||
data-testid="validator-table-voting-power"
|
||||
title={t('VOTING POWER')}
|
||||
>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('UNNORMALISED VOTING POWER')}</span>
|
||||
<span>{unnormalisedVotingPower}</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>
|
||||
<strong>{t('NORMALISED VOTING POWER')}</strong>
|
||||
</span>
|
||||
<span>
|
||||
<strong>
|
||||
{normalisedVotingPower(node.rankingScore.votingPower)}
|
||||
</strong>
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
18
apps/token/src/routes/staking/shared.ts
Normal file
18
apps/token/src/routes/staking/shared.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { removePaginationWrapper, toBigNum } from '@vegaprotocol/react-helpers';
|
||||
import type { PreviousEpochQuery } from './__generated___/PreviousEpoch';
|
||||
|
||||
export const rawValidatorScore = (
|
||||
previousEpochData: PreviousEpochQuery | undefined,
|
||||
id: string
|
||||
) => {
|
||||
return previousEpochData
|
||||
? removePaginationWrapper(
|
||||
previousEpochData.epoch?.validatorsConnection?.edges
|
||||
).find((validator) => validator?.id === id)?.rewardScore
|
||||
?.rawValidatorScore
|
||||
: null;
|
||||
};
|
||||
|
||||
export const normalisedVotingPower = (votingPower: string) => {
|
||||
return toBigNum(votingPower, 0).dividedBy(100).dp(2).toString() + '%';
|
||||
};
|
Loading…
Reference in New Issue
Block a user