add project

This commit is contained in:
ZOMBIIIIIII
2026-04-08 14:11:27 +03:00
parent bfa95223a0
commit a81e29807c
115 changed files with 18413 additions and 0 deletions

View File

@@ -0,0 +1,148 @@
import { Request, Response, Router } from 'express';
const router = Router();
const BLOCKSTREAM_BASE = 'https://blockstream.info/api';
const BTC_TIMEOUT_MS = 10_000;
// Validate Bitcoin address format (mainnet only)
const BTC_ADDRESS_RE = /^(bc1[a-zA-HJ-NP-Z0-9]{25,62}|[13][a-km-zA-HJ-NP-Z1-9]{25,34})$/;
router.get('/utxos/:address', getUtxos);
router.get('/fee-estimates', getFeeEstimates);
router.post('/broadcast', broadcastTx);
export default router;
/**
* GET /api/btc/utxos/:address
* Returns confirmed UTXOs for the given address.
*/
async function getUtxos(req: Request, res: Response) {
const address = String(req.params.address);
if (!BTC_ADDRESS_RE.test(address)) {
res.status(400).json({ success: false, error: 'Invalid Bitcoin address' });
return;
}
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), BTC_TIMEOUT_MS);
try {
const response = await fetch(`${BLOCKSTREAM_BASE}/address/${address}/utxo`, {
headers: { Accept: 'application/json' },
signal: controller.signal,
});
if (!response.ok) {
res.status(response.status).json({ success: false, error: 'Blockstream API error' });
return;
}
const utxos = await response.json();
// Filter to confirmed only
const confirmed = (utxos as Array<{ status: { confirmed: boolean }; txid: string; vout: number; value: number }>)
.filter((u) => u.status?.confirmed)
.map((u) => ({
txid: u.txid,
vout: u.vout,
value: u.value,
}));
res.json({ success: true, data: confirmed });
} catch (error) {
if (controller.signal.aborted) {
res.status(504).json({ success: false, error: 'Blockstream request timeout' });
return;
}
res.status(502).json({ success: false, error: 'Failed to reach Blockstream' });
} finally {
clearTimeout(timeout);
}
}
/**
* GET /api/btc/fee-estimates
* Returns fee rate estimates in sat/vB for different confirmation targets.
*/
async function getFeeEstimates(_req: Request, res: Response) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), BTC_TIMEOUT_MS);
try {
const response = await fetch(`${BLOCKSTREAM_BASE}/fee-estimates`, {
headers: { Accept: 'application/json' },
signal: controller.signal,
});
if (!response.ok) {
res.status(response.status).json({ success: false, error: 'Blockstream fee estimates error' });
return;
}
const data = await response.json();
// Return top 3 tiers: 1-block, 3-block, 6-block confirmation targets
const estimates = data as Record<string, number>;
res.json({
success: true,
data: {
fast: Math.ceil(estimates['1'] ?? estimates['2'] ?? 10),
normal: Math.ceil(estimates['3'] ?? estimates['6'] ?? 5),
slow: Math.ceil(estimates['6'] ?? estimates['12'] ?? 2),
},
});
} catch (error) {
if (controller.signal.aborted) {
res.status(504).json({ success: false, error: 'Blockstream fee estimates timeout' });
return;
}
res.status(502).json({ success: false, error: 'Failed to reach Blockstream' });
} finally {
clearTimeout(timeout);
}
}
/**
* POST /api/btc/broadcast
* Broadcasts a raw transaction hex.
*/
async function broadcastTx(req: Request, res: Response) {
const { hex } = req.body;
if (!hex || typeof hex !== 'string' || !/^[0-9a-fA-F]+$/.test(hex)) {
res.status(400).json({ success: false, error: 'Invalid transaction hex' });
return;
}
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), BTC_TIMEOUT_MS);
try {
const response = await fetch(`${BLOCKSTREAM_BASE}/tx`, {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: hex,
signal: controller.signal,
});
const text = await response.text();
if (!response.ok) {
res.status(response.status).json({ success: false, error: text || 'Broadcast failed' });
return;
}
// Blockstream returns the txid as plain text
res.json({ success: true, data: { txid: text.trim() } });
} catch (error) {
if (controller.signal.aborted) {
res.status(504).json({ success: false, error: 'Broadcast timeout' });
return;
}
res.status(502).json({ success: false, error: 'Failed to broadcast transaction' });
} finally {
clearTimeout(timeout);
}
}