refactor(audit): batch 7 — RPC/settings/lite/effects mechanical dedups

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) <noreply@anthropic.com>
This commit is contained in:
2026-07-02 19:56:54 -05:00
parent 4635a56e89
commit dcd09dc542
11 changed files with 245 additions and 328 deletions

View File

@@ -12,6 +12,8 @@
#include <fstream>
#include <filesystem>
#include <ctime>
#include <algorithm>
#include <type_traits>
#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 <typename T>
bool jsonHasType(const json& v)
{
if constexpr (std::is_same_v<T, bool>) return v.is_boolean();
else if constexpr (std::is_same_v<T, std::string>) return v.is_string();
else if constexpr (std::is_floating_point_v<T>) return v.is_number();
else if constexpr (std::is_integral_v<T>) return v.is_number_integer();
else return false;
}
// Reads j[key] into field only when present AND of the matching type.
template <typename T>
void loadScalar(const json& j, const char* key, T& field)
{
if (j.contains(key) && jsonHasType<T>(j[key])) field = j[key].get<T>();
}
// Same as loadScalar but clamps the read value into [lo, hi].
template <typename T>
void loadClamped(const json& j, const char* key, T& field, T lo, T hi)
{
if (j.contains(key) && jsonHasType<T>(j[key]))
field = std::max(lo, std::min(hi, j[key].get<T>()));
}
} // 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<std::string>();
if (j.contains("save_ztxs")) save_ztxs_ = j["save_ztxs"].get<bool>();
if (j.contains("auto_shield")) auto_shield_ = j["auto_shield"].get<bool>();
if (j.contains("use_tor")) use_tor_ = j["use_tor"].get<bool>();
if (j.contains("allow_custom_fees")) allow_custom_fees_ = j["allow_custom_fees"].get<bool>();
if (j.contains("default_fee")) default_fee_ = j["default_fee"].get<double>();
if (j.contains("fetch_prices")) fetch_prices_ = j["fetch_prices"].get<bool>();
if (j.contains("tx_explorer_url")) tx_explorer_url_ = j["tx_explorer_url"].get<std::string>();
if (j.contains("address_explorer_url")) address_explorer_url_ = j["address_explorer_url"].get<std::string>();
if (j.contains("language")) language_ = j["language"].get<std::string>();
if (j.contains("skin_id")) skin_id_ = j["skin_id"].get<std::string>();
if (j.contains("acrylic_enabled")) acrylic_enabled_ = j["acrylic_enabled"].get<bool>();
if (j.contains("acrylic_quality")) acrylic_quality_ = j["acrylic_quality"].get<int>();
if (j.contains("blur_multiplier")) blur_multiplier_ = j["blur_multiplier"].get<float>();
if (j.contains("noise_opacity")) noise_opacity_ = j["noise_opacity"].get<float>();
if (j.contains("gradient_background")) gradient_background_ = j["gradient_background"].get<bool>();
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<float>();
} else if (j.contains("reduced_transparency") && j["reduced_transparency"].get<bool>()) {
ui_opacity_ = 1.0f; // legacy: reduced = fully opaque
}
if (j.contains("window_opacity")) window_opacity_ = j["window_opacity"].get<float>();
loadScalar(j, "window_opacity", window_opacity_);
if (j.contains("balance_layout")) {
if (j["balance_layout"].is_string())
balance_layout_ = j["balance_layout"].get<std::string>();
@@ -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<bool>();
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<bool>();
if (j.contains("auto_lock_timeout")) auto_lock_timeout_ = j["auto_lock_timeout"].get<int>();
if (j.contains("unlock_duration")) unlock_duration_ = j["unlock_duration"].get<int>();
if (j.contains("pin_enabled")) pin_enabled_ = j["pin_enabled"].get<bool>();
if (j.contains("keep_daemon_running")) keep_daemon_running_ = j["keep_daemon_running"].get<bool>();
if (j.contains("stop_external_daemon")) stop_external_daemon_ = j["stop_external_daemon"].get<bool>();
if (j.contains("max_connections")) max_connections_ = j["max_connections"].get<int>();
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<std::string>());
}
}
if (j.contains("verbose_logging")) verbose_logging_ = j["verbose_logging"].get<bool>();
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<std::string>());
}
if (j.contains("theme_effects_enabled")) theme_effects_enabled_ = j["theme_effects_enabled"].get<bool>();
if (j.contains("low_spec_mode")) low_spec_mode_ = j["low_spec_mode"].get<bool>();
if (j.contains("reduce_motion")) reduce_motion_ = j["reduce_motion"].get<bool>();
if (j.contains("selected_exchange")) selected_exchange_ = j["selected_exchange"].get<std::string>();
if (j.contains("selected_pair")) selected_pair_ = j["selected_pair"].get<std::string>();
if (j.contains("pool_url")) pool_url_ = j["pool_url"].get<std::string>();
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<std::string>();
if (j.contains("pool_worker")) pool_worker_ = j["pool_worker"].get<std::string>();
if (j.contains("pool_threads")) pool_threads_ = j["pool_threads"].get<int>();
if (j.contains("pool_tls")) pool_tls_ = j["pool_tls"].get<bool>();
if (j.contains("pool_hugepages")) pool_hugepages_ = j["pool_hugepages"].get<bool>();
if (j.contains("pool_mode")) pool_mode_ = j["pool_mode"].get<bool>();
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<bool>();
if (j.contains("xmrig_version")) xmrig_version_ = j["xmrig_version"].get<std::string>();
if (j.contains("mine_idle_delay")) mine_idle_delay_= std::max(30, j["mine_idle_delay"].get<int>());
if (j.contains("idle_thread_scaling")) idle_thread_scaling_ = j["idle_thread_scaling"].get<bool>();
if (j.contains("idle_threads_active")) idle_threads_active_ = j["idle_threads_active"].get<int>();
if (j.contains("idle_threads_idle")) idle_threads_idle_ = j["idle_threads_idle"].get<int>();
if (j.contains("idle_gpu_aware")) idle_gpu_aware_ = j["idle_gpu_aware"].get<bool>();
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<int>());
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<float>()));
if (j.contains("window_width") && j["window_width"].is_number_integer())
window_width_ = j["window_width"].get<int>();
if (j.contains("window_height") && j["window_height"].is_number_integer())
window_height_ = j["window_height"].get<int>();
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<std::string>();
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(),

