diff --git a/frontend/src/components/explorer/BridgeMonitoringPage.tsx b/frontend/src/components/explorer/BridgeMonitoringPage.tsx index d72f1ca..4aff7e1 100644 --- a/frontend/src/components/explorer/BridgeMonitoringPage.tsx +++ b/frontend/src/components/explorer/BridgeMonitoringPage.tsx @@ -25,6 +25,11 @@ interface RelayLaneCard { failed: number lastPolled: string bridgeAddress: string + issueScope: string | null + issueMessage: string | null + inventoryShortfallWei: string | null + inventoryRequiredWei: string | null + inventoryAvailableWei: string | null } const relayOrder = ['mainnet_cw', 'mainnet_weth', 'bsc', 'avax', 'avax_cw', 'avax_to_138'] @@ -53,6 +58,9 @@ function resolveSnapshot(relay?: MissionControlRelayPayload): MissionControlRela function relayPolicyCue(snapshot: MissionControlRelaySnapshot | null): string | null { if (!snapshot) return null + if (snapshot.last_error?.scope === 'bridge_inventory') { + return 'Queued release waiting on bridge inventory' + } if (String(snapshot.status || '').toLowerCase() === 'paused' && snapshot.monitoring?.delivery_enabled === false) { return 'Delivery disabled by policy' } @@ -81,6 +89,18 @@ function statusPillClasses(status: string): string { return 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-100' } +function formatWeiToToken(value?: string | null): string { + if (!value) return 'Unknown' + try { + const raw = BigInt(value) + const whole = raw / 10n ** 18n + const fractional = (raw % 10n ** 18n).toString().padStart(18, '0').slice(0, 6).replace(/0+$/, '') + return fractional ? `${whole.toString()}.${fractional}` : whole.toString() + } catch { + return value + } +} + function ActionLink({ href, label, @@ -171,7 +191,7 @@ export default function BridgeMonitoringPage({ return { key, label: getMissionControlRelayLabel(key), - status, + status: snapshot?.last_error?.scope === 'bridge_inventory' ? 'underfunded' : status, profile: snapshot?.service?.profile || key, sourceChain: snapshot?.source?.chain_name || 'Unknown', destinationChain: snapshot?.destination?.chain_name || 'Unknown', @@ -184,6 +204,11 @@ export default function BridgeMonitoringPage({ snapshot?.destination?.relay_bridge || snapshot?.source?.bridge_filter || '', + issueScope: snapshot?.last_error?.scope || null, + issueMessage: snapshot?.last_error?.message || null, + inventoryShortfallWei: snapshot?.last_error?.shortfall || null, + inventoryRequiredWei: snapshot?.last_error?.required_amount || null, + inventoryAvailableWei: snapshot?.last_error?.available_amount || null, } }) .sort((left, right) => { @@ -313,6 +338,17 @@ export default function BridgeMonitoringPage({ {relayPolicyCue(resolveSnapshot((getMissionControlRelays(bridgeStatus) || {})[lane.key]))} ) : null} + {lane.issueScope === 'bridge_inventory' ? ( +
+
Bridge inventory below required release amount
+
+ Shortfall: {formatWeiToToken(lane.inventoryShortfallWei)} WETH +
+
+ Required: {formatWeiToToken(lane.inventoryRequiredWei)} WETH ยท Available: {formatWeiToToken(lane.inventoryAvailableWei)} WETH +
+
+ ) : null} ))} diff --git a/frontend/src/services/api/missionControl.ts b/frontend/src/services/api/missionControl.ts index 7d0a7e8..74415e6 100644 --- a/frontend/src/services/api/missionControl.ts +++ b/frontend/src/services/api/missionControl.ts @@ -16,6 +16,16 @@ export interface MissionControlRelayItemSummary { 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 @@ -135,6 +145,11 @@ function relativeAge(isoString?: string): string { } 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' }