/** * Bridge API: cross-chain bridge status and metrics. * GET /api/v1/bridge/routes — CCIP WETH9/WETH10 + Trustless (Snap / dApps). * GET /api/v1/bridge/status, /api/v1/bridge/metrics, /api/v1/bridge/preflight — GRU Transport readiness + cross-chain guidance. */ import { Router, Request, Response } from 'express'; import fs from 'fs'; import path from 'path'; import { fetchRemoteJson } from '../utils/fetch-remote-json'; import { buildDefaultBridgeRoutes } from '../utils/default-bridge-routes'; import { getActivePublicPools, getActiveTransportPairs, getGruTransportMetadata } from '../../config/gru-transport'; import { logger } from '../../utils/logger'; const router: Router = Router(); function buildGruTransportStatus() { const metadata = getGruTransportMetadata(); const transportPairs = getActiveTransportPairs(); const publicPools = getActivePublicPools(); if (!metadata) return null; return { system: metadata.system, terminology: metadata.terminology, summary: metadata.counts, gasAssetFamilies: metadata.gasAssetFamilies ?? [], gasRedeemGroups: metadata.gasRedeemGroups ?? [], gasProtocolExposure: metadata.gasProtocolExposure ?? [], pairs: transportPairs.map((pair) => ({ key: pair.key, canonicalChainId: pair.canonicalChainId, destinationChainId: pair.destinationChainId, canonicalSymbol: pair.canonicalSymbol, mirroredSymbol: pair.mirroredSymbol, assetClass: pair.assetClass ?? null, familyKey: pair.familyKey ?? null, laneGroup: pair.laneGroup ?? null, backingMode: pair.backingMode ?? null, redeemPolicy: pair.redeemPolicy ?? null, wrappedNativeQuoteSymbol: pair.wrappedNativeQuoteSymbol ?? null, stableQuoteSymbol: pair.stableQuoteSymbol ?? null, referenceVenue: pair.referenceVenue ?? null, eligible: pair.eligible === true, runtimeReady: pair.runtimeReady === true, runtimeBridgeReady: pair.runtimeBridgeReady === true, runtimeReserveVerifierReady: pair.runtimeReserveVerifierReady === true, runtimeMaxOutstandingReady: pair.runtimeMaxOutstandingReady === true, runtimeSupplyAccountingReady: pair.runtimeSupplyAccountingReady ?? null, supplyInvariantSatisfied: pair.supplyInvariantSatisfied ?? null, runtimeOutstandingValue: pair.runtimeOutstandingValue ?? null, runtimeEscrowedValue: pair.runtimeEscrowedValue ?? null, runtimeTreasuryBackedValue: pair.runtimeTreasuryBackedValue ?? null, runtimeTreasuryCapValue: pair.runtimeTreasuryCapValue ?? null, bridgeAvailable: pair.bridgeAvailable ?? null, protocolExposure: pair.protocolExposure ?? null, eligibilityBlockers: Array.isArray(pair.eligibilityBlockers) ? pair.eligibilityBlockers : [], runtimeMissingRequirements: Array.isArray(pair.runtimeMissingRequirements) ? pair.runtimeMissingRequirements : [], activePublicPoolKeys: Array.isArray(pair.publicPoolKeys) ? pair.publicPoolKeys : [], })), publicPools, }; } function uniquePaths(paths: Array): string[] { const seen = new Set(); const out: string[] = []; for (const candidate of paths) { if (typeof candidate !== 'string') continue; const trimmed = candidate.trim(); if (!trimmed || seen.has(trimmed)) continue; seen.add(trimmed); out.push(trimmed); } return out; } function resolveBridgeRoutesPath(): string | null { const candidates = uniquePaths([ process.env.BRIDGE_LIST_JSON_PATH, process.env.BRIDGE_ROUTES_JSON_PATH, path.resolve(process.cwd(), 'config/bridge-routes-chain138-default.json'), path.resolve(process.cwd(), '../config/bridge-routes-chain138-default.json'), path.resolve(process.cwd(), '../../config/bridge-routes-chain138-default.json'), path.resolve(__dirname, '../../../../../config/bridge-routes-chain138-default.json'), ]); for (const candidate of candidates) { if (fs.existsSync(candidate)) return candidate; } return null; } function loadRuntimeBridgeRoutes(): { payload: Record; lastModified?: string } | null { const filePath = resolveBridgeRoutesPath(); if (!filePath) return null; try { const raw = fs.readFileSync(filePath, 'utf8'); const stat = fs.statSync(filePath); return { payload: JSON.parse(raw) as Record, lastModified: stat.mtime.toISOString(), }; } catch { return null; } } /** * GET /api/v1/bridge/routes * Optional BRIDGE_LIST_JSON_URL — remote JSON replaces entire payload (5m cache). */ router.get('/routes', async (_req: Request, res: Response) => { const gruTransportMetadata = getGruTransportMetadata(); res.set('Cache-Control', 'public, max-age=0, must-revalidate'); const url = process.env.BRIDGE_LIST_JSON_URL?.trim(); if (url) { try { const data = await fetchRemoteJson(url); const basePayload = data && typeof data === 'object' ? data : { data }; res.json({ source: 'remote-url', ...basePayload, gruTransport: gruTransportMetadata ? { system: gruTransportMetadata.system, summary: gruTransportMetadata.counts, activeTransportPairs: getActiveTransportPairs(), activePublicPools: getActivePublicPools(), } : undefined, }); return; } catch (e) { logger.error('BRIDGE_LIST_JSON_URL fetch failed, trying runtime file/built-in routes:', e); } } const runtimePayload = loadRuntimeBridgeRoutes(); if (runtimePayload) { res.json({ source: 'runtime-file', lastModified: runtimePayload.lastModified, ...runtimePayload.payload, gruTransport: gruTransportMetadata ? { system: gruTransportMetadata.system, summary: gruTransportMetadata.counts, activeTransportPairs: getActiveTransportPairs(), activePublicPools: getActivePublicPools(), } : undefined, }); return; } res.json({ source: 'built-in', ...buildDefaultBridgeRoutes(), }); }); router.get('/status', (_req: Request, res: Response) => { const gruTransport = buildGruTransportStatus(); res.json({ ok: true, bridges: [], gruTransport, message: 'Bridge status includes GRU Transport runtime readiness. Use /api/v1/bridge/preflight for missing refs and /api/v1/report/cross-chain for volume/lanes.', }); }); router.get('/metrics', (_req: Request, res: Response) => { const gruTransport = buildGruTransportStatus(); res.json({ ok: true, lanes: [], gruTransport: gruTransport ? { system: gruTransport.system, summary: gruTransport.summary, } : null, message: 'Bridge metrics include GRU Transport summary counts. Use /api/v1/report/cross-chain for aggregated data.', }); }); router.get('/preflight', (_req: Request, res: Response) => { const gruTransport = buildGruTransportStatus(); if (!gruTransport) { return res.status(503).json({ ok: false, error: 'GRU transport config not available', }); } const blockedPairs = gruTransport.pairs.filter( (pair) => pair.eligible !== true || pair.runtimeReady !== true ); const readyPairs = gruTransport.pairs.filter( (pair) => pair.eligible === true && pair.runtimeReady === true ); return res.json({ ok: blockedPairs.length === 0, generatedAt: new Date().toISOString(), gruTransport: { system: gruTransport.system, summary: gruTransport.summary, blockedPairs, readyPairs: readyPairs.map((pair) => ({ key: pair.key, canonicalSymbol: pair.canonicalSymbol, mirroredSymbol: pair.mirroredSymbol, destinationChainId: pair.destinationChainId, assetClass: pair.assetClass ?? null, familyKey: pair.familyKey ?? null, backingMode: pair.backingMode ?? null, redeemPolicy: pair.redeemPolicy ?? null, supplyInvariantSatisfied: pair.supplyInvariantSatisfied ?? null, })), }, }); }); export default router;