From f16b3912b05cae1a45faffb510c1510eb893345d Mon Sep 17 00:00:00 2001 From: Ricki Moore Date: Thu, 2 Nov 2023 12:09:43 +0100 Subject: [PATCH] Feat/proposer duties (#226) --- src/App.tsx | 15 +- src/components/DiagnosticTable/AlertInfo.tsx | 12 +- src/components/Fallback/AppLoadFallback.tsx | 14 ++ src/components/ProposerAlerts/AlertGroup.tsx | 76 ++++++++ .../ProposerAlerts/ProposalAlert.tsx | 41 +++++ .../ProposerAlerts/ProposerAlerts.tsx | 58 +++++++ src/constants/constants.ts | 2 - .../__tests__/useEpochAprEstimate.spec.ts | 28 ++- .../useValidatorEpochBalance.spec.ts | 13 ++ src/hooks/useBeaconSyncPolling.ts | 6 +- src/hooks/useEpochAprEstimate.ts | 8 +- src/hooks/useProposerDutiesPolling.ts | 48 +++++ src/hooks/useValidatorEarnings.ts | 18 +- src/hooks/useValidatorEpochBalance.ts | 6 +- src/hooks/useValidatorInfoPolling.ts | 5 +- src/hooks/useValidatorPeerPolling.ts | 5 +- src/hooks/useValidatorSyncPolling.ts | 5 +- src/locales/translations/en-US.json | 14 +- src/mocks/beaconSpec.ts | 7 + src/mocks/validatorResults.ts | 164 +++++++++--------- src/recoil/atoms.ts | 14 +- src/recoil/selectors/selectBnSpec.ts | 2 +- src/types/beacon.ts | 1 + src/types/index.ts | 7 + .../__tests__/calculateEpochEstimate.spec.ts | 6 +- src/utilities/calculateEpochEstimate.ts | 10 +- src/utilities/formatUniqueObjectArray.ts | 9 + src/utilities/getSlotTimeData.ts | 15 ++ src/utilities/groupArray.ts | 9 + src/wrappers/MainPollingWrapper.tsx | 2 + 30 files changed, 492 insertions(+), 128 deletions(-) create mode 100644 src/components/Fallback/AppLoadFallback.tsx create mode 100644 src/components/ProposerAlerts/AlertGroup.tsx create mode 100644 src/components/ProposerAlerts/ProposalAlert.tsx create mode 100644 src/components/ProposerAlerts/ProposerAlerts.tsx create mode 100644 src/hooks/useProposerDutiesPolling.ts create mode 100644 src/mocks/beaconSpec.ts create mode 100644 src/utilities/formatUniqueObjectArray.ts create mode 100644 src/utilities/getSlotTimeData.ts create mode 100644 src/utilities/groupArray.ts diff --git a/src/App.tsx b/src/App.tsx index 7c631bb8..e7090d1d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { Suspense } from 'react' import { useRecoilValue } from 'recoil' import { appView } from './recoil/atoms' import { AppView } from './constants/enums' @@ -11,6 +11,7 @@ import 'rodal/lib/rodal.css' import SSELogProvider from './components/SSELogProvider/SSELogProvider' import SyncPollingWrapper from './wrappers/SyncPollingWrapper' import ChangeScreen from './views/ChangeScreen' +import AppLoadFallback from './components/Fallback/AppLoadFallback' function App() { const view = useRecoilValue(appView) @@ -19,11 +20,13 @@ function App() { switch (view) { case AppView.DASHBOARD: return ( - - - - - + }> + + + + + + ) case AppView.ONBOARD: return diff --git a/src/components/DiagnosticTable/AlertInfo.tsx b/src/components/DiagnosticTable/AlertInfo.tsx index 2b8e5bd3..fb914427 100644 --- a/src/components/DiagnosticTable/AlertInfo.tsx +++ b/src/components/DiagnosticTable/AlertInfo.tsx @@ -8,6 +8,9 @@ import sortAlertMessagesBySeverity from '../../utilities/sortAlerts' import { StatusColor } from '../../types' import AlertFilterSettings, { FilterValue } from '../AlertFilterSettings/AlertFilterSettings' import useMediaQuery from '../../hooks/useMediaQuery' +import { useRecoilValue } from 'recoil' +import ProposerAlerts from '../ProposerAlerts/ProposerAlerts' +import { proposerDuties } from '../../recoil/atoms' const AlertInfo = () => { const { t } = useTranslation() @@ -15,6 +18,7 @@ const AlertInfo = () => { const { ref, dimensions } = useDivDimensions() const headerDimensions = useDivDimensions() const [filter, setFilter] = useState('all') + const duties = useRecoilValue(proposerDuties) const setFilterValue = (value: FilterValue) => setFilter(value) const isMobile = useMediaQuery('(max-width: 425px)') @@ -29,7 +33,10 @@ const AlertInfo = () => { return sortAlertMessagesBySeverity(baseAlerts) }, [alerts, filter]) - const isFiller = formattedAlerts.length < 6 + const isFiller = formattedAlerts.length + (duties?.length || 0) < 6 + const isAlerts = formattedAlerts.length > 0 || duties?.length > 0 + const isProposerAlerts = + duties?.length > 0 && (filter === 'all' || filter === StatusColor.SUCCESS) useEffect(() => { const intervalId = setInterval(() => { @@ -61,7 +68,7 @@ const AlertInfo = () => { } className='h-full w-full flex flex-col' > - {formattedAlerts.length > 0 && ( + {isAlerts && (
{formattedAlerts.map((alert) => { const { severity, subText, message, id } = alert @@ -78,6 +85,7 @@ const AlertInfo = () => { /> ) })} + {isProposerAlerts && }
)} {isFiller && ( diff --git a/src/components/Fallback/AppLoadFallback.tsx b/src/components/Fallback/AppLoadFallback.tsx new file mode 100644 index 00000000..c2dd6491 --- /dev/null +++ b/src/components/Fallback/AppLoadFallback.tsx @@ -0,0 +1,14 @@ +import LoadingSpinner from '../LoadingSpinner/LoadingSpinner' + +const AppLoadFallback = () => { + return ( +
+
+
+ +
+
+ ) +} + +export default AppLoadFallback diff --git a/src/components/ProposerAlerts/AlertGroup.tsx b/src/components/ProposerAlerts/AlertGroup.tsx new file mode 100644 index 00000000..4acafab2 --- /dev/null +++ b/src/components/ProposerAlerts/AlertGroup.tsx @@ -0,0 +1,76 @@ +import { ProposerDuty, StatusColor } from '../../types' +import { FC, useState } from 'react' +import StatusBar from '../StatusBar/StatusBar' +import Typography from '../Typography/Typography' +import ProposalAlert from './ProposalAlert' +import getSlotTimeData from '../../utilities/getSlotTimeData' +import { useTranslation } from 'react-i18next' + +export interface AlertGroupProps { + duties: ProposerDuty[] + onClick: (ids: string[]) => void + genesis: number + secondsPerSlot: number +} + +const AlertGroup: FC = ({ duties, genesis, secondsPerSlot, onClick }) => { + const { t } = useTranslation() + const indices = duties.map(({ validator_index }) => validator_index) + const uuids = duties.map(({ uuid }) => uuid) + const isFullGroup = duties.length > 1 + const [isExpand, toggleGroup] = useState(false) + + const sortedDutiesBySlot = [...duties].sort((a, b) => Number(b.slot) - Number(a.slot)) + const latestDuty = sortedDutiesBySlot[0] + const latestDutyTime = getSlotTimeData(Number(latestDuty.slot), genesis, secondsPerSlot) + + const toggle = () => toggleGroup(!isExpand) + const removeGroup = () => onClick(uuids) + + const renderMappedDuties = () => + duties?.map((duty, index) => { + const { isFuture, shortHand } = getSlotTimeData(Number(duty.slot), genesis, secondsPerSlot) + + return ( + + ) + }) + + return ( + <> + {isFullGroup ? ( + <> +
+ +
+ + {t( + `alertMessages.groupedProposers.${latestDutyTime.isFuture ? 'future' : 'past'}`, + { count: duties?.length, indices: indices.join(', ') }, + )} + + + {isExpand ? t('collapseInfo') : t('expandInfo')} + +
+ +
+ {isExpand && renderMappedDuties()} + + ) : ( + renderMappedDuties() + )} + + ) +} + +export default AlertGroup diff --git a/src/components/ProposerAlerts/ProposalAlert.tsx b/src/components/ProposerAlerts/ProposalAlert.tsx new file mode 100644 index 00000000..b2d17b85 --- /dev/null +++ b/src/components/ProposerAlerts/ProposalAlert.tsx @@ -0,0 +1,41 @@ +import { ProposerDuty, StatusColor } from '../../types' +import { FC } from 'react' +import StatusBar from '../StatusBar/StatusBar' +import Typography from '../Typography/Typography' +import { Trans } from 'react-i18next' + +export interface ProposalAlertProps { + duty: ProposerDuty + time: string + isFuture?: boolean + onDelete?: (uuid: string[]) => void +} + +const ProposalAlert: FC = ({ duty, time, isFuture, onDelete }) => { + const { validator_index, slot, uuid } = duty + + const removeAlert = () => onDelete?.([uuid]) + return ( +
+ +
+ + + + + +
+ {onDelete && ( + + )} +
+ ) +} + +export default ProposalAlert diff --git a/src/components/ProposerAlerts/ProposerAlerts.tsx b/src/components/ProposerAlerts/ProposerAlerts.tsx new file mode 100644 index 00000000..c1299a5a --- /dev/null +++ b/src/components/ProposerAlerts/ProposerAlerts.tsx @@ -0,0 +1,58 @@ +import { FC } from 'react' +import { ProposerDuty } from '../../types' +import groupArray from '../../utilities/groupArray' +import AlertGroup from './AlertGroup' +import ProposalAlert from './ProposalAlert' +import { useRecoilValue, useSetRecoilState } from 'recoil' +import { selectGenesisBlock } from '../../recoil/selectors/selectGenesisBlock' +import { selectBnSpec } from '../../recoil/selectors/selectBnSpec' +import { proposerDuties } from '../../recoil/atoms' +import getSlotTimeData from '../../utilities/getSlotTimeData' + +export interface ProposerAlertsProps { + duties: ProposerDuty[] +} + +const ProposerAlerts: FC = ({ duties }) => { + const { SECONDS_PER_SLOT } = useRecoilValue(selectBnSpec) + const genesis = useRecoilValue(selectGenesisBlock) as number + const setProposers = useSetRecoilState(proposerDuties) + const groups = groupArray(duties, 10) + + const removeAlert = (uuids: string[]) => { + setProposers((prev) => prev.filter(({ uuid }) => !uuids.includes(uuid))) + } + + return ( + <> + {duties.length >= 10 + ? groups.map((group, index) => ( + + )) + : duties.map((duty, index) => { + const { isFuture, shortHand } = getSlotTimeData( + Number(duty.slot), + genesis, + SECONDS_PER_SLOT, + ) + return ( + + ) + })} + + ) +} + +export default ProposerAlerts diff --git a/src/constants/constants.ts b/src/constants/constants.ts index 967dfddc..d90633c6 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -96,9 +96,7 @@ export const CLIENT_PROVIDERS = [ ] as ClientProvider[] export const initialEthDeposit = 32 -export const secondsInSlot = 12 export const slotsInEpoc = 32 -export const secondsInEpoch = secondsInSlot * 32 export const secondsInHour = 3600 export const secondsInDay = 86400 export const secondsInWeek = 604800 diff --git a/src/hooks/__tests__/useEpochAprEstimate.spec.ts b/src/hooks/__tests__/useEpochAprEstimate.spec.ts index 8c053147..792d081d 100644 --- a/src/hooks/__tests__/useEpochAprEstimate.spec.ts +++ b/src/hooks/__tests__/useEpochAprEstimate.spec.ts @@ -9,6 +9,14 @@ import { mockShortValidatorCache, mockedRecentWithdrawalCash, } from '../../mocks/validatorResults' +import { mockBeaconSpec } from '../../mocks/beaconSpec' +import useFilteredValidatorCacheData from '../useFilteredValidatorCacheData' + +jest.mock('../useFilteredValidatorCacheData', () => jest.fn()) + +const mockedUseFilteredValidatorCacheData = useFilteredValidatorCacheData as jest.MockedFn< + typeof useFilteredValidatorCacheData +> jest.mock('ethers/lib/utils', () => ({ formatUnits: jest.fn(), @@ -21,6 +29,8 @@ describe('useEpochAprEstimate hook', () => { mockedFormatUnits.mockImplementation((value) => value.toString()) }) it('should return default values', () => { + mockedRecoilValue.mockReturnValue(mockBeaconSpec) + mockedUseFilteredValidatorCacheData.mockReturnValue(undefined) const { result } = renderHook(() => useEpochAprEstimate()) expect(result.current).toStrictEqual({ estimatedApr: undefined, @@ -28,7 +38,8 @@ describe('useEpochAprEstimate hook', () => { }) }) it('should return default values when not enough epoch data', () => { - mockedRecoilValue.mockReturnValue(mockShortValidatorCache) + mockedRecoilValue.mockReturnValue(mockBeaconSpec) + mockedUseFilteredValidatorCacheData.mockReturnValue(mockShortValidatorCache) const { result } = renderHook(() => useEpochAprEstimate()) expect(result.current).toStrictEqual({ estimatedApr: undefined, @@ -36,7 +47,8 @@ describe('useEpochAprEstimate hook', () => { }) }) it('should return correct values', () => { - mockedRecoilValue.mockReturnValue(mockValidatorCache) + mockedRecoilValue.mockReturnValue(mockBeaconSpec) + mockedUseFilteredValidatorCacheData.mockReturnValue(mockValidatorCache) const { result } = renderHook(() => useEpochAprEstimate()) expect(result.current).toStrictEqual({ estimatedApr: 1.3438636363304557, @@ -45,7 +57,8 @@ describe('useEpochAprEstimate hook', () => { }) it('should return correct values when provided an array of indexes', () => { - mockedRecoilValue.mockReturnValue(mockValidatorCache) + mockedRecoilValue.mockReturnValue(mockBeaconSpec) + mockedUseFilteredValidatorCacheData.mockReturnValue(mockValidatorCache) const { result } = renderHook(() => useEpochAprEstimate(['1234567'])) expect(result.current).toStrictEqual({ estimatedApr: 1.3438636363304557, @@ -54,7 +67,8 @@ describe('useEpochAprEstimate hook', () => { }) it('should return correct when there is a withdrawal value', () => { - mockedRecoilValue.mockReturnValue(mockedWithdrawalCash) + mockedRecoilValue.mockReturnValue(mockBeaconSpec) + mockedUseFilteredValidatorCacheData.mockReturnValue(mockedWithdrawalCash) const { result } = renderHook(() => useEpochAprEstimate()) expect(result.current).toStrictEqual({ estimatedApr: 3.8495973450145105, @@ -63,7 +77,8 @@ describe('useEpochAprEstimate hook', () => { }) it('should return correct when there is a withdrawal values at a loss', () => { - mockedRecoilValue.mockReturnValue(mockedWithdrawalCashLoss) + mockedRecoilValue.mockReturnValue(mockBeaconSpec) + mockedUseFilteredValidatorCacheData.mockReturnValue(mockedWithdrawalCashLoss) const { result } = renderHook(() => useEpochAprEstimate()) expect(result.current).toStrictEqual({ estimatedApr: -0.1710932155095768, @@ -72,7 +87,8 @@ describe('useEpochAprEstimate hook', () => { }) it('should return correct values when last epoch was a withdrawal', () => { - mockedRecoilValue.mockReturnValue(mockedRecentWithdrawalCash) + mockedRecoilValue.mockReturnValue(mockBeaconSpec) + mockedUseFilteredValidatorCacheData.mockReturnValue(mockedRecentWithdrawalCash) const { result } = renderHook(() => useEpochAprEstimate()) expect(result.current).toStrictEqual({ estimatedApr: 0, diff --git a/src/hooks/__tests__/useValidatorEpochBalance.spec.ts b/src/hooks/__tests__/useValidatorEpochBalance.spec.ts index 475818e9..4fa3ffc7 100644 --- a/src/hooks/__tests__/useValidatorEpochBalance.spec.ts +++ b/src/hooks/__tests__/useValidatorEpochBalance.spec.ts @@ -5,6 +5,7 @@ import { mockActiveValidators, mockValidatorCacheResults } from '../../mocks/val import { waitFor } from '@testing-library/react' import { formatUnits } from 'ethers/lib/utils' import clearAllMocks = jest.clearAllMocks +import { mockBeaconSpec } from '../../mocks/beaconSpec' jest.mock('../../recoil/selectors/selectSlicedActiveValidators', () => ({ selectSlicedActiveValidators: 'selectSlicedActiveValidators', @@ -14,6 +15,10 @@ jest.mock('../../recoil/atoms', () => ({ validatorCacheBalanceResult: 'validatorCacheBalanceResult', })) +jest.mock('../../recoil/selectors/selectBnSpec', () => ({ + selectBnSpec: 'selectBnSpec', +})) + jest.mock('ethers/lib/utils', () => ({ formatUnits: jest.fn(), })) @@ -25,6 +30,11 @@ describe('useValidatorEpochBalance', () => { clearAllMocks() }) it('should return default values', () => { + mockedRecoilValue.mockImplementation((data) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + if (data === 'selectBnSpec') return mockBeaconSpec + }) const { result } = renderHook(() => useValidatorEpochBalance()) expect(result.current).toStrictEqual({ @@ -35,6 +45,9 @@ describe('useValidatorEpochBalance', () => { }) it('should call fetch function', async () => { mockedRecoilValue.mockImplementation((data) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + if (data === 'selectBnSpec') return mockBeaconSpec // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore if (data === 'selectSlicedActiveValidators') return mockActiveValidators diff --git a/src/hooks/useBeaconSyncPolling.ts b/src/hooks/useBeaconSyncPolling.ts index d94357b3..3c741102 100644 --- a/src/hooks/useBeaconSyncPolling.ts +++ b/src/hooks/useBeaconSyncPolling.ts @@ -1,12 +1,14 @@ import { useRecoilValue, useSetRecoilState } from 'recoil' import { beaconNetworkError, beaconSyncInfo, activeDevice } from '../recoil/atoms' import { useEffect } from 'react' -import { secondsInSlot } from '../constants/constants' import usePollApi from './usePollApi' import { PollingOptions } from '../types' +import { selectBnSpec } from '../recoil/selectors/selectBnSpec' const useBeaconSyncPolling = (options?: PollingOptions) => { - const { time = secondsInSlot * 1000, isReady = true } = options || {} + const { SECONDS_PER_SLOT } = useRecoilValue(selectBnSpec) + + const { time = SECONDS_PER_SLOT * 1000, isReady = true } = options || {} const { beaconUrl } = useRecoilValue(activeDevice) const url = `${beaconUrl}/eth/v1/node/syncing` const setBeaconSyncInfo = useSetRecoilState(beaconSyncInfo) diff --git a/src/hooks/useEpochAprEstimate.ts b/src/hooks/useEpochAprEstimate.ts index 9b0ff181..b15a635e 100644 --- a/src/hooks/useEpochAprEstimate.ts +++ b/src/hooks/useEpochAprEstimate.ts @@ -1,11 +1,14 @@ import useFilteredValidatorCacheData from './useFilteredValidatorCacheData' import { useMemo } from 'react' import { formatUnits } from 'ethers/lib/utils' -import { secondsInDay, secondsInEpoch } from '../constants/constants' +import { secondsInDay, slotsInEpoc } from '../constants/constants' import calculateAprPercentage from '../utilities/calculateAprPercentage' import formatBalanceColor from '../utilities/formatBalanceColor' +import { useRecoilValue } from 'recoil' +import { selectBnSpec } from '../recoil/selectors/selectBnSpec' const useEpochAprEstimate = (indices?: string[]) => { + const { SECONDS_PER_SLOT } = useRecoilValue(selectBnSpec) const filteredValidatorCache = useFilteredValidatorCacheData(indices) const formattedCache = useMemo(() => { @@ -30,7 +33,8 @@ const useEpochAprEstimate = (indices?: string[]) => { const initialBalance = formattedWithdrawalCache[0] const currentBalance = formattedWithdrawalCache[formattedWithdrawalCache.length - 1] const rewards = currentBalance - initialBalance - const multiplier = (secondsInDay * 365) / secondsInEpoch / formattedWithdrawalCache.length + const multiplier = + (secondsInDay * 365) / (SECONDS_PER_SLOT * slotsInEpoc) / formattedWithdrawalCache.length const rewardsMultiplied = rewards * multiplier const projectedBalance = rewardsMultiplied + initialBalance diff --git a/src/hooks/useProposerDutiesPolling.ts b/src/hooks/useProposerDutiesPolling.ts new file mode 100644 index 00000000..e92dc28d --- /dev/null +++ b/src/hooks/useProposerDutiesPolling.ts @@ -0,0 +1,48 @@ +import { useRecoilValue, useSetRecoilState } from 'recoil' +import { activeDevice, beaconSyncInfo, proposerDuties } from '../recoil/atoms' +import { slotsInEpoc } from '../constants/constants' +import usePollApi from './usePollApi' +import { PollingOptions, ProposerDuty } from '../types' +import { useEffect } from 'react' +import { selectBnSpec } from '../recoil/selectors/selectBnSpec' +import { selectActiveValidators } from '../recoil/selectors/selectActiveValidators' +import formatUniqueObjectArray from '../utilities/formatUniqueObjectArray' + +const useProposerDutiesPolling = (options?: PollingOptions) => { + const { SECONDS_PER_SLOT } = useRecoilValue(selectBnSpec) + const { beaconUrl } = useRecoilValue(activeDevice) + const activeVals = useRecoilValue(selectActiveValidators) + const activeIds = activeVals.map(({ index }) => index) + const { head_slot } = useRecoilValue(beaconSyncInfo) || {} + const time = ((SECONDS_PER_SLOT * slotsInEpoc) / 2) * 1000 + const closestEpoch = head_slot ? Math.floor(head_slot / slotsInEpoc) + 1 : undefined + const url = `${beaconUrl}/eth/v1/validator/duties/proposer/${closestEpoch}` + const setDuties = useSetRecoilState(proposerDuties) + + const { data } = usePollApi({ + key: 'validatorProposerDuties', + time, + isReady: !!closestEpoch && options?.isReady, + url, + }) + + useEffect(() => { + const duties = data?.data + + if (duties && activeIds?.length) { + setDuties((prev) => + formatUniqueObjectArray([ + ...prev, + ...duties + .filter((duty: ProposerDuty) => activeIds.includes(duty.validator_index)) + .map((duty: ProposerDuty) => ({ + ...duty, + uuid: `${duty.slot}${duty.validator_index}`, + })), + ]), + ) + } + }, [data, activeIds?.length]) +} + +export default useProposerDutiesPolling diff --git a/src/hooks/useValidatorEarnings.ts b/src/hooks/useValidatorEarnings.ts index 8b2fedbc..fd1cd59a 100644 --- a/src/hooks/useValidatorEarnings.ts +++ b/src/hooks/useValidatorEarnings.ts @@ -11,8 +11,10 @@ import calculateEpochEstimate from '../utilities/calculateEpochEstimate' import { selectValidatorInfos } from '../recoil/selectors/selectValidatorInfos' import calculateAprPercentage from '../utilities/calculateAprPercentage' import useFilteredValidatorCacheData from './useFilteredValidatorCacheData' +import { selectBnSpec } from '../recoil/selectors/selectBnSpec' const useValidatorEarnings = (indices?: string[]) => { + const { SECONDS_PER_SLOT } = useRecoilValue(selectBnSpec) const validators = useRecoilValue(selectValidatorInfos) const filteredCacheData = useFilteredValidatorCacheData(indices) @@ -49,23 +51,23 @@ const useValidatorEarnings = (indices?: string[]) => { }, [filteredValidators]) const hourlyEstimate = useMemo( - () => calculateEpochEstimate(secondsInHour, epochCaches), - [epochCaches], + () => calculateEpochEstimate(secondsInHour, SECONDS_PER_SLOT, epochCaches), + [epochCaches, SECONDS_PER_SLOT], ) const dailyEstimate = useMemo( - () => calculateEpochEstimate(secondsInDay, epochCaches), - [epochCaches], + () => calculateEpochEstimate(secondsInDay, SECONDS_PER_SLOT, epochCaches), + [epochCaches, SECONDS_PER_SLOT], ) const weeklyEstimate = useMemo( - () => calculateEpochEstimate(secondsInWeek, epochCaches), - [epochCaches], + () => calculateEpochEstimate(secondsInWeek, SECONDS_PER_SLOT, epochCaches), + [epochCaches, SECONDS_PER_SLOT], ) const monthlyEstimate = useMemo( - () => calculateEpochEstimate(secondsInWeek * 4, epochCaches), - [epochCaches], + () => calculateEpochEstimate(secondsInWeek * 4, SECONDS_PER_SLOT, epochCaches), + [epochCaches, SECONDS_PER_SLOT], ) const initialEth = filteredValidators.length * initialEthDeposit diff --git a/src/hooks/useValidatorEpochBalance.ts b/src/hooks/useValidatorEpochBalance.ts index bb4f2e72..d5bafb23 100644 --- a/src/hooks/useValidatorEpochBalance.ts +++ b/src/hooks/useValidatorEpochBalance.ts @@ -2,13 +2,15 @@ import { useRecoilValue } from 'recoil' import { useMemo } from 'react' import { formatUnits } from 'ethers/lib/utils' import { validatorCacheBalanceResult } from '../recoil/atoms' -import { BALANCE_COLORS, secondsInSlot, slotsInEpoc } from '../constants/constants' +import { BALANCE_COLORS, slotsInEpoc } from '../constants/constants' import moment from 'moment' import { selectGenesisBlock } from '../recoil/selectors/selectGenesisBlock' import getAverageValue from '../utilities/getAverageValue' import { selectSlicedActiveValidators } from '../recoil/selectors/selectSlicedActiveValidators' +import { selectBnSpec } from '../recoil/selectors/selectBnSpec' const useValidatorEpochBalance = () => { + const { SECONDS_PER_SLOT } = useRecoilValue(selectBnSpec) const validatorCacheData = useRecoilValue(validatorCacheBalanceResult) const activeValidators = useRecoilValue(selectSlicedActiveValidators) const genesisBlock = useRecoilValue(selectGenesisBlock) as number @@ -38,7 +40,7 @@ const useValidatorEpochBalance = () => { ? data.map(({ epoch }) => { const slot = epoch * slotsInEpoc - return moment((genesisBlock + slot * secondsInSlot) * 1000).format('HH:mm') + return moment((genesisBlock + slot * SECONDS_PER_SLOT) * 1000).format('HH:mm') }) : [] }, [validatorCacheData, genesisBlock]) diff --git a/src/hooks/useValidatorInfoPolling.ts b/src/hooks/useValidatorInfoPolling.ts index abd410c4..36685850 100644 --- a/src/hooks/useValidatorInfoPolling.ts +++ b/src/hooks/useValidatorInfoPolling.ts @@ -1,14 +1,15 @@ import { useRecoilValue, useRecoilValueLoadable, useSetRecoilState } from 'recoil' -import { secondsInSlot } from '../constants/constants' import { useEffect } from 'react' import { selectValidators } from '../recoil/selectors/selectValidators' import { activeDevice, validatorStateInfo } from '../recoil/atoms' import { Validator } from '../types/validator' import usePollApi from './usePollApi' import { PollingOptions } from '../types' +import { selectBnSpec } from '../recoil/selectors/selectBnSpec' const useValidatorInfoPolling = (options?: PollingOptions) => { - const { time = secondsInSlot * 1000, isReady = true } = options || {} + const { SECONDS_PER_SLOT } = useRecoilValue(selectBnSpec) + const { time = SECONDS_PER_SLOT * 1000, isReady = true } = options || {} const { beaconUrl } = useRecoilValue(activeDevice) const { contents: validators } = useRecoilValueLoadable(selectValidators) const setStateInfo = useSetRecoilState(validatorStateInfo) diff --git a/src/hooks/useValidatorPeerPolling.ts b/src/hooks/useValidatorPeerPolling.ts index a0cff7c9..1ce3ac46 100644 --- a/src/hooks/useValidatorPeerPolling.ts +++ b/src/hooks/useValidatorPeerPolling.ts @@ -1,12 +1,13 @@ import { useRecoilValue, useSetRecoilState } from 'recoil' import { activeDevice, beaconNetworkError, validatorPeerCount } from '../recoil/atoms' import usePollApi from './usePollApi' -import { secondsInSlot } from '../constants/constants' import { useEffect } from 'react' import { PollingOptions } from '../types' +import { selectBnSpec } from '../recoil/selectors/selectBnSpec' const useValidatorPeerPolling = (options?: PollingOptions) => { - const { time = secondsInSlot * 2000, isReady = true } = options || {} + const { SECONDS_PER_SLOT } = useRecoilValue(selectBnSpec) + const { time = SECONDS_PER_SLOT * 2000, isReady = true } = options || {} const { beaconUrl } = useRecoilValue(activeDevice) const setPeerCount = useSetRecoilState(validatorPeerCount) const setBeaconNetworkError = useSetRecoilState(beaconNetworkError) diff --git a/src/hooks/useValidatorSyncPolling.ts b/src/hooks/useValidatorSyncPolling.ts index b139f215..8d0ee0b1 100644 --- a/src/hooks/useValidatorSyncPolling.ts +++ b/src/hooks/useValidatorSyncPolling.ts @@ -2,11 +2,12 @@ import { useEffect } from 'react' import { useRecoilValue, useSetRecoilState } from 'recoil' import { activeDevice, beaconNetworkError, validatorSyncInfo } from '../recoil/atoms' import usePollApi from './usePollApi' -import { secondsInSlot } from '../constants/constants' import { PollingOptions } from '../types' +import { selectBnSpec } from '../recoil/selectors/selectBnSpec' const useValidatorSyncPolling = (options?: PollingOptions) => { - const { time = secondsInSlot * 1000, isReady = true } = options || {} + const { SECONDS_PER_SLOT } = useRecoilValue(selectBnSpec) + const { time = SECONDS_PER_SLOT * 1000, isReady = true } = options || {} const { beaconUrl } = useRecoilValue(activeDevice) const setBeaconNetworkError = useSetRecoilState(beaconNetworkError) const url = `${beaconUrl}/lighthouse/eth1/syncing` diff --git a/src/locales/translations/en-US.json b/src/locales/translations/en-US.json index 82e5a863..553fea4c 100644 --- a/src/locales/translations/en-US.json +++ b/src/locales/translations/en-US.json @@ -449,6 +449,16 @@ "ethClientNotSync": "Ethereum Client is not in sync", "excessiveWarningLogs": "Unusual amount of warning logs detected", "critLog": "CRITICAL ERROR: {{message}}", - "errorLog": "ERROR: {{message}}" - } + "errorLog": "ERROR: {{message}}", + "groupedProposers": { + "past": "{{count}} validators were scheduled for block proposals in a previous epoch. Including indices: {{indices}}", + "future": "{{count}} validators are scheduled for block proposals in this epoch. Including indices: {{indices}}" + }, + "proposerAlert": { + "past": "Validator <0>{{validator_index}} was scheduled for block proposal at slot {{slot}} {{time}}", + "future": "Validator <0>{{validator_index}} is scheduled for block proposal at slot {{slot}} {{time}}" + } + }, + "collapseInfo": "Collapse Info", + "expandInfo": "Expand Info" } \ No newline at end of file diff --git a/src/mocks/beaconSpec.ts b/src/mocks/beaconSpec.ts new file mode 100644 index 00000000..0aceaa6f --- /dev/null +++ b/src/mocks/beaconSpec.ts @@ -0,0 +1,7 @@ +export const mockBeaconSpec = { + CONFIG_NAME: 'mock-name', + DEPOSIT_CHAIN_ID: 'mock-id', + DEPOSIT_CONTRACT_ADDRESS: 'mock-address', + DEPOSIT_NETWORK_ID: 'mock-network-id', + SECONDS_PER_SLOT: '12', +} diff --git a/src/mocks/validatorResults.ts b/src/mocks/validatorResults.ts index 1777a72a..39cdaf56 100644 --- a/src/mocks/validatorResults.ts +++ b/src/mocks/validatorResults.ts @@ -108,114 +108,114 @@ export const mockValidatorCacheResults = { export const mockValidatorCache = { 1234567: [ - { epoch: '12345678', total_balance: 33 }, - { epoch: '12345679', total_balance: 33.00002 }, - { epoch: '12345679', total_balance: 33.000025 }, - { epoch: '12345679', total_balance: 33.00003 }, - { epoch: '12345679', total_balance: 33.00004 }, - { epoch: '12345679', total_balance: 33.000045 }, - { epoch: '12345679', total_balance: 33.000046 }, - { epoch: '12345679', total_balance: 33.000047 }, - { epoch: '12345679', total_balance: 33.00005 }, - { epoch: '12345679', total_balance: 33.000054 }, + { epoch: 12345678, total_balance: 33 }, + { epoch: 12345679, total_balance: 33.00002 }, + { epoch: 12345679, total_balance: 33.000025 }, + { epoch: 12345679, total_balance: 33.00003 }, + { epoch: 12345679, total_balance: 33.00004 }, + { epoch: 12345679, total_balance: 33.000045 }, + { epoch: 12345679, total_balance: 33.000046 }, + { epoch: 12345679, total_balance: 33.000047 }, + { epoch: 12345679, total_balance: 33.00005 }, + { epoch: 12345679, total_balance: 33.000054 }, ], 1234568: [ - { epoch: '12345678', total_balance: 33 }, - { epoch: '12345679', total_balance: 33.00002 }, - { epoch: '12345679', total_balance: 33.000025 }, - { epoch: '12345679', total_balance: 33.00003 }, - { epoch: '12345679', total_balance: 33.00004 }, - { epoch: '12345679', total_balance: 33.000045 }, - { epoch: '12345679', total_balance: 33.000046 }, - { epoch: '12345679', total_balance: 33.000047 }, - { epoch: '12345679', total_balance: 33.00005 }, - { epoch: '12345679', total_balance: 33.000054 }, + { epoch: 12345678, total_balance: 33 }, + { epoch: 12345679, total_balance: 33.00002 }, + { epoch: 12345679, total_balance: 33.000025 }, + { epoch: 12345679, total_balance: 33.00003 }, + { epoch: 12345679, total_balance: 33.00004 }, + { epoch: 12345679, total_balance: 33.000045 }, + { epoch: 12345679, total_balance: 33.000046 }, + { epoch: 12345679, total_balance: 33.000047 }, + { epoch: 12345679, total_balance: 33.00005 }, + { epoch: 12345679, total_balance: 33.000054 }, ], } export const mockShortValidatorCache = { - 1234567: [{ epoch: '12345678', total_balance: 32 }], - 1234568: [{ epoch: '12345678', total_balance: 32 }], + 1234567: [{ epoch: 12345678, total_balance: 32 }], + 1234568: [{ epoch: 12345678, total_balance: 32 }], } export const mockedWithdrawalCash = { 1234567: [ - { epoch: '12345678', total_balance: 33 }, - { epoch: '12345679', total_balance: 33.00002 }, - { epoch: '12345679', total_balance: 33.000025 }, - { epoch: '12345679', total_balance: 33.00003 }, - { epoch: '12345679', total_balance: 32.0001 }, - { epoch: '12345679', total_balance: 32.00015 }, - { epoch: '12345679', total_balance: 32.00016 }, - { epoch: '12345679', total_balance: 32.00017 }, - { epoch: '12345679', total_balance: 32.00018 }, - { epoch: '12345679', total_balance: 32.00019 }, + { epoch: 12345678, total_balance: 33 }, + { epoch: 12345679, total_balance: 33.00002 }, + { epoch: 12345679, total_balance: 33.000025 }, + { epoch: 12345679, total_balance: 33.00003 }, + { epoch: 12345679, total_balance: 32.0001 }, + { epoch: 12345679, total_balance: 32.00015 }, + { epoch: 12345679, total_balance: 32.00016 }, + { epoch: 12345679, total_balance: 32.00017 }, + { epoch: 12345679, total_balance: 32.00018 }, + { epoch: 12345679, total_balance: 32.00019 }, ], 1234568: [ - { epoch: '12345678', total_balance: 33 }, - { epoch: '12345679', total_balance: 33.00002 }, - { epoch: '12345679', total_balance: 33.000025 }, - { epoch: '12345679', total_balance: 33.00003 }, - { epoch: '12345679', total_balance: 32.0001 }, - { epoch: '12345679', total_balance: 32.00015 }, - { epoch: '12345679', total_balance: 32.00016 }, - { epoch: '12345679', total_balance: 32.00017 }, - { epoch: '12345679', total_balance: 32.00018 }, - { epoch: '12345679', total_balance: 32.00019 }, + { epoch: 12345678, total_balance: 33 }, + { epoch: 12345679, total_balance: 33.00002 }, + { epoch: 12345679, total_balance: 33.000025 }, + { epoch: 12345679, total_balance: 33.00003 }, + { epoch: 12345679, total_balance: 32.0001 }, + { epoch: 12345679, total_balance: 32.00015 }, + { epoch: 12345679, total_balance: 32.00016 }, + { epoch: 12345679, total_balance: 32.00017 }, + { epoch: 12345679, total_balance: 32.00018 }, + { epoch: 12345679, total_balance: 32.00019 }, ], } export const mockedRecentWithdrawalCash = { 1234567: [ - { epoch: '12345678', total_balance: 33 }, - { epoch: '12345679', total_balance: 33.00002 }, - { epoch: '12345679', total_balance: 33.000025 }, - { epoch: '12345679', total_balance: 33.00003 }, - { epoch: '12345679', total_balance: 33.00015 }, - { epoch: '12345679', total_balance: 33.00016 }, - { epoch: '12345679', total_balance: 33.00017 }, - { epoch: '12345679', total_balance: 33.00018 }, - { epoch: '12345679', total_balance: 33.00019 }, - { epoch: '12345679', total_balance: 32.0001 }, + { epoch: 12345678, total_balance: 33 }, + { epoch: 12345679, total_balance: 33.00002 }, + { epoch: 12345679, total_balance: 33.000025 }, + { epoch: 12345679, total_balance: 33.00003 }, + { epoch: 12345679, total_balance: 33.00015 }, + { epoch: 12345679, total_balance: 33.00016 }, + { epoch: 12345679, total_balance: 33.00017 }, + { epoch: 12345679, total_balance: 33.00018 }, + { epoch: 12345679, total_balance: 33.00019 }, + { epoch: 12345679, total_balance: 32.0001 }, ], 1234568: [ - { epoch: '12345678', total_balance: 33 }, - { epoch: '12345679', total_balance: 33.00002 }, - { epoch: '12345679', total_balance: 33.000025 }, - { epoch: '12345679', total_balance: 33.00003 }, - { epoch: '12345679', total_balance: 33.00015 }, - { epoch: '12345679', total_balance: 33.00016 }, - { epoch: '12345679', total_balance: 33.00017 }, - { epoch: '12345679', total_balance: 33.00018 }, - { epoch: '12345679', total_balance: 33.00019 }, - { epoch: '12345679', total_balance: 32.0001 }, + { epoch: 12345678, total_balance: 33 }, + { epoch: 12345679, total_balance: 33.00002 }, + { epoch: 12345679, total_balance: 33.000025 }, + { epoch: 12345679, total_balance: 33.00003 }, + { epoch: 12345679, total_balance: 33.00015 }, + { epoch: 12345679, total_balance: 33.00016 }, + { epoch: 12345679, total_balance: 33.00017 }, + { epoch: 12345679, total_balance: 33.00018 }, + { epoch: 12345679, total_balance: 33.00019 }, + { epoch: 12345679, total_balance: 32.0001 }, ], } export const mockedWithdrawalCashLoss = { 1234567: [ - { epoch: '12345678', total_balance: 33 }, - { epoch: '12345679', total_balance: 33.00002 }, - { epoch: '12345679', total_balance: 33.000025 }, - { epoch: '12345679', total_balance: 33.00003 }, - { epoch: '12345679', total_balance: 32.0001 }, - { epoch: '12345679', total_balance: 32.000092 }, - { epoch: '12345679', total_balance: 32.000093 }, - { epoch: '12345679', total_balance: 32.000094 }, - { epoch: '12345679', total_balance: 32.000095 }, - { epoch: '12345679', total_balance: 32.000096 }, + { epoch: 12345678, total_balance: 33 }, + { epoch: 12345679, total_balance: 33.00002 }, + { epoch: 12345679, total_balance: 33.000025 }, + { epoch: 12345679, total_balance: 33.00003 }, + { epoch: 12345679, total_balance: 32.0001 }, + { epoch: 12345679, total_balance: 32.000092 }, + { epoch: 12345679, total_balance: 32.000093 }, + { epoch: 12345679, total_balance: 32.000094 }, + { epoch: 12345679, total_balance: 32.000095 }, + { epoch: 12345679, total_balance: 32.000096 }, ], 1234568: [ - { epoch: '12345678', total_balance: 33 }, - { epoch: '12345679', total_balance: 33.00002 }, - { epoch: '12345679', total_balance: 33.000025 }, - { epoch: '12345679', total_balance: 33.00003 }, - { epoch: '12345679', total_balance: 32.0001 }, - { epoch: '12345679', total_balance: 32.000092 }, - { epoch: '12345679', total_balance: 32.000093 }, - { epoch: '12345679', total_balance: 32.000094 }, - { epoch: '12345679', total_balance: 32.000095 }, - { epoch: '12345679', total_balance: 32.000096 }, + { epoch: 12345678, total_balance: 33 }, + { epoch: 12345679, total_balance: 33.00002 }, + { epoch: 12345679, total_balance: 33.000025 }, + { epoch: 12345679, total_balance: 33.00003 }, + { epoch: 12345679, total_balance: 32.0001 }, + { epoch: 12345679, total_balance: 32.000092 }, + { epoch: 12345679, total_balance: 32.000093 }, + { epoch: 12345679, total_balance: 32.000094 }, + { epoch: 12345679, total_balance: 32.000095 }, + { epoch: 12345679, total_balance: 32.000096 }, ], } diff --git a/src/recoil/atoms.ts b/src/recoil/atoms.ts index 6a81c353..523bbb99 100644 --- a/src/recoil/atoms.ts +++ b/src/recoil/atoms.ts @@ -1,6 +1,13 @@ import { atom } from 'recoil' import { AppView, ContentView, OnboardView, SetupSteps, UiMode } from '../constants/enums' -import { ActiveDevice, AlertMessage, DeviceList, EthExchangeRates, ValAliases } from '../types' +import { + ActiveDevice, + AlertMessage, + DeviceList, + EthExchangeRates, + ProposerDuty, + ValAliases, +} from '../types' import { BeaconSyncResult, HealthDiagnosticResult, ValidatorSyncResult } from '../types/diagnostic' import { BeaconValidatorResult, ValidatorCache } from '../types/validator' import { BeaconValidatorMetricResults } from '../types/beacon' @@ -154,3 +161,8 @@ export const alertLogs = atom({ key: 'alertLogs', default: [], }) + +export const proposerDuties = atom({ + key: 'proposerDuties', + default: [], +}) diff --git a/src/recoil/selectors/selectBnSpec.ts b/src/recoil/selectors/selectBnSpec.ts index 8cb49a37..4c1e822c 100644 --- a/src/recoil/selectors/selectBnSpec.ts +++ b/src/recoil/selectors/selectBnSpec.ts @@ -10,7 +10,7 @@ export const selectBnSpec = selector({ try { const { data } = await fetchBnSpec(beaconUrl) - return data?.data + return { ...data?.data, SECONDS_PER_SLOT: Number(data?.data.SECONDS_PER_SLOT) } } catch (e) { console.error(e) return undefined diff --git a/src/types/beacon.ts b/src/types/beacon.ts index 9bb5d948..e24c5db7 100644 --- a/src/types/beacon.ts +++ b/src/types/beacon.ts @@ -11,6 +11,7 @@ export type BeaconNodeSpecResults = { DEPOSIT_CHAIN_ID: string DEPOSIT_CONTRACT_ADDRESS: string DEPOSIT_NETWORK_ID: string + SECONDS_PER_SLOT: number } export type BeaconValidatorMetric = { diff --git a/src/types/index.ts b/src/types/index.ts index d7fbbab2..ad61ae77 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -115,3 +115,10 @@ export type EthExchangeRates = { rates: Rates currencies: string[] } + +export type ProposerDuty = { + pubkey: string + validator_index: string + slot: string + uuid: string +} diff --git a/src/utilities/__tests__/calculateEpochEstimate.spec.ts b/src/utilities/__tests__/calculateEpochEstimate.spec.ts index 8a828956..31181c2d 100644 --- a/src/utilities/__tests__/calculateEpochEstimate.spec.ts +++ b/src/utilities/__tests__/calculateEpochEstimate.spec.ts @@ -10,11 +10,11 @@ const mockedFormatUnits = formatUnits as jest.MockedFn describe('calculateEpochEstimate util', () => { it('should return default value', () => { - expect(calculateEpochEstimate(secondsInHour)).toBe(0) + expect(calculateEpochEstimate(secondsInHour, 12)).toBe(0) }) it('should return default value', () => { expect( - calculateEpochEstimate(secondsInHour, { + calculateEpochEstimate(secondsInHour, 12, { 2323: [32000000, 32000000], }), ).toBe(0) @@ -22,7 +22,7 @@ describe('calculateEpochEstimate util', () => { it('should return correct values', () => { mockedFormatUnits.mockReturnValue('3000') expect( - calculateEpochEstimate(secondsInHour, { + calculateEpochEstimate(secondsInHour, 12, { 2323: [32000000, 32000000], 2324: [32000000, 32000000], }), diff --git a/src/utilities/calculateEpochEstimate.ts b/src/utilities/calculateEpochEstimate.ts index 191b3935..04513052 100644 --- a/src/utilities/calculateEpochEstimate.ts +++ b/src/utilities/calculateEpochEstimate.ts @@ -1,9 +1,13 @@ import { FormattedValidatorCache } from '../types/validator' -import { secondsInEpoch } from '../constants/constants' +import { slotsInEpoc } from '../constants/constants' import reduceAddNum from './reduceAddNum' import { formatUnits } from 'ethers/lib/utils' -const calculateEpochEstimate = (timeInSeconds: number, epochs?: FormattedValidatorCache) => { +const calculateEpochEstimate = ( + timeInSeconds: number, + secondsInSlot: number, + epochs?: FormattedValidatorCache, +) => { let difference = 0 if (!epochs) return difference @@ -12,7 +16,7 @@ const calculateEpochEstimate = (timeInSeconds: number, epochs?: FormattedValidat if (!epochCount || epochCount === 1) return difference - const timeMultiplier = timeInSeconds / (secondsInEpoch * epochCount) + const timeMultiplier = timeInSeconds / (secondsInSlot * slotsInEpoc * epochCount) difference = epochValues[epochValues.length - 1].reduce(reduceAddNum, 0) - diff --git a/src/utilities/formatUniqueObjectArray.ts b/src/utilities/formatUniqueObjectArray.ts new file mode 100644 index 00000000..6cd6b325 --- /dev/null +++ b/src/utilities/formatUniqueObjectArray.ts @@ -0,0 +1,9 @@ +const formatUniqueObjectArray = (arr: object[]): any[] => { + const seen = new Set() + return arr.filter((item) => { + const itemStr = JSON.stringify(Object.fromEntries(Object.entries(item).sort())) + return !seen.has(itemStr) && seen.add(itemStr) + }) +} + +export default formatUniqueObjectArray diff --git a/src/utilities/getSlotTimeData.ts b/src/utilities/getSlotTimeData.ts new file mode 100644 index 00000000..22e6bcda --- /dev/null +++ b/src/utilities/getSlotTimeData.ts @@ -0,0 +1,15 @@ +import moment from 'moment' + +const getSlotTimeData = (slot: number, genesis: number, secondsPerSlot: number) => { + const today = moment() + const dutyTime = moment((genesis + slot * secondsPerSlot) * 1000) + const isFuture = today.diff(dutyTime) < 0 + + return { + time: dutyTime, + shortHand: dutyTime.fromNow(), + isFuture, + } +} + +export default getSlotTimeData diff --git a/src/utilities/groupArray.ts b/src/utilities/groupArray.ts new file mode 100644 index 00000000..88197436 --- /dev/null +++ b/src/utilities/groupArray.ts @@ -0,0 +1,9 @@ +const groupArray = (arr: any[], size: number) => { + return arr.length < size + ? arr + : Array.from({ length: Math.ceil(arr.length / size) }, (_, i) => + arr.slice(i * size, (i + 1) * size), + ) +} + +export default groupArray diff --git a/src/wrappers/MainPollingWrapper.tsx b/src/wrappers/MainPollingWrapper.tsx index ff59cfa7..b10bffd8 100644 --- a/src/wrappers/MainPollingWrapper.tsx +++ b/src/wrappers/MainPollingWrapper.tsx @@ -7,6 +7,7 @@ import { FC, ReactElement, useEffect, useState } from 'react' import { useRecoilValue } from 'recoil' import { beaconNetworkError, validatorNetworkError } from '../recoil/atoms' import useExchangeRatePolling from '../hooks/useExchangeRatePolling' +import useProposerDutiesPolling from '../hooks/useProposerDutiesPolling' export interface MainPollingWrapperProps { children: ReactElement | ReactElement[] @@ -17,6 +18,7 @@ const MainPollingWrapper: FC = ({ children }) => { const isBnModal = useRecoilValue(beaconNetworkError) const isVcModal = useRecoilValue(validatorNetworkError) + useProposerDutiesPolling({ isReady }) useExchangeRatePolling({ isReady }) useValidatorInfoPolling({ isReady: isReady && !isBnModal }) useValidatorHealthPolling({ isReady: isReady && !isVcModal })