2 Commits

Author SHA1 Message Date
dan_s
dc4426810f fix: accurate sync speed display, add missing i18n keys, native language names
- Fix blk/s calculation that was inflated ~10x due to resetting the
  time baseline every frame instead of only when blocks advanced
- Add decay when no new blocks arrive for 10s so rate doesn't stay stale
- Add 7 missing translation keys (timeout_off/1min/5min/15min/30min/1hour,
  slider_off) to all 8 language files so settings dropdowns translate
- Show language names in native script (中文, Русский, 日本語, 한국어)
2026-04-12 15:12:36 -05:00
dan_s
915c1b4d23 feat: non-blocking warmup — connect during daemon initialization
Instead of blocking the entire UI with "Activating best chain..." until
the daemon finishes warmup, treat warmup responses as a successful
connection. The wallet now:

- Sets connected=true + warming_up=true when daemon returns RPC -28
- Shows warmup status with block progress in the loading overlay
- Polls getinfo every few seconds to detect warmup completion
- Allows Console, Peers, Settings tabs during warmup
- Shows orange status indicator with warmup message in status bar
- Skips balance/tx/address refresh until warmup completes
- Triggers full data refresh once daemon is ready

Also: fix curl handle/header leak on reconnect, fill in empty
externalDetected error branch, bump version to v1.2.0 in build scripts.
2026-04-12 14:32:57 -05:00
26 changed files with 5345 additions and 4934 deletions

View File

@@ -20,7 +20,7 @@
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VERSION="1.1.1"
VERSION="1.2.0"
# ── Colours ──────────────────────────────────────────────────────────────────
RED='\033[0;31m'

View File

@@ -654,6 +654,7 @@
"show_qr_code": "QR-Code anzeigen",
"showing_transactions": "Zeige %d%d von %d Transaktionen (gesamt: %zu)",
"simple_background": "Einfacher Hintergrund",
"slider_off": "Aus",
"start_mining": "Mining starten",
"status": "Status",
"stop_external": "Externen Daemon stoppen",
@@ -670,6 +671,12 @@
"time_hours_ago": "vor %d Stunden",
"time_minutes_ago": "vor %d Minuten",
"time_seconds_ago": "vor %d Sekunden",
"timeout_15min": "15 Min",
"timeout_1hour": "1 Stunde",
"timeout_1min": "1 Min",
"timeout_30min": "30 Min",
"timeout_5min": "5 Min",
"timeout_off": "Aus",
"to": "An",
"to_upper": "AN",
"tools": "WERKZEUGE",

View File

@@ -654,6 +654,7 @@
"show_qr_code": "Mostrar Código QR",
"showing_transactions": "Mostrando %d%d de %d transacciones (total: %zu)",
"simple_background": "Fondo simple",
"slider_off": "Apagado",
"start_mining": "Iniciar Minería",
"status": "Estado",
"stop_external": "Detener daemon externo",
@@ -670,6 +671,12 @@
"time_hours_ago": "hace %d horas",
"time_minutes_ago": "hace %d minutos",
"time_seconds_ago": "hace %d segundos",
"timeout_15min": "15 min",
"timeout_1hour": "1 hora",
"timeout_1min": "1 min",
"timeout_30min": "30 min",
"timeout_5min": "5 min",
"timeout_off": "Apagado",
"to": "Para",
"to_upper": "PARA",
"tools": "HERRAMIENTAS",

View File

@@ -654,6 +654,7 @@
"show_qr_code": "Afficher le code QR",
"showing_transactions": "Affichage %d%d sur %d transactions (total : %zu)",
"simple_background": "Arrière-plan simple",
"slider_off": "Désactivé",
"start_mining": "Démarrer le minage",
"status": "Statut",
"stop_external": "Arrêter le daemon externe",
@@ -670,6 +671,12 @@
"time_hours_ago": "il y a %d heures",
"time_minutes_ago": "il y a %d minutes",
"time_seconds_ago": "il y a %d secondes",
"timeout_15min": "15 min",
"timeout_1hour": "1 heure",
"timeout_1min": "1 min",
"timeout_30min": "30 min",
"timeout_5min": "5 min",
"timeout_off": "Désactivé",
"to": "À",
"to_upper": "À",
"tools": "OUTILS",

View File

@@ -654,6 +654,7 @@
"show_qr_code": "QRコードを表示",
"showing_transactions": "%d%d / %d 件の取引を表示中(合計:%zu",
"simple_background": "シンプル背景",
"slider_off": "オフ",
"start_mining": "マイニング開始",
"status": "ステータス",
"stop_external": "外部デーモンを停止",
@@ -670,6 +671,12 @@
"time_hours_ago": "%d時間前",
"time_minutes_ago": "%d分前",
"time_seconds_ago": "%d秒前",
"timeout_15min": "15分",
"timeout_1hour": "1時間",
"timeout_1min": "1分",
"timeout_30min": "30分",
"timeout_5min": "5分",
"timeout_off": "オフ",
"to": "宛先",
"to_upper": "宛先",
"tools": "ツール",

