From 008cc82ee50f819e1566498ed7df021a52df1c04 Mon Sep 17 00:00:00 2001 From: DanS Date: Mon, 6 Jul 2026 17:05:20 -0500 Subject: [PATCH] feat(chat): harvest chat memos on the lite variant (lite receive) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The full-node receive harvest works off z_viewtransaction; the lite wallet's transactions come from the backend instead, so lite chat receive was unwired. App::ingestLiteChatMemos runs after each lite refresh: the backend lists one entry per received (non-change) note — same txid, distinct position + decoded UTF-8 memo — so a chat tx yields two entries (header + payload). Group the Receive-kind notes by txid, feed them through the SAME extractHushChatTransaction Metadata parser (now order-tolerant, so the daemon's output shuffle is a non-issue), and thread the result into ChatService. The store dedups (txid+position), so re-listing every refresh is harmless. This closes the lite variant's chat loop end-to-end: identity (exportSeed), DB unlock/load, receive (here), UI, and send (broadcastChatMemosLite). Gated by DRAGONX_ENABLE_CHAT (default OFF). Verified: Linux + Windows build with chat ON, ctest 100%, hygiene clean; caches restored to OFF. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app.cpp | 3 +++ src/app.h | 3 ++- src/app_network.cpp | 30 ++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/app.cpp b/src/app.cpp index ba5ac41..4ee09e7 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -682,6 +682,9 @@ void App::update() wallet::LiteWalletAppRefreshModel liteModel; if (lite_wallet_->takeRefreshedModel(liteModel)) { wallet::applyLiteRefreshModelToWalletState(liteModel, state_); + // HushChat (lite): harvest chat memos from the refreshed transactions and thread them + // (no-op when the feature is off or no identity; the store dedups across refreshes). + ingestLiteChatMemos(liteModel); } // Deliver a completed async send/shield result to the waiting send_tab callback. wallet::LiteBroadcastResult broadcast; diff --git a/src/app.h b/src/app.h index a71ac92..a64d35e 100644 --- a/src/app.h +++ b/src/app.h @@ -38,7 +38,7 @@ namespace dragonx { namespace config { class Settings; } namespace daemon { class DaemonController; class EmbeddedDaemon; class XmrigManager; } namespace util { class Bootstrap; class SecureVault; } - namespace wallet { class LiteWalletController; } + namespace wallet { class LiteWalletController; struct LiteWalletAppRefreshModel; } } namespace dragonx { @@ -597,6 +597,7 @@ private: std::string generateChatLocalId(const char* prefix, int numBytes) const; // unique echo id / cid void broadcastChatMemos(const chat::OutgoingChatMemos& memos); // full-node z_sendmany / lite void broadcastChatMemosLite(const chat::OutgoingChatMemos& memos); // lite two-recipient send + void ingestLiteChatMemos(const wallet::LiteWalletAppRefreshModel& model); // lite chat receive harvest // Lite first-run welcome prompt: dismissed for the session once the user picks an action. bool lite_firstrun_dismissed_ = false; // Lite send-time unlock: set to show the unlock modal when a spend is attempted while locked. diff --git a/src/app_network.cpp b/src/app_network.cpp index 3fb1b42..bf6cf61 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -38,6 +38,7 @@ #include #include "config/settings.h" #include "wallet/lite_wallet_controller.h" // lite send/new-address routing +#include "wallet/lite_wallet_state_mapper.h" // LiteWalletAppRefreshModel for lite chat harvest #include "daemon/daemon_controller.h" #include "daemon/embedded_daemon.h" #include "daemon/xmrig_manager.h" @@ -2630,6 +2631,35 @@ void App::startChatConversation(const std::string& peerZaddr, const std::string& ui::Notifications::instance().success("Contact request queued."); } +// HushChat (lite variant): the full-node harvest works off z_viewtransaction, but the lite wallet's +// transactions come from the backend. The backend lists one entry per received (non-change) note — +// same txid, distinct position + decoded-UTF-8 memo — so a chat tx yields two entries (header + +// payload). Group the received notes by txid, run them through the SAME parser, and thread the +// result. The store dedups (txid+position), so re-listing every refresh is harmless. +void App::ingestLiteChatMemos(const wallet::LiteWalletAppRefreshModel& model) +{ + if (!chat::hushChatFeatureEnabledAtBuild() || !chat_service_.hasIdentity()) return; + + std::unordered_map byTxid; + std::unordered_map txTimestamps; + for (const auto& tx : model.transactions) { + if (tx.kind != wallet::LiteWalletAppTransactionKind::Receive) continue; // only incoming carry chat + if (tx.memo.empty() || !tx.position) continue; + auto& input = byTxid[tx.txid]; + input.txid = tx.txid; + input.outputs.push_back({static_cast(*tx.position), tx.memo}); + txTimestamps[tx.txid] = tx.timestamp; + } + if (byTxid.empty()) return; + + std::vector metadata; + for (auto& entry : byTxid) { + auto extracted = chat::extractHushChatTransactionMetadata(entry.second, true); + for (auto& meta : extracted.metadata) metadata.push_back(std::move(meta)); + } + if (!metadata.empty()) chat_service_.ingest(metadata, txTimestamps, std::time(nullptr)); +} + void App::exportAllKeys(std::function callback) { if (!state_.connected || !rpc_) {