/** * Looking up a BNS name's current on-chain owner via the Silent Mode gateway. * * The listing endpoint uses this to answer "did this publisher really sign * this?" — recover the signer address from the BIP-137 signature (that's just * cryptography) and compare it against what the chain says owns the name * (that's the trust claim). * * Memory [[recovery-is-not-verification]] is what makes this a two-step check * rather than one — recovery always succeeds against *some* key, so we must * compare the result against a claimed destination. * * MOCK_BNS_OWNER= short-circuits the network call for local tests * where we don't have a chipnet gateway reachable or don't want to depend on * one for a unit test. */ const DEFAULT_GATEWAY = (process.env.BNS_NETWORK || 'chipnet').toLowerCase() === 'mainnet' ? 'https://silentmode.st' : 'https://navigate.st'; const BNS_GATEWAY = process.env.BNS_GATEWAY || DEFAULT_GATEWAY; /** * Fetch a name's record from the gateway. Returns: * {ok: true, owner, records, registered: true} * {ok: false, reason} * * Caches successful lookups for 60 seconds — ownership rarely changes faster * than that, and a listing POST should not thrash the gateway on retries. */ const cache = new Map(); // name -> {at, result} const CACHE_TTL_MS = 60_000; export async function lookupName(name) { const mock = process.env.MOCK_BNS_OWNER; if (mock) { return { ok: true, owner: mock, records: {}, registered: true, source: 'mock' }; } const key = String(name).toLowerCase(); const now = Date.now(); const cached = cache.get(key); if (cached && now - cached.at < CACHE_TTL_MS) { return cached.result; } let resp; try { resp = await fetch(`${BNS_GATEWAY}/api/name/${encodeURIComponent(key)}`, { headers: { accept: 'application/json' }, signal: AbortSignal.timeout(5000), }); } catch (err) { return { ok: false, reason: `gateway unreachable: ${err?.message || err}` }; } if (!resp.ok) { return { ok: false, reason: `gateway returned HTTP ${resp.status}` }; } let body; try { body = await resp.json(); } catch (err) { return { ok: false, reason: 'gateway returned non-JSON' }; } if (!body.registered) { const result = { ok: false, reason: 'name is not registered', body }; cache.set(key, { at: now, result }); return result; } const result = { ok: true, owner: body.owner, records: body.records || {}, registered: true, source: 'gateway', }; cache.set(key, { at: now, result }); return result; }