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.
95 lines
3.9 KiB
JavaScript
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 };
|