diff options
| author | Adam Soltys <[email protected]> | 2026-10-09 21:55:11 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-09 21:55:11 -0700 |
| commit | 3d6bf81a19ca080d3710893b1e65f5aa3f01315f (patch) | |
| tree | 71a102a6440a3d0b730f635010f723b5f624f212 /src | |
| parent | faec1f84ed1e9e8ee78055796a8ebc92936abe9d (diff) | |
| download | coinosv3-3d6bf81a19ca080d3710893b1e65f5aa3f01315f.tar.gz coinosv3-3d6bf81a19ca080d3710893b1e65f5aa3f01315f.zip | |
Names: letters and numbers only, 2–24 — coinos v2's rule. The registrar took a-z, 0-9 and . _ - (1–30), so bitcoin-education and a dozen others got through. A NEW claim must now match /^[a-z0-9]{2,24}$/ (v2's /^[\p{L}\p{N}]{2,24}$/u, held to ASCII since a name is a DNS label); a name already held (19 with . _ -, plus "f" and "g") is grandfathered — its owner can still update it — as are v2 migrations carrying their own name and npub-shaped defaults. /name/<n> answers invalid: true for a free name that breaks the rule; the claim form says "Letters and numbers only" as it's typed, and the suggested name from a nostr profile is stripped to letters and digits.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
Diffstat (limited to 'src')
| -rw-r--r-- | src/features/names.js | 12 | ||||
| -rw-r--r-- | src/i18n.js | 1 |
2 files changed, 11 insertions, 2 deletions
diff --git a/src/features/names.js b/src/features/names.js index a29ee7d..fe5ab0a 100644 --- a/src/features/names.js +++ b/src/features/names.js @@ -420,8 +420,9 @@ export function namesFeature(ctx) { (async () => { try { const prof = hook('cachedProfile', me); - const cand = String((prof && prof.name) || '').toLowerCase().replace(/[^a-z0-9._-]/g, '').slice(0, 30); - if (!cand || /^npub1/.test(cand)) return; + // letters and digits only, 2–24: what the registrar takes for a new name + const cand = String((prof && prof.name) || '').toLowerCase().replace(/[^a-z0-9]/g, '').slice(0, 24); + if (cand.length < 2 || /^npub1/.test(cand)) return; const r = await withTimeout( fetch(`${REGISTRAR}/name/${encodeURIComponent(cand)}?domain=${encodeURIComponent(DOMAIN())}`).then((x) => x.json()), 8000, 'registrar'); @@ -432,6 +433,8 @@ export function namesFeature(ctx) { })(); } + // letters and digits, 2–24 (coinos v2's rule) — a hint while typing + const nameRuleHint = (v) => !!v && !/^npub1/.test(v) && /[^a-z0-9]/.test(v); function claimForm(big) { suggestName(); return h('div', { class: 'col', style: 'gap:8px;width:100%' }, @@ -442,11 +445,16 @@ export function namesFeature(ctx) { autocapitalize: 'none', autocomplete: 'off', spellcheck: 'false', value: ui.nameClaim || '', onInput: (e) => { + const was = nameRuleHint(ui.nameClaim); ui.nameClaim = e.target.value.toLowerCase().trim(); if (ui.nameClaimMigration) { ui.nameClaimMigration = null; render(); } + else if (nameRuleHint(ui.nameClaim) !== was) render(); }, }), h('span', { class: 'muted', style: 'align-self:center' }, '@' + DOMAIN())), + // said as it's typed, not after a round trip (the registrar is the judge: + // a name claimed before the rule can still be retaken by its owner) + nameRuleHint(ui.nameClaim) ? h('div', { class: 'small', style: 'color:var(--red,#c0392b)' }, t('namesRule')) : null, h('button', { class: 'btn-primary btn-block', disabled: ui.busy, onClick: async () => { const name = (ui.nameClaim || '').toLowerCase().trim(); if (!name) return; diff --git a/src/i18n.js b/src/i18n.js index d8ced5b..32c55b0 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -1251,6 +1251,7 @@ const STR = { namesPosCopyLink: 'Copy link', namesDesc: 'Claim your payment address, like [email protected]. People can pay you just by typing it, no codes to copy or QR to scan.', namesPlaceholder: 'username', + namesRule: 'Letters and numbers only', namesResolving: 'Looking up {name}…', namesSendTo: 'Send to {name}', namesClaim: 'Claim', |
