From 5aa5fa759441c6558377262ca848d8587c26e952 Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 20 Oct 2026 00:26:59 -0500 Subject: [PATCH] feat(chat): broadcast self-addressed Control memos (App::sendChatControl) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds App::sendChatControl(cid, op, label): composes a self-addressed Control memo and hands it to the existing chat send coordinator (enqueueChatSend → pump → the 0-value kChatMinFeeDrgx z_sendmany / lite send) exactly as a message does — no new fund-path semantics. It carries no UI echo bubble (empty echoLocalId; resolveOutgoing("") is a no-op), and the memo harvested back to us is routed to the reducer, never shown as a message. Phase A, step 4 (broadcast) of on-chain chat delete/restore. UI wiring next. Co-Authored-By: Claude Opus 4.8 --- src/app.h | 4 ++++ src/app_network.cpp | 29 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/app.h b/src/app.h index 998e57e..22ee680 100644 --- a/src/app.h +++ b/src/app.h @@ -321,6 +321,10 @@ public: // Send a contact request into an existing conversation (used to retry a failed request in place). void sendContactRequestForCid(const std::string& cid, const std::string& peerZaddr, const std::string& text); + // Broadcast a self-addressed Control memo (op "del"/"res") so every device and a fresh seed import + // hides/restores this conversation. `label` is a plaintext hint (peer address/name) carried on-chain. + // Reuses the chat send coordinator + the 0-value kChatMinFeeDrgx z_sendmany path; no UI echo bubble. + void sendChatControl(const std::string& conversationId, const std::string& op, const std::string& label); // Debug/sweep convenience: give the Chat tab a demo identity + a few sample conversations // (in-memory only, not persisted) so the screenshot sweep captures the populated UI. No-op when // the chat feature is off. Returns true when it had to fabricate the (random) demo identity diff --git a/src/app_network.cpp b/src/app_network.cpp index 838968e..9b274e1 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -4675,6 +4675,35 @@ void App::sendChatMessage(const std::string& conversationId, const std::string& enqueueChatSend(LiteOpKind::ChatSend, memos, echo.txid); } +void App::sendChatControl(const std::string& conversationId, const std::string& op, const std::string& label) +{ + if (!chat::hushChatFeatureEnabledAtBuild() || !chat_service_.hasIdentity()) return; + if (conversationId.empty() || op.empty()) return; + + // A control signal is addressed to our OWN reply address — it never reaches the peer; only this + // wallet's seed (on any device, including a fresh import) harvests and reduces it. + const std::string myReply = chatReplyZaddr(); + if (myReply.empty()) { + ui::Notifications::instance().error(TR("chat_toast_no_zaddr")); + return; + } + const std::string myPubKey = chat_service_.identityPublicKeyHex(); + if (myPubKey.empty()) return; + + // The label must be a non-empty, non-'{' hint; fall back to the cid if the caller has no name. + const std::string hint = label.empty() ? conversationId : label; + + chat::OutgoingChatMemos memos; + if (chat::buildOutgoingControl(myPubKey, myReply, conversationId, op, hint, memos) + != chat::ChatComposeStatus::Ok) { + return; // never block the local delete/restore on a compose edge case; it is cosmetic cross-device + } + // Reuse the chat send coordinator exactly as a message does (0-value, kChatMinFeeDrgx fee, two memo + // outputs). No echo bubble: pass an empty echoLocalId — resolveOutgoing("") is a no-op, and the memo, + // harvested back to us, is routed to the reducer (not shown as a message). + enqueueChatSend(LiteOpKind::ChatSend, memos, /*echoLocalId=*/""); +} + void App::startChatConversation(const std::string& peerZaddr, const std::string& text) { if (!chat::hushChatFeatureEnabledAtBuild() || !chat_service_.hasIdentity()) return;