aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-18 23:12:18 -0700
committerAdam Soltys <[email protected]>2026-09-18 23:12:18 -0700
commit0ba71aa0757af0d5ab6902c8c405d5a3cc8c65a9 (patch)
treea9353ee09c620e325b634a87807549525f36a0a6
parent13b3e9d0272b3ab8aed26b15cd8db6177a5e9400 (diff)
downloadcoinosv3-server-0ba71aa0757af0d5ab6902c8c405d5a3cc8c65a9.tar.gz
coinosv3-server-0ba71aa0757af0d5ab6902c8c405d5a3cc8c65a9.zip
Accounts from the header, snapshot-first Spending restore, name ownership grants.
Header buttons always leave the current feature screen; an account with no remembered nostr identity opens Accounts instead of nothing. Logout confirms in place. A scan finishing after logout or an account switch no longer drops the new session into offline mode (stateGeneration guards in sync + ark). Spending restores from the snapshot first. A migration grant names the identity that owns an address; its manager may register it without becoming that identity (names/ownership.js). Browser tests for account access, profile retry, spending restore, thread reply focus, custom-emoji reactions and registry ownership. Co-Authored-By: Claude Fable 5.1 <[email protected]>
-rw-r--r--names/ownership.js10
-rw-r--r--names/server.js6
-rw-r--r--tools/registry-ownership-test.js18
3 files changed, 30 insertions, 4 deletions
diff --git a/names/ownership.js b/names/ownership.js
new file mode 100644
index 0000000..19ae4ef
--- /dev/null
+++ b/names/ownership.js
@@ -0,0 +1,10 @@
+// A migration grant names the identity that owns the address. Its manager
+// may submit the registration without becoming that identity.
+export function registrationIdentity({ existing, signer, manager, grant, granted }) {
+ const pubkey = existing?.pubkey || (granted ? grant.pubkey : signer);
+ const nominated = /^[0-9a-f]{64}$/.test(manager || '') && manager !== pubkey;
+ return {
+ pubkey,
+ manager: nominated ? manager : existing?.manager || (granted ? grant.manager : undefined),
+ };
+}
diff --git a/names/server.js b/names/server.js
index ed44ad3..97ac662 100644
--- a/names/server.js
+++ b/names/server.js
@@ -31,6 +31,7 @@ import { decodeAddress } from '../src/ark/proto.js';
import { decodeNoffer } from '../src/noffer.js';
import { decodeBolt11 } from '../src/ark/lightning.js';
import { decodeOffer } from '../src/bolt12.js';
+import { registrationIdentity } from './ownership.js';
const CFG = JSON.parse(readFileSync(process.env.NAMES_CONFIG
|| join(import.meta.dir, 'config.json'), 'utf8'));
@@ -1624,10 +1625,7 @@ Bun.serve({
const recordId = await cfWrite(name, domain, published, existing?.recordId);
state.names[key] = {
- pubkey: existing?.pubkey || auth.pubkey,
- // the owner may nominate a key that manages the record from here on
- manager: (claim.manager && /^[0-9a-f]{64}$/.test(claim.manager) && auth.pubkey !== claim.manager)
- ? claim.manager : existing?.manager,
+ ...registrationIdentity({ existing, signer: auth.pubkey, manager: claim.manager, grant, granted }),
uri: published, recordId, updated: Date.now(), domain,
// the wallet's CLINK offer key, so LNURL can ask it for invoices
offerPk: (claim.offerPk && /^[0-9a-f]{64}$/.test(claim.offerPk)) ? claim.offerPk : existing?.offerPk,
diff --git a/tools/registry-ownership-test.js b/tools/registry-ownership-test.js
new file mode 100644
index 0000000..43683a1
--- /dev/null
+++ b/tools/registry-ownership-test.js
@@ -0,0 +1,18 @@
+import assert from 'node:assert/strict';
+import { registrationIdentity } from '../names/ownership.js';
+
+const owner = 'a'.repeat(64), wallet = 'b'.repeat(64), stranger = 'c'.repeat(64);
+const grant = { pubkey: owner, manager: wallet };
+for (const signer of [owner, wallet]) {
+ assert.deepEqual(registrationIdentity({ signer, grant, granted: true }), { pubkey: owner, manager: wallet });
+}
+console.log('✓ owner and manager registrations both preserve the migration identity');
+assert.deepEqual(registrationIdentity({ signer: wallet, manager: wallet, grant, granted: true }), { pubkey: owner, manager: wallet });
+console.log('✓ the signing wallet can remain the nominated manager');
+const existing = { pubkey: owner, manager: wallet };
+assert.deepEqual(registrationIdentity({ existing, signer: wallet, grant: { pubkey: stranger }, granted: false }), existing);
+console.log('✓ later wallet updates preserve existing ownership and management');
+assert.deepEqual(registrationIdentity({ signer: wallet, grant, granted: false }), { pubkey: wallet, manager: undefined });
+assert.deepEqual(registrationIdentity({ signer: owner, manager: wallet }), existing);
+assert.deepEqual(registrationIdentity({ signer: owner, manager: owner }), { pubkey: owner, manager: undefined });
+console.log('✓ ordinary claims retain their signer and ignore unauthorized grants');