View File

@@ -654,6 +654,7 @@
"show_qr_code": "QR 코드 표시",
"showing_transactions": "%d%d / %d건의 거래 표시 중 (총: %zu)",
"simple_background": "단순 배경",
"slider_off": "끔",
"start_mining": "채굴 시작",
"status": "상태",
"stop_external": "외부 데몬 중지",
@@ -670,6 +671,12 @@
"time_hours_ago": "%d시간 전",
"time_minutes_ago": "%d분 전",
"time_seconds_ago": "%d초 전",
"timeout_15min": "15분",
"timeout_1hour": "1시간",
"timeout_1min": "1분",
"timeout_30min": "30분",
"timeout_5min": "5분",
"timeout_off": "끔",
"to": "받는 곳",
"to_upper": "받는 곳",
"tools": "도구",

View File

@@ -654,6 +654,7 @@
"show_qr_code": "Mostrar Código QR",
"showing_transactions": "Mostrando %d%d de %d transações (total: %zu)",
"simple_background": "Fundo simples",
"slider_off": "Desligado",
"start_mining": "Iniciar Mineração",
"status": "Status",
"stop_external": "Parar daemon externo",
@@ -670,6 +671,12 @@
"time_hours_ago": "há %d horas",
"time_minutes_ago": "há %d minutos",
"time_seconds_ago": "há %d segundos",
"timeout_15min": "15 min",
"timeout_1hour": "1 hora",
"timeout_1min": "1 min",
"timeout_30min": "30 min",
"timeout_5min": "5 min",
"timeout_off": "Desligado",
"to": "Para",
"to_upper": "PARA",
"tools": "FERRAMENTAS",

View File

@@ -654,6 +654,7 @@
"show_qr_code": "Показать QR-код",
"showing_transactions": "Показано %d%d из %d транзакций (всего: %zu)",
"simple_background": "Простой фон",
"slider_off": "Выкл",
"start_mining": "Начать майнинг",
"status": "Статус",
"stop_external": "Остановить внешний daemon",
@@ -670,6 +671,12 @@
"time_hours_ago": "%d часов назад",
"time_minutes_ago": "%d минут назад",
"time_seconds_ago": "%d секунд назад",
"timeout_15min": "15 мин",
"timeout_1hour": "1 час",
"timeout_1min": "1 мин",
"timeout_30min": "30 мин",
"timeout_5min": "5 мин",
"timeout_off": "Выкл",
"to": "Кому",
"to_upper": "КОМУ",
"tools": "ИНСТРУМЕНТЫ",

View File

@@ -654,6 +654,7 @@
"show_qr_code": "显示二维码",
"showing_transactions": "显示第 %d%d 笔,共 %d 笔交易(总计:%zu",
"simple_background": "简单背景",
"slider_off": "关闭",
"start_mining": "开始挖矿",
"status": "状态",
"stop_external": "停止外部守护进程",
@@ -670,6 +671,12 @@
"time_hours_ago": "%d 小时前",
"time_minutes_ago": "%d 分钟前",
"time_seconds_ago": "%d 秒前",
"timeout_15min": "15分钟",
"timeout_1hour": "1小时",
"timeout_1min": "1分钟",
"timeout_30min": "30分钟",
"timeout_5min": "5分钟",
"timeout_off": "关闭",
"to": "至",
"to_upper": "至",
"tools": "工具",

View File

@@ -8,7 +8,7 @@ set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUILD_DIR="${SCRIPT_DIR}/build/linux"
APPDIR="${BUILD_DIR}/AppDir"
VERSION="1.1.1"
VERSION="1.2.0"
# Colors
GREEN='\033[0;32m'

View File

@@ -7,7 +7,7 @@ set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUILD_DIR="${SCRIPT_DIR}/build/linux"
VERSION="1.1.1"
VERSION="1.2.0"
# Colors for output
RED='\033[0;31m'

View File

