Files
smom-dbis-138/services/settlement-middleware/dist/api/routes/settlement.js
zaragoza444 b72b986a12
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m15s
CI/CD Pipeline / Security Scanning (push) Successful in 2m16s
CI/CD Pipeline / Lint and Format (push) Failing after 48s
CI/CD Pipeline / Terraform Validation (push) Failing after 28s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 29s
Validation / validate-genesis (push) Successful in 32s
Validation / validate-terraform (push) Failing after 31s
Validation / validate-kubernetes (push) Failing after 13s
Validation / validate-smart-contracts (push) Failing after 10s
Validation / validate-security (push) Failing after 1m20s
Validation / validate-documentation (push) Failing after 21s
fix: Fineract GL balances via journal fallback when report missing
Use journalentry replay when General Ledger Report is unavailable, and expose fineractConnected on the public money-supply endpoint.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-28 15:55:15 -07:00

306 lines
12 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createSettlementRouter = createSettlementRouter;
const express_1 = require("express");
const settlement_core_1 = require("@dbis/settlement-core");
const config_1 = require("../../config");
const external_transfer_1 = require("../../workflows/external-transfer");
const token_load_1 = require("../../workflows/token-load");
const transfer_1 = require("../../workflows/transfer");
const office_scope_1 = require("../../workflows/office-scope");
const settlement_store_1 = require("../../store/settlement-store");
const swift_inbound_1 = require("../../workflows/swift-inbound");
const fineract_1 = require("../../adapters/fineract");
function requireApiKey(req, res) {
const cfg = (0, config_1.loadConfig)();
if (!cfg.production.requireApiKey)
return true;
const key = req.header('Authorization')?.replace(/^Bearer\s+/i, '') || req.header('X-API-Key');
if (key && key === process.env.OMNL_API_KEY)
return true;
res.status(401).json({ error: 'Unauthorized' });
return false;
}
function createSettlementRouter() {
const router = (0, express_1.Router)();
const officeId = (0, config_1.settlementOfficeId)();
const profile = (0, config_1.loadOfficeProfile)();
router.get('/health', (_req, res) => {
let chainStats = { total: 0, active: 0 };
try {
const reg = (0, settlement_core_1.loadOmnlChainRegistry)();
chainStats = { total: reg.chains.length, active: (0, settlement_core_1.getActiveChains)().length };
}
catch {
/* optional */
}
res.json({
service: 'omnl-settlement-middleware',
status: 'ok',
mode: process.env.SETTLEMENT_MIDDLEWARE_CONFIG?.includes('production') ? 'production' : 'standard',
role: 'OMNL central-bank settlement orchestration',
chainSupport: { max: 128, ...chainStats },
settlementOffice: {
officeId: profile.officeId,
externalId: profile.externalId,
name: profile.name,
},
enforceSingleOffice: (0, config_1.loadConfig)().enforceSingleOffice ?? false,
});
});
router.get('/office', (_req, res) => {
res.json({
...profile,
locked: true,
storePath: `data/settlement-store/office-${officeId}`,
});
});
router.get('/money-supply/public', async (_req, res) => {
const cfg = (0, config_1.loadConfig)();
const allow = cfg.production.onlineBankMode || process.env.OMNL_PUBLIC_MONEY_SUPPLY === '1';
if (!allow) {
res.status(404).json({ error: 'Public money supply disabled' });
return;
}
try {
const [balances, fineractConnected] = await Promise.all([
(0, fineract_1.fetchGlBalances)(officeId),
(0, fineract_1.fineractOfficeReachable)(officeId),
]);
const snapshot = (0, settlement_core_1.buildMoneySupplySnapshot)(officeId, balances);
const hasActivity = Object.values(balances).some((v) => Math.abs(parseFloat(v) || 0) > 0);
res.json({
...snapshot,
fineractConnected,
fineractLive: fineractConnected && hasActivity,
interoffice: {
dueFromHeadOffice: balances['1410'] ?? '0',
gl1410: balances['1410'] ?? '0',
},
});
}
catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
router.get('/money-supply', async (req, res) => {
if (!requireApiKey(req, res))
return;
try {
res.json(await (0, external_transfer_1.getMoneySupply)(officeId));
}
catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
router.get('/money-supply/:officeIdParam', async (req, res) => {
if (!requireApiKey(req, res))
return;
try {
const requested = parseInt(req.params.officeIdParam, 10);
res.json(await (0, external_transfer_1.getMoneySupply)((0, config_1.settlementOfficeId)(requested)));
}
catch (e) {
res.status(400).json({ error: e instanceof Error ? e.message : String(e) });
}
});
router.get('/settlements', (req, res) => {
if (!requireApiKey(req, res))
return;
res.json({
officeId,
items: settlement_store_1.settlementStore.list(Number(req.query.limit ?? 50)),
});
});
router.get('/settlements/:id', (req, res) => {
if (!requireApiKey(req, res))
return;
const rec = (0, external_transfer_1.getSettlementStatus)(req.params.id);
if (!rec) {
res.status(404).json({ error: 'Not found' });
return;
}
res.json(rec);
});
router.post('/external-transfer', async (req, res) => {
if (!requireApiKey(req, res))
return;
try {
const merged = (0, office_scope_1.bindOffice24)(req.body);
const record = await (0, external_transfer_1.processExternalTransfer)(merged);
res.status(record.phase === 'FAILED' ? 422 : 200).json(record);
}
catch (e) {
res.status(e instanceof Error && e.message.includes('locked') ? 403 : 500).json({
error: e instanceof Error ? e.message : String(e),
});
}
});
router.post('/swift/inbound', async (req, res) => {
if (!requireApiKey(req, res))
return;
try {
const raw = typeof req.body === 'string' ? req.body : req.body?.raw;
if (!raw) {
res.status(400).json({ error: 'raw SWIFT payload required' });
return;
}
const record = await (0, swift_inbound_1.processSwiftInbound)(String(raw));
res.json(record);
}
catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
router.post('/trade-finance/:instrument', async (req, res) => {
if (!requireApiKey(req, res))
return;
const instrument = req.params.instrument.toUpperCase();
if (!['SBLC', 'DLC', 'BG', 'LS'].includes(instrument)) {
res.status(400).json({ error: 'Invalid instrument' });
return;
}
try {
const body = req.body;
const merged = (0, office_scope_1.bindOffice24)({
...body,
tradeFinance: {
instrument,
reference: body.tradeFinance?.reference ?? body.idempotencyKey,
expiryDate: body.tradeFinance?.expiryDate,
},
rail: 'SWIFT',
moneyLayers: body.moneyLayers ?? ['M1', 'M2'],
});
const record = await (0, external_transfer_1.processExternalTransfer)(merged);
res.status(record.phase === 'FAILED' ? 422 : 200).json(record);
}
catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
router.post('/convert/fiat-to-crypto', async (req, res) => {
if (!requireApiKey(req, res))
return;
try {
const body = req.body;
const merged = (0, office_scope_1.bindOffice24)({
...body,
rail: body.rail ?? 'INTERNAL',
convertToCrypto: {
enabled: true,
targetChainId: body.convertToCrypto?.targetChainId ?? 138,
tokenLineId: body.convertToCrypto?.tokenLineId ?? `${body.currency ?? 'USD'}-M2`,
recipientAddress: body.convertToCrypto?.recipientAddress ?? '',
},
moneyLayers: ['M2'],
});
const record = await (0, external_transfer_1.processExternalTransfer)(merged);
res.status(record.phase === 'FAILED' ? 422 : 200).json(record);
}
catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
router.post('/token-load', async (req, res) => {
if (!requireApiKey(req, res))
return;
try {
const body = req.body;
if (!body.idempotencyKey || !body.lineId || !body.amount || !body.recipientAddress) {
res.status(400).json({ error: 'idempotencyKey, lineId, amount, recipientAddress required' });
return;
}
const record = await (0, token_load_1.processTokenLoad)({
...body,
officeId: (0, config_1.settlementOfficeId)(body.officeId),
});
res.status(record.phase === 'FAILED' ? 422 : 200).json(record);
}
catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
router.post('/transfer/internal', async (req, res) => {
if (!requireApiKey(req, res))
return;
try {
const body = req.body;
if (!body.idempotencyKey || !body.tokenAddress || !body.amount || !body.recipientAddress) {
res.status(400).json({ error: 'idempotencyKey, tokenAddress, amount, recipientAddress required' });
return;
}
const record = await (0, transfer_1.processTransfer)({
...body,
officeId: (0, config_1.settlementOfficeId)(body.officeId),
rail: 'INTERNAL',
moneyLayers: body.moneyLayers ?? ['M2'],
});
res.status(record.phase === 'FAILED' ? 422 : 200).json(record);
}
catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
router.post('/transfer/external', async (req, res) => {
if (!requireApiKey(req, res))
return;
try {
const body = req.body;
if (!body.creditorIban) {
res.status(400).json({ error: 'creditorIban required for external transfer' });
return;
}
const record = await (0, transfer_1.processTransfer)({
...body,
officeId: (0, config_1.settlementOfficeId)(body.officeId),
rail: body.rail === 'RTGS' ? 'RTGS' : 'SWIFT',
moneyLayers: body.moneyLayers ?? ['M2'],
});
res.status(record.phase === 'FAILED' ? 422 : 200).json(record);
}
catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
router.get('/tokens/m2', (_req, res) => {
try {
const fs = require('fs');
const path = require('path');
const p = process.env.OMNL_M2_TOKEN_REGISTRY ||
path.resolve(__dirname, '../../../../../config/omnl-m2-token-registry.v1.json');
const registry = JSON.parse(fs.readFileSync(p, 'utf8'));
res.json(registry);
}
catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
router.get('/chains', (_req, res) => {
try {
const registry = (0, settlement_core_1.loadOmnlChainRegistry)();
res.json({
brand: registry.brand,
maxCapacity: registry.maxCapacity,
primaryChainId: registry.primaryChainId,
mirrorChainId: registry.mirrorChainId,
settlementOfficeId: registry.settlementOfficeId,
stats: registry.stats,
chains: registry.chains,
});
}
catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
router.get('/chains/active', (_req, res) => {
try {
res.json({ count: (0, settlement_core_1.getActiveChains)().length, chains: (0, settlement_core_1.getActiveChains)() });
}
catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
return router;
}