07. Validator Dashboard
Last updated
Was this helpful?
Was this helpful?
import axiosInstance from '@/packages/validator-dashboard/api/axiosInstance'
import type { MonitoringReport } from '@/packages/validator-dashboard/types'
export const getValidatorDashboardByAddress = async (address: string) => {
const response = await axiosInstance.get<Array<MonitoringReport>>(
`/validator_status/${address}`
)
return response.data
}export enum ValidatorStatus {
OK = 'OK',
FROZEN = 'Frozen',
WARNED = 'Warned',
SLASHED = 'Slashed',
RESTORED = 'Restored',
NEW = 'New'
}
interface MonitoringReport {
validator_id: string
status: ValidatorStatus
ss58_address: string
self_hosted: boolean
share: number
block: number
time: number
}[
{
"validator_id": "123",
"status": "OK",
"ss58_address": "g6x123...",
"self_hosted": true,
"share": 1000,
"block": 12345,
"time": 1705320600
}
]import { useQuery } from '@tanstack/react-query'
import { getValidatorDashboardByAddress } from '@/packages/validator-dashboard/api/queries'
function ValidatorDashboard({ validatorAddress }: { validatorAddress: string }) {
const { data, isLoading } = useQuery({
queryKey: ['validator-dashboard', validatorAddress],
queryFn: () => getValidatorDashboardByAddress(validatorAddress),
enabled: !!validatorAddress,
refetchInterval: 300000, // 5 minutes
staleTime: 60000 // 1 minute
})
if (isLoading) return <div>Loading...</div>
return (
<Card>
<CardHeader>
<CardTitle>Validator {validatorAddress}</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-muted-foreground">Status</p>
<Badge variant={data.status === 'OK' ? 'success' : 'secondary'}>
{data.status}
</Badge>
</div>
<div>
<p className="text-sm text-muted-foreground">Self Hosted</p>
<p className="text-2xl font-bold">{data.self_hosted ? 'Yes' : 'No'}</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Share</p>
<p className="text-2xl font-bold">{data.share}</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Block Height</p>
<p className="text-2xl font-bold">{data.block}</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Last Update</p>
<p className="text-md">{new Date(data.time * 1000).toLocaleString()}</p>
</div>
</div>
</CardContent>
</Card>
)
}export const getAllValidators = async () => {
const response = await axiosInstance.get<string[]>('/reward_addresses')
return response.data
}["g6x123...", "g6x456...", "g6x789..."]import { getAllValidators } from '@/packages/validator-dashboard/api/queries'
function ValidatorList() {
const { data: validators, isLoading } = useQuery({
queryKey: ['validators', 'all'],
queryFn: getAllValidators,
staleTime: 300000 // 5 minutes
})
if (isLoading) return <div>Loading...</div>
return (
<div>
<h2>Active Validators ({validators?.length || 0})</h2>
<div className="grid gap-4">
{validators?.map(address => (
<ValidatorCard key={address} address={address} />
))}
</div>
</div>
)
}
function ValidatorCard({ address }: { address: string }) {
const { data: dashboard } = useQuery({
queryKey: ['validator-dashboard', address],
queryFn: () => getValidatorDashboardByAddress(address)
})
return (
<Card>
<CardContent>
<p className="font-mono text-sm">{address}</p>
{dashboard && (
<div className="flex gap-4 mt-2">
<Badge variant={dashboard.status === 'active' ? 'success' : 'secondary'}>
{dashboard.status}
</Badge>
<span className="text-sm">Uptime: {dashboard.uptime.toFixed(2)}%</span>
</div>
)}
</CardContent>
</Card>
)
}
interface BlockchainValidatorData {
activeValidators: Array<string> // List of active validator addresses
currentBlock: number // Current block number
totalTransactions: string // Estimated total transactions
avgTrxPerBlock: number // Average transactions per block
}import { useBlockchainData } from '@/packages/validator-dashboard/hooks/useBlockchainData'
function NetworkStats() {
const { data: blockchainData, isLoading } = useBlockchainData()
if (isLoading) return <div>Loading network data...</div>
return (
<div>
<p>Current Block: {blockchainData?.currentBlock}</p>
<p>Active Validators: {blockchainData?.activeValidators?.length}</p>
<p>Avg Transactions/Block: {blockchainData?.avgTrxPerBlock}</p>
<p>Total Transactions: {blockchainData?.totalTransactions}</p>
</div>
)
}function ValidatorDashboardPage() {
// API data for specific validator
const { data: validatorReports } = useValidatorDashboard(validatorAddress)
// Real-time blockchain network data
const { data: blockchainData, isLoading: isBlockchainLoading } =
useBlockchainData()
return (
<div>
<NetworkOverview blockchainData={blockchainData} />
<ValidatorDetails reports={validatorReports} />
</div>
)
}import { useQuery } from '@tanstack/react-query'
export const useAccountBalance = (address?: string) => {
const { api } = useGen6()
return useQuery({
queryKey: ['accountBalance', address],
queryFn: async () => {
if (!api || !address) return null
const account = await api.query.system.account(address)
return account.toJSON()
},
enabled: !!api && !!address,
staleTime: 30000
})
}{
nonce: number,
consumers: number,
providers: number,
sufficients: number,
data: {
free: string, // Free balance
reserved: string, // Reserved balance
miscFrozen: string,
feeFrozen: string
}
}import axios from 'axios'
import { getValidatorsUrl } from '@/data/nodes'
export const axiosInstance = axios.create({
baseURL: getValidatorsUrl(),
headers: {
'Content-Type': 'application/json'
},
withCredentials: false // No authentication required
})
// Response interceptor for error handling
axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
const message =
error.response?.data?.error ||
error.response?.data?.detail ||
error.message
return Promise.reject(new Error(message))
}
)// All validators
;['validators', 'all'][
// Specific validator dashboard
('validator-dashboard', address)
]function LiveValidatorMonitor({ address }: { address: string }) {
const { data, isLoading, dataUpdatedAt } = useQuery({
queryKey: ['validatorDashboard', address],
queryFn: () => getValidatorDashboardByAddress(address),
refetchInterval: 30000, // Refetch every 30 seconds
refetchOnWindowFocus: true, // Refetch when user returns to tab
refetchOnReconnect: true // Refetch on network reconnect
})
const lastUpdate = new Date(dataUpdatedAt).toLocaleTimeString()
return (
<div>
<ValidatorDashboard data={data} />
<p className="text-xs text-muted-foreground mt-2">
Last updated: {lastUpdate}
</p>
</div>
)
}function ValidatorStatusBadge({ status }: { status: 'active' | 'inactive' }) {
const config = {
active: {
variant: 'success' as const,
icon: '🟢',
label: 'Active'
},
inactive: {
variant: 'secondary' as const,
icon: '🔴',
label: 'Inactive'
}
}
const { variant, icon, label } = config[status]
return (
<Badge variant={variant}>
{icon} {label}
</Badge>
)
}import { BN } from 'bn.js'
function ValidatorRewards({ rewards }: { rewards: string }) {
const { api } = useGen6()
const decimals = api.registry.chainDecimals[0]
const rewardsBN = new BN(rewards).div(new BN(10).pow(new BN(decimals)))
return (
<div>
<p className="text-sm text-muted-foreground">Total Rewards</p>
<p className="text-2xl font-bold">{rewardsBN.toString()} GEN6</p>
</div>
)
}function ValidatorMonitoringDashboard() {
const { data: validators } = useQuery({
queryKey: ['validators'],
queryFn: getAllValidators,
staleTime: 5 * 60 * 1000 // Cache for 5 minutes
})
const [selectedValidator, setSelectedValidator] = useState<string | null>(null)
return (
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Validator List */}
<Card className="lg:col-span-1">
<CardHeader>
<CardTitle>Validators ({validators?.total_count || 0})</CardTitle>
</CardHeader>
<CardContent>
<ScrollArea className="h-[600px]">
{validators?.validators.map(address => (
<button
key={address}
onClick={() => setSelectedValidator(address)}
className="w-full text-left p-2 hover:bg-accent rounded"
>
<ValidatorListItem address={address} />
</button>
))}
</ScrollArea>
</CardContent>
</Card>
{/* Detailed Dashboard */}
<div className="lg:col-span-2">
{selectedValidator ? (
<LiveValidatorMonitor address={selectedValidator} />
) : (
<Card>
<CardContent className="flex items-center justify-center h-[600px]">
<p className="text-muted-foreground">
Select a validator to view details
</p>
</CardContent>
</Card>
)}
</div>
</div>
)
}
function ValidatorListItem({ address }: { address: string }) {
const { data } = useQuery({
queryKey: ['validatorDashboard', address],
queryFn: () => getValidatorDashboardByAddress(address),
refetchInterval: 60000
})
return (
<div className="flex items-center justify-between">
<span className="font-mono text-xs truncate flex-1">
{address.slice(0, 10)}...{address.slice(-8)}
</span>
{data && (
<div className="flex items-center gap-2">
<span className="text-xs">{data.uptime.toFixed(1)}%</span>
<div className={`w-2 h-2 rounded-full ${
data.status === 'active' ? 'bg-green-500' : 'bg-gray-400'
}`} />
</div>
)}
</div>
)
}