Add bridge lane health API and config-ready lane UI for Tier A Week 3.
Some checks failed
Deploy Explorer Live / deploy (push) Failing after 13s
Validate Explorer / frontend (push) Failing after 21s
Validate Explorer / smoke-e2e (push) Has been skipped

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:
defiQUG
2026-05-23 04:21:44 -07:00
parent 763ca75c21
commit 228fa0eef6
14 changed files with 605 additions and 3 deletions

View File

@@ -0,0 +1,255 @@
package track1
import (
"context"
_ "embed"
"encoding/json"
"math/big"
"net/http"
"os"
"strconv"
"strings"
"time"
)
//go:embed bridge_lanes_default.json
var defaultBridgeLanesJSON []byte
type bridgeLaneDefinition struct {
Key string `json:"key"`
ChainName string `json:"chain_name"`
ChainID int64 `json:"chain_id"`
ConfigReady bool `json:"config_ready"`
RPCEnvs []string `json:"rpc_envs"`
RPCDefault string `json:"rpc_default"`
LinkToken string `json:"link_token"`
WETH9Bridge string `json:"weth9_bridge"`
WETH10Bridge string `json:"weth10_bridge"`
}
type bridgeLanesConfig struct {
Updated string `json:"updated"`
MinLinkWei string `json:"min_link_wei"`
Lanes []bridgeLaneDefinition `json:"lanes"`
}
func loadBridgeLanesConfig() bridgeLanesConfig {
path := strings.TrimSpace(os.Getenv("MISSION_CONTROL_BRIDGE_LANES_JSON"))
if path != "" {
if b, err := os.ReadFile(path); err == nil && len(b) > 0 {
var cfg bridgeLanesConfig
if json.Unmarshal(b, &cfg) == nil && len(cfg.Lanes) > 0 {
return cfg
}
}
}
var cfg bridgeLanesConfig
_ = json.Unmarshal(defaultBridgeLanesJSON, &cfg)
return cfg
}
func resolveLaneRPC(def bridgeLaneDefinition) string {
for _, key := range def.RPCEnvs {
if value := strings.TrimSpace(os.Getenv(key)); value != "" {
return value
}
}
for _, row := range ParseExtraRPCProbes() {
chainKey := row[2]
if chainKey == strconv.FormatInt(def.ChainID, 10) {
return row[1]
}
}
return strings.TrimSpace(def.RPCDefault)
}
func erc20BalanceOf(ctx context.Context, rpcURL, tokenAddress, holderAddress string) (string, error) {
tokenAddress = strings.ToLower(strings.TrimSpace(tokenAddress))
holderAddress = strings.ToLower(strings.TrimPrefix(strings.TrimSpace(holderAddress), "0x"))
if len(holderAddress) != 40 {
return "0", nil
}
data := "0x70a08231" + strings.Repeat("0", 24) + holderAddress
raw, _, err := postJSONRPC(ctx, bridgeLaneHTTPClient(), rpcURL, "eth_call", []interface{}{
map[string]interface{}{
"to": tokenAddress,
"data": data,
},
"latest",
})
if err != nil {
return "", err
}
var hex string
if err := json.Unmarshal(raw, &hex); err != nil {
return "", err
}
hex = strings.TrimSpace(hex)
if hex == "" || hex == "0x" {
return "0", nil
}
value := new(big.Int)
if _, ok := value.SetString(strings.TrimPrefix(hex, "0x"), 16); !ok {
return "0", nil
}
return value.String(), nil
}
func bridgeLaneHTTPClient() *http.Client {
return &http.Client{Timeout: 6 * time.Second}
}
func bridgeFundingStatus(linkBalanceWei, minLinkWei string) string {
balance, okBalance := new(big.Int).SetString(strings.TrimSpace(linkBalanceWei), 10)
minimum, okMin := new(big.Int).SetString(strings.TrimSpace(minLinkWei), 10)
if !okBalance || !okMin {
return "unknown"
}
if balance.Cmp(minimum) >= 0 {
return "funded"
}
if balance.Sign() > 0 {
return "degraded"
}
return "unfunded"
}
func proofStatusForLane(key string, proofs map[string]interface{}) string {
if proofs == nil {
return "proof-pending"
}
laneProofs, ok := proofs[key].([]interface{})
if !ok || len(laneProofs) == 0 {
return "proof-pending"
}
for _, item := range laneProofs {
row, ok := item.(map[string]interface{})
if !ok {
continue
}
if tx, ok := row["tx_hash"].(string); ok && strings.TrimSpace(tx) != "" {
return "proof-recorded"
}
}
return "proof-pending"
}
func readProofTransfersJSON() map[string]interface{} {
path := strings.TrimSpace(os.Getenv("MISSION_CONTROL_PROOF_TRANSFERS_JSON"))
if path == "" {
return nil
}
b, err := os.ReadFile(path)
if err != nil || len(b) == 0 {
return map[string]interface{}{"error": "unreadable or empty", "path": path}
}
if len(b) > 512*1024 {
return map[string]interface{}{"error": "file too large", "path": path}
}
var payload map[string]interface{}
if err := json.Unmarshal(b, &payload); err != nil {
return map[string]interface{}{"error": err.Error(), "path": path}
}
return payload
}
func probeBridgeContract(ctx context.Context, rpcURL, linkToken, bridgeAddress, minLinkWei string) map[string]interface{} {
result := map[string]interface{}{
"bridge": strings.TrimSpace(bridgeAddress),
}
if rpcURL == "" {
result["status"] = "unknown"
result["error"] = "rpc unavailable"
return result
}
if linkToken == "" || bridgeAddress == "" {
result["status"] = "unknown"
result["error"] = "missing link token or bridge address"
return result
}
balance, err := erc20BalanceOf(ctx, rpcURL, linkToken, bridgeAddress)
if err != nil {
result["status"] = "unknown"
result["error"] = err.Error()
return result
}
result["link_balance_wei"] = balance
result["status"] = bridgeFundingStatus(balance, minLinkWei)
return result
}
func aggregateLaneStatus(weth9Status, weth10Status, proofStatus string) string {
statuses := []string{weth9Status, weth10Status}
hasUnfunded := false
hasDegraded := false
hasUnknown := false
for _, status := range statuses {
switch status {
case "unfunded":
hasUnfunded = true
case "degraded":
hasDegraded = true
case "unknown":
hasUnknown = true
}
}
if hasUnfunded {
return "unfunded"
}
if hasDegraded {
return "degraded"
}
if hasUnknown {
return "unknown"
}
if proofStatus == "proof-pending" {
return "proof-pending"
}
return "funded"
}
func BuildBridgeLaneHealth(ctx context.Context) (map[string]interface{}, map[string]interface{}) {
cfg := loadBridgeLanesConfig()
minLinkWei := strings.TrimSpace(cfg.MinLinkWei)
if minLinkWei == "" {
minLinkWei = "1000000000000000000"
}
proofPayload := readProofTransfersJSON()
proofByLane := map[string]interface{}{}
if proofPayload != nil {
if lanes, ok := proofPayload["lanes"].(map[string]interface{}); ok {
proofByLane = lanes
}
}
lanes := make([]map[string]interface{}, 0, len(cfg.Lanes))
for _, def := range cfg.Lanes {
rpcURL := resolveLaneRPC(def)
weth9 := probeBridgeContract(ctx, rpcURL, def.LinkToken, def.WETH9Bridge, minLinkWei)
weth10 := probeBridgeContract(ctx, rpcURL, def.LinkToken, def.WETH10Bridge, minLinkWei)
weth9Status, _ := weth9["status"].(string)
weth10Status, _ := weth10["status"].(string)
proofStatus := proofStatusForLane(def.Key, proofByLane)
lanes = append(lanes, map[string]interface{}{
"key": def.Key,
"chain_name": def.ChainName,
"chain_id": def.ChainID,
"config_ready": def.ConfigReady,
"link_token": def.LinkToken,
"status": aggregateLaneStatus(weth9Status, weth10Status, proofStatus),
"proof_status": proofStatus,
"weth9": weth9,
"weth10": weth10,
"rpc_endpoint": redactRPCOrigin(rpcURL),
})
}
laneHealth := map[string]interface{}{
"updated_at": time.Now().UTC().Format(time.RFC3339),
"min_link_wei": minLinkWei,
"lanes": lanes,
}
return laneHealth, proofPayload
}

