diff options
| author | Adam Soltys <[email protected]> | 2026-09-10 11:00:48 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-10 11:00:48 -0700 |
| commit | 84628442c8c4f2cf30e79f84fbbcc872ea8fe818 (patch) | |
| tree | 7c5dc304332f724146570dcb67ec6a1b8603e1a2 | |
| parent | dd3ca845bfaf901dfb0fe702a7e5cb1c32485d25 (diff) | |
| download | coinosv3-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
| -rw-r--r-- | names/server.js | 17 |
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)) { |
