diff --git a/frontend/src/components/common/HoBackingSummaryCard.tsx b/frontend/src/components/common/HoBackingSummaryCard.tsx
index c7c25f1..375956d 100644
--- a/frontend/src/components/common/HoBackingSummaryCard.tsx
+++ b/frontend/src/components/common/HoBackingSummaryCard.tsx
@@ -50,7 +50,7 @@ export default function HoBackingSummaryCard({ market }: HoBackingSummaryCardPro
) : null}
- Per-token supply vs 1.2× external nostro cash. Large c* supply can break while reserve economics remain sufficient.
+ Per-token supply vs 1.2× external nostro cash. Large c* supply can break while reserve economics remain sufficient. GL 2100 M1 book money is never cash-in-bank.
diff --git a/frontend/src/components/explorer/HoLiquidityPage.tsx b/frontend/src/components/explorer/HoLiquidityPage.tsx
new file mode 100644
index 0000000..174fbd5
--- /dev/null
+++ b/frontend/src/components/explorer/HoLiquidityPage.tsx
@@ -0,0 +1,174 @@
+import Link from 'next/link'
+import EntityBadge from '@/components/common/EntityBadge'
+import { explorerFeaturePages } from '@/data/explorerOperations'
+import { formatUsdValue } from '@/utils/displayCurrency'
+import {
+ formatHoReserveCoverageLabel,
+ formatSupplyNostroMatchLabel,
+} from '@/utils/tokenMarket'
+import OperationsPageShell from './OperationsPageShell'
+
+export type HoLiquiditySnapshot = {
+ generatedAt: string
+ fineractConfigured: boolean
+ cashInBank?: {
+ cashInBankUsd?: number
+ externalNostroGlCode?: string
+ audit?: { status?: string }
+ supplyNostroMatch?: {
+ status?: string
+ multiplier?: number
+ totalSupplyUsd?: number
+ externalNostroCashUsd?: number
+ expectedSupplyUsd?: number
+ }
+ }
+ reserveCoverage?: {
+ status?: string
+ coverageRatio?: number
+ reserveAssetsUsd?: number
+ requiredReserveUsd?: number
+ issuedCStarUsd?: number
+ reserveAssetGlCodes?: string[]
+ multiplier?: number
+ }
+ aggregateSupplyNostroMatch?: {
+ status?: string
+ multiplier?: number
+ }
+ alerts?: Array<{ code: string; severity: string; message: string }>
+ tripleState?: { aligned?: boolean; breaksCount?: number; generatedAt?: string }
+ perTokenSamples?: {
+ cUSDC?: {
+ totalSupplyUsd?: number
+ supplyNostroMatch?: { status?: string }
+ }
+ }
+}
+
+type HoLiquidityPageProps = {
+ snapshot: HoLiquiditySnapshot | null
+ loadError?: string | null
+}
+
+function formatRatio(value: number | undefined): string {
+ if (value == null || !Number.isFinite(value)) return '—'
+ return `${value.toFixed(2)}×`
+}
+
+export default function HoLiquidityPage({ snapshot, loadError }: HoLiquidityPageProps) {
+ const page = explorerFeaturePages.hoLiquidity
+ const coverage = snapshot?.reserveCoverage
+ const nostro = snapshot?.cashInBank
+ const aggregateMatch = snapshot?.aggregateSupplyNostroMatch ?? nostro?.supplyNostroMatch
+ const cusdcSample = snapshot?.perTokenSamples?.cUSDC
+
+ return (
+
+ {loadError ? (
+
+ {loadError}
+
+ ) : null}
+ {snapshot ? (
+
+
+ Fineract audit {new Date(snapshot.generatedAt).toLocaleString()}
+
+ {(snapshot.alerts ?? []).length === 0 ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ Cash-in-bank
+
+ {formatUsdValue(nostro?.cashInBankUsd ?? 0)}
+
+
+ HO GL {nostro?.externalNostroGlCode ?? '13010'} nostro (Dr) — external correspondent cash, not GL 2100 M1.
+
+ {aggregateMatch ? (
+
+
+
+ ) : null}
+
+
+
+ Reserve coverage
+
+ {formatRatio(coverage?.coverageRatio)}
+
+
+ {coverage ? formatHoReserveCoverageLabel(coverage as never) : 'Unavailable'}
+
+ {coverage ? (
+
+ Reserve assets {formatUsdValue(coverage.reserveAssetsUsd ?? 0)} · required{' '}
+ {formatUsdValue(coverage.requiredReserveUsd ?? 0)} · issued c*{' '}
+ {formatUsdValue(coverage.issuedCStarUsd ?? 0)}
+
+ ) : null}
+
+
+
+ PMM cash liquidity
+
+ On-chain spendable pool depth (~$13M cUSDC-side) is shown on wallet and token pages as Cash liq. — not full DEX TVL.
+
+
+ Open cUSDC token detail
+
+
+
+
+ {cusdcSample ? (
+
+ cUSDC per-token nostro settlement
+
+ Total supply {formatUsdValue(cusdcSample.totalSupplyUsd ?? 0)} · match{' '}
+ {cusdcSample.supplyNostroMatch?.status ?? 'pending'}
+
+
+ Per-token break is expected at current supply until nostro is scaled; aggregate reserve coverage can still be sufficient.
+
+
+ ) : null}
+
+ {snapshot.tripleState ? (
+
+ Triple-state reconcile
+
+ {snapshot.tripleState.aligned ? 'Aligned' : `${snapshot.tripleState.breaksCount ?? 0} break(s)`}
+ {snapshot.tripleState.generatedAt ? ` · ${new Date(snapshot.tripleState.generatedAt).toLocaleString()}` : ''}
+
+
+ ) : null}
+
+ {(snapshot.alerts ?? []).length > 0 ? (
+
+ Alerts
+
+ {snapshot.alerts?.map((alert) => (
+
+ {alert.severity} · {alert.code}: {alert.message}
+
+ ))}
+
+
+ ) : null}
+
+ ) : null}
+
+ )
+}
diff --git a/frontend/src/components/wallet/TokenLiquidityStack.tsx b/frontend/src/components/wallet/TokenLiquidityStack.tsx
index 278a839..a6b92a1 100644
--- a/frontend/src/components/wallet/TokenLiquidityStack.tsx
+++ b/frontend/src/components/wallet/TokenLiquidityStack.tsx
@@ -6,6 +6,7 @@ import {
formatCashLiquidityLabel,
formatHoLiquidityAuditFreshness,
formatHoReserveCoverageLabel,
+ formatSupplyNostroMatchLabel,
resolveDisplayedCashInBankUsd,
resolveDisplayedCashLiquidityUsd,
} from '@/utils/tokenMarket'
@@ -41,6 +42,11 @@ export default function TokenLiquidityStack({
Reserve coverage {formatHoReserveCoverageLabel(coverage)}
) : null}
+ {market.supplyNostroMatch ? (
+
+ Nostro match {formatSupplyNostroMatchLabel(market.supplyNostroMatch)}
+
+ ) : null}
{formatHoLiquidityAuditFreshness(market)}
)
diff --git a/frontend/src/data/explorerOperations.ts b/frontend/src/data/explorerOperations.ts
index e0a0063..4802a3e 100644
--- a/frontend/src/data/explorerOperations.ts
+++ b/frontend/src/data/explorerOperations.ts
@@ -266,6 +266,12 @@ export const explorerFeaturePages = {
href: '/routes',
label: 'Open routes page',
},
+ {
+ title: 'HO nostro & reserve',
+ description: 'Fineract GL 13010 cash-in-bank, reserve coverage, and PMM cash liquidity.',
+ href: '/ho-liquidity',
+ label: 'Open HO liquidity',
+ },
{
title: 'WETH utilities',
description: 'Open the WETH-focused landing page and bridge-adjacent shortcuts.',
@@ -298,6 +304,41 @@ export const explorerFeaturePages = {
},
],
},
+ hoLiquidity: {
+ eyebrow: 'HO liquidity',
+ title: 'HO Nostro & Reserve Coverage',
+ description:
+ 'Public semi-redacted view of Fineract GL 13010 nostro cash, HO GL 1000+1050 reserve coverage vs aggregate issued c*, and PMM cash pool depth — distinct from DEX TVL and GL 2100 M1 book money.',
+ note: 'Refreshed from token-aggregation every 30 minutes. Cash liq. on wallet pages is PMM pool depth only.',
+ actions: [
+ {
+ title: 'Live snapshot JSON',
+ description: 'Open the public ho-liquidity-snapshot API (cached Fineract replay).',
+ href: '/token-aggregation/api/v1/omnl/ho-liquidity-snapshot',
+ label: 'Open API JSON',
+ external: true,
+ },
+ {
+ title: 'cUSDC token detail',
+ description: 'Per-token nostro settlement line and HO backing card.',
+ href: '/tokens/0xf22258f57794CC8E06237084b353Ab30fFfa640b',
+ label: 'Open cUSDC',
+ },
+ {
+ title: 'Reserve capacity',
+ description: 'GRU multi-point proof and reserve capacity dashboard.',
+ href: '/token-aggregation/api/v1/reserve/capacity',
+ label: 'Reserve capacity JSON',
+ external: true,
+ },
+ {
+ title: 'Operations hub',
+ description: 'Return to consolidated operator surfaces.',
+ href: '/operations',
+ label: 'Open operations hub',
+ },
+ ],
+ },
} as const satisfies Record
export interface ExplorerOperationsSurface {
@@ -322,6 +363,11 @@ export const explorerOperationsSurfaces: ExplorerOperationsSurface[] = [
label: 'Routes',
description: 'Live route matrix and execution paths.',
},
+ {
+ href: '/ho-liquidity',
+ label: 'HO nostro & reserve',
+ description: 'Fineract nostro cash, reserve coverage, and PMM cash liquidity.',
+ },
{
href: '/liquidity',
label: 'Liquidity',
diff --git a/frontend/src/pages/ho-liquidity/index.tsx b/frontend/src/pages/ho-liquidity/index.tsx
new file mode 100644
index 0000000..a3311f1
--- /dev/null
+++ b/frontend/src/pages/ho-liquidity/index.tsx
@@ -0,0 +1,36 @@
+import type { GetStaticProps } from 'next'
+import HoLiquidityPage, { type HoLiquiditySnapshot } from '@/components/explorer/HoLiquidityPage'
+
+type PageProps = {
+ snapshot: HoLiquiditySnapshot | null
+ loadError: string | null
+}
+
+export default function HoLiquidityRoute(props: PageProps) {
+ return
+}
+
+export const getStaticProps: GetStaticProps = async () => {
+ const base = process.env.EXPLORER_PUBLIC_BASE_URL || 'https://explorer.d-bis.org'
+ try {
+ const res = await fetch(`${base}/token-aggregation/api/v1/omnl/ho-liquidity-snapshot`, {
+ headers: { Accept: 'application/json' },
+ })
+ if (!res.ok) {
+ return {
+ props: { snapshot: null, loadError: `ho-liquidity-snapshot HTTP ${res.status}` },
+ revalidate: 300,
+ }
+ }
+ const snapshot = (await res.json()) as HoLiquiditySnapshot
+ return { props: { snapshot, loadError: null }, revalidate: 300 }
+ } catch (error) {
+ return {
+ props: {
+ snapshot: null,
+ loadError: error instanceof Error ? error.message : 'ho-liquidity-snapshot unavailable',
+ },
+ revalidate: 300,
+ }
+ }
+}