Files
smom-dbis-138/services/btc-intake/src/server.ts
zaragoza444 5a9889cace
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
feat(settlement): production BTC L1 rails, hot cBTC liquidity, Dfns/Cobo smoke
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>
2026-07-19 09:28:24 -07:00

143 lines
4.1 KiB
TypeScript

import express from 'express';
import type { AuditPublisher, OutstandingPolicy } from './custody-adapter';
import { BitcoindCustodyAdapter } from './adapters/bitcoind-custody';
import { SettlementMintJobSink } from './adapters/settlement-mint-sink';
import { NativeBitcoinWatcher } from './native-bitcoin-watcher';
import type { AuditRecord, BasketMandateSnapshot } from './types';
const PORT = Number(process.env.BTC_INTAKE_PORT || 3013);
const SYNC_INTERVAL_MS = Number(process.env.BTC_INTAKE_SYNC_INTERVAL_MS || 30_000);
class ConsoleAuditPublisher implements AuditPublisher {
async publish(record: AuditRecord): Promise<void> {
// eslint-disable-next-line no-console
console.log(
JSON.stringify({
level: 'audit',
service: 'btc-intake',
...record,
}),
);
}
}
class EnvOutstandingPolicy implements OutstandingPolicy {
async getCurrentOutstandingSats(): Promise<number> {
return Number(process.env.BTC_CURRENT_OUTSTANDING_SATS || 0);
}
async getMaxOutstandingSats(): Promise<number> {
return Number(process.env.BTC_MAX_OUTSTANDING_SATS || 0);
}
}
async function main(): Promise<void> {
const adapter = new BitcoindCustodyAdapter();
const mintSink = new SettlementMintJobSink();
const audit = new ConsoleAuditPublisher();
const outstanding = new EnvOutstandingPolicy();
const watcher = new NativeBitcoinWatcher(adapter, mintSink, audit, outstanding);
let lastSyncAt: string | null = null;
let lastSyncError: string | null = null;
let syncing = false;
const runSync = async () => {
if (syncing) return;
syncing = true;
try {
await watcher.sync();
lastSyncAt = new Date().toISOString();
lastSyncError = null;
} catch (err) {
lastSyncError = err instanceof Error ? err.message : String(err);
// eslint-disable-next-line no-console
console.error('[btc-intake] sync error', lastSyncError);
} finally {
syncing = false;
}
};
const app = express();
app.use(express.json({ limit: '1mb' }));
app.get('/health', async (_req, res) => {
const bitcoindOk = await adapter.ping();
res.status(bitcoindOk ? 200 : 503).json({
service: 'btc-intake',
status: bitcoindOk ? 'ok' : 'degraded',
bitcoind: bitcoindOk,
lastSyncAt,
lastSyncError,
syncing,
network: process.env.BTC_NETWORK || 'mainnet',
});
});
app.post('/deposit-instructions', async (req, res) => {
try {
const basketMandate = req.body?.basketMandate as BasketMandateSnapshot | undefined;
if (
!basketMandate?.id ||
!basketMandate.clientId ||
!basketMandate.chain138VaultAddress?.startsWith('0x')
) {
res.status(400).json({
error: 'basketMandate with id, clientId, chain138VaultAddress (0x) required',
});
return;
}
const expectedAmountSats =
req.body?.expectedAmountSats != null
? Number(req.body.expectedAmountSats)
: undefined;
const instruction = await watcher.createDepositInstruction({
basketMandate,
expectedAmountSats,
});
res.status(201).json(instruction);
} catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
app.get('/deposit-instructions/:id', (req, res) => {
const instruction = watcher.getInstruction(req.params.id);
if (!instruction) {
res.status(404).json({ error: 'not found' });
return;
}
res.json(instruction);
});
app.post('/sync', async (_req, res) => {
await runSync();
res.json({
ok: !lastSyncError,
lastSyncAt,
lastSyncError,
mintJobs: watcher.getMintJobs(),
});
});
app.get('/mint-jobs', (_req, res) => {
res.json({ jobs: watcher.getMintJobs() });
});
setInterval(() => {
void runSync();
}, SYNC_INTERVAL_MS);
void runSync();
app.listen(PORT, () => {
// eslint-disable-next-line no-console
console.log(`[btc-intake] listening on :${PORT} (sync every ${SYNC_INTERVAL_MS}ms)`);
});
}
main().catch((err) => {
// eslint-disable-next-line no-console
console.error('[btc-intake] fatal', err);
process.exit(1);
});