import { useEffect, useState, useMemo } from "react"; import { Box, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography, Link, LinearProgress, } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import WarningIcon from '@mui/icons-material/Warning'; import CancelIcon from '@mui/icons-material/Cancel'; import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import GhostStyledIcon from "../../components/Icon/GhostIcon"; import InfoTooltip from "../../components/Tooltip/InfoTooltip"; import { PrimaryButton } from "../../components/Button"; import { GHOST_CONNECT } from "../../constants/ecosystem"; export const ValidatorTable = ({ currentTime, currentBlock, latestCommits, isVerySmallScreen, bridgeStability, providerDetail, }) => { const theme = useTheme(); const [sortedBy, setsortedBy] = useState(undefined); const stabilityColor = useMemo(() => { const red = Math.round(255 * (1 - bridgeStability / 100)); const green = Math.round(255 * (bridgeStability / 100)); return `rgb(${red}, ${green}, 0)`; }, [bridgeStability]); const sortedCommits = useMemo(() => { return latestCommits?.sort((a, b) => { let one = 0; let two = 0; switch (sortedBy) { case -1: one = Number(b.lastUpdated ?? 0n); two = Number(a.lastUpdated ?? 0n); break; case 1: one = Number(a.lastUpdated ?? 0n); two = Number(b.lastUpdated ?? 0n); break; case -2: one = Number(b.lastStoredBlock ?? 0n); two = Number(a.lastStoredBlock ?? 0n); break; case 2: one = Number(a.lastStoredBlock ?? 0n); two = Number(b.lastStoredBlock ?? 0n); break; case -3: one = Number(b.lastStoredBlock ?? 0n); two = Number(a.lastStoredBlock ?? 0n); break; case 3: one = Number(a.lastStoredBlock ?? 0n); two = Number(b.lastStoredBlock ?? 0n); break; case -4: one = b.disabled ? 1 : 0; two = a.disabled ? 1 : 0; break; case 4: one = a.disabled ? 1 : 0; two = b.disabled ? 1 : 0; break; default: break; } return one - two; }) ?? []; }, [latestCommits, sortedBy]); const changeSort = (prevValue, newValue) => { if (prevValue === undefined) { return newValue; } const prevValueAbs = Math.abs(prevValue); const newValueAbs = Math.abs(newValue); if (prevValueAbs === newValueAbs) { return prevValue * -1; } else { return newValue; } } return ( {!providerDetail && GHOST Connect is not detected on your browser! Download GHOST Connect browser extension for real-time visibility into validator status and related transaction risks. Important: The GHOST Connect is optional, but be aware that your bridge transaction will succeed or fail irreversibly based on the condition of the validators. window.open(GHOST_CONNECT, '_blank', 'noopener,noreferrer')}> Get GHOST Connect } {providerDetail && setsortedBy(undefined)} value="Validator" background={theme.colors.paper.cardHover} borderTopLeftRadius="3px" /> setsortedBy(prev => changeSort(prev, 1))} value="Last Acitve" background={theme.colors.paper.cardHover} tooltip="GHOST Validators must submit block commitments every 10 minutes to remain active." /> setsortedBy(prev => changeSort(prev, 2))} value="Block Height" background={theme.colors.paper.cardHover} tooltip="The latest EVM block height reported by the Validator." /> setsortedBy(prev => changeSort(prev, 3))} value="Block Delayed" background={theme.colors.paper.cardHover} tooltip="Block delays under 4 hours are safe. Block delays over 4 hours risk bridge transaction failure." /> setsortedBy(prev => changeSort(prev, 4))} value="Status" background={theme.colors.paper.cardHover} borderTopRightRadius="3px" tooltip="Active and disabled validators fore the current GHOST Epoch." /> {latestCommits?.map((commit, index) => { return ( ) })}
} See Validator Telemetry
) } const BridgeHeaderTableCell = ({ index=0, sortedBy=undefined, changeSort, align="center", borderTopRightRadius="0px", borderTopLeftRadius="0px", borderBottomRightRadius="0px", borderBottomLeftRadius="0px", background="transparent", fontSize="12px", padding="0px", tooltip, value }) => { return ( {value} {tooltip ? Math.abs(sortedBy) !== index ? : 0 ? KeyboardArrowDownIcon : KeyboardArrowUpIcon} /> : <> } ) } const BridgeTableCell = ({ align="center", borderTopRightRadius="0px", borderTopLeftRadius="0px", borderBottomRightRadius="0px", borderBottomLeftRadius="0px", background="transparent", fontSize="10px", padding="0px", value }) => { return ( {value} ) } const ValidatorRow = ({ colors, index, currentTime, currentBlock, commit, }) => { const background = index % 2 === 1 ? "" : colors.gray[750]; return ( ) } const sliceString = (string, first, second) => { if (!string) return ""; return string.slice(0, first) + "..." + string.slice(second); } const getTimeAgo = (timestampDiff) => { const seconds = Math.floor(timestampDiff / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); if (seconds < 60) return `${seconds}s ago`; if (minutes < 60) return `${minutes}m ago`; if (hours < 24) return `${hours}h ago`; return "long ago"; } const blockDelayIcon = (colors, timestampDiff) => { let color = colors.feedback.error; let icon = CancelIcon; if (timestampDiff < 900000n) { color = colors.feedback.success; icon = CheckCircleIcon; } else if (timestampDiff < 2700000n) { color = colors.feedback.warning; icon = WarningIcon; } return ( ) } const statusIcon = (colors, disabled) => { let color = colors.feedback.success; let icon = CheckCircleIcon; if (disabled === true) { color = colors.feedback.error; icon = CancelIcon; } return ( ) }