Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m13s
CI/CD Pipeline / Lint and Format (push) Has been cancelled
CI/CD Pipeline / Security Scanning (push) Has been cancelled
CI/CD Pipeline / Terraform Validation (push) Has been cancelled
CI/CD Pipeline / Kubernetes Validation (push) Has been cancelled
Deploy ChainID 138 / Deploy ChainID 138 (push) Has been cancelled
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (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
Verify Deployment / Verify Deployment (push) Has been cancelled
Exempt dashboard read APIs from anonymous rate limits, preserve nginx-injected auth, wire chain-mint and DB env through deploy/LXC scripts, resolve M3 token addresses from registry, and harden super-admin seeding. Co-authored-by: Cursor <cursoragent@cursor.com>
147 lines
5.2 KiB
TypeScript
147 lines
5.2 KiB
TypeScript
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<SettlementRecord> {
|
|
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<SettlementRecord> = {}) => {
|
|
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;
|
|
}
|
|
}
|