#!/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}`));