feat(settlement): production BTC L1 rails, hot cBTC liquidity, Dfns/Cobo smoke
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m3s
CI/CD Pipeline / Security Scanning (push) Successful in 2m58s
CI/CD Pipeline / Lint and Format (push) Failing after 46s
CI/CD Pipeline / Terraform Validation (push) Failing after 26s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 29s
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 41s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 22s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 24s
Validation / validate-genesis (push) Successful in 32s
Validation / validate-terraform (push) Failing after 28s
Validation / validate-kubernetes (push) Failing after 13s
Validation / validate-smart-contracts (push) Failing after 12s
Validation / validate-security (push) Failing after 1m31s
Validation / validate-documentation (push) Failing after 23s
Verify Deployment / Verify Deployment (push) Failing after 1m4s

Add BTC L1 settle/reconcile/ledger APIs, bitcoind intake, cBTC PMM hot-LP scripts, and custody credential smoke tests (secrets stay gitignored). Enables full-prod local green health and server pull-deploy for secure.omdnl.org /btc/*.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-19 09:26:45 -07:00
parent 286b2a8915
commit 5a9889cace
68 changed files with 7558 additions and 16 deletions

View File

@@ -0,0 +1,71 @@
/**
* Unit tests for BTC L1 settlement helpers (pricing gate + amount math).
* Run via: npm run build && npm test (node --test)
*/
import { describe, it, mock } from 'node:test';
import assert from 'node:assert/strict';
import {
btcUsdToJournalAmount,
satsToBtcDecimal,
} from '@dbis/settlement-core';
describe('btc-l1-settlement amounts', () => {
it('mints cBTC amount from sats at 8 decimals', () => {
assert.equal(satsToBtcDecimal(25_000_000), '0.25000000');
});
it('posts USD fair value = sats × rate', () => {
// 0.5 BTC @ 92_500 = 46_250.00
assert.equal(btcUsdToJournalAmount(50_000_000, 92_500), '46250.00');
});
});
describe('pricing gate reject static fallback', () => {
it('flags 90000 as suspicious when live required', () => {
const rate = 90000;
const requireLive = true;
const envOverride = Number('');
const isSuspicious =
requireLive &&
Math.abs(rate - 90000) < 0.01 &&
!(envOverride > 0 && Math.abs(envOverride - 90000) < 0.01);
assert.equal(isSuspicious, true);
});
it('allows non-fallback live rates', () => {
const rate = 97_432.55;
const requireLive = true;
const isSuspicious = requireLive && Math.abs(rate - 90000) < 0.01;
assert.equal(isSuspicious, false);
});
});
describe('idempotency key shape', () => {
it('derives stable key from txId + instructionId', () => {
const txId = 'deadbeef';
const instructionId = 'inst-42';
const key = `btc-l1-${txId}-${instructionId}`;
assert.equal(key, 'btc-l1-deadbeef-inst-42');
});
});
describe('allowChainMintExecute env override', () => {
it('treats SETTLEMENT_ALLOW_CHAIN_MINT_EXECUTE=0 as dry-run', () => {
const env: string = '0';
const cfgAllow = true;
const allow =
env === '0' || env === 'false' ? false : env === '1' || env === 'true' ? true : cfgAllow;
assert.equal(allow, false);
});
it('treats SETTLEMENT_ALLOW_CHAIN_MINT_EXECUTE=1 as execute', () => {
const env: string = '1';
const cfgAllow = false;
const allow =
env === '0' || env === 'false' ? false : env === '1' || env === 'true' ? true : cfgAllow;
assert.equal(allow, true);
});
});
// silence unused mock import in older node
void mock;

View File

