Add public /ho-liquidity dashboard and extend wallet liquidity stack.
Some checks failed
Deploy Explorer Live / deploy (push) Failing after 26s
Validate Explorer / frontend (push) Failing after 48s
Validate Explorer / smoke-e2e (push) Has been skipped

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 <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-06-26 01:31:00 -07:00
parent 391e84797f
commit e0194621a1
5 changed files with 263 additions and 1 deletions

View File

@@ -50,7 +50,7 @@ export default function HoBackingSummaryCard({ market }: HoBackingSummaryCardPro
</div>
) : null}
<p className="text-xs text-gray-500 dark:text-gray-400">
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.
</p>
</div>
<div className="space-y-2 text-sm text-gray-700 dark:text-gray-300">

View File

@@ -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 (
<OperationsPageShell page={page}>
{loadError ? (
<div className="mb-4 rounded-xl border border-amber-300 bg-amber-50 p-4 text-sm text-amber-900 dark:border-amber-700 dark:bg-amber-950/40 dark:text-amber-100">
{loadError}
</div>
) : null}
{snapshot ? (
<div className="space-y-6">
<div className="flex flex-wrap items-center gap-3 text-sm text-gray-500 dark:text-gray-400">
<span>Fineract audit {new Date(snapshot.generatedAt).toLocaleString()}</span>
<EntityBadge
label={snapshot.fineractConfigured ? 'Fineract configured' : 'Fineract pending'}
tone={snapshot.fineractConfigured ? 'success' : 'warning'}
/>
{(snapshot.alerts ?? []).length === 0 ? (
<EntityBadge label="No active alerts" tone="success" />
) : (
<EntityBadge label={`${snapshot.alerts?.length ?? 0} alert(s)`} tone="warning" />
)}
</div>
<div className="grid gap-4 lg:grid-cols-3">
<section className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-900/40">
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-500">Cash-in-bank</h2>
<p className="mt-2 text-2xl font-semibold text-gray-900 dark:text-white">
{formatUsdValue(nostro?.cashInBankUsd ?? 0)}
</p>
<p className="mt-2 text-sm text-gray-600 dark:text-gray-300">
HO GL {nostro?.externalNostroGlCode ?? '13010'} nostro (Dr) external correspondent cash, not GL 2100 M1.
</p>
{aggregateMatch ? (
<div className="mt-3">
<EntityBadge
label={`Aggregate nostro match: ${formatSupplyNostroMatchLabel(aggregateMatch as never)}`}
tone={aggregateMatch.status === 'matched' ? 'success' : aggregateMatch.status === 'break' ? 'warning' : 'neutral'}
/>
</div>
) : null}
</section>
<section className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-900/40">
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-500">Reserve coverage</h2>
<p className="mt-2 text-2xl font-semibold text-gray-900 dark:text-white">
{formatRatio(coverage?.coverageRatio)}
</p>
<p className="mt-2 text-sm text-gray-600 dark:text-gray-300">
{coverage ? formatHoReserveCoverageLabel(coverage as never) : 'Unavailable'}
</p>
{coverage ? (
<p className="mt-2 text-xs text-gray-500 dark:text-gray-400">
Reserve assets {formatUsdValue(coverage.reserveAssetsUsd ?? 0)} · required{' '}
{formatUsdValue(coverage.requiredReserveUsd ?? 0)} · issued c*{' '}
{formatUsdValue(coverage.issuedCStarUsd ?? 0)}
</p>
) : null}
</section>
<section className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-900/40">
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-500">PMM cash liquidity</h2>
<p className="mt-2 text-sm text-gray-600 dark:text-gray-300">
On-chain spendable pool depth (~$13M cUSDC-side) is shown on wallet and token pages as <strong>Cash liq.</strong> not full DEX TVL.
</p>
<Link href="/tokens/0xf22258f57794CC8E06237084b353Ab30fFfa640b" className="mt-3 inline-block text-sm text-blue-600 hover:underline dark:text-blue-400">
Open cUSDC token detail
</Link>
</section>
</div>
{cusdcSample ? (
<section className="rounded-2xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/40">
<h3 className="text-sm font-semibold text-gray-900 dark:text-white">cUSDC per-token nostro settlement</h3>
<p className="mt-2 text-sm text-gray-600 dark:text-gray-300">
Total supply {formatUsdValue(cusdcSample.totalSupplyUsd ?? 0)} · match{' '}
{cusdcSample.supplyNostroMatch?.status ?? 'pending'}
</p>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
Per-token break is expected at current supply until nostro is scaled; aggregate reserve coverage can still be sufficient.
</p>
</section>
) : null}
{snapshot.tripleState ? (
<section className="rounded-2xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/40">
<h3 className="text-sm font-semibold text-gray-900 dark:text-white">Triple-state reconcile</h3>
<p className="mt-2 text-sm text-gray-600 dark:text-gray-300">
{snapshot.tripleState.aligned ? 'Aligned' : `${snapshot.tripleState.breaksCount ?? 0} break(s)`}
{snapshot.tripleState.generatedAt ? ` · ${new Date(snapshot.tripleState.generatedAt).toLocaleString()}` : ''}
</p>
</section>
) : null}
{(snapshot.alerts ?? []).length > 0 ? (
<section className="rounded-2xl border border-gray-200 p-4 dark:border-gray-700">
<h3 className="text-sm font-semibold text-gray-900 dark:text-white">Alerts</h3>
<ul className="mt-2 space-y-2 text-sm text-gray-700 dark:text-gray-300">
{snapshot.alerts?.map((alert) => (
<li key={alert.code}>
<span className="font-medium">{alert.severity}</span> · {alert.code}: {alert.message}
</li>
))}
</ul>
</section>
) : null}
</div>
) : null}
</OperationsPageShell>
)
}

View File

@@ -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)}
</div>
) : null}
{market.supplyNostroMatch ? (
<div className="text-[11px] opacity-80" title="Per-token supply vs 1.2× HO nostro cash (GL 13010). May break for large c* while aggregate reserve coverage remains sufficient.">
Nostro match {formatSupplyNostroMatchLabel(market.supplyNostroMatch)}
</div>
) : null}
<div className="text-[11px] opacity-80">{formatHoLiquidityAuditFreshness(market)}</div>
</div>
)

View File

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

View File

@@ -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 <HoLiquidityPage {...props} />
}
export const getStaticProps: GetStaticProps<PageProps> = 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,
}
}
}