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 && ( + + )} +
+