aboutsummaryrefslogtreecommitdiffstats
path: root/lib/names.ts
blob: 284c595d815f9153b9a711e61de2730320cf4acd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { warn } from "$lib/logging";
import { fail } from "$lib/utils";

// The v3 registrar (coinos v3 / halwallet) owns any name it has a record for:
// routes/lnurl.ts serves a claimed name's lightning address from the registrar
// ahead of the local account. So a local account taking a name the registrar
// holds silently hands its incoming payments to whoever holds the v3 name.
// Both places that assign a username — registration and the profile rename —
// must therefore refuse a name the registrar holds, unless the account's own
// nostr key is the holder (a user re-creating their migrated name is fine).
const NAMES_URL = process.env.NAMES_URL || "https://names.coinos.io";
const DOMAIN = "coinos.io";

// Fail CLOSED: if the registrar can't be reached we don't know who owns the
// name, and handing out a squatted name is worse than a retry.
export const assertNameFree = async (username: string, ownPubkey?: string) => {
  const name = username.toLowerCase();
  let body: any;
  try {
    const r = await fetch(
      `${NAMES_URL}/name/${encodeURIComponent(name)}?domain=${DOMAIN}`,
      { signal: AbortSignal.timeout(3000) },
    );
    if (!r.ok) throw new Error(`registrar said ${r.status}`);
    body = await r.json();
  } catch (e: any) {
    warn("names registrar unreachable checking", name, e?.message);
    fail("Username unavailable right now, please try again");
  }
  // `pubkey` is only present for a real v3 record. `taken` without one means
  // the registrar is echoing our own account list back at us — not a claim.
  const holder = body?.taken && typeof body.pubkey === "string" ? body.pubkey : null;
  if (holder && holder !== ownPubkey) {
    warn("username held by v3 registrar", name, holder.slice(0, 8));
    fail(`Username ${name} taken`);
  }
};