import fs from "node:fs"; import { parseArgs } from "node:util"; import { prefetchRates } from "./src/api.js"; import { Bot } from "./src/bot.js"; import logger from "./src/logger.js"; import { initDB, insertIntoDB, closePool } from "./src/db.js"; import { startServer } from "./src/server.js"; process.on("uncaughtException", (err) => { logger.fatal(err, "Uncaught exception. Application crashing..."); process.exit(1); }); process.on("unhandledRejection", (reason) => { logger.fatal(reason, "Unhandled promise Rejection"); process.exit(1); }); const options = { pairs: { type: "string", short: "p", default: process.env.PAIRS || "BTC-USD", }, interval: { type: "string", short: "i", default: process.env.INTERVAL || "5000", }, threshold: { type: "string", short: "t", default: process.env.THRESHOLD || "0.01", }, stats: { type: "boolean", default: process.env.STATS === "true", description: "Show performance stats periodically", }, }; const { values } = parseArgs({ options, strict: true, allowPositionals: true, args: process.argv.slice(2), }); const pairs = values.pairs.split(",").map((p) => p.trim()); const interval = parseInt(values.interval, 10); const threshold = parseFloat(values.threshold); if (isNaN(interval) || interval < 100) { logger.error("Interval must be a number larger than 100ms"); process.exit(1); } if (pairs.length === 0 || pairs.some((p) => !p.match(/^[A-Z]+-?[A-Z]+$/))) { logger.error("Invalid pair format (expected BTC-USD or CNYUSD)"); process.exit(1); } if (threshold <= 0 || threshold > 100) { logger.error("Threshold must be between 0 and 100"); process.exit(1); } async function main() { logger.info("Uphold price alert bot starting..."); logger.info( `[WATCHING] ${pairs.join(", ")} | Every ${interval}ms | Threshold ${threshold}%`, ); await initDB(); startServer(); try { await prefetchRates(pairs); } catch (err) { logger.error(err, "Critical failure during cache warming"); process.exit(1); } fs.promises.writeFile("/tmp/healthy", "ok"); const handleAlert = async (alertData) => { await insertIntoDB(alertData); }; const bots = pairs.map((pair) => { return new Bot( { pair, interval, threshold, }, handleAlert, ); }); bots.forEach((b) => b.start()); const shutdown = async () => { logger.info("Shutting down..."); bots.forEach((b) => b.stop()); await new Promise((resolve) => setTimeout(resolve, 1000)); await closePool(); process.exit(0); }; process.on("SIGINT", shutdown); process.on("SIGTERM", shutdown); } main().catch((err) => { logger.fatal(err, "Fatal error in main loop"); process.exit(1); });