Add HO nostro cash-in-bank and reserve coverage to token-aggregation.
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m59s
CI/CD Pipeline / Security Scanning (push) Successful in 2m54s
CI/CD Pipeline / Lint and Format (push) Failing after 45s
CI/CD Pipeline / Terraform Validation (push) Failing after 28s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 28s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 31s
Validation / validate-genesis (push) Successful in 34s
Validation / validate-terraform (push) Failing after 26s
Validation / validate-kubernetes (push) Failing after 12s
Validation / validate-smart-contracts (push) Failing after 11s
Validation / validate-security (push) Failing after 1m25s
Validation / validate-documentation (push) Failing after 19s
Verify Deployment / Verify Deployment (push) Failing after 1m30s

Expose Fineract GL 13010 nostro cash, aggregate c* reserve coverage, PMM cash liquidity, and a public ho-liquidity-snapshot API for the explorer three-line stack and 30-minute audit collector.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-06-24 01:24:38 -07:00
parent 6ba33a4d3f
commit 73d785cdfc
22 changed files with 1795 additions and 106 deletions

View File

@@ -0,0 +1,33 @@
import { Router, Request, Response } from 'express';
import { cacheMiddleware } from '../middleware/cache';
import { buildHoLiquiditySnapshot } from '../../services/omnl-ho-liquidity-snapshot';
const router = Router();
/**
* GET /omnl/ho-liquidity-snapshot — public read-only HO nostro + reserve coverage (cached).
* Refreshed on 30-minute operator timer; ?refresh=1 forces Fineract replay when configured.
*/
router.get('/omnl/ho-liquidity-snapshot', cacheMiddleware(30 * 60 * 1000), async (req: Request, res: Response) => {
try {
const forceRefresh = req.query.refresh === '1';
const officeId = req.query.officeId != null ? Number(req.query.officeId) : undefined;
const snapshot = await buildHoLiquiditySnapshot({
officeId: Number.isFinite(officeId) ? officeId : undefined,
forceRefresh,
lineId: String(req.query.lineId || '') || undefined,
});
const hasCritical = snapshot.alerts.some((a) => a.severity === 'critical');
const status = snapshot.fineractConfigured && !hasCritical ? 200 : hasCritical ? 503 : 409;
res.setHeader('Cache-Control', 'public, max-age=300, stale-while-revalidate=900');
res.setHeader('X-OMNL-HO-Nostro-Audit-At', snapshot.generatedAt);
res.status(status).json(snapshot);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
res.status(500).json({ error: msg });
}
});
export default router;

View File

