From b3542dfa5225d6079116f5a1d7e04380c08ef8a3 Mon Sep 17 00:00:00 2001 From: Jefferson Youashi <119521983+clintjeff2@users.noreply.github.com> Date: Wed, 24 Jun 2026 21:31:16 +0100 Subject: [PATCH] Add validator consolidation dashboard --- .../validators/ConsolidationDashboard.tsx | 85 ++++++++++++++++++ .../validators/ValidatorDashboard.tsx | 25 ++++++ src/hooks/useConsolidationEligibility.ts | 76 ++++++++++++++++ src/utils/consolidationEligibility.ts | 88 +++++++++++++++++++ src/utils/ethMath.ts | 15 ++++ src/utils/gasEstimator.ts | 28 ++++++ src/workers/consolidationScannerWorker.ts | 59 +++++++++++++ 7 files changed, 376 insertions(+) create mode 100644 src/components/validators/ConsolidationDashboard.tsx create mode 100644 src/hooks/useConsolidationEligibility.ts create mode 100644 src/utils/consolidationEligibility.ts create mode 100644 src/utils/ethMath.ts create mode 100644 src/utils/gasEstimator.ts create mode 100644 src/workers/consolidationScannerWorker.ts diff --git a/src/components/validators/ConsolidationDashboard.tsx b/src/components/validators/ConsolidationDashboard.tsx new file mode 100644 index 0000000..d59ded6 --- /dev/null +++ b/src/components/validators/ConsolidationDashboard.tsx @@ -0,0 +1,85 @@ +'use client' + +import { useMemo, useState } from 'react' +import type { ValidatorReconciliation } from '@/src/hooks/useValidatorBalances' +import { useConsolidationEligibility } from '@/src/hooks/useConsolidationEligibility' +import { sortConsolidationRecommendations, type ConsolidationSortKey } from '@/src/utils/consolidationEligibility' + +interface ConsolidationDashboardProps { + validatorIndices: number[] + byValidator: Record +} + +const sortLabels: Record = { + savings: 'Potential savings', + validator_count: 'Validator count', + readiness_score: 'Readiness score', +} + +export function ConsolidationDashboard({ validatorIndices, byValidator }: ConsolidationDashboardProps) { + const [sortBy, setSortBy] = useState('savings') + const { recommendations, processed, total, isLoading, error } = useConsolidationEligibility(validatorIndices, byValidator) + const sorted = useMemo(() => sortConsolidationRecommendations(recommendations, sortBy), [recommendations, sortBy]) + + return ( +
+
+
+

EIP-7251 consolidation eligibility

+

+ Scans up to 10,000 validators in 500-validator worker chunks and groups validators that share withdrawal credentials. +

+
+ +
+ + {isLoading &&

Scanning validators… {processed.toLocaleString()} / {total.toLocaleString()}

} + {error &&

{error}

} + +
+ + + + + + + + + + + + + {sorted.map((group) => ( + + + + + + + + + ))} + {!isLoading && sorted.length === 0 && ( + + + + )} + +
Group IDCurrent countMerged countPotential gas savings/yearReadiness scoreTotal effective ETH
{group.groupId}{group.currentCount.toLocaleString()}{group.mergedCount.toLocaleString()}{group.estimatedAnnualGasSavings.toLocaleString()} gas{(group.readinessScore * 100).toFixed(1)}%{group.totalEffectiveBalanceEth.toFixed(2)}
+ No consolidation-eligible groups found for this validator set. +
+
+
+ ) +} diff --git a/src/components/validators/ValidatorDashboard.tsx b/src/components/validators/ValidatorDashboard.tsx index 3e1b355..3e5716f 100644 --- a/src/components/validators/ValidatorDashboard.tsx +++ b/src/components/validators/ValidatorDashboard.tsx @@ -6,6 +6,7 @@ import { ValidatorUnlockCard } from '@/src/components/validators/ValidatorUnlock import { ExitQueuePositionCard } from '@/src/components/validators/ExitQueuePositionCard' import { CommitteeTopologyMap } from '@/src/components/canvas/CommitteeTopologyMap' import { ShardLegend } from '@/src/components/validators/ShardLegend' +import { ConsolidationDashboard } from '@/src/components/validators/ConsolidationDashboard' import { useValidatorBalances } from '@/src/hooks/useValidatorBalances' import { useCommitteeAssignments } from '@/src/hooks/useCommitteeAssignments' @@ -26,6 +27,7 @@ export function ValidatorDashboard({ const [open, setOpen] = useState(true) const [queueOpen, setQueueOpen] = useState(true) const [topoOpen, setTopoOpen] = useState(true) + const [consolidationOpen, setConsolidationOpen] = useState(true) const { byValidator } = useValidatorBalances(validatorIndices, { beaconNodeUrl }) const committee = useCommitteeAssignments(validatorIndices, { beaconNodeUrl }) @@ -102,6 +104,29 @@ export function ValidatorDashboard({ )} +
+ + + {consolidationOpen && ( + + )} +
+