Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 59s
CI/CD Pipeline / Security Scanning (push) Successful in 2m34s
CI/CD Pipeline / Lint and Format (push) Failing after 49s
CI/CD Pipeline / Terraform Validation (push) Failing after 22s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 26s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 19s
Validation / validate-genesis (push) Successful in 31s
Validation / validate-terraform (push) Failing after 27s
Validation / validate-kubernetes (push) Failing after 10s
Validation / validate-smart-contracts (push) Failing after 12s
Validation / validate-security (push) Failing after 1m26s
Validation / validate-documentation (push) Failing after 18s
Wire Business/Platform/Spot adapters with fail-closed live gates, status/probe/dispatch APIs, smoke script, and portal rails UI. Co-authored-by: Cursor <cursoragent@cursor.com>
236 lines
11 KiB
JavaScript
236 lines
11 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.processTransfer = processTransfer;
|
|
const uuid_1 = require("uuid");
|
|
const settlement_core_1 = require("@dbis/settlement-core");
|
|
const config_1 = require("../config");
|
|
const fineract_1 = require("../adapters/fineract");
|
|
const hybx_production_1 = require("../adapters/hybx-production");
|
|
const omnl_1 = require("../adapters/omnl");
|
|
const swift_1 = require("../adapters/swift");
|
|
const settlement_store_1 = require("../store/settlement-store");
|
|
const btc_l1_settlement_1 = require("./btc-l1-settlement");
|
|
/**
|
|
* INTERNAL / CHAIN138 → on-chain ERC-20 when execute enabled.
|
|
* SWIFT / RTGS / REVOLUT / WISE → bank rail (IBAN).
|
|
* BINANCE → exchange withdraw (address) after ledger journal.
|
|
* CHAIN138 with externalRecipient → treated as external crypto egress (still on-chain).
|
|
*/
|
|
async function processTransfer(input) {
|
|
const cfg = (0, config_1.loadConfig)();
|
|
const profile = (0, config_1.loadOfficeProfile)();
|
|
const officeId = (0, config_1.settlementOfficeId)(input.officeId);
|
|
const req = { ...input, officeId };
|
|
const isBankExternal = req.rail === 'SWIFT' || req.rail === 'RTGS' || req.rail === 'REVOLUT' || req.rail === 'WISE';
|
|
const isExchangeExternal = req.rail === 'BINANCE';
|
|
/** Crypto leaving OMNL custody to any 0x wallet (external transferable). */
|
|
const isCryptoExternal = !isBankExternal &&
|
|
!isExchangeExternal &&
|
|
Boolean(req.tokenAddress?.startsWith('0x')) &&
|
|
Boolean(req.recipientAddress?.startsWith('0x')) &&
|
|
(req.rail === 'CHAIN138' || req.remittanceInfo?.includes('EXTERNAL_CRYPTO'));
|
|
const isExternal = isBankExternal || isCryptoExternal || isExchangeExternal;
|
|
const existing = settlement_store_1.settlementStore.getByIdempotencyKey(req.idempotencyKey);
|
|
if (existing?.phase === 'SETTLED')
|
|
return existing;
|
|
const now = new Date().toISOString();
|
|
let record = existing ?? {
|
|
settlementId: (0, uuid_1.v4)(),
|
|
idempotencyKey: req.idempotencyKey,
|
|
phase: 'RECEIVED',
|
|
request: {
|
|
idempotencyKey: req.idempotencyKey,
|
|
officeId,
|
|
valueDate: now.slice(0, 10),
|
|
currency: req.currency ?? (req.tokenSymbol === 'cBTC' ? 'BTC' : 'USD'),
|
|
amount: req.amount,
|
|
creditorIban: req.creditorIban ??
|
|
(isBankExternal
|
|
? ''
|
|
: isExchangeExternal
|
|
? `EXT-BINANCE-${req.recipientAddress || 'withdraw'}`
|
|
: isCryptoExternal
|
|
? `EXT-CRYPTO-${req.recipientAddress}`
|
|
: 'INTERNAL-CHAIN138'),
|
|
beneficiaryName: req.beneficiaryName ?? req.recipientAddress,
|
|
remittanceInfo: req.remittanceInfo ??
|
|
(isCryptoExternal
|
|
? `EXTERNAL_CRYPTO ${req.tokenSymbol} → ${req.recipientAddress}`
|
|
: isExchangeExternal
|
|
? `BINANCE_WITHDRAW ${req.currency || req.tokenSymbol}`
|
|
: `Transfer ${req.tokenSymbol} → ${req.recipientAddress}`),
|
|
moneyLayers: req.moneyLayers.length
|
|
? req.moneyLayers
|
|
: isBankExternal || isExchangeExternal
|
|
? ['M2', 'M4']
|
|
: isCryptoExternal
|
|
? ['M3', 'M4']
|
|
: ['M2', 'M3'],
|
|
rail: req.rail === 'CHAIN138'
|
|
? 'CHAIN138'
|
|
: req.rail === 'INTERNAL'
|
|
? 'INTERNAL'
|
|
: req.rail === 'REVOLUT' || req.rail === 'WISE' || req.rail === 'BINANCE'
|
|
? req.rail
|
|
: 'SWIFT',
|
|
},
|
|
errors: [],
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
};
|
|
const advance = (phase, patch = {}) => {
|
|
record = { ...record, phase, updatedAt: new Date().toISOString(), ...patch };
|
|
settlement_store_1.settlementStore.save(record);
|
|
};
|
|
try {
|
|
if (isBankExternal && !req.creditorIban) {
|
|
throw new Error('creditorIban required for external bank transfer');
|
|
}
|
|
if (isExchangeExternal && !req.recipientAddress?.startsWith('0x') && !req.recipientAddress) {
|
|
throw new Error('recipientAddress required for Binance withdraw');
|
|
}
|
|
if ((isCryptoExternal || req.rail === 'INTERNAL' || req.rail === 'CHAIN138') &&
|
|
!req.recipientAddress?.startsWith('0x')) {
|
|
throw new Error('recipientAddress (0x) required for on-chain transfer');
|
|
}
|
|
advance('VALIDATED');
|
|
advance('VERBIAGE_ROLLED', {
|
|
verbiageDocument: [
|
|
`OMNL ${req.rail} transfer · ${req.tokenSymbol}`,
|
|
`Layers ${record.request.moneyLayers.join('/')} · ${req.amount}`,
|
|
isBankExternal
|
|
? `IBAN ${req.creditorIban} · M4 GL ${profile.settlement.glM4NearMoney ?? '1000'} suspense`
|
|
: isCryptoExternal
|
|
? `EXTERNAL CRYPTO egress → ${req.recipientAddress} (transferableExternal)`
|
|
: `To ${req.recipientAddress} · internal chain`,
|
|
].join('\n'),
|
|
});
|
|
const amount = parseFloat(req.amount) || 0;
|
|
if (amount > 0 && isBankExternal) {
|
|
const layers = record.request.moneyLayers;
|
|
const m4Journal = (0, settlement_core_1.resolveM4OutboundJournal)(layers, {
|
|
glM1: profile.settlement.glM1 ?? settlement_core_1.GL_CODES.M1_CIRCULATING,
|
|
glM2: profile.settlement.glM2 ?? settlement_core_1.GL_CODES.M2_BROAD,
|
|
glM4NearMoney: profile.settlement.glM4NearMoney ??
|
|
profile.settlement.glSettlement ??
|
|
settlement_core_1.GL_CODES.SETTLEMENT_SUSPENSE,
|
|
});
|
|
const debitGl = m4Journal?.debitGl ?? profile.settlement.glM2 ?? settlement_core_1.GL_CODES.M2_BROAD;
|
|
const creditGl = m4Journal?.creditGl ?? (profile.settlement.glSettlement ?? settlement_core_1.GL_CODES.SETTLEMENT_SUSPENSE);
|
|
const [debitGlId, creditGlId] = await Promise.all([
|
|
(0, fineract_1.resolveGlAccountId)(debitGl),
|
|
(0, fineract_1.resolveGlAccountId)(creditGl),
|
|
]);
|
|
const je = await (0, fineract_1.postJournalEntry)({
|
|
officeId,
|
|
transactionDate: now.slice(0, 10),
|
|
referenceNumber: req.idempotencyKey,
|
|
comments: `${req.rail} transfer ${req.tokenSymbol}`,
|
|
debits: [{ glAccountId: debitGlId, amount }],
|
|
credits: [{ glAccountId: creditGlId, amount }],
|
|
});
|
|
advance('FINERACT_POSTED', { fineractJournalRef: String(je.resourceId) });
|
|
}
|
|
else if (amount > 0 && !isCryptoExternal) {
|
|
const debitGl = profile.settlement.glM2 ?? settlement_core_1.GL_CODES.M2_BROAD;
|
|
const creditGl = profile.settlement.glM3 ?? settlement_core_1.GL_CODES.M3_TOKEN_LIABILITY;
|
|
const [debitGlId, creditGlId] = await Promise.all([
|
|
(0, fineract_1.resolveGlAccountId)(debitGl),
|
|
(0, fineract_1.resolveGlAccountId)(creditGl),
|
|
]);
|
|
const je = await (0, fineract_1.postJournalEntry)({
|
|
officeId,
|
|
transactionDate: now.slice(0, 10),
|
|
referenceNumber: req.idempotencyKey,
|
|
comments: `${req.rail} transfer ${req.tokenSymbol}`,
|
|
debits: [{ glAccountId: debitGlId, amount }],
|
|
credits: [{ glAccountId: creditGlId, amount }],
|
|
});
|
|
advance('FINERACT_POSTED', { fineractJournalRef: String(je.resourceId) });
|
|
}
|
|
else {
|
|
// Crypto external: liability already on M3; on-chain move is the egress — skip double M2→M3
|
|
advance('FINERACT_POSTED');
|
|
}
|
|
if (isBankExternal && cfg.rails.hybx.enabled) {
|
|
const hybx = new hybx_production_1.HybxProductionRail();
|
|
const pay = await hybx.dispatchPayment({
|
|
idempotencyKey: req.idempotencyKey,
|
|
amount: req.amount,
|
|
currency: req.currency ?? 'USD',
|
|
creditorIban: req.creditorIban,
|
|
beneficiaryName: req.beneficiaryName ?? req.recipientAddress,
|
|
remittanceInfo: req.remittanceInfo ?? `OMNL ${req.tokenSymbol} external transfer`,
|
|
rail: req.rail === 'RTGS' ? 'RTGS' : 'SWIFT',
|
|
});
|
|
advance('HYBX_RAIL_DISPATCHED', { hybxPaymentId: pay.paymentId });
|
|
}
|
|
else {
|
|
advance('HYBX_RAIL_DISPATCHED');
|
|
}
|
|
if (isBankExternal && cfg.rails.swift.enabled && req.creditorIban) {
|
|
const swiftRaw = (0, swift_1.buildMt103Stub)({
|
|
senderRef: req.idempotencyKey,
|
|
currency: req.currency ?? 'USD',
|
|
amount: req.amount,
|
|
valueDate: now.slice(0, 10),
|
|
orderingCustomer: cfg.omnlLegalName,
|
|
beneficiary: req.beneficiaryName ?? req.recipientAddress,
|
|
iban: req.creditorIban,
|
|
remittance: req.remittanceInfo ?? `OMNL ${req.tokenSymbol}`,
|
|
});
|
|
await (0, swift_1.forwardSwiftToListener)(swiftRaw);
|
|
const iso = await (0, omnl_1.archiveIso20022)({
|
|
messageType: 'pacs.008',
|
|
direction: 'OUTBOUND',
|
|
raw: swiftRaw,
|
|
settlementRef: record.settlementId,
|
|
});
|
|
advance('ISO20022_ARCHIVED', { iso20022MessageId: iso.messageId });
|
|
}
|
|
else {
|
|
advance('ISO20022_ARCHIVED');
|
|
}
|
|
const doChain = (req.rail === 'INTERNAL' || req.rail === 'CHAIN138' || isCryptoExternal) &&
|
|
Boolean(req.tokenAddress) &&
|
|
(0, btc_l1_settlement_1.allowChainMintExecute)();
|
|
if (doChain) {
|
|
const xfer = await (0, omnl_1.requestTokenTransfer)({
|
|
tokenAddress: req.tokenAddress,
|
|
amount: req.amount,
|
|
recipient: req.recipientAddress,
|
|
settlementRef: record.settlementId,
|
|
dryRun: false,
|
|
symbol: req.tokenSymbol,
|
|
});
|
|
advance('CHAIN_MINT_REQUESTED', {
|
|
chainTxHash: xfer.txHash,
|
|
verbiageDocument: [
|
|
record.verbiageDocument,
|
|
isCryptoExternal
|
|
? `EXTERNAL on-chain ERC-20 egress: ${req.tokenAddress} → ${req.recipientAddress}`
|
|
: `On-chain ERC-20 transfer: ${req.tokenAddress} → ${req.recipientAddress}`,
|
|
xfer.txHash ? `tx ${xfer.txHash}` : `status ${xfer.status}`,
|
|
].join('\n'),
|
|
});
|
|
}
|
|
else {
|
|
advance('CHAIN_MINT_REQUESTED', {
|
|
verbiageDocument: [
|
|
record.verbiageDocument,
|
|
isBankExternal
|
|
? 'Bank rail (SWIFT/IBAN) — no on-chain transfer'
|
|
: `On-chain ERC-20 transfer prepared (execute flag off): ${req.tokenAddress} → ${req.recipientAddress}`,
|
|
].join('\n'),
|
|
});
|
|
}
|
|
advance('SETTLED');
|
|
return record;
|
|
}
|
|
catch (err) {
|
|
record.errors.push(err instanceof Error ? err.message : String(err));
|
|
advance('FAILED');
|
|
return record;
|
|
}
|
|
}
|