Add bridge lane health API and config-ready lane UI for Tier A Week 3.
Probe LINK balances on CCIP bridge contracts, expose proof-transfer metadata on bridge status, and render funded/unfunded lane health on /bridge with extended smoke coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
107
frontend/src/components/explorer/BridgeLaneHealthPanel.tsx
Normal file
107
frontend/src/components/explorer/BridgeLaneHealthPanel.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { Card } from '@/libs/frontend-ui-primitives'
|
||||
import EntityBadge from '@/components/common/EntityBadge'
|
||||
import type { MissionControlBridgeLane, MissionControlBridgeLaneHealth } from '@/services/api/missionControl'
|
||||
|
||||
function laneTone(status?: string | null): 'success' | 'warning' | 'info' | 'neutral' {
|
||||
switch (String(status || '').toLowerCase()) {
|
||||
case 'funded':
|
||||
case 'proof-recorded':
|
||||
return 'success'
|
||||
case 'degraded':
|
||||
case 'proof-pending':
|
||||
return 'warning'
|
||||
case 'unfunded':
|
||||
return 'warning'
|
||||
default:
|
||||
return 'neutral'
|
||||
}
|
||||
}
|
||||
|
||||
function formatLinkBalance(wei?: string | null): string {
|
||||
if (!wei) return 'Unknown'
|
||||
try {
|
||||
const value = BigInt(wei)
|
||||
const whole = value / 10n ** 18n
|
||||
const fractional = (value % 10n ** 18n).toString().padStart(18, '0').slice(0, 4).replace(/0+$/, '')
|
||||
return fractional ? `${whole}.${fractional} LINK` : `${whole} LINK`
|
||||
} catch {
|
||||
return wei
|
||||
}
|
||||
}
|
||||
|
||||
export default function BridgeLaneHealthPanel({
|
||||
laneHealth,
|
||||
className = '',
|
||||
}: {
|
||||
laneHealth?: MissionControlBridgeLaneHealth | null
|
||||
className?: string
|
||||
}) {
|
||||
const lanes = laneHealth?.lanes || []
|
||||
if (lanes.length === 0) return null
|
||||
|
||||
const normalizedClassName = className ? ` ${className}` : ''
|
||||
|
||||
return (
|
||||
<Card title="Config-ready lane health" className={`mb-8${normalizedClassName}`}>
|
||||
<p className="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
LINK balances are read from each remote CCIP bridge contract. Proof-transfer status comes from operator-recorded CCIP message hashes when available.
|
||||
</p>
|
||||
<p className="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
Operator runbook: fund LINK with{' '}
|
||||
<code className="rounded bg-gray-100 px-1.5 py-0.5 text-xs dark:bg-gray-900">fund-ccip-bridges-with-link.sh</code>
|
||||
{' '}· lane probe{' '}
|
||||
<code className="rounded bg-gray-100 px-1.5 py-0.5 text-xs dark:bg-gray-900">probe-bridge-lane-link-balances.sh</code>
|
||||
{' '}· routing reference{' '}
|
||||
<Link href="/docs/public-api-access" className="text-primary-600 hover:underline">
|
||||
public API access
|
||||
</Link>
|
||||
</p>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-200 text-left text-xs uppercase tracking-wide text-gray-500 dark:border-gray-700 dark:text-gray-400">
|
||||
<th className="py-2 pr-4">Lane</th>
|
||||
<th className="py-2 pr-4">Overall</th>
|
||||
<th className="py-2 pr-4">Proof</th>
|
||||
<th className="py-2 pr-4">WETH9 bridge</th>
|
||||
<th className="py-2 pr-4">WETH10 bridge</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{lanes.map((lane: MissionControlBridgeLane) => (
|
||||
<tr key={lane.key} className="border-b border-gray-100 align-top last:border-0 dark:border-gray-800">
|
||||
<td className="py-3 pr-4">
|
||||
<div className="font-medium text-gray-900 dark:text-white">{lane.chain_name || lane.key}</div>
|
||||
<div className="mt-1 text-xs text-gray-500 dark:text-gray-400">chain {lane.chain_id ?? '?'}</div>
|
||||
</td>
|
||||
<td className="py-3 pr-4">
|
||||
<EntityBadge label={lane.status || 'unknown'} tone={laneTone(lane.status)} />
|
||||
</td>
|
||||
<td className="py-3 pr-4">
|
||||
<EntityBadge label={lane.proof_status || 'proof-pending'} tone={laneTone(lane.proof_status)} />
|
||||
</td>
|
||||
<td className="py-3 pr-4">
|
||||
<div className="font-mono text-xs text-gray-700 dark:text-gray-300">{lane.weth9?.bridge || '—'}</div>
|
||||
<div className="mt-1 flex flex-wrap items-center gap-2">
|
||||
<EntityBadge label={lane.weth9?.status || 'unknown'} tone={laneTone(lane.weth9?.status)} className="px-2 py-0.5 text-[11px]" />
|
||||
<span className="text-xs text-gray-600 dark:text-gray-400">{formatLinkBalance(lane.weth9?.link_balance_wei)}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 pr-4">
|
||||
<div className="font-mono text-xs text-gray-700 dark:text-gray-300">{lane.weth10?.bridge || '—'}</div>
|
||||
<div className="mt-1 flex flex-wrap items-center gap-2">
|
||||
<EntityBadge label={lane.weth10?.status || 'unknown'} tone={laneTone(lane.weth10?.status)} className="px-2 py-0.5 text-[11px]" />
|
||||
<span className="text-xs text-gray-600 dark:text-gray-400">{formatLinkBalance(lane.weth10?.link_balance_wei)}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import { resolveEffectiveFreshness } from '@/utils/explorerFreshness'
|
||||
import { bridgeRoutesApi, normalizeBridgeRouteEntries, type BridgeRoutesResponse } from '@/services/api/bridgeRoutes'
|
||||
import { createVisibilityAwarePoller } from '@/utils/visibilityRefresh'
|
||||
import { HOME_DASHBOARD_REFRESH_MS } from '@/utils/featuredTokens'
|
||||
import BridgeLaneHealthPanel from '@/components/explorer/BridgeLaneHealthPanel'
|
||||
import OperationsSurfaceNav from './OperationsSurfaceNav'
|
||||
import OperationsActionGrid from './OperationsActionGrid'
|
||||
|
||||
@@ -460,6 +461,8 @@ export default function BridgeMonitoringPage({
|
||||
))}
|
||||
</div>
|
||||
|
||||
<BridgeLaneHealthPanel laneHealth={bridgeStatus?.data?.bridge_lanes} />
|
||||
|
||||
{routeEntries.length > 0 ? (
|
||||
<Card title="CCIP route catalog" className="mb-8">
|
||||
<p className="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
|
||||
@@ -10,6 +10,11 @@ const docsCards = [
|
||||
href: '/docs/posture-glossary',
|
||||
description: 'First-read definitions for x402, ISO-20022, forward-canonical, cW public-network, and related explorer badges.',
|
||||
},
|
||||
{
|
||||
title: 'Config compatibility keys',
|
||||
href: '/docs/posture-glossary#transportactive-config-compatibility',
|
||||
description: 'Methodology for public /config compatibility keys (transportActive, forward-canonical) and planned v2 alias mapping.',
|
||||
},
|
||||
{
|
||||
title: 'Public API access',
|
||||
href: '/docs/public-api-access',
|
||||
|
||||
@@ -36,7 +36,8 @@ export default function PostureGlossaryDocsPage() {
|
||||
</Card>
|
||||
|
||||
{postureGlossaryTerms.map((term) => (
|
||||
<Card key={term.id} title={term.title}>
|
||||
<div key={term.id} id={term.id === 'transport-active' ? 'transportactive-config-compatibility' : undefined}>
|
||||
<Card title={term.title}>
|
||||
<div className="space-y-3 text-sm leading-6 text-gray-600 dark:text-gray-400">
|
||||
<p>{term.summary}</p>
|
||||
<div className="rounded-xl border border-sky-200 bg-sky-50/70 p-4 text-sky-950 dark:border-sky-900/50 dark:bg-sky-950/20 dark:text-sky-100">
|
||||
@@ -52,6 +53,7 @@ export default function PostureGlossaryDocsPage() {
|
||||
) : null}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<Card title="Related references">
|
||||
|
||||
@@ -365,7 +365,9 @@ export default function TokenDetailPage() {
|
||||
<PostureBadge label="Non-canonical indexed token" tone="warning" />
|
||||
</div>
|
||||
<p className="mt-3 text-sm leading-6 text-amber-950 dark:text-amber-100">
|
||||
This contract is indexed by Blockscout but is not in the curated Chain 138 token registry. Prefer canonical addresses from the token index for trading, liquidity, and bridge routing.
|
||||
This contract is indexed by Blockscout but is not in the curated Chain 138 token registry. Prefer canonical addresses from the{' '}
|
||||
<Link href="/tokens" className="font-medium text-primary-600 hover:underline">token index</Link>
|
||||
{' '}and the posture glossary for trading, liquidity, and bridge routing.
|
||||
</p>
|
||||
</Card>
|
||||
) : null}
|
||||
|
||||
@@ -95,6 +95,32 @@ export interface MissionControlSubsystemStatus {
|
||||
completeness?: string | null
|
||||
}
|
||||
|
||||
export interface MissionControlBridgeLaneContract {
|
||||
bridge?: string
|
||||
link_balance_wei?: string
|
||||
status?: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface MissionControlBridgeLane {
|
||||
key: string
|
||||
chain_name?: string
|
||||
chain_id?: number
|
||||
config_ready?: boolean
|
||||
link_token?: string
|
||||
status?: string
|
||||
proof_status?: string
|
||||
weth9?: MissionControlBridgeLaneContract
|
||||
weth10?: MissionControlBridgeLaneContract
|
||||
rpc_endpoint?: string
|
||||
}
|
||||
|
||||
export interface MissionControlBridgeLaneHealth {
|
||||
updated_at?: string
|
||||
min_link_wei?: string
|
||||
lanes?: MissionControlBridgeLane[]
|
||||
}
|
||||
|
||||
export interface MissionControlBridgeStatusResponse {
|
||||
data?: {
|
||||
status?: string
|
||||
@@ -112,6 +138,8 @@ export interface MissionControlBridgeStatusResponse {
|
||||
chains?: Record<string, MissionControlChainStatus>
|
||||
ccip_relay?: MissionControlRelayPayload
|
||||
ccip_relays?: Record<string, MissionControlRelayPayload>
|
||||
bridge_lanes?: MissionControlBridgeLaneHealth
|
||||
proof_transfers?: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user