fix(settlement): add z-bank M0-M4 workflow source for production build
Some checks failed
CI/CD Pipeline / Security Scanning (push) Has been cancelled
CI/CD Pipeline / Lint and Format (push) Has been cancelled
CI/CD Pipeline / Terraform Validation (push) Has been cancelled
CI/CD Pipeline / Solidity Contracts (push) Has been cancelled
CI/CD Pipeline / Kubernetes Validation (push) Has been cancelled
Validation / validate-genesis (push) Has been cancelled
Validation / validate-terraform (push) Has been cancelled
Validation / validate-kubernetes (push) Has been cancelled
Validation / validate-smart-contracts (push) Has been cancelled
Validation / validate-security (push) Has been cancelled
Validation / validate-documentation (push) Has been cancelled

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-03 02:07:02 -07:00
parent abb63b4336
commit c80c5364ca

View File

@@ -0,0 +1,230 @@
import { v4 as uuidv4 } from 'uuid';
import {
ALL_MONEY_LAYERS,
buildMoneySupplySnapshot,
isIbanValid,
m2FiatToTokenLoadAmount,
m3TokenLoadAmount,
rollExternalTransferVerbiage,
resolveZBankLayerJournal,
zBankM0ToM4Hops,
type ExternalTransferRequest,
type SettlementRecord,
type ZBankLayerStep,
type ZBankM0M4Request,
} from '@dbis/settlement-core';
import { loadConfig, loadOfficeProfile, settlementOfficeId } from '../config';
import { fetchGlBalances, postJournalEntry, resolveGlAccountId } from '../adapters/fineract';
import { HybxProductionRail } from '../adapters/hybx-production';
import { archiveIso20022, requestTokenMint } from '../adapters/omnl';
import { buildMt103Stub, forwardSwiftToListener } from '../adapters/swift';
import { settlementStore } from '../store/settlement-store';
function toExternalRequest(input: ZBankM0M4Request, officeId: number): ExternalTransferRequest {
return {
idempotencyKey: input.idempotencyKey,
officeId,
valueDate: input.valueDate ?? new Date().toISOString().slice(0, 10),
currency: input.currency ?? 'USD',
amount: input.amount,
creditorIban: input.creditorIban,
creditorBic: input.creditorBic,
beneficiaryName: input.beneficiaryName ?? 'Z Online Bank',
orderingName: 'Z Online Bank — OMNL Office 24',
remittanceInfo: input.remittanceInfo ?? 'Z Online Bank full settlement M0→M4',
moneyLayers: ALL_MONEY_LAYERS,
rail: 'SWIFT',
swiftMessageKind: 'MT103',
convertToCrypto: input.recipientAddress
? {
enabled: true,
targetChainId: 138,
tokenLineId: input.tokenLineId ?? 'USD-M2',
recipientAddress: input.recipientAddress,
}
: undefined,
};
}
export async function processZBankM0ToM4(input: ZBankM0M4Request): Promise<SettlementRecord> {
const cfg = loadConfig();
const profile = loadOfficeProfile();
const officeId = settlementOfficeId(input.officeId);
const includeM3 = Boolean(input.recipientAddress?.trim());
const req = toExternalRequest(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,
layerSteps: [],
errors: [],
createdAt: now,
updatedAt: now,
};
const advance = (phase: SettlementRecord['phase'], patch: Partial<SettlementRecord> = {}) => {
record = { ...record, phase, updatedAt: new Date().toISOString(), ...patch };
settlementStore.save(record);
};
const glProfile = {
glM0: profile.settlement.glM0 ?? cfg.moneySupply.glM0 ?? '1050',
glM1: profile.settlement.glM1 ?? cfg.moneySupply.glM1 ?? '2100',
glM2: profile.settlement.glM2 ?? cfg.moneySupply.glM2 ?? '2200',
glM3: profile.settlement.glM3 ?? cfg.moneySupply.glM3 ?? '2300',
glM4NearMoney:
profile.settlement.glM4NearMoney ?? cfg.moneySupply.glM4NearMoney ?? cfg.moneySupply.glSettlement ?? '1000',
};
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 hops = zBankM0ToM4Hops(includeM3);
const steps: ZBankLayerStep[] = [];
let lastJournalRef: string | undefined;
if (amount > 0) {
const balances = await fetchGlBalances(officeId);
const snap = buildMoneySupplySnapshot(officeId, balances);
for (const hop of hops) {
const pair = resolveZBankLayerJournal(hop.from, hop.to, glProfile);
if (!pair) {
steps.push({
from: hop.from,
to: hop.to,
debitGl: '',
creditGl: '',
status: 'skipped',
});
continue;
}
if (hop.to === 'M3') {
const { fullyBacked } = m2FiatToTokenLoadAmount(req.amount, snap.M2.broadMoney);
const m3Check = m3TokenLoadAmount(req.amount, snap.M2.broadMoney, snap.M3.tokenLiabilities);
if (!fullyBacked || !m3Check.fullyBacked) {
steps.push({
from: hop.from,
to: hop.to,
debitGl: pair.debitGl,
creditGl: pair.creditGl,
status: 'failed',
});
throw new Error(
`M2/M3 backing insufficient (${m3Check.loadAmount}/${req.amount} M3 headroom)`,
);
}
}
const [debitGlId, creditGlId] = await Promise.all([
resolveGlAccountId(pair.debitGl),
resolveGlAccountId(pair.creditGl),
]);
const ref = `${req.idempotencyKey}-${hop.from}${hop.to}`;
const je = await postJournalEntry({
officeId,
transactionDate: req.valueDate,
referenceNumber: ref,
comments: `${pair.narrative} · ${verbiage.slice(0, 200)}`,
debits: [{ glAccountId: debitGlId, amount }],
credits: [{ glAccountId: creditGlId, amount }],
});
lastJournalRef = String(je.resourceId);
steps.push({
from: hop.from,
to: hop.to,
debitGl: pair.debitGl,
creditGl: pair.creditGl,
journalRef: lastJournalRef,
status: 'posted',
});
}
}
advance('FINERACT_POSTED', { fineractJournalRef: lastJournalRef, layerSteps: steps });
if (cfg.rails.hybx.enabled) {
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 swiftRaw = 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: 'pacs.008',
direction: 'OUTBOUND',
raw: swiftRaw,
settlementRef: record.settlementId,
});
advance('ISO20022_ARCHIVED', { iso20022MessageId: iso.messageId });
if (req.convertToCrypto?.enabled && req.convertToCrypto.recipientAddress) {
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) {
const mint = await requestTokenMint({
lineId: req.convertToCrypto.tokenLineId,
amount: m3Check.loadAmount,
recipient: req.convertToCrypto.recipientAddress,
settlementRef: record.settlementId,
dryRun: !cfg.production.allowChainMintExecute,
});
advance('CHAIN_MINT_REQUESTED', { chainTxHash: mint.txHash });
} else {
record.errors.push('M3 mint skipped — insufficient M2/M3 backing');
advance('CHAIN_MINT_REQUESTED');
}
} 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;
}
}