77 行
2.3 KiB
JavaScript
77 行
2.3 KiB
JavaScript
const { JsonRpcProvider } = require('ethers');
|
|
const { readDb, writeDb } = require('../storage');
|
|
const { getEthereumRpcUrls, createRoundRobinPicker } = require('../utils/rpc');
|
|
|
|
function startEvmWatcher({ notifyMerchant }) {
|
|
const rpcUrls = getEthereumRpcUrls();
|
|
if (!rpcUrls.length) {
|
|
console.warn('[evmWatcher] No Ethereum 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 === 'evm:ethereum' && tx.status === 'pending');
|
|
if (pending.length === 0) {
|
|
running = false;
|
|
return;
|
|
}
|
|
|
|
const rpcUrl = pickRpcUrl();
|
|
const provider = new JsonRpcProvider(rpcUrl);
|
|
const latestBlock = await provider.getBlockNumber();
|
|
const now = new Date().toISOString();
|
|
const confirmedEvents = [];
|
|
|
|
for (const tx of pending.slice(0, maxPerCycle)) {
|
|
const receipt = await provider.getTransactionReceipt(tx.txHash);
|
|
tx.lastCheckedAt = now;
|
|
|
|
if (!receipt) continue;
|
|
|
|
const confirmations = latestBlock - receipt.blockNumber + 1;
|
|
tx.confirmations = confirmations;
|
|
tx.updatedAt = now;
|
|
|
|
if (receipt.status === 1 && 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 });
|
|
}
|
|
} else if (receipt.status === 0) {
|
|
tx.status = 'failed';
|
|
}
|
|
}
|
|
|
|
await writeDb(db);
|
|
|
|
for (const event of confirmedEvents) {
|
|
await notifyMerchant(event.order, event.tx);
|
|
}
|
|
} catch (error) {
|
|
console.error('[evmWatcher] poll error', error.message);
|
|
} finally {
|
|
running = false;
|
|
}
|
|
}
|
|
|
|
poll();
|
|
setInterval(poll, pollInterval);
|
|
console.log(`[evmWatcher] started with interval ${pollInterval}ms`);
|
|
}
|
|
|
|
module.exports = { startEvmWatcher };
|