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.
83 lines
3.5 KiB
JavaScript
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 };
|