#!/usr/bin/env node // Checks that a deployed site answers /api/stats and /api/supply with every documented field (the FIELDS lists in // site/api/stats.mjs and site/api/supply.mjs, the same lists docs/api/public-stats.md is written from). // node tools/ci/public-api-check.mjs [https://igneum.network] // Exit 1 on a missing field, a non-200 answer or a stale observer older than 10 minutes (the numbers are what pool // software and profitability sites read; a stale answer is a wrong answer). import { FIELDS as STATS, REWARD_FIELDS, LAST_BLOCK_FIELDS } from '../../site/api/stats.mjs'; import { FIELDS as SUPPLY, SCHEDULE_FIELDS } from '../../site/api/supply.mjs'; const base = (process.argv[2] || process.env.PUBLIC_API_URL || 'https://igneum.network').replace(/\/$/, ''); const missing = []; async function get(path) { const r = await fetch(base + path, { headers: { 'cache-control': 'no-cache' }, signal: AbortSignal.timeout(15000) }); const j = await r.json().catch(() => null); if (r.status !== 200 || !j || j.ok !== true) { console.error(`${path}: HTTP ${r.status} ${j ? JSON.stringify(j).slice(0, 200) : ''}`); process.exit(1); } return j; } const s = await get('/api/stats'); for (const f of STATS) if (!(f in s)) missing.push(`stats.${f}`); if (s.block_reward) for (const f of REWARD_FIELDS) if (!(f in s.block_reward)) missing.push(`stats.block_reward.${f}`); if (s.last_block) for (const f of LAST_BLOCK_FIELDS) if (!(f in s.last_block)) missing.push(`stats.last_block.${f}`); const p = await get('/api/supply'); for (const f of SUPPLY) if (!(f in p)) missing.push(`supply.${f}`); if (p.schedule && p.schedule[0]) for (const f of SCHEDULE_FIELDS) if (!(f in p.schedule[0])) missing.push(`supply.schedule.${f}`); if (missing.length) { console.error(`public api check: ${missing.length} missing field(s): ${missing.join(', ')}`); process.exit(1); } if (s.stale && s.age_s > 600) { console.error(`public api check: the observer is stale (${s.age_s} s)`); process.exit(1); } console.log(`public api check: ${base} /api/stats (${STATS.length} fields, height ${s.height}, reward ${s.block_reward && s.block_reward.ign} IGN, ${s.stale ? 'STALE ' + s.age_s + ' s' : 'live'}) and /api/supply (${SUPPLY.length} fields, circulating ${p.circulating_ign} IGN of ${p.max_supply_ign}) answer with every documented field`);