// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 // // App-owned controller that drives the lite wallet. It constructs and owns the // lite services with bridge calls ENABLED (the services otherwise default to // allowBridgeCalls=false and are never instantiated), executes real create/open/ // restore operations through the linked SDXL backend, securely wipes secrets after // use, tracks wallet-open state, and invokes a persistence callback on success. // // Construction: // - Production: LiteWalletController::createLinked(caps, connectionSettings) // (uses LiteClientBridge::linkedSdxl(); requires DRAGONX_ENABLE_LITE_BACKEND). // - Tests: construct directly with an injected bridge // (e.g. LiteClientBridge::fromApi(makeFakeLiteApi())). #pragma once #include "lite_client_bridge.h" #include "lite_connection_service.h" #include "lite_wallet_lifecycle_service.h" #include "lite_wallet_gateway.h" #include "lite_sync_service.h" #include "lite_wallet_state_mapper.h" #include "wallet_backend.h" #include "wallet_capabilities.h" #include #include #include #include #include #include #include #include namespace dragonx { struct WalletState; // data/wallet_state.h namespace wallet { // Securely zero and clear a string holding secret material (seed/passphrase). void secureWipeLiteSecret(std::string& secret); // Apply a normalized lite refresh model onto the app's WalletState (the last hop the // existing Balance/Receive/Transactions tabs read from). Converts zatoshis -> DRGX, // splits addresses into shielded/transparent, and maps sync progress. Mutates in place // (WalletState is non-copyable); only sections present in the model are touched. void applyLiteRefreshModelToWalletState(const LiteWalletAppRefreshModel& model, dragonx::WalletState& state); struct LiteWalletControllerOptions { bool allowBridgeCalls = true; }; class LiteWalletController { public: LiteWalletController(WalletCapabilities capabilities, LiteConnectionSettings connectionSettings, LiteClientBridge bridge, LiteWalletControllerOptions options = LiteWalletControllerOptions{}); ~LiteWalletController(); // stops + joins the background refresh worker LiteWalletController(const LiteWalletController&) = delete; LiteWalletController& operator=(const LiteWalletController&) = delete; // Production factory: links the SDXL backend compiled into this build. static std::unique_ptr createLinked( WalletCapabilities capabilities, LiteConnectionSettings connectionSettings); // Invoked after a wallet becomes ready, so the owner can persist settings. void setPersistCallback(std::function callback) { persist_ = std::move(callback); } bool walletOpen() const { return walletOpen_; } const WalletBackendStatus& status() const { return status_; } LiteWalletLifecycleAvailability availability() const { return lifecycle_.availability(); } // Execute a real lifecycle operation. The request is taken by value; its secret // fields are securely wiped before returning. On a ready wallet, walletOpen() // becomes true and the persist callback (if any) fires. LiteWalletLifecycleResult createWallet(LiteWalletCreateRequest request); LiteWalletLifecycleResult openWallet(LiteWalletOpenRequest request); LiteWalletLifecycleResult restoreWallet(LiteWalletRestoreRequest request); bool syncStarted() const { return syncStarted_; } bool syncComplete() const { return syncDone_ && syncDone_->load(); } // Launch the backend sync on a detached background thread (NON-blocking; the backend's // `sync` command runs a full, uninterruptible chain scan). Auto-invoked when a lifecycle // op produces a ready wallet; safe to call once. void startSync(); // Poll sync status + fetch balance/addresses/transactions, and apply the result into the // app's WalletState. Returns true if state was updated. Safe no-op when no wallet is open. // Synchronous (blocks on the backend); used by tests and as the worker's unit of work. bool refreshWalletState(dragonx::WalletState& state); // Synchronous refresh that returns the mapped model (or nullopt). Pure w.r.t. shared // state; safe to call from the background worker. nullopt when no wallet is open or the // refresh produced nothing usable. std::optional refreshModel(); // Main-thread handoff: if the background worker has produced a fresh model since the last // call, move it into `out` and return true. Apply it with applyLiteRefreshModelToWalletState. bool takeRefreshedModel(LiteWalletAppRefreshModel& out); private: void onLifecycleResult(const LiteWalletLifecycleResult& result); void startWorker(); void stopWorker(); void workerLoop(); // The bridge is shared (not just owned) so the detached, uninterruptible sync thread can // safely outlive the controller: it holds a ref, so the underlying bridge is destroyed // (and litelib_shutdown called) only once BOTH the controller and a running sync release it. std::shared_ptr bridge_; LiteWalletLifecycleService lifecycle_; LiteWalletGateway gateway_; LiteSyncService sync_; std::function persist_; std::atomic walletOpen_{false}; std::atomic syncStarted_{false}; WalletBackendStatus status_; // written only on the main thread (lifecycle ops) // Detached background sync (backend `sync` is a blocking, uninterruptible full scan). std::thread syncThread_; bool syncLaunched_ = false; std::shared_ptr> syncDone_ = std::make_shared>(false); // Joinable background refresh worker (fast iterations: syncstatus, plus data once synced). std::thread worker_; std::atomic running_{false}; std::mutex wakeMutex_; std::condition_variable wakeCv_; std::mutex modelMutex_; std::optional pendingModel_; // guarded by modelMutex_ static constexpr int kRefreshIntervalMs = 2000; }; } // namespace wallet } // namespace dragonx