Add mainnet checkpoint stack: ISO attestation, participant Etherscan surface, and services.
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m3s
CI/CD Pipeline / Security Scanning (push) Successful in 2m18s
CI/CD Pipeline / Lint and Format (push) Failing after 34s
CI/CD Pipeline / Terraform Validation (push) Failing after 20s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 22s
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 40s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 49s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 21s
Validation / validate-genesis (push) Successful in 25s
Validation / validate-terraform (push) Failing after 21s
Validation / validate-kubernetes (push) Failing after 8s
Validation / validate-smart-contracts (push) Failing after 8s
Validation / validate-security (push) Failing after 1m11s
Validation / validate-documentation (push) Failing after 14s
Verify Deployment / Verify Deployment (push) Failing after 45s

Ship AddressActivityRegistry V1/V2, ISO20022IntakeGateway, Chain138ParticipantSurface,
checkpoint hub contracts, checkpoint-core package, aggregator/indexer/sdk services,
relay profile guards, M00 diamond bridge facet, and OMNL compliance contracts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-05-25 00:30:45 -07:00
parent 9a83aa2034
commit c336809676
326 changed files with 21108 additions and 334 deletions

View File

@@ -338,6 +338,54 @@ export class CoinGeckoAdapter implements ExternalApiAdapter {
}
}
/**
* Spot USD price for canonical reference symbols (ETH, BTC, …) when the chain
* is not on CoinGecko's contract API (e.g. Chain 138 native ETH / WETH proxy).
*/
async getReferenceMarketData(referenceSymbol: string): Promise<MarketData | null> {
const symbol = referenceSymbol.trim().toUpperCase();
const coinId = REFERENCE_SYMBOL_TO_COIN_ID[symbol];
if (!coinId) {
return null;
}
const cacheKey = `reference_market_${coinId}`;
const cached = this.cache.get(cacheKey);
if (cached && cached.expiresAt > new Date()) {
return cached.data as MarketData;
}
try {
const response = await this.api.get<Record<string, { usd?: number }>>('/simple/price', {
params: {
ids: coinId,
vs_currencies: 'usd',
include_last_updated_at: true,
},
});
const usd = response.data?.[coinId]?.usd;
if (usd === undefined || !Number.isFinite(usd) || usd <= 0) {
return null;
}
const marketData: MarketData = {
priceUsd: usd,
lastUpdated: new Date(),
};
this.cache.set(cacheKey, {
data: marketData,
expiresAt: new Date(Date.now() + 5 * 60 * 1000),
});
return marketData;
} catch (error) {
logger.error(`Error fetching CoinGecko reference market data for ${referenceSymbol}:`, error);
return null;
}
}
async getHistoricalReferencePrice(
referenceSymbol: string,
timestamp: Date

View File

@@ -0,0 +1,26 @@
import type { Request, Response, NextFunction } from 'express';
import { randomUUID } from 'crypto';
import { appendOmnlAudit } from '../../services/omnl-audit-log';
/** Attach trace id and log every /omnl request (response status on finish). */
export function omnlAuditMiddleware(req: Request, res: Response, next: NextFunction): void {
const traceId = (req.headers['x-omnl-trace-id'] as string) || randomUUID();
res.setHeader('X-OMNL-Trace-Id', traceId);
const started = Date.now();
res.on('finish', () => {
appendOmnlAudit({
category: 'api',
action: `${req.method} ${req.path}`,
actor: req.headers['x-omnl-actor'] as string | undefined,
sourceSystem: (req.headers['x-omnl-source'] as string) || 'http',
traceId,
status: res.statusCode < 400 ? 'ok' : 'error',
metadata: {
statusCode: res.statusCode,
durationMs: Date.now() - started,
query: req.query,
},
});
});
next();
}

View File

@@ -1,5 +1,12 @@
import type { Request, Response, NextFunction } from 'express';
function extractBearerOrQuery(req: Request, key: string): boolean {
const auth = String(req.headers.authorization || '');
const bearer = auth.startsWith('Bearer ') ? auth.slice(7).trim() : '';
const q = String(req.query.access_token ?? '').trim();
return bearer === key || q === key;
}
/**
* When `OMNL_API_KEY` is set, require `Authorization: Bearer <key>` or `?access_token=<key>`.
* If unset, all requests pass (backwards compatible).
@@ -10,12 +17,47 @@ export function omnlSensitiveRouteGuard(req: Request, res: Response, next: NextF
next();
return;
}
const auth = String(req.headers.authorization || '');
const bearer = auth.startsWith('Bearer ') ? auth.slice(7).trim() : '';
const q = String(req.query.access_token ?? '').trim();
if (bearer === key || q === key) {
if (extractBearerOrQuery(req, key)) {
next();
return;
}
res.status(401).json({ error: 'Unauthorized', hint: 'Set Authorization: Bearer or access_token for OMNL_API_KEY' });
}
/** Public discovery endpoints (always unauthenticated). */
const OMNL_PUBLIC_PATH_SUFFIXES = ['/omnl/openapi.json', '/omnl/catalog', '/omnl/integration-status'];
function isPublicOmnlPath(path: string): boolean {
return OMNL_PUBLIC_PATH_SUFFIXES.some((s) => path.endsWith(s));
}
/**
* When `OMNL_REQUIRE_API_KEY=1` or `NODE_ENV=production`, require OMNL_API_KEY on all /omnl routes
* except openapi.json, catalog, and integration-status.
*/
export function omnlRequireApiKeyInProduction(req: Request, res: Response, next: NextFunction): void {
if (!req.path.startsWith('/omnl')) {
next();
return;
}
const requireKey =
process.env.OMNL_REQUIRE_API_KEY === '1' ||
(process.env.NODE_ENV || '').toLowerCase() === 'production';
if (!requireKey || isPublicOmnlPath(req.path)) {
next();
return;
}
const key = process.env.OMNL_API_KEY?.trim();
if (!key) {
res.status(503).json({
error: 'OMNL_API_KEY required in production',
hint: 'Set OMNL_API_KEY and pass Authorization: Bearer <key>',
});
return;
}
if (extractBearerOrQuery(req, key)) {
next();
return;
}
res.status(401).json({ error: 'Unauthorized' });
}

View File

@@ -1,32 +1,40 @@
import rateLimit from 'express-rate-limit';
import type { RequestHandler } from 'express';
/** express-rate-limit handlers are compatible at runtime; Express 5 typings need a narrow cast. */
function asRateLimitMiddleware(
handler: ReturnType<typeof rateLimit>
): RequestHandler {
return handler as unknown as RequestHandler;
}
const windowMs = parseInt(process.env.RATE_LIMIT_WINDOW_MS || '60000', 10);
const maxRequests = parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || '100', 10);
export const apiRateLimiter = rateLimit({
export const apiRateLimiter = asRateLimitMiddleware(rateLimit({
windowMs,
max: maxRequests,
message: 'Too many requests from this IP, please try again later.',
standardHeaders: true,
legacyHeaders: false,
});
}));
export const strictRateLimiter = rateLimit({
export const strictRateLimiter = asRateLimitMiddleware(rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 10,
message: 'Too many requests, please try again later.',
standardHeaders: true,
legacyHeaders: false,
});
}));
/** Stricter limit for RPC-heavy /api/v1/omnl/* (stacks with apiRateLimiter). */
const omnlWindowMs = parseInt(process.env.OMNL_RATE_LIMIT_WINDOW_MS || '60000', 10);
const omnlMax = parseInt(process.env.OMNL_RATE_LIMIT_MAX || '30', 10);
export const omnlRateLimiter = rateLimit({
export const omnlRateLimiter = asRateLimitMiddleware(rateLimit({
windowMs: omnlWindowMs,
max: omnlMax,
message: 'Too many OMNL API requests from this IP, please try again later.',
standardHeaders: true,
legacyHeaders: false,
});
}));

View File

@@ -0,0 +1,152 @@
import { Router, Request, Response } from 'express';
import { existsSync, readFileSync, readdirSync } from 'fs';
import { join, resolve } from 'path';
import { logger } from '../../utils/logger';
const router: Router = Router();
function indexerBase(): string {
return (process.env.CHECKPOINT_INDEXER_URL || 'http://127.0.0.1:3099').replace(/\/$/, '');
}
function batchDir(): string {
const custom = process.env.CHECKPOINT_BATCH_DIR?.trim();
if (custom) return custom;
const root =
process.env.PROXMOX_ROOT?.trim() ||
process.env.PHOENIX_REPO_ROOT?.trim() ||
resolve(__dirname, '../../../../../..');
return join(root, 'reports/checkpoint-indexer/batches');
}
type CheckpointLeaf = {
txHash?: string;
valueUsd?: string;
nativeValueUsd?: string;
tokenValueUsd?: string;
totalTransfersUsd?: string;
priceSource?: string;
transfers?: Array<{
token?: string;
tokenAddress?: string;
tokenSymbol?: string;
symbol?: string;
amountRaw?: string;
decimals?: number;
valueUsd?: string;
priceSource?: string;
}>;
};
function findLeaf(payload: { leaves?: CheckpointLeaf[] } | null, txHash: string): CheckpointLeaf | null {
if (!payload?.leaves?.length) return null;
const want = txHash.toLowerCase();
return payload.leaves.find((l) => (l.txHash || '').toLowerCase() === want) ?? null;
}
function loadLocalAttestation(txHash: string): {
included: boolean;
batchId: string;
batchTotalUsd?: string;
leaf: CheckpointLeaf | null;
} | null {
const dir = batchDir();
if (!existsSync(dir)) return null;
const want = txHash.toLowerCase();
const files = readdirSync(dir).filter((f) => /^batch-\d+\.json$/i.test(f));
for (const file of files.sort((a, b) => {
const na = parseInt(a.replace(/\D/g, ''), 10);
const nb = parseInt(b.replace(/\D/g, ''), 10);
return nb - na;
})) {
try {
const raw = JSON.parse(readFileSync(join(dir, file), 'utf8')) as {
batchId?: string;
batchTotalUsd?: string;
leaves?: CheckpointLeaf[];
};
const leaf = findLeaf(raw, want);
if (!leaf) continue;
const batchId = String(raw.batchId ?? file.replace(/^batch-|\.json$/gi, ''));
return { included: true, batchId, batchTotalUsd: raw.batchTotalUsd, leaf };
} catch {
continue;
}
}
return null;
}
function leafResponse(leaf: CheckpointLeaf | null) {
if (!leaf) return null;
return {
valueUsd: leaf.valueUsd,
nativeValueUsd: leaf.nativeValueUsd,
tokenValueUsd: leaf.tokenValueUsd,
totalTransfersUsd: leaf.totalTransfersUsd,
priceSource: leaf.priceSource,
transfers: leaf.transfers?.map((t) => ({
...t,
tokenAddress: t.tokenAddress || t.token,
symbol: t.symbol || t.tokenSymbol,
})),
};
}
/**
* GET /checkpoint/tx/:txHash/attestation — USD-enriched attestation (indexer proxy + local batch fallback).
*/
router.get('/checkpoint/tx/:txHash/attestation', async (req: Request, res: Response) => {
const txHash = String(req.params.txHash || '').trim();
if (!/^0x[a-fA-F0-9]{64}$/.test(txHash)) {
return res.status(400).json({ error: 'invalid tx hash' });
}
const url = `${indexerBase()}/v1/tx/${txHash}/attestation`;
try {
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), 12_000);
const upstream = await fetch(url, { signal: ctrl.signal });
clearTimeout(timer);
if (upstream.ok) {
const raw = (await upstream.json()) as {
txHash?: string;
included?: boolean;
batchId?: string;
payload?: { leaves?: CheckpointLeaf[]; batchTotalUsd?: string };
};
const leaf = findLeaf(raw.payload ?? null, txHash);
return res.json({
txHash: raw.txHash ?? txHash,
included: Boolean(raw.included),
batchId: raw.batchId ?? '0',
batchTotalUsd: raw.payload?.batchTotalUsd,
leaf: leafResponse(leaf),
source: 'checkpoint-indexer',
});
}
} catch (e) {
logger.debug('checkpoint indexer proxy miss', { txHash, error: String(e) });
}
const local = loadLocalAttestation(txHash);
if (local) {
return res.json({
txHash,
included: true,
batchId: local.batchId,
batchTotalUsd: local.batchTotalUsd,
leaf: leafResponse(local.leaf),
source: 'checkpoint-batch-dir',
});
}
res.json({
txHash,
included: false,
batchId: '0',
leaf: null,
source: 'none',
});
});
export default router;