@@ -610,13 +610,16 @@ void App::update()
core_timer_ = 0.0f;
refreshCoreData();
}
if (transaction_timer_ >= active_tx_interval_) {
transaction_timer_ = 0.0f;
refreshTransactionData();
}
if (address_timer_ >= active_addr_interval_) {
address_timer_ = 0.0f;
refreshAddressData();
// Skip balance/tx/address refresh during warmup — RPC calls fail with -28
if (!state_.warming_up) {
if (transaction_timer_ >= active_tx_interval_) {
transaction_timer_ = 0.0f;
refreshTransactionData();
}
if (address_timer_ >= active_addr_interval_) {
address_timer_ = 0.0f;
refreshAddressData();
}
}
if (peer_timer_ >= active_peer_interval_) {
peer_timer_ = 0.0f;
@@ -1078,16 +1081,17 @@ void App::render()
int fgVtxStart = fgDL->VtxBuffer.Size;
// ---------------------------------------------------------------
// Loading overlay — show only while daemon is not yet connected.
// Once connected, all tabs render normally; individual tabs show
// inline sync-progress indicators when the chain is still syncing.
// Pages that remain accessible even without a connection:
// Console, Peers, Settings
// Loading overlay — show only on tabs that truly need wallet data.
// Tabs that work without full daemon connection:
// Console, Peers, Settings, Market, Mining
// Tabs that need balance/transaction data (show overlay):
// Overview, Send, Receive, History
// ---------------------------------------------------------------
bool pageNeedsDaemon = (current_page_ != ui::NavPage::Console &&
current_page_ != ui::NavPage::Peers &&
current_page_ != ui::NavPage::Settings);
bool daemonReady = state_.connected; // don't gate on sync state
bool pageNeedsWalletData = (current_page_ == ui::NavPage::Overview ||
current_page_ == ui::NavPage::Send ||
current_page_ == ui::NavPage::Receive ||
current_page_ == ui::NavPage::History);
bool daemonReady = state_.connected && !state_.warming_up;
// Don't show lock screen while pool mining — xmrig runs independently
// of the wallet and locking would block the mining UI needlessly.
@@ -1095,7 +1099,12 @@ void App::render()
if (state_.isLocked() && !poolMiningActive) {
// Lock screen — covers tab content just like the loading overlay
renderLockScreen();
} else if (pageNeedsDaemon && (!daemonReady || (state_.connected && !state_.encryption_state_known))) {
} else if (pageNeedsWalletData && state_.warming_up) {
// Daemon is reachable but still initializing — show warmup overlay
// only on wallet-data tabs. Other tabs render normally.
lock_screen_was_visible_ = false;
renderLoadingOverlay(contentH);
} else if (pageNeedsWalletData && (!daemonReady || (state_.connected && !state_.encryption_state_known))) {
// Track how long we've been waiting for encryption state
if (state_.connected && !state_.encryption_state_known) {
encryption_check_timer_ += ImGui::GetIO().DeltaTime;
@@ -1421,7 +1430,16 @@ void App::renderStatusBar()
// Connection status
float dotOpacity = S.drawElement("components.status-bar", "connection-dot").opacity;
if (dotOpacity < 0.0f) dotOpacity = 1.0f;
if (state_.connected) {
if (state_.warming_up) {
ImGui::PushFont(ui::material::Type().iconSmall());
ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.0f, dotOpacity), ICON_MD_CIRCLE);
ImGui::PopFont();
ImGui::SameLine(0, sbIconTextGap);
// Show truncated warmup status (e.g. "Activating best chain... (Block 12345)")
const char* warmupText = state_.warmup_status.empty()
? "Warming up..." : state_.warmup_status.c_str();
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), "%s", warmupText);
} else if (state_.connected) {
ImGui::PushFont(ui::material::Type().iconSmall());
ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, dotOpacity), ICON_MD_CIRCLE);
ImGui::PopFont();
@@ -1467,7 +1485,11 @@ void App::renderStatusBar()
static double s_prev_time = 0.0;
static double s_blocks_per_sec = 0.0;
double now = ImGui::GetTime();
if (s_prev_time > 0.0 && state_.sync.blocks > s_prev_blocks) {
if (s_prev_time == 0.0) {
// First sample — seed baseline, don't compute rate yet
s_prev_blocks = state_.sync.blocks;
s_prev_time = now;
} else if (state_.sync.blocks > s_prev_blocks) {
double dt = now - s_prev_time;
if (dt > 0.5) {
double raw = (state_.sync.blocks - s_prev_blocks) / dt;
@@ -1478,9 +1500,10 @@ void App::renderStatusBar()
s_prev_blocks = state_.sync.blocks;
s_prev_time = now;
}
} else {
s_prev_blocks = state_.sync.blocks;
s_prev_time = now;
} else if (now - s_prev_time > 10.0) {
// No new blocks for 10s — decay the displayed rate
s_blocks_per_sec *= 0.5;
s_prev_time = now;
}
if (s_blocks_per_sec > 0.1) {
@@ -2769,6 +2792,21 @@ void App::renderLoadingOverlay(float contentH)
curY += ts.y + gap;
}
// -------------------------------------------------------------------
// 2b. Warmup description (subtitle explaining what's happening)
// -------------------------------------------------------------------
if (state_.warming_up && !state_.warmup_description.empty()) {
const char* descText = state_.warmup_description.c_str();
ImFont* capFont = Type().caption();
if (!capFont) capFont = ImGui::GetFont();
// Wrap to barW so long descriptions don't overflow
ImVec2 ts = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, barW, descText);
dl->AddText(capFont, capFont->LegacySize,
ImVec2(wp.x + cx - ts.x * 0.5f, curY),
IM_COL32(160, 160, 160, 200), descText, nullptr, barW);
curY += ts.y + gap;
}
// -------------------------------------------------------------------
// 3. Sync progress bar (if connected and syncing)
// -------------------------------------------------------------------

