feat(settlement): wire real Chain 138 contracts for 1,000 BTC ledger
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 52s
CI/CD Pipeline / Security Scanning (push) Successful in 2m40s
CI/CD Pipeline / Lint and Format (push) Failing after 49s
CI/CD Pipeline / Terraform Validation (push) Failing after 24s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 27s
Deploy ChainID 138 / Deploy ChainID 138 (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
Verify Deployment / Verify Deployment (push) Has been cancelled
Validation / validate-smart-contracts (push) Failing after 14s
Validation / validate-security (push) Failing after 1m20s
Validation / validate-documentation (push) Failing after 18s
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 52s
CI/CD Pipeline / Security Scanning (push) Successful in 2m40s
CI/CD Pipeline / Lint and Format (push) Failing after 49s
CI/CD Pipeline / Terraform Validation (push) Failing after 24s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 27s
Deploy ChainID 138 / Deploy ChainID 138 (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
Verify Deployment / Verify Deployment (push) Has been cancelled
Validation / validate-smart-contracts (push) Failing after 14s
Validation / validate-security (push) Failing after 1m20s
Validation / validate-documentation (push) Failing after 18s
Add canonical btc-l1-ledger config, /btc/contracts API, Python chain138-oracle service, and production deploy scripts so secure.omdnl.org can serve live cBTC, DODO, and PMM pool addresses with headroom-aware mint gating. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,64 @@
|
||||
import axios from 'axios';
|
||||
import { createHmac } from 'crypto';
|
||||
import { loadConfig } from '../config';
|
||||
import { loadHybxConfig } from '@dbis/integration-foundation';
|
||||
|
||||
/** RFC1918 / loopback hosts — not reachable from public VPS deploys (e.g. secure.omdnl.org). */
|
||||
function isPrivateSidecarHost(url: string): boolean {
|
||||
try {
|
||||
const host = new URL(url).hostname.toLowerCase();
|
||||
if (host === 'localhost' || host.endsWith('.local')) return true;
|
||||
if (/^127\./.test(host) || /^10\./.test(host) || /^192\.168\./.test(host)) return true;
|
||||
const m = /^172\.(\d+)\./.exec(host);
|
||||
if (m) {
|
||||
const second = parseInt(m[1], 10);
|
||||
if (second >= 16 && second <= 31) return true;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolve outbound payment URL — public HYBX RTGS API, not LAN sidecar unless explicitly allowed. */
|
||||
export function resolveHybxPaymentsUrl(baseUrl: string): string {
|
||||
const explicit = process.env.HYBX_PAYMENTS_URL?.trim();
|
||||
if (explicit) return explicit.replace(/\/$/, '');
|
||||
|
||||
const hybxApi = process.env.HYBX_API?.trim().replace(/\/$/, '');
|
||||
if (hybxApi) return `${hybxApi}/rtgs/payments`;
|
||||
|
||||
const sidecar = process.env.HYBX_MIFOS_FINERACT_SIDECAR_URL?.trim();
|
||||
const allowLan = process.env.HYBX_ALLOW_LAN_SIDECAR === '1';
|
||||
if (sidecar && (allowLan || !isPrivateSidecarHost(sidecar))) {
|
||||
return `${sidecar.replace(/\/$/, '')}/v1/rtgs/payments`;
|
||||
}
|
||||
return `${baseUrl.replace(/\/$/, '')}/v1/rtgs/payments`;
|
||||
}
|
||||
|
||||
function hybxWebhookSecret(): string {
|
||||
return (
|
||||
process.env.HYBX_WEBHOOK_SECRET?.trim() ||
|
||||
process.env.HYBX_SIGNING_SECRET?.trim() ||
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
/** HYBX RTGS API: X-API-Key + X-Timestamp + X-Signature = HMAC-SHA256(secret, raw JSON body). */
|
||||
export function buildHybxRtgsHeaders(bodyJson: string, apiKey: string): Record<string, string> {
|
||||
const secret = hybxWebhookSecret();
|
||||
if (!secret) {
|
||||
throw new Error('HYBX_WEBHOOK_SECRET required for signed RTGS dispatch');
|
||||
}
|
||||
const timestamp = Math.floor(Date.now() / 1000).toString();
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': apiKey,
|
||||
'X-Timestamp': timestamp,
|
||||
'X-Signature': createHmac('sha256', secret).update(bodyJson).digest('hex'),
|
||||
};
|
||||
}
|
||||
|
||||
/** Production HYBX rail client for real settlement dispatch */
|
||||
export class HybxProductionRail {
|
||||
private readonly baseUrl: string;
|
||||
@@ -18,7 +75,8 @@ export class HybxProductionRail {
|
||||
throw new Error(
|
||||
'HYBX production requires SETTLEMENT_ALLOW_HYBX_PRODUCTION=1 or production.allowHybxProduction in config',
|
||||
);
|
||||
} this.baseUrl = cfg.baseUrl.replace(/\/$/, '');
|
||||
}
|
||||
this.baseUrl = cfg.baseUrl.replace(/\/$/, '');
|
||||
this.apiKey = cfg.apiKey;
|
||||
this.clientSecret = cfg.clientSecret;
|
||||
}
|
||||
@@ -32,35 +90,50 @@ export class HybxProductionRail {
|
||||
beneficiaryName: string;
|
||||
remittanceInfo: string;
|
||||
rail: string;
|
||||
debtorIban?: string;
|
||||
}): Promise<{ paymentId: string; status: string }> {
|
||||
const sidecar = process.env.HYBX_MIFOS_FINERACT_SIDECAR_URL?.replace(/\/$/, '');
|
||||
const url = sidecar ? `${sidecar}/v1/rtgs/payments` : `${this.baseUrl}/v1/payments`;
|
||||
const { data } = await axios.post(
|
||||
url,
|
||||
{
|
||||
externalId: payload.idempotencyKey,
|
||||
amount: payload.amount,
|
||||
currency: payload.currency,
|
||||
beneficiary: {
|
||||
iban: payload.creditorIban,
|
||||
bic: payload.creditorBic,
|
||||
name: payload.beneficiaryName,
|
||||
},
|
||||
remittanceInformation: payload.remittanceInfo,
|
||||
rail: payload.rail,
|
||||
const url = resolveHybxPaymentsUrl(this.baseUrl);
|
||||
const body: Record<string, unknown> = {
|
||||
externalId: payload.idempotencyKey,
|
||||
amount: payload.amount,
|
||||
currency: payload.currency,
|
||||
beneficiary: {
|
||||
iban: payload.creditorIban,
|
||||
bic: payload.creditorBic,
|
||||
name: payload.beneficiaryName,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
remittanceInformation: payload.remittanceInfo,
|
||||
rail: payload.rail,
|
||||
};
|
||||
if (payload.debtorIban) body.debtorIban = payload.debtorIban;
|
||||
|
||||
const bodyJson = JSON.stringify(body);
|
||||
const useRtgsSignedApi = Boolean(process.env.HYBX_API?.trim() || process.env.HYBX_PAYMENTS_URL?.trim());
|
||||
const headers = useRtgsSignedApi
|
||||
? buildHybxRtgsHeaders(bodyJson, this.apiKey)
|
||||
: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': this.apiKey,
|
||||
Authorization: `Bearer ${this.clientSecret}`,
|
||||
},
|
||||
timeout: 120000,
|
||||
},
|
||||
);
|
||||
return {
|
||||
paymentId: String(data.paymentId ?? data.id ?? payload.idempotencyKey),
|
||||
status: String(data.status ?? 'DISPATCHED'),
|
||||
};
|
||||
};
|
||||
|
||||
if (useRtgsSignedApi && !hybxWebhookSecret()) {
|
||||
throw new Error('HYBX_WEBHOOK_SECRET required for signed RTGS dispatch (HYBX_API set)');
|
||||
}
|
||||
|
||||
try {
|
||||
const { data } = await axios.post(url, bodyJson, { headers, timeout: 120000 });
|
||||
return {
|
||||
paymentId: String(data.paymentId ?? data.id ?? payload.idempotencyKey),
|
||||
status: String(data.status ?? 'DISPATCHED'),
|
||||
};
|
||||
} catch (e) {
|
||||
const ax = e as { response?: { status?: number; data?: unknown }; message?: string };
|
||||
const detail =
|
||||
ax.response?.data != null
|
||||
? JSON.stringify(ax.response.data).slice(0, 400)
|
||||
: ax.message ?? String(e);
|
||||
throw new Error(`HYBX payment failed (${url}): ${detail}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { resolveTokenAggregationBase } from './price-service';
|
||||
|
||||
export async function archiveIso20022(payload: {
|
||||
messageType: string;
|
||||
@@ -6,7 +7,7 @@ export async function archiveIso20022(payload: {
|
||||
raw: string;
|
||||
settlementRef: string;
|
||||
}): Promise<{ messageId: string }> {
|
||||
const base = (process.env.TOKEN_AGGREGATION_URL || 'http://localhost:3000').replace(/\/$/, '');
|
||||
const base = resolveTokenAggregationBase();
|
||||
const key = process.env.OMNL_API_KEY || '';
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (key) headers.Authorization = `Bearer ${key}`;
|
||||
@@ -33,7 +34,7 @@ export async function requestTokenMint(payload: {
|
||||
symbol?: string;
|
||||
tokenAddress?: string;
|
||||
}): Promise<{ txHash?: string; status: string }> {
|
||||
const base = (process.env.TOKEN_AGGREGATION_URL || 'http://localhost:3000').replace(/\/$/, '');
|
||||
const base = resolveTokenAggregationBase();
|
||||
const key = process.env.OMNL_API_KEY || '';
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (key) headers.Authorization = `Bearer ${key}`;
|
||||
@@ -66,7 +67,7 @@ export async function requestTokenTransfer(payload: {
|
||||
dryRun: boolean;
|
||||
symbol?: string;
|
||||
}): Promise<{ txHash?: string; status: string }> {
|
||||
const base = (process.env.TOKEN_AGGREGATION_URL || 'http://localhost:3000').replace(/\/$/, '');
|
||||
const base = resolveTokenAggregationBase();
|
||||
const key = process.env.OMNL_API_KEY || '';
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (key) headers.Authorization = `Bearer ${key}`;
|
||||
@@ -117,7 +118,7 @@ export async function requestFiatLpToLiquidity(payload: {
|
||||
settlementRef: string;
|
||||
dryRun: boolean;
|
||||
}): Promise<FiatLpLiquidityAdapterResult> {
|
||||
const base = (process.env.TOKEN_AGGREGATION_URL || 'http://localhost:3000').replace(/\/$/, '');
|
||||
const base = resolveTokenAggregationBase();
|
||||
const key = process.env.OMNL_API_KEY || '';
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (key) headers.Authorization = `Bearer ${key}`;
|
||||
|
||||
15
services/settlement-middleware/src/adapters/price-service.ts
Normal file
15
services/settlement-middleware/src/adapters/price-service.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Chain 138 spot-price / oracle HTTP base.
|
||||
* Pricing uses CHAIN138_ORACLE_URL (Python oracle, port 3500) when set;
|
||||
* falls back to TOKEN_AGGREGATION_URL for gradual cutover.
|
||||
*/
|
||||
export function resolveChain138OracleBase(): string {
|
||||
const oracle = (process.env.CHAIN138_ORACLE_URL || process.env.ORACLE_PUBLISHER_URL || '').replace(/\/$/, '');
|
||||
if (oracle) return oracle;
|
||||
return (process.env.TOKEN_AGGREGATION_URL || 'http://localhost:3500').replace(/\/$/, '');
|
||||
}
|
||||
|
||||
/** token-aggregation base for OMNL mint/transfer/ISO20022 (not spot prices). */
|
||||
export function resolveTokenAggregationBase(): string {
|
||||
return (process.env.TOKEN_AGGREGATION_URL || 'http://localhost:3000').replace(/\/$/, '');
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import axios from 'axios';
|
||||
import { resolveChain138OracleBase } from './price-service';
|
||||
import { loadBtcLedgerConfig } from '../btc-ledger-config';
|
||||
|
||||
const CBTC_ADDRESS = '0xe94260c555ac1d9d3cc9e1632883452ebdf0082e';
|
||||
const CHAIN_138 = 138;
|
||||
|
||||
export type LiveBtcUsdQuote = {
|
||||
@@ -10,8 +11,12 @@ export type LiveBtcUsdQuote = {
|
||||
tokenAddress: string;
|
||||
};
|
||||
|
||||
function tokenAggregationBase(): string {
|
||||
return (process.env.TOKEN_AGGREGATION_URL || 'http://localhost:3000').replace(/\/$/, '');
|
||||
function cbtcAddress(): string {
|
||||
try {
|
||||
return loadBtcLedgerConfig().token.address.toLowerCase();
|
||||
} catch {
|
||||
return '0xe94260c555ac1d9d3cc9e1632883452ebdf0082e';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -19,14 +24,14 @@ function tokenAggregationBase(): string {
|
||||
* production must not fall back to the $90k repo snapshot.
|
||||
*/
|
||||
export async function resolveLiveBtcUsd(): Promise<LiveBtcUsdQuote> {
|
||||
const base = tokenAggregationBase();
|
||||
const base = resolveChain138OracleBase();
|
||||
const key = process.env.OMNL_API_KEY || '';
|
||||
const headers: Record<string, string> = { Accept: 'application/json' };
|
||||
if (key) headers.Authorization = `Bearer ${key}`;
|
||||
|
||||
const url =
|
||||
`${base}/api/v1/prices/metamask/v2/chains/${CHAIN_138}/spot-prices` +
|
||||
`?tokenAddresses=${CBTC_ADDRESS}&vsCurrency=usd`;
|
||||
`?tokenAddresses=${cbtcAddress()}&vsCurrency=usd`;
|
||||
|
||||
let data: unknown;
|
||||
try {
|
||||
@@ -49,13 +54,14 @@ export async function resolveLiveBtcUsd(): Promise<LiveBtcUsdQuote> {
|
||||
throw err;
|
||||
}
|
||||
const map = (data ?? {}) as Record<string, Record<string, number>>;
|
||||
const row = map[CBTC_ADDRESS.toLowerCase()] ?? map[CBTC_ADDRESS];
|
||||
const addr = cbtcAddress();
|
||||
const row = map[addr] ?? map[addr.toLowerCase()];
|
||||
const rate = row?.usd ?? row?.USD;
|
||||
|
||||
if (!(typeof rate === 'number' && Number.isFinite(rate) && rate > 0)) {
|
||||
throw new Error(
|
||||
'Live BTC/USD price unresolved — settlement blocked (OMNL_REQUIRE_LIVE_BTC_PRICE). ' +
|
||||
'Ensure token-aggregation FX refresh / CoinGecko is reachable.',
|
||||
'Ensure chain138-oracle (CHAIN138_ORACLE_URL) or token-aggregation FX refresh is reachable.',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,8 +85,8 @@ export async function resolveLiveBtcUsd(): Promise<LiveBtcUsdQuote> {
|
||||
|
||||
return {
|
||||
btcUsdRate: rate,
|
||||
source: 'token-aggregation/metamask-spot-prices',
|
||||
source: 'chain138-oracle/metamask-spot-prices',
|
||||
asOf: new Date().toISOString(),
|
||||
tokenAddress: CBTC_ADDRESS,
|
||||
tokenAddress: addr,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from '../../workflows/btc-l1-settlement';
|
||||
import { fetchGlBalances, fineractOfficeReachable } from '../../adapters/fineract';
|
||||
import { resolveLiveBtcUsd } from '../../adapters/pricing';
|
||||
import { btcLedgerContractsPayload, loadBtcLedgerConfig } from '../../btc-ledger-config';
|
||||
import {
|
||||
dispatchExternalBankOrExchange,
|
||||
getExternalRailsStatus,
|
||||
@@ -670,9 +671,19 @@ export function createSettlementRouter(): Router {
|
||||
res.json({ count: rows.length, settlements: rows });
|
||||
});
|
||||
|
||||
router.get('/btc/contracts', (_req, res) => {
|
||||
if (!requireApiKey(_req, res)) return;
|
||||
try {
|
||||
res.json(btcLedgerContractsPayload());
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/btc/ledger', async (_req, res) => {
|
||||
if (!requireApiKey(_req, res)) return;
|
||||
try {
|
||||
const btcCfg = loadBtcLedgerConfig();
|
||||
const [balancesHo, balancesO24, quote] = await Promise.all([
|
||||
fetchGlBalances(1),
|
||||
fetchGlBalances(officeId),
|
||||
@@ -690,6 +701,10 @@ export function createSettlementRouter(): Router {
|
||||
.filter((r) => r.phase === 'SETTLED')
|
||||
.reduce((sum, r) => sum + (r.btcSettlement?.amountSats ?? 0), 0);
|
||||
|
||||
const m2 = parseFloat(balancesO24['2200'] ?? '0');
|
||||
const m3 = parseFloat(balancesO24['2300'] ?? '0');
|
||||
const headroomUsd = m2 - m3;
|
||||
|
||||
res.json({
|
||||
asOf: new Date().toISOString(),
|
||||
gl: {
|
||||
@@ -698,6 +713,16 @@ export function createSettlementRouter(): Router {
|
||||
m2Broad2200: balancesO24['2200'] ?? '0',
|
||||
m3Token2300: balancesO24['2300'] ?? '0',
|
||||
},
|
||||
headroomUsd: headroomUsd.toFixed(2),
|
||||
requiredHeadroomUsd: btcCfg.requiredHeadroomUsd,
|
||||
mint1000BtcAllowed: headroomUsd >= btcCfg.requiredHeadroomUsd,
|
||||
contracts: {
|
||||
cbtc: btcCfg.token.address,
|
||||
lineId: btcCfg.token.lineId,
|
||||
primaryRecipient: btcCfg.recipients.primaryMintTarget,
|
||||
dodoPmmIntegration: btcCfg.dodoEvm.pmmIntegration,
|
||||
pmmPools: btcCfg.pmmPools,
|
||||
},
|
||||
btcUsd:
|
||||
'btcUsdRate' in quote
|
||||
? { rate: quote.btcUsdRate, source: quote.source, asOf: quote.asOf }
|
||||
|
||||
128
services/settlement-middleware/src/btc-ledger-config.ts
Normal file
128
services/settlement-middleware/src/btc-ledger-config.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export type BtcLedgerConfig = {
|
||||
version: string;
|
||||
chainId: number;
|
||||
officeId: number;
|
||||
amountBtc: number;
|
||||
amountSats: number;
|
||||
btcUsdPolicyRate: number;
|
||||
requiredHeadroomUsd: number;
|
||||
token: {
|
||||
symbol: string;
|
||||
address: string;
|
||||
decimals: number;
|
||||
omnlLine: string;
|
||||
lineId: string;
|
||||
};
|
||||
recipients: {
|
||||
primaryMintTarget: string;
|
||||
existingMintHolder1000: string;
|
||||
};
|
||||
fineractGl: Record<string, { glCode: string; officeId: number; memo?: string }>;
|
||||
oracle: Record<string, string>;
|
||||
dodoEvm: Record<string, string>;
|
||||
pmmPools: Record<string, string>;
|
||||
quoteTokens: Record<string, string>;
|
||||
rpc: { envVar: string; public: string; explorer: string };
|
||||
settlementApi: Record<string, string>;
|
||||
};
|
||||
|
||||
let cached: BtcLedgerConfig | null = null;
|
||||
|
||||
function repoRoot(): string {
|
||||
return path.resolve(__dirname, '../../../');
|
||||
}
|
||||
|
||||
function configPath(): string {
|
||||
const rel =
|
||||
process.env.OMNL_BTC_LEDGER_CONFIG ||
|
||||
process.env.BTC_L1_LEDGER_CONFIG ||
|
||||
'config/btc-l1-ledger-1000.chain138.v1.json';
|
||||
return path.isAbsolute(rel) ? rel : path.join(repoRoot(), rel.replace(/^\//, ''));
|
||||
}
|
||||
|
||||
export function loadBtcLedgerConfig(): BtcLedgerConfig {
|
||||
if (cached) return cached;
|
||||
const raw = JSON.parse(fs.readFileSync(configPath(), 'utf8')) as BtcLedgerConfig;
|
||||
cached = applyEnvOverrides(raw);
|
||||
return cached;
|
||||
}
|
||||
|
||||
function envAddr(key: string, fallback: string): string {
|
||||
const v = process.env[key]?.trim();
|
||||
return v || fallback;
|
||||
}
|
||||
|
||||
/** Env overrides for deployment without editing JSON. */
|
||||
function applyEnvOverrides(cfg: BtcLedgerConfig): BtcLedgerConfig {
|
||||
return {
|
||||
...cfg,
|
||||
token: {
|
||||
...cfg.token,
|
||||
address: envAddr('CBTC_ADDRESS_138', cfg.token.address),
|
||||
lineId: process.env.OMNL_BTC_LINE_ID?.trim() || cfg.token.lineId,
|
||||
},
|
||||
recipients: {
|
||||
primaryMintTarget: envAddr(
|
||||
'BTC_L1_RECIPIENT_ADDRESS',
|
||||
envAddr('BTC_E2E_RECIPIENT', cfg.recipients.primaryMintTarget),
|
||||
),
|
||||
existingMintHolder1000: envAddr(
|
||||
'BTC_EXISTING_MINT_HOLDER',
|
||||
cfg.recipients.existingMintHolder1000,
|
||||
),
|
||||
},
|
||||
oracle: {
|
||||
...cfg.oracle,
|
||||
aggregator: envAddr('AGGREGATOR_ADDRESS', envAddr('ORACLE_AGGREGATOR_ADDRESS', cfg.oracle.aggregator)),
|
||||
proxy: envAddr('ORACLE_PROXY_ADDRESS', cfg.oracle.proxy),
|
||||
oraclePriceFeed: envAddr('ORACLE_PRICE_FEED', cfg.oracle.oraclePriceFeed),
|
||||
reserveSystem: envAddr('RESERVE_SYSTEM', cfg.oracle.reserveSystem),
|
||||
priceFeedKeeper: envAddr('PRICE_FEED_KEEPER_ADDRESS', cfg.oracle.priceFeedKeeper),
|
||||
},
|
||||
dodoEvm: {
|
||||
...cfg.dodoEvm,
|
||||
pmmIntegration: envAddr(
|
||||
'DODO_PMM_INTEGRATION',
|
||||
envAddr('CHAIN_138_DODO_PMM_INTEGRATION', cfg.dodoEvm.pmmIntegration),
|
||||
),
|
||||
},
|
||||
pmmPools: {
|
||||
cBTC_cUSDT: envAddr('CHAIN138_POOL_CBTC_CUSDT', cfg.pmmPools.cBTC_cUSDT),
|
||||
cBTC_cUSDC: envAddr('CHAIN138_POOL_CBTC_CUSDC', cfg.pmmPools.cBTC_cUSDC),
|
||||
cBTC_cXAUC: envAddr('CHAIN138_POOL_CBTC_CXAUC', cfg.pmmPools.cBTC_cXAUC),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function btcLedgerContractsPayload(): Record<string, unknown> {
|
||||
const cfg = loadBtcLedgerConfig();
|
||||
return {
|
||||
configPath: configPath(),
|
||||
chainId: cfg.chainId,
|
||||
officeId: cfg.officeId,
|
||||
amountBtc: cfg.amountBtc,
|
||||
amountSats: cfg.amountSats,
|
||||
requiredHeadroomUsd: cfg.requiredHeadroomUsd,
|
||||
token: cfg.token,
|
||||
recipients: cfg.recipients,
|
||||
fineractGl: cfg.fineractGl,
|
||||
oracle: cfg.oracle,
|
||||
dodoEvm: cfg.dodoEvm,
|
||||
pmmPools: cfg.pmmPools,
|
||||
quoteTokens: cfg.quoteTokens,
|
||||
rpc: cfg.rpc,
|
||||
explorerLinks: {
|
||||
cbtc: `${cfg.rpc.explorer}/address/${cfg.token.address}`,
|
||||
pmmIntegration: `${cfg.rpc.explorer}/address/${cfg.dodoEvm.pmmIntegration}`,
|
||||
primaryRecipient: `${cfg.rpc.explorer}/address/${cfg.recipients.primaryMintTarget}`,
|
||||
poolCbtcCusdc: `${cfg.rpc.explorer}/address/${cfg.pmmPools.cBTC_cUSDC}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function clearBtcLedgerConfigCache(): void {
|
||||
cached = null;
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export type MiddlewareConfig = {
|
||||
defaultCurrency: string;
|
||||
rails: {
|
||||
swift: { enabled: boolean; listenerUrl: string };
|
||||
hybx: { enabled: boolean; sidecarUrl: string };
|
||||
hybx: { enabled: boolean; sidecarUrl?: string; publicBaseUrl?: string };
|
||||
chain138: { enabled: boolean; rpcEnv: string; defaultLineId: string };
|
||||
multiChain?: {
|
||||
enabled: boolean;
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from '../adapters/fineract';
|
||||
import { requestTokenMint } from '../adapters/omnl';
|
||||
import { resolveLiveBtcUsd } from '../adapters/pricing';
|
||||
import { loadBtcLedgerConfig } from '../btc-ledger-config';
|
||||
import { settlementStore } from '../store/settlement-store';
|
||||
|
||||
const MIN_CONFIRMATIONS = Number(process.env.BTC_CONFIRMATIONS_REQUIRED || 6);
|
||||
@@ -214,13 +215,15 @@ export async function processBtcL1Settlement(
|
||||
advance('ISO20022_ARCHIVED');
|
||||
|
||||
// 3) Mint cBTC in satoshi-accurate BTC decimal amount
|
||||
const btcLedger = loadBtcLedgerConfig();
|
||||
const mint = await requestTokenMint({
|
||||
lineId: 'BTC-M2',
|
||||
lineId: btcLedger.token.lineId,
|
||||
amount: btcAmount,
|
||||
recipient: req.recipientAddress,
|
||||
settlementRef: record.settlementId,
|
||||
dryRun: !allowChainMintExecute(),
|
||||
symbol: 'cBTC',
|
||||
symbol: btcLedger.token.symbol,
|
||||
tokenAddress: btcLedger.token.address,
|
||||
});
|
||||
advance('CHAIN_MINT_REQUESTED', { chainTxHash: mint.txHash });
|
||||
advance('SETTLED');
|
||||
|
||||
@@ -97,6 +97,7 @@ export async function processExternalTransfer(
|
||||
beneficiaryName: req.beneficiaryName,
|
||||
remittanceInfo: req.remittanceInfo,
|
||||
rail: req.rail,
|
||||
debtorIban: req.debtorIban,
|
||||
});
|
||||
advance('HYBX_RAIL_DISPATCHED', { hybxPaymentId: pay.paymentId });
|
||||
} else {
|
||||
|
||||
@@ -168,6 +168,7 @@ export async function processZBankM0ToM4(input: ZBankM0M4Request): Promise<Settl
|
||||
beneficiaryName: req.beneficiaryName,
|
||||
remittanceInfo: req.remittanceInfo,
|
||||
rail: req.rail,
|
||||
debtorIban: req.debtorIban,
|
||||
});
|
||||
advance('HYBX_RAIL_DISPATCHED', { hybxPaymentId: pay.paymentId });
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user