From dcd09dc5428baff7d6c3c2420736c6a46b83e2c1 Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 2 Jul 2026 19:56:54 -0500 Subject: [PATCH] =?UTF-8?q?refactor(audit):=20batch=207=20=E2=80=94=20RPC/?= =?UTF-8?q?settings/lite/effects=20mechanical=20dedups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six behavior-preserving consolidations from the audit: - RPCClient::call() overloads share a private performCall() (payload dump + curl_easy_perform + http code) and a static parseRpcResult() (error->RpcError extraction). The timeout overload restores the prior timeout in both the success and catch paths, exactly as before. - The six UnifiedCallback methods delegate to one splitUnified() that builds the (Callback, ErrorCallback) pair once (null-check preserved). - One liteTrimCopy() in lite_connection_service replaces 5 file-local trim-copy helpers across the lite slice. - lite_wallet_controller's inline height JSON parse now routes through the tested parseLiteHeightResponse(). - theme_effects.cpp: unpackRGB()/scaledAlpha() replace 14 RGB-unpack + 5 alpha-scale copy-paste blocks. - settings.cpp load() uses loadScalar()/loadClamped() helpers for ~44 scalar fields. Besides removing boilerplate this HARDENS loading: ~44 fields that previously only checked contains() now also verify the JSON type, so a malformed value in settings.json falls back to the default instead of throwing/misreading. Custom cases (enum parses, legacy migrations, arrays) stay inline; save() is unchanged. Full-node + Lite build clean; ctest 1/1; hygiene clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/config/settings.cpp | 133 ++++++++------ src/rpc/rpc_client.cpp | 164 ++++++------------ src/rpc/rpc_client.h | 11 ++ src/ui/effects/theme_effects.cpp | 134 +++++++------- src/wallet/lite_connection_service.cpp | 32 ++-- src/wallet/lite_connection_service.h | 2 + src/wallet/lite_sync_service.cpp | 14 +- src/wallet/lite_wallet_controller.cpp | 22 +-- src/wallet/lite_wallet_gateway.cpp | 14 +- src/wallet/lite_wallet_lifecycle_service.cpp | 16 +- .../lite_wallet_server_selection_adapter.cpp | 31 +--- 11 files changed, 245 insertions(+), 328 deletions(-) diff --git a/src/config/settings.cpp b/src/config/settings.cpp index a4a9fca..28a2ca1 100644 --- a/src/config/settings.cpp +++ b/src/config/settings.cpp @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include "../util/logger.h" #include "../util/platform.h" @@ -76,6 +78,34 @@ const char* poolSelectModeName(Settings::PoolSelectMode mode) return "manual"; } +// True if j[key] exists and holds a JSON value convertible to T. Guards every scalar +// read so a malformed value (wrong type / missing key) leaves the field's default +// instead of throwing out of the whole load(). +template +bool jsonHasType(const json& v) +{ + if constexpr (std::is_same_v) return v.is_boolean(); + else if constexpr (std::is_same_v) return v.is_string(); + else if constexpr (std::is_floating_point_v) return v.is_number(); + else if constexpr (std::is_integral_v) return v.is_number_integer(); + else return false; +} + +// Reads j[key] into field only when present AND of the matching type. +template +void loadScalar(const json& j, const char* key, T& field) +{ + if (j.contains(key) && jsonHasType(j[key])) field = j[key].get(); +} + +// Same as loadScalar but clamps the read value into [lo, hi]. +template +void loadClamped(const json& j, const char* key, T& field, T lo, T hi) +{ + if (j.contains(key) && jsonHasType(j[key])) + field = std::max(lo, std::min(hi, j[key].get())); +} + } // namespace std::string Settings::getDefaultPath() @@ -127,29 +157,29 @@ bool Settings::load(const std::string& path) json j; file >> j; - if (j.contains("theme")) theme_ = j["theme"].get(); - if (j.contains("save_ztxs")) save_ztxs_ = j["save_ztxs"].get(); - if (j.contains("auto_shield")) auto_shield_ = j["auto_shield"].get(); - if (j.contains("use_tor")) use_tor_ = j["use_tor"].get(); - if (j.contains("allow_custom_fees")) allow_custom_fees_ = j["allow_custom_fees"].get(); - if (j.contains("default_fee")) default_fee_ = j["default_fee"].get(); - if (j.contains("fetch_prices")) fetch_prices_ = j["fetch_prices"].get(); - if (j.contains("tx_explorer_url")) tx_explorer_url_ = j["tx_explorer_url"].get(); - if (j.contains("address_explorer_url")) address_explorer_url_ = j["address_explorer_url"].get(); - if (j.contains("language")) language_ = j["language"].get(); - if (j.contains("skin_id")) skin_id_ = j["skin_id"].get(); - if (j.contains("acrylic_enabled")) acrylic_enabled_ = j["acrylic_enabled"].get(); - if (j.contains("acrylic_quality")) acrylic_quality_ = j["acrylic_quality"].get(); - if (j.contains("blur_multiplier")) blur_multiplier_ = j["blur_multiplier"].get(); - if (j.contains("noise_opacity")) noise_opacity_ = j["noise_opacity"].get(); - if (j.contains("gradient_background")) gradient_background_ = j["gradient_background"].get(); + loadScalar(j, "theme", theme_); + loadScalar(j, "save_ztxs", save_ztxs_); + loadScalar(j, "auto_shield", auto_shield_); + loadScalar(j, "use_tor", use_tor_); + loadScalar(j, "allow_custom_fees", allow_custom_fees_); + loadScalar(j, "default_fee", default_fee_); + loadScalar(j, "fetch_prices", fetch_prices_); + loadScalar(j, "tx_explorer_url", tx_explorer_url_); + loadScalar(j, "address_explorer_url", address_explorer_url_); + loadScalar(j, "language", language_); + loadScalar(j, "skin_id", skin_id_); + loadScalar(j, "acrylic_enabled", acrylic_enabled_); + loadScalar(j, "acrylic_quality", acrylic_quality_); + loadScalar(j, "blur_multiplier", blur_multiplier_); + loadScalar(j, "noise_opacity", noise_opacity_); + loadScalar(j, "gradient_background", gradient_background_); // Migrate legacy reduced_transparency bool -> ui_opacity float if (j.contains("ui_opacity")) { ui_opacity_ = j["ui_opacity"].get(); } else if (j.contains("reduced_transparency") && j["reduced_transparency"].get()) { ui_opacity_ = 1.0f; // legacy: reduced = fully opaque } - if (j.contains("window_opacity")) window_opacity_ = j["window_opacity"].get(); + loadScalar(j, "window_opacity", window_opacity_); if (j.contains("balance_layout")) { if (j["balance_layout"].is_string()) balance_layout_ = j["balance_layout"].get(); @@ -163,7 +193,7 @@ bool Settings::load(const std::string& path) if (idx >= 0 && idx < 9) balance_layout_ = legacyIds[idx]; } } - if (j.contains("scanline_enabled")) scanline_enabled_ = j["scanline_enabled"].get(); + loadScalar(j, "scanline_enabled", scanline_enabled_); if (j.contains("hidden_addresses") && j["hidden_addresses"].is_array()) { hidden_addresses_.clear(); for (const auto& a : j["hidden_addresses"]) @@ -189,13 +219,13 @@ bool Settings::load(const std::string& path) address_meta_[addr] = m; } } - if (j.contains("wizard_completed")) wizard_completed_ = j["wizard_completed"].get(); - if (j.contains("auto_lock_timeout")) auto_lock_timeout_ = j["auto_lock_timeout"].get(); - if (j.contains("unlock_duration")) unlock_duration_ = j["unlock_duration"].get(); - if (j.contains("pin_enabled")) pin_enabled_ = j["pin_enabled"].get(); - if (j.contains("keep_daemon_running")) keep_daemon_running_ = j["keep_daemon_running"].get(); - if (j.contains("stop_external_daemon")) stop_external_daemon_ = j["stop_external_daemon"].get(); - if (j.contains("max_connections")) max_connections_ = j["max_connections"].get(); + loadScalar(j, "wizard_completed", wizard_completed_); + loadScalar(j, "auto_lock_timeout", auto_lock_timeout_); + loadScalar(j, "unlock_duration", unlock_duration_); + loadScalar(j, "pin_enabled", pin_enabled_); + loadScalar(j, "keep_daemon_running", keep_daemon_running_); + loadScalar(j, "stop_external_daemon", stop_external_daemon_); + loadScalar(j, "max_connections", max_connections_); if (j.contains("lite_wallet") && j["lite_wallet"].is_object()) { const auto& lite = j["lite_wallet"]; if (lite.contains("server_selection_mode")) { @@ -256,34 +286,36 @@ bool Settings::load(const std::string& path) if (u.is_string()) lite_hidden_servers_.insert(u.get()); } } - if (j.contains("verbose_logging")) verbose_logging_ = j["verbose_logging"].get(); + loadScalar(j, "verbose_logging", verbose_logging_); if (j.contains("debug_categories") && j["debug_categories"].is_array()) { debug_categories_.clear(); for (const auto& c : j["debug_categories"]) if (c.is_string()) debug_categories_.insert(c.get()); } - if (j.contains("theme_effects_enabled")) theme_effects_enabled_ = j["theme_effects_enabled"].get(); - if (j.contains("low_spec_mode")) low_spec_mode_ = j["low_spec_mode"].get(); - if (j.contains("reduce_motion")) reduce_motion_ = j["reduce_motion"].get(); - if (j.contains("selected_exchange")) selected_exchange_ = j["selected_exchange"].get(); - if (j.contains("selected_pair")) selected_pair_ = j["selected_pair"].get(); - if (j.contains("pool_url")) pool_url_ = j["pool_url"].get(); + loadScalar(j, "theme_effects_enabled", theme_effects_enabled_); + loadScalar(j, "low_spec_mode", low_spec_mode_); + loadScalar(j, "reduce_motion", reduce_motion_); + loadScalar(j, "selected_exchange", selected_exchange_); + loadScalar(j, "selected_pair", selected_pair_); + loadScalar(j, "pool_url", pool_url_); // Migrate old default pool URL that was missing the stratum port if (pool_url_ == "pool.dragonx.is") pool_url_ = "pool.dragonx.is:3433"; - if (j.contains("pool_algo")) pool_algo_ = j["pool_algo"].get(); - if (j.contains("pool_worker")) pool_worker_ = j["pool_worker"].get(); - if (j.contains("pool_threads")) pool_threads_ = j["pool_threads"].get(); - if (j.contains("pool_tls")) pool_tls_ = j["pool_tls"].get(); - if (j.contains("pool_hugepages")) pool_hugepages_ = j["pool_hugepages"].get(); - if (j.contains("pool_mode")) pool_mode_ = j["pool_mode"].get(); + loadScalar(j, "pool_algo", pool_algo_); + loadScalar(j, "pool_worker", pool_worker_); + loadScalar(j, "pool_threads", pool_threads_); + loadScalar(j, "pool_tls", pool_tls_); + loadScalar(j, "pool_hugepages", pool_hugepages_); + loadScalar(j, "pool_mode", pool_mode_); if (j.contains("pool_select_mode")) pool_select_mode_ = parsePoolSelectMode(j["pool_select_mode"]); - if (j.contains("mine_when_idle")) mine_when_idle_ = j["mine_when_idle"].get(); - if (j.contains("xmrig_version")) xmrig_version_ = j["xmrig_version"].get(); - if (j.contains("mine_idle_delay")) mine_idle_delay_= std::max(30, j["mine_idle_delay"].get()); - if (j.contains("idle_thread_scaling")) idle_thread_scaling_ = j["idle_thread_scaling"].get(); - if (j.contains("idle_threads_active")) idle_threads_active_ = j["idle_threads_active"].get(); - if (j.contains("idle_threads_idle")) idle_threads_idle_ = j["idle_threads_idle"].get(); - if (j.contains("idle_gpu_aware")) idle_gpu_aware_ = j["idle_gpu_aware"].get(); + loadScalar(j, "mine_when_idle", mine_when_idle_); + loadScalar(j, "xmrig_version", xmrig_version_); + // Lower-bounded only (min 30s), matching setMineIdleDelay; now type-guarded too. + if (j.contains("mine_idle_delay") && j["mine_idle_delay"].is_number_integer()) + mine_idle_delay_ = std::max(30, j["mine_idle_delay"].get()); + loadScalar(j, "idle_thread_scaling", idle_thread_scaling_); + loadScalar(j, "idle_threads_active", idle_threads_active_); + loadScalar(j, "idle_threads_idle", idle_threads_idle_); + loadScalar(j, "idle_gpu_aware", idle_gpu_aware_); if (j.contains("saved_pool_urls") && j["saved_pool_urls"].is_array()) { saved_pool_urls_.clear(); for (const auto& u : j["saved_pool_urls"]) @@ -307,15 +339,12 @@ bool Settings::load(const std::string& path) if (!entry.label.empty()) portfolio_entries_.push_back(std::move(entry)); } } - if (j.contains("font_scale") && j["font_scale"].is_number()) - font_scale_ = std::max(1.0f, std::min(1.5f, j["font_scale"].get())); - if (j.contains("window_width") && j["window_width"].is_number_integer()) - window_width_ = j["window_width"].get(); - if (j.contains("window_height") && j["window_height"].is_number_integer()) - window_height_ = j["window_height"].get(); + loadClamped(j, "font_scale", font_scale_, 1.0f, 1.5f); + loadScalar(j, "window_width", window_width_); + loadScalar(j, "window_height", window_height_); // Version tracking — detect upgrades so we can re-save with new defaults - if (j.contains("settings_version")) settings_version_ = j["settings_version"].get(); + loadScalar(j, "settings_version", settings_version_); if (settings_version_ != DRAGONX_VERSION) { DEBUG_LOGF("Settings version %s differs from wallet %s — will re-save\n", settings_version_.empty() ? "(none)" : settings_version_.c_str(), diff --git a/src/rpc/rpc_client.cpp b/src/rpc/rpc_client.cpp index c2fc683..0aa931a 100644 --- a/src/rpc/rpc_client.cpp +++ b/src/rpc/rpc_client.cpp @@ -290,13 +290,8 @@ json RPCClient::makePayload(const std::string& method, const json& params) }; } -json RPCClient::call(const std::string& method, const json& params) +std::string RPCClient::performCall(const std::string& method, const json& params, long& httpCodeOut) { - std::lock_guard lk(curl_mutex_); - if (!impl_->curl) { - throw std::runtime_error("Not connected"); - } - emitRpcTrace(method); json payload = makePayload(method, params); @@ -310,22 +305,27 @@ json RPCClient::call(const std::string& method, const json& params) // Perform request CURLcode res = curl_easy_perform(impl_->curl); - + if (res != CURLE_OK) { throw std::runtime_error("RPC request failed: " + std::string(curl_easy_strerror(res))); } // Check HTTP response code - long http_code = 0; - curl_easy_getinfo(impl_->curl, CURLINFO_RESPONSE_CODE, &http_code); - + httpCodeOut = 0; + curl_easy_getinfo(impl_->curl, CURLINFO_RESPONSE_CODE, &httpCodeOut); + + return response_data; +} + +json RPCClient::parseRpcResult(long httpCode, const std::string& body) +{ // Bitcoin/Hush RPC returns HTTP 500 for application-level errors // (insufficient funds, bad params, etc.) with a valid JSON body. // Parse the body first to extract the real error message. - if (http_code != 200) { + if (httpCode != 200) { int errCode = 0; try { - json response = json::parse(response_data); + json response = json::parse(body); if (response.contains("error") && response["error"].is_object()) { if (response["error"].contains("code") && response["error"]["code"].is_number_integer()) errCode = response["error"]["code"].get(); @@ -337,10 +337,10 @@ json RPCClient::call(const std::string& method, const json& params) } catch (const json::exception&) { // Body wasn't valid JSON — fall through to generic HTTP error } - throw RpcError(errCode, "RPC error: HTTP " + std::to_string(http_code)); + throw RpcError(errCode, "RPC error: HTTP " + std::to_string(httpCode)); } - json response = json::parse(response_data); + json response = json::parse(body); if (response.contains("error") && !response["error"].is_null()) { int errCode = 0; @@ -358,6 +358,18 @@ json RPCClient::call(const std::string& method, const json& params) return response["result"]; } +json RPCClient::call(const std::string& method, const json& params) +{ + std::lock_guard lk(curl_mutex_); + if (!impl_->curl) { + throw std::runtime_error("Not connected"); + } + + long http_code = 0; + std::string response_data = performCall(method, params, http_code); + return parseRpcResult(http_code, response_data); +} + json RPCClient::call(const std::string& method, const json& params, long timeoutSec) { std::lock_guard lk(curl_mutex_); @@ -365,65 +377,16 @@ json RPCClient::call(const std::string& method, const json& params, long timeout throw std::runtime_error("Not connected"); } - emitRpcTrace(method); - // Temporarily override timeout long prevTimeout = 30L; curl_easy_setopt(impl_->curl, CURLOPT_TIMEOUT, timeoutSec); try { - // Unlock before calling to avoid recursive lock issues — but we already hold it, - // and call() also locks with recursive_mutex, so just delegate to the body directly. - json payload = makePayload(method, params); - std::string body = payload.dump(); - std::string response_data; - - curl_easy_setopt(impl_->curl, CURLOPT_POSTFIELDS, body.c_str()); - curl_easy_setopt(impl_->curl, CURLOPT_POSTFIELDSIZE, (long)body.size()); - curl_easy_setopt(impl_->curl, CURLOPT_WRITEDATA, &response_data); - - CURLcode res = curl_easy_perform(impl_->curl); - + long http_code = 0; + std::string response_data = performCall(method, params, http_code); // Restore original timeout curl_easy_setopt(impl_->curl, CURLOPT_TIMEOUT, prevTimeout); - - if (res != CURLE_OK) { - throw std::runtime_error("RPC request failed: " + std::string(curl_easy_strerror(res))); - } - - long http_code = 0; - curl_easy_getinfo(impl_->curl, CURLINFO_RESPONSE_CODE, &http_code); - - if (http_code != 200) { - int errCode = 0; - try { - json response = json::parse(response_data); - if (response.contains("error") && response["error"].is_object()) { - if (response["error"].contains("code") && response["error"]["code"].is_number_integer()) - errCode = response["error"]["code"].get(); - if (response["error"].contains("message") && response["error"]["message"].is_string()) - throw RpcError(errCode, response["error"]["message"].get()); - throw RpcError(errCode, "RPC error: " + response["error"].dump()); - } - } catch (const json::exception&) {} - throw RpcError(errCode, "RPC error: HTTP " + std::to_string(http_code)); - } - - json response = json::parse(response_data); - if (response.contains("error") && !response["error"].is_null()) { - int errCode = 0; - std::string err_msg; - if (response["error"].is_object()) { - if (response["error"].contains("code") && response["error"]["code"].is_number_integer()) - errCode = response["error"]["code"].get(); - if (response["error"].contains("message") && response["error"]["message"].is_string()) - err_msg = response["error"]["message"].get(); - } - if (err_msg.empty()) err_msg = response["error"].dump(); - throw RpcError(errCode, "RPC error: " + err_msg); - } - - return response["result"]; + return parseRpcResult(http_code, response_data); } catch (...) { // Ensure timeout is always restored curl_easy_setopt(impl_->curl, CURLOPT_TIMEOUT, prevTimeout); @@ -762,43 +725,40 @@ void RPCClient::z_listReceivedByAddress(const std::string& address, int minconf, doRPC("z_listreceivedbyaddress", {address, minconf}, cb, err); } -// Unified callback versions -void RPCClient::getInfo(UnifiedCallback cb) +// Adapts a UnifiedCallback into the (Callback, ErrorCallback) pair doRPC expects: the +// success lambda forwards (result, ""), the error lambda forwards ({}, error). The cb +// null-check is preserved on each invocation. +std::pair RPCClient::splitUnified(UnifiedCallback cb) { - doRPC("getinfo", {}, + return { [cb](const json& result) { if (cb) cb(result, ""); }, [cb](const std::string& error) { if (cb) cb(json{}, error); } - ); + }; +} + +// Unified callback versions +void RPCClient::getInfo(UnifiedCallback cb) +{ + auto handlers = splitUnified(std::move(cb)); + doRPC("getinfo", {}, handlers.first, handlers.second); } void RPCClient::rescanBlockchain(int startHeight, UnifiedCallback cb) { // hush/komodo daemons expose this as "rescan ", not bitcoin's "rescanblockchain". - doRPC("rescan", {startHeight}, - [cb](const json& result) { - if (cb) cb(result, ""); - }, - [cb](const std::string& error) { - if (cb) cb(json{}, error); - } - ); + auto handlers = splitUnified(std::move(cb)); + doRPC("rescan", {startHeight}, handlers.first, handlers.second); } void RPCClient::z_shieldCoinbase(const std::string& fromAddr, const std::string& toAddr, double fee, int limit, UnifiedCallback cb) { - doRPC("z_shieldcoinbase", {fromAddr, toAddr, fee, limit}, - [cb](const json& result) { - if (cb) cb(result, ""); - }, - [cb](const std::string& error) { - if (cb) cb(json{}, error); - } - ); + auto handlers = splitUnified(std::move(cb)); + doRPC("z_shieldcoinbase", {fromAddr, toAddr, fee, limit}, handlers.first, handlers.second); } void RPCClient::z_mergeToAddress(const std::vector& fromAddrs, const std::string& toAddr, @@ -808,14 +768,8 @@ void RPCClient::z_mergeToAddress(const std::vector& fromAddrs, cons for (const auto& addr : fromAddrs) { addrs.push_back(addr); } - doRPC("z_mergetoaddress", {addrs, toAddr, fee, 0, limit}, - [cb](const json& result) { - if (cb) cb(result, ""); - }, - [cb](const std::string& error) { - if (cb) cb(json{}, error); - } - ); + auto handlers = splitUnified(std::move(cb)); + doRPC("z_mergetoaddress", {addrs, toAddr, fee, 0, limit}, handlers.first, handlers.second); } void RPCClient::z_getOperationStatus(const std::vector& opids, UnifiedCallback cb) @@ -824,30 +778,18 @@ void RPCClient::z_getOperationStatus(const std::vector& opids, Unif for (const auto& id : opids) { ids.push_back(id); } - doRPC("z_getoperationstatus", {ids}, - [cb](const json& result) { - if (cb) cb(result, ""); - }, - [cb](const std::string& error) { - if (cb) cb(json{}, error); - } - ); + auto handlers = splitUnified(std::move(cb)); + doRPC("z_getoperationstatus", {ids}, handlers.first, handlers.second); } void RPCClient::getBlock(int height, UnifiedCallback cb) { // First get block hash, then get block - getBlockHash(height, + getBlockHash(height, [this, cb](const json& hashResult) { std::string hash = hashResult.get(); - getBlock(hash, - [cb](const json& blockResult) { - if (cb) cb(blockResult, ""); - }, - [cb](const std::string& error) { - if (cb) cb(json{}, error); - } - ); + auto handlers = splitUnified(cb); + getBlock(hash, handlers.first, handlers.second); }, [cb](const std::string& error) { if (cb) cb(json{}, error); diff --git a/src/rpc/rpc_client.h b/src/rpc/rpc_client.h index 9bd2e84..e5d588d 100644 --- a/src/rpc/rpc_client.h +++ b/src/rpc/rpc_client.h @@ -245,6 +245,17 @@ private: json makePayload(const std::string& method, const json& params = json::array()); void doRPC(const std::string& method, const json& params, Callback cb, ErrorCallback err); + // Shared blocking-call core: dumps the payload, performs the request (throwing on a + // curl transport error) and returns the response body plus HTTP status. Caller must + // hold curl_mutex_ and have verified impl_->curl. + std::string performCall(const std::string& method, const json& params, long& httpCodeOut); + // Centralizes the HTTP-code check and JSON error->RpcError extraction, returning + // response["result"] on success. + static json parseRpcResult(long httpCode, const std::string& body); + + // Splits a UnifiedCallback into the (Callback, ErrorCallback) pair used by doRPC. + static std::pair splitUnified(UnifiedCallback cb); + std::string host_; std::string port_; std::string auth_; // Base64 encoded "user:password" diff --git a/src/ui/effects/theme_effects.cpp b/src/ui/effects/theme_effects.cpp index 53051a8..999fcdb 100644 --- a/src/ui/effects/theme_effects.cpp +++ b/src/ui/effects/theme_effects.cpp @@ -15,6 +15,23 @@ namespace dragonx { namespace ui { namespace effects { +namespace { + +// Colors are packed little-endian RGBA (R at bit 0, G at 8, B at 16, A at 24) — the +// same layout IM_COL32 produces. unpackRGB pulls out the three color channels (alpha is +// handled separately at each site). +struct RGB { int r, g, b; }; +static inline RGB unpackRGB(ImU32 c) { + return RGB{ (int)(c & 0xFF), (int)((c >> 8) & 0xFF), (int)((c >> 16) & 0xFF) }; +} + +// Scales a 0..1 base alpha by the background opacity into a clamped 0..255 int. +static inline int scaledAlpha(float base, float bgOpacity) { + return std::clamp((int)(base * bgOpacity * 255.0f), 0, 255); +} + +} // namespace + // ============================================================================ // Singleton // ============================================================================ @@ -327,16 +344,13 @@ void ThemeEffects::drawShimmer(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax, if (clLeft >= clRight) return; float mid = (clLeft + clRight) * 0.5f; - int peakA = (int)(shimmer_.alpha * bgOpacity_ * 255.0f); - peakA = std::clamp(peakA, 0, 255); + int peakA = scaledAlpha(shimmer_.alpha, bgOpacity_); // Extract shimmer color RGB (ignore alpha, use shimmer_.alpha) - int sR = shimmer_.color & 0xFF; - int sG = (shimmer_.color >> 8) & 0xFF; - int sB = (shimmer_.color >> 16) & 0xFF; + RGB s = unpackRGB(shimmer_.color); - ImU32 clear = IM_COL32(sR, sG, sB, 0); - ImU32 peak = IM_COL32(sR, sG, sB, peakA); + ImU32 clear = IM_COL32(s.r, s.g, s.b, 0); + ImU32 peak = IM_COL32(s.r, s.g, s.b, peakA); // Inset clip rect by corner rounding to prevent shimmer bleeding into rounded corners float cr = std::min(rounding, std::min(w, h) * 0.5f) * 0.3f; @@ -374,9 +388,7 @@ void ThemeEffects::drawSpecularGlare(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax, float minDim = std::min(w, h); float glareR = minDim * specular_glare_.radius; - int cR = specular_glare_.color & 0xFF; - int cG = (specular_glare_.color >> 8) & 0xFF; - int cB = (specular_glare_.color >> 16) & 0xFF; + RGB c = unpackRGB(specular_glare_.color); // Per-panel seed from position — unique drift per panel float seed = pMin.x * 0.0073f + pMin.y * 0.0137f; @@ -410,7 +422,7 @@ void ThemeEffects::drawSpecularGlare(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax, if (a <= 0) continue; dl->AddCircleFilled(ImVec2(cx, cy), ringRadius, - IM_COL32(cR, cG, cB, a), 32); + IM_COL32(c.r, c.g, c.b, a), 32); } } @@ -445,18 +457,14 @@ ImU32 ThemeEffects::tintByPosition(ImU32 baseColor, float screenY) const { float s = positional_hue_.strength; float inv = 1.0f - s; - int bR = baseColor & 0xFF; - int bG = (baseColor >> 8) & 0xFF; - int bB = (baseColor >> 16) & 0xFF; + RGB base = unpackRGB(baseColor); int bA = (baseColor >> 24) & 0xFF; - int tR = tint & 0xFF; - int tG = (tint >> 8) & 0xFF; - int tB = (tint >> 16) & 0xFF; + RGB t = unpackRGB(tint); - int r = (int)(bR * inv + tR * s); - int g = (int)(bG * inv + tG * s); - int b = (int)(bB * inv + tB * s); + int r = (int)(base.r * inv + t.r * s); + int g = (int)(base.g * inv + t.g * s); + int b = (int)(base.b * inv + t.b * s); return IM_COL32(r, g, b, bA); } @@ -474,10 +482,8 @@ void ThemeEffects::drawGlowPulse(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax, float alpha = glow_pulse_.minAlpha + phase * (glow_pulse_.maxAlpha - glow_pulse_.minAlpha); if (alpha <= 0.001f) return; - int a = std::clamp((int)(alpha * bgOpacity_ * 255.0f), 0, 255); - int cR = glow_pulse_.color & 0xFF; - int cG = (glow_pulse_.color >> 8) & 0xFF; - int cB = (glow_pulse_.color >> 16) & 0xFF; + int a = scaledAlpha(alpha, bgOpacity_); + RGB c = unpackRGB(glow_pulse_.color); // Border-only glow: concentric outlines with smooth Gaussian falloff. // Use many fine sub-rings (4× the pixel radius) to avoid visible @@ -496,7 +502,7 @@ void ThemeEffects::drawGlowPulse(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax, dl->AddRect( ImVec2(pMin.x - expand, pMin.y - expand), ImVec2(pMax.x + expand, pMax.y + expand), - IM_COL32(cR, cG, cB, ringAlpha), + IM_COL32(c.r, c.g, c.b, ringAlpha), rounding + expand * 0.5f, 0, thickness); } } @@ -513,17 +519,13 @@ void ThemeEffects::drawGradientBorderShift(ImDrawList* dl, ImVec2 pMin, ImVec2 p float phase = std::sin(time_ * gradient_border_.speed * 2.0f * 3.14159265f) * 0.5f + 0.5f; // Extract RGBA from both colors and lerp - int aR = gradient_border_.colorA & 0xFF; - int aG = (gradient_border_.colorA >> 8) & 0xFF; - int aB = (gradient_border_.colorA >> 16) & 0xFF; - int bR = gradient_border_.colorB & 0xFF; - int bG = (gradient_border_.colorB >> 8) & 0xFF; - int bB = (gradient_border_.colorB >> 16) & 0xFF; + RGB ca = unpackRGB(gradient_border_.colorA); + RGB cb = unpackRGB(gradient_border_.colorB); - int r = aR + (int)((bR - aR) * phase); - int g = aG + (int)((bG - aG) * phase); - int b = aB + (int)((bB - aB) * phase); - int a = std::clamp((int)(gradient_border_.alpha * bgOpacity_ * 255.0f), 0, 255); + int r = ca.r + (int)((cb.r - ca.r) * phase); + int g = ca.g + (int)((cb.g - ca.g) * phase); + int b = ca.b + (int)((cb.b - ca.b) * phase); + int a = scaledAlpha(gradient_border_.alpha, bgOpacity_); // Draw the shifting border dl->AddRect(pMin, pMax, IM_COL32(r, g, b, a), @@ -640,9 +642,7 @@ void ThemeEffects::drawEdgeTrace(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax, float headPos = std::fmod(time_ * edge_trace_.speed, 1.0f); float traceLen = edge_trace_.length; - int cR = edge_trace_.color & 0xFF; - int cG = (edge_trace_.color >> 8) & 0xFF; - int cB = (edge_trace_.color >> 16) & 0xFF; + RGB c = unpackRGB(edge_trace_.color); // Draw the trace as connected line segments with fading alpha. // Use enough segments to make curved corners smooth. @@ -667,16 +667,16 @@ void ThemeEffects::drawEdgeTrace(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax, aFrac = aFrac * aFrac; // quadratic falloff for natural fade int a = (int)(edge_trace_.alpha * aFrac * bgOpacity_ * 255.0f); if (a > 0) { - dl->AddLine(prev, pt, IM_COL32(cR, cG, cB, a), edge_trace_.thickness); + dl->AddLine(prev, pt, IM_COL32(c.r, c.g, c.b, a), edge_trace_.thickness); } prev = pt; } // Bright dot at the head - int headA = (int)(edge_trace_.alpha * bgOpacity_ * 255.0f); + int headA = scaledAlpha(edge_trace_.alpha, bgOpacity_); ImVec2 headPt = perimeterPoint(pMin, pMax, headPos, rounding); dl->AddCircleFilled(headPt, edge_trace_.thickness * 1.5f, - IM_COL32(cR, cG, cB, headA), 8); + IM_COL32(c.r, c.g, c.b, headA), 8); } // ============================================================================ @@ -690,9 +690,7 @@ void ThemeEffects::drawEmberRise(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax) const float h = pMax.y - pMin.y; if (w <= 0 || h <= 0) return; - int cR = ember_rise_.color & 0xFF; - int cG = (ember_rise_.color >> 8) & 0xFF; - int cB = (ember_rise_.color >> 16) & 0xFF; + RGB c = unpackRGB(ember_rise_.color); for (int i = 0; i < ember_rise_.count; i++) { // Golden-ratio spacing gives evenly distributed phases @@ -723,11 +721,11 @@ void ThemeEffects::drawEmberRise(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax) const // Warm core dl->AddCircleFilled(ImVec2(x, y), size, - IM_COL32(cR, cG, cB, a), 6); + IM_COL32(c.r, c.g, c.b, a), 6); // Softer outer glow if (a > 30) { dl->AddCircleFilled(ImVec2(x, y), size * 2.0f, - IM_COL32(cR, cG, cB, a / 4), 6); + IM_COL32(c.r, c.g, c.b, a / 4), 6); } } } @@ -746,9 +744,7 @@ void ThemeEffects::drawViewportEmbers(ImDrawList* dl) const { float vpY = vp->WorkPos.y; if (vpW <= 0 || vpH <= 0) return; - int cR = ember_rise_.color & 0xFF; - int cG = (ember_rise_.color >> 8) & 0xFF; - int cB = (ember_rise_.color >> 16) & 0xFF; + RGB c = unpackRGB(ember_rise_.color); // Viewport embers: more particles, spread across the whole screen int vpCount = ember_rise_.count * 3; @@ -777,10 +773,10 @@ void ThemeEffects::drawViewportEmbers(ImDrawList* dl) const { if (a <= 0) continue; dl->AddCircleFilled(ImVec2(x, y), size, - IM_COL32(cR, cG, cB, a), 6); + IM_COL32(c.r, c.g, c.b, a), 6); if (a > 20) { dl->AddCircleFilled(ImVec2(x, y), size * 2.5f, - IM_COL32(cR, cG, cB, a / 5), 6); + IM_COL32(c.r, c.g, c.b, a / 5), 6); } } } @@ -799,9 +795,7 @@ void ThemeEffects::drawSandstorm(ImDrawList* dl) const { float vpY = vp->WorkPos.y; if (vpW <= 0 || vpH <= 0) return; - int cR = sandstorm_.color & 0xFF; - int cG = (sandstorm_.color >> 8) & 0xFF; - int cB = (sandstorm_.color >> 16) & 0xFF; + RGB c = unpackRGB(sandstorm_.color); // Wind direction vector from angle (degrees from horizontal) float windRad = sandstorm_.windAngle * 3.14159265f / 180.0f; @@ -875,21 +869,21 @@ void ThemeEffects::drawSandstorm(ImDrawList* dl) const { dl->AddLine( ImVec2(x, y), ImVec2(x + sx, y + sy), - IM_COL32(cR, cG, cB, a), + IM_COL32(c.r, c.g, c.b, a), size * 0.7f); // Bright head dot dl->AddCircleFilled(ImVec2(x, y), size * 0.5f, - IM_COL32(cR, cG, cB, std::min(255, a * 3 / 2)), 5); + IM_COL32(c.r, c.g, c.b, std::min(255, a * 3 / 2)), 5); } else { // Small/slow particles: simple circle dl->AddCircleFilled(ImVec2(x, y), size, - IM_COL32(cR, cG, cB, a), 6); + IM_COL32(c.r, c.g, c.b, a), 6); } // Occasional larger dust puff (every ~8th particle, double size, half alpha) if (i % 8 == 0 && a > 15) { dl->AddCircleFilled(ImVec2(x, y), size * 2.5f, - IM_COL32(cR, cG, cB, a / 4), 8); + IM_COL32(c.r, c.g, c.b, a / 4), 8); } } } @@ -916,9 +910,7 @@ void ThemeEffects::drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax, float headPos = std::fmod(time_ * edge_trace_.speed + posKey, 1.0f); float traceLen = edge_trace_.length; - int cR = edge_trace_.color & 0xFF; - int cG = (edge_trace_.color >> 8) & 0xFF; - int cB = (edge_trace_.color >> 16) & 0xFF; + RGB c = unpackRGB(edge_trace_.color); // Panel edge trace is subtler than sidebar — 60% alpha float panelAlpha = edge_trace_.alpha * 0.6f * bgOpacity_; @@ -935,7 +927,7 @@ void ThemeEffects::drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax, aFrac = aFrac * aFrac; int a = (int)(panelAlpha * aFrac * 255.0f); if (a > 0) { - dl->AddLine(prev, pt, IM_COL32(cR, cG, cB, a), + dl->AddLine(prev, pt, IM_COL32(c.r, c.g, c.b, a), edge_trace_.thickness * 0.8f); } prev = pt; @@ -945,7 +937,7 @@ void ThemeEffects::drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax, int headA = (int)(panelAlpha * 255.0f); ImVec2 headPt = perimeterPoint(pMin, pMax, headPos, rounding); dl->AddCircleFilled(headPt, edge_trace_.thickness * 1.2f, - IM_COL32(cR, cG, cB, headA), 6); + IM_COL32(c.r, c.g, c.b, headA), 6); } } @@ -958,9 +950,7 @@ void ThemeEffects::drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax, int panelCount = std::max(2, ember_rise_.count / 3); float panelAlpha = ember_rise_.alpha * 0.5f * bgOpacity_; - int cR = ember_rise_.color & 0xFF; - int cG = (ember_rise_.color >> 8) & 0xFF; - int cB = (ember_rise_.color >> 16) & 0xFF; + RGB c = unpackRGB(ember_rise_.color); // Use panel position as seed for unique particle distribution float seed = pMin.x * 0.013f + pMin.y * 0.031f; @@ -987,7 +977,7 @@ void ThemeEffects::drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax, if (a <= 0) continue; dl->AddCircleFilled(ImVec2(x, y), size, - IM_COL32(cR, cG, cB, a), 6); + IM_COL32(c.r, c.g, c.b, a), 6); } dl->PopClipRect(); @@ -1074,14 +1064,12 @@ void ThemeEffects::drawViewportOverlay(ImDrawList* dl) const { // === Vignette: edge darkening/tinting (4 gradient strips) === // Overlapping strips naturally darken corners more than edges — cinematic look if (viewport_overlay_.vignetteEnabled) { - int cR = viewport_overlay_.vignetteColor & 0xFF; - int cG = (viewport_overlay_.vignetteColor >> 8) & 0xFF; - int cB = (viewport_overlay_.vignetteColor >> 16) & 0xFF; - int maxA = std::clamp((int)(viewport_overlay_.vignetteAlpha * bgOpacity_ * 255.0f), 0, 255); + RGB c = unpackRGB(viewport_overlay_.vignetteColor); + int maxA = scaledAlpha(viewport_overlay_.vignetteAlpha, bgOpacity_); if (maxA > 0) { - ImU32 edgeCol = IM_COL32(cR, cG, cB, maxA); - ImU32 clearCol = IM_COL32(cR, cG, cB, 0); + ImU32 edgeCol = IM_COL32(c.r, c.g, c.b, maxA); + ImU32 clearCol = IM_COL32(c.r, c.g, c.b, 0); float fadeW = vpSize.x * viewport_overlay_.vignetteRadius; float fadeH = vpSize.y * viewport_overlay_.vignetteRadius; diff --git a/src/wallet/lite_connection_service.cpp b/src/wallet/lite_connection_service.cpp index 67b3ae4..6133122 100644 --- a/src/wallet/lite_connection_service.cpp +++ b/src/wallet/lite_connection_service.cpp @@ -13,17 +13,6 @@ namespace wallet { namespace { -std::string trimCopy(const std::string& value) -{ - auto begin = value.begin(); - while (begin != value.end() && std::isspace(static_cast(*begin))) ++begin; - - auto end = value.end(); - while (end != begin && std::isspace(static_cast(*(end - 1)))) --end; - - return std::string(begin, end); -} - bool startsWith(const std::string& value, const char* prefix) { const std::string prefixValue(prefix); @@ -34,7 +23,7 @@ bool startsWith(const std::string& value, const char* prefix) LiteServerEndpoint trimmedEndpoint(const LiteServerEndpoint& endpoint) { LiteServerEndpoint normalized = endpoint; - normalized.url = trimCopy(normalized.url); + normalized.url = liteTrimCopy(normalized.url); return normalized; } @@ -52,12 +41,23 @@ std::vector> usableConfiguredServers( std::string normalizedChainName(const std::string& chainName) { - const std::string normalized = trimCopy(chainName); + const std::string normalized = liteTrimCopy(chainName); return normalized.empty() ? kDragonXLiteChainName : normalized; } } // namespace +std::string liteTrimCopy(const std::string& value) +{ + auto begin = value.begin(); + while (begin != value.end() && std::isspace(static_cast(*begin))) ++begin; + + auto end = value.end(); + while (end != begin && std::isspace(static_cast(*(end - 1)))) --end; + + return std::string(begin, end); +} + std::vector defaultDragonXLiteServers() { return { @@ -83,13 +83,13 @@ LiteConnectionSettings defaultLiteConnectionSettings() bool isLiteServerUrlUsable(const std::string& serverUrl) { - const std::string normalized = trimCopy(serverUrl); + const std::string normalized = liteTrimCopy(serverUrl); return startsWith(normalized, "https://") || startsWith(normalized, "http://"); } bool isOfficialLiteServer(const std::string& serverUrl) { - const std::string url = trimCopy(serverUrl); + const std::string url = liteTrimCopy(serverUrl); for (const auto& s : defaultDragonXLiteServers()) if (s.url == url) return true; return false; @@ -126,7 +126,7 @@ const char* liteConnectionAvailabilityName(LiteConnectionAvailability availabili LiteServerSelectionResult selectLiteServer(const LiteConnectionSettings& settings) { auto usableServers = usableConfiguredServers(settings); - const std::string stickyServerUrl = trimCopy(settings.stickyServerUrl); + const std::string stickyServerUrl = liteTrimCopy(settings.stickyServerUrl); if (settings.selectionMode == LiteServerSelectionMode::Sticky && isLiteServerUrlUsable(stickyServerUrl)) { diff --git a/src/wallet/lite_connection_service.h b/src/wallet/lite_connection_service.h index ecabded..bbbc1cc 100644 --- a/src/wallet/lite_connection_service.h +++ b/src/wallet/lite_connection_service.h @@ -84,6 +84,8 @@ struct LiteServerHealthCheckResult { std::string error; }; +// Trims leading/trailing whitespace, returning a copy. Shared across the lite slice. +std::string liteTrimCopy(const std::string& value); std::vector defaultDragonXLiteServers(); LiteConnectionSettings defaultLiteConnectionSettings(); bool isLiteServerUrlUsable(const std::string& serverUrl); diff --git a/src/wallet/lite_sync_service.cpp b/src/wallet/lite_sync_service.cpp index 2a78a5d..97bb21d 100644 --- a/src/wallet/lite_sync_service.cpp +++ b/src/wallet/lite_sync_service.cpp @@ -1,24 +1,12 @@ #include "wallet/lite_sync_service.h" #include -#include #include #include namespace dragonx::wallet { namespace { -std::string trimSyncCopy(const std::string& value) -{ - auto begin = value.begin(); - while (begin != value.end() && std::isspace(static_cast(*begin))) ++begin; - - auto end = value.end(); - while (end != begin && std::isspace(static_cast(*(end - 1)))) --end; - - return std::string(begin, end); -} - std::uint64_t effectiveStagnantThreshold(std::uint64_t threshold) { return threshold == 0 ? 1 : threshold; @@ -283,7 +271,7 @@ LiteSyncStatusResult LiteSyncService::pollSyncStatus(const LiteSyncStatusRequest LiteServerSelectionResult LiteSyncService::selectServerForRequest(const std::string& serverUrl) const { - const auto overrideUrl = trimSyncCopy(serverUrl); + const auto overrideUrl = liteTrimCopy(serverUrl); if (!overrideUrl.empty()) { if (!isLiteServerUrlUsable(overrideUrl)) { return LiteServerSelectionResult{false, {}, 0, false, "lite sync server URL is not usable"}; diff --git a/src/wallet/lite_wallet_controller.cpp b/src/wallet/lite_wallet_controller.cpp index 23cf564..6d860d3 100644 --- a/src/wallet/lite_wallet_controller.cpp +++ b/src/wallet/lite_wallet_controller.cpp @@ -5,6 +5,7 @@ #include "lite_wallet_controller.h" #include "lite_diagnostics.h" +#include "lite_result_parsers.h" #include "../data/wallet_state.h" #include @@ -695,19 +696,14 @@ std::optional LiteWalletController::refreshModel() if (bridge_) { const auto h = bridge_->execute("height", ""); if (h.ok) { - try { - const auto j = nlohmann::json::parse(h.value); - if (j.is_object() && j.contains("height") && j["height"].is_number_unsigned()) { - const unsigned long long height = j["height"].get(); - if (height > 0) { - model.hasSyncStatus = true; - model.sync.walletHeight = height; - model.sync.chainHeight = height; // synced: wallet height == chain tip - model.sync.complete = true; - model.sync.progress = 1.0; - } - } - } catch (...) { /* leave the mapped sync fields as-is */ } + const auto parsed = parseLiteHeightResponse(h.value); + if (parsed.ok && parsed.height.height > 0) { + model.hasSyncStatus = true; + model.sync.walletHeight = parsed.height.height; + model.sync.chainHeight = parsed.height.height; // synced: wallet height == chain tip + model.sync.complete = true; + model.sync.progress = 1.0; + } } } return model; diff --git a/src/wallet/lite_wallet_gateway.cpp b/src/wallet/lite_wallet_gateway.cpp index 2573205..7b52fd9 100644 --- a/src/wallet/lite_wallet_gateway.cpp +++ b/src/wallet/lite_wallet_gateway.cpp @@ -1,22 +1,10 @@ #include "wallet/lite_wallet_gateway.h" -#include #include namespace dragonx::wallet { namespace { -std::string trimGatewayCopy(const std::string& value) -{ - auto begin = value.begin(); - while (begin != value.end() && std::isspace(static_cast(*begin))) ++begin; - - auto end = value.end(); - while (end != begin && std::isspace(static_cast(*(end - 1)))) --end; - - return std::string(begin, end); -} - WalletBackendStatus makeGatewayStatus(WalletBackendState state, std::string message) { WalletBackendStatus status; @@ -262,7 +250,7 @@ LiteWalletRefreshResult LiteWalletGateway::refresh(const LiteWalletRefreshReques LiteServerSelectionResult LiteWalletGateway::selectServerForRequest(const std::string& serverUrl) const { - const auto overrideUrl = trimGatewayCopy(serverUrl); + const auto overrideUrl = liteTrimCopy(serverUrl); if (!overrideUrl.empty()) { if (!isLiteServerUrlUsable(overrideUrl)) { return LiteServerSelectionResult{false, {}, 0, false, "lite gateway server URL is not usable"}; diff --git a/src/wallet/lite_wallet_lifecycle_service.cpp b/src/wallet/lite_wallet_lifecycle_service.cpp index 434b3ea..dd4882f 100644 --- a/src/wallet/lite_wallet_lifecycle_service.cpp +++ b/src/wallet/lite_wallet_lifecycle_service.cpp @@ -4,7 +4,6 @@ #include "lite_wallet_lifecycle_service.h" -#include #include namespace dragonx { @@ -12,17 +11,6 @@ namespace wallet { namespace { -std::string trimLifecycleCopy(const std::string& value) -{ - auto begin = value.begin(); - while (begin != value.end() && std::isspace(static_cast(*begin))) ++begin; - - auto end = value.end(); - while (end != begin && std::isspace(static_cast(*(end - 1)))) --end; - - return std::string(begin, end); -} - LiteRedactedPrivateData redactedField(LitePrivateDataKind kind, const std::string& value) { return LiteRedactedPrivateData{kind, !value.empty(), redactLitePrivateDataValue(value)}; @@ -172,7 +160,7 @@ LiteWalletLifecyclePlan LiteWalletLifecycleService::planOpenWallet( LiteWalletLifecyclePlan LiteWalletLifecycleService::planRestoreWallet( const LiteWalletRestoreRequest& request) const { - const std::string validationError = trimLifecycleCopy(request.seedPhrase).empty() + const std::string validationError = liteTrimCopy(request.seedPhrase).empty() ? "restore seed phrase is required" : std::string(); return makePlan(LiteWalletLifecycleOperation::RestoreFromSeed, @@ -220,7 +208,7 @@ LiteWalletLifecycleResult LiteWalletLifecycleService::restoreWallet( LiteServerSelectionResult LiteWalletLifecycleService::selectServerForRequest( const std::string& serverUrl) const { - const std::string overrideUrl = trimLifecycleCopy(serverUrl); + const std::string overrideUrl = liteTrimCopy(serverUrl); if (!overrideUrl.empty()) { if (!isLiteServerUrlUsable(overrideUrl)) { return LiteServerSelectionResult{false, {}, 0, false, "lite lifecycle server URL is not usable"}; diff --git a/src/wallet/lite_wallet_server_selection_adapter.cpp b/src/wallet/lite_wallet_server_selection_adapter.cpp index 68f8fd6..3e3a24c 100644 --- a/src/wallet/lite_wallet_server_selection_adapter.cpp +++ b/src/wallet/lite_wallet_server_selection_adapter.cpp @@ -1,25 +1,10 @@ #include "lite_wallet_server_selection_adapter.h" -#include -#include - namespace dragonx { namespace wallet { namespace { -std::string trimCopy(const std::string& value) -{ - const auto first = std::find_if_not(value.begin(), value.end(), [](unsigned char ch) { - return std::isspace(ch) != 0; - }); - const auto last = std::find_if_not(value.rbegin(), value.rend(), [](unsigned char ch) { - return std::isspace(ch) != 0; - }).base(); - if (first >= last) return {}; - return std::string(first, last); -} - LiteServerSelectionMode walletModeFromSettings( config::Settings::LiteServerSelectionPreferenceMode mode) { @@ -52,14 +37,14 @@ LiteConnectionSettings liteConnectionSettingsFromAppSettings(const config::Setti connectionSettings.servers.clear(); for (const auto& server : settings.getLiteServers()) { connectionSettings.servers.push_back(LiteServerEndpoint{ - trimCopy(server.url), + liteTrimCopy(server.url), server.label, server.enabled }); } connectionSettings.selectionMode = walletModeFromSettings(settings.getLiteServerSelectionMode()); - connectionSettings.stickyServerUrl = trimCopy(settings.getLiteStickyServerUrl()); - connectionSettings.chainName = trimCopy(settings.getLiteChainName()); + connectionSettings.stickyServerUrl = liteTrimCopy(settings.getLiteStickyServerUrl()); + connectionSettings.chainName = liteTrimCopy(settings.getLiteChainName()); if (connectionSettings.chainName.empty()) connectionSettings.chainName = kDragonXLiteChainName; connectionSettings.randomSelectionSeed = settings.getLiteRandomSelectionSeed(); return connectionSettings; @@ -72,24 +57,24 @@ void applyLiteConnectionSettingsToAppSettings(config::Settings& settings, servers.reserve(connectionSettings.servers.size()); for (const auto& server : connectionSettings.servers) { servers.push_back(config::Settings::LiteServerPreference{ - trimCopy(server.url), + liteTrimCopy(server.url), server.label, server.enabled }); } settings.setLiteServerSelectionMode(settingsModeFromWallet(connectionSettings.selectionMode)); - settings.setLiteStickyServerUrl(trimCopy(connectionSettings.stickyServerUrl)); - settings.setLiteChainName(trimCopy(connectionSettings.chainName).empty() + settings.setLiteStickyServerUrl(liteTrimCopy(connectionSettings.stickyServerUrl)); + settings.setLiteChainName(liteTrimCopy(connectionSettings.chainName).empty() ? kDragonXLiteChainName - : trimCopy(connectionSettings.chainName)); + : liteTrimCopy(connectionSettings.chainName)); settings.setLiteRandomSelectionSeed(connectionSettings.randomSelectionSeed); settings.setLiteServers(servers); } std::string redactLiteServerSelectionValue(const std::string& value) { - const std::string trimmed = trimCopy(value); + const std::string trimmed = liteTrimCopy(value); if (trimmed.empty()) return ""; const auto scheme = trimmed.find("://"); if (scheme == std::string::npos) return "";