feat(chat): harvest chat memos on the lite variant (lite receive)
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) <noreply@anthropic.com>
This commit is contained in:
@@ -682,6 +682,9 @@ void App::update()
|
|||||||
wallet::LiteWalletAppRefreshModel liteModel;
|
wallet::LiteWalletAppRefreshModel liteModel;
|
||||||
if (lite_wallet_->takeRefreshedModel(liteModel)) {
|
if (lite_wallet_->takeRefreshedModel(liteModel)) {
|
||||||
wallet::applyLiteRefreshModelToWalletState(liteModel, state_);
|
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.
|
// Deliver a completed async send/shield result to the waiting send_tab callback.
|
||||||
wallet::LiteBroadcastResult broadcast;
|
wallet::LiteBroadcastResult broadcast;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace dragonx {
|
|||||||
namespace config { class Settings; }
|
namespace config { class Settings; }
|
||||||
namespace daemon { class DaemonController; class EmbeddedDaemon; class XmrigManager; }
|
namespace daemon { class DaemonController; class EmbeddedDaemon; class XmrigManager; }
|
||||||
namespace util { class Bootstrap; class SecureVault; }
|
namespace util { class Bootstrap; class SecureVault; }
|
||||||
namespace wallet { class LiteWalletController; }
|
namespace wallet { class LiteWalletController; struct LiteWalletAppRefreshModel; }
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace dragonx {
|
namespace dragonx {
|
||||||
@@ -597,6 +597,7 @@ private:
|
|||||||
std::string generateChatLocalId(const char* prefix, int numBytes) const; // unique echo id / cid
|
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 broadcastChatMemos(const chat::OutgoingChatMemos& memos); // full-node z_sendmany / lite
|
||||||
void broadcastChatMemosLite(const chat::OutgoingChatMemos& memos); // lite two-recipient send
|
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.
|
// Lite first-run welcome prompt: dismissed for the session once the user picks an action.
|
||||||
bool lite_firstrun_dismissed_ = false;
|
bool lite_firstrun_dismissed_ = false;
|
||||||
// Lite send-time unlock: set to show the unlock modal when a spend is attempted while locked.
|
// Lite send-time unlock: set to show the unlock modal when a spend is attempted while locked.
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include "config/settings.h"
|
#include "config/settings.h"
|
||||||
#include "wallet/lite_wallet_controller.h" // lite send/new-address routing
|
#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/daemon_controller.h"
|
||||||
#include "daemon/embedded_daemon.h"
|
#include "daemon/embedded_daemon.h"
|
||||||
#include "daemon/xmrig_manager.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.");
|
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<std::string, chat::HushChatTransactionInput> byTxid;
|
||||||
|
std::unordered_map<std::string, std::int64_t> 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<std::size_t>(*tx.position), tx.memo});
|
||||||
|
txTimestamps[tx.txid] = tx.timestamp;
|
||||||
|
}
|
||||||
|
if (byTxid.empty()) return;
|
||||||
|
|
||||||
|
std::vector<chat::HushChatTransactionMetadata> 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<void(const std::string&, int, int)> callback)
|
void App::exportAllKeys(std::function<void(const std::string&, int, int)> callback)
|
||||||
{
|
{
|
||||||
if (!state_.connected || !rpc_) {
|
if (!state_.connected || !rpc_) {
|
||||||
|
|||||||
Reference in New Issue
Block a user