View File

@@ -0,0 +1,61 @@
{
"updated": "2026-05-23",
"min_link_wei": "1000000000000000000",
"lanes": [
{
"key": "chain138",
"chain_name": "Defi Oracle Meta Mainnet (138)",
"chain_id": 138,
"config_ready": true,
"rpc_envs": ["RPC_URL", "RPC_URL_138"],
"rpc_default": "http://192.168.11.211:8545",
"link_token": "0xb7721dd53a8c629d9f1ba31a5819afe250002b03",
"weth9_bridge": "0xcacfd227A040002e49e2e01626363071324f820a",
"weth10_bridge": "0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
},
{
"key": "gnosis",
"chain_name": "Gnosis (100)",
"chain_id": 100,
"config_ready": true,
"rpc_envs": ["GNOSIS_RPC", "GNOSIS_MAINNET_RPC", "GNOSIS_RPC_URL"],
"rpc_default": "https://rpc.gnosischain.com",
"link_token": "0xE2e73A1c69ecF83F464EFCE6A5be353a37cA09b2",
"weth9_bridge": "0xc8656F24488cb90c452058da92d1a25BA464eaAE",
"weth10_bridge": "0xa846aeAD3071df1b6439d5D813156aCE7C2c1DA1"
},
{
"key": "cronos",
"chain_name": "Cronos (25)",
"chain_id": 25,
"config_ready": true,
"rpc_envs": ["CRONOS_RPC", "CRONOS_RPC_URL", "CRONOS_MAINNET_RPC"],
"rpc_default": "https://evm.cronos.org",
"link_token": "0x8c80A01F461f297Df7F9DA3A4f740D7297C8Ac85",
"weth9_bridge": "0x3Cc23d086fCcbAe1e5f3FE2bA4A263E1D27d8Cab",
"weth10_bridge": "0x105F8A15b819948a89153505762444Ee9f324684"
},
{
"key": "celo",
"chain_name": "Celo (42220)",
"chain_id": 42220,
"config_ready": true,
"rpc_envs": ["CELO_RPC", "CELO_MAINNET_RPC"],
"rpc_default": "https://forno.celo.org",
"link_token": "0xd07294e6E917e07dfDcee882dd1e2565085C2ae0",
"weth9_bridge": "0xAb57BF30F1354CA0590af22D8974c7f24DB2DbD7",
"weth10_bridge": "0xa780ef19A041745d353c9432f2a7f5A241335ffE"
},
{
"key": "wemix",
"chain_name": "Wemix (1111)",
"chain_id": 1111,
"config_ready": true,
"rpc_envs": ["WEMIX_RPC", "WEMIX_MAINNET_RPC"],
"rpc_default": "https://api.wemix.com",
"link_token": "0x80f1FcdC96B55e459BF52b998aBBE2c364935d69",
"weth9_bridge": "0xD3AD6831aacB5386B8A25BB8D8176a6C8a026f04",
"weth10_bridge": "0xa4B9DD039565AeD9641D45b57061f99d9cA6Df08"
}
]
}

