refactor: move project under backend

这个提交包含在:
cryptocommuniums-afk
2026-02-02 10:12:26 +08:00
父节点 810d0420d6
当前提交 616f9bd8c6
修改 27 个文件,包含 35 行新增16 行删除

查看文件

@@ -0,0 +1,103 @@
const { readDb, writeDb } = require('../storage');
const { getBitcoinRpcUrls, createRoundRobinPicker } = require('../utils/rpc');
const BTC_NETWORK = 'btc:mainnet';
async function callRpc(url, method, params = []) {
const body = JSON.stringify({
jsonrpc: '1.0',
id: 'capay',
method,
params
});
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body
});
if (!response.ok) {
throw new Error(`rpc ${method} failed (${response.status})`);
}
const payload = await response.json();
if (payload.error) {
const message = payload.error.message || JSON.stringify(payload.error);
throw new Error(`rpc ${method} error: ${message}`);
}
return payload.result;
}
function startBtcWatcher({ notifyMerchant }) {
const rpcUrls = getBitcoinRpcUrls();
if (!rpcUrls.length) {
console.warn('[btcWatcher] No Bitcoin RPC URLs configured. Skipping watcher.');
return;
}
const pollInterval = Number(process.env.TX_POLL_INTERVAL_MS || 180000);
const maxPerCycle = Number(process.env.MAX_TX_CHECK_PER_CYCLE || 20);
const minConfirmations = Number(process.env.MIN_CONFIRMATIONS || 1);
const pickRpcUrl = createRoundRobinPicker(rpcUrls);
let running = false;
async function poll() {
if (running) return;
running = true;
try {
const db = await readDb();
const pending = db.txs.filter((tx) => tx.network === BTC_NETWORK && tx.status === 'pending');
if (pending.length === 0) {
running = false;
return;
}
const rpcUrl = pickRpcUrl();
const now = new Date().toISOString();
const confirmedEvents = [];
for (const tx of pending.slice(0, maxPerCycle)) {
try {
const result = await callRpc(rpcUrl, 'getrawtransaction', [tx.txHash, true]);
const confirmations = Number(result?.confirmations || 0);
tx.confirmations = confirmations;
tx.lastCheckedAt = now;
tx.updatedAt = now;
if (confirmations >= minConfirmations) {
tx.status = 'confirmed';
const order = db.orders.find((item) => item.id === tx.orderId);
if (order) {
order.status = 'paid';
order.updatedAt = now;
confirmedEvents.push({ order, tx });
}
}
} catch (error) {
tx.lastCheckedAt = now;
console.warn('[btcWatcher] rpc error', tx.txHash, error.message);
}
}
await writeDb(db);
for (const event of confirmedEvents) {
await notifyMerchant(event.order, event.tx);
}
} catch (error) {
console.error('[btcWatcher] poll error', error.message);
} finally {
running = false;
}
}
poll();
setInterval(poll, pollInterval);
console.log(`[btcWatcher] started with interval ${pollInterval}ms`);
}
module.exports = { startBtcWatcher };