From 7351d8a06d512a3a6df2db8d8800b28a2387f697 Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 16 Jul 2026 21:21:41 -0500 Subject: [PATCH] fix(chat): stop our own sent messages appearing as incoming duplicates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two causes of the "every message I send shows up again as a reply from the peer": 1. Harvest bug — the full-refresh path fed a SENT tx's outgoing memos (from z_viewtransaction outgoing outputs) into the chat metadata extractor, and ChatService::ingest marks everything Incoming. So each send was re-ingested as a phantom "from peer" message. The recent-refresh path already omitted this, so it was accidental. Drop the outgoing chat-harvest (keep the tx-history harvest); genuine incoming still comes from z_listreceivedbyaddress, and our sends are recorded by the local echo. 2. Own-identity ingest filter — a memo whose sender public key equals our own identity is by definition something we sent (only we hold our key); it must never be ingested as incoming. Skip those in ingest. This also collapses same-seed self-chat (running the SAME wallet in the full node and Lite makes them one chat identity, so sends land on an address we also own and loop back). For a real two-party chat use two DIFFERENT wallets/seeds — same-seed wallets are one identity and can't be distinct peers. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/chat/chat_service.cpp | 8 ++++++++ src/services/network_refresh_service.cpp | 22 ++++++---------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/chat/chat_service.cpp b/src/chat/chat_service.cpp index 359b14a..0388960 100644 --- a/src/chat/chat_service.cpp +++ b/src/chat/chat_service.cpp @@ -29,8 +29,16 @@ int ChatService::ingest(const std::vector& metadata std::vector* newIncomingCids) { if (!has_identity_) return 0; + const std::string myPubKey = chatIdentityPublicKeyHex(identity_); + int added = 0; for (const auto& meta : metadata) { + // A memo whose sender is our OWN identity is something we sent (only we hold our key). The local + // echo already records it as outgoing — ingesting it as incoming would duplicate it as a phantom + // "from peer" message. (This also collapses same-seed self-chat, where the "peer" wallet shares + // our identity, so every message would otherwise loop back.) + if (!myPubKey.empty() && meta.sender_public_key_hex == myPubKey) continue; + ChatMessage message; message.direction = ChatDirection::Incoming; message.txid = meta.txid; diff --git a/src/services/network_refresh_service.cpp b/src/services/network_refresh_service.cpp index 8d1cf3b..b776b3c 100644 --- a/src/services/network_refresh_service.cpp +++ b/src/services/network_refresh_service.cpp @@ -168,20 +168,6 @@ void appendExtractedHushChatMetadata(std::vector& destination, - const std::string& txid, - const NetworkRefreshService::TransactionViewCacheEntry& entry) -{ - if (!chat::hushChatFeatureEnabledAtBuild()) return; - - std::vector outputs; - outputs.reserve(entry.outgoing_outputs.size()); - for (const auto& output : entry.outgoing_outputs) { - if (!output.memo.empty()) outputs.push_back(chat::HushChatMemoOutput{output.position, output.memo}); - } - appendExtractedHushChatMetadata(destination, txid, outputs); -} - void appendExtractedHushChatMetadata(std::vector& destination, const HushChatMemoOutputMap& outputsByTxid) { @@ -920,7 +906,10 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectTr if (cached != snapshot.viewTxCache.end()) { if (!trackedSend || !cached->second.outgoing_outputs.empty()) { appendViewTransactionOutputs(result.transactions, txid, cached->second); - appendExtractedHushChatMetadata(result.hushChatMetadata, txid, cached->second); + // NB: do NOT extract chat metadata from our OWN outgoing memos here — ingest marks + // everything Incoming, so that duplicates every sent message as a phantom "from peer" + // entry. Genuine incoming comes from the z_listreceivedbyaddress path; our sends are + // recorded by the local echo. (The recent-refresh variant already omits this.) continue; } } @@ -945,7 +934,8 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectTr auto entry = parseViewTransactionCacheEntry(viewTransaction); appendViewTransactionOutputs(result.transactions, txid, entry); - appendExtractedHushChatMetadata(result.hushChatMetadata, txid, entry); + // (Chat metadata is harvested only from RECEIVED notes, not our own outgoing memos — see + // the cached-view branch above.) json rawTransaction; bool hasRawTransaction = false;