View File

@@ -1,4 +1,6 @@
import { Router, Request, Response } from 'express';
import { readFileSync, existsSync } from 'fs';
import path from 'path';
import { PoolRepository } from '../../database/repositories/pool-repo';
import { TokenRepository } from '../../database/repositories/token-repo';
import { resolvePoolTokenDisplays } from '../../services/token-display';
@@ -124,26 +126,86 @@ router.get('/routes/health', cacheMiddleware(60 * 1000), async (_req: Request, r
}
});
/**
* Load bridge lane probe JSON from proxmox reports/status (repo root).
*/
function loadBridgeLaneProbes(): Record<string, unknown> | null {
const candidates = [
process.env.BRIDGE_LANE_PROBES_JSON,
path.resolve(__dirname, '../../../../../../reports/status/bridge-lane-link-probes-latest.json'),
path.resolve(process.cwd(), 'reports/status/bridge-lane-link-probes-latest.json'),
].filter(Boolean) as string[];
for (const p of candidates) {
if (existsSync(p)) {
try {
return JSON.parse(readFileSync(p, 'utf8')) as Record<string, unknown>;
} catch {
// try next
}
}
}
return null;
}
const CHAIN_ID_BY_LANE: Record<string, number> = {
chain138: 138,
gnosis: 100,
cronos: 25,
celo: 42220,
wemix: 1111,
};
/**
* GET /api/v1/bridges/metrics
* Bridge telemetry (stub; fill from relay/CCIP when available).
* Bridge telemetry from reports/status/bridge-lane-link-probes-latest.json when present.
*/
router.get('/bridges/metrics', cacheMiddleware(60 * 1000), async (_req: Request, res: Response) => {
try {
res.json({
bridges: [
{
const probe = loadBridgeLaneProbes();
const minLinkWei = probe?.min_link_wei ? String(probe.min_link_wei) : '1000000000000000000';
const lanes = (probe?.lanes ?? {}) as Record<string, {
chain_name?: string;
weth9?: { bridge?: string; link_balance_wei?: string; status?: string };
weth10?: { bridge?: string; link_balance_wei?: string; status?: string };
}>;
const bridges: Array<Record<string, unknown>> = [];
for (const [laneKey, lane] of Object.entries(lanes)) {
const chainId = CHAIN_ID_BY_LANE[laneKey] ?? 0;
for (const variant of ['weth9', 'weth10'] as const) {
const entry = lane[variant];
if (!entry?.bridge) continue;
const bal = entry.link_balance_wei ?? '0';
const funded = BigInt(bal) >= BigInt(minLinkWei);
bridges.push({
bridge: 'CCIP',
fromChainId: 138,
toChainId: 1,
asset: 'WETH',
p50LatencySeconds: 180,
p95LatencySeconds: 420,
feeUsd: 4.25,
successRate: 0.998,
health: 'ok',
},
],
lane: laneKey,
variant: variant.toUpperCase(),
chainId,
chainName: lane.chain_name ?? laneKey,
bridgeAddress: entry.bridge,
linkBalanceWei: bal,
minLinkWei,
status: entry.status ?? (funded ? 'funded' : 'degraded'),
health: funded ? 'ok' : entry.status === 'unfunded' ? 'critical' : 'degraded',
source: probe ? 'reports/status/bridge-lane-link-probes-latest.json' : 'unknown',
asset: 'LINK',
});
}
}
if (bridges.length === 0) {
return res.json({
bridges: [],
updated: null,
note: 'No probe file; run scripts/verify/probe-bridge-lane-link-balances.sh',
});
}
res.json({
updated: probe?.updated ?? null,
minLinkWei,
bridges,
});
} catch (error) {
// eslint-disable-next-line no-console -- route error logging

View File

@@ -0,0 +1,166 @@
import { Router, Request, Response } from 'express';
import { omnlRateLimiter } from '../middleware/rate-limit';
import { omnlSensitiveRouteGuard, omnlRequireApiKeyInProduction } from '../middleware/omnl-guards';
import { omnlAuditMiddleware } from '../middleware/omnl-audit-middleware';
import { runTripleStateReconcile } from '../../services/omnl-triple-reconcile';
import {
buildIfrs7Disclosure,
buildIfrs9Disclosure,
buildIfrs13Disclosure,
buildIas21Ias37Disclosure,
buildFullDisclosure,
getComplianceSignoffsSummary,
} from '../../services/omnl-ifrs-disclosures';
import {
saveIso20022Message,
listIso20022Messages,
getIso20022Message,
type Iso20022MessageType,
} from '../../services/omnl-iso20022-store';
import { verifyOmnlWebhookSignature } from '../../services/omnl-webhooks';
import { appendOmnlAudit } from '../../services/omnl-audit-log';
import { getWeb3ComplianceSummary, buildNotarizationIntent } from '../../services/omnl-web3-compliance';
import { readFileSync, existsSync } from 'fs';
import { resolve } from 'path';
const router = Router();
router.use(omnlRateLimiter);
router.use(omnlAuditMiddleware);
router.use(omnlRequireApiKeyInProduction);
router.get('/omnl/reconcile/triple-state', omnlSensitiveRouteGuard, async (req: Request, res: Response) => {
try {
const lineId = String(req.query.lineId || '');
const report = await runTripleStateReconcile(lineId || undefined);
res.status(report.aligned ? 200 : 409).json(report);
} 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));
} catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
router.get('/omnl/disclosures/ifrs9', omnlSensitiveRouteGuard, (_req, res) => {
res.json(buildIfrs9Disclosure());
});
router.get('/omnl/disclosures/ifrs13', omnlSensitiveRouteGuard, (req, res) => {
res.json(buildIfrs13Disclosure(String(req.query.lineId || '') || undefined));
});
router.get('/omnl/disclosures/ias21-ias37', omnlSensitiveRouteGuard, (_req, res) => {
res.json(buildIas21Ias37Disclosure());
});
router.get('/omnl/compliance/signoffs', omnlSensitiveRouteGuard, (_req, res) => {
res.json(getComplianceSignoffsSummary());
});
router.get('/omnl/compliance/web3', omnlSensitiveRouteGuard, (_req, res) => {
res.json(getWeb3ComplianceSummary());
});
router.post('/omnl/compliance/notarization-intent', omnlSensitiveRouteGuard, (req, res) => {
const body = req.body as {
jurisdictionId?: string;
matrixControlId?: string;
contentHash?: string;
merkleRoot?: string;
metadataHash?: string;
};
if (!body?.jurisdictionId || !body?.matrixControlId || !body?.contentHash) {
res.status(400).json({ error: 'jurisdictionId, matrixControlId, contentHash required' });
return;
}
res.json(buildNotarizationIntent({
jurisdictionId: body.jurisdictionId,
matrixControlId: body.matrixControlId,
contentHash: body.contentHash,
merkleRoot: body.merkleRoot,
metadataHash: body.metadataHash,
}));
});
router.get('/omnl/disclosures/full', omnlSensitiveRouteGuard, async (req: Request, res: Response) => {
const lineId = String(req.query.lineId || '') || undefined;
res.json(await buildFullDisclosure(lineId));
});
router.post('/omnl/iso20022/messages', omnlSensitiveRouteGuard, (req: Request, res: Response) => {
try {
const body = req.body as {
messageType?: Iso20022MessageType;
payload?: string;
uetr?: string;
instructionId?: string;
settlementOrChainRef?: string;
accountingRef?: string;
};
if (!body?.messageType || !body?.payload) {
res.status(400).json({ error: 'messageType and payload required' });
return;
}
const record = saveIso20022Message({
messageType: body.messageType,
payload: body.payload,
uetr: body.uetr,
instructionId: body.instructionId,
settlementOrChainRef: body.settlementOrChainRef,
accountingRef: body.accountingRef,
});
res.status(201).json(record);
} catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
router.get('/omnl/iso20022/messages', omnlSensitiveRouteGuard, (req, res) => {
const limit = parseInt(String(req.query.limit || '50'), 10);
res.json({ messages: listIso20022Messages(limit) });
});
router.get('/omnl/iso20022/messages/:id', omnlSensitiveRouteGuard, (req, res) => {
const r = getIso20022Message(req.params.id as string);
if (!r) {
res.status(404).json({ error: 'not found' });
return;
}
res.json(r);
});
router.post('/omnl/webhooks/inbound', (req: Request, res: Response) => {
const secret = process.env.OMNL_WEBHOOK_SECRET || '';
const raw = typeof req.body === 'string' ? req.body : JSON.stringify(req.body);
const sig = req.headers['x-omnl-signature'] as string | undefined;
if (secret && !verifyOmnlWebhookSignature(raw, sig, secret)) {
res.status(401).json({ error: 'invalid signature' });
return;
}
appendOmnlAudit({
category: 'webhook_in',
action: 'inbound_webhook',
traceId: (req.body as { deliveryId?: string })?.deliveryId,
metadata: { bodyPreview: raw.slice(0, 500) },
status: 'ok',
});
res.json({ received: true });
});
router.get('/omnl/roles/matrix', omnlSensitiveRouteGuard, (_req, res) => {
const root = process.env.PROXMOX_ROOT || process.env.PHOENIX_REPO_ROOT || process.cwd();
const p = resolve(root, 'config/omnl-fineract-roles.v1.json');
if (!existsSync(p)) {
res.status(404).json({ error: 'config/omnl-fineract-roles.v1.json not found' });
return;
}
res.type('application/json').send(readFileSync(p, 'utf8'));
});
export default router;

View File

@@ -1,6 +1,7 @@
import { Router, Request, Response } from 'express';
import { omnlRateLimiter } from '../middleware/rate-limit';
import { omnlSensitiveRouteGuard } from '../middleware/omnl-guards';
import { omnlSensitiveRouteGuard, omnlRequireApiKeyInProduction } from '../middleware/omnl-guards';
import { omnlAuditMiddleware } from '../middleware/omnl-audit-middleware';
import {
loadIpsasRegistry,
validateJournalPairWithMatrix,
@@ -10,14 +11,17 @@ import {
} from '../../services/omnl-ipsas-gl';
import { loadJournalMatrix } from '../../services/omnl-journal-matrix';
import { fetchOmnlCompliance, fetchOmnlComplianceAggregated } from '../../services/omnl-compliance';
import { omnlComplianceCore138 } from '../../services/omnl-chain138-addresses';
const router = Router();
router.use(omnlRateLimiter);
router.use(omnlAuditMiddleware);
router.use(omnlRequireApiKeyInProduction);
/**
* GET /omnl/ipsas/registry — full IPSAS GL registry (codes, pairs, monetary layer hints).
*/
router.get('/omnl/ipsas/registry', (_req: Request, res: Response) => {
router.get('/omnl/ipsas/registry', omnlSensitiveRouteGuard, (_req: Request, res: Response) => {
try {
res.json(loadIpsasRegistry());
} catch (e) {
@@ -29,7 +33,7 @@ router.get('/omnl/ipsas/registry', (_req: Request, res: Response) => {
/**
* GET /omnl/ipsas/matrix — journal matrix (T-001…T-008) for Fineract posting alignment.
*/
router.get('/omnl/ipsas/matrix', (_req: Request, res: Response) => {
router.get('/omnl/ipsas/matrix', omnlSensitiveRouteGuard, (_req: Request, res: Response) => {
try {
res.json(loadJournalMatrix());
} catch (e) {
@@ -125,7 +129,7 @@ router.get('/omnl/ipsas/validate-pair', (req: Request, res: Response) => {
/**
* GET /omnl/ipsas/fineract-health — probe Fineract `/glaccounts?limit=1` (credentials from OMNL_FINERACT_*).
*/
router.get('/omnl/ipsas/fineract-health', async (_req: Request, res: Response) => {
router.get('/omnl/ipsas/fineract-health', omnlSensitiveRouteGuard, async (_req: Request, res: Response) => {
try {
const r = await checkFineractConnectivity();
const configured = Boolean(
@@ -197,9 +201,9 @@ router.get('/omnl/ipsas/compliance-context/:lineId', omnlSensitiveRouteGuard, as
if (aggregated) {
compliance = await fetchOmnlComplianceAggregated(lineId);
} else {
const addr = process.env.OMNL_COMPLIANCE_CORE_138;
const addr = omnlComplianceCore138();
if (!addr) {
res.status(503).json({ error: 'OMNL_COMPLIANCE_CORE_138 not set' });
res.status(503).json({ error: 'OMNL_COMPLIANCE_CORE_138 / V2 not set' });
return;
}
compliance = await fetchOmnlCompliance(138, lineId, addr);

View File

@@ -1,6 +1,8 @@
import { Router, Request, Response } from 'express';
import { Contract, JsonRpcProvider, type InterfaceAbi } from 'ethers';
import { omnlRateLimiter } from '../middleware/rate-limit';
import { omnlRequireApiKeyInProduction } from '../middleware/omnl-guards';
import { omnlAuditMiddleware } from '../middleware/omnl-audit-middleware';
import {
fetchOmnlCompliance,
fetchOmnlComplianceAggregated,
@@ -9,6 +11,7 @@ import {
loadCrossChainLines,
loadCrossChainConfigPath,
} from '../../services/omnl-compliance';
import { omnlComplianceCore138, omnlReserveStore138 } from '../../services/omnl-chain138-addresses';
import { computeOmnlReconcileAnchor } from '../../services/omnl-reconcile-anchor';
import { getOmnlIntegrationStatus } from '../../services/omnl-integration-status';
import { getOmnlApiCatalog } from '../../services/omnl-api-catalog';
@@ -16,6 +19,8 @@ import omnlOpenApi from '../../resources/omnl-openapi.json';
const router = Router();
router.use(omnlRateLimiter);
router.use(omnlAuditMiddleware);
router.use(omnlRequireApiKeyInProduction);
const REGISTRY_ABI: InterfaceAbi = [
'function allLineIds() view returns (bytes32[])',
@@ -133,7 +138,7 @@ router.get('/omnl/mirror-coordinator', async (req: Request, res: Response) => {
function addrForChain(chainId: number): string | undefined {
if (chainId === 138) {
return process.env.OMNL_COMPLIANCE_CORE_138;
return omnlComplianceCore138();
}
if (chainId === 651940) {
return process.env.OMNL_COMPLIANCE_CORE_651940;
@@ -251,7 +256,7 @@ router.get('/omnl/breaker', async (req: Request, res: Response) => {
router.get('/omnl/mirror-status/:lineId', async (req: Request, res: Response) => {
try {
const lineId = req.params.lineId as string;
const a138 = process.env.OMNL_RESERVE_STORE_138;
const a138 = omnlReserveStore138();
const a651940 = process.env.OMNL_RESERVE_STORE_651940;
const out: Record<string, unknown> = {
lineId,
@@ -293,7 +298,7 @@ router.get('/omnl/health', async (req: Request, res: Response) => {
});
return;
}
const a138 = process.env.OMNL_COMPLIANCE_CORE_138;
const a138 = omnlComplianceCore138();
const a651940 = process.env.OMNL_COMPLIANCE_CORE_651940;
const out: Record<string, unknown> = {
lineId: line,

View File

@@ -3,6 +3,7 @@ import { PoolRepository } from '../../database/repositories/pool-repo';
import { cacheMiddleware } from '../middleware/cache';
import { logger } from '../../utils/logger';
import { filterPoolsForRouting } from '../../config/gru-transport';
import { CHAIN138_CANONICAL_LIVE_POOLS } from '../../config/chain138-live-dodo-pools';
import { resolveCanonicalQuoteAddress } from '../../config/canonical-tokens';
import { getLiveDodoPools } from '../../services/live-dodo-fallback';
import {
@@ -14,6 +15,9 @@ import {
const router: Router = Router();
const poolRepo = new PoolRepository();
/** Live traded cUSDT/cUSDC DVM — see docs/11-references/EXPLORER_TOKEN_LIST_CROSSCHECK.md */
const CANONICAL_CUSDT_CUSDC_POOL = CHAIN138_CANONICAL_LIVE_POOLS[0];
/**
* Uniswap V2-style constant-product quote: amountOut = (reserveOut * amountIn * 997) / (reserveIn * 1000 + amountIn * 997)
*
@@ -84,21 +88,67 @@ router.get(
tokenInResolution.lookupAddress
);
const indexedPools = filterPoolsForRouting(chainId, indexedPoolsRaw ?? []);
const livePools =
indexedPools.length > 0
? []
: filterPoolsForRouting(chainId, (await getLiveDodoPools(chainId)) ?? []).filter(
(pool) =>
pool.token0Address.toLowerCase() === tokenInResolution.lookupAddress ||
pool.token1Address.toLowerCase() === tokenInResolution.lookupAddress
);
const pairPools = [...indexedPools, ...livePools].filter(
const livePoolsForToken = filterPoolsForRouting(
chainId,
(await getLiveDodoPools(chainId)) ?? []
).filter(
(pool) =>
pool.token0Address.toLowerCase() === tokenInResolution.lookupAddress ||
pool.token1Address.toLowerCase() === tokenInResolution.lookupAddress
);
const poolsByAddress = new Map<string, (typeof indexedPools)[number]>();
for (const pool of [...indexedPools, ...livePoolsForToken]) {
poolsByAddress.set(pool.poolAddress.toLowerCase(), pool);
}
const pairPools = [...poolsByAddress.values()].filter(
(p) =>
p.token0Address.toLowerCase() === tokenOutResolution.lookupAddress ||
p.token1Address.toLowerCase() === tokenOutResolution.lookupAddress
);
if (pairPools.length === 0) {
const pmmRpc = resolvePmmQuoteRpcUrl();
const cusdt = '0x93e66202a11b1772e55407b32b44e5cd8eda7f22';
const cusdc = '0xf22258f57794cc8e06237084b353ab30fffa640b';
const isCusdtCusdc =
chainId === 138 &&
pmmRpc &&
((tokenInResolution.lookupAddress === cusdt &&
tokenOutResolution.lookupAddress === cusdc) ||
(tokenInResolution.lookupAddress === cusdc &&
tokenOutResolution.lookupAddress === cusdt));
if (isCusdtCusdc) {
const onChainOut = await pmmQuoteAmountOutFromChain({
rpcUrl: pmmRpc,
poolAddress: CANONICAL_CUSDT_CUSDC_POOL,
tokenInLookup: tokenInResolution.lookupAddress,
amountIn,
traderForView: resolvePmmQuoteTrader(),
});
if (onChainOut !== null && onChainOut > BigInt(0)) {
return res.json({
amountOut: onChainOut.toString(),
poolAddress: CANONICAL_CUSDT_CUSDC_POOL,
dexType: 'dodo',
executorAddress:
process.env.CHAIN_138_DODO_PMM_INTEGRATION ||
process.env.DODO_PMM_INTEGRATION ||
null,
quoteEngine: 'pmm-onchain',
canonicalLiquidity: {
requestedTokenInAddress: tokenInResolution.requestedAddress,
requestedTokenOutAddress: tokenOutResolution.requestedAddress,
requestedTokenInSymbol: tokenInResolution.requestedSymbol,
requestedTokenOutSymbol: tokenOutResolution.requestedSymbol,
lookupTokenInAddress: tokenInResolution.lookupAddress,
lookupTokenOutAddress: tokenOutResolution.lookupAddress,
lookupTokenInSymbol: tokenInResolution.lookupSymbol,
lookupTokenOutSymbol: tokenOutResolution.lookupSymbol,
usedFallback: true,
},
});
}
}
return res.json({
amountOut: null,
error: 'No pool found for this token pair',

View File

@@ -785,6 +785,10 @@ describe('Report API', () => {
status: 'routing_enabled',
statusReason: expect.stringContaining('public routing is enabled'),
});
expect(body.lifecycle).toMatchObject({
routingEnabled: 1,
routeVisible: 1,
});
} finally {
await import('fs/promises').then((fs) => fs.unlink(tempPath).catch(() => undefined));
if (previousPath === undefined) {
@@ -796,6 +800,22 @@ describe('Report API', () => {
});
});
describe('GET /api/v1/report/external-indexer-readiness', () => {
it('returns tracker-facing readiness and source endpoints', async () => {
const res = await fetch(`${baseUrl}/api/v1/report/external-indexer-readiness?chainId=138`);
expect(res.status).toBe(200);
const body = (await res.json()) as Record<string, any>;
expect(body.schema).toBe('dbis-external-indexer-readiness/v1');
expect(body.chainId).toBe(138);
expect(body.summary.tokenCount).toBeGreaterThan(0);
expect(body.endpoints.coingecko).toContain('/api/v1/report/coingecko?chainId=138');
expect(body.trackers.defiLlama.status).toBe('submitted_external_review_pending');
expect(body.trackers.coinGecko).toHaveProperty('readyFromDbisSide');
expect(body.trackers.coinMarketCap).toHaveProperty('readyFromDbisSide');
expect(body.trackers.dexScreener.status).toBe('chain_indexing_external_pending');
});
});
describe('GET /api/v1/report/gas-registry', () => {
it('reads the live gas rollout registry from deployment-status json when available', async () => {
const res = await fetch(`${baseUrl}/api/v1/report/gas-registry?chainId=10`);

View File

@@ -1332,6 +1332,84 @@ router.get(
}
);
/** GET /report/external-indexer-readiness — operator-facing readiness for third-party trackers. */
router.get(
'/external-indexer-readiness',
cacheMiddleware(2 * 60 * 1000),
async (req: Request, res: Response) => {
try {
const chainId = parseInt(req.query.chainId as string, 10) || 138;
const tokens = await buildTokenReport(chainId);
const pools = filterPoolsForExposure(chainId, await poolRepo.getPoolsByChain(chainId));
const tokenPoolRows = tokens.flatMap((token) => token.pools);
const positiveTokenPoolRows = tokenPoolRows.filter((pool) => pool.tvl > 0);
const positiveIndexedPools = pools.filter((pool) => (pool.totalLiquidityUsd ?? 0) > 0);
const totalLiquidityUsd = pools.reduce((sum, pool) => sum + (pool.totalLiquidityUsd || 0), 0);
const totalTokenPoolLiquidityUsd = tokenPoolRows.reduce((sum, pool) => sum + (pool.tvl || 0), 0);
const totalReportableLiquidityUsd = totalLiquidityUsd > 0 ? totalLiquidityUsd : totalTokenPoolLiquidityUsd;
const gruPools = getGruV2DeploymentPoolRows().filter((pool) => pool.chainId === chainId);
const gruRoutingEnabledPools = gruPools.filter((pool) => pool.status === 'routing_enabled' || pool.status === 'live');
const publicBase = resolvePublicBaseUrl(req);
res.set('Cache-Control', 'public, max-age=0, must-revalidate');
res.json({
generatedAt: new Date().toISOString(),
chainId,
schema: 'dbis-external-indexer-readiness/v1',
summary: {
tokenCount: tokens.length,
indexedPoolCount: pools.length,
positiveIndexedPoolCount: positiveIndexedPools.length,
tokenPoolRows: tokenPoolRows.length,
positiveTokenPoolRows: positiveTokenPoolRows.length,
totalLiquidityUsd: totalReportableLiquidityUsd,
gruV2PoolCount: gruPools.length,
gruV2RoutingEnabledPoolCount: gruRoutingEnabledPools.length,
},
endpoints: {
all: `${publicBase}/api/v1/report/all?chainId=${chainId}`,
coingecko: `${publicBase}/api/v1/report/coingecko?chainId=${chainId}`,
cmc: `${publicBase}/api/v1/report/cmc?chainId=${chainId}`,
tokenList: `${publicBase}/api/v1/report/token-list?chainId=${chainId}`,
gruV2PmmPools: `${publicBase}/api/v1/report/gru-v2-pmm-pools?chainId=${chainId}`,
adoptionReadiness: `${publicBase}/api/v1/report/adoption-readiness`,
},
trackers: {
defiLlama: {
status: chainId === 138 ? 'merged_chain_listed_tvl_pending_server_pricing' : 'not_applicable',
readyFromDbisSide: chainId === 138 && totalReportableLiquidityUsd > 0,
blocker:
chainId === 138
? 'Chain listed at defillama.com/chain/defi-oracle-meta; TVL $0 until defillama-server prices cUSDT/cUSDC on dfio_meta_main.'
: undefined,
submission: chainId === 138 ? 'https://github.com/DefiLlama/DefiLlama-Adapters/pull/19198' : undefined,
chainPage: chainId === 138 ? 'https://defillama.com/chain/defi-oracle-meta' : undefined,
mergedAt: chainId === 138 ? '2026-05-21T22:13:42Z' : undefined,
},
coinGecko: {
status: totalReportableLiquidityUsd > 0 && tokens.length > 0 ? 'report_ready_external_listing_pending' : 'needs_internal_data',
readyFromDbisSide: totalReportableLiquidityUsd > 0 && tokens.length > 0,
blocker: 'CoinGecko asset-platform and token listing acceptance is external to this report API.',
},
coinMarketCap: {
status: totalReportableLiquidityUsd > 0 && positiveTokenPoolRows.length > 0 ? 'report_ready_external_listing_pending' : 'needs_internal_data',
readyFromDbisSide: totalReportableLiquidityUsd > 0 && positiveTokenPoolRows.length > 0,
blocker: 'CoinMarketCap DEX/listing acceptance is external to this report API.',
},
dexScreener: {
status: chainId === 138 ? 'chain_indexing_external_pending' : 'report_ready_external_listing_pending',
readyFromDbisSide: chainId === 138 && gruRoutingEnabledPools.length > 0,
blocker: chainId === 138 ? 'Dexscreener must index Chain 138 and its native pairs before native pair pages appear.' : undefined,
},
},
});
} catch (error) {
logger.error('Error building report/external-indexer-readiness:', error);
res.status(500).json({ error: 'Internal server error' });
}
}
);
/** GET /report/token-price/:symbol — compact reviewer-facing price and evidence snapshot. */
router.get(
'/token-price/:symbol',
@@ -1586,6 +1664,13 @@ router.get('/gru-v2-pmm-pools', async (req: Request, res: Response) => {
lastModified: fileBacked?.lastModified,
homeChainId: fileBacked?.data.homeChainId,
count: pools.length,
lifecycle: {
live: pools.filter((pool) => pool.status === 'live').length,
routingEnabled: pools.filter((pool) => pool.status === 'routing_enabled').length,
configured: pools.filter((pool) => pool.status === 'configured').length,
proofRequired: pools.filter((pool) => pool.status === 'proof_required').length,
routeVisible: pools.filter((pool) => pool.status === 'live' || pool.status === 'routing_enabled').length,
},
pools,
});
} catch (error) {

View File

@@ -86,12 +86,41 @@ function isAcceptableHistoricalTimestamp(
return deltaMs >= 0 && deltaMs <= maxAgeMs;
}
type ExternalMarketFeeds = {
coingecko?: Awaited<ReturnType<CoinGeckoAdapter['getMarketData']>>;
cmc?: Awaited<ReturnType<CoinMarketCapAdapter['getMarketData']>>;
dexscreener?: Awaited<ReturnType<DexScreenerAdapter['getMarketData']>>;
};
async function enrichExternalFeedsWithReferencePrice(
chainId: number,
lookupAddress: string,
external: ExternalMarketFeeds | null
): Promise<ExternalMarketFeeds> {
const feeds: ExternalMarketFeeds = { ...(external ?? {}) };
if (feeds.coingecko?.priceUsd != null && feeds.coingecko.priceUsd > 0) {
return feeds;
}
const canonical = resolveCanonicalPriceUsd(chainId, lookupAddress.toLowerCase());
if (!canonical.referenceSymbol) {
return feeds;
}
const referenceMarket = await coingeckoAdapter.getReferenceMarketData(canonical.referenceSymbol);
if (referenceMarket?.priceUsd != null && referenceMarket.priceUsd > 0) {
feeds.coingecko = referenceMarket;
}
return feeds;
}
function buildMarketPricingExplorer(
chainId: number,
displayAddress: string,
lookupAddress: string,
marketData: Awaited<ReturnType<MarketDataRepository['getMarketData']>>,
external: { coingecko?: Awaited<ReturnType<CoinGeckoAdapter['getMarketData']>>; cmc?: Awaited<ReturnType<CoinMarketCapAdapter['getMarketData']>>; dexscreener?: Awaited<ReturnType<DexScreenerAdapter['getMarketData']>> } | null
external: ExternalMarketFeeds | null
) {
const pricing = resolveUsdValuation({
chainId,
@@ -566,12 +595,17 @@ router.get('/tokens/:address', cacheMiddleware(60 * 1000), async (req: Request,
cmcAdapter.getMarketData(chainId, resolution.lookupAddress),
dexscreenerAdapter.getMarketData(chainId, resolution.lookupAddress),
]);
const externalFeeds = await enrichExternalFeedsWithReferencePrice(chainId, resolution.lookupAddress, {
coingecko: coingeckoMarket,
cmc: cmcMarket,
dexscreener: dexscreenerMarket,
});
const { market: marketData, pricing, explorer } = buildMarketPricingExplorer(
chainId,
normalizedAddress,
resolution.lookupAddress,
marketDataRaw,
{ coingecko: coingeckoMarket, cmc: cmcMarket, dexscreener: dexscreenerMarket }
externalFeeds
);
res.json({

View File

@@ -20,6 +20,8 @@ import partnerPayloadRoutes from './routes/partner-payloads';
import plannerV2Routes from './routes/planner-v2';
import omnlRoutes from './routes/omnl';
import omnlIpsasRoutes from './routes/omnl-ipsas';
import omnlComplianceRoutes from './routes/omnl-compliance-routes';
import checkpointRoutes from './routes/checkpoint';
import { MultiChainIndexer } from '../indexer/chain-indexer';
import { OmnlEventPoller } from '../indexer/omnl-event-poller';
import { getDatabasePool } from '../database/client';
@@ -207,8 +209,10 @@ export class ApiServer {
this.app.use('/api/v1', arbitrageRoutes);
this.app.use('/api/v1', aggregatorRouteMatrixRoutes);
this.app.use('/api/v1', partnerPayloadRoutes);
this.app.use('/api/v1', checkpointRoutes);
this.app.use('/api/v1', omnlRoutes);
this.app.use('/api/v1', omnlIpsasRoutes);
this.app.use('/api/v1', omnlComplianceRoutes);
this.app.use('/api/v2', plannerV2Routes);
// Admin routes (stricter rate limit)

View File

@@ -7,6 +7,10 @@
import { isISO4217Supported } from './iso4217-symbol-registry';
import { isMonetaryUnitSupported } from './monetary-unit-symbol-registry';
import { loadTokenMappingLoader } from './repo-config-loader';
import {
getCapitalMarketsClassification,
type CapitalMarketsFields,
} from './capital-markets-taxonomy';
export type TokenType = 'base' | 'w' | 'asset' | 'debt';
export type TokenRegistryFamily = 'iso4217' | 'commodity' | 'monetary_unit' | 'gas_native' | 'unclassified';
@@ -37,6 +41,8 @@ export interface CanonicalTokenSpec {
preferredForX402?: boolean;
/** Symbol whose current liquidity should be used for quote fallback until cutover. */
liquiditySourceSymbol?: string;
/** Institutional capital-markets taxonomy (from rwa-capital-markets-taxonomy.v1.json). */
capitalMarkets?: CapitalMarketsFields;
}
interface GruTransportDeployment {
@@ -778,7 +784,17 @@ export const CANONICAL_TOKENS: CanonicalTokenSpec[] = [
description: 'Planned ALL Mainnet bridge-minted wrapped XAU representation for inbound Chain 138 cXAUT transport.',
addresses: { [CHAIN_651940]: addr('cWAXAUT', CHAIN_651940) || '' },
},
{ symbol: 'LiXAU', name: 'XAU Liquidity-adjusted', type: 'base', decimals: 6, currencyCode: 'XAU', addresses: { [CHAIN_138]: addr('LiXAU', CHAIN_138), [CHAIN_651940]: addr('LiXAU', CHAIN_651940) } },
{
symbol: 'LiXAU',
name: 'XAU Liquidity-adjusted',
type: 'base',
decimals: 6,
currencyCode: 'XAU',
registryFamily: 'commodity',
capitalMarkets: getCapitalMarketsClassification('LiXAU'),
description: 'M00 GRU XAU liquidity-adjusted commodity index — DefiLlama RWA candidate; not M1 cXAUC eMoney.',
addresses: { [CHAIN_138]: addr('LiXAU', CHAIN_138), [CHAIN_651940]: addr('LiXAU', CHAIN_651940) },
},
// --- ISO-4217 W ---
{ symbol: 'USDW', name: 'USD W Token', type: 'w', decimals: 2, currencyCode: 'USD', addresses: { [CHAIN_138]: addr('USDW', CHAIN_138), [CHAIN_25]: addr('USDW', CHAIN_25), [CHAIN_651940]: addr('USDW', CHAIN_651940) } },
{ symbol: 'EURW', name: 'EUR W Token', type: 'w', decimals: 2, currencyCode: 'EUR', addresses: { [CHAIN_138]: addr('EURW', CHAIN_138), [CHAIN_25]: addr('EURW', CHAIN_25), [CHAIN_651940]: addr('EURW', CHAIN_651940) } },
@@ -818,6 +834,13 @@ export const CANONICAL_TOKENS: CanonicalTokenSpec[] = [
{ symbol: 'sdcXAUC', name: 'Debt cXAUC (stable)', type: 'debt', decimals: 6, addresses: { [CHAIN_138]: addr('sdcXAUC', CHAIN_138), [CHAIN_651940]: addr('sdcXAUC', CHAIN_651940) } },
];
for (const spec of CANONICAL_TOKENS) {
if (!spec.capitalMarkets) {
const cm = getCapitalMarketsClassification(spec.symbol);
if (cm) spec.capitalMarkets = cm;
}
}
export function getCanonicalTokensByChain(chainId: number): CanonicalTokenSpec[] {
return CANONICAL_TOKENS.filter(
(t) => t.addresses[chainId] && String(t.addresses[chainId]).trim() !== ''
@@ -844,8 +867,27 @@ export interface CanonicalQuoteAddressResolution {
usedFallback: boolean;
}
function isEvmAddress(value: string): boolean {
return /^0x[a-f0-9]{40}$/.test(value);
}
export function resolveCanonicalQuoteAddress(chainId: number, address: string): CanonicalQuoteAddressResolution {
const requestedAddress = normalizeAddress(address);
const raw = address.trim();
const bySymbol = !isEvmAddress(normalizeAddress(raw))
? getCanonicalTokenBySymbol(chainId, raw)
: undefined;
if (bySymbol) {
const lookupAddress = normalizeAddress(bySymbol.addresses[chainId] || '');
return {
requestedAddress: raw.toLowerCase(),
requestedSymbol: bySymbol.symbol,
lookupAddress: lookupAddress || raw.toLowerCase(),
lookupSymbol: bySymbol.symbol,
usedFallback: false,
};
}
const requestedAddress = normalizeAddress(raw);
const requestedSpec = getCanonicalTokenByAddress(chainId, requestedAddress);
if (!requestedSpec) {
return {

View File

@@ -0,0 +1,51 @@
/**
* Institutional capital-markets classification (exposure vs instrument).
* Source of truth: config/rwa-capital-markets-taxonomy.v1.json
*/
import taxonomy from '../../../../../config/rwa-capital-markets-taxonomy.v1.json';
export interface CapitalMarketsFields {
assetClass: string;
assetGroup: string;
instrumentType: string;
underlyingAsset: string;
tenor: string;
collateralType: string;
gruLayer?: string;
defillamaRwaEligible?: boolean;
}
type TaxonomyInstrument = {
ticker: string;
asset_class: string;
asset_group: string;
instrument_type: string;
underlying_asset: string;
tenor: string;
collateral_type: string;
gru_layer?: string;
defillamaEligible?: boolean;
};
const byTicker = new Map<string, CapitalMarketsFields>(
(taxonomy.instruments as TaxonomyInstrument[]).map((row) => [
row.ticker,
{
assetClass: row.asset_class,
assetGroup: row.asset_group,
instrumentType: row.instrument_type,
underlyingAsset: row.underlying_asset,
tenor: row.tenor,
collateralType: row.collateral_type,
gruLayer: row.gru_layer,
defillamaRwaEligible: row.defillamaEligible,
},
]),
);
export function getCapitalMarketsClassification(symbol: string): CapitalMarketsFields | undefined {
return byTicker.get(symbol);
}
export const RWA_CAPITAL_MARKETS_TAXONOMY_VERSION = taxonomy.version;

View File

@@ -0,0 +1,17 @@
/** Live traded DODO PMM pools on Chain 138 (Stack A). See EXPLORER_TOKEN_LIST_CROSSCHECK.md */
export const CHAIN138_CANONICAL_LIVE_POOLS: readonly string[] = [
'0x9e89bAe009adf128782E19e8341996c596ac40dC',
'0x866Cb44b59303d8dc5f4F9E3E7A8e8b0bf238d66',
'0xc39B7D0F40838cbFb54649d327f49a6DAC964062',
'0x67049e7333481e2cac91af61403ac7bddfab7bcd',
'0x72f1a0794153c3b8a1e8a731f1d8e1a52cb10dc5',
'0xb53a0508940b1ff90f1aad4f6cb50a7012fe5593',
'0xe227f6c0520c0c6e8786fe56fa76c4914f861533',
'0xf3e8a07d419b61f002114e64d79f7cf8f7989433',
] as const;
const LIVE_POOL_SET = new Set(CHAIN138_CANONICAL_LIVE_POOLS.map((p) => p.toLowerCase()));
export function isChain138CanonicalLivePool(poolAddress: string): boolean {
return LIVE_POOL_SET.has(poolAddress.trim().toLowerCase());
}

View File

@@ -112,7 +112,7 @@ export interface RoutingRegistryEntry {
}
const ALLTRA_ADAPTER_138 = envAddr('ALLTRA_ADAPTER_ADDRESS') || envAddr('ALLTRA_CUSTOM_BRIDGE_ADDRESS') || '0x66FEBA2fC9a0B47F26DD4284DAd24F970436B8Dc';
const CCIP_WETH9_138 = envAddr('CCIPWETH9_BRIDGE_CHAIN138') || '0x971cD9D156f193df8051E48043C476e53ECd4693';
const CCIP_WETH9_138 = envAddr('CCIPWETH9_BRIDGE_CHAIN138') || '0xcacfd227A040002e49e2e01626363071324f820a';
const CCIP_STABLE_138 = envAnyAddr('CCIP_STABLE_BRIDGE_CHAIN138', 'CCIP_STABLECOIN_BRIDGE_CHAIN138');
const UNIVERSAL_CCIP_138 = envAnyAddr('UNIVERSAL_CCIP_BRIDGE_ADDRESS', 'UNIVERSAL_CCIP_BRIDGE');

View File

@@ -1,3 +1,4 @@
import { isChain138CanonicalLivePool } from './chain138-live-dodo-pools';
import { loadTokenMappingLoader } from './repo-config-loader';
export interface ConfigRef {
@@ -311,6 +312,7 @@ export function shouldExposePublicPool(
token0Address: string,
token1Address: string
): boolean {
if (chainId === 138 && isChain138CanonicalLivePool(poolAddress)) return true;
const loader = loadGruTransportLoader();
return loader?.shouldExposePublicPool?.(chainId, poolAddress, token0Address, token1Address) ?? true;
}
@@ -321,6 +323,7 @@ export function shouldUsePublicPoolForRouting(
token0Address: string,
token1Address: string
): boolean {
if (chainId === 138 && isChain138CanonicalLivePool(poolAddress)) return true;
const loader = loadGruTransportLoader();
return loader?.shouldUsePublicPoolForRouting?.(chainId, poolAddress, token0Address, token1Address) ?? true;
}

View File

@@ -2,6 +2,7 @@ import { Contract, JsonRpcProvider, type InterfaceAbi } from 'ethers';
import { logger } from '../utils/logger';
import { emitOmnlWebhook } from '../services/omnl-webhooks';
import { loadLastProcessedBlock, saveLastProcessedBlock } from './omnl-poller-state';
import { omnlReserveStore138 } from '../services/omnl-chain138-addresses';
const RESERVE_ABI: InterfaceAbi = [
'event ReserveCommitted(bytes32 indexed lineId,uint256 R,uint256 validUntil,bytes32 evidenceHash,bytes32 merkleRoot,uint256 version,address indexed by)',
@@ -21,7 +22,7 @@ export class OmnlEventPoller {
for (const chainId of chains) {
const addr =
chainId === 138
? process.env.OMNL_RESERVE_STORE_138
? omnlReserveStore138()
: chainId === 651940
? process.env.OMNL_RESERVE_STORE_651940
: process.env[`OMNL_RESERVE_STORE_${chainId}`];

View File

@@ -2,6 +2,7 @@ import { ethers } from 'ethers';
import type { LiquidityPool } from '../database/repositories/pool-repo';
import { getChainConfig } from '../config/chains';
import { getDexFactories } from '../config/dex-factories';
import { CHAIN138_CANONICAL_LIVE_POOLS, isChain138CanonicalLivePool } from '../config/chain138-live-dodo-pools';
import { shouldExposePublicPool } from '../config/gru-transport';
import { logger } from '../utils/logger';
import { estimateChain138DodoLiquidityUsd } from './chain138-dodo-liquidity';
@@ -23,6 +24,12 @@ const DODO_DVM_POOL_ABI = [
const CACHE_TTL_MS = 15_000;
const livePoolCache = new Map<number, { expiresAt: number; pools: LiquidityPool[] }>();
function bypassGruPoolFilter(chainId: number, poolAddress: string): boolean {
if (chainId !== 138) return false;
if (process.env.TOKEN_AGGREGATION_BYPASS_GRU_POOL_FILTER === '1') return true;
return isChain138CanonicalLivePool(poolAddress);
}
interface LivePoolSnapshot {
token0Address: string;
token1Address: string;
@@ -130,7 +137,10 @@ export async function getLiveDodoPools(chainId: number): Promise<LiquidityPool[]
);
const { token0Address, token1Address, reserve0, reserve1, price, createdAt } = snapshot;
if (!shouldExposePublicPool(chainId, poolAddress, token0Address, token1Address)) {
if (
!bypassGruPoolFilter(chainId, poolAddress) &&
!shouldExposePublicPool(chainId, poolAddress, token0Address, token1Address)
) {
continue;
}
@@ -170,6 +180,54 @@ export async function getLiveDodoPools(chainId: number): Promise<LiquidityPool[]
}
}
if (chainId === 138 && integrations.length > 0) {
const integrationAddress = integrations[0];
const integration = new ethers.Contract(
integrationAddress,
DODO_PMM_INTEGRATION_ABI,
provider
);
for (const poolAddressRaw of CHAIN138_CANONICAL_LIVE_POOLS) {
const poolAddress = poolAddressRaw.toLowerCase();
if (poolsByAddress.has(poolAddress)) continue;
try {
const snapshot =
await readPoolViaIntegration(integration, poolAddressRaw).catch(async () =>
readPoolDirectly(provider, poolAddressRaw)
);
const { token0Address, token1Address, reserve0, reserve1, price, createdAt } = snapshot;
const liquidityUsd = estimateChain138DodoLiquidityUsd({
token0Address,
token1Address,
reserve0,
reserve1,
price,
});
poolsByAddress.set(poolAddress, {
chainId,
poolAddress,
token0Address,
token1Address,
dexType: 'dodo',
factoryAddress: integrationAddress,
reserve0: reserve0.toString(),
reserve1: reserve1.toString(),
reserve0Usd: liquidityUsd.reserve0Usd,
reserve1Usd: liquidityUsd.reserve1Usd,
totalLiquidityUsd: liquidityUsd.totalLiquidityUsd,
volume24h: 0,
createdAtBlock: 0,
createdAtTimestamp: createdAt,
lastUpdated: new Date(),
});
} catch (error) {
logger.warn(
`Skipping canonical live DODO pool ${poolAddressRaw} on chain ${chainId}: ${String(error)}`
);
}
}
}
const pools = Array.from(poolsByAddress.values()).sort(
(a, b) => b.totalLiquidityUsd - a.totalLiquidityUsd
);

View File

@@ -15,16 +15,21 @@ export function getOmnlApiCatalog(): {
return {
service: 'HYBX OMNL',
basePath: '/api/v1',
version: '1.2.0',
version: '1.3.0',
endpoints: [
{ method: 'GET', path: '/omnl/openapi.json', description: 'OpenAPI 3.0 JSON (Swagger-compatible)', auth: 'none' },
{ method: 'GET', path: '/omnl/catalog', description: 'This catalog (machine-readable)', auth: 'none' },
{ method: 'GET', path: '/omnl/integration-status', description: 'Which env-backed integrations are configured', auth: 'none' },
{ method: 'GET', path: '/omnl/reconcile-anchor', description: 'SHA-256 of canonical IPSAS registry + journal matrix JSON', auth: 'none' },
{ method: 'GET', path: '/omnl/reconcile-anchor', description: 'SHA-256 of canonical IPSAS registry + journal matrix JSON', auth: 'OMNL_API_KEY when OMNL_REQUIRE_API_KEY=1' },
{ method: 'GET', path: '/omnl/reconcile/triple-state', description: 'Fineract + on-chain + custodian reconcile', query: ['lineId'], auth: 'OMNL_API_KEY' },
{ method: 'GET', path: '/omnl/disclosures/full', description: 'IFRS 7/9/13 + IAS 21/37 extracts', query: ['lineId'], auth: 'OMNL_API_KEY' },
{ method: 'POST', path: '/omnl/iso20022/messages', description: 'Store ISO 20022 message (10y retention)', auth: 'OMNL_API_KEY' },
{ method: 'GET', path: '/omnl/iso20022/messages', description: 'List stored ISO messages', auth: 'OMNL_API_KEY' },
{ method: 'GET', path: '/omnl/roles/matrix', description: 'Fineract SoD roles matrix JSON', auth: 'OMNL_API_KEY' },
{ method: 'GET', path: '/omnl/cross-chain-lines', description: 'hybx-omnl-cross-chain-lines.json lines[]', auth: 'none' },
{ method: 'GET', path: '/omnl/zk-verifier', description: 'ZK verifier address from OMNL_ZK_VERIFIER', auth: 'none' },
{ method: 'GET', path: '/omnl/mirror-coordinator', description: 'Read on-chain mirror destination', query: ['chainId'], auth: 'none' },
{ method: 'GET', path: '/omnl/compliance/:lineId', description: 'ComplianceCore snapshot', query: ['chainId'], auth: 'none' },
{ method: 'GET', path: '/omnl/compliance/:lineId', description: 'ComplianceCore snapshot', query: ['chainId'], auth: 'OMNL_API_KEY when OMNL_REQUIRE_API_KEY=1' },
{ method: 'GET', path: '/omnl/compliance-aggregated/:lineId', description: 'Aggregated supply + policy', auth: 'none' },
{ method: 'GET', path: '/omnl/instruments', description: 'InstrumentRegistry lines', query: ['chainId'], auth: 'none' },
{ method: 'GET', path: '/omnl/attestations/:lineId', description: 'ReserveCommitmentStore commitment', query: ['chainId'], auth: 'none' },

View File

@@ -0,0 +1,37 @@
import { appendFileSync, mkdirSync } from 'fs';
import { dirname, resolve } from 'path';
import { randomUUID } from 'crypto';
export type OmnlAuditEntry = {
id: string;
ts: string;
category: 'api' | 'fineract' | 'webhook_out' | 'webhook_in' | 'reconcile' | 'iso20022';
action: string;
actor?: string;
sourceSystem?: string;
traceId?: string;
referenceNumber?: string;
transactionHash?: string;
journalEntryId?: string;
status?: 'ok' | 'error' | 'skipped';
metadata?: Record<string, unknown>;
};
export function omnlAuditLogPath(): string {
const raw = process.env.OMNL_AUDIT_LOG_PATH?.trim();
if (raw) return resolve(raw);
const root = process.env.PROXMOX_ROOT || process.env.PHOENIX_REPO_ROOT || process.cwd();
return resolve(root, 'reports/audit/omnl-audit.jsonl');
}
/** Append-only JSONL audit trail (immutable by convention — no delete API). */
export function appendOmnlAudit(entry: Omit<OmnlAuditEntry, 'id' | 'ts'> & { id?: string; ts?: string }): void {
const path = omnlAuditLogPath();
mkdirSync(dirname(path), { recursive: true });
const row: OmnlAuditEntry = {
id: entry.id || randomUUID(),
ts: entry.ts || new Date().toISOString(),
...entry,
};
appendFileSync(path, `${JSON.stringify(row)}\n`, 'utf8');
}

View File

@@ -0,0 +1,30 @@
/**
* Chain 138 OMNL address resolution — prefers v2 stack after cutover.
*/
export function omnlComplianceCore138(): string | undefined {
const v2 = (process.env.OMNL_COMPLIANCE_CORE_V2_138 || '').trim();
if (v2) return v2;
return (process.env.OMNL_COMPLIANCE_CORE_138 || '').trim() || undefined;
}
export function omnlReserveStore138(): string | undefined {
const v2 = (process.env.OMNL_RESERVE_STORE_V2_138 || '').trim();
if (v2) return v2;
const legacy = (process.env.OMNL_RESERVE_STORE_138 || '').trim();
if (legacy) return legacy;
return (process.env.OMNL_RESERVE_COMMITMENT_STORE || '').trim() || undefined;
}
export function omnlStackCutover(): {
usingV2: boolean;
complianceCore: string | undefined;
reserveStore: string | undefined;
} {
const complianceCore = omnlComplianceCore138();
const reserveStore = omnlReserveStore138();
const usingV2 = Boolean(
(process.env.OMNL_COMPLIANCE_CORE_V2_138 || '').trim() &&
complianceCore === (process.env.OMNL_COMPLIANCE_CORE_V2_138 || '').trim()
);
return { usingV2, complianceCore, reserveStore };
}

View File

@@ -0,0 +1,150 @@
import { readFileSync, existsSync } from 'fs';
import { resolve } from 'path';
import { createHash } from 'crypto';
function projectRoot(): string {
return (
process.env.PROXMOX_ROOT?.trim() ||
process.env.PHOENIX_REPO_ROOT?.trim() ||
resolve(__dirname, '../../../../../..')
);
}
function readJson<T>(relPath: string): T | null {
const p = resolve(projectRoot(), relPath);
if (!existsSync(p)) return null;
return JSON.parse(readFileSync(p, 'utf8')) as T;
}
export interface SignoffRecord {
schemaVersion: string;
status: string;
policyId?: string;
methodologyId?: string;
policyDocPath?: string;
methodologyDocPath?: string;
signedAt?: string | null;
checklist?: Record<string, boolean>;
classification?: string | Record<string, unknown>;
model?: {
approach?: string;
stagingEnabled?: boolean;
forwardLookingScenarios?: boolean;
};
}
export function loadIas32Signoff(): SignoffRecord | null {
return readJson<SignoffRecord>('config/compliance/ias32-gru-signoff.v1.json');
}
export function loadIfrs9Signoff(): SignoffRecord | null {
return readJson<SignoffRecord>('config/compliance/ifrs9-ecl-signoff.v1.json');
}
export function isIas32Signed(): boolean {
const s = loadIas32Signoff();
return s?.status === 'signed';
}
export function isIfrs9Signed(): boolean {
const s = loadIfrs9Signoff();
return s?.status === 'signed';
}
export interface Ifrs9EclParameters {
schemaVersion: string;
reportingCurrency: string;
segments: Array<{
id: string;
pd: number;
lgd: number;
stage: number;
glCodesExposure?: string[];
officeIds?: number[];
}>;
journalTemplate?: {
debitGlCode: string;
creditGlCode: string;
officeId: number;
narrativePrefix: string;
};
}
export function loadIfrs9EclParameters(): Ifrs9EclParameters | null {
const custom = process.env.OMNL_IFRS9_PARAMETERS_PATH?.trim();
if (custom && existsSync(custom)) {
return JSON.parse(readFileSync(custom, 'utf8')) as Ifrs9EclParameters;
}
const live = readJson<Ifrs9EclParameters>('config/compliance/ifrs9-ecl-parameters.v1.json');
if (live) return live;
return readJson<Ifrs9EclParameters>('config/compliance/ifrs9-ecl-parameters.v1.example.json');
}
export function loadXauReconcileBaseline(): Record<string, unknown> | null {
return readJson('config/compliance/gru-xau-reconcile-baseline.v1.json');
}
export function computeEclAllowance(params: Ifrs9EclParameters, exposures: Array<{ segmentId: string; ead: number }>): {
totalEcl: number;
bySegment: Array<{ segmentId: string; ead: number; pd: number; lgd: number; ecl: number; stage: number }>;
} {
const bySegment: Array<{ segmentId: string; ead: number; pd: number; lgd: number; ecl: number; stage: number }> = [];
let totalEcl = 0;
for (const exp of exposures) {
const seg = params.segments.find((s) => s.id === exp.segmentId);
if (!seg) continue;
const ecl = exp.ead * seg.pd * seg.lgd;
bySegment.push({
segmentId: exp.segmentId,
ead: exp.ead,
pd: seg.pd,
lgd: seg.lgd,
ecl,
stage: seg.stage,
});
totalEcl += ecl;
}
return { totalEcl, bySegment };
}
export function hashPolicyDoc(relPath: string): string | null {
const p = resolve(projectRoot(), relPath);
if (!existsSync(p)) return null;
return createHash('sha256').update(readFileSync(p, 'utf8')).digest('hex');
}
export function loadIfrs13FairValueFeeds(): Record<string, unknown> | null {
return readJson('config/compliance/ifrs13-fair-value-feeds.v1.json');
}
export function loadProvisionsRegister(): Record<string, unknown> | null {
return readJson('config/omnl-provisions-register.json');
}
export function getComplianceSignoffsSummary(): Record<string, unknown> {
const ias32 = loadIas32Signoff();
const ifrs9 = loadIfrs9Signoff();
const eclParams = loadIfrs9EclParameters();
return {
generatedAt: new Date().toISOString(),
ias32: ias32
? {
...ias32,
policySha256Computed: ias32.policyDocPath
? hashPolicyDoc('gru-docs/docs/compliance/accounting/Accounting_Policy_IFRS_IAS32_GRU.md')
: null,
readyForExternalUse: ias32.status === 'signed',
}
: null,
ifrs9: ifrs9
? {
...ifrs9,
readyForExternalUse: ifrs9.status === 'signed' && process.env.OMNL_IFRS9_ECL_ENABLED === '1',
}
: null,
eclParametersLoaded: Boolean(eclParams),
custodianSnapshotConfigured: existsSync(
resolve(projectRoot(), process.env.OMNL_CUSTODIAN_SNAPSHOT_PATH || 'config/omnl-custodian-snapshot.json')
),
};
}

View File

@@ -2,6 +2,7 @@ import { Contract, JsonRpcProvider, type InterfaceAbi } from 'ethers';
import { readFileSync, existsSync } from 'fs';
import { resolve } from 'path';
import { isCompliant, maxM1ForM0, minReservesForM0 } from './omnl-policy-math';
import { omnlComplianceCore138, omnlReserveStore138 } from './omnl-chain138-addresses';
const COMPLIANCE_ABI: InterfaceAbi = [
'function getCompliance(bytes32 lineId) view returns (uint256 s0, uint256 s1, uint256 r, uint256 validUntil, bytes32 evidenceHash, bytes32 merkleRoot, uint256 minR, uint256 maxS1, bool m0Ok, bool m1Ok, bool attestationStale, bool policyOk, bool operational, bool reportingCompliant)',
@@ -137,9 +138,9 @@ async function readTotalSupply(rpcUrl: string, chainId: number, token: string):
* Sum M0/M1 supply across chains for a logical line; use R/TTL from primary (138) reserve or compliance.
*/
export async function fetchOmnlComplianceAggregated(lineIdHex: string): Promise<OmnlAggregatedSnapshot> {
const addr138 = process.env.OMNL_COMPLIANCE_CORE_138;
const addr138 = omnlComplianceCore138();
if (!addr138) {
throw new Error('OMNL_COMPLIANCE_CORE_138 required for aggregated view');
throw new Error('OMNL_COMPLIANCE_CORE_138 or OMNL_COMPLIANCE_CORE_V2_138 required for aggregated view');
}
const lineId = lineIdHex.startsWith('0x') ? lineIdHex : `0x${lineIdHex}`;
@@ -220,7 +221,7 @@ export async function fetchLatestAttestation(
): Promise<{ r: string; validUntil: string; evidenceHash: string; merkleRoot: string; version: string }> {
const addr =
chainId === 138
? process.env.OMNL_RESERVE_STORE_138
? omnlReserveStore138()
: chainId === 651940
? process.env.OMNL_RESERVE_STORE_651940
: process.env[`OMNL_RESERVE_STORE_${chainId}`];

View File

@@ -0,0 +1,64 @@
import axios, { type AxiosRequestConfig } from 'axios';
function fineractAuthHeaders(): { base: string; headers: Record<string, string> } {
const base = (process.env.OMNL_FINERACT_BASE_URL || '').replace(/\/$/, '');
const tenant = process.env.OMNL_FINERACT_TENANT || 'omnl';
const user =
process.env.OMNL_FINERACT_USER?.trim() ||
process.env.OMNL_FINERACT_USERNAME?.trim() ||
'app.omnl';
const pass = process.env.OMNL_FINERACT_PASSWORD || '';
if (!base || !pass) {
throw new Error('OMNL_FINERACT_BASE_URL and OMNL_FINERACT_PASSWORD required');
}
const auth = Buffer.from(`${user}:${pass}`).toString('base64');
return {
base,
headers: {
Authorization: `Basic ${auth}`,
'Fineract-Platform-TenantId': tenant,
'Content-Type': 'application/json',
},
};
}
export async function fineractJournalExistsByReference(referenceNumber: string): Promise<boolean> {
const { base, headers } = fineractAuthHeaders();
const url = `${base}/journalentries`;
const cfg: AxiosRequestConfig = {
headers,
timeout: 60000,
params: { referenceNumber, limit: 1 },
};
try {
const { data } = await axios.get(url, cfg);
if (Array.isArray(data)) return data.length > 0;
const items = (data as { pageItems?: unknown[] })?.pageItems;
return Array.isArray(items) && items.length > 0;
} catch {
return false;
}
}
export async function fineractOfficeHasClosureOnOrBefore(officeId: number, dateIso: string): Promise<boolean> {
const { base, headers } = fineractAuthHeaders();
const url = `${base}/glclosures`;
const { data } = await axios.get(url, { headers, params: { officeId }, timeout: 60000 });
const rows = Array.isArray(data) ? data : [];
const target = new Date(dateIso).getTime();
return rows.some((r: { closingDate?: string[] | string }) => {
const raw = Array.isArray(r.closingDate) ? r.closingDate.join('-') : r.closingDate;
if (!raw) return false;
return new Date(raw).getTime() >= target;
});
}
export async function fetchLatestJournalEntryId(officeId?: number): Promise<string | null> {
const { base, headers } = fineractAuthHeaders();
const params: Record<string, string | number> = { limit: 1, orderBy: 'id', sortOrder: 'DESC' };
if (officeId) params.officeId = officeId;
const { data } = await axios.get(`${base}/journalentries`, { headers, params, timeout: 60000 });
const row = Array.isArray(data) ? data[0] : (data as { pageItems?: { id?: number }[] })?.pageItems?.[0];
if (!row?.id) return null;
return String(row.id);
}

View File

@@ -0,0 +1,176 @@
import { fetchFineractGlAccounts, loadIpsasRegistry } from './omnl-ipsas-gl';
import { fetchOmnlComplianceAggregated } from './omnl-compliance';
import {
loadIas32Signoff,
loadIfrs9Signoff,
loadIfrs9EclParameters,
computeEclAllowance,
isIas32Signed,
isIfrs9Signed,
getComplianceSignoffsSummary,
loadIfrs13FairValueFeeds,
loadProvisionsRegister,
} from './omnl-compliance-pack';
export { getComplianceSignoffsSummary };
/** IFRS 7-style liquidity / liability snapshot (machine-readable; requires accountant review). */
export async function buildIfrs7Disclosure(lineId?: string): Promise<Record<string, unknown>> {
const line = lineId || process.env.OMNL_HEALTH_LINE_ID?.trim() || '';
const registry = loadIpsasRegistry();
const ias32 = loadIas32Signoff();
let glCount = 0;
try {
glCount = (await fetchFineractGlAccounts()).length;
} catch {
glCount = 0;
}
let compliance: Record<string, unknown> | null = null;
if (line) {
try {
compliance = (await fetchOmnlComplianceAggregated(line)) as unknown as Record<string, unknown>;
} catch {
compliance = null;
}
}
return {
standard: 'IFRS 7',
disclaimer: isIas32Signed()
? 'Automated extract — signed IAS 32 policy on file'
: 'Requires qualified accountant sign-off — IAS 32 policy pending',
generatedAt: new Date().toISOString(),
lineId: line || null,
ias32SignoffStatus: ias32?.status ?? 'missing',
financialLiabilities: {
policyReference: 'gru-docs/docs/compliance/accounting/Accounting_Policy_IFRS_IAS32_GRU.md',
classification: ias32?.classification ?? null,
glRoles: registry.monetaryLayerHints?.m1_liability,
fineractGlAccountCount: glCount,
},
liquidityRisk: {
onChainReserveR: compliance?.r ?? null,
onChainM1SupplyS1: compliance?.s1 ?? null,
reportingCompliant: compliance?.reportingCompliant ?? null,
},
creditRisk: {
eclEngineConfigured: Boolean(process.env.OMNL_IFRS9_ECL_ENABLED),
ifrs9SignoffStatus: loadIfrs9Signoff()?.status ?? 'missing',
},
};
}
/** IFRS 9 classification / measurement + ECL allowance from parameters. */
export function buildIfrs9Disclosure(exposureOverrides?: Array<{ segmentId: string; ead: number }>): Record<string, unknown> {
const signoff = loadIfrs9Signoff();
const params = loadIfrs9EclParameters();
const exposures =
exposureOverrides ??
(params?.segments ?? []).map((s) => ({
segmentId: s.id,
ead: parseFloat(process.env[`ECL_EAD_${s.id.toUpperCase()}`] || '0') || 0,
}));
const ecl =
params && exposures.some((e) => e.ead > 0)
? computeEclAllowance(params, exposures)
: { totalEcl: 0, bySegment: [] };
return {
standard: 'IFRS 9',
disclaimer: isIfrs9Signed()
? 'Signed methodology on file — verify amounts before posting'
: 'Requires qualified accountant sign-off — methodology pending',
generatedAt: new Date().toISOString(),
signoffStatus: signoff?.status ?? 'missing',
classification: {
gruInstrument: 'financial_liability',
defaultMeasurement: 'amortized_cost',
policyDoc: 'gru-docs/docs/compliance/accounting/IFRS9_ECL_METHODOLOGY_GRU.md',
approach: signoff?.model?.approach ?? (params ? 'from_parameters_file' : 'unset'),
},
impairment: {
eclEnabled: process.env.OMNL_IFRS9_ECL_ENABLED === '1',
stagingModel: process.env.OMNL_IFRS9_STAGING_MODEL || signoff?.model?.approach || 'simplified',
provisionGlCodes: ['52100', '23010'],
totalEclAllowance: ecl.totalEcl,
bySegment: ecl.bySegment,
parametersSchemaVersion: params?.schemaVersion ?? null,
},
derecognition: {
automated: false,
finteractJournalRequired: true,
monthlyScript: 'scripts/compliance/run-ifrs9-ecl-monthly.sh',
},
};
}
/** IFRS 13 fair-value hierarchy tagging for reserve evidence. */
export function buildIfrs13Disclosure(lineId?: string): Record<string, unknown> {
const line = lineId || process.env.OMNL_HEALTH_LINE_ID?.trim() || '';
const feeds = loadIfrs13FairValueFeeds();
return {
standard: 'IFRS 13',
disclaimer: isIas32Signed() ? 'Supporting reserve evidence hierarchy' : 'Pending IAS 32 sign-off',
generatedAt: new Date().toISOString(),
lineId: line || null,
configSchemaVersion: feeds?.schemaVersion ?? null,
hierarchy: feeds?.hierarchy ?? {
level1: {
description: 'Quoted prices in active markets (e.g. LBMA gold fix feeds)',
envFeed: process.env.OMNL_LBMA_FEED_URL || null,
},
level2: {
description: 'Observable inputs',
contracts: {
reserve: process.env.OMNL_RESERVE_COMMITMENT_STORE || null,
notary: process.env.OMNL_NOTARY_REGISTRY || null,
},
},
level3: {
description: 'Custodian / PoR attestation',
custodianPath: process.env.OMNL_CUSTODIAN_SNAPSHOT_PATH || 'config/omnl-custodian-snapshot.json',
},
},
deviationThresholdPercent: feeds?.deviationThresholdPercent ?? 2.5,
reportScript: 'scripts/omnl/omnl-fair-value-hierarchy-report.sh',
reserveCommitment: {
primaryChain: 138,
mirrorChain: 651940,
},
};
}
/** IAS 21 FX / IAS 37 provisions pointers. */
export function buildIas21Ias37Disclosure(): Record<string, unknown> {
const provisions = loadProvisionsRegister();
const provList = (provisions?.provisions as unknown[]) ?? [];
const contingent = (provisions?.contingentLiabilities as unknown[]) ?? [];
return {
generatedAt: new Date().toISOString(),
ias21: {
revaluationGlIncome: '42100',
revaluationGlExpense: '52100',
fxReserveGl: ['12010', '12020', '12090'],
automatedJob: 'scripts/omnl/omnl-fx-revaluation-post.sh',
},
ias37: {
provisionGlHeader: '23000',
provisionGlDetail: '23010',
registerPath: 'config/omnl-provisions-register.json',
provisionCount: provList.length,
contingentLiabilityCount: contingent.length,
provisions: provList,
contingentLiabilities: contingent,
reportScript: 'scripts/omnl/omnl-provisions-report.sh',
},
};
}
export async function buildFullDisclosure(lineId?: string): Promise<Record<string, unknown>> {
return {
generatedAt: new Date().toISOString(),
signoffs: getComplianceSignoffsSummary(),
ifrs7: await buildIfrs7Disclosure(lineId),
ifrs9: buildIfrs9Disclosure(),
ifrs13: buildIfrs13Disclosure(lineId),
ias21_ias37: buildIas21Ias37Disclosure(),
};
}

View File

@@ -1,5 +1,6 @@
import { loadIpsasRegistryPath } from './omnl-ipsas-gl';
import { loadJournalMatrixPath } from './omnl-journal-matrix';
import { omnlComplianceCore138, omnlReserveStore138 } from './omnl-chain138-addresses';
/** Non-secret snapshot of which OMNL integrations are configured (for probes and dashboards). */
export function getOmnlIntegrationStatus(): Record<string, unknown> {
@@ -7,12 +8,14 @@ export function getOmnlIntegrationStatus(): Record<string, unknown> {
fineract: Boolean(
(process.env.OMNL_FINERACT_BASE_URL || '').trim() && (process.env.OMNL_FINERACT_PASSWORD || '').trim()
),
complianceCore138: Boolean((process.env.OMNL_COMPLIANCE_CORE_138 || '').trim()),
complianceCore138: Boolean(omnlComplianceCore138()),
complianceCoreV2_138: Boolean((process.env.OMNL_COMPLIANCE_CORE_V2_138 || '').trim()),
complianceCore651940: Boolean((process.env.OMNL_COMPLIANCE_CORE_651940 || '').trim()),
instrumentRegistry138: Boolean(
(process.env.OMNL_INSTRUMENT_REGISTRY_138 || process.env.OMNL_INSTRUMENT_REGISTRY || '').trim()
),
reserveStore138: Boolean((process.env.OMNL_RESERVE_STORE_138 || '').trim()),
reserveStore138: Boolean(omnlReserveStore138()),
reserveStoreV2_138: Boolean((process.env.OMNL_RESERVE_STORE_V2_138 || '').trim()),
reserveStore651940: Boolean((process.env.OMNL_RESERVE_STORE_651940 || '').trim()),
circuitBreaker138: Boolean((process.env.OMNL_CIRCUIT_BREAKER_138 || '').trim()),
circuitBreaker651940: Boolean((process.env.OMNL_CIRCUIT_BREAKER_651940 || '').trim()),

View File

@@ -0,0 +1,100 @@
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'fs';
import { join, resolve } from 'path';
import { createHash, randomUUID } from 'crypto';
import { appendOmnlAudit } from './omnl-audit-log';
export type Iso20022MessageType = 'pain.001' | 'pacs.008' | 'camt.053' | 'other';
export type Iso20022Record = {
id: string;
messageType: Iso20022MessageType;
receivedAt: string;
retentionUntil: string;
uetr?: string;
instructionId?: string;
settlementOrChainRef?: string;
accountingRef?: string;
payloadSha256: string;
payload: string;
};
function storeDir(): string {
const raw = process.env.OMNL_ISO20022_STORE_DIR?.trim();
if (raw) return resolve(raw);
const root = process.env.PROXMOX_ROOT || process.env.PHOENIX_REPO_ROOT || process.cwd();
return resolve(root, 'config/iso20022-omnl/messages');
}
function retentionYears(): number {
const y = parseInt(process.env.OMNL_ISO20022_RETENTION_YEARS || '10', 10);
return Number.isFinite(y) && y > 0 ? y : 10;
}
export function iso20022RetentionUntil(fromIso?: string): string {
const d = fromIso ? new Date(fromIso) : new Date();
d.setFullYear(d.getFullYear() + retentionYears());
return d.toISOString();
}
export function saveIso20022Message(input: {
messageType: Iso20022MessageType;
payload: string;
uetr?: string;
instructionId?: string;
settlementOrChainRef?: string;
accountingRef?: string;
}): Iso20022Record {
const dir = storeDir();
mkdirSync(dir, { recursive: true });
const id = randomUUID();
const receivedAt = new Date().toISOString();
const payloadSha256 = createHash('sha256').update(input.payload, 'utf8').digest('hex');
const record: Iso20022Record = {
id,
messageType: input.messageType,
receivedAt,
retentionUntil: iso20022RetentionUntil(receivedAt),
uetr: input.uetr,
instructionId: input.instructionId,
settlementOrChainRef: input.settlementOrChainRef,
accountingRef: input.accountingRef,
payloadSha256,
payload: input.payload,
};
writeFileSync(join(dir, `${id}.json`), JSON.stringify(record, null, 2), 'utf8');
appendOmnlAudit({
category: 'iso20022',
action: 'store_message',
traceId: id,
referenceNumber: input.instructionId,
metadata: { messageType: input.messageType, payloadSha256 },
status: 'ok',
});
return record;
}
export function listIso20022Messages(limit = 50): Array<Pick<Iso20022Record, 'id' | 'messageType' | 'receivedAt' | 'instructionId' | 'accountingRef'>> {
const dir = storeDir();
if (!existsSync(dir)) return [];
const files = readdirSync(dir)
.filter((f) => f.endsWith('.json'))
.sort()
.reverse()
.slice(0, limit);
return files.map((f) => {
const r = JSON.parse(readFileSync(join(dir, f), 'utf8')) as Iso20022Record;
return {
id: r.id,
messageType: r.messageType,
receivedAt: r.receivedAt,
instructionId: r.instructionId,
accountingRef: r.accountingRef,
};
});
}
export function getIso20022Message(id: string): Iso20022Record | null {
const p = join(storeDir(), `${id}.json`);
if (!existsSync(p)) return null;
return JSON.parse(readFileSync(p, 'utf8')) as Iso20022Record;
}

View File

@@ -0,0 +1,226 @@
import axios from 'axios';
import { readFileSync, existsSync } from 'fs';
import { resolve } from 'path';
import { fetchFineractGlAccounts } from './omnl-ipsas-gl';
import { fetchOmnlComplianceAggregated } from './omnl-compliance';
import { appendOmnlAudit } from './omnl-audit-log';
import { loadXauReconcileBaseline } from './omnl-compliance-pack';
export interface TripleReconcileBreak {
code: string;
severity: 'critical' | 'high' | 'medium' | 'low';
message: string;
fineract?: unknown;
onChain?: unknown;
custodian?: unknown;
}
export interface TripleReconcileReport {
generatedAt: string;
lineId: string;
breaks: TripleReconcileBreak[];
aligned: boolean;
fineract: {
glSnapshot: Array<{ glCode: string; name?: string; id: number }>;
liabilityGlCodes: string[];
};
onChain: {
r: string;
s0: string;
s1: string;
reportingCompliant: boolean;
attestationStale: boolean;
} | null;
custodian: {
source: string;
xauOunces?: string;
asOf?: string;
porAttestationRef?: string;
} | null;
comparison?: {
custodianOz?: number;
onChainROz?: number;
deltaOz?: number;
deltaPercent?: number;
withinTolerance?: boolean;
} | null;
}
function loadCustodianSnapshot(): {
xauOunces?: string;
asOf?: string;
source: string;
porAttestationRef?: string;
} | null {
const envOz = process.env.OMNL_CUSTODIAN_RESERVE_XAU_OZ?.trim();
if (envOz) {
return { source: 'env:OMNL_CUSTODIAN_RESERVE_XAU_OZ', xauOunces: envOz, asOf: new Date().toISOString() };
}
const file =
process.env.OMNL_CUSTODIAN_SNAPSHOT_PATH?.trim() ||
resolve(process.cwd(), 'config/omnl-custodian-snapshot.json');
if (!existsSync(file)) return null;
try {
const j = JSON.parse(readFileSync(file, 'utf8')) as {
xauOunces?: string;
asOf?: string;
porAttestationRef?: string;
};
return {
source: file,
xauOunces: j.xauOunces,
asOf: j.asOf,
porAttestationRef: j.porAttestationRef,
};
} catch {
return null;
}
}
/** Bank/custodian ↔ Fineract GL ↔ on-chain reserve R — operational reconciliation (not statutory sign-off). */
export async function runTripleStateReconcile(lineId?: string): Promise<TripleReconcileReport> {
const line = lineId || process.env.OMNL_HEALTH_LINE_ID?.trim() || '';
const breaks: TripleReconcileBreak[] = [];
const generatedAt = new Date().toISOString();
let glRows: Awaited<ReturnType<typeof fetchFineractGlAccounts>> = [];
try {
glRows = await fetchFineractGlAccounts();
} catch (e) {
breaks.push({
code: 'FINERACT_UNREACHABLE',
severity: 'critical',
message: e instanceof Error ? e.message : String(e),
});
}
const liabilityCodes = ['2000', '2100', '1050', '1000', '21010'];
const glSnapshot = glRows
.filter((r) => r.glCode && liabilityCodes.includes(String(r.glCode)))
.map((r) => ({ glCode: String(r.glCode), name: r.name, id: r.id }));
let onChain: TripleReconcileReport['onChain'] = null;
if (!line) {
breaks.push({
code: 'LINE_ID_MISSING',
severity: 'high',
message: 'Set OMNL_HEALTH_LINE_ID or pass lineId query param',
});
} else {
try {
const snap = await fetchOmnlComplianceAggregated(line);
onChain = {
r: snap.r,
s0: snap.s0,
s1: snap.s1,
reportingCompliant: snap.reportingCompliant,
attestationStale: snap.attestationStale,
};
if (!snap.reportingCompliant) {
breaks.push({
code: 'ONCHAIN_NOT_REPORTING_COMPLIANT',
severity: 'high',
message: 'ComplianceCore reportingCompliant is false',
onChain,
});
}
if (snap.attestationStale) {
breaks.push({
code: 'ONCHAIN_ATTESTATION_STALE',
severity: 'medium',
message: 'Reserve attestation past validUntil',
onChain,
});
}
} catch (e) {
breaks.push({
code: 'ONCHAIN_READ_FAILED',
severity: 'high',
message: e instanceof Error ? e.message : String(e),
});
}
}
const custodian = loadCustodianSnapshot();
if (!custodian?.xauOunces) {
breaks.push({
code: 'CUSTODIAN_SNAPSHOT_MISSING',
severity: 'medium',
message: 'Set OMNL_CUSTODIAN_RESERVE_XAU_OZ or config/omnl-custodian-snapshot.json',
});
}
let comparison: TripleReconcileReport['comparison'] = null;
const baseline = loadXauReconcileBaseline();
const ratioStr =
process.env.OMNL_XAU_R_TO_OZ_RATIO?.trim() ||
(baseline?.rMinorUnitsPerTroyOunce
? String(1 / Number(baseline.rMinorUnitsPerTroyOunce))
: '');
if (custodian?.xauOunces && onChain?.r && ratioStr) {
const custOz = parseFloat(custodian.xauOunces);
const rMinor = parseFloat(onChain.r);
const ratio = parseFloat(ratioStr);
if (Number.isFinite(custOz) && Number.isFinite(rMinor) && Number.isFinite(ratio) && ratio > 0) {
const onChainOz = rMinor * ratio;
const deltaOz = custOz - onChainOz;
const deltaPercent = onChainOz !== 0 ? (Math.abs(deltaOz) / onChainOz) * 100 : 0;
const tol = Number(baseline?.tolerancePercent ?? 2.5);
comparison = {
custodianOz: custOz,
onChainROz: onChainOz,
deltaOz,
deltaPercent,
withinTolerance: deltaPercent <= tol,
};
if (!comparison.withinTolerance) {
breaks.push({
code: 'CUSTODIAN_ONCHAIN_DELTA_EXCEEDED',
severity: 'high',
message: `Custodian vs on-chain XAU delta ${deltaPercent.toFixed(2)}% exceeds tolerance ${tol}%`,
custodian,
onChain,
});
}
}
} else if (custodian?.xauOunces && onChain?.r && !ratioStr) {
breaks.push({
code: 'CUSTODIAN_CHAIN_RATIO_UNCONFIGURED',
severity: 'medium',
message: 'Set OMNL_XAU_R_TO_OZ_RATIO or config/compliance/gru-xau-reconcile-baseline.v1.json',
custodian,
onChain,
});
}
if (glSnapshot.length === 0 && glRows.length > 0) {
breaks.push({
code: 'FINERACT_CORE_GL_MISSING',
severity: 'high',
message: 'Expected GL codes 1000/1050/2000/2100 not all present in Fineract',
fineract: { count: glRows.length },
});
}
const aligned = breaks.filter((b) => b.severity === 'critical' || b.severity === 'high').length === 0;
const report: TripleReconcileReport = {
generatedAt,
lineId: line,
breaks,
aligned,
fineract: { glSnapshot, liabilityGlCodes: liabilityCodes },
onChain,
custodian,
comparison,
};
appendOmnlAudit({
category: 'reconcile',
action: 'triple_state_reconcile',
traceId: line || undefined,
status: aligned ? 'ok' : 'error',
metadata: { breakCount: breaks.length, aligned },
});
return report;
}

View File

@@ -0,0 +1,95 @@
import { readFileSync, existsSync } from 'fs';
import { resolve } from 'path';
import { createHash } from 'crypto';
import { id as keccakId } from 'ethers';
function projectRoot(): string {
return (
process.env.PROXMOX_ROOT?.trim() ||
process.env.PHOENIX_REPO_ROOT?.trim() ||
resolve(__dirname, '../../../../../..')
);
}
function readJson<T>(rel: string): T | null {
const p = resolve(projectRoot(), rel);
if (!existsSync(p)) return null;
return JSON.parse(readFileSync(p, 'utf8')) as T;
}
/** Matches Solidity `keccak256(bytes(string))` for short jurisdiction / matrix ids. */
export function jurisdictionIdBytes32(id: string): string {
return keccakId(id);
}
export function matrixControlIdBytes32(rowId: string): string {
return keccakId(rowId);
}
export interface Web3ComplianceSummary {
generatedAt: string;
deployed: Record<string, string | null>;
universalControls: unknown[];
jurisdictions: Record<string, unknown>;
gnosisSafeRecommendation: unknown;
notarizationRequiredForRows: string[];
}
export function getWeb3ComplianceSummary(): Web3ComplianceSummary {
const web3 = readJson<{
universalControls: unknown[];
jurisdictions: Record<string, { matrixRowsRequiringNotarization?: string[] }>;
gnosisSafeRecommendation: unknown;
}>('config/compliance/jurisdiction-web3-controls.v1.json');
const deployed = readJson<{ contracts?: Record<string, string> }>(
'config/compliance/web3-multisig-deployed.v1.json'
);
const envDeployed = {
OMNLJurisdictionPolicyRegistry: process.env.OMNL_JURISDICTION_REGISTRY || null,
OMNLNotaryRegistry: process.env.OMNL_NOTARY_REGISTRY || null,
OMNLComplianceMultisig: process.env.OMNL_COMPLIANCE_MULTISIG || null,
ReserveCommitmentStore: process.env.OMNL_RESERVE_COMMITMENT_STORE || null,
GovernanceController: process.env.OMNL_GOVERNANCE_CONTROLLER || null,
GnosisSafeAdmin: process.env.OMNL_GNOSIS_SAFE_ADMIN || null,
};
const idRows = web3?.jurisdictions?.ID?.matrixRowsRequiringNotarization ?? [];
return {
generatedAt: new Date().toISOString(),
deployed: {
...envDeployed,
...(deployed?.contracts ?? {}),
},
universalControls: web3?.universalControls ?? [],
jurisdictions: web3?.jurisdictions ?? {},
gnosisSafeRecommendation: web3?.gnosisSafeRecommendation ?? null,
notarizationRequiredForRows: idRows,
};
}
export function buildNotarizationIntent(input: {
jurisdictionId: string;
matrixControlId: string;
contentHash: string;
merkleRoot?: string;
metadataHash?: string;
}): Record<string, unknown> {
const content = input.contentHash.startsWith('0x') ? input.contentHash : `0x${input.contentHash}`;
return {
jurisdictionId: jurisdictionIdBytes32(input.jurisdictionId),
matrixControlId: matrixControlIdBytes32(input.matrixControlId),
contentHash: content,
merkleRoot: input.merkleRoot ?? `0x${'0'.repeat(64)}`,
metadataHash: input.metadataHash ?? `0x${'0'.repeat(64)}`,
kind: 'EvidencePackage',
contract: process.env.OMNL_NOTARY_REGISTRY || null,
policyConfigHash: existsSync(resolve(projectRoot(), 'config/compliance/jurisdiction-web3-controls.v1.json'))
? `0x${createHash('sha256')
.update(readFileSync(resolve(projectRoot(), 'config/compliance/jurisdiction-web3-controls.v1.json')))
.digest('hex')}`
: null,
};
}

View File

@@ -1,6 +1,7 @@
import axios from 'axios';
import { createHmac, timingSafeEqual } from 'crypto';
import { logger } from '../utils/logger';
import { appendOmnlAudit } from './omnl-audit-log';
export type OmnlWebhookPayload = {
event: string;
@@ -61,8 +62,22 @@ export async function emitOmnlWebhook(body: OmnlWebhookPayload): Promise<void> {
urls.map(async (url) => {
try {
await axios.post(url, rawBody, { timeout: 15000, headers });
appendOmnlAudit({
category: 'webhook_out',
action: 'emit',
traceId: body.deliveryId,
metadata: { url, event: body.event, chainId: body.chainId },
status: 'ok',
});
} catch (e) {
logger.warn(`OMNL webhook failed ${url}`, { error: e instanceof Error ? e.message : String(e) });
appendOmnlAudit({
category: 'webhook_out',
action: 'emit',
traceId: body.deliveryId,
metadata: { url, event: body.event },
status: 'error',
});
}
})
);

View File

@@ -182,6 +182,31 @@ describe('valuation-precedence', () => {
expect(v.priceUsd).toBe(9.99);
});
it('skips fresh indexer when it only mirrors repo-fallback FX and CoinGecko is available', () => {
process.env.TOKEN_AGG_PRICE_INDEXER_MAX_AGE_SECONDS = '3600';
const lu = new Date();
const v = resolveUsdValuation({
chainId: 138,
normalizedAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
indexer: {
chainId: 138,
tokenAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
priceUsd: 2490,
volume24h: 0,
volume7d: 0,
volume30d: 0,
liquidityUsd: 0,
holdersCount: 0,
transfers24h: 0,
lastUpdated: lu,
},
coingecko: { priceUsd: 2130.5, lastUpdated: new Date() },
});
expect(v.sourceLayer).toBe('external_coingecko');
expect(v.priceUsd).toBe(2130.5);
expect(v.stale).toBe(false);
});
it('mergeMarketWithValuation applies priced layer onto indexer row', () => {
const pricing = resolveUsdValuation({
chainId: 138,

View File

@@ -2,7 +2,7 @@ import type { MarketData } from '../adapters/base-adapter';
import type { TokenMarketData } from '../database/repositories/token-repo';
import { getChainConfig } from '../config/chains';
import { getCanonicalTokenByAddress } from '../config/canonical-tokens';
import { resolveCanonicalPriceUsd } from './canonical-price-oracle';
import { resolveCanonicalPriceUsd, type CanonicalPriceResolution } from './canonical-price-oracle';
const CHAIN_138 = 138;
@@ -88,6 +88,20 @@ function iso(d: Date): string {
return d.toISOString();
}
/** Indexer rows seeded from the static FX snapshot should not beat live reference feeds. */
function indexerMatchesRepoFallbackSnapshot(
indexer: TokenMarketData,
canonical: CanonicalPriceResolution
): boolean {
if (canonical.source !== 'repo-fallback' || canonical.priceUsd === undefined) {
return false;
}
if (indexer.priceUsd === undefined || indexer.priceUsd === null) {
return false;
}
return Math.abs(indexer.priceUsd - canonical.priceUsd) < 1e-6;
}
/**
* Layered USD valuation:
* - Default: indexer (staleness-aware) → CoinGecko → CMC → DexScreener → canonical env/repo.
@@ -117,7 +131,8 @@ export function resolveUsdValuation(input: ValuationInput): TokenPricing {
if (indexer?.priceUsd !== undefined && indexer.priceUsd !== null) {
const lu = indexer.lastUpdated instanceof Date ? indexer.lastUpdated : new Date(indexer.lastUpdated);
const stale = ageMsOf(lu) > maxAgeSeconds * 1000;
const stale =
ageMsOf(lu) > maxAgeSeconds * 1000 || indexerMatchesRepoFallbackSnapshot(indexer, canonical);
out.push({
layer: 'indexer_market',
rank: 1,