"use strict"; // Handshake resolver — routes non-ICANN TLDs through a public HNS web // gateway (hns.to by default). hns.to receives `foo..hns.to`, looks up // the Handshake TLD, fetches the on-chain site, and serves it back. From // Theseus's side the adapter is a one-liner URL rewrite + a collision check // to decide whether to touch the hostname at all. // // The collision check matters: Handshake auctioned on-chain TLDs that // collide with real DNS names (e.g. ".app", ".dev"). We don't want to // hijack those by default. With collision_policy = "icann-wins" (default), // the adapter returns null for any TLD in a bundled "common ICANN" list so // clearnet takes over. With "hns-wins", we always try HNS first. // // The list isn't the full IANA root zone (~1600 entries). It's the few // hundred TLDs real users actually see: original gTLDs, popular new gTLDs, // every ccTLD. If a TLD escapes the list and happens to collide with an // HNS registration, the user can switch to "icann-wins" and whitelist via // per-host overrides (future). Nothing breaks silently — the collision is // opt-in by the user turning HNS on. const CACHE_TTL_MS = 10 * 60 * 1000; const CACHE = new Map(); // Common ICANN TLDs — the ones a browser would actually see in practice. // Not exhaustive; grown as needed. const ICANN_TLDS = new Set([ // original gTLDs "com","org","net","edu","gov","mil","int","arpa","info","biz","name","pro","museum","aero","coop","jobs","mobi","tel","travel","cat","asia","post","tv","me","cc","ws","bz","su","ac", // common new gTLDs "app","dev","io","co","xyz","online","site","shop","store","tech","cloud","blog","news","world","world","global","global","email","agency","media","design","photo","art","gallery","pub","bar","cafe","wine","rest","restaurant","hotel","travel","fitness","gym","run","bike","boats","car","cars","fish","flight","flights","garden","farm","pet","pizza","realtor","salon","school","soccer","software","solar","taxi","vet","yoga","academy","band","bank","beer","beauty","best","buzz","build","builders","cash","chat","church","city","claims","clinic","clothing","club","coach","codes","coffee","community","company","computer","construction","contact","contractors","cool","country","credit","dance","deals","delivery","dental","dentist","digital","direct","directory","discount","doctor","download","education","energy","engineer","engineering","enterprises","equipment","events","expert","exposed","express","family","fashion","finance","financial","fit","fitness","food","foundation","fund","furniture","games","gift","glass","gold","graphics","group","guide","guru","haus","healthcare","help","holdings","holiday","home","host","house","icu","immo","industries","ink","insurance","international","investments","jewelry","kitchen","land","lawyer","legal","life","lighting","limited","limo","link","live","loan","loans","lol","love","ltd","management","market","marketing","marketplace","mba","media","medical","money","mortgage","movie","music","network","ninja","parts","party","photo","photography","photos","pics","pictures","plumbing","plus","press","productions","properties","pub","quest","radio","recipes","reviews","rip","run","sale","salon","school","science","security","services","shoes","shop","show","singles","soccer","software","solar","solutions","space","sport","store","studio","style","supplies","supply","support","surgery","systems","taxi","team","tech","technology","theatre","tickets","today","tools","tours","town","trade","training","transfer","travel","tv","university","vacations","ventures","vet","video","villas","vision","vote","voyage","watch","website","wedding","wiki","win","work","works","world","xxx","zone","gay","ltda","health","hiphop","fail","flowers","forsale","free","garden","gives","giving","golf","loan","map","mom","organic","place","play","promo","review","rodeo","show","shows","sky","soccer","social","spa","style","sucks","survey","tech","tires","top","vote","wtf","vip","hey","kiwi","earth", // common ccTLDs (two-letter) "ac","ad","ae","af","ag","ai","al","am","ao","aq","ar","as","at","au","aw","ax","az","ba","bb","bd","be","bf","bg","bh","bi","bj","bm","bn","bo","br","bs","bt","bv","bw","by","bz","ca","cc","cd","cf","cg","ch","ci","ck","cl","cm","cn","co","cr","cu","cv","cw","cx","cy","cz","de","dj","dk","dm","do","dz","ec","ee","eg","er","es","et","eu","fi","fj","fk","fm","fo","fr","ga","gb","gd","ge","gf","gg","gh","gi","gl","gm","gn","gp","gq","gr","gs","gt","gu","gw","gy","hk","hm","hn","hr","ht","hu","id","ie","il","im","in","io","iq","ir","is","it","je","jm","jo","jp","ke","kg","kh","ki","km","kn","kp","kr","kw","ky","kz","la","lb","lc","li","lk","lr","ls","lt","lu","lv","ly","ma","mc","md","me","mg","mh","mk","ml","mm","mn","mo","mp","mq","mr","ms","mt","mu","mv","mw","mx","my","mz","na","nc","ne","nf","ng","ni","nl","no","np","nr","nu","nz","om","pa","pe","pf","pg","ph","pk","pl","pm","pn","pr","ps","pt","pw","py","qa","re","ro","rs","ru","rw","sa","sb","sc","sd","se","sg","sh","si","sj","sk","sl","sm","sn","so","sr","ss","st","sv","sx","sy","sz","tc","td","tf","tg","th","tj","tk","tl","tm","tn","to","tr","tt","tv","tw","tz","ua","ug","uk","us","uy","uz","va","vc","ve","vg","vi","vn","vu","wf","ws","ye","yt","za","zm","zw", ]); async function resolve(host, { endpoint, collision_policy } = {}) { if (!host || !host.includes(".")) return null; const name = String(host).toLowerCase(); // Collision gate — only skip ICANN TLDs when icann-wins is set (the default). const tld = name.split(".").pop(); if ((collision_policy || "icann-wins") === "icann-wins" && ICANN_TLDS.has(tld)) return null; // Other extra-resolver TLDs run before HNS in tryExtraResolvers — return // null here so Handshake doesn't shadow them when their toggle is off and // the user's intent was clearnet (e.g. a .crypto name that's not registered // on UD should 404 clearnet, not get hijacked into HNS). const CLAIMED = new Set([ "eth", "bit", "sol", // Unstoppable Domains TLDs "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", ]); if (CLAIMED.has(tld)) return null; const cached = CACHE.get(name); if (cached && cached.exp > Date.now()) return cached.result; const gw = (endpoint || "https://hns.to").replace(/\/+$/, "").replace(/^https?:\/\//, ""); const url = `https://${name}.${gw}`; const result = { url, protocol: "handshake", gateway: gw }; CACHE.set(name, { result, exp: Date.now() + CACHE_TTL_MS }); return result; } function clearCache() { CACHE.clear(); } module.exports = { resolve, clearCache, ICANN_TLDS };