"use strict"; // Unstoppable Domains resolver — the whole UD TLD roster except .x, which // belongs to BNS/Sirius. "BNS wins on .x" is the design rule: a Sirius // registration must always resolve to the Sirius record, so this adapter // treats .x as not-ours and returns null, letting the chain fall through. // // Resolution: HTTPS GET to resolve.unstoppabledomains.com/domains/. // Response shape (as of 2026-10): // { records: { // "browser.redirect_url": "https://…", // "ipfs.html.value": "", // "dweb.ipfs.hash": "", // ... // }, // meta: { owner, resolver, blockchain, networkId } } // // Precedence inside the record set: browser.redirect_url > ipfs.html.value // > dweb.ipfs.hash. Users who want a different gateway swap it in Advanced. const POS_TTL_MS = 10 * 60 * 1000; const NEG_TTL_MS = 60 * 1000; const RPC_TIMEOUT_MS = 5000; const CACHE = new Map(); // TLDs on Unstoppable's registries (crypto + .nft + the Polygon-era batch). // Trimmed, not exhaustive — Unstoppable keeps adding new ones; a user whose // TLD isn't here can toggle Handshake "hns-wins" or add a per-host override // later. We explicitly skip .x — BNS owns .x in Theseus. const UD_TLDS = new Set([ "crypto", "nft", "wallet", "bitcoin", "blockchain", "dao", "888", "polygon", "klever", "hi", "kresus", "anime", "manga", "go", "unstoppable", "pudgy", "binanceus", "austin", "bitget", "tball", "secret", "farms", "raiin", "stepn", "emir", "smokey", "lfg", "pog", "witg", "metropolis", "wrkx", "benji", "altimist", "ethermail", ]); async function udFetch(base, name) { const url = base.replace(/\/+$/, "") + "/domains/" + encodeURIComponent(name); const ctrl = new AbortController(); const t = setTimeout(() => ctrl.abort(new Error("Unstoppable timeout")), RPC_TIMEOUT_MS); try { const res = await fetch(url, { method: "GET", headers: { "accept": "application/json", "user-agent": "Theseus/0.3.99 (silentmode.st)" }, signal: ctrl.signal, }); if (!res.ok) throw new Error(`Unstoppable HTTP ${res.status}`); return await res.json(); } finally { clearTimeout(t); } } async function resolve(host, { endpoint, gateway } = {}) { if (!host || !host.includes(".")) return null; const name = String(host).toLowerCase(); const tld = name.split(".").pop(); if (!UD_TLDS.has(tld)) return null; // not an Unstoppable TLD // BNS owns .x — never shadow a Sirius registration with an Unstoppable one. if (tld === "x") return null; const cached = CACHE.get(name); if (cached && cached.exp > Date.now()) return cached.result; const base = endpoint || "https://resolve.unstoppabledomains.com"; const gw = (gateway || "https://ipfs.io").replace(/\/+$/, ""); try { const data = await udFetch(base, name); const records = data?.records || {}; const redirect = records["browser.redirect_url"]; if (redirect && typeof redirect === "string") { const u = /^https?:\/\//i.test(redirect) ? redirect : `https://${redirect}`; const result = { url: u, protocol: "unstoppable", kind: "redirect", target: u }; CACHE.set(name, { result, exp: Date.now() + POS_TTL_MS }); return result; } const ipfs = records["ipfs.html.value"] || records["dweb.ipfs.hash"]; if (ipfs && typeof ipfs === "string") { const result = { url: `${gw}/ipfs/${ipfs}`, protocol: "unstoppable", kind: "ipfs", cid: ipfs }; CACHE.set(name, { result, exp: Date.now() + POS_TTL_MS }); return result; } const result = { error: "no browser.redirect_url or ipfs record" }; CACHE.set(name, { result, exp: Date.now() + NEG_TTL_MS }); return result; } catch (err) { const result = { error: String(err?.message || err) }; CACHE.set(name, { result, exp: Date.now() + NEG_TTL_MS }); return result; } } function clearCache() { CACHE.clear(); } module.exports = { resolve, clearCache, UD_TLDS };