From e0194621a18146f010b1f2de8fc9187ba9fd11c9 Mon Sep 17 00:00:00 2001 From: defiQUG Date: Fri, 26 Jun 2026 01:31:00 -0700 Subject: [PATCH] Add public /ho-liquidity dashboard and extend wallet liquidity stack. Surfaces Fineract GL 13010 nostro cash, HO reserve coverage, aggregate nostro match, and PMM cash liquidity context with operations hub navigation. Co-authored-by: Cursor --- .../common/HoBackingSummaryCard.tsx | 2 +- .../components/explorer/HoLiquidityPage.tsx | 174 ++++++++++++++++++ .../components/wallet/TokenLiquidityStack.tsx | 6 + frontend/src/data/explorerOperations.ts | 46 +++++ frontend/src/pages/ho-liquidity/index.tsx | 36 ++++ 5 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/explorer/HoLiquidityPage.tsx create mode 100644 frontend/src/pages/ho-liquidity/index.tsx 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, + } + } +}