View File

@@ -0,0 +1,37 @@
package track1
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestBridgeFundingStatus(t *testing.T) {
require.Equal(t, "funded", bridgeFundingStatus("2000000000000000000", "1000000000000000000"))
require.Equal(t, "degraded", bridgeFundingStatus("500000000000000000", "1000000000000000000"))
require.Equal(t, "unfunded", bridgeFundingStatus("0", "1000000000000000000"))
}
func TestAggregateLaneStatus(t *testing.T) {
require.Equal(t, "unfunded", aggregateLaneStatus("unfunded", "funded", "proof-recorded"))
require.Equal(t, "degraded", aggregateLaneStatus("degraded", "funded", "proof-recorded"))
require.Equal(t, "proof-pending", aggregateLaneStatus("funded", "funded", "proof-pending"))
require.Equal(t, "funded", aggregateLaneStatus("funded", "funded", "proof-recorded"))
}
func TestProofStatusForLane(t *testing.T) {
proofs := map[string]interface{}{
"gnosis": []interface{}{
map[string]interface{}{"tx_hash": "0xabc"},
},
}
require.Equal(t, "proof-recorded", proofStatusForLane("gnosis", proofs))
require.Equal(t, "proof-pending", proofStatusForLane("cronos", proofs))
}
func TestLoadBridgeLanesConfigDefault(t *testing.T) {
t.Setenv("MISSION_CONTROL_BRIDGE_LANES_JSON", "")
cfg := loadBridgeLanesConfig()
require.NotEmpty(t, cfg.Lanes)
require.NotEmpty(t, cfg.MinLinkWei)
}

View File