View File

@@ -49,6 +49,40 @@ namespace dragonx {
using json = nlohmann::json;
// ============================================================================
// Warmup Message Translation
// Maps raw daemon RPC warmup messages to user-friendly text.
// ============================================================================
struct WarmupText {
const char* title;
const char* description;
};
static WarmupText translateWarmup(const std::string& raw)
{
if (raw.find("Loading") != std::string::npos)
return {"Loading blockchain data...",
"Reading the block database from disk. This may take a few minutes after updates."};
if (raw.find("Verifying") != std::string::npos)
return {"Verifying blockchain...",
"Checking recent blocks to make sure your chain data is valid."};
if (raw.find("Activating") != std::string::npos)
return {"Processing blocks...",
"Applying blocks to build the current chain state."};
if (raw.find("Rewinding") != std::string::npos)
return {"Reorganizing chain...",
"A chain reorganization was detected. Reverting to the correct chain."};
if (raw.find("Rescanning") != std::string::npos)
return {"Scanning for transactions...",
"Searching the blockchain for transactions belonging to your wallet. This can take a while."};
if (raw.find("Pruning") != std::string::npos)
return {"Optimizing storage...",
"Removing old block data to free up disk space."};
// Fallback: use the raw message
return {raw.c_str(), ""};
}
// ============================================================================
// Connection Management
// ============================================================================
@@ -150,12 +184,31 @@ void App::tryConnect()
worker_->post([this, config, daemonStarting, externalDetected, attempt]() -> rpc::RPCWorker::MainCb {
bool connected = rpc_->connect(config.host, config.port, config.rpcuser, config.rpcpassword);
std::string connectErr = rpc_->getLastConnectError();
bool warmingUp = rpc_->isWarmingUp();
std::string warmupStatus = rpc_->getWarmupStatus();
return [this, config, connected, daemonStarting, externalDetected, attempt, connectErr]() {
return [this, config, connected, warmingUp, warmupStatus, daemonStarting, externalDetected, attempt, connectErr]() {
if (connected) {
VERBOSE_LOGF("[connect #%d] Connected successfully\n", attempt);
VERBOSE_LOGF("[connect #%d] Connected successfully%s\n", attempt,
warmingUp ? " (daemon warming up)" : "");
saved_config_ = config; // save for fast-lane connection
onConnected();
if (warmingUp) {
// Daemon is reachable and auth works, but RPC calls will
// fail until warmup completes. Set the warmup state so
// the UI shows status instead of a blocking overlay.
state_.warming_up = true;
auto wt = translateWarmup(warmupStatus);
state_.warmup_status = wt.title;
state_.warmup_description = wt.description;
// Append current block height from daemon output
if (embedded_daemon_) {
int h = embedded_daemon_->getLastBlockHeight();
if (h > 0)
state_.warmup_status += " (Block " + std::to_string(h) + ")";
}
connection_status_ = state_.warmup_status;
}
} else {
// HTTP 401 = authentication failure. The daemon is running
// but our rpcuser/rpcpassword don't match. Don't retry
@@ -203,18 +256,6 @@ void App::tryConnect()
"RPC authentication failed (HTTP 401). "
"The rpcuser/rpcpassword in DRAGONX.conf don't match the running daemon. "
"Restart the daemon or correct the credentials.");
} else if (connectErr.find("Loading") != std::string::npos ||
connectErr.find("Verifying") != std::string::npos ||
connectErr.find("Activating") != std::string::npos ||
connectErr.find("Rewinding") != std::string::npos ||
connectErr.find("Rescanning") != std::string::npos ||
connectErr.find("Pruning") != std::string::npos) {
// Daemon is reachable but still in warmup (Loading block index, etc.)
// Check this BEFORE daemonStarting so the actual warmup status is shown.
state_.connected = false;
connection_status_ = connectErr;
VERBOSE_LOGF("[connect #%d] Daemon warmup: %s\n", attempt, connectErr.c_str());
core_timer_ = CORE_INTERVAL_DEFAULT - 1.0f;
} else if (daemonStarting) {
state_.connected = false;
// Show the actual RPC error alongside the waiting message so
@@ -228,6 +269,15 @@ void App::tryConnect()
attempt, connectErr.c_str());
core_timer_ = CORE_INTERVAL_DEFAULT - 1.0f;
} else if (externalDetected) {
state_.connected = false;
if (!connectErr.empty()) {
connection_status_ = "Connecting to daemon — " + connectErr;
} else {
connection_status_ = "Connecting to external daemon...";
}
VERBOSE_LOGF("[connect #%d] External daemon detected but RPC failed (%s), will retry...\n",
attempt, connectErr.c_str());
core_timer_ = CORE_INTERVAL_DEFAULT - 1.0f;
} else {
onDisconnected("Connection failed");
VERBOSE_LOGF("[connect #%d] RPC connection failed — no daemon starting, no external detected\n", attempt);
@@ -376,6 +426,8 @@ void App::onConnected()
void App::onDisconnected(const std::string& reason)
{
state_.connected = false;
state_.warming_up = false;
state_.warmup_status.clear();
state_.clear();
connection_status_ = reason;
@@ -490,6 +542,13 @@ void App::refreshData()
{
if (!state_.connected || !rpc_ || !worker_) return;
// During warmup, only poll for warmup completion via refreshCoreData.
// Other RPC calls (balance, addresses, transactions) will fail with -28.
if (state_.warming_up) {
refreshCoreData();
return;
}
// Dispatch each category independently — results trickle into the UI
// as each completes, rather than waiting for the slowest phase.
refreshCoreData();
@@ -518,6 +577,60 @@ void App::refreshCoreData()
{
if (!state_.connected) return;
// During warmup, poll getinfo to detect when warmup ends.
// Most RPC calls (balance, blockchain info) will fail with -28 during warmup.
if (state_.warming_up) {
if (core_refresh_in_progress_.exchange(true)) return;
worker_->post([this]() -> rpc::RPCWorker::MainCb {
json info;
bool ok = false;
std::string errMsg;
try {
info = rpc_->call("getinfo");
ok = true;
} catch (const std::exception& e) {
errMsg = e.what();
}
return [this, info, ok, errMsg]() {
if (ok) {
// Warmup finished — daemon is fully ready
state_.warming_up = false;
state_.warmup_status.clear();
state_.warmup_description.clear();
connection_status_ = "Connected";
VERBOSE_LOGF("[warmup] Daemon ready, warmup complete\n");
// Parse initial info
try {
if (info.contains("version"))
state_.daemon_version = info["version"].get<int>();
if (info.contains("blocks"))
state_.sync.blocks = info["blocks"].get<int>();
if (info.contains("longestchain")) {
int lc = info["longestchain"].get<int>();
if (lc > 0) state_.longestchain = lc;
}
} catch (...) {}
// Trigger full data refresh now that daemon is ready
refreshData();
} else {
// Still warming up — update status
auto wt = translateWarmup(errMsg);
state_.warmup_status = wt.title;
state_.warmup_description = wt.description;
if (embedded_daemon_) {
int h = embedded_daemon_->getLastBlockHeight();
if (h > 0)
state_.warmup_status += " (Block " + std::to_string(h) + ")";
}
connection_status_ = state_.warmup_status;
VERBOSE_LOGF("[warmup] Still warming up: %s\n", errMsg.c_str());
}
core_refresh_in_progress_.store(false, std::memory_order_release);
};
});
return;
}
// Use fast-lane on Overview for snappier balance updates
bool useFast = (current_page_ == ui::NavPage::Overview);
auto* w = useFast && fast_worker_ && fast_worker_->isRunning()

View File

@@ -116,6 +116,26 @@ public:
* @brief Get last N lines of daemon output (thread-safe snapshot)
*/
std::vector<std::string> getRecentLines(int maxLines = 8) const;
/**
* @brief Extract the latest block height from daemon output (thread-safe).
* Parses the last "height=N" from UpdateTip lines without copying
* the entire output buffer. Returns -1 if no UpdateTip found.
*/
int getLastBlockHeight() const {
std::lock_guard<std::mutex> lk(output_mutex_);
// Search backwards from the end for "height="
size_t pos = process_output_.rfind("height=");
if (pos == std::string::npos) return -1;
pos += 7; // skip "height="
int h = 0;
for (size_t i = pos; i < process_output_.size(); ++i) {
char c = process_output_[i];
if (c >= '0' && c <= '9') h = h * 10 + (c - '0');
else break;
}
return h > 0 ? h : -1;
}
/**
* @brief Whether start() detected an existing daemon on the RPC port.

View File

@@ -180,6 +180,9 @@ struct PoolMiningState {
struct WalletState {
// Connection
bool connected = false;
bool warming_up = false; // daemon reachable but in RPC warmup (error -28)
std::string warmup_status; // user-friendly title, e.g. "Processing blocks..."
std::string warmup_description; // subtitle explaining the stage
int daemon_version = 0;
std::string daemon_subversion;
int protocol_version = 0;
@@ -250,6 +253,9 @@ struct WalletState {
void clear() {
connected = false;
warming_up = false;
warmup_status.clear();
warmup_description.clear();
daemon_version = 0;
daemon_subversion.clear();
protocol_version = 0;

View File

@@ -2736,56 +2736,73 @@ unsigned char res_lang_de_json[] = {
0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x69,
0x6e, 0x66, 0x61, 0x63, 0x68, 0x65, 0x72, 0x20, 0x48, 0x69, 0x6e, 0x74,
0x65, 0x72, 0x67, 0x72, 0x75, 0x6e, 0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x22, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x69, 0x6e,
0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x69, 0x6e, 0x69, 0x6e,
0x67, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x6e, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22,
0x3a, 0x20, 0x22, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x65, 0x78,
0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0x45, 0x78,
0x74, 0x65, 0x72, 0x6e, 0x65, 0x6e, 0x20, 0x44, 0x61, 0x65, 0x6d, 0x6f,
0x6e, 0x20, 0x73, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x6e, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x6d, 0x69,
0x20, 0x20, 0x22, 0x73, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x6f, 0x66,
0x66, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x75, 0x73, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x69,
0x6e, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x69, 0x6e, 0x69,
0x6e, 0x67, 0x20, 0x73, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x6e, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74,
0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,
0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6e,
0x73, 0x61, 0x6b, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x69, 0x72, 0x64,
0x20, 0xc3, 0xbc, 0x62, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x6c,
0x74, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x45,
0x72, 0x66, 0x6f, 0x6c, 0x67, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x3a, 0x20, 0x22,
0x5a, 0x75, 0x73, 0x61, 0x6d, 0x6d, 0x65, 0x6e, 0x66, 0x61, 0x73, 0x73,
0x75, 0x6e, 0x67, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73,
0x79, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x53, 0x79,
0x6e, 0x63, 0x68, 0x72, 0x6f, 0x6e, 0x69, 0x73, 0x69, 0x65, 0x72, 0x65,
0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74,
0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3a,
0x20, 0x22, 0x54, 0x2d, 0x41, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x6e,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x65, 0x73, 0x74,
0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22,
0x3a, 0x20, 0x22, 0x54, 0x65, 0x73, 0x74, 0x65, 0x6e, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x68, 0x65, 0x6d, 0x65, 0x22, 0x3a,
0x20, 0x22, 0x44, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x22, 0x2c, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x22, 0x74, 0x68, 0x65, 0x6d, 0x65, 0x5f, 0x65, 0x66,
0x66, 0x65, 0x63, 0x74, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x65, 0x73,
0x69, 0x67, 0x6e, 0x2d, 0x45, 0x66, 0x66, 0x65, 0x6b, 0x74, 0x65, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x69, 0x6d, 0x65, 0x5f,
0x64, 0x61, 0x79, 0x73, 0x5f, 0x61, 0x67, 0x6f, 0x22, 0x3a, 0x20, 0x22,
0x76, 0x6f, 0x72, 0x20, 0x25, 0x64, 0x20, 0x54, 0x61, 0x67, 0x65, 0x6e,
0x6e, 0x67, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x6e, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x22, 0x3a, 0x20, 0x22, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x65,
0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x22, 0x3a, 0x20, 0x22, 0x45,
0x78, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x6e, 0x20, 0x44, 0x61, 0x65, 0x6d,
0x6f, 0x6e, 0x20, 0x73, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x6e, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x6d,
0x69, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x69, 0x6e,
0x69, 0x6e, 0x67, 0x20, 0x73, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x6e, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x75, 0x62, 0x6d, 0x69,
0x74, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x54, 0x72, 0x61,
0x6e, 0x73, 0x61, 0x6b, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x69, 0x72,
0x64, 0x20, 0xc3, 0xbc, 0x62, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x74, 0x65,
0x6c, 0x74, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x20, 0x22,
0x45, 0x72, 0x66, 0x6f, 0x6c, 0x67, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x3a, 0x20,
0x22, 0x5a, 0x75, 0x73, 0x61, 0x6d, 0x6d, 0x65, 0x6e, 0x66, 0x61, 0x73,
0x73, 0x75, 0x6e, 0x67, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x73, 0x79, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x22, 0x53,
0x79, 0x6e, 0x63, 0x68, 0x72, 0x6f, 0x6e, 0x69, 0x73, 0x69, 0x65, 0x72,
0x65, 0x2e, 0x2e, 0x2e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22,
0x3a, 0x20, 0x22, 0x54, 0x2d, 0x41, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65,
0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x65, 0x73,
0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x22, 0x3a, 0x20, 0x22, 0x54, 0x65, 0x73, 0x74, 0x65, 0x6e, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x68, 0x65, 0x6d, 0x65, 0x22,
0x3a, 0x20, 0x22, 0x44, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x68, 0x65, 0x6d, 0x65, 0x5f, 0x65,
0x66, 0x66, 0x65, 0x63, 0x74, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x44, 0x65,
0x73, 0x69, 0x67, 0x6e, 0x2d, 0x45, 0x66, 0x66, 0x65, 0x6b, 0x74, 0x65,
0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x69, 0x6d, 0x65,
0x5f, 0x68, 0x6f, 0x75, 0x72, 0x73, 0x5f, 0x61, 0x67, 0x6f, 0x22, 0x3a,
0x20, 0x22, 0x76, 0x6f, 0x72, 0x20, 0x25, 0x64, 0x20, 0x53, 0x74, 0x75,
0x6e, 0x64, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22,
0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x75, 0x74, 0x65, 0x73,
0x5f, 0x61, 0x67, 0x6f, 0x22, 0x3a, 0x20, 0x22, 0x76, 0x6f, 0x72, 0x20,
0x25, 0x64, 0x20, 0x4d, 0x69, 0x6e, 0x75, 0x74, 0x65, 0x6e, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x73,
0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x5f, 0x61, 0x67, 0x6f, 0x22, 0x3a,
0x20, 0x22, 0x76, 0x6f, 0x72, 0x20, 0x25, 0x64, 0x20, 0x53, 0x65, 0x6b,
0x5f, 0x64, 0x61, 0x79, 0x73, 0x5f, 0x61, 0x67, 0x6f, 0x22, 0x3a, 0x20,
0x22, 0x76, 0x6f, 0x72, 0x20, 0x25, 0x64, 0x20, 0x54, 0x61, 0x67, 0x65,
0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x69, 0x6d,
0x65, 0x5f, 0x68, 0x6f, 0x75, 0x72, 0x73, 0x5f, 0x61, 0x67, 0x6f, 0x22,
0x3a, 0x20, 0x22, 0x76, 0x6f, 0x72, 0x20, 0x25, 0x64, 0x20, 0x53, 0x74,
0x75, 0x6e, 0x64, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x75, 0x74, 0x65,
0x73, 0x5f, 0x61, 0x67, 0x6f, 0x22, 0x3a, 0x20, 0x22, 0x76, 0x6f, 0x72,
0x20, 0x25, 0x64, 0x20, 0x4d, 0x69, 0x6e, 0x75, 0x74, 0x65, 0x6e, 0x22,
0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x69, 0x6d, 0x65, 0x5f,
0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x5f, 0x61, 0x67, 0x6f, 0x22,
0x3a, 0x20, 0x22, 0x76, 0x6f, 0x72, 0x20, 0x25, 0x64, 0x20, 0x53, 0x65,
0x6b, 0x75, 0x6e, 0x64, 0x65, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x22, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x31, 0x35,
0x6d, 0x69, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x31, 0x35, 0x20, 0x4d, 0x69,
0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x69, 0x6d,
0x65, 0x6f, 0x75, 0x74, 0x5f, 0x31, 0x68, 0x6f, 0x75, 0x72, 0x22, 0x3a,
0x20, 0x22, 0x31, 0x20, 0x53, 0x74, 0x75, 0x6e, 0x64, 0x65, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75,
0x74, 0x5f, 0x31, 0x6d, 0x69, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x31, 0x20,
0x4d, 0x69, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74,
0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x33, 0x30, 0x6d, 0x69, 0x6e,
0x22, 0x3a, 0x20, 0x22, 0x33, 0x30, 0x20, 0x4d, 0x69, 0x6e, 0x22, 0x2c,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75,
0x74, 0x5f, 0x35, 0x6d, 0x69, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x35, 0x20,
0x4d, 0x69, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74,
0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x6f, 0x66, 0x66, 0x22, 0x3a,
0x20, 0x22, 0x41, 0x75, 0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x22, 0x74, 0x6f, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x6e, 0x22, 0x2c, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6f, 0x5f, 0x75, 0x70, 0x70, 0x65,
0x72, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x4e, 0x22, 0x2c, 0x0a, 0x20, 0x20,
@@ -3402,4 +3419,4 @@ unsigned char res_lang_de_json[] = {
0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x5a, 0x2d, 0x41, 0x64, 0x72,
0x65, 0x73, 0x73, 0x65, 0x6e, 0x22, 0x0a, 0x7d, 0x0a
};
unsigned int res_lang_de_json_len = 40821;
unsigned int res_lang_de_json_len = 41025;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -73,6 +73,16 @@ bool RPCClient::connect(const std::string& host, const std::string& port,
impl_->url = "http://" + host + ":" + port + "/";
VERBOSE_LOGF("Connecting to dragonxd at %s\n", impl_->url.c_str());
// Clean up previous curl handle/headers to avoid leaks on retries
if (impl_->headers) {
curl_slist_free_all(impl_->headers);
impl_->headers = nullptr;
}
if (impl_->curl) {
curl_easy_cleanup(impl_->curl);
impl_->curl = nullptr;
}
// Initialize curl handle
impl_->curl = curl_easy_init();
if (!impl_->curl) {
@@ -81,7 +91,7 @@ bool RPCClient::connect(const std::string& host, const std::string& port,
}
// Set up headers - daemon expects text/plain, not application/json
impl_->headers = curl_slist_append(impl_->headers, "Content-Type: text/plain");
impl_->headers = curl_slist_append(nullptr, "Content-Type: text/plain");
std::string auth_header = "Authorization: Basic " + auth_;
impl_->headers = curl_slist_append(impl_->headers, auth_header.c_str());
@@ -97,6 +107,8 @@ bool RPCClient::connect(const std::string& host, const std::string& port,
json result = call("getinfo");
if (result.contains("version")) {
connected_ = true;
warming_up_ = false;
warmup_status_.clear();
last_connect_error_.clear();
DEBUG_LOGF("Connected to dragonxd v%d\n", result["version"].get<int>());
return true;
@@ -104,7 +116,9 @@ bool RPCClient::connect(const std::string& host, const std::string& port,
} catch (const std::exception& e) {
last_connect_error_ = e.what();
// Daemon warmup messages (Loading block index, Verifying blocks, etc.)
// are normal startup progress — don't label them "Connection failed".
// are normal startup progress — the daemon is reachable and auth works,
// it just hasn't finished initializing yet. Mark as connected+warmup
// so the wallet can show the UI instead of a blocking overlay.
std::string msg = e.what();
bool isWarmup = (msg.find("Loading") != std::string::npos ||
msg.find("Verifying") != std::string::npos ||
@@ -113,13 +127,19 @@ bool RPCClient::connect(const std::string& host, const std::string& port,
msg.find("Rescanning") != std::string::npos ||
msg.find("Pruning") != std::string::npos);
if (isWarmup) {
DEBUG_LOGF("Daemon starting: %s\n", msg.c_str());
connected_ = true;
warming_up_ = true;
warmup_status_ = msg;
DEBUG_LOGF("Daemon warming up: %s\n", msg.c_str());
return true;
} else {
DEBUG_LOGF("Connection failed: %s\n", msg.c_str());
}
}
connected_ = false;
warming_up_ = false;
warmup_status_.clear();
return false;
}
@@ -127,6 +147,8 @@ void RPCClient::disconnect()
{
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
connected_ = false;
warming_up_ = false;
warmup_status_.clear();
if (impl_->curl) {
curl_easy_cleanup(impl_->curl);
impl_->curl = nullptr;

View File

@@ -53,6 +53,19 @@ public:
*/
bool isConnected() const { return connected_; }
/**
* @brief True if the last connect() succeeded but daemon returned a warmup error.
* The curl handle is valid and auth succeeded — RPC calls will throw warmup errors
* until the daemon finishes initializing.
*/
bool isWarmingUp() const { return warming_up_; }
/**
* @brief The warmup status message (e.g. "Activating best chain...").
* Empty when not in warmup.
*/
const std::string& getWarmupStatus() const { return warmup_status_; }
/**
* @brief Get the error message from the last failed connect() attempt.
*/
@@ -182,6 +195,8 @@ private:
std::string port_;
std::string auth_; // Base64 encoded "user:password"
bool connected_ = false;
bool warming_up_ = false;
std::string warmup_status_;
std::string last_connect_error_;
mutable std::recursive_mutex curl_mutex_; // serializes all curl handle access

View File

@@ -33,13 +33,13 @@ I18n::I18n()
// Cyrillic glyphs are included in the Ubuntu font glyph ranges.
registerLanguage("en", "English");
registerLanguage("es", "Español");
registerLanguage("zh", "Chinese");
registerLanguage("ru", "Russian");
registerLanguage("zh", "中文");
registerLanguage("ru", "Русский");
registerLanguage("de", "Deutsch");
registerLanguage("fr", "Français");
registerLanguage("pt", "Português");
registerLanguage("ja", "Japanese");
registerLanguage("ko", "Korean");
registerLanguage("ja", "日本語");
registerLanguage("ko", "한국어");
// Load default English strings (built-in fallback)
loadLanguage("en");