import type { Block } from '@/services/api/blocks' import type { Transaction } from '@/services/api/transactions' import type { ExplorerFreshnessSnapshot } from '@/services/api/stats' export type ChainActivityState = 'active' | 'low' | 'inactive' | 'unknown' export interface ChainActivityContext { latest_block_number: number | null latest_block_timestamp: string | null latest_transaction_block_number: number | null latest_transaction_timestamp: string | null last_non_empty_block_number: number | null last_non_empty_block_timestamp: string | null block_gap_to_latest_transaction: number | null latest_transaction_age_seconds: number | null state: ChainActivityState head_is_idle: boolean transaction_visibility_unavailable: boolean } function sortDescending(values: number[]): number[] { return [...values].sort((left, right) => right - left) } function toTimestamp(value?: string | null): number | null { if (!value) return null const parsed = Date.parse(value) return Number.isFinite(parsed) ? parsed : null } export function summarizeChainActivity(input: { blocks?: Block[] transactions?: Transaction[] latestBlockNumber?: number | null latestBlockTimestamp?: string | null freshness?: ExplorerFreshnessSnapshot | null }): ChainActivityContext { const freshness = input.freshness || null const blocks = Array.isArray(input.blocks) ? input.blocks : [] const transactions = Array.isArray(input.transactions) ? input.transactions : [] const latestBlockFromList = sortDescending(blocks.map((block) => block.number).filter((value) => Number.isFinite(value)))[0] ?? null const latestBlock = freshness?.chain_head.block_number ?? input.latestBlockNumber ?? latestBlockFromList const latestBlockTimestamp = freshness?.chain_head.timestamp ?? input.latestBlockTimestamp ?? blocks.find((block) => block.number === latestBlock)?.timestamp ?? blocks[0]?.timestamp ?? null const latestTransaction = freshness?.latest_indexed_transaction.block_number ?? sortDescending( transactions.map((transaction) => transaction.block_number).filter((value) => Number.isFinite(value)), )[0] ?? null const latestTransactionRecord = transactions.find((transaction) => transaction.block_number === latestTransaction) ?? transactions[0] ?? null const nonEmptyBlock = freshness?.latest_non_empty_block.block_number ?? sortDescending(blocks.filter((block) => block.transaction_count > 0).map((block) => block.number))[0] ?? latestTransaction const nonEmptyBlockTimestamp = freshness?.latest_non_empty_block.timestamp ?? blocks.find((block) => block.number === nonEmptyBlock)?.timestamp ?? latestTransactionRecord?.created_at ?? null const latestTransactionTimestamp = freshness?.latest_indexed_transaction.timestamp ?? latestTransactionRecord?.created_at ?? null const transactionVisibilityUnavailable = freshness?.latest_indexed_transaction.source === 'unavailable' || freshness?.latest_indexed_transaction.completeness === 'unavailable' const latestTransactionAgeSeconds = freshness?.latest_indexed_transaction.age_seconds ?? (() => { const timestamp = toTimestamp(latestTransactionTimestamp) if (timestamp == null) return null return Math.max(0, Math.round((Date.now() - timestamp) / 1000)) })() const gap = freshness?.latest_non_empty_block.distance_from_head ?? (latestBlock != null && latestTransaction != null ? Math.max(0, latestBlock - latestTransaction) : null) const state: ChainActivityState = latestTransactionAgeSeconds == null ? 'unknown' : latestTransactionAgeSeconds <= 15 * 60 ? 'active' : latestTransactionAgeSeconds <= 3 * 60 * 60 ? 'low' : 'inactive' const headIsIdle = gap != null && gap > 0 && latestTransactionAgeSeconds != null && latestTransactionAgeSeconds > 0 return { latest_block_number: latestBlock, latest_block_timestamp: latestBlockTimestamp, latest_transaction_block_number: latestTransaction, latest_transaction_timestamp: latestTransactionTimestamp, last_non_empty_block_number: nonEmptyBlock, last_non_empty_block_timestamp: nonEmptyBlockTimestamp, block_gap_to_latest_transaction: gap, latest_transaction_age_seconds: latestTransactionAgeSeconds, state, head_is_idle: headIsIdle, transaction_visibility_unavailable: transactionVisibilityUnavailable, } }