@@ -0,0 +1,317 @@
import { v4 as uuidv4 } from 'uuid';
import axios from 'axios';
import {
buildMoneySupplySnapshot,
btcUsdToJournalAmount,
GL_CODES,
m2FiatToTokenLoadAmount,
m3TokenLoadAmount,
resolveBtcL1CustodyJournal,
satsToBtcDecimal,
type BtcL1ReconcileRequest,
type BtcL1SettlementRequest,
type SettlementRecord,
} from '@dbis/settlement-core';
import { loadConfig, loadOfficeProfile, settlementOfficeId } from '../config';
import {
fetchGlBalances,
postJournalEntry,
resolveBtcGlAccountIds,
resolveGlAccountId,
} from '../adapters/fineract';
import { requestTokenMint } from '../adapters/omnl';
import { resolveLiveBtcUsd } from '../adapters/pricing';
import { settlementStore } from '../store/settlement-store';
const MIN_CONFIRMATIONS = Number(process.env.BTC_CONFIRMATIONS_REQUIRED || 6);
/** Env SETTLEMENT_ALLOW_CHAIN_MINT_EXECUTE overrides production JSON when set. */
export function allowChainMintExecute(): boolean {
const env = process.env.SETTLEMENT_ALLOW_CHAIN_MINT_EXECUTE?.trim();
if (env === '0' || env === 'false') return false;
if (env === '1' || env === 'true') return true;
return loadConfig().production.allowChainMintExecute === true;
}
export function formatSettlementError(err: unknown): string {
if (axios.isAxiosError(err)) {
const status = err.response?.status;
const body = err.response?.data;
const detail =
typeof body === 'string'
? body.slice(0, 500)
: body != null
? JSON.stringify(body).slice(0, 500)
: err.message;
return status != null ? `HTTP ${status}: ${detail}` : detail;
}
return err instanceof Error ? err.message : String(err);
}
/**
* L1 BTC deposit confirmed → Fineract custody journal (12015/12424) +
* M2→M3 cBTC mint on Chain 138, valued at live BTC/USD.
*/
export async function processBtcL1Settlement(
input: BtcL1SettlementRequest,
): Promise<SettlementRecord> {
const profile = loadOfficeProfile();
const officeId = settlementOfficeId(input.officeId);
const req = { ...input, officeId };
if (!req.idempotencyKey || !req.txId || !req.instructionId) {
throw new Error('idempotencyKey, txId, and instructionId are required');
}
if (!(req.amountSats > 0)) {
throw new Error('amountSats must be positive');
}
if (!req.recipientAddress?.startsWith('0x')) {
throw new Error('recipientAddress (0x Chain 138 vault) required');
}
if ((req.confirmations ?? 0) < MIN_CONFIRMATIONS) {
throw new Error(
`Insufficient confirmations: ${req.confirmations ?? 0} < ${MIN_CONFIRMATIONS}`,
);
}
const existing = settlementStore.getByIdempotencyKey(req.idempotencyKey);
if (existing?.phase === 'SETTLED') return existing;
// Fail-closed: FAILED BTC settles must be reconciled, not blindly retried
// (avoids double journals / double mint after partial or out-of-band completion).
if (existing?.phase === 'FAILED') {
throw new Error(
`BTC settlement ${existing.settlementId} is FAILED — use POST /btc/l1-reconcile ` +
`(idempotencyKey=${existing.idempotencyKey}) instead of re-settling`,
);
}
// Also idempotent on txId — avoid double-posting same L1 deposit
const byTx = settlementStore
.list(500)
.find((r) => r.btcSettlement?.txId === req.txId && r.phase === 'SETTLED');
if (byTx) return byTx;
const now = new Date().toISOString();
const valueDate = req.valueDate ?? now.slice(0, 10);
const btcAmount = satsToBtcDecimal(req.amountSats);
let record: SettlementRecord = existing ?? {
settlementId: uuidv4(),
idempotencyKey: req.idempotencyKey,
phase: 'RECEIVED',
request: {
idempotencyKey: req.idempotencyKey,
officeId,
valueDate,
currency: 'BTC',
amount: btcAmount,
creditorIban: 'INTERNAL-BTC-L1-CUSTODY',
beneficiaryName: req.recipientAddress,
remittanceInfo: `L1 BTC settle tx=${req.txId} instruction=${req.instructionId} → cBTC`,
moneyLayers: ['M0', 'M2', 'M3'],
rail: 'CHAIN138',
convertToCrypto: {
enabled: true,
targetChainId: 138,
tokenLineId: 'BTC-M2',
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');
const quote = await resolveLiveBtcUsd();
const usdValue = btcUsdToJournalAmount(req.amountSats, quote.btcUsdRate);
const usdAmount = parseFloat(usdValue) || 0;
if (!(usdAmount > 0)) {
throw new Error(`USD journal amount invalid for ${req.amountSats} sats @ ${quote.btcUsdRate}`);
}
const custody = resolveBtcL1CustodyJournal(officeId);
advance('VERBIAGE_ROLLED', {
btcSettlement: {
amountSats: req.amountSats,
txId: req.txId,
btcUsdRate: quote.btcUsdRate,
usdValue,
depositAddress: req.depositAddress,
instructionId: req.instructionId,
priceSource: quote.source,
priceAsOf: quote.asOf,
},
verbiageDocument: [
`OMNL L1 BTC custody settlement (IFRS 13 fair value)`,
`Office ${officeId} · instruction ${req.instructionId}`,
`L1 tx ${req.txId} · ${req.confirmations} confirmations`,
`Deposit ${req.depositAddress}`,
`Amount ${btcAmount} BTC (${req.amountSats} sats)`,
`BTC/USD ${quote.btcUsdRate} (${quote.source} @ ${quote.asOf}) → USD ${usdValue}`,
`Custody journal ${custody.memo}: Dr ${custody.debitGl} / Cr ${custody.creditGl}`,
`M2→M3 cBTC mint BTC-M2 → ${req.recipientAddress}`,
`Note: cBTC real-liquidity activation maps to WETH/USDT/USDC on Chain 138, not L1 BTC payout`,
].join('\n'),
});
// 1) L1 custody journal — HO office 1 posts Dr 12015 / Cr 12424
const btcGl = await resolveBtcGlAccountIds(officeId);
const custodyJe = await postJournalEntry({
officeId: 1,
transactionDate: valueDate,
referenceNumber: `${req.idempotencyKey}-L1`,
comments: `${custody.memo} ${req.txId} ${usdValue} USD`,
debits: [{ glAccountId: btcGl.custodyHoGlId, amount: usdAmount }],
credits: [{ glAccountId: btcGl.interofficeBtcGlId, amount: usdAmount }],
});
// 2) M2→M3 backing check + journal at Office 24 (USD fair value)
const balances = await fetchGlBalances(officeId);
const snap = buildMoneySupplySnapshot(officeId, balances);
const { loadAmount, fullyBacked } = m2FiatToTokenLoadAmount(usdValue, snap.M2.broadMoney);
const m3Check = m3TokenLoadAmount(loadAmount, snap.M2.broadMoney, snap.M3.tokenLiabilities);
if (!fullyBacked || !m3Check.fullyBacked) {
record.errors.push(
`M2/M3 backing insufficient for BTC settlement (M2 load ${loadAmount}/${usdValue}, M3 headroom ${m3Check.loadAmount})`,
);
advance('FAILED', {
fineractJournalRef: String(custodyJe.resourceId),
});
return record;
}
const m2Amount = parseFloat(loadAmount) || 0;
let m2m3Ref = '';
if (m2Amount > 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 m2Je = await postJournalEntry({
officeId,
transactionDate: valueDate,
referenceNumber: `${req.idempotencyKey}-M2M3`,
comments: `T-BTC-M2M3 cBTC load ${btcAmount} BTC`,
debits: [{ glAccountId: debitGlId, amount: m2Amount }],
credits: [{ glAccountId: creditGlId, amount: m2Amount }],
});
m2m3Ref = String(m2Je.resourceId);
}
advance('FINERACT_POSTED', {
fineractJournalRef: [String(custodyJe.resourceId), m2m3Ref].filter(Boolean).join(','),
});
advance('HYBX_RAIL_DISPATCHED');
advance('ISO20022_ARCHIVED');
// 3) Mint cBTC in satoshi-accurate BTC decimal amount
const mint = await requestTokenMint({
lineId: 'BTC-M2',
amount: btcAmount,
recipient: req.recipientAddress,
settlementRef: record.settlementId,
dryRun: !allowChainMintExecute(),
symbol: 'cBTC',
});
advance('CHAIN_MINT_REQUESTED', { chainTxHash: mint.txHash });
advance('SETTLED');
return record;
} catch (err) {
record.errors.push(formatSettlementError(err));
advance('FAILED');
return record;
}
}
/**
* Force SETTLED when custody journals + cBTC mint already completed out-of-band.
* Does not call Fineract or mint again.
*/
export function reconcileBtcL1Settlement(input: BtcL1ReconcileRequest): SettlementRecord {
if (!input.settlementId && !input.idempotencyKey) {
throw new Error('settlementId or idempotencyKey required');
}
if (!(input.amountSats > 0)) {
throw new Error('amountSats must be positive');
}
if (!input.chainTxHash?.startsWith('0x')) {
throw new Error('chainTxHash (0x…) required');
}
if (!(input.btcUsdRate > 0)) {
throw new Error('btcUsdRate must be positive');
}
if (!input.recipientAddress?.startsWith('0x')) {
throw new Error('recipientAddress required');
}
const existing = input.settlementId
? settlementStore.getById(input.settlementId)
: settlementStore.getByIdempotencyKey(input.idempotencyKey!);
if (!existing) {
throw new Error(
`Settlement not found (id=${input.settlementId ?? ''} key=${input.idempotencyKey ?? ''})`,
);
}
if (existing.phase === 'SETTLED' && existing.btcSettlement?.amountSats === input.amountSats) {
return existing;
}
const now = new Date().toISOString();
const btcAmount = satsToBtcDecimal(input.amountSats);
const usdValue =
input.usdValue ?? btcUsdToJournalAmount(input.amountSats, input.btcUsdRate);
const officeId = settlementOfficeId(existing.request.officeId);
const record: SettlementRecord = {
...existing,
phase: 'SETTLED',
updatedAt: now,
chainTxHash: input.chainTxHash,
fineractJournalRef: input.fineractJournalRef ?? existing.fineractJournalRef,
errors: [],
btcSettlement: {
amountSats: input.amountSats,
txId: input.txId,
btcUsdRate: input.btcUsdRate,
usdValue,
depositAddress: input.depositAddress,
instructionId: input.instructionId,
priceSource: input.priceSource ?? 'reconcile/ops',
priceAsOf: input.priceAsOf ?? now,
},
verbiageDocument: [
`OMNL L1 BTC custody settlement — RECONCILED (no remint / no re-journal)`,
`Office ${officeId} · instruction ${input.instructionId}`,
`L1/ops tx ${input.txId}`,
`Amount ${btcAmount} BTC (${input.amountSats} sats)`,
`BTC/USD ${input.btcUsdRate} → USD ${usdValue}`,
`Chain mint ${input.chainTxHash}${input.recipientAddress}`,
input.fineractJournalRef ? `Fineract refs ${input.fineractJournalRef}` : 'Fineract refs: prior out-of-band journals',
].join('\n'),
request: {
...existing.request,
amount: btcAmount,
beneficiaryName: input.recipientAddress,
remittanceInfo: `RECONCILED L1 BTC settle tx=${input.txId} → cBTC ${input.chainTxHash}`,
convertToCrypto: {
enabled: true,
targetChainId: 138,
tokenLineId: 'BTC-M2',
recipientAddress: input.recipientAddress,
},
},
};
settlementStore.save(record);
return record;
}

View File

@@ -0,0 +1,105 @@
import { v4 as uuidv4 } from 'uuid';
import type { FiatLpSwapLeg, FiatLpToLiquidityRequest, SettlementRecord } from '@dbis/settlement-core';
import { loadConfig, settlementOfficeId } from '../config';
import { requestFiatLpToLiquidity } from '../adapters/omnl';
import { settlementStore } from '../store/settlement-store';
export async function processFiatLpToLiquidity(
input: FiatLpToLiquidityRequest,
): Promise<SettlementRecord> {
const cfg = loadConfig();
const officeId = settlementOfficeId(input.officeId);
const req = { ...input, officeId };
const holder = req.holderAddress.trim();
const recipient = (req.recipientAddress ?? holder).trim();
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: 'USD',
amount: req.amount ?? '0',
creditorIban: 'INTERNAL-FIAT-LP-LIQUIDITY',
beneficiaryName: recipient,
remittanceInfo: `Chain 138 fiat LP → real liquidity (${req.convertAll ? 'all c*' : req.symbol ?? 'single'})`,
moneyLayers: ['M2', 'M3'],
rail: 'CHAIN138',
},
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 Chain 138 fiat LP → real fund liquidity activation',
`Holder ${holder} → recipient ${recipient}`,
req.convertAll ? 'Convert all swappable c* tokens' : `Symbol ${req.symbol ?? 'n/a'}`,
'Target: official USDT / USDC / WETH on Chain 138 (mainnet-style parity)',
].join('\n'),
});
advance('FINERACT_POSTED');
advance('HYBX_RAIL_DISPATCHED');
advance('ISO20022_ARCHIVED');
const dryRun = req.dryRun ?? !cfg.production.allowChainMintExecute;
const result = await requestFiatLpToLiquidity({
holderAddress: holder,
recipientAddress: recipient,
symbol: req.symbol,
amount: req.amount,
convertAll: req.convertAll,
maxSlippageBps: req.maxSlippageBps,
settlementRef: record.settlementId,
dryRun,
});
const liquiditySwaps: FiatLpSwapLeg[] = result.swaps.map((s) => ({
lpSymbol: s.lpSymbol,
tokenIn: s.tokenIn,
tokenOut: s.tokenOut,
realSymbol: s.realSymbol,
amountIn: s.amountIn,
amountOutQuoted: s.amountOutQuoted,
txHash: s.txHash,
status: s.status as FiatLpSwapLeg['status'],
error: s.error,
}));
const lastTx = liquiditySwaps.find((s) => s.txHash)?.txHash;
const anyFailed = liquiditySwaps.some((s) => s.status === 'FAILED');
advance('CHAIN_MINT_REQUESTED', {
chainTxHash: lastTx,
liquiditySwaps,
});
if (anyFailed && !dryRun) {
record.errors.push('One or more LP→liquidity swaps failed');
advance('FAILED');
return record;
}
advance('SETTLED');
return record;
} catch (err) {
record.errors.push(err instanceof Error ? err.message : String(err));
advance('FAILED');
return record;
}
}