import { v4 as uuidv4 } from 'uuid'; import fs from 'fs'; import path from 'path'; import { buildMoneySupplySnapshot, findM2TokenByLineId, GL_CODES, m2FiatToTokenLoadAmount, m3TokenLoadAmount, type M2TokenRegistryFile, type TokenLoadRequest, type SettlementRecord, } from '@dbis/settlement-core'; import { loadConfig, loadOfficeProfile, settlementOfficeId } from '../config'; import { fetchGlBalances, postJournalEntry, resolveGlAccountId } from '../adapters/fineract'; import { requestTokenMint } from '../adapters/omnl'; import { settlementStore } from '../store/settlement-store'; function loadTokenRegistry(): M2TokenRegistryFile { const registryPath = process.env.OMNL_M2_TOKEN_REGISTRY || path.resolve(__dirname, '../../../config/omnl-m2-token-registry.v1.json'); return JSON.parse(fs.readFileSync(registryPath, 'utf8')) as M2TokenRegistryFile; } function resolveTokenAddress(req: TokenLoadRequest): string | undefined { if (req.tokenAddress?.startsWith('0x')) return req.tokenAddress; const registry = loadTokenRegistry(); const byLine = findM2TokenByLineId(registry, req.lineId); if (byLine?.address.startsWith('0x') && byLine.address !== '0x0000000000000000000000000000000000000000') { return byLine.address; } if (req.symbol) { const bySym = registry.tokens.find((t) => t.symbol.toLowerCase() === req.symbol!.toLowerCase()); if (bySym?.address.startsWith('0x') && bySym.address !== '0x0000000000000000000000000000000000000000') { return bySym.address; } } return undefined; } export async function processTokenLoad(input: TokenLoadRequest): Promise { const cfg = loadConfig(); const profile = loadOfficeProfile(); const officeId = settlementOfficeId(input.officeId); const req = { ...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: { idempotencyKey: req.idempotencyKey, officeId, valueDate: now.slice(0, 10), currency: req.currency ?? 'USD', amount: req.amount, creditorIban: 'INTERNAL-M2-TOKEN-LOAD', beneficiaryName: req.recipientAddress, remittanceInfo: `M2→M3 token load ${req.lineId} → ${req.symbol ?? req.tokenAddress ?? 'token'}`, moneyLayers: ['M2', 'M3'], rail: 'INTERNAL', convertToCrypto: { enabled: true, targetChainId: 138, tokenLineId: req.lineId, recipientAddress: req.recipientAddress, }, }, errors: [], createdAt: now, updatedAt: now, }; const advance = (phase: SettlementRecord['phase'], patch: Partial = {}) => { record = { ...record, phase, updatedAt: new Date().toISOString(), ...patch }; settlementStore.save(record); }; try { advance('VALIDATED'); advance('VERBIAGE_ROLLED', { verbiageDocument: [ `OMNL M2→M3 fiat-backed token load`, `Office ${officeId} · line ${req.lineId}`, `Amount ${req.amount} · recipient ${req.recipientAddress}`, `GL ${GL_CODES.M2_BROAD} → ${GL_CODES.M3_TOKEN_LIABILITY}`, ].join('\n'), }); const balances = await fetchGlBalances(officeId); const snap = buildMoneySupplySnapshot(officeId, balances); const { loadAmount, fullyBacked } = m2FiatToTokenLoadAmount(req.amount, snap.M2.broadMoney); const m3Check = m3TokenLoadAmount(loadAmount, snap.M2.broadMoney, snap.M3.tokenLiabilities); if (!fullyBacked || !m3Check.fullyBacked) { record.errors.push( `M2/M3 backing insufficient (M2 load ${loadAmount}/${req.amount}, M3 headroom ${m3Check.loadAmount})`, ); advance('FAILED'); return record; } const amount = parseFloat(loadAmount) || 0; if (amount > 0) { const [debitGlId, creditGlId] = await Promise.all([ resolveGlAccountId(profile.settlement.glM2 ?? GL_CODES.M2_BROAD), resolveGlAccountId(profile.settlement.glM3 ?? GL_CODES.M3_TOKEN_LIABILITY), ]); const je = await postJournalEntry({ officeId, transactionDate: now.slice(0, 10), referenceNumber: req.idempotencyKey, comments: `M2→M3 token load ${req.lineId}`, debits: [{ glAccountId: debitGlId, amount }], credits: [{ glAccountId: creditGlId, amount }], }); advance('FINERACT_POSTED', { fineractJournalRef: String(je.resourceId) }); } else { advance('FINERACT_POSTED'); } advance('HYBX_RAIL_DISPATCHED'); advance('ISO20022_ARCHIVED'); const mint = await requestTokenMint({ lineId: req.lineId, amount: loadAmount, recipient: req.recipientAddress, settlementRef: record.settlementId, dryRun: !cfg.production.allowChainMintExecute, symbol: req.symbol, tokenAddress: resolveTokenAddress(req), }); advance('CHAIN_MINT_REQUESTED', { chainTxHash: mint.txHash }); advance('SETTLED'); return record; } catch (err) { record.errors.push(err instanceof Error ? err.message : String(err)); advance('FAILED'); return record; } }