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}}0> was scheduled for block proposal at slot {{slot}} {{time}}",
+ "future": "Validator <0>{{validator_index}}0> 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 })