Files
explorer-monorepo/frontend/src/services/api/missionControl.ts
defiQUG 26b0f1bf48 feat(bridge-monitoring): show inventory shortfall for queued releases
Surface mission-control bridge_inventory scope, wei shortfall fields, and
relay card formatting for WETH display.

Made-with: Cursor
2026-04-12 06:44:20 -07:00

300 lines
8.3 KiB
TypeScript

import { getExplorerApiBase } from './blockscout'
export interface MissionControlRelaySummary {
text: string
tone: 'normal' | 'warning' | 'danger'
items: MissionControlRelayItemSummary[]
}
export interface MissionControlRelayItemSummary {
key: string
label: string
status: string
text: string
tone: 'normal' | 'warning' | 'danger'
}
export interface MissionControlRelaySnapshot {
status?: string
last_error?: {
scope?: string
message?: string
message_id?: string
target_bridge?: string
token?: string
available_amount?: string
required_amount?: string
shortfall?: string
}
monitoring?: {
delivery_enabled?: boolean
shedding?: boolean
}
service?: {
profile?: string
}
source?: {
chain_name?: string
bridge_filter?: string
}
destination?: {
chain_name?: string
relay_bridge?: string
relay_bridge_default?: string
}
queue?: {
size?: number
processed?: number
failed?: number
}
last_source_poll?: {
at?: string
ok?: boolean
logs_fetched?: number
}
}
export interface MissionControlRelayProbe {
ok?: boolean
body?: MissionControlRelaySnapshot
}
export interface MissionControlRelayPayload {
url_probe?: MissionControlRelayProbe
file_snapshot?: MissionControlRelaySnapshot
file_snapshot_error?: string
}
export interface MissionControlChainStatus {
status?: string
name?: string
head_age_sec?: number
latency_ms?: number
block_number?: string
}
export interface MissionControlMode {
kind?: string | null
updated_at?: string | null
age_seconds?: number | null
reason?: string | null
scope?: string | null
source?: string | null
confidence?: string | null
provenance?: string | null
}
export interface MissionControlSubsystemStatus {
status?: string | null
updated_at?: string | null
age_seconds?: number | null
source?: string | null
confidence?: string | null
provenance?: string | null
completeness?: string | null
}
export interface MissionControlBridgeStatusResponse {
data?: {
status?: string
checked_at?: string
freshness?: unknown
sampling?: {
stats_generated_at?: string | null
rpc_probe_at?: string | null
stats_window_seconds?: number | null
issues?: Record<string, string> | null
}
mode?: MissionControlMode
subsystems?: Record<string, MissionControlSubsystemStatus>
chains?: Record<string, MissionControlChainStatus>
ccip_relay?: MissionControlRelayPayload
ccip_relays?: Record<string, MissionControlRelayPayload>
}
}
const missionControlRelayLabels: Record<string, string> = {
mainnet: 'Mainnet',
mainnet_weth: 'Mainnet WETH',
mainnet_cw: 'Mainnet cW',
bsc: 'BSC',
avax: 'Avalanche',
avalanche: 'Avalanche',
avax_cw: 'Avalanche cW',
avax_to_138: 'Avalanche -> 138',
}
function getMissionControlStreamUrl(): string {
return `${getExplorerApiBase()}/explorer-api/v1/mission-control/stream`
}
function getMissionControlBridgeStatusUrl(): string {
return `${getExplorerApiBase()}/explorer-api/v1/track1/bridge/status`
}
function relativeAge(isoString?: string): string {
if (!isoString) return ''
const parsed = Date.parse(isoString)
if (!Number.isFinite(parsed)) return ''
const seconds = Math.max(0, Math.round((Date.now() - parsed) / 1000))
if (seconds < 60) return `${seconds}s ago`
const minutes = Math.round(seconds / 60)
if (minutes < 60) return `${minutes}m ago`
const hours = Math.round(minutes / 60)
return `${hours}h ago`
}
function describeRelayStatus(snapshot: MissionControlRelaySnapshot, status: string): string {
if (snapshot.last_error?.scope === 'bridge_inventory') {
return snapshot.queue?.size && snapshot.queue.size > 0
? 'underfunded queued release'
: 'underfunded release'
}
if (status === 'paused' && snapshot.monitoring?.delivery_enabled === false) {
return snapshot.queue?.size && snapshot.queue.size > 0 ? 'delivery paused (queueing)' : 'delivery paused'
}
if (status === 'paused' && snapshot.monitoring?.shedding) {
return 'paused (shedding)'
}
return status
}
export function summarizeMissionControlRelay(
response: MissionControlBridgeStatusResponse | null | undefined
): MissionControlRelaySummary | null {
const relays = getMissionControlRelays(response)
if (!relays) return null
const items = Object.entries(relays)
.map(([key, relay]): MissionControlRelayItemSummary | null => {
const label = getMissionControlRelayLabel(key)
if (relay.url_probe && relay.url_probe.ok === false) {
return {
key,
label,
status: 'down',
text: `${label}: probe failed`,
tone: 'danger',
}
}
if (relay.file_snapshot_error) {
return {
key,
label,
status: 'snapshot-error',
text: `${label}: snapshot error`,
tone: 'danger',
}
}
const snapshot = relay.url_probe?.body || relay.file_snapshot
if (!snapshot) {
return {
key,
label,
status: 'configured',
text: `${label}: probe configured`,
tone: 'normal',
}
}
const status = String(snapshot.status || 'unknown').toLowerCase()
const statusLabel = describeRelayStatus(snapshot, status)
const destination = snapshot.destination?.chain_name
const queueSize = snapshot.queue?.size
const pollAge = relativeAge(snapshot.last_source_poll?.at)
let text = `${label}: ${statusLabel}`
if (destination) text += ` -> ${destination}`
if (queueSize != null) text += ` · queue ${queueSize}`
if (pollAge) text += ` · polled ${pollAge}`
let tone: MissionControlRelaySummary['tone'] = 'normal'
if (['paused', 'starting'].includes(status)) {
tone = 'warning'
}
if (['degraded', 'stale', 'stopped', 'down'].includes(status)) {
tone = 'danger'
}
return { key, label, status, text, tone }
})
.filter((item): item is MissionControlRelayItemSummary => item !== null)
if (items.length === 0) return null
const tone: MissionControlRelaySummary['tone'] = items.some((item) => item.tone === 'danger')
? 'danger'
: items.some((item) => item.tone === 'warning')
? 'warning'
: 'normal'
const text =
items.length === 1
? items[0].text
: tone === 'normal'
? `${items.length} relay lanes operational`
: `Relay lanes need attention`
return { text, tone, items }
}
export function getMissionControlRelays(
response: MissionControlBridgeStatusResponse | null | undefined
): Record<string, MissionControlRelayPayload> | null {
return response?.data?.ccip_relays && Object.keys(response.data.ccip_relays).length > 0
? response.data.ccip_relays
: response?.data?.ccip_relay
? { mainnet: response.data.ccip_relay }
: null
}
export function getMissionControlRelayLabel(key: string): string {
return missionControlRelayLabels[key] || key.toUpperCase()
}
export const missionControlApi = {
getBridgeStatus: async (): Promise<MissionControlBridgeStatusResponse> => {
const response = await fetch(getMissionControlBridgeStatusUrl())
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
return (await response.json()) as MissionControlBridgeStatusResponse
},
subscribeBridgeStatus: (
onStatus: (status: MissionControlBridgeStatusResponse) => void,
onError?: (error: unknown) => void
): (() => void) => {
if (typeof window === 'undefined' || typeof window.EventSource === 'undefined') {
onError?.(new Error('EventSource is not available in this environment'))
return () => {}
}
const eventSource = new window.EventSource(getMissionControlStreamUrl())
const handleMessage = (event: MessageEvent<string>) => {
try {
const payload = JSON.parse(event.data) as MissionControlBridgeStatusResponse
onStatus(payload)
} catch (error) {
onError?.(error)
}
}
eventSource.addEventListener('mission-control', handleMessage)
eventSource.onmessage = handleMessage
eventSource.onerror = () => {
onError?.(new Error('Mission-control live stream connection lost'))
}
return () => {
eventSource.removeEventListener('mission-control', handleMessage)
eventSource.close()
}
},
}