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
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:
67
packages/settlement-core/src/chain-registry.ts
Normal file
67
packages/settlement-core/src/chain-registry.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export type OmnlChainEntry = {
|
||||
chainId: number;
|
||||
name: string;
|
||||
type: 'evm' | 'non-evm';
|
||||
nativeSymbol: string;
|
||||
tier: string;
|
||||
omnlRole: string;
|
||||
status: 'active' | 'staged' | 'reserved';
|
||||
rpcEnv: string;
|
||||
explorerUrl?: string;
|
||||
rpcDefault?: string;
|
||||
settlement: {
|
||||
m2LoadEnabled: boolean;
|
||||
swapEnabled: boolean;
|
||||
bridgeEnabled: boolean;
|
||||
officeId: number;
|
||||
};
|
||||
complianceEnv?: string;
|
||||
};
|
||||
|
||||
export type OmnlChainRegistry = {
|
||||
version: string;
|
||||
brand: string;
|
||||
maxCapacity: number;
|
||||
primaryChainId: number;
|
||||
mirrorChainId: number;
|
||||
settlementOfficeId: number;
|
||||
chains: OmnlChainEntry[];
|
||||
stats?: { total: number; active: number; staged: number; reserved: number };
|
||||
};
|
||||
|
||||
let cached: OmnlChainRegistry | null = null;
|
||||
|
||||
export function resolveChainRegistryPath(): string {
|
||||
if (process.env.OMNL_SUPPORTED_CHAINS_CONFIG) return process.env.OMNL_SUPPORTED_CHAINS_CONFIG;
|
||||
const candidates = [
|
||||
path.resolve(process.cwd(), 'config/omnl-supported-chains.v1.json'),
|
||||
path.resolve(__dirname, '../../../config/omnl-supported-chains.v1.json'),
|
||||
path.resolve(__dirname, '../../../../config/omnl-supported-chains.v1.json'),
|
||||
];
|
||||
for (const p of candidates) {
|
||||
if (fs.existsSync(p)) return p;
|
||||
}
|
||||
return candidates[1];
|
||||
}
|
||||
|
||||
export function loadOmnlChainRegistry(): OmnlChainRegistry {
|
||||
if (cached) return cached;
|
||||
const p = resolveChainRegistryPath();
|
||||
cached = JSON.parse(fs.readFileSync(p, 'utf8')) as OmnlChainRegistry;
|
||||
return cached;
|
||||
}
|
||||
|
||||
export function getActiveChains(): OmnlChainEntry[] {
|
||||
return loadOmnlChainRegistry().chains.filter((c) => c.status === 'active');
|
||||
}
|
||||
|
||||
export function getChainById(chainId: number): OmnlChainEntry | undefined {
|
||||
return loadOmnlChainRegistry().chains.find((c) => c.chainId === chainId);
|
||||
}
|
||||
|
||||
export function getSupportedChainIds128(): number[] {
|
||||
return loadOmnlChainRegistry().chains.map((c) => c.chainId);
|
||||
}
|
||||
7
packages/settlement-core/src/index.ts
Normal file
7
packages/settlement-core/src/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export * from './types';
|
||||
export * from './money-supply';
|
||||
export * from './instruments';
|
||||
export * from './verbiage';
|
||||
export * from './state-machine';
|
||||
export * from './m2-token-registry';
|
||||
export * from './chain-registry';
|
||||
31
packages/settlement-core/src/instruments.ts
Normal file
31
packages/settlement-core/src/instruments.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { SwiftMessageKind, TradeFinanceInstrument } from './types';
|
||||
|
||||
/** SWIFT / trade-finance instrument routing */
|
||||
export const INSTRUMENT_ROUTING: Record<
|
||||
TradeFinanceInstrument,
|
||||
{ swift: SwiftMessageKind[]; glDebit: string; glCredit: string }
|
||||
> = {
|
||||
SBLC: { swift: ['MT103', 'MT202'], glDebit: '1410', glCredit: '2100' },
|
||||
DLC: { swift: ['MT103', 'MT102'], glDebit: '1410', glCredit: '1000' },
|
||||
BG: { swift: ['MT103'], glDebit: '1410', glCredit: '2100' },
|
||||
LS: { swift: ['MT102', 'MT103'], glDebit: '1000', glCredit: '2200' },
|
||||
};
|
||||
|
||||
export function resolveInstrumentGl(
|
||||
instrument: TradeFinanceInstrument,
|
||||
): { debitGl: string; creditGl: string; swiftKinds: SwiftMessageKind[] } {
|
||||
const r = INSTRUMENT_ROUTING[instrument];
|
||||
return { debitGl: r.glDebit, creditGl: r.glCredit, swiftKinds: r.swift };
|
||||
}
|
||||
|
||||
export function isIbanValid(iban: string): boolean {
|
||||
const normalized = iban.replace(/\s/g, '').toUpperCase();
|
||||
if (!/^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$/.test(normalized)) return false;
|
||||
const rearranged = normalized.slice(4) + normalized.slice(0, 4);
|
||||
const numeric = rearranged.replace(/[A-Z]/g, (c) => String(c.charCodeAt(0) - 55));
|
||||
let remainder = 0;
|
||||
for (const ch of numeric) {
|
||||
remainder = (remainder * 10 + parseInt(ch, 10)) % 97;
|
||||
}
|
||||
return remainder === 1;
|
||||
}
|
||||
135
packages/settlement-core/src/m2-token-registry.ts
Normal file
135
packages/settlement-core/src/m2-token-registry.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import type { MoneyLayer } from './types';
|
||||
import { GL_CODES } from './money-supply';
|
||||
|
||||
export type M2TokenCapabilities = {
|
||||
symbol: string;
|
||||
name: string;
|
||||
address: string;
|
||||
decimals: number;
|
||||
native?: boolean;
|
||||
currencyCode?: string;
|
||||
moneyLayer: MoneyLayer;
|
||||
omnlLine: string;
|
||||
loadFromGl: string;
|
||||
swappable: boolean;
|
||||
convertible: boolean;
|
||||
transferableInternal: boolean;
|
||||
transferableExternal: boolean;
|
||||
};
|
||||
|
||||
export type M2TokenRegistryFile = {
|
||||
version: string;
|
||||
chainId: number;
|
||||
defaultMoneyLayer: MoneyLayer;
|
||||
defaultLoadFromGl: string;
|
||||
tokens: M2TokenCapabilities[];
|
||||
};
|
||||
|
||||
const CURRENCY_FROM_SYMBOL: Record<string, string> = {
|
||||
USD: 'USD',
|
||||
EUR: 'EUR',
|
||||
GBP: 'GBP',
|
||||
AUD: 'AUD',
|
||||
JPY: 'JPY',
|
||||
CHF: 'CHF',
|
||||
CAD: 'CAD',
|
||||
XAU: 'XAU',
|
||||
BTC: 'BTC',
|
||||
ETH: 'ETH',
|
||||
BNB: 'BNB',
|
||||
POL: 'POL',
|
||||
AVAX: 'AVAX',
|
||||
CRO: 'CRO',
|
||||
XDAI: 'XDAI',
|
||||
CELO: 'CELO',
|
||||
WEMIX: 'WEMIX',
|
||||
};
|
||||
|
||||
export function inferCurrencyCode(symbol: string): string {
|
||||
const s = symbol.toUpperCase();
|
||||
for (const [key, code] of Object.entries(CURRENCY_FROM_SYMBOL)) {
|
||||
if (s.includes(key)) return code;
|
||||
}
|
||||
if (s.startsWith('C') && s.length > 1) {
|
||||
for (const [key, code] of Object.entries(CURRENCY_FROM_SYMBOL)) {
|
||||
if (s.slice(1).includes(key)) return code;
|
||||
}
|
||||
}
|
||||
return 'USD';
|
||||
}
|
||||
|
||||
export function resolveOmnlLineM2(currencyCode?: string, symbol?: string): string {
|
||||
const ccy = (currencyCode || inferCurrencyCode(symbol ?? 'USD')).toUpperCase();
|
||||
return `${ccy}-M2`;
|
||||
}
|
||||
|
||||
export function withM2Capabilities(
|
||||
token: {
|
||||
symbol: string;
|
||||
name: string;
|
||||
address: string;
|
||||
decimals: number;
|
||||
native?: boolean;
|
||||
currencyCode?: string;
|
||||
omnlLine?: string;
|
||||
},
|
||||
overrides: Partial<M2TokenCapabilities> = {},
|
||||
): M2TokenCapabilities {
|
||||
const currencyCode = token.currencyCode ?? inferCurrencyCode(token.symbol);
|
||||
return {
|
||||
symbol: token.symbol,
|
||||
name: token.name,
|
||||
address: token.address,
|
||||
decimals: token.decimals,
|
||||
native: token.native,
|
||||
currencyCode,
|
||||
moneyLayer: 'M2',
|
||||
omnlLine: token.omnlLine ?? resolveOmnlLineM2(currencyCode, token.symbol),
|
||||
loadFromGl: GL_CODES.M2_BROAD,
|
||||
swappable: true,
|
||||
convertible: true,
|
||||
transferableInternal: true,
|
||||
transferableExternal: true,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function mergeExchangeTokensWithM2Registry(
|
||||
baseTokens: {
|
||||
symbol: string;
|
||||
name: string;
|
||||
address: string;
|
||||
decimals: number;
|
||||
native?: boolean;
|
||||
omnlLine?: string;
|
||||
}[],
|
||||
registry: M2TokenRegistryFile,
|
||||
): M2TokenCapabilities[] {
|
||||
const byAddress = new Map<string, M2TokenCapabilities>();
|
||||
for (const t of registry.tokens) {
|
||||
byAddress.set(t.address.toLowerCase(), t);
|
||||
}
|
||||
for (const t of baseTokens) {
|
||||
const key = t.address.toLowerCase();
|
||||
if (!byAddress.has(key)) {
|
||||
byAddress.set(key, withM2Capabilities(t));
|
||||
}
|
||||
}
|
||||
return Array.from(byAddress.values()).sort((a, b) => a.symbol.localeCompare(b.symbol));
|
||||
}
|
||||
|
||||
export function findM2TokenByAddress(
|
||||
registry: M2TokenRegistryFile,
|
||||
address: string,
|
||||
): M2TokenCapabilities | undefined {
|
||||
const lower = address.toLowerCase();
|
||||
return registry.tokens.find((t) => t.address.toLowerCase() === lower);
|
||||
}
|
||||
|
||||
export function findM2TokenBySymbol(
|
||||
registry: M2TokenRegistryFile,
|
||||
symbol: string,
|
||||
): M2TokenCapabilities | undefined {
|
||||
const s = symbol.trim().toLowerCase();
|
||||
return registry.tokens.find((t) => t.symbol.toLowerCase() === s);
|
||||
}
|
||||
82
packages/settlement-core/src/money-supply.ts
Normal file
82
packages/settlement-core/src/money-supply.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type { MoneyLayer, MoneySupplySnapshot } from './types';
|
||||
|
||||
/** IPSAS GL mapping for OMNL central-bank money aggregates */
|
||||
export const GL_CODES = {
|
||||
M0_RESERVE: '1050',
|
||||
M1_LIABILITY: '2000',
|
||||
M1_CIRCULATING: '2100',
|
||||
M2_BROAD: '2200',
|
||||
SETTLEMENT_SUSPENSE: '1000',
|
||||
TRADE_FINANCE_CONTINGENT: '1410',
|
||||
CRYPTO_TOKEN_LIABILITY: '2300',
|
||||
} as const;
|
||||
|
||||
/** Policy caps from hybx-omnl-policy (1.2× M0 reserve → M1 capacity; 5× M1 → M2 broad) */
|
||||
export const POLICY = {
|
||||
M0_BACKING_MULTIPLIER: 1.2,
|
||||
M1_CAP_MULTIPLIER: 5,
|
||||
M2_META_FIAT_RAIL_CONVERSION: true,
|
||||
} as const;
|
||||
|
||||
export function aggregateMetaFiatLayers(
|
||||
layers: Partial<Record<MoneyLayer, string>>,
|
||||
): { metaFiatTotal: string; breakdown: Record<MoneyLayer, string> } {
|
||||
const breakdown: Record<MoneyLayer, string> = {
|
||||
M0: layers.M0 ?? '0',
|
||||
M1: layers.M1 ?? '0',
|
||||
M2: layers.M2 ?? '0',
|
||||
M00: layers.M00 ?? '0',
|
||||
};
|
||||
const sum = [breakdown.M0, breakdown.M1, breakdown.M2]
|
||||
.map((v) => parseFloat(v) || 0)
|
||||
.reduce((a, b) => a + b, 0);
|
||||
return { metaFiatTotal: sum.toFixed(2), breakdown };
|
||||
}
|
||||
|
||||
export function buildMoneySupplySnapshot(
|
||||
officeId: number,
|
||||
balances: Partial<Record<string, string>>,
|
||||
): MoneySupplySnapshot {
|
||||
const m0 = balances[GL_CODES.M0_RESERVE] ?? '0';
|
||||
const m1 = balances[GL_CODES.M1_CIRCULATING] ?? '0';
|
||||
const m2 = balances[GL_CODES.M2_BROAD] ?? '0';
|
||||
const pending = balances[GL_CODES.SETTLEMENT_SUSPENSE] ?? '0';
|
||||
const m0Num = parseFloat(m0) || 0;
|
||||
const m1Num = parseFloat(m1) || 0;
|
||||
return {
|
||||
asOf: new Date().toISOString(),
|
||||
officeId,
|
||||
M0: {
|
||||
gl1050: m0,
|
||||
backingRatio: POLICY.M0_BACKING_MULTIPLIER,
|
||||
},
|
||||
M1: {
|
||||
circulating: m1,
|
||||
gl2000: balances[GL_CODES.M1_LIABILITY] ?? m1,
|
||||
gl2100: m1,
|
||||
capMultiple: POLICY.M1_CAP_MULTIPLIER,
|
||||
},
|
||||
M2: {
|
||||
broadMoney: m2,
|
||||
gl2200: m2,
|
||||
metaFiatPending: pending,
|
||||
},
|
||||
metaFiatRealRail: {
|
||||
pendingSettlement: pending,
|
||||
inFlightSwift: balances['inFlightSwift'] ?? '0',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function m2FiatToTokenLoadAmount(fiatAmount: string, m2Available: string): {
|
||||
loadAmount: string;
|
||||
fullyBacked: boolean;
|
||||
} {
|
||||
const fiat = parseFloat(fiatAmount) || 0;
|
||||
const m2 = parseFloat(m2Available) || 0;
|
||||
const load = Math.min(fiat, m2);
|
||||
return {
|
||||
loadAmount: load.toFixed(2),
|
||||
fullyBacked: load >= fiat && fiat > 0,
|
||||
};
|
||||
}
|
||||
26
packages/settlement-core/src/state-machine.ts
Normal file
26
packages/settlement-core/src/state-machine.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { SettlementPhase } from './types';
|
||||
|
||||
const ORDER: SettlementPhase[] = [
|
||||
'RECEIVED',
|
||||
'VALIDATED',
|
||||
'VERBIAGE_ROLLED',
|
||||
'FINERACT_POSTED',
|
||||
'HYBX_RAIL_DISPATCHED',
|
||||
'ISO20022_ARCHIVED',
|
||||
'CHAIN_MINT_REQUESTED',
|
||||
'SETTLED',
|
||||
];
|
||||
|
||||
export function nextPhase(current: SettlementPhase): SettlementPhase | null {
|
||||
if (current === 'FAILED' || current === 'SETTLED') return null;
|
||||
const idx = ORDER.indexOf(current);
|
||||
if (idx < 0 || idx >= ORDER.length - 1) return null;
|
||||
return ORDER[idx + 1];
|
||||
}
|
||||
|
||||
export function canAdvance(from: SettlementPhase, to: SettlementPhase): boolean {
|
||||
const fi = ORDER.indexOf(from);
|
||||
const ti = ORDER.indexOf(to);
|
||||
if (fi < 0 || ti < 0) return to === 'FAILED';
|
||||
return ti === fi + 1 || to === 'FAILED';
|
||||
}
|
||||
113
packages/settlement-core/src/types.ts
Normal file
113
packages/settlement-core/src/types.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/** OMNL central-bank settlement domain types */
|
||||
|
||||
export type MoneyLayer = 'M0' | 'M1' | 'M2' | 'M00';
|
||||
|
||||
export type PaymentRail =
|
||||
| 'SWIFT'
|
||||
| 'SEPA'
|
||||
| 'TARGET2'
|
||||
| 'RTGS'
|
||||
| 'ACH'
|
||||
| 'FEDWIRE'
|
||||
| 'INTERNAL'
|
||||
| 'CHAIN138';
|
||||
|
||||
export type TradeFinanceInstrument = 'SBLC' | 'DLC' | 'BG' | 'LS';
|
||||
|
||||
export type SwiftMessageKind = 'MT103' | 'MT102' | 'MT202' | 'MT910';
|
||||
|
||||
export type SettlementPhase =
|
||||
| 'RECEIVED'
|
||||
| 'VALIDATED'
|
||||
| 'VERBIAGE_ROLLED'
|
||||
| 'FINERACT_POSTED'
|
||||
| 'HYBX_RAIL_DISPATCHED'
|
||||
| 'ISO20022_ARCHIVED'
|
||||
| 'CHAIN_MINT_REQUESTED'
|
||||
| 'SETTLED'
|
||||
| 'FAILED';
|
||||
|
||||
export type ExternalTransferRequest = {
|
||||
idempotencyKey: string;
|
||||
officeId: number;
|
||||
valueDate: string;
|
||||
currency: string;
|
||||
amount: string;
|
||||
debtorIban?: string;
|
||||
creditorIban: string;
|
||||
creditorBic?: string;
|
||||
beneficiaryName: string;
|
||||
orderingName?: string;
|
||||
remittanceInfo: string;
|
||||
moneyLayers: MoneyLayer[];
|
||||
rail: PaymentRail;
|
||||
swiftMessageKind?: SwiftMessageKind;
|
||||
tradeFinance?: {
|
||||
instrument: TradeFinanceInstrument;
|
||||
reference: string;
|
||||
expiryDate?: string;
|
||||
};
|
||||
rwaVerbiage?: {
|
||||
assetClass: string;
|
||||
tokenLineId: string;
|
||||
externalTransferRoll: boolean;
|
||||
};
|
||||
convertToCrypto?: {
|
||||
enabled: boolean;
|
||||
targetChainId: number;
|
||||
tokenLineId: string;
|
||||
recipientAddress: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TokenLoadRequest = {
|
||||
idempotencyKey: string;
|
||||
officeId?: number;
|
||||
lineId: string;
|
||||
symbol?: string;
|
||||
tokenAddress?: string;
|
||||
amount: string;
|
||||
recipientAddress: string;
|
||||
currency?: string;
|
||||
};
|
||||
|
||||
export type TransferRequest = {
|
||||
idempotencyKey: string;
|
||||
officeId?: number;
|
||||
tokenAddress: string;
|
||||
tokenSymbol: string;
|
||||
amount: string;
|
||||
recipientAddress: string;
|
||||
senderAddress?: string;
|
||||
moneyLayers: MoneyLayer[];
|
||||
rail: 'INTERNAL' | 'CHAIN138' | 'SWIFT' | 'RTGS';
|
||||
/** External SWIFT/HYBX beneficiary IBAN when rail is external */
|
||||
creditorIban?: string;
|
||||
beneficiaryName?: string;
|
||||
remittanceInfo?: string;
|
||||
currency?: string;
|
||||
};
|
||||
|
||||
export type SettlementRecord = {
|
||||
settlementId: string;
|
||||
idempotencyKey: string;
|
||||
phase: SettlementPhase;
|
||||
request: ExternalTransferRequest;
|
||||
fineractJournalRef?: string;
|
||||
hybxPaymentId?: string;
|
||||
iso20022MessageId?: string;
|
||||
chainTxHash?: string;
|
||||
verbiageDocument?: string;
|
||||
errors: string[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type MoneySupplySnapshot = {
|
||||
asOf: string;
|
||||
officeId: number;
|
||||
M0: { reserveOz?: string; gl1050: string; backingRatio: number };
|
||||
M1: { circulating: string; gl2000: string; gl2100: string; capMultiple: number };
|
||||
M2: { broadMoney: string; gl2200: string; metaFiatPending: string };
|
||||
metaFiatRealRail: { pendingSettlement: string; inFlightSwift: string };
|
||||
};
|
||||
58
packages/settlement-core/src/verbiage.ts
Normal file
58
packages/settlement-core/src/verbiage.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import type { ExternalTransferRequest, SwiftMessageKind, TradeFinanceInstrument } from './types';
|
||||
|
||||
/** OMNL central-bank external transfer verbiage roll (audit + operator copy) */
|
||||
export function rollExternalTransferVerbiage(req: ExternalTransferRequest): string {
|
||||
const lines = [
|
||||
'=== OMNL CENTRAL BANK — EXTERNAL TRANSFER VERBIAGE ROLL ===',
|
||||
`Settlement ref: ${req.idempotencyKey}`,
|
||||
`Value date: ${req.valueDate}`,
|
||||
`Amount: ${req.amount} ${req.currency}`,
|
||||
`Rail: ${req.rail}`,
|
||||
`Money layers: ${req.moneyLayers.join(' + ')}`,
|
||||
`Beneficiary: ${req.beneficiaryName}`,
|
||||
`Creditor IBAN: ${req.creditorIban}`,
|
||||
];
|
||||
if (req.creditorBic) lines.push(`Creditor BIC: ${req.creditorBic}`);
|
||||
if (req.debtorIban) lines.push(`Debtor IBAN: ${req.debtorIban}`);
|
||||
if (req.orderingName) lines.push(`Ordering party: ${req.orderingName}`);
|
||||
lines.push(`Remittance: ${req.remittanceInfo}`);
|
||||
if (req.swiftMessageKind) lines.push(`SWIFT message: ${req.swiftMessageKind}`);
|
||||
if (req.tradeFinance) {
|
||||
lines.push(
|
||||
`Trade finance: ${req.tradeFinance.instrument} ref ${req.tradeFinance.reference}`,
|
||||
);
|
||||
}
|
||||
if (req.rwaVerbiage?.externalTransferRoll) {
|
||||
lines.push(
|
||||
`RWA roll: ${req.rwaVerbiage.assetClass} → line ${req.rwaVerbiage.tokenLineId}`,
|
||||
);
|
||||
}
|
||||
if (req.convertToCrypto?.enabled) {
|
||||
lines.push(
|
||||
`Post-settlement crypto conversion: chain ${req.convertToCrypto.targetChainId} → ${req.convertToCrypto.recipientAddress}`,
|
||||
);
|
||||
}
|
||||
lines.push(
|
||||
'',
|
||||
'DISCLAIMER: Internal OMNL operator verbiage — not SWIFT NET transmission.',
|
||||
'Fineract SoR + ISO 20022 archive bind settlement finality.',
|
||||
);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function buildMt103Narrative(req: ExternalTransferRequest): string {
|
||||
return `/BNF/${req.beneficiaryName}//${req.remittanceInfo}`.slice(0, 140);
|
||||
}
|
||||
|
||||
export function buildMt102Narrative(req: ExternalTransferRequest): string {
|
||||
const tf = req.tradeFinance ? `/${req.tradeFinance.instrument}/${req.tradeFinance.reference}` : '';
|
||||
return `/BNF/${req.beneficiaryName}${tf}//${req.remittanceInfo}`.slice(0, 140);
|
||||
}
|
||||
|
||||
export function swiftKindForRequest(req: ExternalTransferRequest): SwiftMessageKind {
|
||||
if (req.swiftMessageKind) return req.swiftMessageKind;
|
||||
if (req.tradeFinance?.instrument === 'LS' || req.tradeFinance?.instrument === 'DLC') {
|
||||
return 'MT102';
|
||||
}
|
||||
return 'MT103';
|
||||
}
|
||||
Reference in New Issue
Block a user