From 011cc09e3ab13ab68b950db61520250d20446a6e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Apr 2026 22:42:14 +0100 Subject: [PATCH] feat(status-api): initial push Co-Authored-By: Claude Opus 4.6 (1M context) --- bot.js | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 bot.js diff --git a/bot.js b/bot.js new file mode 100644 index 0000000..041902f --- /dev/null +++ b/bot.js @@ -0,0 +1,148 @@ +#!/usr/bin/env node +/** + * Lethean Chain Status Bot + * + * Lightweight HTTP API that returns chain status in formats suitable for + * embedding in Telegram/Discord bots, status pages, or shell scripts. + * + * Endpoints: + * GET / — human-readable status + * GET /json — full JSON status + * GET /height — just the height (plain text) + * GET /badge — shields.io compatible badge JSON + * GET /check/:h — check if a given height has been reached + * + * Usage: + * node bot.js + * curl http://localhost:8101/height → "10969" + * curl http://localhost:8101/json → {"height":10969,"hf":[0,1,2,3],...} + * curl http://localhost:8101/check/11000 → {"reached":false,"current":10969,"remaining":31} + * curl http://localhost:8101/badge → shields.io badge JSON + */ + +const http = require('http'); + +const PORT = process.env.PORT || 8101; +const DAEMON = process.env.DAEMON_URL || 'http://127.0.0.1:46941'; + +async function getChainInfo() { + try { + const resp = await fetch(`${DAEMON}/json_rpc`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ jsonrpc: '2.0', id: '0', method: 'getinfo' }), + }); + const data = await resp.json(); + const r = data.result; + return { + height: r.height, + hf: r.is_hardfok_active.map((v, i) => v ? i : null).filter(v => v !== null), + difficulty: r.pow_difficulty, + aliases: r.alias_count, + txPool: r.tx_pool_size, + posAllowed: r.pos_allowed, + synced: r.daemon_network_state === 2, + }; + } catch { + return null; + } +} + +const server = http.createServer(async (req, res) => { + const url = new URL(req.url, `http://localhost:${PORT}`); + res.setHeader('Access-Control-Allow-Origin', '*'); + + const info = await getChainInfo(); + if (!info) { + res.writeHead(503, { 'Content-Type': 'application/json' }); + return res.end('{"error":"daemon unreachable"}'); + } + + // Plain height + if (url.pathname === '/height') { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + return res.end(String(info.height)); + } + + // Full JSON + if (url.pathname === '/json') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + return res.end(JSON.stringify(info)); + } + + // Height check + const checkMatch = url.pathname.match(/^\/check\/(\d+)$/); + if (checkMatch) { + const target = parseInt(checkMatch[1]); + res.writeHead(200, { 'Content-Type': 'application/json' }); + return res.end(JSON.stringify({ + reached: info.height >= target, + current: info.height, + target, + remaining: Math.max(0, target - info.height), + eta: info.height < target ? `~${(target - info.height) * 2} min` : 'now', + })); + } + + // Shields.io badge + if (url.pathname === '/badge') { + const color = info.height >= 11000 ? 'brightgreen' : info.height >= 10500 ? 'yellow' : 'blue'; + res.writeHead(200, { 'Content-Type': 'application/json' }); + return res.end(JSON.stringify({ + schemaVersion: 1, + label: 'Lethean Testnet', + message: `height ${info.height} | HF${info.hf[info.hf.length - 1]}`, + color, + })); + } + + // HTML dashboard + if (url.pathname === '/dashboard') { + const hf5eta = info.height >= 11500 ? 'ACTIVE' : `${11500 - info.height} blocks (~${Math.round((11500 - info.height) * 2 / 60)}h)`; + res.writeHead(200, { 'Content-Type': 'text/html' }); + return res.end(`Lethean Status + + +

Lethean Network Status

+ + + + +
ChainHeight ${info.height} | HF${info.hf[info.hf.length-1]} | ${info.aliases} aliases | Pool: ${info.txPool} tx
HF5 (ITNS)${hf5eta}
Synced${info.synced?'YES':'NO'}
+

Services

+ + + + + + + + + +
ServiceURL
Explorerexplorer.lthn.io
Trade DEXtrade.lthn.io
Docstestnet-docs.lthn.io
Downloadsdownloads.lthn.io
VPN Configvpn.lthn.io
SWAPswap.lthn.io
LNS DNSdig @10.69.69.165 charon.lthn A
LNS APIlns.lthn.io/health
+

Quick Start

+
curl -sL https://downloads.lthn.io/setup.sh | bash
+

Auto-refreshes every 30s | JSON API | Badge

+`); + } + + // Human-readable + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end([ + `Lethean Testnet Status`, + ` Height: ${info.height}`, + ` HF Active: ${info.hf.join(', ')}`, + ` Difficulty: ${info.difficulty}`, + ` Aliases: ${info.aliases}`, + ` TX Pool: ${info.txPool}`, + ` PoS: ${info.posAllowed ? 'enabled' : 'disabled'}`, + ` Synced: ${info.synced}`, + ``, + ` HF4 (11000): ${info.height >= 11000 ? 'ACTIVE' : `in ${11000 - info.height} blocks (~${(11000 - info.height) * 2} min)`}`, + ` HF5 (11500): ${info.height >= 11500 ? 'ACTIVE' : `in ${11500 - info.height} blocks`}`, + ].join('\n')); +}); + +server.listen(PORT, () => console.log(`Chain status bot on :${PORT}`));