feat: OMNL Bank online production — 128 chains, Central Bank UI, settlement stack
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m22s
CI/CD Pipeline / Security Scanning (push) Successful in 2m30s
CI/CD Pipeline / Lint and Format (push) Failing after 44s
CI/CD Pipeline / Terraform Validation (push) Failing after 27s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 31s
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 56s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 29s
Validation / validate-genesis (push) Successful in 34s
Validation / validate-terraform (push) Failing after 39s
Validation / validate-kubernetes (push) Failing after 14s
Validation / validate-smart-contracts (push) Failing after 20s
Validation / validate-security (push) Failing after 1m19s
Validation / validate-documentation (push) Failing after 27s
Verify Deployment / Verify Deployment (push) Failing after 1m13s

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-28 08:44:42 -07:00
parent 2b1a3ba6af
commit 5dd67e2b1d
113 changed files with 13223 additions and 98 deletions

View File

@@ -0,0 +1,171 @@
import { v4 as uuidv4 } from 'uuid';
import {
buildMoneySupplySnapshot,
isIbanValid,
m2FiatToTokenLoadAmount,
resolveInstrumentGl,
rollExternalTransferVerbiage,
swiftKindForRequest,
type ExternalTransferRequest,
type SettlementRecord,
} from '@dbis/settlement-core';
import { loadConfig, loadOfficeProfile, settlementOfficeId } from '../config';
import { fetchGlBalances, postJournalEntry } from '../adapters/fineract';
import { HybxProductionRail } from '../adapters/hybx-production';
import { archiveIso20022, requestTokenMint } from '../adapters/omnl';
import { buildMt102Stub, buildMt103Stub, forwardSwiftToListener } from '../adapters/swift';
import { settlementStore } from '../store/settlement-store';
function glId(code: string): number {
return parseInt(code, 10);
}
export async function processExternalTransfer(
input: ExternalTransferRequest,
): Promise<SettlementRecord> {
const cfg = loadConfig();
const profile = loadOfficeProfile();
const officeId = settlementOfficeId(input.officeId);
const req: ExternalTransferRequest = { ...input, officeId };
const existing = settlementStore.getByIdempotencyKey(req.idempotencyKey);
if (existing?.phase === 'SETTLED') return existing;
const now = new Date().toISOString();
let record: SettlementRecord = existing ?? {
settlementId: uuidv4(),
idempotencyKey: req.idempotencyKey,
phase: 'RECEIVED',
request: req,
errors: [],
createdAt: now,
updatedAt: now,
};
const advance = (phase: SettlementRecord['phase'], patch: Partial<SettlementRecord> = {}) => {
record = { ...record, phase, updatedAt: new Date().toISOString(), ...patch };
settlementStore.save(record);
};
try {
if (!isIbanValid(req.creditorIban)) {
throw new Error(`Invalid creditor IBAN: ${req.creditorIban}`);
}
advance('VALIDATED');
const verbiage = rollExternalTransferVerbiage(req);
advance('VERBIAGE_ROLLED', { verbiageDocument: verbiage });
const amount = parseFloat(req.amount) || 0;
const tf = req.tradeFinance;
const gl = tf ? resolveInstrumentGl(tf.instrument) : null;
const debitGl = gl?.debitGl ?? profile.settlement.glSettlement ?? cfg.moneySupply.glSettlement;
const creditGl = gl?.creditGl ?? profile.settlement.glM1 ?? cfg.moneySupply.glM1;
if (amount > 0) {
const je = await postJournalEntry({
officeId: req.officeId,
transactionDate: req.valueDate,
referenceNumber: req.idempotencyKey,
comments: verbiage.slice(0, 500),
debits: [{ glAccountId: glId(debitGl), amount }],
credits: [{ glAccountId: glId(creditGl), amount }],
});
advance('FINERACT_POSTED', { fineractJournalRef: String(je.resourceId) });
} else {
advance('FINERACT_POSTED');
}
if (cfg.rails.hybx.enabled && req.rail !== 'INTERNAL') {
const hybx = new HybxProductionRail();
const pay = await hybx.dispatchPayment({
idempotencyKey: req.idempotencyKey,
amount: req.amount,
currency: req.currency,
creditorIban: req.creditorIban,
creditorBic: req.creditorBic,
beneficiaryName: req.beneficiaryName,
remittanceInfo: req.remittanceInfo,
rail: req.rail,
});
advance('HYBX_RAIL_DISPATCHED', { hybxPaymentId: pay.paymentId });
} else {
advance('HYBX_RAIL_DISPATCHED');
}
const swiftKind = swiftKindForRequest(req);
const swiftRaw =
swiftKind === 'MT102'
? buildMt102Stub({
senderRef: req.idempotencyKey,
currency: req.currency,
amount: req.amount,
valueDate: req.valueDate,
orderingInstitution: req.orderingName ?? cfg.omnlLegalName,
beneficiary: req.beneficiaryName,
iban: req.creditorIban,
remittance: req.remittanceInfo,
})
: buildMt103Stub({
senderRef: req.idempotencyKey,
currency: req.currency,
amount: req.amount,
valueDate: req.valueDate,
orderingCustomer: req.orderingName ?? cfg.omnlLegalName,
beneficiary: req.beneficiaryName,
iban: req.creditorIban,
bic: req.creditorBic,
remittance: req.remittanceInfo,
});
if (cfg.rails.swift.enabled) {
await forwardSwiftToListener(swiftRaw);
}
const iso = await archiveIso20022({
messageType: swiftKind === 'MT102' ? 'pacs.008' : 'pacs.008',
direction: 'OUTBOUND',
raw: swiftRaw,
settlementRef: record.settlementId,
});
advance('ISO20022_ARCHIVED', { iso20022MessageId: iso.messageId });
if (req.convertToCrypto?.enabled) {
const balances = await fetchGlBalances(req.officeId);
const snap = buildMoneySupplySnapshot(req.officeId, balances);
const { loadAmount, fullyBacked } = m2FiatToTokenLoadAmount(
req.amount,
snap.M2.broadMoney,
);
if (!fullyBacked) {
record.errors.push(`M2 backing insufficient for token load (${loadAmount}/${req.amount})`);
}
const mint = await requestTokenMint({
lineId: req.convertToCrypto.tokenLineId,
amount: loadAmount,
recipient: req.convertToCrypto.recipientAddress,
settlementRef: record.settlementId,
dryRun: !cfg.production.allowChainMintExecute,
});
advance('CHAIN_MINT_REQUESTED', { chainTxHash: mint.txHash });
} else {
advance('CHAIN_MINT_REQUESTED');
}
advance('SETTLED');
return record;
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
record.errors.push(msg);
advance('FAILED');
return record;
}
}
export async function getMoneySupply(officeId: number) {
const balances = await fetchGlBalances(officeId);
return buildMoneySupplySnapshot(officeId, balances);
}
export function getSettlementStatus(id: string): SettlementRecord | null {
return settlementStore.getById(id);
}