@@ -30,6 +30,14 @@ func TestResolveBridgeDeliveryModeMixedWhenTransactionVisibilityStale(t *testing
require.Equal(t, "bridge_monitoring_and_homepage", got.Scope)
}
func TestResolveBridgeDeliveryModeMixedWhenQuietChain(t *testing.T) {
diagnostics := &freshness.Diagnostics{
ActivityState: "quiet_chain",
}
got := resolveBridgeDeliveryMode(false, diagnostics, freshness.CompletenessComplete)
require.Equal(t, "live", got.Kind)
}
func TestIsStaleTransactionVisibility(t *testing.T) {
require.True(t, isStaleTransactionVisibility(&freshness.Diagnostics{ActivityState: "fresh_head_stale_transaction_visibility"}))
require.False(t, isStaleTransactionVisibility(&freshness.Diagnostics{ActivityState: "healthy"}))

View File

@@ -198,6 +198,12 @@ func (s *Server) BuildBridgeStatusData(ctx context.Context) map[string]interface
}
}
}
if laneHealth, proofTransfers := BuildBridgeLaneHealth(ctx); laneHealth != nil {
data["bridge_lanes"] = laneHealth
if proofTransfers != nil {
data["proof_transfers"] = proofTransfers
}
}
if mode, ok := data["mode"].(map[string]interface{}); ok {
if relays, ok := data["ccip_relays"].(map[string]interface{}); ok && len(relays) > 0 {
var diagnostics *freshness.Diagnostics

View File

@@ -0,0 +1,61 @@
{
"updated": "2026-05-23",
"min_link_wei": "1000000000000000000",
"lanes": [
{
"key": "chain138",
"chain_name": "Defi Oracle Meta Mainnet (138)",
"chain_id": 138,
"config_ready": true,
"rpc_envs": ["RPC_URL", "RPC_URL_138"],
"rpc_default": "http://192.168.11.211:8545",
"link_token": "0xb7721dd53a8c629d9f1ba31a5819afe250002b03",
"weth9_bridge": "0xcacfd227A040002e49e2e01626363071324f820a",
"weth10_bridge": "0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
},
{
"key": "gnosis",
"chain_name": "Gnosis (100)",
"chain_id": 100,
"config_ready": true,
"rpc_envs": ["GNOSIS_RPC", "GNOSIS_MAINNET_RPC", "GNOSIS_RPC_URL"],
"rpc_default": "https://rpc.gnosischain.com",
"link_token": "0xE2e73A1c69ecF83F464EFCE6A5be353a37cA09b2",
"weth9_bridge": "0xc8656F24488cb90c452058da92d1a25BA464eaAE",
"weth10_bridge": "0xa846aeAD3071df1b6439d5D813156aCE7C2c1DA1"
},
{
"key": "cronos",
"chain_name": "Cronos (25)",
"chain_id": 25,
"config_ready": true,
"rpc_envs": ["CRONOS_RPC", "CRONOS_RPC_URL", "CRONOS_MAINNET_RPC"],
"rpc_default": "https://evm.cronos.org",
"link_token": "0x8c80A01F461f297Df7F9DA3A4f740D7297C8Ac85",
"weth9_bridge": "0x3Cc23d086fCcbAe1e5f3FE2bA4A263E1D27d8Cab",
"weth10_bridge": "0x105F8A15b819948a89153505762444Ee9f324684"
},
{
"key": "celo",
"chain_name": "Celo (42220)",
"chain_id": 42220,
"config_ready": true,
"rpc_envs": ["CELO_RPC", "CELO_MAINNET_RPC"],
"rpc_default": "https://forno.celo.org",
"link_token": "0xd07294e6E917e07dfDcee882dd1e2565085C2ae0",
"weth9_bridge": "0xAb57BF30F1354CA0590af22D8974c7f24DB2DbD7",
"weth10_bridge": "0xa780ef19A041745d353c9432f2a7f5A241335ffE"
},
{
"key": "wemix",
"chain_name": "Wemix (1111)",
"chain_id": 1111,
"config_ready": true,
"rpc_envs": ["WEMIX_RPC", "WEMIX_MAINNET_RPC"],
"rpc_default": "https://api.wemix.com",
"link_token": "0x80f1FcdC96B55e459BF52b998aBBE2c364935d69",
"weth9_bridge": "0xD3AD6831aacB5386B8A25BB8D8176a6C8a026f04",
"weth10_bridge": "0xa4B9DD039565AeD9641D45b57061f99d9cA6Df08"
}
]
}

View 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>
)
}

