alias-dns-bridge/index.js

135 lines
3.8 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
/**
* Lethean Alias-DNS Bridge
*
* Reads chain aliases via daemon RPC, parses v=lthn1 comment format,
* and validates HNS zone bindings. Outputs a JSON registry of
* alias HNS mappings with verification status.
*
* Usage:
* node index.js [--daemon http://127.0.0.1:46941] [--interval 60]
*/
const DAEMON_URL = process.env.DAEMON_URL || process.argv.find(a => a.startsWith('--daemon='))?.split('=')[1] || 'http://127.0.0.1:46941';
const POLL_INTERVAL = parseInt(process.env.POLL_INTERVAL || '60') * 1000;
async function rpc(method, params = {}) {
const resp = await fetch(`${DAEMON_URL}/json_rpc`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: '0', method, params })
});
const data = await resp.json();
if (data.error) throw new Error(`RPC ${method}: ${data.error.message}`);
return data.result;
}
/**
* Parse alias comment in v=lthn1 format
* Format: v=lthn1;hns=name.lthn;type=gateway;cap=vpn,dns
*/
function parseComment(comment) {
if (!comment || !comment.includes('v=lthn1')) return null;
const fields = {};
comment.split(';').forEach(pair => {
const [key, ...rest] = pair.trim().split('=');
if (key && rest.length) {
fields[key.trim()] = rest.join('=').trim();
}
});
if (fields.v !== 'lthn1') return null;
return {
version: fields.v,
hns: fields.hns || null,
type: fields.type || 'user',
capabilities: fields.cap ? fields.cap.split(',') : [],
tls: fields.tls || null,
};
}
/**
* Fetch all aliases from chain and build registry
*/
async function buildRegistry() {
const result = await rpc('get_all_alias_details', {});
const aliases = result.aliases || [];
const registry = [];
for (const alias of aliases) {
const parsed = parseComment(alias.comment);
registry.push({
alias: alias.alias,
address: alias.address,
comment: alias.comment,
hasLthnBinding: parsed !== null,
hns: parsed?.hns || null,
type: parsed?.type || null,
capabilities: parsed?.capabilities || [],
tls: parsed?.tls || null,
trackingKey: alias.tracking_key || null,
});
}
return registry;
}
/**
* Check chain info for context
*/
async function getChainContext() {
const info = await rpc('getinfo');
return {
height: info.height,
aliasCount: info.alias_count,
hfActive: info.is_hardfok_active.map((v, i) => v ? i : null).filter(v => v !== null),
};
}
// Main loop
async function main() {
console.log(`Lethean Alias-DNS Bridge`);
console.log(`Daemon: ${DAEMON_URL}`);
console.log(`Poll interval: ${POLL_INTERVAL / 1000}s`);
console.log('');
while (true) {
try {
const chain = await getChainContext();
console.log(`[${new Date().toISOString()}] Height: ${chain.height} | Aliases: ${chain.aliasCount} | HF: [${chain.hfActive}]`);
if (chain.aliasCount > 0) {
const registry = await buildRegistry();
const withHns = registry.filter(r => r.hasLthnBinding);
console.log(` Total aliases: ${registry.length}`);
console.log(` With HNS binding: ${withHns.length}`);
for (const entry of withHns) {
console.log(` ${entry.alias}.lthn → ${entry.hns} [${entry.type}] caps=[${entry.capabilities}]`);
}
// Write registry to file
const fs = await import('fs');
fs.writeFileSync('/tmp/lthn-alias-registry.json', JSON.stringify({
timestamp: new Date().toISOString(),
chain: chain,
aliases: registry,
}, null, 2));
} else {
console.log(' No aliases registered yet (requires HF4)');
}
} catch (err) {
console.error(` Error: ${err.message}`);
}
await new Promise(r => setTimeout(r, POLL_INTERVAL));
}
}
main().catch(console.error);