aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-08 11:15:02 -0700
committerAdam Soltys <[email protected]>2026-09-08 11:15:02 -0700
commit7b444d6f0750b1ff316204dd5ec4425201c480e9 (patch)
treefb6b4a7393b22402d49b91fe98ee89304a2977b6 /src
parent779284629dd30df04b5e594b9d729546e08bbe71 (diff)
downloadcoinosv3-7b444d6f0750b1ff316204dd5ec4425201c480e9.tar.gz
coinosv3-7b444d6f0750b1ff316204dd5ec4425201c480e9.zip
DMs: one nostr identity per wallet (stop the phantom seed-key inbox).
A wallet has two nostr keys — the seed-derived key and, when signed in, the login identity (extension/bunker/passkey/Google). Sending already used one identity, but RECEIVING/advertising used both: startDMs subscribed kind-1059 gift wraps on '#p' for both keys and fetched both keys' inbox relays, and ensureDmRelayList always published a kind-10050 inbox for the seed key even under a login. Result: a login wallet advertised the seed key as a SECOND DM inbox, so the welcomer greeted both keys (duplicate welcome) and a person's DMs split across two identities — one showed up as if from "another user". Collapse to a single DM identity: new dmSubKeys() returns just the login pubkey when signed in, else the seed key, and startDMs subscribes/fetches only that; ensureDmRelayList no longer advertises the seed key when a login identity is active. isMe() and dmDecryptors() stay tolerant of both keys so already-received messages still thread and align. Not a privacy fix — nothing ever leaked to a third party (both keys were the user's own); this removes the confusing second self-identity going forward. (Existing cached DMs to the seed key on a given device don't retro-clean; a chat re-sync/forget clears them.) Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz
Diffstat (limited to 'src')
-rw-r--r--src/features/messages.js22
1 files changed, 20 insertions, 2 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index 4455711..6e4c781 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -212,6 +212,19 @@ export function messagesFeature(ctx) {
return pks;
};
const isMe = (pk) => myPubkeys().includes(pk);
+ // The ONE nostr identity this wallet uses for DMs: the login identity when
+ // signed in (extension/bunker/passkey/Google), else the seed-derived key. A
+ // wallet has two keys (seed + login), but only this one should receive and
+ // advertise DMs — advertising/polling the seed key as a SECOND inbox is what
+ // produced duplicate welcome DMs and split a person's messages across two
+ // identities. isMe/dmDecryptors stay tolerant of both so already-received
+ // messages still thread and align; we just stop treating the seed key as a
+ // live second inbox. (One key here, but kept as a list for the '#p' filters.)
+ const dmSubKeys = () => {
+ const id = hook('nostrLoginIdentity');
+ const pk = (id && id.pubkey) || (wallet.nostr && wallet.nostr.pk) || null;
+ return pk ? [pk] : [];
+ };
// "No identity" while soft-locked means the keys left with the lock — say
// that, not "signer disconnected" (which reads as a nostr-login problem).
const noIdToast = () => {
@@ -1446,7 +1459,9 @@ export function messagesFeature(ctx) {
function startDMs() {
if (dmStarted) return;
- const pks = myPubkeys();
+ // Poll/advertise only the single DM identity (see dmSubKeys) — not the seed
+ // key as a phantom second inbox.
+ const pks = dmSubKeys();
if (!pks.length) return;
dmStarted = true;
// warm threads from the local cache. A self-thread of our own messages is
@@ -1490,9 +1505,12 @@ export function messagesFeature(ctx) {
// Publish a kind 10050 DM-relay list for the wallet key if none exists, so
// other NIP-17 clients can find our inbox. Never touch a login npub's list
- // — the user's other clients own that.
+ // — the user's other clients own that. And when a login identity IS active,
+ // don't advertise the seed key at all: it's internal, not a second inbox
+ // (advertising it is what let DMs land on a phantom identity).
async function ensureDmRelayList() {
if (!wallet.nostr || !wallet.nostr.sk) return;
+ if (hook('nostrLoginIdentity')) return;
const pk = wallet.nostr.pk;
const existing = await queryOn(DM_RELAYS, { kinds: [10050], authors: [pk] }, 2500);
if (existing.length) return;