135 lines
3.8 KiB
JavaScript
135 lines
3.8 KiB
JavaScript
|
|
#!/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);
|