theseus/lib/resolvers/sns.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

83 lines
3.5 KiB
JavaScript

"use strict";
// Solana Name Service resolver — .sol names via Bonfida's public SDK proxy.
// SNS stores URL and IPFS records on-chain; the proxy exposes them as a
// REST endpoint so Theseus doesn't need the Solana web3.js stack bundled in.
//
// Proxy responses (as of 2026-10):
// GET /resolve/<name> → { s: "ok", result: "<address or url>" }
// or { s: "error", result: "..." }
// GET /record/<name>/url → { s: "ok", result: "https://…" }
// GET /record/<name>/ipfs → { s: "ok", result: "<cid>" }
// GET /record/<name>/arwv → { s: "ok", result: "<arweave-tx>" }
//
// We prefer URL (clearnet redirect) over IPFS (gateway) over Arweave
// (gateway) because URL is explicit intent and the user's gateway choice
// shouldn't matter for a plain redirect.
//
// If the user's IP is Cloudflare-blocked for the default proxy, they can
// paste any Solana RPC + sns-sdk-proxy compatible endpoint into Advanced.
const POS_TTL_MS = 10 * 60 * 1000;
const NEG_TTL_MS = 60 * 1000;
const RPC_TIMEOUT_MS = 5000;
const CACHE = new Map();
async function proxyGet(base, path) {
const url = base.replace(/\/+$/, "") + path;
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(new Error("SNS proxy 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(`SNS HTTP ${res.status}`);
const json = await res.json();
if (json?.s === "ok" && json.result) return String(json.result);
return null; // no record / name not registered
} finally { clearTimeout(t); }
}
async function resolve(host, { rpc, gateway } = {}) {
if (!host) return null;
const name = String(host).toLowerCase();
if (!name.endsWith(".sol") || name === ".sol" || name === "sol") return null;
const cached = CACHE.get(name);
if (cached && cached.exp > Date.now()) return cached.result;
// "rpc" here is the SNS proxy base URL (not a Solana JSON-RPC); naming
// is for consistency with the other adapters.
const base = rpc || "https://sns-sdk-proxy.bonfida.workers.dev";
const gw = (gateway || "https://ipfs.io").replace(/\/+$/, "");
const bareName = name.replace(/\.sol$/i, "");
try {
// URL takes precedence (explicit redirect intent).
const urlRec = await proxyGet(base, `/record/${encodeURIComponent(bareName)}.sol/url`).catch(() => null);
if (urlRec) {
const u = /^https?:\/\//i.test(urlRec) ? urlRec : `https://${urlRec}`;
const result = { url: u, protocol: "sns", kind: "url" };
CACHE.set(name, { result, exp: Date.now() + POS_TTL_MS });
return result;
}
// IPFS fallback.
const ipfsRec = await proxyGet(base, `/record/${encodeURIComponent(bareName)}.sol/ipfs`).catch(() => null);
if (ipfsRec) {
const result = { url: `${gw}/ipfs/${ipfsRec}`, protocol: "sns", kind: "ipfs", cid: ipfsRec };
CACHE.set(name, { result, exp: Date.now() + POS_TTL_MS });
return result;
}
const result = { error: "no URL or IPFS record on this .sol name" };
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 };