import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { usePezkuwi } from '@/contexts/PezkuwiContext'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Activity, Wifi, WifiOff, Users, Box, TrendingUp } from 'lucide-react'; export const NetworkStats: React.FC = () => { const { t } = useTranslation(); const { api, assetHubApi, peopleApi, isApiReady, isAssetHubReady, isPeopleReady, error } = usePezkuwi(); const [blockNumber, setBlockNumber] = useState(0); const [blockHash, setBlockHash] = useState(''); const [finalizedBlock, setFinalizedBlock] = useState(0); const [validatorCount, setValidatorCount] = useState(0); const [collatorCount, setCollatorCount] = useState(0); const [nominatorCount, setNominatorCount] = useState(0); const [peers, setPeers] = useState(0); useEffect(() => { if (!api || !isApiReady) return; let unsubscribeNewHeads: () => void; let unsubscribeFinalizedHeads: () => void; let intervalId: NodeJS.Timeout; const subscribeToBlocks = async () => { try { // Subscribe to new blocks unsubscribeNewHeads = await api.rpc.chain.subscribeNewHeads((header) => { setBlockNumber(header.number.toNumber()); setBlockHash(header.hash.toHex()); }); // Subscribe to finalized blocks unsubscribeFinalizedHeads = await api.rpc.chain.subscribeFinalizedHeads((header) => { setFinalizedBlock(header.number.toNumber()); }); // Update validator count, collator count, nominator count, and peer count every 3 seconds const updateNetworkStats = async () => { try { const health = await api.rpc.system.health(); // 1. Fetch Validators let vCount = 0; try { if (api.query.session?.validators) { const validators = await api.query.session.validators(); if (validators) { vCount = validators.length; } } } catch (err) { if (import.meta.env.DEV) console.warn('Failed to fetch validators', err); } // 2. Fetch Collators from Parachains (Asset Hub + People Chain) let cCount = 0; // Fetch from Asset Hub try { if (isAssetHubReady && assetHubApi?.query.collatorSelection?.invulnerables) { const assetHubCollators = await assetHubApi.query.collatorSelection.invulnerables(); if (assetHubCollators) { cCount += assetHubCollators.length; } } } catch (err) { if (import.meta.env.DEV) console.warn('Failed to fetch Asset Hub collators', err); } // Fetch from People Chain try { if (isPeopleReady && peopleApi?.query.collatorSelection?.invulnerables) { const peopleCollators = await peopleApi.query.collatorSelection.invulnerables(); if (peopleCollators) { cCount += peopleCollators.length; } } } catch (err) { if (import.meta.env.DEV) console.warn('Failed to fetch People Chain collators', err); } // 3. Count Nominators from Asset Hub (staking migrated to AH) let nCount = 0; try { if (isAssetHubReady && assetHubApi?.query.staking?.nominators) { const nominators = await assetHubApi.query.staking.nominators.entries(); if (nominators) { nCount = nominators.length; } } } catch { if (import.meta.env.DEV) console.warn('Staking pallet not available on AH, nominators = 0'); } setValidatorCount(vCount); setCollatorCount(cCount); setNominatorCount(nCount); setPeers(health.peers.toNumber()); } catch (err) { if (import.meta.env.DEV) console.error('Failed to update network stats:', err); } }; // Initial update await updateNetworkStats(); // Update every 3 seconds intervalId = setInterval(updateNetworkStats, 3000); } catch (err) { if (import.meta.env.DEV) console.error('Failed to subscribe to blocks:', err); } }; subscribeToBlocks(); return () => { if (unsubscribeNewHeads) unsubscribeNewHeads(); if (unsubscribeFinalizedHeads) unsubscribeFinalizedHeads(); if (intervalId) clearInterval(intervalId); }; }, [api, assetHubApi, peopleApi, isApiReady, isAssetHubReady, isPeopleReady]); if (error) { return ( {t('networkStats.disconnected')}

{error}

{t('networkStats.disconnectedDesc')}

); } if (!isApiReady) { return ( {t('networkStats.connecting')} ); } return (
{/* Connection Status */} {t('networkStats.title')}
{t('networkStats.connected')} {peers} {t('networkStats.peers')}
{/* Latest Block */} {t('networkStats.latestBlock')}
#{blockNumber.toLocaleString()}
{blockHash.slice(0, 10)}...{blockHash.slice(-8)}
{/* Finalized Block */} {t('networkStats.finalizedBlock')}
#{finalizedBlock.toLocaleString()}
{blockNumber - finalizedBlock} {t('networkStats.blocksBehind')}
{/* Validators */} {t('networkStats.activeValidators')}
{validatorCount}
{t('networkStats.validating')}
{/* Collators */} {t('networkStats.activeCollators')}
{collatorCount}
{t('networkStats.producing')}
{/* Nominators */} {t('networkStats.activeNominators')}
{nominatorCount}
{t('networkStats.staking')}
); };