theseus/lib/resolvers/unstoppable.cjs
Local Dev 208a192fc9 Theseus 0.3.99: SNS + Unstoppable adapters, Hermes moves to the sidebar
Two more resolvers under Settings › Names & Certificates. SNS — .sol names
resolve through Bonfida's public SDK proxy, preferring an explicit URL
record over an IPFS record (served via a configurable IPFS gateway).
Unstoppable Domains — the full UD TLD roster (.crypto, .nft, .wallet,
.bitcoin, .blockchain, .dao, …) through resolve.unstoppabledomains.com;
browser.redirect_url takes precedence over an ipfs record. .x stays with
BNS — the UD adapter explicitly skips it so a Sirius registration can
never be shadowed by an Unstoppable one.

Both proxies are Cloudflare-fronted; users on blocked IPs can swap the
endpoint in Advanced. The handshake adapter's skip-list grows to cover
.sol and every UD TLD so a user with HNS + UD + SNS all enabled gets the
specific adapter, never the generic hns.to gateway, for those names.

Hermes.X moves from the home page's Silent Mode card grid to the Theseus
left-sidebar quick-link strip, slotting in right after Proton Mail. The
default list grows to 6 entries; existing profiles pick it up through the
LATER_DEFAULTS one-time offer (same mechanism Proton used in 0.3.88).
Users who still have Hermes on their custom home page can remove it manually.
2026-10-21 19:12:19 +03:00

95 lines
3.9 KiB
JavaScript

"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/<name>.
// Response shape (as of 2026-10):
// { records: {
// "browser.redirect_url": "https://…",
// "ipfs.html.value": "<cid>",
// "dweb.ipfs.hash": "<cid>",
// ...
// },
// 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 };