diff options
| author | Adam Soltys <[email protected]> | 2026-08-03 10:09:09 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-08-03 10:09:09 -0700 |
| commit | 4d09a091d0aa6a63860260637feddc1e11bed708 (patch) | |
| tree | f2457ac5c29ff27ec17d1379e04188491d384260 | |
| parent | 258f815bea0bd0e44091bda8b48b83ff15e39aa6 (diff) | |
| download | coinosv3-4d09a091d0aa6a63860260637feddc1e11bed708.tar.gz coinosv3-4d09a091d0aa6a63860260637feddc1e11bed708.zip | |
Onboarding: a step for migrating a legacy coinos.io account
Between "do you have a nostr account" and claiming a username, new users
are asked whether they already have an account on the original coinos.
Yes hands off to coinos.io/migrate with their fresh v3 payment address
and a return URL — they're already signed in there, so no password or
key ever travels. That page sweeps the balance over Lightning and
renames the old account, releasing the username.
Coming back, coinos.io returns ?migrated=<username>; v3 claims that name
on the registrar (retrying for a minute, since the registrar's
"is this a legacy user?" guard caches), and their lightning address now
resolves to the v3 wallet.
The legacy page ships in coinos-ui and is NOT deployed yet.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
| -rw-r--r-- | src/app.js | 52 | ||||
| -rw-r--r-- | src/features/names.js | 2 | ||||
| -rw-r--r-- | src/i18n.js | 8 |
3 files changed, 61 insertions, 1 deletions
@@ -2035,7 +2035,7 @@ function onboardScreen() { const o = ui.onb; // steps that were waiting on something advance the moment it exists if ((o.step === 'nostr' || o.step === 'signin') && ui.screen === 'wallet' && activeAccount()) { - o.step = 'username'; + o.step = 'legacy'; o.enterAddr = featureHook('namesAddress') || null; } if (o.step === 'username') { @@ -2049,6 +2049,27 @@ function onboardScreen() { const page = (kids) => h('div', { class: 'col onb', style: 'gap:20px' }, ...kids); const title = (txt) => h('h2', { class: 'onb-title' }, txt); + if (o.step === 'legacy') { + const addr = featureHook('namesAddress'); + const url = addr + ? `https://coinos.io/migrate?to=${encodeURIComponent(addr)}&back=${encodeURIComponent(location.origin + '/')}` + : null; + return page([ + title(t('onbLegacyTitle')), + h('p', { class: 'muted', style: 'margin:0' }, t('onbLegacyBody')), + url + ? h('a', { + class: 'btn btn-primary btn-block', href: url, + style: 'text-align:center;display:block;text-decoration:none;padding:14px', + onClick: () => { o.wentToLegacy = true; }, + }, t('onbLegacyGo')) + : h('div', { class: 'row gap6', style: 'align-items:center' }, + h('span', { class: 'spinner sm' }), h('span', { class: 'small muted' }, t('onbNameWait'))), + h('button', { class: 'btn-block', style: 'padding:14px', onClick: () => { o.step = 'username'; render(); } }, + o.wentToLegacy ? t('onbLegacyDone') : t('onbLegacyNo')), + h('button', { class: 'linklike small', onClick: () => { o.step = 'nostr'; render(); } }, t('back')), + ]); + } if (o.step === 'welcome') { return page([ h('div', { class: 'onb-hero' }, brandHeader(false)), @@ -2153,6 +2174,34 @@ function onboardScreen() { ]); } +// Returning from the legacy migration: coinos.io sends ?migrated=<username> +// once it has swept the balance and released the name. Claim it here — the +// registrar's "is this a legacy user?" guard now passes. +function claimMigratedName() { + let name = null; + try { + const u = new URL(location.href); + name = u.searchParams.get('migrated'); + if (name) { u.searchParams.delete('migrated'); history.replaceState(null, '', u.pathname + u.search); } + } catch {} + if (!name) return; + ui.migrating = name; + render(); + let tries = 0; + const attempt = () => { + featureHook('namesClaimName', name) + .then(() => { ui.migrating = null; toast(t('migrateClaimed', { name })); render(); }) + .catch(() => { + // DNS/registrar caches the legacy lookup briefly; give it a minute + if (++tries < 12) return setTimeout(attempt, 5000); + ui.migrating = null; + toast(t('migrateClaimFailed', { name })); + render(); + }); + }; + attempt(); +} + // ---- the coach-mark tour: three stops, measured against the live DOM ---- const TOUR = [ { sel: '.header-avatar', key: 'tourAvatar' }, @@ -3450,6 +3499,7 @@ document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden') onAppHidden(); else onAppVisible(); }); +window.addEventListener('load', () => setTimeout(() => { if (activeAccount()) claimMigratedName(); }, 1500)); loadLocale(getLang()).finally(() => { applyBootAutoLogout(); // clear an overdue session before we read it for claim targets if (featureHook('bootUrl')) return; // a feature consumed the URL (e.g. a gift claim) diff --git a/src/features/names.js b/src/features/names.js index 788c944..edc3be1 100644 --- a/src/features/names.js +++ b/src/features/names.js @@ -385,6 +385,8 @@ export function namesFeature(ctx) { namesAddress() { const st = load(); return st.name ? `${st.name}@${st.domain || DOMAIN}` : null; }, // the onboarding wizard renders the same claim form on its username step namesClaimForm() { return claimForm(true); }, + // claim a specific name (the migration flow, after coinos.io released it) + namesClaimName(name) { return claim(String(name).toLowerCase()); }, stop() { clearTimeout(retryTimer); clearTimeout(deadline); deadline = null; }, receiveModes() { if (!available()) return []; diff --git a/src/i18n.js b/src/i18n.js index e87f097..4474b63 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -493,6 +493,14 @@ const STR = { onbNostrYes: 'Yes — sign me in', onbNostrNo: 'No — create my wallet', onbSigninTitle: 'Sign in with Nostr', + onbLegacyTitle: 'Do you have an account on coinos.io?', + onbLegacyBody: 'The original coinos. We can move your balance over and bring your username with you — your old account gets renamed and stops receiving.', + onbLegacyGo: 'Move my coinos.io account', + onbLegacyDone: "I'm done — continue", + onbLegacyNo: "No, I'm new here", + migrateClaimed: 'Welcome back — {name} is yours here now.', + migrateClaimFailed: "Your balance moved, but {name} isn't free yet. Try claiming it in a minute.", + migrateWorking: 'Claiming {name}…', onbNameTitle: 'Claim your username', onbNameBody: 'Your payment address — people can send you bitcoin with just this.', onbNameWait: 'Getting things ready…', |