View File

@@ -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<std::recursive_mutex> 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<int>();
@@ -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<std::recursive_mutex> 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<std::recursive_mutex> 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<int>();
if (response["error"].contains("message") && response["error"]["message"].is_string())
throw RpcError(errCode, response["error"]["message"].get<std::string>());
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<int>();
if (response["error"].contains("message") && response["error"]["message"].is_string())
err_msg = response["error"]["message"].get<std::string>();
}
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<Callback, ErrorCallback> 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 <height>", 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<std::string>& fromAddrs, const std::string& toAddr,
@@ -808,14 +768,8 @@ void RPCClient::z_mergeToAddress(const std::vector<std::string>& 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<std::string>& opids, UnifiedCallback cb)
@@ -824,30 +778,18 @@ void RPCClient::z_getOperationStatus(const std::vector<std::string>& 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<std::string>();
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);

View File

@@ -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<Callback, ErrorCallback> splitUnified(UnifiedCallback cb);
std::string host_;
std::string port_;
std::string auth_; // Base64 encoded "user:password"

View File

@@ -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;

View File

@@ -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<unsigned char>(*begin))) ++begin;
auto end = value.end();
while (end != begin && std::isspace(static_cast<unsigned char>(*(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<std::pair<std::size_t, LiteServerEndpoint>> 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<unsigned char>(*begin))) ++begin;
auto end = value.end();
while (end != begin && std::isspace(static_cast<unsigned char>(*(end - 1)))) --end;
return std::string(begin, end);
}
std::vector<LiteServerEndpoint> 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)) {

View File

@@ -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<LiteServerEndpoint> defaultDragonXLiteServers();
LiteConnectionSettings defaultLiteConnectionSettings();
bool isLiteServerUrlUsable(const std::string& serverUrl);

View File

@@ -1,24 +1,12 @@
#include "wallet/lite_sync_service.h"
#include <algorithm>
#include <cctype>
#include <limits>
#include <utility>
namespace dragonx::wallet {
namespace {
std::string trimSyncCopy(const std::string& value)
{
auto begin = value.begin();
while (begin != value.end() && std::isspace(static_cast<unsigned char>(*begin))) ++begin;
auto end = value.end();
while (end != begin && std::isspace(static_cast<unsigned char>(*(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"};

View File

@@ -5,6 +5,7 @@
#include "lite_wallet_controller.h"
#include "lite_diagnostics.h"
#include "lite_result_parsers.h"
#include "../data/wallet_state.h"
#include <algorithm>
@@ -695,19 +696,14 @@ std::optional<LiteWalletAppRefreshModel> 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<unsigned long long>();
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;

View File

@@ -1,22 +1,10 @@
#include "wallet/lite_wallet_gateway.h"
#include <cctype>
#include <utility>
namespace dragonx::wallet {
namespace {
std::string trimGatewayCopy(const std::string& value)
{
auto begin = value.begin();
while (begin != value.end() && std::isspace(static_cast<unsigned char>(*begin))) ++begin;
auto end = value.end();
while (end != begin && std::isspace(static_cast<unsigned char>(*(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"};

View File

@@ -4,7 +4,6 @@
#include "lite_wallet_lifecycle_service.h"
#include <cctype>
#include <utility>
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<unsigned char>(*begin))) ++begin;
auto end = value.end();
while (end != begin && std::isspace(static_cast<unsigned char>(*(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"};

View File

@@ -1,25 +1,10 @@
#include "lite_wallet_server_selection_adapter.h"
#include <algorithm>
#include <cctype>
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 "<empty>";
const auto scheme = trimmed.find("://");
if (scheme == std::string::npos) return "<redacted>";