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:
39
packages/settlement-core/dist/chain-registry.d.ts
vendored
Normal file
39
packages/settlement-core/dist/chain-registry.d.ts
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
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;
|
||||
};
|
||||
};
|
||||
export declare function resolveChainRegistryPath(): string;
|
||||
export declare function loadOmnlChainRegistry(): OmnlChainRegistry;
|
||||
export declare function getActiveChains(): OmnlChainEntry[];
|
||||
export declare function getChainById(chainId: number): OmnlChainEntry | undefined;
|
||||
export declare function getSupportedChainIds128(): number[];
|
||||
43
packages/settlement-core/dist/chain-registry.js
vendored
Normal file
43
packages/settlement-core/dist/chain-registry.js
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.resolveChainRegistryPath = resolveChainRegistryPath;
|
||||
exports.loadOmnlChainRegistry = loadOmnlChainRegistry;
|
||||
exports.getActiveChains = getActiveChains;
|
||||
exports.getChainById = getChainById;
|
||||
exports.getSupportedChainIds128 = getSupportedChainIds128;
|
||||
const fs_1 = __importDefault(require("fs"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
let cached = null;
|
||||
function resolveChainRegistryPath() {
|
||||
if (process.env.OMNL_SUPPORTED_CHAINS_CONFIG)
|
||||
return process.env.OMNL_SUPPORTED_CHAINS_CONFIG;
|
||||
const candidates = [
|
||||
path_1.default.resolve(process.cwd(), 'config/omnl-supported-chains.v1.json'),
|
||||
path_1.default.resolve(__dirname, '../../../config/omnl-supported-chains.v1.json'),
|
||||
path_1.default.resolve(__dirname, '../../../../config/omnl-supported-chains.v1.json'),
|
||||
];
|
||||
for (const p of candidates) {
|
||||
if (fs_1.default.existsSync(p))
|
||||
return p;
|
||||
}
|
||||
return candidates[1];
|
||||
}
|
||||
function loadOmnlChainRegistry() {
|
||||
if (cached)
|
||||
return cached;
|
||||
const p = resolveChainRegistryPath();
|
||||
cached = JSON.parse(fs_1.default.readFileSync(p, 'utf8'));
|
||||
return cached;
|
||||
}
|
||||
function getActiveChains() {
|
||||
return loadOmnlChainRegistry().chains.filter((c) => c.status === 'active');
|
||||
}
|
||||
function getChainById(chainId) {
|
||||
return loadOmnlChainRegistry().chains.find((c) => c.chainId === chainId);
|
||||
}
|
||||
function getSupportedChainIds128() {
|
||||
return loadOmnlChainRegistry().chains.map((c) => c.chainId);
|
||||
}
|
||||
7
packages/settlement-core/dist/index.d.ts
vendored
Normal file
7
packages/settlement-core/dist/index.d.ts
vendored
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';
|
||||
23
packages/settlement-core/dist/index.js
vendored
Normal file
23
packages/settlement-core/dist/index.js
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
||||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
__exportStar(require("./types"), exports);
|
||||
__exportStar(require("./money-supply"), exports);
|
||||
__exportStar(require("./instruments"), exports);
|
||||
__exportStar(require("./verbiage"), exports);
|
||||
__exportStar(require("./state-machine"), exports);
|
||||
__exportStar(require("./m2-token-registry"), exports);
|
||||
__exportStar(require("./chain-registry"), exports);
|
||||
13
packages/settlement-core/dist/instruments.d.ts
vendored
Normal file
13
packages/settlement-core/dist/instruments.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { SwiftMessageKind, TradeFinanceInstrument } from './types';
|
||||
/** SWIFT / trade-finance instrument routing */
|
||||
export declare const INSTRUMENT_ROUTING: Record<TradeFinanceInstrument, {
|
||||
swift: SwiftMessageKind[];
|
||||
glDebit: string;
|
||||
glCredit: string;
|
||||
}>;
|
||||
export declare function resolveInstrumentGl(instrument: TradeFinanceInstrument): {
|
||||
debitGl: string;
|
||||
creditGl: string;
|
||||
swiftKinds: SwiftMessageKind[];
|
||||
};
|
||||
export declare function isIbanValid(iban: string): boolean;
|
||||
28
packages/settlement-core/dist/instruments.js
vendored
Normal file
28
packages/settlement-core/dist/instruments.js
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.INSTRUMENT_ROUTING = void 0;
|
||||
exports.resolveInstrumentGl = resolveInstrumentGl;
|
||||
exports.isIbanValid = isIbanValid;
|
||||
/** SWIFT / trade-finance instrument routing */
|
||||
exports.INSTRUMENT_ROUTING = {
|
||||
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' },
|
||||
};
|
||||
function resolveInstrumentGl(instrument) {
|
||||
const r = exports.INSTRUMENT_ROUTING[instrument];
|
||||
return { debitGl: r.glDebit, creditGl: r.glCredit, swiftKinds: r.swift };
|
||||
}
|
||||
function isIbanValid(iban) {
|
||||
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;
|
||||
}
|
||||
44
packages/settlement-core/dist/m2-token-registry.d.ts
vendored
Normal file
44
packages/settlement-core/dist/m2-token-registry.d.ts
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { MoneyLayer } from './types';
|
||||
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[];
|
||||
};
|
||||
export declare function inferCurrencyCode(symbol: string): string;
|
||||
export declare function resolveOmnlLineM2(currencyCode?: string, symbol?: string): string;
|
||||
export declare function withM2Capabilities(token: {
|
||||
symbol: string;
|
||||
name: string;
|
||||
address: string;
|
||||
decimals: number;
|
||||
native?: boolean;
|
||||
currencyCode?: string;
|
||||
omnlLine?: string;
|
||||
}, overrides?: Partial<M2TokenCapabilities>): M2TokenCapabilities;
|
||||
export declare function mergeExchangeTokensWithM2Registry(baseTokens: {
|
||||
symbol: string;
|
||||
name: string;
|
||||
address: string;
|
||||
decimals: number;
|
||||
native?: boolean;
|
||||
omnlLine?: string;
|
||||
}[], registry: M2TokenRegistryFile): M2TokenCapabilities[];
|
||||
export declare function findM2TokenByAddress(registry: M2TokenRegistryFile, address: string): M2TokenCapabilities | undefined;
|
||||
export declare function findM2TokenBySymbol(registry: M2TokenRegistryFile, symbol: string): M2TokenCapabilities | undefined;
|
||||
86
packages/settlement-core/dist/m2-token-registry.js
vendored
Normal file
86
packages/settlement-core/dist/m2-token-registry.js
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.inferCurrencyCode = inferCurrencyCode;
|
||||
exports.resolveOmnlLineM2 = resolveOmnlLineM2;
|
||||
exports.withM2Capabilities = withM2Capabilities;
|
||||
exports.mergeExchangeTokensWithM2Registry = mergeExchangeTokensWithM2Registry;
|
||||
exports.findM2TokenByAddress = findM2TokenByAddress;
|
||||
exports.findM2TokenBySymbol = findM2TokenBySymbol;
|
||||
const money_supply_1 = require("./money-supply");
|
||||
const CURRENCY_FROM_SYMBOL = {
|
||||
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',
|
||||
};
|
||||
function inferCurrencyCode(symbol) {
|
||||
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';
|
||||
}
|
||||
function resolveOmnlLineM2(currencyCode, symbol) {
|
||||
const ccy = (currencyCode || inferCurrencyCode(symbol ?? 'USD')).toUpperCase();
|
||||
return `${ccy}-M2`;
|
||||
}
|
||||
function withM2Capabilities(token, overrides = {}) {
|
||||
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: money_supply_1.GL_CODES.M2_BROAD,
|
||||
swappable: true,
|
||||
convertible: true,
|
||||
transferableInternal: true,
|
||||
transferableExternal: true,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
function mergeExchangeTokensWithM2Registry(baseTokens, registry) {
|
||||
const byAddress = new Map();
|
||||
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));
|
||||
}
|
||||
function findM2TokenByAddress(registry, address) {
|
||||
const lower = address.toLowerCase();
|
||||
return registry.tokens.find((t) => t.address.toLowerCase() === lower);
|
||||
}
|
||||
function findM2TokenBySymbol(registry, symbol) {
|
||||
const s = symbol.trim().toLowerCase();
|
||||
return registry.tokens.find((t) => t.symbol.toLowerCase() === s);
|
||||
}
|
||||
26
packages/settlement-core/dist/money-supply.d.ts
vendored
Normal file
26
packages/settlement-core/dist/money-supply.d.ts
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { MoneyLayer, MoneySupplySnapshot } from './types';
|
||||
/** IPSAS GL mapping for OMNL central-bank money aggregates */
|
||||
export declare const GL_CODES: {
|
||||
readonly M0_RESERVE: "1050";
|
||||
readonly M1_LIABILITY: "2000";
|
||||
readonly M1_CIRCULATING: "2100";
|
||||
readonly M2_BROAD: "2200";
|
||||
readonly SETTLEMENT_SUSPENSE: "1000";
|
||||
readonly TRADE_FINANCE_CONTINGENT: "1410";
|
||||
readonly CRYPTO_TOKEN_LIABILITY: "2300";
|
||||
};
|
||||
/** Policy caps from hybx-omnl-policy (1.2× M0 reserve → M1 capacity; 5× M1 → M2 broad) */
|
||||
export declare const POLICY: {
|
||||
readonly M0_BACKING_MULTIPLIER: 1.2;
|
||||
readonly M1_CAP_MULTIPLIER: 5;
|
||||
readonly M2_META_FIAT_RAIL_CONVERSION: true;
|
||||
};
|
||||
export declare function aggregateMetaFiatLayers(layers: Partial<Record<MoneyLayer, string>>): {
|
||||
metaFiatTotal: string;
|
||||
breakdown: Record<MoneyLayer, string>;
|
||||
};
|
||||
export declare function buildMoneySupplySnapshot(officeId: number, balances: Partial<Record<string, string>>): MoneySupplySnapshot;
|
||||
export declare function m2FiatToTokenLoadAmount(fiatAmount: string, m2Available: string): {
|
||||
loadAmount: string;
|
||||
fullyBacked: boolean;
|
||||
};
|
||||
74
packages/settlement-core/dist/money-supply.js
vendored
Normal file
74
packages/settlement-core/dist/money-supply.js
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.POLICY = exports.GL_CODES = void 0;
|
||||
exports.aggregateMetaFiatLayers = aggregateMetaFiatLayers;
|
||||
exports.buildMoneySupplySnapshot = buildMoneySupplySnapshot;
|
||||
exports.m2FiatToTokenLoadAmount = m2FiatToTokenLoadAmount;
|
||||
/** IPSAS GL mapping for OMNL central-bank money aggregates */
|
||||
exports.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',
|
||||
};
|
||||
/** Policy caps from hybx-omnl-policy (1.2× M0 reserve → M1 capacity; 5× M1 → M2 broad) */
|
||||
exports.POLICY = {
|
||||
M0_BACKING_MULTIPLIER: 1.2,
|
||||
M1_CAP_MULTIPLIER: 5,
|
||||
M2_META_FIAT_RAIL_CONVERSION: true,
|
||||
};
|
||||
function aggregateMetaFiatLayers(layers) {
|
||||
const breakdown = {
|
||||
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 };
|
||||
}
|
||||
function buildMoneySupplySnapshot(officeId, balances) {
|
||||
const m0 = balances[exports.GL_CODES.M0_RESERVE] ?? '0';
|
||||
const m1 = balances[exports.GL_CODES.M1_CIRCULATING] ?? '0';
|
||||
const m2 = balances[exports.GL_CODES.M2_BROAD] ?? '0';
|
||||
const pending = balances[exports.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: exports.POLICY.M0_BACKING_MULTIPLIER,
|
||||
},
|
||||
M1: {
|
||||
circulating: m1,
|
||||
gl2000: balances[exports.GL_CODES.M1_LIABILITY] ?? m1,
|
||||
gl2100: m1,
|
||||
capMultiple: exports.POLICY.M1_CAP_MULTIPLIER,
|
||||
},
|
||||
M2: {
|
||||
broadMoney: m2,
|
||||
gl2200: m2,
|
||||
metaFiatPending: pending,
|
||||
},
|
||||
metaFiatRealRail: {
|
||||
pendingSettlement: pending,
|
||||
inFlightSwift: balances['inFlightSwift'] ?? '0',
|
||||
},
|
||||
};
|
||||
}
|
||||
function m2FiatToTokenLoadAmount(fiatAmount, m2Available) {
|
||||
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,
|
||||
};
|
||||
}
|
||||
3
packages/settlement-core/dist/state-machine.d.ts
vendored
Normal file
3
packages/settlement-core/dist/state-machine.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import type { SettlementPhase } from './types';
|
||||
export declare function nextPhase(current: SettlementPhase): SettlementPhase | null;
|
||||
export declare function canAdvance(from: SettlementPhase, to: SettlementPhase): boolean;
|
||||
29
packages/settlement-core/dist/state-machine.js
vendored
Normal file
29
packages/settlement-core/dist/state-machine.js
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.nextPhase = nextPhase;
|
||||
exports.canAdvance = canAdvance;
|
||||
const ORDER = [
|
||||
'RECEIVED',
|
||||
'VALIDATED',
|
||||
'VERBIAGE_ROLLED',
|
||||
'FINERACT_POSTED',
|
||||
'HYBX_RAIL_DISPATCHED',
|
||||
'ISO20022_ARCHIVED',
|
||||
'CHAIN_MINT_REQUESTED',
|
||||
'SETTLED',
|
||||
];
|
||||
function nextPhase(current) {
|
||||
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];
|
||||
}
|
||||
function canAdvance(from, to) {
|
||||
const fi = ORDER.indexOf(from);
|
||||
const ti = ORDER.indexOf(to);
|
||||
if (fi < 0 || ti < 0)
|
||||
return to === 'FAILED';
|
||||
return ti === fi + 1 || to === 'FAILED';
|
||||
}
|
||||
102
packages/settlement-core/dist/types.d.ts
vendored
Normal file
102
packages/settlement-core/dist/types.d.ts
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
/** 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;
|
||||
};
|
||||
};
|
||||
3
packages/settlement-core/dist/types.js
vendored
Normal file
3
packages/settlement-core/dist/types.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
/** OMNL central-bank settlement domain types */
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
6
packages/settlement-core/dist/verbiage.d.ts
vendored
Normal file
6
packages/settlement-core/dist/verbiage.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { ExternalTransferRequest, SwiftMessageKind } from './types';
|
||||
/** OMNL central-bank external transfer verbiage roll (audit + operator copy) */
|
||||
export declare function rollExternalTransferVerbiage(req: ExternalTransferRequest): string;
|
||||
export declare function buildMt103Narrative(req: ExternalTransferRequest): string;
|
||||
export declare function buildMt102Narrative(req: ExternalTransferRequest): string;
|
||||
export declare function swiftKindForRequest(req: ExternalTransferRequest): SwiftMessageKind;
|
||||
54
packages/settlement-core/dist/verbiage.js
vendored
Normal file
54
packages/settlement-core/dist/verbiage.js
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.rollExternalTransferVerbiage = rollExternalTransferVerbiage;
|
||||
exports.buildMt103Narrative = buildMt103Narrative;
|
||||
exports.buildMt102Narrative = buildMt102Narrative;
|
||||
exports.swiftKindForRequest = swiftKindForRequest;
|
||||
/** OMNL central-bank external transfer verbiage roll (audit + operator copy) */
|
||||
function rollExternalTransferVerbiage(req) {
|
||||
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');
|
||||
}
|
||||
function buildMt103Narrative(req) {
|
||||
return `/BNF/${req.beneficiaryName}//${req.remittanceInfo}`.slice(0, 140);
|
||||
}
|
||||
function buildMt102Narrative(req) {
|
||||
const tf = req.tradeFinance ? `/${req.tradeFinance.instrument}/${req.tradeFinance.reference}` : '';
|
||||
return `/BNF/${req.beneficiaryName}${tf}//${req.remittanceInfo}`.slice(0, 140);
|
||||
}
|
||||
function swiftKindForRequest(req) {
|
||||
if (req.swiftMessageKind)
|
||||
return req.swiftMessageKind;
|
||||
if (req.tradeFinance?.instrument === 'LS' || req.tradeFinance?.instrument === 'DLC') {
|
||||
return 'MT102';
|
||||
}
|
||||
return 'MT103';
|
||||
}
|
||||
47
packages/settlement-core/package-lock.json
generated
Normal file
47
packages/settlement-core/package-lock.json
generated
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@dbis/settlement-core",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@dbis/settlement-core",
|
||||
"version": "0.1.0",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"typescript": "^5.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.43",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
|
||||
"integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
17
packages/settlement-core/package.json
Normal file
17
packages/settlement-core/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@dbis/settlement-core",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "OMNL central-bank settlement: M0/M1/M2 money supply, SWIFT rails, trade-finance instruments, verbiage",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": ["dist"],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "node --test dist/**/*.test.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"typescript": "^5.4.0"
|
||||
}
|
||||
}
|
||||
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';
|
||||
}
|
||||
13
packages/settlement-core/tsconfig.json
Normal file
13
packages/settlement-core/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user