Clicking Settings → Rescan restarted the daemon with -rescan correctly, but the
progress poll fired "Blockchain rescan complete" the instant it was clicked,
then showed nothing for the entire (multi-hour) rescan — so it looked broken.
Cause: the very first getrescaninfo poll runs before the daemon has restarted
and hits the still-running pre-restart daemon, which answers rescanning=false.
The completion branch took that as "done", cleared the rescanning flag, and the
real rescan then ran invisibly. (Confirmed from a Windows debug-log capture: an
instant OK{"rescanning":false}, then ~6400 warmup errors over ~5h, all swallowed.)
Fixes:
- Gate completion on a new rescan_confirmed_active_ flag that's only set once we
actually observe the rescan running, so a pre-restart rescanning=false can't be
misread as completion.
- While the daemon is in -rescan RPC warmup it rejects every call with the live
phase as the message ("Loading block index..." -> "Rescanning..."). Treat that
as proof-of-progress: surface it as rescan_status and mark confirmed-active,
instead of silently swallowing it. The status bar keeps its animated
"Rescanning..." for the whole run, then reports complete when warmup ends.
- Read rescan_progress whether the daemon returns it as a string or a number
(the get<std::string>() would have thrown on a numeric field).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
757 lines
33 KiB
C++
757 lines
33 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
#pragma once
|
|
|
|
#include <memory>
|
|
#include <string>
|
|
#include <functional>
|
|
#include <thread>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include "data/transaction_history_cache.h"
|
|
#include "data/wallet_state.h"
|
|
#include "rpc/connection.h"
|
|
#include "services/network_refresh_service.h"
|
|
#include "services/wallet_security_controller.h"
|
|
#include "services/wallet_security_workflow.h"
|
|
#include "util/async_task_manager.h"
|
|
#include "wallet/wallet_capabilities.h"
|
|
#include "ui/sidebar.h"
|
|
#include "ui/windows/console_tab.h"
|
|
#include "imgui.h"
|
|
|
|
// Forward declarations
|
|
namespace dragonx {
|
|
namespace rpc {
|
|
class RPCClient;
|
|
class RPCWorker;
|
|
}
|
|
namespace config { class Settings; }
|
|
namespace daemon { class DaemonController; class EmbeddedDaemon; class XmrigManager; }
|
|
namespace util { class Bootstrap; class SecureVault; }
|
|
namespace wallet { class LiteWalletController; }
|
|
}
|
|
|
|
namespace dragonx {
|
|
|
|
/**
|
|
* @brief First-run wizard states
|
|
*/
|
|
enum class WizardPhase {
|
|
None, // No wizard — normal operation
|
|
BootstrapOffer, // Step 1: offer bootstrap download
|
|
BootstrapInProgress,// downloading / extracting
|
|
BootstrapFailed, // error with retry/skip option
|
|
Appearance, // Step 2: visual effects / performance options
|
|
EncryptOffer, // Step 3: offer wallet encryption
|
|
EncryptInProgress, // encrypting + daemon restarting
|
|
PinSetup, // Step 4: optional PIN setup (after encryption)
|
|
Done // wizard complete, launch normally
|
|
};
|
|
|
|
/**
|
|
* @brief Encrypt-wallet dialog phases (settings page flow)
|
|
*/
|
|
enum class EncryptDialogPhase {
|
|
PassphraseEntry, // Enter & confirm passphrase
|
|
Encrypting, // In-progress animation
|
|
PinSetup, // Offer PIN after successful encryption
|
|
Done // Finished — close dialog
|
|
};
|
|
|
|
/**
|
|
* @brief Main application class
|
|
*
|
|
* Manages application state, RPC connection, and coordinates UI rendering.
|
|
*/
|
|
class App {
|
|
public:
|
|
App();
|
|
~App();
|
|
|
|
// Non-copyable
|
|
App(const App&) = delete;
|
|
App& operator=(const App&) = delete;
|
|
|
|
/**
|
|
* @brief Initialize the application
|
|
* @return true if initialization succeeded
|
|
*/
|
|
bool init();
|
|
|
|
/**
|
|
* @brief Update application state (called every frame)
|
|
*/
|
|
void update();
|
|
|
|
/**
|
|
* @brief Pre-frame tasks that must run BEFORE ImGui::NewFrame().
|
|
*
|
|
* Font atlas rebuilds (hot-reload, user font scale changes) must
|
|
* happen before NewFrame() because NewFrame() caches font pointers.
|
|
* Rebuilding mid-frame causes dangling pointer crashes.
|
|
*/
|
|
void preFrame();
|
|
|
|
/**
|
|
* @brief Render the application UI (called every frame)
|
|
*/
|
|
void render();
|
|
|
|
/**
|
|
* @brief Clean shutdown
|
|
*/
|
|
void shutdown();
|
|
|
|
/**
|
|
* @brief Check if app should exit (render loop can stop)
|
|
* Enforces minimum 1-second display of shutdown screen so user sees feedback.
|
|
*/
|
|
bool shouldQuit() const {
|
|
if (!quit_requested_ || !shutdown_complete_) return false;
|
|
auto elapsed = std::chrono::steady_clock::now() - shutdown_start_time_;
|
|
return elapsed >= std::chrono::seconds(1);
|
|
}
|
|
|
|
/**
|
|
* @brief Request application exit — begins async shutdown
|
|
*/
|
|
void requestQuit();
|
|
|
|
/**
|
|
* @brief Begin graceful shutdown (called on window close)
|
|
* Starts daemon stop on a background thread while UI keeps rendering.
|
|
*/
|
|
void beginShutdown();
|
|
|
|
/**
|
|
* @brief Whether we are in the shutdown phase
|
|
*/
|
|
bool isShuttingDown() const { return shutting_down_; }
|
|
wallet::WalletCapabilities walletCapabilities() const { return wallet::currentWalletCapabilities(); }
|
|
bool isLiteBuild() const { return wallet::isLiteBuild(walletCapabilities()); }
|
|
bool supportsEmbeddedDaemon() const { return wallet::supportsEmbeddedDaemon(walletCapabilities()); }
|
|
bool supportsFullNodeLifecycleActions() const { return wallet::supportsFullNodeLifecycleActions(walletCapabilities()); }
|
|
bool supportsSoloMining() const { return wallet::supportsSoloMining(walletCapabilities()); }
|
|
bool supportsPoolMining() const { return wallet::supportsPoolMining(walletCapabilities()); }
|
|
bool supportsLiteBackend() const { return wallet::supportsLiteBackend(walletCapabilities()); }
|
|
|
|
/**
|
|
* @brief Render the shutdown overlay (called instead of normal UI during shutdown)
|
|
*/
|
|
void renderShutdownScreen();
|
|
|
|
/**
|
|
* @brief Render loading overlay in content area while daemon is starting/syncing
|
|
* @param contentH Height of the content area child window
|
|
*/
|
|
void renderLoadingOverlay(float contentH);
|
|
|
|
// Accessors for subsystems
|
|
rpc::RPCClient* rpc() { return rpc_.get(); }
|
|
rpc::RPCWorker* worker() { return worker_.get(); }
|
|
config::Settings* settings() { return settings_.get(); }
|
|
// Lite wallet controller (non-null only in lite builds with a linked backend).
|
|
wallet::LiteWalletController* liteWallet() { return lite_wallet_.get(); }
|
|
// Reason the lite wallet failed to auto-open this session (empty if none / opened OK).
|
|
const std::string& liteOpenError() const { return lite_open_error_; }
|
|
// Show the lite send-time unlock modal (called when a spend is attempted on a locked wallet).
|
|
void requestLiteUnlock() { lite_unlock_prompt_ = true; }
|
|
// (Re)build the lite controller from current settings so a changed lite-server selection
|
|
// takes effect. No-op on non-lite/unlinked builds; preserves a live wallet (see app.cpp).
|
|
void rebuildLiteWallet(bool force = false);
|
|
WalletState& state() { return state_; }
|
|
const WalletState& state() const { return state_; }
|
|
const WalletState& getWalletState() const { return state_; }
|
|
|
|
// Connection state (convenience wrappers)
|
|
bool isConnected() const { return state_.connected; }
|
|
int getBlockHeight() const { return state_.sync.blocks; }
|
|
const std::string& getConnectionStatus() const { return connection_status_; }
|
|
const std::string& getDaemonStatus() const { return daemon_status_; }
|
|
|
|
// Balance info (convenience wrappers)
|
|
double getShieldedBalance() const { return state_.shielded_balance; }
|
|
double getTransparentBalance() const { return state_.transparent_balance; }
|
|
double getTotalBalance() const { return state_.total_balance; }
|
|
double getUnconfirmedBalance() const { return state_.unconfirmed_balance; }
|
|
|
|
// Addresses
|
|
const std::vector<AddressInfo>& getZAddresses() const { return state_.z_addresses; }
|
|
const std::vector<AddressInfo>& getTAddresses() const { return state_.t_addresses; }
|
|
|
|
// Transactions
|
|
const std::vector<TransactionInfo>& getTransactions() const { return state_.transactions; }
|
|
|
|
// Mining
|
|
const MiningInfo& getMiningInfo() const { return state_.mining; }
|
|
void startMining(int threads);
|
|
void stopMining();
|
|
bool isMiningToggleInProgress() const { return mining_toggle_in_progress_.load(std::memory_order_relaxed); }
|
|
|
|
// Pool mining (xmrig)
|
|
void startPoolMining(int threads);
|
|
void stopPoolMining();
|
|
int getXmrigRequestedThreads() const {
|
|
return xmrig_manager_ ? xmrig_manager_->getRequestedThreads() : 0;
|
|
}
|
|
// True while the pool miner process is live — used to refuse replacing the binary under it.
|
|
bool isPoolMinerRunning() const {
|
|
return xmrig_manager_ && xmrig_manager_->isRunning();
|
|
}
|
|
|
|
// Mine-when-idle state query
|
|
bool isIdleMiningActive() const { return idle_mining_active_; }
|
|
|
|
// Peers
|
|
const std::vector<PeerInfo>& getPeers() const { return state_.peers; }
|
|
const std::vector<BannedPeer>& getBannedPeers() const { return state_.bannedPeers; }
|
|
bool isPeerRefreshInProgress() const {
|
|
return network_refresh_.jobInProgress(services::NetworkRefreshService::Job::Peers);
|
|
}
|
|
void banPeer(const std::string& ip, int duration_seconds = 86400);
|
|
void unbanPeer(const std::string& ip);
|
|
void clearBans();
|
|
|
|
// Address operations
|
|
void createNewZAddress(std::function<void(const std::string&)> callback = nullptr);
|
|
void createNewTAddress(std::function<void(const std::string&)> callback = nullptr);
|
|
|
|
// Hide/unhide addresses from the address list (persisted in settings)
|
|
void hideAddress(const std::string& addr);
|
|
void unhideAddress(const std::string& addr);
|
|
bool isAddressHidden(const std::string& addr) const;
|
|
int getHiddenAddressCount() const;
|
|
|
|
void favoriteAddress(const std::string& addr);
|
|
void unfavoriteAddress(const std::string& addr);
|
|
bool isAddressFavorite(const std::string& addr) const;
|
|
|
|
// Address metadata (labels, icons, custom ordering)
|
|
void setAddressLabel(const std::string& addr, const std::string& label);
|
|
void setAddressIcon(const std::string& addr, const std::string& icon);
|
|
std::string getAddressLabel(const std::string& addr) const;
|
|
std::string getAddressIcon(const std::string& addr) const;
|
|
int getAddressSortOrder(const std::string& addr) const;
|
|
void setAddressSortOrder(const std::string& addr, int order);
|
|
int getNextSortOrder() const;
|
|
void swapAddressOrder(const std::string& a, const std::string& b);
|
|
bool isMiningAddress(const std::string& addr) const;
|
|
void setMiningAddress(const std::string& addr, bool mining);
|
|
void invalidateAddressValidationCache();
|
|
|
|
// Key export/import
|
|
void exportPrivateKey(const std::string& address, std::function<void(const std::string&)> callback);
|
|
void exportAllKeys(std::function<void(const std::string&)> callback);
|
|
void importPrivateKey(const std::string& key, std::function<void(bool, const std::string&)> callback);
|
|
|
|
// Wallet backup
|
|
void backupWallet(const std::string& destination, std::function<void(bool, const std::string&)> callback);
|
|
|
|
// Transaction operations
|
|
void sendTransaction(const std::string& from, const std::string& to,
|
|
double amount, double fee, const std::string& memo,
|
|
std::function<void(bool success, const std::string& result)> callback);
|
|
|
|
// Register a daemon async operation id (z_shieldcoinbase / z_mergetoaddress /
|
|
// auto-shield) with the shared opid poller so its eventual success/failure is
|
|
// surfaced and balances/transactions refresh on completion. z_sendmany uses the
|
|
// richer pending-send path internally; this is for operations with no optimistic
|
|
// transaction row of their own.
|
|
void trackOperation(const std::string& opid);
|
|
|
|
// Force refresh
|
|
void refreshNow();
|
|
void refreshMiningInfo();
|
|
void refreshPeerInfo();
|
|
void refreshMarketData();
|
|
|
|
/// @brief Per-category refresh intervals, adjusted by active tab
|
|
using RefreshIntervals = services::NetworkRefreshService::Intervals;
|
|
|
|
/// @brief Get recommended refresh intervals for a given page
|
|
static RefreshIntervals getIntervalsForPage(ui::NavPage page);
|
|
|
|
// UI navigation
|
|
void setCurrentPage(ui::NavPage page);
|
|
ui::NavPage getCurrentPage() const { return current_page_; }
|
|
|
|
// Dialog triggers (used by settings page to open modal dialogs)
|
|
void showImportKeyDialog() { show_import_key_ = true; }
|
|
void showExportKeyDialog() { show_export_key_ = true; }
|
|
void showBackupDialog() { show_backup_ = true; }
|
|
void showAboutDialog() { show_about_ = true; }
|
|
|
|
// Legacy tab compat — maps int to NavPage
|
|
void setCurrentTab(int tab);
|
|
int getCurrentTab() const { return static_cast<int>(current_page_); }
|
|
|
|
// Payment URI handling
|
|
void handlePaymentURI(const std::string& uri);
|
|
bool hasPendingPayment() const { return pending_payment_valid_; }
|
|
void clearPendingPayment() { pending_payment_valid_ = false; }
|
|
std::string getPendingToAddress() const { return pending_to_address_; }
|
|
double getPendingAmount() const { return pending_amount_; }
|
|
std::string getPendingMemo() const { return pending_memo_; }
|
|
std::string getPendingLabel() const { return pending_label_; }
|
|
|
|
// Embedded daemon control
|
|
bool startEmbeddedDaemon();
|
|
void stopEmbeddedDaemon();
|
|
bool isEmbeddedDaemonRunning() const;
|
|
bool isUsingEmbeddedDaemon() const { return supportsEmbeddedDaemon() && use_embedded_daemon_; }
|
|
void setUseEmbeddedDaemon(bool use) { use_embedded_daemon_ = use && supportsEmbeddedDaemon(); }
|
|
void rescanBlockchain(); // restart daemon with -rescan flag
|
|
void deleteBlockchainData(); // stop daemon, delete chain data, restart fresh
|
|
bool stopDaemonForBootstrap(); // stop daemon + disconnect for bootstrap, returns true if was running
|
|
bool isBootstrapDownloading() const { return bootstrap_downloading_; }
|
|
void setBootstrapDownloading(bool v) { bootstrap_downloading_ = v; }
|
|
|
|
// Get daemon memory usage in MB (uses embedded daemon handle if available,
|
|
// falls back to platform-level process scan for external daemons)
|
|
double getDaemonMemoryUsageMB() const;
|
|
|
|
// Diagnostic string describing daemon memory detection path (for debugging)
|
|
const std::string& getDaemonMemDiag() const { return daemon_mem_diag_; }
|
|
|
|
// Background gradient overlay texture
|
|
void setGradientTexture(ImTextureID tex) { gradient_tex_ = tex; }
|
|
ImTextureID getGradientTexture() const { return gradient_tex_; }
|
|
|
|
// Logo texture accessor (wallet branding icon)
|
|
ImTextureID getLogoTexture() const { return logo_tex_; }
|
|
int getLogoWidth() const { return logo_w_; }
|
|
int getLogoHeight() const { return logo_h_; }
|
|
|
|
// Coin logo texture accessor (DragonX currency icon for balance tab)
|
|
ImTextureID getCoinLogoTexture() const { return coin_logo_tex_; }
|
|
|
|
/**
|
|
* @brief Reload theme images (background gradient + logo) from new paths
|
|
* @param bgPath Path to background image override (empty = use default)
|
|
* @param logoPath Path to logo image override (empty = use default)
|
|
*/
|
|
void reloadThemeImages(const std::string& bgPath, const std::string& logoPath);
|
|
|
|
// Wizard / first-run
|
|
WizardPhase getWizardPhase() const { return wizard_phase_; }
|
|
bool isFirstRun() const;
|
|
|
|
/**
|
|
* @brief Stop daemon and re-run the setup wizard
|
|
* Called from Settings. Daemon restarts automatically when wizard completes.
|
|
*/
|
|
void restartWizard();
|
|
|
|
/**
|
|
* @brief Restart the embedded daemon (e.g. after changing debug categories)
|
|
* Shows "Restarting daemon..." in the loading overlay while the daemon cycles.
|
|
*/
|
|
void restartDaemon();
|
|
|
|
// Wallet encryption helpers
|
|
void encryptWalletWithPassphrase(const std::string& passphrase);
|
|
void unlockWallet(const std::string& passphrase, int timeout);
|
|
void lockWallet();
|
|
void changePassphrase(const std::string& oldPass, const std::string& newPass);
|
|
|
|
// Dialog triggers for encryption (from settings page)
|
|
void showEncryptDialog() {
|
|
show_encrypt_dialog_ = true;
|
|
encrypt_dialog_phase_ = EncryptDialogPhase::PassphraseEntry;
|
|
encrypt_status_.clear();
|
|
memset(encrypt_pass_buf_, 0, sizeof(encrypt_pass_buf_));
|
|
memset(encrypt_confirm_buf_, 0, sizeof(encrypt_confirm_buf_));
|
|
memset(enc_dlg_pin_buf_, 0, sizeof(enc_dlg_pin_buf_));
|
|
memset(enc_dlg_pin_confirm_buf_, 0, sizeof(enc_dlg_pin_confirm_buf_));
|
|
enc_dlg_saved_passphrase_.clear();
|
|
enc_dlg_pin_status_.clear();
|
|
}
|
|
void showChangePassphraseDialog() { show_change_passphrase_ = true; }
|
|
void showDecryptDialog() {
|
|
show_decrypt_dialog_ = true;
|
|
wallet_security_workflow_.reset();
|
|
memset(decrypt_pass_buf_, 0, sizeof(decrypt_pass_buf_));
|
|
}
|
|
|
|
// Dialog triggers for PIN (from settings page)
|
|
void showPinSetupDialog() { show_pin_setup_ = true; pin_status_.clear(); }
|
|
void showPinChangeDialog() { show_pin_change_ = true; pin_status_.clear(); }
|
|
void showPinRemoveDialog() { show_pin_remove_ = true; pin_status_.clear(); }
|
|
bool hasPinVault() const;
|
|
|
|
/// @brief Check if RPC worker has queued results waiting to be processed
|
|
bool hasPendingRPCResults() const;
|
|
bool hasTransactionSendProgress() const { return send_progress_active_ || send_submissions_in_flight_ > 0 || !pending_opids_.empty(); }
|
|
std::string transactionSendProgressText() const;
|
|
std::string transactionRefreshProgressText() const;
|
|
|
|
// Copy a SECRET (seed phrase, private key) to the clipboard and arm an auto-clear: after a
|
|
// short delay the clipboard is wiped IF it still holds this secret (so we don't clobber
|
|
// something the user copied afterwards). Only a hash of the secret is retained, never the
|
|
// plaintext. Call pumpSecretClipboardClear() each frame to action the clear.
|
|
void copySecretToClipboard(const std::string& secret);
|
|
void pumpSecretClipboardClear();
|
|
bool isTransactionRefreshInProgress() const {
|
|
return network_refresh_.jobInProgress(services::NetworkRefreshService::Job::Transactions);
|
|
}
|
|
|
|
private:
|
|
friend class AppDaemonLifecycleRuntime;
|
|
friend class AppDaemonLifecycleTaskContext;
|
|
|
|
bool sendStopCommandSafely(rpc::RPCClient& client, const char* context);
|
|
void maybeFinishTransactionSendProgress();
|
|
void upsertPendingSendTransaction(const std::string& opid,
|
|
const std::string& from,
|
|
const std::string& to,
|
|
double amount,
|
|
const std::string& memo);
|
|
void markPendingSendTransactionSucceeded(const std::string& opid,
|
|
const std::string& txid);
|
|
void removePendingSendTransactions(const std::vector<std::string>& opids,
|
|
bool restoreBalances);
|
|
// Deliver a deferred z_sendmany result to its waiting UI callback once the opid
|
|
// reaches a terminal status. Returns true if a callback was registered (and fired).
|
|
bool invokeSendResultCallback(const std::string& opid, bool ok,
|
|
const std::string& result);
|
|
void applyPendingSendBalanceDeltas(bool includeAggregateBalances);
|
|
std::string transactionHistoryCacheWalletIdentity() const;
|
|
bool ensureTransactionHistoryCacheUnlockedFor(const std::string& walletIdentity);
|
|
void unlockTransactionHistoryCacheWithPassphrase(const std::string& passphrase);
|
|
void loadTransactionHistoryCacheIfAvailable();
|
|
void storeTransactionHistoryCacheIfAvailable();
|
|
void wipePendingTransactionHistoryCachePassphrase();
|
|
void resetTransactionHistoryCacheSession();
|
|
void pruneShieldedHistoryScanProgress();
|
|
void invalidateShieldedHistoryScanProgress(bool persistCache);
|
|
|
|
// Subsystems
|
|
std::unique_ptr<rpc::RPCClient> rpc_;
|
|
std::unique_ptr<rpc::RPCWorker> worker_;
|
|
|
|
// Fast-lane: dedicated RPC connection + worker for 1-second mining polls.
|
|
// Runs on its own thread with its own curl handle so it never blocks behind
|
|
// the main refresh batch.
|
|
std::unique_ptr<rpc::RPCClient> fast_rpc_;
|
|
std::unique_ptr<rpc::RPCWorker> fast_worker_;
|
|
|
|
// Saved connection credentials (needed to open the fast-lane connection)
|
|
rpc::ConnectionConfig saved_config_;
|
|
|
|
std::unique_ptr<config::Settings> settings_;
|
|
std::unique_ptr<wallet::LiteWalletController> lite_wallet_; // lite builds w/ linked backend
|
|
// Pending send_tab callback for an in-flight lite send (delivered in update() once the
|
|
// controller's async broadcast result arrives). Only one lite send runs at a time.
|
|
std::function<void(bool, const std::string&)> lite_send_callback_;
|
|
// One-shot guard: auto-open an existing lite wallet on the first update() tick (kept off
|
|
// init() so a slow initialize_existing network call doesn't freeze startup before the window).
|
|
bool lite_autoopen_done_ = false;
|
|
double lite_open_last_attempt_ = 0.0; // ImGui time of the last async open attempt (retry timer)
|
|
// Reason an existing lite wallet failed to auto-open (e.g. server unreachable). Surfaced in
|
|
// the UI so a stuck "disconnected" state isn't silent; cleared once a wallet opens.
|
|
std::string lite_open_error_;
|
|
// 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.
|
|
bool lite_unlock_prompt_ = false;
|
|
// One-shot: prompt to unlock on startup once we learn the auto-opened wallet is encrypted+locked.
|
|
bool lite_startup_lock_checked_ = false;
|
|
std::unique_ptr<daemon::DaemonController> daemon_controller_;
|
|
std::unique_ptr<daemon::XmrigManager> xmrig_manager_;
|
|
util::AsyncTaskManager async_tasks_;
|
|
bool pending_antivirus_dialog_ = false; // Show Windows Defender help dialog
|
|
|
|
// Wallet state
|
|
WalletState state_;
|
|
|
|
// Shutdown state
|
|
std::atomic<bool> shutting_down_{false};
|
|
std::atomic<bool> shutdown_complete_{false};
|
|
bool address_list_dirty_ = false; // P8: dedup rebuildAddressList
|
|
std::string shutdown_status_;
|
|
std::thread shutdown_thread_;
|
|
float shutdown_timer_ = 0.0f;
|
|
bool force_quit_confirm_ = false;
|
|
std::chrono::steady_clock::time_point shutdown_start_time_;
|
|
|
|
// Daemon restart (e.g. after changing debug log categories)
|
|
std::atomic<bool> daemon_restarting_{false};
|
|
|
|
// Encryption state check timeout
|
|
float encryption_check_timer_ = 0.0f;
|
|
|
|
// UI State
|
|
bool quit_requested_ = false;
|
|
bool show_demo_window_ = false;
|
|
bool show_settings_ = false;
|
|
bool show_about_ = false;
|
|
bool show_import_key_ = false;
|
|
bool show_export_key_ = false;
|
|
bool show_backup_ = false;
|
|
bool show_address_book_ = false;
|
|
|
|
// Embedded daemon state
|
|
bool use_embedded_daemon_ = wallet::supportsEmbeddedDaemon(wallet::currentWalletCapabilities());
|
|
std::string daemon_status_;
|
|
mutable std::string daemon_mem_diag_; // diagnostic info for daemon memory detection
|
|
size_t daemon_output_offset_ = 0; // for incremental output parsing (rescan detection)
|
|
|
|
// Export/Import state
|
|
std::string export_result_;
|
|
char import_key_input_[512] = {0};
|
|
std::string export_address_;
|
|
std::string import_status_;
|
|
bool import_success_ = false;
|
|
std::string backup_status_;
|
|
bool backup_success_ = false;
|
|
|
|
// Connection
|
|
std::string connection_status_ = "Disconnected";
|
|
bool connection_in_progress_ = false;
|
|
bool remote_rpc_plaintext_warning_shown_ = false;
|
|
// Startup daemon-launch diagnostics: bound the "RPC port busy, no config" wait before warning,
|
|
// and show the embedded-daemon start failure (binary/params/spawn) only once. Reset on connect.
|
|
int daemon_wait_attempts_ = 0;
|
|
bool daemon_start_error_shown_ = false;
|
|
int daemon_last_seen_crashes_ = 0; // surface each new embedded-daemon crash reason once
|
|
bool refresh_policy_syncing_ = false; // whether the sync-throttle refresh profile is active
|
|
// Auto-clear for secrets copied to the clipboard. Only a hash of the copied secret is kept.
|
|
std::uint64_t clipboard_secret_hash_ = 0;
|
|
double clipboard_clear_deadline_ = 0.0;
|
|
float loading_timer_ = 0.0f; // spinner animation for loading overlay
|
|
|
|
// Current page (sidebar navigation)
|
|
ui::NavPage current_page_ = ui::NavPage::Overview;
|
|
ui::NavPage prev_page_ = ui::NavPage::Overview;
|
|
float page_alpha_ = 1.0f; // 0→1 fade on page switch
|
|
bool sidebar_collapsed_ = false; // true = icon-only mode
|
|
bool sidebar_user_toggled_ = false; // user manually toggled — suppress auto-collapse
|
|
float sidebar_width_anim_ = 0.0f; // animated width (0 = uninitialized)
|
|
float prev_dpi_scale_ = 0.0f; // detect DPI changes to snap sidebar width
|
|
|
|
// Background gradient overlay
|
|
ImTextureID gradient_tex_ = 0;
|
|
|
|
// Logo texture (reload on skin change / dark↔light switch)
|
|
ImTextureID logo_tex_ = 0;
|
|
int logo_w_ = 0;
|
|
int logo_h_ = 0;
|
|
bool logo_loaded_ = false;
|
|
bool logo_is_dark_variant_ = true; // tracks which variant is currently loaded
|
|
|
|
// Coin logo texture (DragonX currency icon, separate from wallet branding)
|
|
ImTextureID coin_logo_tex_ = 0;
|
|
int coin_logo_w_ = 0;
|
|
int coin_logo_h_ = 0;
|
|
bool coin_logo_loaded_ = false;
|
|
|
|
// Console tab
|
|
ui::ConsoleTab console_tab_;
|
|
|
|
// Pending payment from URI
|
|
bool pending_payment_valid_ = false;
|
|
std::string pending_to_address_;
|
|
double pending_amount_ = 0.0;
|
|
std::string pending_memo_;
|
|
std::string pending_label_;
|
|
|
|
// Per-category refresh timers, policy, and worker queue guards.
|
|
services::NetworkRefreshService network_refresh_;
|
|
int mining_slow_counter_ = 0; // counts fast ticks; fires slow refresh every N
|
|
|
|
// Mining toggle guard (prevents concurrent setgenerate calls)
|
|
std::atomic<bool> mining_toggle_in_progress_{false};
|
|
|
|
// Auto-shield guard (prevents concurrent auto-shield operations)
|
|
std::atomic<bool> auto_shield_pending_{false};
|
|
|
|
// P4: Incremental transaction cache
|
|
int last_tx_block_height_ = -1; // block height at last full tx fetch
|
|
static constexpr int MAX_VIEWTX_PER_CYCLE = 25; // cap z_viewtransaction calls per refresh
|
|
std::size_t shielded_history_scan_cursor_ = 0;
|
|
bool shielded_history_scan_pending_ = false;
|
|
std::unordered_map<std::string, int> shielded_history_scan_heights_;
|
|
|
|
// P4b: z_viewtransaction result cache — avoids re-calling the RPC for
|
|
// txids we've already enriched. Keyed by txid.
|
|
using ViewTxCacheEntry = services::NetworkRefreshService::TransactionViewCacheEntry;
|
|
services::NetworkRefreshService::TransactionViewCache viewtx_cache_;
|
|
|
|
// P4c: Confirmed transaction cache — deeply-confirmed txns (>= 10 confs)
|
|
// are accumulated here and reused across refresh cycles. Only
|
|
// recent/unconfirmed txns are re-fetched from the daemon each time.
|
|
std::vector<TransactionInfo> confirmed_tx_cache_;
|
|
std::unordered_set<std::string> confirmed_tx_ids_; // fast lookup
|
|
int confirmed_cache_block_ = -1; // block height when cache was last built
|
|
|
|
// Dirty flags for demand-driven refresh
|
|
bool addresses_dirty_ = true; // true → refreshAddresses() will run
|
|
bool address_validation_cache_dirty_ = true;
|
|
bool transactions_dirty_ = false; // true → force tx refresh regardless of block height
|
|
bool encryption_state_prefetched_ = false; // suppress duplicate getwalletinfo on connect
|
|
bool rescan_status_poll_in_progress_ = false;
|
|
// True once we've actually observed the rescan running (daemon restarted into -rescan warmup).
|
|
// Gates the "rescan complete" detection so a getrescaninfo poll that hits the still-running
|
|
// pre-restart daemon (which reports rescanning=false) can't fire a false "complete" instantly.
|
|
bool rescan_confirmed_active_ = false;
|
|
bool opid_poll_in_progress_ = false;
|
|
// Consecutive Core-refresh cycles where BOTH core RPCs failed → likely a dead
|
|
// connection. After kCoreFailuresBeforeDisconnect, tear down and reconnect.
|
|
int consecutive_core_failures_ = 0;
|
|
|
|
// Pending z_sendmany operation tracking
|
|
bool send_progress_active_ = false;
|
|
int send_submissions_in_flight_ = 0;
|
|
std::vector<std::string> pending_opids_; // opids to poll for completion
|
|
struct PendingSendInfo {
|
|
std::string from;
|
|
std::string to;
|
|
std::string memo;
|
|
double amount = 0.0;
|
|
std::int64_t timestamp = 0;
|
|
};
|
|
std::unordered_map<std::string, PendingSendInfo> pending_send_info_;
|
|
// z_sendmany UI callbacks held until the opid reaches a terminal status, so the
|
|
// user isn't told "sent successfully" before the tx is actually built/broadcast.
|
|
std::unordered_map<std::string, std::function<void(bool, const std::string&)>>
|
|
pending_send_callbacks_;
|
|
// 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<std::string> send_txids_;
|
|
|
|
// First-run wizard state
|
|
WizardPhase wizard_phase_ = WizardPhase::None;
|
|
std::unique_ptr<util::Bootstrap> bootstrap_;
|
|
bool bootstrap_downloading_ = false; // true while settings bootstrap dialog is active
|
|
std::string wizard_pending_passphrase_; // held until daemon connects
|
|
std::string wizard_saved_passphrase_; // held until PinSetup completes/skipped
|
|
|
|
// Wallet security flow state shared by wizard/settings encryption paths.
|
|
services::WalletSecurityController wallet_security_;
|
|
services::WalletSecurityWorkflow wallet_security_workflow_;
|
|
|
|
// Wizard: stopping an external daemon before bootstrap
|
|
bool wizard_stopping_external_ = false;
|
|
std::string wizard_stop_status_;
|
|
|
|
// PIN vault
|
|
std::unique_ptr<util::SecureVault> vault_;
|
|
data::TransactionHistoryCache transaction_history_cache_;
|
|
std::string pending_transaction_history_cache_passphrase_;
|
|
bool transaction_history_cache_loaded_ = false;
|
|
|
|
// Lock screen state
|
|
bool lock_screen_was_visible_ = false; // tracks lock→unlock transitions for auto-focus
|
|
bool lock_use_pin_ = true; // true = PIN input, false = passphrase input
|
|
char lock_pin_buf_[16] = {};
|
|
char lock_passphrase_buf_[256] = {};
|
|
std::string lock_error_msg_;
|
|
float lock_error_timer_ = 0.0f;
|
|
int lock_attempts_ = 0;
|
|
float lock_lockout_timer_ = 0.0f;
|
|
bool lock_unlock_in_progress_ = false;
|
|
|
|
// Encrypt wallet dialog state
|
|
bool show_encrypt_dialog_ = false;
|
|
bool show_change_passphrase_ = false;
|
|
EncryptDialogPhase encrypt_dialog_phase_ = EncryptDialogPhase::PassphraseEntry;
|
|
char encrypt_pass_buf_[256] = {};
|
|
char encrypt_confirm_buf_[256] = {};
|
|
char change_old_pass_buf_[256] = {};
|
|
char change_new_pass_buf_[256] = {};
|
|
char change_confirm_buf_[256] = {};
|
|
std::string encrypt_status_;
|
|
bool encrypt_in_progress_ = false;
|
|
std::string enc_dlg_saved_passphrase_; // held for PIN setup after encrypt
|
|
char enc_dlg_pin_buf_[16] = {};
|
|
char enc_dlg_pin_confirm_buf_[16] = {};
|
|
std::string enc_dlg_pin_status_;
|
|
|
|
// PIN setup dialog state (settings page)
|
|
bool show_pin_setup_ = false;
|
|
bool show_pin_change_ = false;
|
|
bool show_pin_remove_ = false;
|
|
char pin_buf_[16] = {};
|
|
char pin_confirm_buf_[16] = {};
|
|
char pin_old_buf_[16] = {};
|
|
char pin_passphrase_buf_[256] = {};
|
|
std::string pin_status_;
|
|
bool pin_in_progress_ = false;
|
|
|
|
// Decrypt wallet dialog state
|
|
bool show_decrypt_dialog_ = false;
|
|
char decrypt_pass_buf_[256] = {};
|
|
|
|
// Wizard PIN setup state
|
|
char wizard_pin_buf_[16] = {};
|
|
char wizard_pin_confirm_buf_[16] = {};
|
|
std::string wizard_pin_status_;
|
|
|
|
// Auto-lock on idle
|
|
std::chrono::steady_clock::time_point last_interaction_ = std::chrono::steady_clock::now();
|
|
|
|
// Mine-when-idle: auto-start/stop mining based on system idle state
|
|
bool idle_mining_active_ = false; // true when mining was auto-started by idle detection
|
|
bool idle_scaled_to_idle_ = false; // true when threads have been scaled up for idle
|
|
|
|
// Private methods - rendering
|
|
void renderStatusBar();
|
|
void renderAboutDialog();
|
|
void renderLiteFirstRunPrompt(); // lite-only welcome modal when no wallet exists yet
|
|
void renderLiteUnlockPrompt(); // lite-only send-time unlock modal
|
|
void renderImportKeyDialog();
|
|
void renderExportKeyDialog();
|
|
void renderBackupDialog();
|
|
void renderFirstRunWizard();
|
|
void renderLockScreen();
|
|
void renderEncryptWalletDialog();
|
|
void renderDecryptWalletDialog();
|
|
void renderPinDialogs();
|
|
void renderAntivirusHelpDialog();
|
|
void processDeferredEncryption();
|
|
|
|
// Private methods - connection
|
|
void tryConnect();
|
|
void onConnected();
|
|
void onDisconnected(const std::string& reason);
|
|
// Set the "node is initializing" UI state (status line + overlay description) from the
|
|
// embedded/external daemon's launch state and its own console output (current phase + block
|
|
// height), so a connect probe that times out while the daemon loads shows WHAT it's doing.
|
|
// `reachableButBusy` is true when the probe connected but got no RPC reply (a timeout),
|
|
// false when the daemon is merely launching (not bound yet). Returns the status title.
|
|
std::string applyDaemonInitStatus(bool reachableButBusy);
|
|
// Tear down a connection that died mid-session (daemon crash / restart / dropped
|
|
// socket) so update()'s reconnect branch re-enters tryConnect(). Unlike onDisconnected
|
|
// alone, this also rpc_->disconnect()s so rpc_->isConnected() actually flips to false.
|
|
void handleLostConnection(const std::string& reason);
|
|
void applyDefaultBanlist();
|
|
|
|
// Private methods - data refresh
|
|
void refreshData(); // Orchestrator: dispatches per-category refreshes
|
|
void refreshCoreData(); // Balance + blockchain info (can use fast_worker_)
|
|
void refreshAddressData(); // Address lists + balances
|
|
void refreshTransactionData(); // Transaction list + z_viewtransaction enrichment
|
|
void refreshRecentTransactionData(); // Lightweight recent/unconfirmed tx poll
|
|
bool refreshEncryptionState(); // Wallet encryption/lock state
|
|
void refreshBalance(); // Legacy: balance-only refresh (used by specific callers)
|
|
void refreshAddresses(); // Legacy: standalone address refresh
|
|
void refreshPrice();
|
|
void refreshWalletEncryptionState();
|
|
void applyRefreshPolicy(ui::NavPage page);
|
|
bool currentPageNeedsWalletDataRefresh() const;
|
|
bool shouldRunWalletTransactionRefresh() const;
|
|
bool shouldRefreshTransactions() const;
|
|
bool shouldRefreshRecentTransactions() const;
|
|
void checkAutoLock();
|
|
void checkIdleMining();
|
|
};
|
|
|
|
} // namespace dragonx
|