View File

@@ -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">

View File

@@ -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',

View File

@@ -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">

View File

@@ -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}

View File

@@ -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>
}
}

View File

@@ -59,6 +59,10 @@ echo "✅ Docs bundle prepared"
echo "=== Step 3: Upload artifacts ==="
scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$TMP_DIR/explorer-config-api" root@"$PROXMOX_HOST":/tmp/explorer-config-api
scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$TMP_DIR/explorer-ai-docs.tar.gz" root@"$PROXMOX_HOST":/tmp/explorer-ai-docs.tar.gz
PROOF_JSON="$REPO_ROOT/reports/status/bridge-lane-proof-transfers-latest.json"
if [ -f "$PROOF_JSON" ]; then
scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$PROOF_JSON" root@"$PROXMOX_HOST":/tmp/bridge-lane-proof-transfers.json
fi
echo "✅ Artifacts uploaded"
echo "=== Step 4: Install backend, refresh docs, and ensure env ==="
@@ -91,8 +95,11 @@ if [ -z "$DB_URL" ]; then
fi
fi
pct exec "$VMID" -- bash -lc 'mkdir -p /opt/explorer-ai-docs /etc/systemd/system/explorer-config-api.service.d'
pct exec "$VMID" -- bash -lc 'mkdir -p /opt/explorer-ai-docs /etc/systemd/system/explorer-config-api.service.d /opt/explorer-bridge-status'
pct push "$VMID" /tmp/explorer-ai-docs.tar.gz /tmp/explorer-ai-docs.tar.gz --perms 0644
if [ -f /tmp/bridge-lane-proof-transfers.json ]; then
pct push "$VMID" /tmp/bridge-lane-proof-transfers.json /opt/explorer-bridge-status/proof-transfers.json --perms 0644
fi
pct push "$VMID" /tmp/explorer-config-api /usr/local/bin/explorer-config-api.new --perms 0755
pct exec "$VMID" -- env \
@@ -181,6 +188,16 @@ else
rm -f /etc/systemd/system/explorer-config-api.service.d/walletconnect.conf
fi
cat > /etc/systemd/system/explorer-config-api.service.d/bridge-lanes.conf <<'BRIDGEEOF'
[Service]
Environment=RPC_URL=http://192.168.11.211:8545
Environment="MISSION_CONTROL_EXTRA_RPCS=gnosis|https://rpc.gnosischain.com|100
cronos|https://evm.cronos.org|25
celo|https://forno.celo.org|42220
wemix|https://api.wemix.com|1111"
Environment=MISSION_CONTROL_PROOF_TRANSFERS_JSON=/opt/explorer-bridge-status/proof-transfers.json
BRIDGEEOF
systemctl daemon-reload
systemctl restart explorer-config-api
sleep 2

View File

@@ -45,12 +45,22 @@ test.describe('Explorer sprint smoke', () => {
await expect(page.getByText(/CCIP route catalog/i).first()).toBeVisible({ timeout: 15000 })
await expect(page.getByText(/Wemix/i).first()).toBeVisible({ timeout: 15000 })
await expect(page.getByText(/Bridge Freshness Context/i).first()).toBeVisible({ timeout: 10000 })
await expect(page.getByText(/Config-ready lane health/i).first()).toBeVisible({ timeout: 10000 })
await expect(page.getByText(/unfunded|funded/i).first()).toBeVisible({ timeout: 10000 })
})
test('posture glossary doc page loads', async ({ page }) => {
await page.goto(`${EXPLORER_URL}/docs/posture-glossary`, { waitUntil: 'domcontentloaded', timeout: 30000 })
await expect(page.getByRole('heading', { name: /Posture glossary/i })).toBeVisible({ timeout: 15000 })
await expect(page.getByText(/x402 readiness/i).first()).toBeVisible({ timeout: 10000 })
await expect(page.getByText(/transportActive/i).first()).toBeVisible({ timeout: 10000 })
})
test('posture glossary drawer opens from docs badges', async ({ page }) => {
await page.goto(`${EXPLORER_URL}/docs/posture-glossary`, { waitUntil: 'domcontentloaded', timeout: 30000 })
await page.getByRole('button', { name: /^GRU$/i }).click()
await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 })
await expect(page.getByText(/Methodology/i).first()).toBeVisible({ timeout: 10000 })
})
test('public API access doc page loads', async ({ page }) => {