// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #pragma once #include #include #include #include #include #include #include "exchange_info.h" #include "candle.h" namespace dragonx { /** * @brief Represents an address with its balance */ struct AddressInfo { std::string address; double balance = 0.0; std::string type; // "shielded" or "transparent" bool has_spending_key = true; // false for view-only (imported via z_importviewingkey) // For display std::string label; // Derived bool isZAddr() const { return !address.empty() && address[0] == 'z'; } bool isShielded() const { return type == "shielded"; } bool isSpendable() const { return has_spending_key; } }; std::vector sortedSpendableAddressIndices(const std::vector& addresses, bool requirePositiveBalance = true); int bestSpendableAddressIndex(const std::vector& addresses); /** * @brief Represents a wallet transaction */ struct TransactionInfo { std::string txid; std::string type; // "send", "receive", "mined" double amount = 0.0; int64_t timestamp = 0; // Unix timestamp int confirmations = 0; std::string address; // destination (send) or source (receive) std::string from_address; // source address for sends std::string memo; // Computed fields std::string getTimeString() const; std::string getTypeDisplay() const; bool isConfirmed() const { return confirmations >= 1; } bool isMature() const { return confirmations >= 100; } }; /** * @brief Represents a connected peer */ struct PeerInfo { int id = 0; std::string addr; std::string subver; std::string services; int version = 0; int64_t conntime = 0; int banscore = 0; double pingtime = 0.0; int64_t bytessent = 0; int64_t bytesrecv = 0; int startingheight = 0; int synced_headers = 0; int synced_blocks = 0; bool inbound = false; // TLS info std::string tls_cipher; bool tls_verified = false; std::string getConnectionTime() const; }; /** * @brief Represents a banned peer */ struct BannedPeer { std::string address; std::string subnet; int64_t banned_until = 0; std::string getBannedUntilString() const; }; /** * @brief Mining statistics */ struct MiningInfo { bool generate = false; int genproclimit = 0; // -1 means max CPUs double localHashrate = 0.0; // Local hashrate (H/s) from getlocalsolps RPC (RandomX) double networkHashrate = 0.0; // Network hashrate (H/s) int blocks = 0; double difficulty = 0.0; std::string chain; double daemon_memory_mb = 0.0; // Daemon process RSS in MB // History for chart std::vector hashrate_history; // Last N samples static constexpr int MAX_HISTORY = 300; // 5 minutes at 1s intervals // Recent daemon log lines for the mining log panel std::vector log_lines; }; /** * @brief Blockchain synchronization info */ struct SyncInfo { int blocks = 0; int headers = 0; double verification_progress = 0.0; bool syncing = false; std::string best_blockhash; // Rescan state (detected from daemon output) bool rescanning = false; float rescan_progress = 0.0f; // 0.0 - 1.0 std::string rescan_status; // e.g. "Rescanning... 25%" // Sapling note witness rebuild — a distinct, often-long phase after a rescan/zap. The daemon // reports it in TWO sub-phases with different signals, so we track which is active: // 1 = initial pass ("Setting Initial Sapling Witness for tx , of ") — progress // is distinct-txs-witnessed / N (the bounces, so it can't be used directly). // 2 = witness-cache walk ("Building Witnesses for block complete, remaining") // — progress derived from how far "remaining" has fallen from its per-phase peak. // The two are sequential with different scales, so progress is NOT carried across the boundary // (that would pin the bar at the initial pass's ~100% through the whole cache walk). bool building_witnesses = false; int witness_phase = 0; // 0 none, 1 initial-witness pass, 2 witness-cache walk float witness_progress = 0.0f; // 0.0 - 1.0, within the current sub-phase int witness_remaining = 0; // blocks left in the cache walk (0 if unknown / phase 1) bool isSynced() const { return !syncing && blocks > 0 && blocks >= headers - 2; } }; /** * @brief Market/price information */ struct MarketInfo { double price_usd = 0.0; double price_btc = 0.0; double volume_24h = 0.0; double change_24h = 0.0; double market_cap = 0.0; std::string last_updated; std::chrono::steady_clock::time_point last_fetch_time{}; bool price_loading = false; std::string price_error; // Live in-session price history: ~1 sample/minute, capped at MAX_HISTORY samples. // Backs the main chart and the "minute" portfolio-sparkline interval. std::vector price_history; static constexpr int MAX_HISTORY = 24; // 24 samples (~24 minutes at the 60s refresh) // Historical USD price series fetched from CoinGecko market_chart, so the portfolio-group // sparklines can show real hour/day/week/month trends instead of resampling the ~24-minute // in-session buffer. Timestamped (unix seconds), oldest->newest; empty until the first fetch. // Refreshed on a slow cadence (~30 min) since historical data moves slowly. std::vector> price_chart_intraday; // ~24h @ 5-minute granularity std::vector> price_chart_daily; // ~1yr @ daily granularity std::chrono::steady_clock::time_point chart_last_fetch_time{}; bool chart_loaded = false; // Per-EXCHANGE candle series for the SELECTED pair, fetched from that venue's own API (see // data/exchange_candles.h). When exchange_chart_active is true the Market chart draws these instead // of the CoinGecko cross-exchange aggregate above; it's set false while switching pairs or when the // selected venue has no adapter / its fetch failed, so the chart gracefully falls back to aggregate. std::vector> exchange_chart_intraday; std::vector> exchange_chart_daily; bool exchange_chart_active = false; // Full OHLC for the same per-exchange candles, backing the candlestick rendering (the aggregate // CoinGecko series is close-only, so it stays a line). Parallel to exchange_chart_* above. std::vector exchange_ohlc_intraday; std::vector exchange_ohlc_daily; // Exchanges/pairs fetched live from CoinGecko (empty until fetched; the Market tab // falls back to data::getExchangeRegistry() while empty). std::vector exchanges; }; /** * @brief Pool mining state (from xmrig HTTP API) */ struct PoolMiningState { bool pool_mode = false; // UI toggle: solo vs pool bool xmrig_running = false; std::string pool_url; std::string algo; std::string version; // running miner's version (from its API) double hashrate_10s = 0; double hashrate_60s = 0; double hashrate_15m = 0; int64_t accepted = 0; int64_t rejected = 0; int64_t uptime_sec = 0; double pool_diff = 0; bool connected = false; // Memory/thread usage (bytes for memory) int64_t memory_used = 0; int threads_active = 0; // Pool-side hashrate (from pool stats API) double pool_hashrate = 0; // Hashrate history for chart (mirrors MiningInfo::hashrate_history) std::vector hashrate_history; static constexpr int MAX_HISTORY = 60; // 5 minutes at ~5s intervals // Recent log lines for the log panel std::vector log_lines; }; /** * @brief Complete wallet state - all data fetched from daemon */ struct WalletState { // Connection bool connected = false; bool warming_up = false; // daemon reachable but in RPC warmup (error -28) // True when the daemon is up/launching but not yet answering RPC (e.g. the connect probe // times out because the node is loading the block index). Distinct from warming_up, which // needs a JSON-RPC -28 reply; here getinfo never returns, so we infer the state from the // daemon's launch state + its own console output. Drives the same loading overlay so the // user sees WHAT the node is doing instead of a bare "Connection failed". bool daemon_initializing = false; std::string warmup_status; // user-friendly title, e.g. "Processing blocks..." std::string warmup_description; // subtitle explaining the stage int daemon_version = 0; std::string daemon_subversion; int protocol_version = 0; int p2p_port = 0; int longestchain = 0; int notarized = 0; // Sync status SyncInfo sync; // Balances (named to match UI usage) double privateBalance = 0.0; // shielded balance double transparentBalance = 0.0; double totalBalance = 0.0; double unconfirmedBalance = 0.0; // Aliases for backward compatibility double& shielded_balance = privateBalance; double& transparent_balance = transparentBalance; double& total_balance = totalBalance; double& unconfirmed_balance = unconfirmedBalance; // Addresses - combined list for UI convenience std::vector addresses; // Also keep separate lists for legacy code std::vector z_addresses; std::vector t_addresses; // Transactions std::vector transactions; // Peers std::vector peers; std::vector bannedPeers; // Aliases for banned_peers std::vector& banned_peers = bannedPeers; // Mining MiningInfo mining; // Pool mining (xmrig) PoolMiningState pool_mining; // Market MarketInfo market; // Wallet encryption state (populated from getwalletinfo) bool encrypted = false; // true if wallet has ever been encrypted bool locked = false; // true if encrypted && unlocked_until <= now int64_t unlocked_until = 0; // 0 = locked, >0 = unix timestamp when auto-lock fires bool encryption_state_known = false; // true once first getwalletinfo response processed bool isEncrypted() const { return encrypted; } bool isLocked() const { return encrypted && locked; } bool isUnlocked() const { return encrypted && !locked; } // Timestamps for refresh logic int64_t last_balance_update = 0; int64_t last_tx_update = 0; int64_t last_peer_update = 0; int64_t last_mining_update = 0; // Helper methods int getAddressCount() const { return addresses.size(); } double getBalanceUSD() const { return totalBalance * market.price_usd; } void clear() { connected = false; warming_up = false; daemon_initializing = false; warmup_status.clear(); warmup_description.clear(); daemon_version = 0; daemon_subversion.clear(); protocol_version = 0; p2p_port = 0; longestchain = 0; notarized = 0; sync = SyncInfo{}; privateBalance = transparentBalance = totalBalance = 0.0; unconfirmedBalance = 0.0; encrypted = false; locked = false; unlocked_until = 0; encryption_state_known = false; addresses.clear(); z_addresses.clear(); t_addresses.clear(); transactions.clear(); peers.clear(); bannedPeers.clear(); // W6-1: reset node-level mining state too — the daemon restarts on a wallet switch (mining // stops), so leaving the previous wallet's hashrate/blocks would show stale mining stats. mining = MiningInfo{}; pool_mining = PoolMiningState{}; } // Rebuild combined addresses list from z/t lists void rebuildAddressList() { addresses.clear(); addresses.reserve(z_addresses.size() + t_addresses.size()); for (const auto& addr : z_addresses) { addresses.push_back(addr); } for (const auto& addr : t_addresses) { addresses.push_back(addr); } } }; } // namespace dragonx