aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-30 21:47:49 -0700
committerAdam Soltys <[email protected]>2026-08-30 21:47:49 -0700
commita9ae409528219d56a9fbab5dca8a1ad3af68a106 (patch)
tree60101dbd792b0f0036b8e3e2f65f684dd4df593f
parent2d9b120e54c24c5e6703183762846b610e337801 (diff)
downloadcoinos-ui-a9ae409528219d56a9fbab5dca8a1ad3af68a106.tar.gz
coinos-ui-a9ae409528219d56a9fbab5dca8a1ad3af68a106.zip
migrate: hand the name over to the registrar too — after flagging the account, tell names.coinos.io which v3 identity may claim it (MIGRATE_TOKEN), with retries; without the grant the registrar squat guard refused every migrated claim
Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
-rw-r--r--src/routes/(app)/migrate/+page.server.ts28
1 files changed, 28 insertions, 0 deletions
diff --git a/src/routes/(app)/migrate/+page.server.ts b/src/routes/(app)/migrate/+page.server.ts
index f1964d1b..48ae6a96 100644
--- a/src/routes/(app)/migrate/+page.server.ts
+++ b/src/routes/(app)/migrate/+page.server.ts
@@ -1,8 +1,33 @@
import { auth, post } from "$lib/utils";
import { redirect } from "@sveltejs/kit";
+import { env } from "$env/dynamic/private";
const REGISTRAR = "https://names.coinos.io";
+// Tell the v3 registrar who may claim the released name. This session — the
+// old account's authenticated login — is the proof of ownership; the
+// registrar's own claim guard otherwise only accepts a claim signed by the
+// old account's nostr key, which a v3 wallet never has.
+async function grantMigration(username: string, to: string) {
+ if (!env.MIGRATE_TOKEN) throw new Error("migration hand-off isn't configured");
+ let lastErr;
+ for (let i = 0; i < 3; i++) {
+ try {
+ const r = await fetch(`${REGISTRAR}/migrate-grant`, {
+ method: "POST",
+ headers: { "content-type": "application/json", authorization: `Bearer ${env.MIGRATE_TOKEN}` },
+ body: JSON.stringify({ name: username, to }),
+ });
+ if (r.ok) return;
+ lastErr = new Error((await r.json().catch(() => null))?.error || `registrar said ${r.status}`);
+ } catch (e) {
+ lastErr = e;
+ }
+ await new Promise((res) => setTimeout(res, 1500));
+ }
+ throw lastErr;
+}
+
// An invoice from the v3 registrar for this address and amount (LNURL-pay).
async function invoiceFor(address: string, sats: number) {
const [name, domain] = String(address).split("@");
@@ -66,6 +91,9 @@ export const actions = {
// as it is — same username, same history, still usable here; the
// registrar just becomes the answer for incoming payments.
await post("/user", { migrated: true, ...(pin ? { pin } : {}) }, auth(cookies));
+ // …and tell the registrar which v3 identity the name now belongs
+ // to, or their claim over there can never pass the squat guard.
+ await grantMigration(username, to);
return { ok: true, sent, released: username };
} catch (e) {
const msg = e && typeof e === "object" && "message" in e ? String(e.message) : String(e);