@@ -5,6 +5,8 @@ import { omnlAuditMiddleware } from '../middleware/omnl-audit-middleware';
import { omnlComplianceGateMiddleware } from '../middleware/omnl-compliance-gate';
import { omnlIdempotencyMiddleware } from '../middleware/omnl-idempotency';
import { runTripleStateReconcile } from '../../services/omnl-triple-reconcile';
import { getCachedCashInBankAuditSnapshot } from '../../services/omnl-cash-in-bank-audit';
import { getCachedHoReserveCoverageSnapshot } from '../../services/chain138-ho-reserve-coverage';
import {
buildIfrs7Disclosure,
buildIfrs9Disclosure,
@@ -49,6 +51,45 @@ router.get('/omnl/reconcile/triple-state', omnlSensitiveRouteGuard, async (req:
}
});
router.get('/omnl/cash-in-bank', omnlSensitiveRouteGuard, async (req: Request, res: Response) => {
try {
const officeId = req.query.officeId != null ? Number(req.query.officeId) : undefined;
const totalSupplyUsdRaw = req.query.totalSupplyUsd != null ? Number(req.query.totalSupplyUsd) : undefined;
const forceRefresh = String(req.query.refresh || '') === '1';
const totalSupplyUsd =
totalSupplyUsdRaw != null && Number.isFinite(totalSupplyUsdRaw) && totalSupplyUsdRaw > 0
? totalSupplyUsdRaw
: undefined;
const snapshot = await getCachedCashInBankAuditSnapshot({
officeId: Number.isFinite(officeId) ? officeId : undefined,
totalSupplyUsd,
forceRefresh,
lineId: String(req.query.lineId || '') || undefined,
});
const status = snapshot.audit.status === 'aligned' ? 200 : snapshot.audit.status === 'unavailable' ? 503 : 409;
res.status(status).json(snapshot);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
res.status(500).json({ error: msg });
}
});
router.get('/omnl/ho-reserve-coverage', omnlSensitiveRouteGuard, async (req: Request, res: Response) => {
try {
const officeId = req.query.officeId != null ? Number(req.query.officeId) : undefined;
const forceRefresh = String(req.query.refresh || '') === '1';
const snapshot = await getCachedHoReserveCoverageSnapshot({
officeId: Number.isFinite(officeId) ? officeId : undefined,
forceRefresh,
});
const status = snapshot.status === 'sufficient' ? 200 : snapshot.status === 'unavailable' ? 503 : 409;
res.status(status).json(snapshot);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
res.status(500).json({ error: msg });
}
});
router.get('/omnl/disclosures/ifrs7', omnlSensitiveRouteGuard, async (req: Request, res: Response) => {
try {
res.json(await buildIfrs7Disclosure(String(req.query.lineId || '') || undefined));

View File

@@ -26,6 +26,17 @@ import {
import { fetchBlockscoutTokenMeta } from '../../services/blockscout-token-meta';
import { refreshChain138LiveReferencePrices } from '../../services/chain138-onchain-reference-prices';
import { MAX_SINGLE_POOL_TVL_USD, resolveVisibleTokenLiquidityUsd } from '../../services/token-visible-liquidity';
import { resolveTokenCashLiquidityUsd } from '../../services/token-cash-liquidity';
import { isChain138CashUsdTokenAddress } from '../../config/chain138-cash-tokens';
import { isChain138IssuedCStarTokenAddress } from '../../config/chain138-gru-cstar-tokens';
import {
evaluateSupplyNostroMatch,
getCachedCashInBankAuditSnapshot,
mergeCashInBankAuditWithSupplyMatch,
type CashInBankAuditInfo,
type SupplyNostroMatchInfo,
} from '../../services/omnl-cash-in-bank-audit';
import { getCachedHoReserveCoverageSnapshot, type HoReserveCoverageInfo } from '../../services/chain138-ho-reserve-coverage';
const router: Router = Router();
const tokenRepo = new TokenRepository();
@@ -280,6 +291,12 @@ export interface TokenMarketSnapshot {
decimals?: number;
priceUsd?: number;
liquidityUsd?: number;
cashLiquidityUsd?: number;
cashInBankUsd?: number;
cashInBankAudit?: CashInBankAuditInfo;
totalSupplyUsd?: number;
supplyNostroMatch?: SupplyNostroMatchInfo;
hoReserveCoverage?: HoReserveCoverageInfo;
volume24h?: number;
lastUpdated?: string;
source?: string;
@@ -436,6 +453,64 @@ async function resolveTokenMarketSnapshot(
poolLiquidityUsd,
);
const liquidityUsd = bestLiquidityUsd > 0 ? bestLiquidityUsd : undefined;
const cashLiquidityUsdRaw = chainId === 138
? await resolveTokenCashLiquidityUsd(chainId, normalizedAddress)
: 0;
const cashLiquidityUsd = cashLiquidityUsdRaw > 0 ? cashLiquidityUsdRaw : undefined;
let cashInBankUsd: number | undefined;
let cashInBankAudit: CashInBankAuditInfo | undefined;
let totalSupplyUsd: number | undefined;
let supplyNostroMatch: SupplyNostroMatchInfo | undefined;
let hoReserveCoverage: HoReserveCoverageInfo | undefined;
const supplyUnits = decimalStringToNumber(token.totalSupply, token.decimals);
const pegPriceUsd = priceUsd != null && priceUsd > 0 ? priceUsd : 1;
if (supplyUnits != null && supplyUnits > 0) {
totalSupplyUsd = supplyUnits * pegPriceUsd;
}
if (chainId === 138 && isChain138IssuedCStarTokenAddress(normalizedAddress)) {
try {
const [bankSnapshot, reserveSnapshot] = await Promise.all([
getCachedCashInBankAuditSnapshot({ skipTripleState: true }),
getCachedHoReserveCoverageSnapshot(),
]);
if (bankSnapshot.cashInBankUsd > 0) {
cashInBankUsd = bankSnapshot.cashInBankUsd;
}
supplyNostroMatch = evaluateSupplyNostroMatch({
totalSupplyUsd,
externalNostroCashUsd: bankSnapshot.cashInBankUsd,
headOfficeId: bankSnapshot.officeId,
});
cashInBankAudit = mergeCashInBankAuditWithSupplyMatch(bankSnapshot.audit, supplyNostroMatch);
hoReserveCoverage = reserveSnapshot;
} catch (error) {
logger.warn('HO liquidity snapshot unavailable', {
chainId,
address: normalizedAddress,
error: error instanceof Error ? error.message : String(error),
});
}
} else if (chainId === 138 && isChain138CashUsdTokenAddress(normalizedAddress)) {
try {
const bankSnapshot = await getCachedCashInBankAuditSnapshot({ skipTripleState: true });
if (bankSnapshot.cashInBankUsd > 0) {
cashInBankUsd = bankSnapshot.cashInBankUsd;
}
supplyNostroMatch = evaluateSupplyNostroMatch({
totalSupplyUsd,
externalNostroCashUsd: bankSnapshot.cashInBankUsd,
headOfficeId: bankSnapshot.officeId,
});
cashInBankAudit = mergeCashInBankAuditWithSupplyMatch(bankSnapshot.audit, supplyNostroMatch);
} catch (error) {
logger.warn('Cash-in-bank audit snapshot unavailable', {
chainId,
address: normalizedAddress,
error: error instanceof Error ? error.message : String(error),
});
}
}
return {
address: normalizedAddress,
@@ -444,12 +519,23 @@ async function resolveTokenMarketSnapshot(
decimals: token.decimals,
priceUsd: priceUsd != null && priceUsd > 0 ? priceUsd : undefined,
liquidityUsd,
cashLiquidityUsd,
cashInBankUsd,
cashInBankAudit,
totalSupplyUsd,
supplyNostroMatch,
hoReserveCoverage,
volume24h: market?.volume24h,
lastUpdated: market?.lastUpdated instanceof Date
? market.lastUpdated.toISOString()
: market?.lastUpdated
? new Date(market.lastUpdated).toISOString()
: undefined,
lastUpdated: (() => {
const marketLastUpdated = market?.lastUpdated instanceof Date
? market.lastUpdated.toISOString()
: market?.lastUpdated
? new Date(market.lastUpdated).toISOString()
: undefined;
const auditAt = hoReserveCoverage?.generatedAt;
const candidates = [auditAt, marketLastUpdated].filter(Boolean) as string[];
return candidates.length > 0 ? candidates.sort().reverse()[0] : undefined;
})(),
source: pricing?.sourceLayer || (blockscoutMeta?.exchangeRate != null ? 'blockscout' : undefined),
blockscoutExchangeRate: blockscoutMeta?.exchangeRate,
pricingKind,

View File

@@ -27,6 +27,7 @@ import omnlComplianceRoutes from './routes/omnl-compliance-routes';
import omnlTerminalRoutes from './routes/omnl-terminal-routes';
import checkpointRoutes from './routes/checkpoint';
import reserveCapacityRoutes from './routes/reserve-capacity';
import hoLiquidityRoutes from './routes/ho-liquidity-routes';
import metamaskPriceRoutes from './routes/metamask-prices';
import { MultiChainIndexer } from '../indexer/chain-indexer';
import { OmnlEventPoller } from '../indexer/omnl-event-poller';
@@ -296,6 +297,7 @@ export class ApiServer {
this.app.use('/api/v1', partnerPayloadRoutes);
this.app.use('/api/v1', checkpointRoutes);
this.app.use('/api/v1', reserveCapacityRoutes);
this.app.use('/api/v1', hoLiquidityRoutes);
this.app.use('/api/v1/prices/metamask', metamaskPriceRoutes);
this.app.use('/api/v1', omnlComplianceRoutes);
this.app.use('/api/v1', omnlTerminalRoutes);