aboutsummaryrefslogtreecommitdiffstats
path: root/names/server.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-10 11:00:48 -0700
committerAdam Soltys <[email protected]>2026-09-10 11:00:48 -0700
commit84628442c8c4f2cf30e79f84fbbcc872ea8fe818 (patch)
tree7c5dc304332f724146570dcb67ec6a1b8603e1a2 /names/server.js
parentdd3ca845bfaf901dfb0fe702a7e5cb1c32485d25 (diff)
downloadcoinosv3-84628442c8c4f2cf30e79f84fbbcc872ea8fe818.tar.gz
coinosv3-84628442c8c4f2cf30e79f84fbbcc872ea8fe818.zip
names: a migration grant unlocks a reserved legacy name.
Migrating the coinos.io house account `coinos` swept its balance fine, then every claim bounced with "name is reserved": the reserved-word check ran before the migration grant was consulted. A reserved word that is also a real coinos.io account can only reach the registrar through a migration, whose grant is the old account's authenticated say-so — so the grant now clears the reserved check too. Reserved names without a grant stay reserved. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01Nw1Dijik1tLVKRM1WkU7xE
Diffstat (limited to 'names/server.js')
-rw-r--r--names/server.js17
1 files changed, 10 insertions, 7 deletions
diff --git a/names/server.js b/names/server.js
index 971108c..ca8ff3c 100644
--- a/names/server.js
+++ b/names/server.js
@@ -1540,7 +1540,16 @@ Bun.serve({
const domain = String(claim.domain || 'halwallet.app').toLowerCase();
if (!DOMAINS[domain]) return json({ error: 'unknown domain' }, 400);
if (!NAME_RE.test(name)) return json({ error: 'invalid name (a-z, 0-9, . _ -, max 30)' }, 400);
- if (RESERVED.has(name)) return json({ error: 'name is reserved' }, 400);
+ // A migration grant (the legacy account's authenticated session told
+ // us where the name is going) trumps BOTH the reserved list and the
+ // legacy reservation: a reserved word that is also a real coinos.io
+ // account (the house account `coinos` itself) can only ever arrive
+ // here through a migration, and the identity check below can never
+ // pass for a v3 wallet key. Reserved without a grant stays reserved.
+ const grant = (state.migrations || {})[`${name}@coinos.io`];
+ const granted = domain === 'coinos.io' && grant
+ && (grant.pubkey === auth.pubkey || (grant.manager && grant.manager === auth.pubkey));
+ if (RESERVED.has(name) && !granted) return json({ error: 'name is reserved' }, 400);
if (!validUri(claim.uri)) return json({ error: 'uri must be a bitcoin: URI' }, 400);
const key = `${name}@${domain}`;
@@ -1549,12 +1558,6 @@ Bun.serve({
if (existing && existing.pubkey !== auth.pubkey && existing.manager !== auth.pubkey) {
return json({ error: 'name is taken' }, 409);
}
- // A migration grant (the legacy account's authenticated session told
- // us where the name is going) trumps the legacy reservation — the
- // identity check below it can never pass for a v3 wallet key.
- const grant = (state.migrations || {})[`${name}@coinos.io`];
- const granted = domain === 'coinos.io' && grant
- && (grant.pubkey === auth.pubkey || (grant.manager && grant.manager === auth.pubkey));
if (!existing && !granted && await takenByCoinosUser(domain, name, auth.pubkey)) return json({ error: 'name is taken' }, 409);
// an npub-shaped name belongs to that identity alone
if (npubPrefixOwner(name)) {