diff --git a/apps/scan/app/[locale]/tokens/page.tsx b/apps/scan/app/[locale]/tokens/page.tsx index 6756372..5157972 100644 --- a/apps/scan/app/[locale]/tokens/page.tsx +++ b/apps/scan/app/[locale]/tokens/page.tsx @@ -14,6 +14,7 @@ import { EmptyState } from "@/components/common/EmptyState"; import { FetchError } from "@/components/common/FetchError"; import { useNetwork, useNetworkFromQuery } from "@/lib/network-context"; import { useTokens } from "@/lib/hooks"; +import { useRefetchOnNewBlock } from "@/lib/ws"; import { formatNumber, shortenAddress } from "@/lib/format"; type SortKey = "supply" | "holders" | "transfers" | "none"; @@ -26,7 +27,9 @@ export default function TokensPage() { // Deeplink network switch. useNetworkFromQuery(); const searchParams = useSearchParams(); - const { data: tokens, loading, error, retry } = useTokens(network); + const { data: tokens, loading, error, retry, refetch } = useTokens(network); + // Refresh shortly after each new block instead of waiting for the 30s poll. + useRefetchOnNewBlock(network, refetch); const [sortKey, setSortKey] = useState("none"); const [sortDir, setSortDir] = useState<"asc" | "desc">("desc"); // ?standard=tokenop|evm pre-selects the filter so the Native rail's diff --git a/apps/scan/app/[locale]/validators/page.tsx b/apps/scan/app/[locale]/validators/page.tsx index ee36458..2f22d9c 100644 --- a/apps/scan/app/[locale]/validators/page.tsx +++ b/apps/scan/app/[locale]/validators/page.tsx @@ -13,6 +13,7 @@ import { StatCard } from "@/components/common/StatCard"; import { EmptyState } from "@/components/common/EmptyState"; import { useNetwork, useNetworkFromQuery } from "@/lib/network-context"; import { useValidators } from "@/lib/hooks"; +import { useRefetchOnNewBlock } from "@/lib/ws"; import { formatNumber } from "@/lib/format"; type SortKey = "blocks" | "stake" | "uptime" | "none"; @@ -32,7 +33,9 @@ export default function ValidatorsPage() { const { network } = useNetwork(); // Deeplink network switch. useNetworkFromQuery(); - const { data: validators, loading, error, retry } = useValidators(network); + const { data: validators, loading, error, retry, refetch } = useValidators(network); + // Refresh shortly after each new block instead of waiting for the poll cycle. + useRefetchOnNewBlock(network, refetch); const [sortKey, setSortKey] = useState("none"); const [sortDir, setSortDir] = useState<"asc" | "desc">("desc"); const [statusFilter, setStatusFilter] = useState("all"); diff --git a/apps/scan/lib/ws.ts b/apps/scan/lib/ws.ts index 5314dc2..bcfd4c5 100644 --- a/apps/scan/lib/ws.ts +++ b/apps/scan/lib/ws.ts @@ -160,6 +160,28 @@ export function useLatestBlock(network: NetworkId): NewHeadEvent | null { return head; } +// Trigger `refetch` shortly after each new block so a page reflects chain +// changes without waiting for its full poll interval. Throttled to at most one +// call per `minIntervalMs`: fast block times (testnet ~0.5s) would otherwise +// turn this into a request flood for data that changes slowly (validator set, +// token list), blowing the per-IP rate budget the poll intervals are sized for. +export function useRefetchOnNewBlock( + network: NetworkId, + refetch: () => void, + minIntervalMs = 10_000, +): void { + const head = useLatestBlock(network); + const lastRun = useRef(0); + useEffect(() => { + if (!head) return; + const now = Date.now(); + if (now - lastRun.current < minIntervalMs) return; + lastRun.current = now; + refetch(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [head?.number, minIntervalMs]); +} + export function useLatestFinalized(network: NetworkId): number | null { const [height, setHeight] = useState(null); useEffect(() => {