- Remove getinfo from refreshMiningInfo slow path; daemon_version, protocol_version, and p2p_port are static per connection (set in onConnected). Move longestchain/notarized into refreshBalance's existing getblockchaininfo callback. - Remove refreshMiningInfo from refreshData(); it already runs on the 1-second fast_refresh_timer_ independently. - Make refreshAddresses demand-driven via addresses_dirty_ flag; only re-fetch when a new address is created or a send completes, not unconditionally every 5 seconds. - Gate refreshPeerInfo to only run when the Peers tab is active. - Skip duplicate getwalletinfo on connect; onConnected() already prefetches it for immediate lock-screen display, so suppress the redundant call in the first refreshData() cycle. Steady-state savings: ~8 fewer RPC calls per 5-second cycle (from ~12+ down to ~4-5 in the common case).
531 lines
19 KiB
C++
531 lines
19 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 "data/wallet_state.h"
|
|
#include "ui/sidebar.h"
|
|
#include "ui/windows/console_tab.h"
|
|
#include "imgui.h"
|
|
|
|
// Forward declarations
|
|
namespace dragonx {
|
|
namespace rpc {
|
|
class RPCClient;
|
|
class RPCWorker;
|
|
struct ConnectionConfig;
|
|
}
|
|
namespace config { class Settings; }
|
|
namespace daemon { class EmbeddedDaemon; class XmrigManager; }
|
|
namespace util { class Bootstrap; class SecureVault; }
|
|
}
|
|
|
|
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 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_; }
|
|
|
|
/**
|
|
* @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(); }
|
|
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();
|
|
|
|
// Peers
|
|
const std::vector<PeerInfo>& getPeers() const { return state_.peers; }
|
|
const std::vector<BannedPeer>& getBannedPeers() const { return state_.bannedPeers; }
|
|
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;
|
|
|
|
// 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);
|
|
|
|
// Force refresh
|
|
void refreshNow();
|
|
void refreshMiningInfo();
|
|
void refreshPeerInfo();
|
|
void refreshMarketData();
|
|
|
|
// UI navigation
|
|
void setCurrentPage(ui::NavPage page) { current_page_ = 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 use_embedded_daemon_; }
|
|
void setUseEmbeddedDaemon(bool use) { use_embedded_daemon_ = use; }
|
|
|
|
// 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_; }
|
|
|
|
// 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;
|
|
decrypt_phase_ = 0; // passphrase entry
|
|
decrypt_status_.clear();
|
|
decrypt_in_progress_ = false;
|
|
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;
|
|
|
|
private:
|
|
// Subsystems
|
|
std::unique_ptr<rpc::RPCClient> rpc_;
|
|
std::unique_ptr<rpc::RPCWorker> worker_;
|
|
std::unique_ptr<config::Settings> settings_;
|
|
std::unique_ptr<daemon::EmbeddedDaemon> embedded_daemon_;
|
|
std::unique_ptr<daemon::XmrigManager> xmrig_manager_;
|
|
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};
|
|
std::atomic<bool> refresh_in_progress_{false};
|
|
bool address_list_dirty_ = false; // P8: dedup rebuildAddressList
|
|
std::string shutdown_status_;
|
|
std::thread shutdown_thread_;
|
|
float shutdown_timer_ = 0.0f;
|
|
std::chrono::steady_clock::time_point shutdown_start_time_;
|
|
|
|
// Daemon restart (e.g. after changing debug log categories)
|
|
std::atomic<bool> daemon_restarting_{false};
|
|
std::thread daemon_restart_thread_;
|
|
|
|
// 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_ = true;
|
|
std::string daemon_status_;
|
|
mutable std::string daemon_mem_diag_; // diagnostic info for daemon memory 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;
|
|
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_;
|
|
|
|
// Timers (in seconds since last update)
|
|
float refresh_timer_ = 0.0f;
|
|
float price_timer_ = 0.0f;
|
|
float fast_refresh_timer_ = 0.0f; // For mining stats
|
|
|
|
// Refresh intervals (seconds)
|
|
static constexpr float REFRESH_INTERVAL = 5.0f;
|
|
static constexpr float PRICE_INTERVAL = 60.0f;
|
|
static constexpr float FAST_REFRESH_INTERVAL = 1.0f;
|
|
|
|
// Mining refresh guard (prevents worker queue pileup)
|
|
std::atomic<bool> mining_refresh_in_progress_{false};
|
|
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
|
|
|
|
// Dirty flags for demand-driven refresh
|
|
bool addresses_dirty_ = true; // true → refreshAddresses() will run
|
|
bool encryption_state_prefetched_ = false; // suppress duplicate getwalletinfo on connect
|
|
|
|
// First-run wizard state
|
|
WizardPhase wizard_phase_ = WizardPhase::None;
|
|
std::unique_ptr<util::Bootstrap> bootstrap_;
|
|
std::string wizard_pending_passphrase_; // held until daemon connects
|
|
std::string wizard_saved_passphrase_; // held until PinSetup completes/skipped
|
|
|
|
// Deferred encryption (wizard background task)
|
|
std::string deferred_encrypt_passphrase_;
|
|
std::string deferred_encrypt_pin_;
|
|
bool deferred_encrypt_pending_ = false;
|
|
|
|
// Wizard: stopping an external daemon before bootstrap
|
|
bool wizard_stopping_external_ = false;
|
|
std::string wizard_stop_status_;
|
|
std::thread wizard_stop_thread_;
|
|
|
|
// PIN vault
|
|
std::unique_ptr<util::SecureVault> vault_;
|
|
|
|
// 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;
|
|
int decrypt_phase_ = 0; // 0=passphrase, 1=working, 2=done, 3=error
|
|
char decrypt_pass_buf_[256] = {};
|
|
std::string decrypt_status_;
|
|
bool decrypt_in_progress_ = false;
|
|
|
|
// 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();
|
|
|
|
// Private methods - rendering
|
|
void renderStatusBar();
|
|
void renderAboutDialog();
|
|
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);
|
|
|
|
// Private methods - data refresh
|
|
void refreshData();
|
|
void refreshBalance();
|
|
void refreshAddresses();
|
|
void refreshTransactions();
|
|
void refreshPrice();
|
|
void refreshWalletEncryptionState();
|
|
void checkAutoLock();
|
|
};
|
|
|
|
} // namespace dragonx
|