"use strict"; const dns2 = require("dns2"); const { default: Resolution } = require("@unstoppabledomains/resolution"); const LISTEN_HOST = process.env.ZILDNS_LISTEN_HOST || "0.0.0.0"; const LISTEN_PORT = Number(process.env.ZILDNS_LISTEN_PORT || "53"); const DEFAULT_TTL = Number(process.env.ZILDNS_TTL || "120"); const CACHE_SECONDS = Number(process.env.ZILDNS_CACHE_SECONDS || "30"); const ZNS_URL = process.env.ZILDNS_ZNS_URL || "https://api.zilliqa.com"; const ZNS_NETWORK = process.env.ZILDNS_ZNS_NETWORK || "mainnet"; const PREWARM_DOMAIN = normalizePrewarmDomain(process.env.ZILDNS_PREWARM_DOMAIN || "brad.zil"); const { Packet, UDPServer, TCPServer } = dns2; const RCODE = { NOERROR: 0, SERVFAIL: 2, NXDOMAIN: 3, }; const resolution = new Resolution({ sourceConfig: { zns: { url: ZNS_URL, network: ZNS_NETWORK, }, }, }); const cache = new Map(); function normalizeName(name) { return String(name || "").replace(/\.$/, "").toLowerCase(); } function normalizePrewarmDomain(name) { const normalized = normalizeName(name); return normalized.endsWith(".zil") ? normalized : ""; } function cacheGet(key) { const entry = cache.get(key); if (!entry) { return null; } if (Date.now() > entry.expiresAt) { cache.delete(key); return null; } return entry.value; } function cacheSet(key, value) { cache.set(key, { value, expiresAt: Date.now() + CACHE_SECONDS * 1000, }); } function withTimeout(promise, ms) { return Promise.race([ promise, new Promise((_, reject) => setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms) ) ]); } async function resolveZilDomain(domain) { const cached = cacheGet(domain); if (cached !== null) { return cached; } try { const [zilResult, ipfsResult] = await withTimeout( Promise.allSettled([ resolution.addr(domain, "ZIL"), resolution.ipfsHash(domain), ]), 5000 // 5 second timeout ); const result = { zilAddress: zilResult.status === "fulfilled" && zilResult.value ? zilResult.value : null, ipfsHash: ipfsResult.status === "fulfilled" && ipfsResult.value ? ipfsResult.value : null, }; const hasData = Boolean(result.zilAddress || result.ipfsHash); const resolved = hasData ? result : null; cacheSet(domain, resolved); return resolved; } catch (err) { console.error(`[zildns] API failed for ${domain}:`, err.message); // Cache negative result for a short time to avoid repeated API failures cacheSet(domain, null); return null; } } function makeResponse(request) { const response = Packet.createResponseFromRequest(request); response.header.aa = 1; response.header.ra = 1; response.header.z = 0; response.header.tc = 0; return response; } function addTxtAnswers(response, name, resolved) { if (resolved.zilAddress) { response.answers.push({ name, type: Packet.TYPE.TXT, class: Packet.CLASS.IN, ttl: DEFAULT_TTL, data: `zil_address=${resolved.zilAddress}`, }); } if (resolved.ipfsHash) { response.answers.push({ name, type: Packet.TYPE.TXT, class: Packet.CLASS.IN, ttl: DEFAULT_TTL, data: `ipfs_hash=${resolved.ipfsHash}`, }); } } function addCnameAnswer(response, name, resolved) { if (!resolved.ipfsHash) { return; } response.answers.push({ name, type: Packet.TYPE.CNAME, class: Packet.CLASS.IN, ttl: DEFAULT_TTL, domain: `${resolved.ipfsHash}.ipfs.dweb.link.`, }); } async function buildResponse(request) { const response = makeResponse(request); const question = request.questions && request.questions[0]; if (!question) { return response; } const qname = normalizeName(question.name); const qtype = question.type; const isType = (name) => qtype === name || qtype === Packet.TYPE[name]; if (!qname.endsWith(".zil")) { response.header.rcode = RCODE.NXDOMAIN; return response; } const resolved = await resolveZilDomain(qname); if (!resolved) { response.header.rcode = RCODE.NXDOMAIN; return response; } if (isType("TXT") || isType("ANY")) { addTxtAnswers(response, question.name, resolved); } if (isType("CNAME") || isType("A") || isType("AAAA") || isType("ANY")) { addCnameAnswer(response, question.name, resolved); } return response; } async function maybePrewarm() { if (!PREWARM_DOMAIN) { console.log("[zildns] prewarm disabled"); return; } const started = Date.now(); try { await resolveZilDomain(PREWARM_DOMAIN); console.log(`[zildns] prewarm complete for ${PREWARM_DOMAIN} in ${Date.now() - started}ms`); } catch (err) { console.warn( `[zildns] prewarm failed for ${PREWARM_DOMAIN}:`, err && err.message ? err.message : String(err), ); } } async function start() { await maybePrewarm(); const udpServer = new UDPServer((request, send) => { buildResponse(request) .then((response) => { send(response); }) .catch((err) => { console.error("[zildns] request failed:", err.message); const response = makeResponse(request); response.header.rcode = RCODE.SERVFAIL; send(response); }); }); const tcpServer = new TCPServer((request, send) => { buildResponse(request) .then((response) => { send(response); }) .catch((err) => { console.error("[zildns] request failed:", err.message); const response = makeResponse(request); response.header.rcode = RCODE.SERVFAIL; send(response); }); }); udpServer.on("error", (err) => { console.error("[zildns] udp error:", err); }); tcpServer.on("error", (err) => { console.error("[zildns] tcp error:", err); }); udpServer.listen(LISTEN_PORT, LISTEN_HOST); tcpServer.listen(LISTEN_PORT, LISTEN_HOST); console.log(`[zildns] listening on ${LISTEN_HOST}:${LISTEN_PORT} (udp/tcp)`); console.log(`[zildns] zns provider ${ZNS_URL} (${ZNS_NETWORK})`); return { udpServer, tcpServer }; } start().catch((err) => { console.error("[zildns] startup failed:", err); process.exit(1); });