diff --git a/src/app.cpp b/src/app.cpp index eb9e398..367c4c7 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -564,13 +564,26 @@ void App::update() }; } } - return [this, done, anySuccess]() { + // Extract txids from successful operations so shielded + // sends are discoverable by z_viewtransaction. + std::vector successTxids; + for (const auto& op : result) { + if (op.value("status", "") == "success" + && op.contains("result") && op["result"].contains("txid")) { + successTxids.push_back(op["result"]["txid"].get()); + } + } + return [this, done, anySuccess, + successTxids = std::move(successTxids)]() { for (const auto& id : done) { pending_opids_.erase( std::remove(pending_opids_.begin(), pending_opids_.end(), id), pending_opids_.end()); } if (anySuccess) { + for (const auto& txid : successTxids) { + send_txids_.insert(txid); + } // Transaction confirmed by daemon — force immediate data refresh transactions_dirty_ = true; addresses_dirty_ = true; diff --git a/src/app.h b/src/app.h index 5a3a54f..a37a779 100644 --- a/src/app.h +++ b/src/app.h @@ -494,6 +494,12 @@ private: float opid_poll_timer_ = 0.0f; static constexpr float OPID_POLL_INTERVAL = 2.0f; + // Txids from completed z_sendmany operations. + // Ensures shielded sends are discoverable by z_viewtransaction + // even when they don't appear in listtransactions or + // z_listreceivedbyaddress. + std::unordered_set send_txids_; + // First-run wizard state WizardPhase wizard_phase_ = WizardPhase::None; std::unique_ptr bootstrap_; diff --git a/src/app_network.cpp b/src/app_network.cpp index e3ba0e6..425a725 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -437,6 +437,9 @@ void App::refreshData() // Snapshot viewtx cache for the worker thread auto viewtxCacheSnap = viewtx_cache_; + // Snapshot send txids so the worker can include them in enrichment + auto sendTxidsSnap = send_txids_; + // Single consolidated worker task — all RPC calls happen back-to-back // on a single thread with no inter-task queue overhead. worker_->post([this, doAddresses, doPeers, doEncrypt, doTransactions, @@ -444,7 +447,8 @@ void App::refreshData() fullyEnriched = std::move(fullyEnriched), cachedConfirmedTxns = std::move(cachedConfirmedTxns), cachedConfirmedIds = std::move(cachedConfirmedIds), - viewtxCacheSnap = std::move(viewtxCacheSnap)]() -> rpc::RPCWorker::MainCb { + viewtxCacheSnap = std::move(viewtxCacheSnap), + sendTxidsSnap = std::move(sendTxidsSnap)]() -> rpc::RPCWorker::MainCb { // ================================================================ // Phase 1: Balance + blockchain info // ================================================================ @@ -596,6 +600,13 @@ void App::refreshData() } } + // Include txids from completed z_sendmany operations so that + // pure shielded sends (which don't appear in listtransactions + // or z_listreceivedbyaddress) are discoverable. + for (const auto& txid : sendTxidsSnap) { + knownTxids.insert(txid); + } + // Phase 3c: detect shielded sends via z_viewtransaction // Check the in-memory viewtx cache first; only make RPC calls // for txids we haven't seen before. @@ -858,6 +869,8 @@ void App::refreshData() // Merge new z_viewtransaction results into the persistent cache for (auto& [txid, entry] : newViewTxEntries) { viewtx_cache_[txid] = std::move(entry); + // Once cached, no need to keep in send_txids_ + send_txids_.erase(txid); } // Rebuild confirmed transaction cache: txns with >= 10