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:
@@ -12,6 +12,8 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
#include "../util/logger.h"
|
#include "../util/logger.h"
|
||||||
#include "../util/platform.h"
|
#include "../util/platform.h"
|
||||||
@@ -76,6 +78,34 @@ const char* poolSelectModeName(Settings::PoolSelectMode mode)
|
|||||||
return "manual";
|
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
|
} // namespace
|
||||||
|
|
||||||
std::string Settings::getDefaultPath()
|
std::string Settings::getDefaultPath()
|
||||||
@@ -127,29 +157,29 @@ bool Settings::load(const std::string& path)
|
|||||||
json j;
|
json j;
|
||||||
file >> j;
|
file >> j;
|
||||||
|
|
||||||
if (j.contains("theme")) theme_ = j["theme"].get<std::string>();
|
loadScalar(j, "theme", theme_);
|
||||||
if (j.contains("save_ztxs")) save_ztxs_ = j["save_ztxs"].get<bool>();
|
loadScalar(j, "save_ztxs", save_ztxs_);
|
||||||
if (j.contains("auto_shield")) auto_shield_ = j["auto_shield"].get<bool>();
|
loadScalar(j, "auto_shield", auto_shield_);
|
||||||
if (j.contains("use_tor")) use_tor_ = j["use_tor"].get<bool>();
|
loadScalar(j, "use_tor", use_tor_);
|
||||||
if (j.contains("allow_custom_fees")) allow_custom_fees_ = j["allow_custom_fees"].get<bool>();
|
loadScalar(j, "allow_custom_fees", allow_custom_fees_);
|
||||||
if (j.contains("default_fee")) default_fee_ = j["default_fee"].get<double>();
|
loadScalar(j, "default_fee", default_fee_);
|
||||||
if (j.contains("fetch_prices")) fetch_prices_ = j["fetch_prices"].get<bool>();
|
loadScalar(j, "fetch_prices", fetch_prices_);
|
||||||
if (j.contains("tx_explorer_url")) tx_explorer_url_ = j["tx_explorer_url"].get<std::string>();
|
loadScalar(j, "tx_explorer_url", tx_explorer_url_);
|
||||||
if (j.contains("address_explorer_url")) address_explorer_url_ = j["address_explorer_url"].get<std::string>();
|
loadScalar(j, "address_explorer_url", address_explorer_url_);
|
||||||
if (j.contains("language")) language_ = j["language"].get<std::string>();
|
loadScalar(j, "language", language_);
|
||||||
if (j.contains("skin_id")) skin_id_ = j["skin_id"].get<std::string>();
|
loadScalar(j, "skin_id", skin_id_);
|
||||||
if (j.contains("acrylic_enabled")) acrylic_enabled_ = j["acrylic_enabled"].get<bool>();
|
loadScalar(j, "acrylic_enabled", acrylic_enabled_);
|
||||||
if (j.contains("acrylic_quality")) acrylic_quality_ = j["acrylic_quality"].get<int>();
|
loadScalar(j, "acrylic_quality", acrylic_quality_);
|
||||||
if (j.contains("blur_multiplier")) blur_multiplier_ = j["blur_multiplier"].get<float>();
|
loadScalar(j, "blur_multiplier", blur_multiplier_);
|
||||||
if (j.contains("noise_opacity")) noise_opacity_ = j["noise_opacity"].get<float>();
|
loadScalar(j, "noise_opacity", noise_opacity_);
|
||||||
if (j.contains("gradient_background")) gradient_background_ = j["gradient_background"].get<bool>();
|
loadScalar(j, "gradient_background", gradient_background_);
|
||||||
// Migrate legacy reduced_transparency bool -> ui_opacity float
|
// Migrate legacy reduced_transparency bool -> ui_opacity float
|
||||||
if (j.contains("ui_opacity")) {
|
if (j.contains("ui_opacity")) {
|
||||||
ui_opacity_ = j["ui_opacity"].get<float>();
|
ui_opacity_ = j["ui_opacity"].get<float>();
|
||||||
} else if (j.contains("reduced_transparency") && j["reduced_transparency"].get<bool>()) {
|
} else if (j.contains("reduced_transparency") && j["reduced_transparency"].get<bool>()) {
|
||||||
ui_opacity_ = 1.0f; // legacy: reduced = fully opaque
|
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.contains("balance_layout")) {
|
||||||
if (j["balance_layout"].is_string())
|
if (j["balance_layout"].is_string())
|
||||||
balance_layout_ = j["balance_layout"].get<std::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 (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()) {
|
if (j.contains("hidden_addresses") && j["hidden_addresses"].is_array()) {
|
||||||
hidden_addresses_.clear();
|
hidden_addresses_.clear();
|
||||||
for (const auto& a : j["hidden_addresses"])
|
for (const auto& a : j["hidden_addresses"])
|
||||||
@@ -189,13 +219,13 @@ bool Settings::load(const std::string& path)
|
|||||||
address_meta_[addr] = m;
|
address_meta_[addr] = m;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (j.contains("wizard_completed")) wizard_completed_ = j["wizard_completed"].get<bool>();
|
loadScalar(j, "wizard_completed", wizard_completed_);
|
||||||
if (j.contains("auto_lock_timeout")) auto_lock_timeout_ = j["auto_lock_timeout"].get<int>();
|
loadScalar(j, "auto_lock_timeout", auto_lock_timeout_);
|
||||||
if (j.contains("unlock_duration")) unlock_duration_ = j["unlock_duration"].get<int>();
|
loadScalar(j, "unlock_duration", unlock_duration_);
|
||||||
if (j.contains("pin_enabled")) pin_enabled_ = j["pin_enabled"].get<bool>();
|
loadScalar(j, "pin_enabled", pin_enabled_);
|
||||||
if (j.contains("keep_daemon_running")) keep_daemon_running_ = j["keep_daemon_running"].get<bool>();
|
loadScalar(j, "keep_daemon_running", keep_daemon_running_);
|
||||||
if (j.contains("stop_external_daemon")) stop_external_daemon_ = j["stop_external_daemon"].get<bool>();
|
loadScalar(j, "stop_external_daemon", stop_external_daemon_);
|
||||||
if (j.contains("max_connections")) max_connections_ = j["max_connections"].get<int>();
|
loadScalar(j, "max_connections", max_connections_);
|
||||||
if (j.contains("lite_wallet") && j["lite_wallet"].is_object()) {
|
if (j.contains("lite_wallet") && j["lite_wallet"].is_object()) {
|
||||||
const auto& lite = j["lite_wallet"];
|
const auto& lite = j["lite_wallet"];
|
||||||
if (lite.contains("server_selection_mode")) {
|
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 (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()) {
|
if (j.contains("debug_categories") && j["debug_categories"].is_array()) {
|
||||||
debug_categories_.clear();
|
debug_categories_.clear();
|
||||||
for (const auto& c : j["debug_categories"])
|
for (const auto& c : j["debug_categories"])
|
||||||
if (c.is_string()) debug_categories_.insert(c.get<std::string>());
|
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>();
|
loadScalar(j, "theme_effects_enabled", theme_effects_enabled_);
|
||||||
if (j.contains("low_spec_mode")) low_spec_mode_ = j["low_spec_mode"].get<bool>();
|
loadScalar(j, "low_spec_mode", low_spec_mode_);
|
||||||
if (j.contains("reduce_motion")) reduce_motion_ = j["reduce_motion"].get<bool>();
|
loadScalar(j, "reduce_motion", reduce_motion_);
|
||||||
if (j.contains("selected_exchange")) selected_exchange_ = j["selected_exchange"].get<std::string>();
|
loadScalar(j, "selected_exchange", selected_exchange_);
|
||||||
if (j.contains("selected_pair")) selected_pair_ = j["selected_pair"].get<std::string>();
|
loadScalar(j, "selected_pair", selected_pair_);
|
||||||
if (j.contains("pool_url")) pool_url_ = j["pool_url"].get<std::string>();
|
loadScalar(j, "pool_url", pool_url_);
|
||||||
// Migrate old default pool URL that was missing the stratum port
|
// Migrate old default pool URL that was missing the stratum port
|
||||||
if (pool_url_ == "pool.dragonx.is") pool_url_ = "pool.dragonx.is:3433";
|
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>();
|
loadScalar(j, "pool_algo", pool_algo_);
|
||||||
if (j.contains("pool_worker")) pool_worker_ = j["pool_worker"].get<std::string>();
|
loadScalar(j, "pool_worker", pool_worker_);
|
||||||
if (j.contains("pool_threads")) pool_threads_ = j["pool_threads"].get<int>();
|
loadScalar(j, "pool_threads", pool_threads_);
|
||||||
if (j.contains("pool_tls")) pool_tls_ = j["pool_tls"].get<bool>();
|
loadScalar(j, "pool_tls", pool_tls_);
|
||||||
if (j.contains("pool_hugepages")) pool_hugepages_ = j["pool_hugepages"].get<bool>();
|
loadScalar(j, "pool_hugepages", pool_hugepages_);
|
||||||
if (j.contains("pool_mode")) pool_mode_ = j["pool_mode"].get<bool>();
|
loadScalar(j, "pool_mode", pool_mode_);
|
||||||
if (j.contains("pool_select_mode")) pool_select_mode_ = parsePoolSelectMode(j["pool_select_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>();
|
loadScalar(j, "mine_when_idle", mine_when_idle_);
|
||||||
if (j.contains("xmrig_version")) xmrig_version_ = j["xmrig_version"].get<std::string>();
|
loadScalar(j, "xmrig_version", xmrig_version_);
|
||||||
if (j.contains("mine_idle_delay")) mine_idle_delay_= std::max(30, j["mine_idle_delay"].get<int>());
|
// Lower-bounded only (min 30s), matching setMineIdleDelay; now type-guarded too.
|
||||||
if (j.contains("idle_thread_scaling")) idle_thread_scaling_ = j["idle_thread_scaling"].get<bool>();
|
if (j.contains("mine_idle_delay") && j["mine_idle_delay"].is_number_integer())
|
||||||
if (j.contains("idle_threads_active")) idle_threads_active_ = j["idle_threads_active"].get<int>();
|
mine_idle_delay_ = std::max(30, j["mine_idle_delay"].get<int>());
|
||||||
if (j.contains("idle_threads_idle")) idle_threads_idle_ = j["idle_threads_idle"].get<int>();
|
loadScalar(j, "idle_thread_scaling", idle_thread_scaling_);
|
||||||
if (j.contains("idle_gpu_aware")) idle_gpu_aware_ = j["idle_gpu_aware"].get<bool>();
|
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()) {
|
if (j.contains("saved_pool_urls") && j["saved_pool_urls"].is_array()) {
|
||||||
saved_pool_urls_.clear();
|
saved_pool_urls_.clear();
|
||||||
for (const auto& u : j["saved_pool_urls"])
|
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 (!entry.label.empty()) portfolio_entries_.push_back(std::move(entry));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (j.contains("font_scale") && j["font_scale"].is_number())
|
loadClamped(j, "font_scale", font_scale_, 1.0f, 1.5f);
|
||||||
font_scale_ = std::max(1.0f, std::min(1.5f, j["font_scale"].get<float>()));
|
loadScalar(j, "window_width", window_width_);
|
||||||
if (j.contains("window_width") && j["window_width"].is_number_integer())
|
loadScalar(j, "window_height", window_height_);
|
||||||
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>();
|
|
||||||
|
|
||||||
// Version tracking — detect upgrades so we can re-save with new defaults
|
// 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) {
|
if (settings_version_ != DRAGONX_VERSION) {
|
||||||
DEBUG_LOGF("Settings version %s differs from wallet %s — will re-save\n",
|
DEBUG_LOGF("Settings version %s differs from wallet %s — will re-save\n",
|
||||||
settings_version_.empty() ? "(none)" : settings_version_.c_str(),
|
settings_version_.empty() ? "(none)" : settings_version_.c_str(),
|
||||||
|
|||||||
@@ -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);
|
emitRpcTrace(method);
|
||||||
|
|
||||||
json payload = makePayload(method, params);
|
json payload = makePayload(method, params);
|
||||||
@@ -310,22 +305,27 @@ json RPCClient::call(const std::string& method, const json& params)
|
|||||||
|
|
||||||
// Perform request
|
// Perform request
|
||||||
CURLcode res = curl_easy_perform(impl_->curl);
|
CURLcode res = curl_easy_perform(impl_->curl);
|
||||||
|
|
||||||
if (res != CURLE_OK) {
|
if (res != CURLE_OK) {
|
||||||
throw std::runtime_error("RPC request failed: " + std::string(curl_easy_strerror(res)));
|
throw std::runtime_error("RPC request failed: " + std::string(curl_easy_strerror(res)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check HTTP response code
|
// Check HTTP response code
|
||||||
long http_code = 0;
|
httpCodeOut = 0;
|
||||||
curl_easy_getinfo(impl_->curl, CURLINFO_RESPONSE_CODE, &http_code);
|
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
|
// Bitcoin/Hush RPC returns HTTP 500 for application-level errors
|
||||||
// (insufficient funds, bad params, etc.) with a valid JSON body.
|
// (insufficient funds, bad params, etc.) with a valid JSON body.
|
||||||
// Parse the body first to extract the real error message.
|
// Parse the body first to extract the real error message.
|
||||||
if (http_code != 200) {
|
if (httpCode != 200) {
|
||||||
int errCode = 0;
|
int errCode = 0;
|
||||||
try {
|
try {
|
||||||
json response = json::parse(response_data);
|
json response = json::parse(body);
|
||||||
if (response.contains("error") && response["error"].is_object()) {
|
if (response.contains("error") && response["error"].is_object()) {
|
||||||
if (response["error"].contains("code") && response["error"]["code"].is_number_integer())
|
if (response["error"].contains("code") && response["error"]["code"].is_number_integer())
|
||||||
errCode = response["error"]["code"].get<int>();
|
errCode = response["error"]["code"].get<int>();
|
||||||
@@ -337,10 +337,10 @@ json RPCClient::call(const std::string& method, const json& params)
|
|||||||
} catch (const json::exception&) {
|
} catch (const json::exception&) {
|
||||||
// Body wasn't valid JSON — fall through to generic HTTP error
|
// 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()) {
|
if (response.contains("error") && !response["error"].is_null()) {
|
||||||
int errCode = 0;
|
int errCode = 0;
|
||||||
@@ -358,6 +358,18 @@ json RPCClient::call(const std::string& method, const json& params)
|
|||||||
return response["result"];
|
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)
|
json RPCClient::call(const std::string& method, const json& params, long timeoutSec)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
|
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");
|
throw std::runtime_error("Not connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
emitRpcTrace(method);
|
|
||||||
|
|
||||||
// Temporarily override timeout
|
// Temporarily override timeout
|
||||||
long prevTimeout = 30L;
|
long prevTimeout = 30L;
|
||||||
curl_easy_setopt(impl_->curl, CURLOPT_TIMEOUT, timeoutSec);
|
curl_easy_setopt(impl_->curl, CURLOPT_TIMEOUT, timeoutSec);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Unlock before calling to avoid recursive lock issues — but we already hold it,
|
long http_code = 0;
|
||||||
// and call() also locks with recursive_mutex, so just delegate to the body directly.
|
std::string response_data = performCall(method, params, http_code);
|
||||||
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);
|
|
||||||
|
|
||||||
// Restore original timeout
|
// Restore original timeout
|
||||||
curl_easy_setopt(impl_->curl, CURLOPT_TIMEOUT, prevTimeout);
|
curl_easy_setopt(impl_->curl, CURLOPT_TIMEOUT, prevTimeout);
|
||||||
|
return parseRpcResult(http_code, response_data);
|
||||||
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"];
|
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
// Ensure timeout is always restored
|
// Ensure timeout is always restored
|
||||||
curl_easy_setopt(impl_->curl, CURLOPT_TIMEOUT, prevTimeout);
|
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);
|
doRPC("z_listreceivedbyaddress", {address, minconf}, cb, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unified callback versions
|
// Adapts a UnifiedCallback into the (Callback, ErrorCallback) pair doRPC expects: the
|
||||||
void RPCClient::getInfo(UnifiedCallback cb)
|
// 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) {
|
[cb](const json& result) {
|
||||||
if (cb) cb(result, "");
|
if (cb) cb(result, "");
|
||||||
},
|
},
|
||||||
[cb](const std::string& error) {
|
[cb](const std::string& error) {
|
||||||
if (cb) cb(json{}, 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)
|
void RPCClient::rescanBlockchain(int startHeight, UnifiedCallback cb)
|
||||||
{
|
{
|
||||||
// hush/komodo daemons expose this as "rescan <height>", not bitcoin's "rescanblockchain".
|
// hush/komodo daemons expose this as "rescan <height>", not bitcoin's "rescanblockchain".
|
||||||
doRPC("rescan", {startHeight},
|
auto handlers = splitUnified(std::move(cb));
|
||||||
[cb](const json& result) {
|
doRPC("rescan", {startHeight}, handlers.first, handlers.second);
|
||||||
if (cb) cb(result, "");
|
|
||||||
},
|
|
||||||
[cb](const std::string& error) {
|
|
||||||
if (cb) cb(json{}, error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RPCClient::z_shieldCoinbase(const std::string& fromAddr, const std::string& toAddr,
|
void RPCClient::z_shieldCoinbase(const std::string& fromAddr, const std::string& toAddr,
|
||||||
double fee, int limit, UnifiedCallback cb)
|
double fee, int limit, UnifiedCallback cb)
|
||||||
{
|
{
|
||||||
doRPC("z_shieldcoinbase", {fromAddr, toAddr, fee, limit},
|
auto handlers = splitUnified(std::move(cb));
|
||||||
[cb](const json& result) {
|
doRPC("z_shieldcoinbase", {fromAddr, toAddr, fee, limit}, handlers.first, handlers.second);
|
||||||
if (cb) cb(result, "");
|
|
||||||
},
|
|
||||||
[cb](const std::string& error) {
|
|
||||||
if (cb) cb(json{}, error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RPCClient::z_mergeToAddress(const std::vector<std::string>& fromAddrs, const std::string& toAddr,
|
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) {
|
for (const auto& addr : fromAddrs) {
|
||||||
addrs.push_back(addr);
|
addrs.push_back(addr);
|
||||||
}
|
}
|
||||||
doRPC("z_mergetoaddress", {addrs, toAddr, fee, 0, limit},
|
auto handlers = splitUnified(std::move(cb));
|
||||||
[cb](const json& result) {
|
doRPC("z_mergetoaddress", {addrs, toAddr, fee, 0, limit}, handlers.first, handlers.second);
|
||||||
if (cb) cb(result, "");
|
|
||||||
},
|
|
||||||
[cb](const std::string& error) {
|
|
||||||
if (cb) cb(json{}, error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RPCClient::z_getOperationStatus(const std::vector<std::string>& opids, UnifiedCallback cb)
|
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) {
|
for (const auto& id : opids) {
|
||||||
ids.push_back(id);
|
ids.push_back(id);
|
||||||
}
|
}
|
||||||
doRPC("z_getoperationstatus", {ids},
|
auto handlers = splitUnified(std::move(cb));
|
||||||
[cb](const json& result) {
|
doRPC("z_getoperationstatus", {ids}, handlers.first, handlers.second);
|
||||||
if (cb) cb(result, "");
|
|
||||||
},
|
|
||||||
[cb](const std::string& error) {
|
|
||||||
if (cb) cb(json{}, error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RPCClient::getBlock(int height, UnifiedCallback cb)
|
void RPCClient::getBlock(int height, UnifiedCallback cb)
|
||||||
{
|
{
|
||||||
// First get block hash, then get block
|
// First get block hash, then get block
|
||||||
getBlockHash(height,
|
getBlockHash(height,
|
||||||
[this, cb](const json& hashResult) {
|
[this, cb](const json& hashResult) {
|
||||||
std::string hash = hashResult.get<std::string>();
|
std::string hash = hashResult.get<std::string>();
|
||||||
getBlock(hash,
|
auto handlers = splitUnified(cb);
|
||||||
[cb](const json& blockResult) {
|
getBlock(hash, handlers.first, handlers.second);
|
||||||
if (cb) cb(blockResult, "");
|
|
||||||
},
|
|
||||||
[cb](const std::string& error) {
|
|
||||||
if (cb) cb(json{}, error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
[cb](const std::string& error) {
|
[cb](const std::string& error) {
|
||||||
if (cb) cb(json{}, error);
|
if (cb) cb(json{}, error);
|
||||||
|
|||||||
@@ -245,6 +245,17 @@ private:
|
|||||||
json makePayload(const std::string& method, const json& params = json::array());
|
json makePayload(const std::string& method, const json& params = json::array());
|
||||||
void doRPC(const std::string& method, const json& params, Callback cb, ErrorCallback err);
|
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 host_;
|
||||||
std::string port_;
|
std::string port_;
|
||||||
std::string auth_; // Base64 encoded "user:password"
|
std::string auth_; // Base64 encoded "user:password"
|
||||||
|
|||||||
@@ -15,6 +15,23 @@ namespace dragonx {
|
|||||||
namespace ui {
|
namespace ui {
|
||||||
namespace effects {
|
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
|
// Singleton
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -327,16 +344,13 @@ void ThemeEffects::drawShimmer(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
|
|||||||
if (clLeft >= clRight) return;
|
if (clLeft >= clRight) return;
|
||||||
|
|
||||||
float mid = (clLeft + clRight) * 0.5f;
|
float mid = (clLeft + clRight) * 0.5f;
|
||||||
int peakA = (int)(shimmer_.alpha * bgOpacity_ * 255.0f);
|
int peakA = scaledAlpha(shimmer_.alpha, bgOpacity_);
|
||||||
peakA = std::clamp(peakA, 0, 255);
|
|
||||||
|
|
||||||
// Extract shimmer color RGB (ignore alpha, use shimmer_.alpha)
|
// Extract shimmer color RGB (ignore alpha, use shimmer_.alpha)
|
||||||
int sR = shimmer_.color & 0xFF;
|
RGB s = unpackRGB(shimmer_.color);
|
||||||
int sG = (shimmer_.color >> 8) & 0xFF;
|
|
||||||
int sB = (shimmer_.color >> 16) & 0xFF;
|
|
||||||
|
|
||||||
ImU32 clear = IM_COL32(sR, sG, sB, 0);
|
ImU32 clear = IM_COL32(s.r, s.g, s.b, 0);
|
||||||
ImU32 peak = IM_COL32(sR, sG, sB, peakA);
|
ImU32 peak = IM_COL32(s.r, s.g, s.b, peakA);
|
||||||
|
|
||||||
// Inset clip rect by corner rounding to prevent shimmer bleeding into rounded corners
|
// 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;
|
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 minDim = std::min(w, h);
|
||||||
float glareR = minDim * specular_glare_.radius;
|
float glareR = minDim * specular_glare_.radius;
|
||||||
|
|
||||||
int cR = specular_glare_.color & 0xFF;
|
RGB c = unpackRGB(specular_glare_.color);
|
||||||
int cG = (specular_glare_.color >> 8) & 0xFF;
|
|
||||||
int cB = (specular_glare_.color >> 16) & 0xFF;
|
|
||||||
|
|
||||||
// Per-panel seed from position — unique drift per panel
|
// Per-panel seed from position — unique drift per panel
|
||||||
float seed = pMin.x * 0.0073f + pMin.y * 0.0137f;
|
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;
|
if (a <= 0) continue;
|
||||||
|
|
||||||
dl->AddCircleFilled(ImVec2(cx, cy), ringRadius,
|
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 s = positional_hue_.strength;
|
||||||
float inv = 1.0f - s;
|
float inv = 1.0f - s;
|
||||||
|
|
||||||
int bR = baseColor & 0xFF;
|
RGB base = unpackRGB(baseColor);
|
||||||
int bG = (baseColor >> 8) & 0xFF;
|
|
||||||
int bB = (baseColor >> 16) & 0xFF;
|
|
||||||
int bA = (baseColor >> 24) & 0xFF;
|
int bA = (baseColor >> 24) & 0xFF;
|
||||||
|
|
||||||
int tR = tint & 0xFF;
|
RGB t = unpackRGB(tint);
|
||||||
int tG = (tint >> 8) & 0xFF;
|
|
||||||
int tB = (tint >> 16) & 0xFF;
|
|
||||||
|
|
||||||
int r = (int)(bR * inv + tR * s);
|
int r = (int)(base.r * inv + t.r * s);
|
||||||
int g = (int)(bG * inv + tG * s);
|
int g = (int)(base.g * inv + t.g * s);
|
||||||
int b = (int)(bB * inv + tB * s);
|
int b = (int)(base.b * inv + t.b * s);
|
||||||
|
|
||||||
return IM_COL32(r, g, b, bA);
|
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);
|
float alpha = glow_pulse_.minAlpha + phase * (glow_pulse_.maxAlpha - glow_pulse_.minAlpha);
|
||||||
if (alpha <= 0.001f) return;
|
if (alpha <= 0.001f) return;
|
||||||
|
|
||||||
int a = std::clamp((int)(alpha * bgOpacity_ * 255.0f), 0, 255);
|
int a = scaledAlpha(alpha, bgOpacity_);
|
||||||
int cR = glow_pulse_.color & 0xFF;
|
RGB c = unpackRGB(glow_pulse_.color);
|
||||||
int cG = (glow_pulse_.color >> 8) & 0xFF;
|
|
||||||
int cB = (glow_pulse_.color >> 16) & 0xFF;
|
|
||||||
|
|
||||||
// Border-only glow: concentric outlines with smooth Gaussian falloff.
|
// Border-only glow: concentric outlines with smooth Gaussian falloff.
|
||||||
// Use many fine sub-rings (4× the pixel radius) to avoid visible
|
// 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(
|
dl->AddRect(
|
||||||
ImVec2(pMin.x - expand, pMin.y - expand),
|
ImVec2(pMin.x - expand, pMin.y - expand),
|
||||||
ImVec2(pMax.x + expand, pMax.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);
|
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;
|
float phase = std::sin(time_ * gradient_border_.speed * 2.0f * 3.14159265f) * 0.5f + 0.5f;
|
||||||
|
|
||||||
// Extract RGBA from both colors and lerp
|
// Extract RGBA from both colors and lerp
|
||||||
int aR = gradient_border_.colorA & 0xFF;
|
RGB ca = unpackRGB(gradient_border_.colorA);
|
||||||
int aG = (gradient_border_.colorA >> 8) & 0xFF;
|
RGB cb = unpackRGB(gradient_border_.colorB);
|
||||||
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;
|
|
||||||
|
|
||||||
int r = aR + (int)((bR - aR) * phase);
|
int r = ca.r + (int)((cb.r - ca.r) * phase);
|
||||||
int g = aG + (int)((bG - aG) * phase);
|
int g = ca.g + (int)((cb.g - ca.g) * phase);
|
||||||
int b = aB + (int)((bB - aB) * phase);
|
int b = ca.b + (int)((cb.b - ca.b) * phase);
|
||||||
int a = std::clamp((int)(gradient_border_.alpha * bgOpacity_ * 255.0f), 0, 255);
|
int a = scaledAlpha(gradient_border_.alpha, bgOpacity_);
|
||||||
|
|
||||||
// Draw the shifting border
|
// Draw the shifting border
|
||||||
dl->AddRect(pMin, pMax, IM_COL32(r, g, b, a),
|
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 headPos = std::fmod(time_ * edge_trace_.speed, 1.0f);
|
||||||
float traceLen = edge_trace_.length;
|
float traceLen = edge_trace_.length;
|
||||||
|
|
||||||
int cR = edge_trace_.color & 0xFF;
|
RGB c = unpackRGB(edge_trace_.color);
|
||||||
int cG = (edge_trace_.color >> 8) & 0xFF;
|
|
||||||
int cB = (edge_trace_.color >> 16) & 0xFF;
|
|
||||||
|
|
||||||
// Draw the trace as connected line segments with fading alpha.
|
// Draw the trace as connected line segments with fading alpha.
|
||||||
// Use enough segments to make curved corners smooth.
|
// 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
|
aFrac = aFrac * aFrac; // quadratic falloff for natural fade
|
||||||
int a = (int)(edge_trace_.alpha * aFrac * bgOpacity_ * 255.0f);
|
int a = (int)(edge_trace_.alpha * aFrac * bgOpacity_ * 255.0f);
|
||||||
if (a > 0) {
|
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;
|
prev = pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bright dot at the head
|
// 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);
|
ImVec2 headPt = perimeterPoint(pMin, pMax, headPos, rounding);
|
||||||
dl->AddCircleFilled(headPt, edge_trace_.thickness * 1.5f,
|
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;
|
float h = pMax.y - pMin.y;
|
||||||
if (w <= 0 || h <= 0) return;
|
if (w <= 0 || h <= 0) return;
|
||||||
|
|
||||||
int cR = ember_rise_.color & 0xFF;
|
RGB c = unpackRGB(ember_rise_.color);
|
||||||
int cG = (ember_rise_.color >> 8) & 0xFF;
|
|
||||||
int cB = (ember_rise_.color >> 16) & 0xFF;
|
|
||||||
|
|
||||||
for (int i = 0; i < ember_rise_.count; i++) {
|
for (int i = 0; i < ember_rise_.count; i++) {
|
||||||
// Golden-ratio spacing gives evenly distributed phases
|
// Golden-ratio spacing gives evenly distributed phases
|
||||||
@@ -723,11 +721,11 @@ void ThemeEffects::drawEmberRise(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax) const
|
|||||||
|
|
||||||
// Warm core
|
// Warm core
|
||||||
dl->AddCircleFilled(ImVec2(x, y), size,
|
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
|
// Softer outer glow
|
||||||
if (a > 30) {
|
if (a > 30) {
|
||||||
dl->AddCircleFilled(ImVec2(x, y), size * 2.0f,
|
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;
|
float vpY = vp->WorkPos.y;
|
||||||
if (vpW <= 0 || vpH <= 0) return;
|
if (vpW <= 0 || vpH <= 0) return;
|
||||||
|
|
||||||
int cR = ember_rise_.color & 0xFF;
|
RGB c = unpackRGB(ember_rise_.color);
|
||||||
int cG = (ember_rise_.color >> 8) & 0xFF;
|
|
||||||
int cB = (ember_rise_.color >> 16) & 0xFF;
|
|
||||||
|
|
||||||
// Viewport embers: more particles, spread across the whole screen
|
// Viewport embers: more particles, spread across the whole screen
|
||||||
int vpCount = ember_rise_.count * 3;
|
int vpCount = ember_rise_.count * 3;
|
||||||
@@ -777,10 +773,10 @@ void ThemeEffects::drawViewportEmbers(ImDrawList* dl) const {
|
|||||||
if (a <= 0) continue;
|
if (a <= 0) continue;
|
||||||
|
|
||||||
dl->AddCircleFilled(ImVec2(x, y), size,
|
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) {
|
if (a > 20) {
|
||||||
dl->AddCircleFilled(ImVec2(x, y), size * 2.5f,
|
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;
|
float vpY = vp->WorkPos.y;
|
||||||
if (vpW <= 0 || vpH <= 0) return;
|
if (vpW <= 0 || vpH <= 0) return;
|
||||||
|
|
||||||
int cR = sandstorm_.color & 0xFF;
|
RGB c = unpackRGB(sandstorm_.color);
|
||||||
int cG = (sandstorm_.color >> 8) & 0xFF;
|
|
||||||
int cB = (sandstorm_.color >> 16) & 0xFF;
|
|
||||||
|
|
||||||
// Wind direction vector from angle (degrees from horizontal)
|
// Wind direction vector from angle (degrees from horizontal)
|
||||||
float windRad = sandstorm_.windAngle * 3.14159265f / 180.0f;
|
float windRad = sandstorm_.windAngle * 3.14159265f / 180.0f;
|
||||||
@@ -875,21 +869,21 @@ void ThemeEffects::drawSandstorm(ImDrawList* dl) const {
|
|||||||
dl->AddLine(
|
dl->AddLine(
|
||||||
ImVec2(x, y),
|
ImVec2(x, y),
|
||||||
ImVec2(x + sx, y + sy),
|
ImVec2(x + sx, y + sy),
|
||||||
IM_COL32(cR, cG, cB, a),
|
IM_COL32(c.r, c.g, c.b, a),
|
||||||
size * 0.7f);
|
size * 0.7f);
|
||||||
// Bright head dot
|
// Bright head dot
|
||||||
dl->AddCircleFilled(ImVec2(x, y), size * 0.5f,
|
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 {
|
} else {
|
||||||
// Small/slow particles: simple circle
|
// Small/slow particles: simple circle
|
||||||
dl->AddCircleFilled(ImVec2(x, y), size,
|
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)
|
// Occasional larger dust puff (every ~8th particle, double size, half alpha)
|
||||||
if (i % 8 == 0 && a > 15) {
|
if (i % 8 == 0 && a > 15) {
|
||||||
dl->AddCircleFilled(ImVec2(x, y), size * 2.5f,
|
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 headPos = std::fmod(time_ * edge_trace_.speed + posKey, 1.0f);
|
||||||
float traceLen = edge_trace_.length;
|
float traceLen = edge_trace_.length;
|
||||||
|
|
||||||
int cR = edge_trace_.color & 0xFF;
|
RGB c = unpackRGB(edge_trace_.color);
|
||||||
int cG = (edge_trace_.color >> 8) & 0xFF;
|
|
||||||
int cB = (edge_trace_.color >> 16) & 0xFF;
|
|
||||||
|
|
||||||
// Panel edge trace is subtler than sidebar — 60% alpha
|
// Panel edge trace is subtler than sidebar — 60% alpha
|
||||||
float panelAlpha = edge_trace_.alpha * 0.6f * bgOpacity_;
|
float panelAlpha = edge_trace_.alpha * 0.6f * bgOpacity_;
|
||||||
@@ -935,7 +927,7 @@ void ThemeEffects::drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
|
|||||||
aFrac = aFrac * aFrac;
|
aFrac = aFrac * aFrac;
|
||||||
int a = (int)(panelAlpha * aFrac * 255.0f);
|
int a = (int)(panelAlpha * aFrac * 255.0f);
|
||||||
if (a > 0) {
|
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);
|
edge_trace_.thickness * 0.8f);
|
||||||
}
|
}
|
||||||
prev = pt;
|
prev = pt;
|
||||||
@@ -945,7 +937,7 @@ void ThemeEffects::drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
|
|||||||
int headA = (int)(panelAlpha * 255.0f);
|
int headA = (int)(panelAlpha * 255.0f);
|
||||||
ImVec2 headPt = perimeterPoint(pMin, pMax, headPos, rounding);
|
ImVec2 headPt = perimeterPoint(pMin, pMax, headPos, rounding);
|
||||||
dl->AddCircleFilled(headPt, edge_trace_.thickness * 1.2f,
|
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);
|
int panelCount = std::max(2, ember_rise_.count / 3);
|
||||||
float panelAlpha = ember_rise_.alpha * 0.5f * bgOpacity_;
|
float panelAlpha = ember_rise_.alpha * 0.5f * bgOpacity_;
|
||||||
|
|
||||||
int cR = ember_rise_.color & 0xFF;
|
RGB c = unpackRGB(ember_rise_.color);
|
||||||
int cG = (ember_rise_.color >> 8) & 0xFF;
|
|
||||||
int cB = (ember_rise_.color >> 16) & 0xFF;
|
|
||||||
|
|
||||||
// Use panel position as seed for unique particle distribution
|
// Use panel position as seed for unique particle distribution
|
||||||
float seed = pMin.x * 0.013f + pMin.y * 0.031f;
|
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;
|
if (a <= 0) continue;
|
||||||
|
|
||||||
dl->AddCircleFilled(ImVec2(x, y), size,
|
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();
|
dl->PopClipRect();
|
||||||
@@ -1074,14 +1064,12 @@ void ThemeEffects::drawViewportOverlay(ImDrawList* dl) const {
|
|||||||
// === Vignette: edge darkening/tinting (4 gradient strips) ===
|
// === Vignette: edge darkening/tinting (4 gradient strips) ===
|
||||||
// Overlapping strips naturally darken corners more than edges — cinematic look
|
// Overlapping strips naturally darken corners more than edges — cinematic look
|
||||||
if (viewport_overlay_.vignetteEnabled) {
|
if (viewport_overlay_.vignetteEnabled) {
|
||||||
int cR = viewport_overlay_.vignetteColor & 0xFF;
|
RGB c = unpackRGB(viewport_overlay_.vignetteColor);
|
||||||
int cG = (viewport_overlay_.vignetteColor >> 8) & 0xFF;
|
int maxA = scaledAlpha(viewport_overlay_.vignetteAlpha, bgOpacity_);
|
||||||
int cB = (viewport_overlay_.vignetteColor >> 16) & 0xFF;
|
|
||||||
int maxA = std::clamp((int)(viewport_overlay_.vignetteAlpha * bgOpacity_ * 255.0f), 0, 255);
|
|
||||||
|
|
||||||
if (maxA > 0) {
|
if (maxA > 0) {
|
||||||
ImU32 edgeCol = IM_COL32(cR, cG, cB, maxA);
|
ImU32 edgeCol = IM_COL32(c.r, c.g, c.b, maxA);
|
||||||
ImU32 clearCol = IM_COL32(cR, cG, cB, 0);
|
ImU32 clearCol = IM_COL32(c.r, c.g, c.b, 0);
|
||||||
|
|
||||||
float fadeW = vpSize.x * viewport_overlay_.vignetteRadius;
|
float fadeW = vpSize.x * viewport_overlay_.vignetteRadius;
|
||||||
float fadeH = vpSize.y * viewport_overlay_.vignetteRadius;
|
float fadeH = vpSize.y * viewport_overlay_.vignetteRadius;
|
||||||
|
|||||||
@@ -13,17 +13,6 @@ namespace wallet {
|
|||||||
|
|
||||||
namespace {
|
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)
|
bool startsWith(const std::string& value, const char* prefix)
|
||||||
{
|
{
|
||||||
const std::string prefixValue(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 trimmedEndpoint(const LiteServerEndpoint& endpoint)
|
||||||
{
|
{
|
||||||
LiteServerEndpoint normalized = endpoint;
|
LiteServerEndpoint normalized = endpoint;
|
||||||
normalized.url = trimCopy(normalized.url);
|
normalized.url = liteTrimCopy(normalized.url);
|
||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,12 +41,23 @@ std::vector<std::pair<std::size_t, LiteServerEndpoint>> usableConfiguredServers(
|
|||||||
|
|
||||||
std::string normalizedChainName(const std::string& chainName)
|
std::string normalizedChainName(const std::string& chainName)
|
||||||
{
|
{
|
||||||
const std::string normalized = trimCopy(chainName);
|
const std::string normalized = liteTrimCopy(chainName);
|
||||||
return normalized.empty() ? kDragonXLiteChainName : normalized;
|
return normalized.empty() ? kDragonXLiteChainName : normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // 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()
|
std::vector<LiteServerEndpoint> defaultDragonXLiteServers()
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
@@ -83,13 +83,13 @@ LiteConnectionSettings defaultLiteConnectionSettings()
|
|||||||
|
|
||||||
bool isLiteServerUrlUsable(const std::string& serverUrl)
|
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://");
|
return startsWith(normalized, "https://") || startsWith(normalized, "http://");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isOfficialLiteServer(const std::string& serverUrl)
|
bool isOfficialLiteServer(const std::string& serverUrl)
|
||||||
{
|
{
|
||||||
const std::string url = trimCopy(serverUrl);
|
const std::string url = liteTrimCopy(serverUrl);
|
||||||
for (const auto& s : defaultDragonXLiteServers())
|
for (const auto& s : defaultDragonXLiteServers())
|
||||||
if (s.url == url) return true;
|
if (s.url == url) return true;
|
||||||
return false;
|
return false;
|
||||||
@@ -126,7 +126,7 @@ const char* liteConnectionAvailabilityName(LiteConnectionAvailability availabili
|
|||||||
LiteServerSelectionResult selectLiteServer(const LiteConnectionSettings& settings)
|
LiteServerSelectionResult selectLiteServer(const LiteConnectionSettings& settings)
|
||||||
{
|
{
|
||||||
auto usableServers = usableConfiguredServers(settings);
|
auto usableServers = usableConfiguredServers(settings);
|
||||||
const std::string stickyServerUrl = trimCopy(settings.stickyServerUrl);
|
const std::string stickyServerUrl = liteTrimCopy(settings.stickyServerUrl);
|
||||||
|
|
||||||
if (settings.selectionMode == LiteServerSelectionMode::Sticky &&
|
if (settings.selectionMode == LiteServerSelectionMode::Sticky &&
|
||||||
isLiteServerUrlUsable(stickyServerUrl)) {
|
isLiteServerUrlUsable(stickyServerUrl)) {
|
||||||
|
|||||||
@@ -84,6 +84,8 @@ struct LiteServerHealthCheckResult {
|
|||||||
std::string error;
|
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();
|
std::vector<LiteServerEndpoint> defaultDragonXLiteServers();
|
||||||
LiteConnectionSettings defaultLiteConnectionSettings();
|
LiteConnectionSettings defaultLiteConnectionSettings();
|
||||||
bool isLiteServerUrlUsable(const std::string& serverUrl);
|
bool isLiteServerUrlUsable(const std::string& serverUrl);
|
||||||
|
|||||||
@@ -1,24 +1,12 @@
|
|||||||
#include "wallet/lite_sync_service.h"
|
#include "wallet/lite_sync_service.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace dragonx::wallet {
|
namespace dragonx::wallet {
|
||||||
namespace {
|
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)
|
std::uint64_t effectiveStagnantThreshold(std::uint64_t threshold)
|
||||||
{
|
{
|
||||||
return threshold == 0 ? 1 : threshold;
|
return threshold == 0 ? 1 : threshold;
|
||||||
@@ -283,7 +271,7 @@ LiteSyncStatusResult LiteSyncService::pollSyncStatus(const LiteSyncStatusRequest
|
|||||||
|
|
||||||
LiteServerSelectionResult LiteSyncService::selectServerForRequest(const std::string& serverUrl) const
|
LiteServerSelectionResult LiteSyncService::selectServerForRequest(const std::string& serverUrl) const
|
||||||
{
|
{
|
||||||
const auto overrideUrl = trimSyncCopy(serverUrl);
|
const auto overrideUrl = liteTrimCopy(serverUrl);
|
||||||
if (!overrideUrl.empty()) {
|
if (!overrideUrl.empty()) {
|
||||||
if (!isLiteServerUrlUsable(overrideUrl)) {
|
if (!isLiteServerUrlUsable(overrideUrl)) {
|
||||||
return LiteServerSelectionResult{false, {}, 0, false, "lite sync server URL is not usable"};
|
return LiteServerSelectionResult{false, {}, 0, false, "lite sync server URL is not usable"};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "lite_wallet_controller.h"
|
#include "lite_wallet_controller.h"
|
||||||
|
|
||||||
#include "lite_diagnostics.h"
|
#include "lite_diagnostics.h"
|
||||||
|
#include "lite_result_parsers.h"
|
||||||
#include "../data/wallet_state.h"
|
#include "../data/wallet_state.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -695,19 +696,14 @@ std::optional<LiteWalletAppRefreshModel> LiteWalletController::refreshModel()
|
|||||||
if (bridge_) {
|
if (bridge_) {
|
||||||
const auto h = bridge_->execute("height", "");
|
const auto h = bridge_->execute("height", "");
|
||||||
if (h.ok) {
|
if (h.ok) {
|
||||||
try {
|
const auto parsed = parseLiteHeightResponse(h.value);
|
||||||
const auto j = nlohmann::json::parse(h.value);
|
if (parsed.ok && parsed.height.height > 0) {
|
||||||
if (j.is_object() && j.contains("height") && j["height"].is_number_unsigned()) {
|
model.hasSyncStatus = true;
|
||||||
const unsigned long long height = j["height"].get<unsigned long long>();
|
model.sync.walletHeight = parsed.height.height;
|
||||||
if (height > 0) {
|
model.sync.chainHeight = parsed.height.height; // synced: wallet height == chain tip
|
||||||
model.hasSyncStatus = true;
|
model.sync.complete = true;
|
||||||
model.sync.walletHeight = height;
|
model.sync.progress = 1.0;
|
||||||
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 */ }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return model;
|
return model;
|
||||||
|
|||||||
@@ -1,22 +1,10 @@
|
|||||||
#include "wallet/lite_wallet_gateway.h"
|
#include "wallet/lite_wallet_gateway.h"
|
||||||
|
|
||||||
#include <cctype>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace dragonx::wallet {
|
namespace dragonx::wallet {
|
||||||
namespace {
|
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 makeGatewayStatus(WalletBackendState state, std::string message)
|
||||||
{
|
{
|
||||||
WalletBackendStatus status;
|
WalletBackendStatus status;
|
||||||
@@ -262,7 +250,7 @@ LiteWalletRefreshResult LiteWalletGateway::refresh(const LiteWalletRefreshReques
|
|||||||
|
|
||||||
LiteServerSelectionResult LiteWalletGateway::selectServerForRequest(const std::string& serverUrl) const
|
LiteServerSelectionResult LiteWalletGateway::selectServerForRequest(const std::string& serverUrl) const
|
||||||
{
|
{
|
||||||
const auto overrideUrl = trimGatewayCopy(serverUrl);
|
const auto overrideUrl = liteTrimCopy(serverUrl);
|
||||||
if (!overrideUrl.empty()) {
|
if (!overrideUrl.empty()) {
|
||||||
if (!isLiteServerUrlUsable(overrideUrl)) {
|
if (!isLiteServerUrlUsable(overrideUrl)) {
|
||||||
return LiteServerSelectionResult{false, {}, 0, false, "lite gateway server URL is not usable"};
|
return LiteServerSelectionResult{false, {}, 0, false, "lite gateway server URL is not usable"};
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
#include "lite_wallet_lifecycle_service.h"
|
#include "lite_wallet_lifecycle_service.h"
|
||||||
|
|
||||||
#include <cctype>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace dragonx {
|
namespace dragonx {
|
||||||
@@ -12,17 +11,6 @@ namespace wallet {
|
|||||||
|
|
||||||
namespace {
|
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)
|
LiteRedactedPrivateData redactedField(LitePrivateDataKind kind, const std::string& value)
|
||||||
{
|
{
|
||||||
return LiteRedactedPrivateData{kind, !value.empty(), redactLitePrivateDataValue(value)};
|
return LiteRedactedPrivateData{kind, !value.empty(), redactLitePrivateDataValue(value)};
|
||||||
@@ -172,7 +160,7 @@ LiteWalletLifecyclePlan LiteWalletLifecycleService::planOpenWallet(
|
|||||||
LiteWalletLifecyclePlan LiteWalletLifecycleService::planRestoreWallet(
|
LiteWalletLifecyclePlan LiteWalletLifecycleService::planRestoreWallet(
|
||||||
const LiteWalletRestoreRequest& request) const
|
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"
|
? "restore seed phrase is required"
|
||||||
: std::string();
|
: std::string();
|
||||||
return makePlan(LiteWalletLifecycleOperation::RestoreFromSeed,
|
return makePlan(LiteWalletLifecycleOperation::RestoreFromSeed,
|
||||||
@@ -220,7 +208,7 @@ LiteWalletLifecycleResult LiteWalletLifecycleService::restoreWallet(
|
|||||||
LiteServerSelectionResult LiteWalletLifecycleService::selectServerForRequest(
|
LiteServerSelectionResult LiteWalletLifecycleService::selectServerForRequest(
|
||||||
const std::string& serverUrl) const
|
const std::string& serverUrl) const
|
||||||
{
|
{
|
||||||
const std::string overrideUrl = trimLifecycleCopy(serverUrl);
|
const std::string overrideUrl = liteTrimCopy(serverUrl);
|
||||||
if (!overrideUrl.empty()) {
|
if (!overrideUrl.empty()) {
|
||||||
if (!isLiteServerUrlUsable(overrideUrl)) {
|
if (!isLiteServerUrlUsable(overrideUrl)) {
|
||||||
return LiteServerSelectionResult{false, {}, 0, false, "lite lifecycle server URL is not usable"};
|
return LiteServerSelectionResult{false, {}, 0, false, "lite lifecycle server URL is not usable"};
|
||||||
|
|||||||
@@ -1,25 +1,10 @@
|
|||||||
#include "lite_wallet_server_selection_adapter.h"
|
#include "lite_wallet_server_selection_adapter.h"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cctype>
|
|
||||||
|
|
||||||
namespace dragonx {
|
namespace dragonx {
|
||||||
namespace wallet {
|
namespace wallet {
|
||||||
|
|
||||||
namespace {
|
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(
|
LiteServerSelectionMode walletModeFromSettings(
|
||||||
config::Settings::LiteServerSelectionPreferenceMode mode)
|
config::Settings::LiteServerSelectionPreferenceMode mode)
|
||||||
{
|
{
|
||||||
@@ -52,14 +37,14 @@ LiteConnectionSettings liteConnectionSettingsFromAppSettings(const config::Setti
|
|||||||
connectionSettings.servers.clear();
|
connectionSettings.servers.clear();
|
||||||
for (const auto& server : settings.getLiteServers()) {
|
for (const auto& server : settings.getLiteServers()) {
|
||||||
connectionSettings.servers.push_back(LiteServerEndpoint{
|
connectionSettings.servers.push_back(LiteServerEndpoint{
|
||||||
trimCopy(server.url),
|
liteTrimCopy(server.url),
|
||||||
server.label,
|
server.label,
|
||||||
server.enabled
|
server.enabled
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
connectionSettings.selectionMode = walletModeFromSettings(settings.getLiteServerSelectionMode());
|
connectionSettings.selectionMode = walletModeFromSettings(settings.getLiteServerSelectionMode());
|
||||||
connectionSettings.stickyServerUrl = trimCopy(settings.getLiteStickyServerUrl());
|
connectionSettings.stickyServerUrl = liteTrimCopy(settings.getLiteStickyServerUrl());
|
||||||
connectionSettings.chainName = trimCopy(settings.getLiteChainName());
|
connectionSettings.chainName = liteTrimCopy(settings.getLiteChainName());
|
||||||
if (connectionSettings.chainName.empty()) connectionSettings.chainName = kDragonXLiteChainName;
|
if (connectionSettings.chainName.empty()) connectionSettings.chainName = kDragonXLiteChainName;
|
||||||
connectionSettings.randomSelectionSeed = settings.getLiteRandomSelectionSeed();
|
connectionSettings.randomSelectionSeed = settings.getLiteRandomSelectionSeed();
|
||||||
return connectionSettings;
|
return connectionSettings;
|
||||||
@@ -72,24 +57,24 @@ void applyLiteConnectionSettingsToAppSettings(config::Settings& settings,
|
|||||||
servers.reserve(connectionSettings.servers.size());
|
servers.reserve(connectionSettings.servers.size());
|
||||||
for (const auto& server : connectionSettings.servers) {
|
for (const auto& server : connectionSettings.servers) {
|
||||||
servers.push_back(config::Settings::LiteServerPreference{
|
servers.push_back(config::Settings::LiteServerPreference{
|
||||||
trimCopy(server.url),
|
liteTrimCopy(server.url),
|
||||||
server.label,
|
server.label,
|
||||||
server.enabled
|
server.enabled
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.setLiteServerSelectionMode(settingsModeFromWallet(connectionSettings.selectionMode));
|
settings.setLiteServerSelectionMode(settingsModeFromWallet(connectionSettings.selectionMode));
|
||||||
settings.setLiteStickyServerUrl(trimCopy(connectionSettings.stickyServerUrl));
|
settings.setLiteStickyServerUrl(liteTrimCopy(connectionSettings.stickyServerUrl));
|
||||||
settings.setLiteChainName(trimCopy(connectionSettings.chainName).empty()
|
settings.setLiteChainName(liteTrimCopy(connectionSettings.chainName).empty()
|
||||||
? kDragonXLiteChainName
|
? kDragonXLiteChainName
|
||||||
: trimCopy(connectionSettings.chainName));
|
: liteTrimCopy(connectionSettings.chainName));
|
||||||
settings.setLiteRandomSelectionSeed(connectionSettings.randomSelectionSeed);
|
settings.setLiteRandomSelectionSeed(connectionSettings.randomSelectionSeed);
|
||||||
settings.setLiteServers(servers);
|
settings.setLiteServers(servers);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string redactLiteServerSelectionValue(const std::string& value)
|
std::string redactLiteServerSelectionValue(const std::string& value)
|
||||||
{
|
{
|
||||||
const std::string trimmed = trimCopy(value);
|
const std::string trimmed = liteTrimCopy(value);
|
||||||
if (trimmed.empty()) return "<empty>";
|
if (trimmed.empty()) return "<empty>";
|
||||||
const auto scheme = trimmed.find("://");
|
const auto scheme = trimmed.find("://");
|
||||||
if (scheme == std::string::npos) return "<redacted>";
|
if (scheme == std::string::npos) return "<redacted>";
|
||||||
|
|||||||
Reference in New Issue
Block a user