diff --git a/src/ui/windows/market_tab.cpp b/src/ui/windows/market_tab.cpp index 6363543..377b913 100644 --- a/src/ui/windows/market_tab.cpp +++ b/src/ui/windows/market_tab.cpp @@ -29,8 +29,7 @@ namespace ui { using namespace material; // ---- Market tab persistent state ---- -static std::vector s_price_history; -static std::vector s_time_history; +static std::vector s_price_history; // mirror of WalletState market.price_history static bool s_history_initialized = false; static double s_last_refresh_time = 0.0; @@ -322,18 +321,11 @@ void RenderMarketTab(App* app) // PRICE CHART — Custom drawn inside glass panel (matches app design) // ================================================================ { - // Initialize history with simulated data if not set - if (!s_history_initialized && market.price_usd > 0) { - s_price_history.clear(); - s_time_history.clear(); - double base = market.price_usd; - for (int i = 0; i < 24; i++) { - double variance = ((rand() % 1000) - 500) / 10000.0 * base; - s_price_history.push_back(base + variance); - s_time_history.push_back(static_cast(i)); - } - s_history_initialized = true; - } + // Plot the REAL accumulated price history (one point per refresh, oldest→newest). + // No synthetic data: until at least two real samples exist, the empty-state below + // ("not enough history yet") is shown rather than inventing a price curve. + s_price_history = market.price_history; + s_history_initialized = true; // Chart height from schema float chartH = std::max(60.0f, chartElem.height * vs); @@ -411,19 +403,14 @@ void RenderMarketTab(App* app) dl->AddCircleFilled(points[i], dotR, dotCol); } - // X-axis labels - const int xlabels[] = {0, 6, 12, 18, 23}; - const char* xlabelText[] = {TR("market_24h"), TR("market_18h"), TR("market_12h"), TR("market_6h"), TR("market_now")}; - for (int xi = 0; xi < 5; xi++) { - int idx = xlabels[xi]; - float t = (float)idx / (float)(n - 1); - float x = plotLeft + t * plotW; - ImVec2 lblSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, xlabelText[xi]); - float lx = x - lblSz.x * 0.5f; - if (lx < plotLeft) lx = plotLeft; - if (lx + lblSz.x > plotRight) lx = plotRight - lblSz.x; + // X-axis: samples are per-refresh, not hourly-timestamped, so only the most + // recent point can be labelled truthfully ("Now"). The series simply runs + // older→newer, left→right. (Avoids the old fake "24h…6h" hourly gradations.) + { + const char* nowText = TR("market_now"); + ImVec2 lblSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, nowText); dl->AddText(capFont, capFont->LegacySize, - ImVec2(lx, plotBottom + 2), OnSurfaceDisabled(), xlabelText[xi]); + ImVec2(plotRight - lblSz.x, plotBottom + 2), OnSurfaceDisabled(), nowText); } // Hover crosshair + tooltip @@ -450,8 +437,9 @@ void RenderMarketTab(App* app) dl->AddCircleFilled(ImVec2(px, py), hoverDotR, dotCol); dl->AddCircle(ImVec2(px, py), hoverRingR, IM_COL32(255, 255, 255, 80), 0, 1.5f); - snprintf(buf, sizeof(buf), "%dh ago: %s", - 24 - idx, FormatPrice(s_price_history[idx]).c_str()); + // Samples are per-refresh, not timestamped, so we don't claim a specific + // "Xh ago" — just show the price at the hovered point. + snprintf(buf, sizeof(buf), "%s", FormatPrice(s_price_history[idx]).c_str()); ImVec2 tipSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, buf); float tipPad = Layout::spacingSm() + Layout::spacingXs(); float tipX = px + 10; @@ -843,8 +831,10 @@ void RenderMarketTab(App* app) WithAlpha(Warning(), 200), shieldedW > 0.5f ? ImDrawFlags_RoundCornersRight : ImDrawFlags_RoundCornersAll, 3.0f); - int pct = (int)(shieldedRatio * 100.0f + 0.5f); - snprintf(buf, sizeof(buf), TR("market_pct_shielded"), pct); + // market_pct_shielded is "%.0f%% Shielded" — pass a double, not an int (passing + // an int to a %f conversion is UB and renders garbage on x86-64). + snprintf(buf, sizeof(buf), TR("market_pct_shielded"), + static_cast(shieldedRatio) * 100.0); dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy + barH + 2), OnSurfaceDisabled(), buf); } diff --git a/src/ui/windows/peers_tab.cpp b/src/ui/windows/peers_tab.cpp index 406ab7e..539b204 100644 --- a/src/ui/windows/peers_tab.cpp +++ b/src/ui/windows/peers_tab.cpp @@ -507,8 +507,10 @@ void RenderPeersTab(App* app) float toggleY = ImGui::GetCursorScreenPos().y; { char connLabel[64], banLabel[64]; - snprintf(connLabel, sizeof(connLabel), TR("peers_connected_count"), state.peers.size()); - snprintf(banLabel, sizeof(banLabel), TR("peers_banned_count"), state.bannedPeers.size()); + // Format strings use %d — cast size_t down to int (a 64-bit size_t passed to %d + // is a format/type mismatch / UB; peer counts comfortably fit in int). + snprintf(connLabel, sizeof(connLabel), TR("peers_connected_count"), (int)state.peers.size()); + snprintf(banLabel, sizeof(banLabel), TR("peers_banned_count"), (int)state.bannedPeers.size()); ImVec2 connSz = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, connLabel); ImVec2 banSz = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, banLabel); @@ -822,9 +824,10 @@ void RenderPeersTab(App* app) TTRow(TR("peers_tt_services"), peer.services.c_str()); snprintf(ttBuf, sizeof(ttBuf), "%d", peer.startingheight); TTRow(TR("peers_tt_start_height"), ttBuf); - snprintf(ttBuf, sizeof(ttBuf), "%ld bytes", peer.bytessent); + // %lld + (long long) — %ld truncates int64_t on Windows (LLP64). + snprintf(ttBuf, sizeof(ttBuf), "%lld bytes", (long long)peer.bytessent); TTRow(TR("peers_tt_sent"), ttBuf); - snprintf(ttBuf, sizeof(ttBuf), "%ld bytes", peer.bytesrecv); + snprintf(ttBuf, sizeof(ttBuf), "%lld bytes", (long long)peer.bytesrecv); TTRow(TR("peers_tt_received"), ttBuf); snprintf(ttBuf, sizeof(ttBuf), "%d / %d", peer.synced_headers, peer.synced_blocks); TTRow(TR("peers_tt_synced"), ttBuf); diff --git a/src/ui/windows/receive_tab.cpp b/src/ui/windows/receive_tab.cpp index 373075f..0af8f37 100644 --- a/src/ui/windows/receive_tab.cpp +++ b/src/ui/windows/receive_tab.cpp @@ -106,8 +106,11 @@ static void TrackNewAddresses(const WalletState& state) { static void RenderSyncBanner(const WalletState& state) { if (!state.sync.syncing || state.sync.isSynced()) return; - float syncPct = (state.sync.headers > 0) - ? (float)state.sync.blocks / state.sync.headers * 100.0f : 0.0f; + // Use the work-weighted verification_progress like every other surface (not the naive + // blocks/headers ratio, which over-reports during early sync). + double vp = state.sync.verification_progress; + if (vp < 0.0) vp = 0.0; else if (vp > 1.0) vp = 1.0; + float syncPct = (float)(vp * 100.0); char syncBuf[128]; snprintf(syncBuf, sizeof(syncBuf), TR("blockchain_syncing"), syncPct); diff --git a/src/ui/windows/send_tab.cpp b/src/ui/windows/send_tab.cpp index 2106dcb..ca0ff2a 100644 --- a/src/ui/windows/send_tab.cpp +++ b/src/ui/windows/send_tab.cpp @@ -191,8 +191,12 @@ static void PasteClipboardToAddress() { static void RenderSyncBanner(const WalletState& state) { if (!state.sync.syncing || state.sync.isSynced()) return; - float syncPct = (state.sync.headers > 0) - ? (float)state.sync.blocks / state.sync.headers * 100.0f : 0.0f; + // Use the work-weighted verification_progress like every other surface (status bar, + // balance, explorer, network, mining). The naive blocks/headers ratio over-reports + // during early sync — misleading on the very screen where the user is about to spend. + double vp = state.sync.verification_progress; + if (vp < 0.0) vp = 0.0; else if (vp > 1.0) vp = 1.0; + float syncPct = (float)(vp * 100.0); char syncBuf[128]; snprintf(syncBuf, sizeof(syncBuf), TR("blockchain_syncing"), syncPct); diff --git a/src/ui/windows/transactions_tab.cpp b/src/ui/windows/transactions_tab.cpp index b63af62..55185ab 100644 --- a/src/ui/windows/transactions_tab.cpp +++ b/src/ui/windows/transactions_tab.cpp @@ -164,7 +164,30 @@ void RenderTransactionsTab(App* app) int recvCount = 0, sendCount = 0, minedCount = 0; double recvTotal = 0.0, sendTotal = 0.0, minedTotal = 0.0; - for (const auto& tx : state.transactions) { + // Identify autoshield legs (same txid with a "send" leg and a "receive"-to-z leg): + // that pair is a single internal shielding move — shown as one "Shield" row in the + // list below — not income or spending. Counting both legs double-counts the amount + // into BOTH the Sent and Received totals, so the cards disagree with the list. + // Mirror the list's pairing logic and exclude both legs from the totals. + std::unordered_map> summaryTxidMap; + for (size_t i = 0; i < state.transactions.size(); i++) + summaryTxidMap[state.transactions[i].txid].push_back(i); + std::vector isShieldLeg(state.transactions.size(), false); + for (const auto& kv : summaryTxidMap) { + if (kv.second.size() < 2) continue; + int send_i = -1, recv_i = -1; + for (size_t si : kv.second) { + const auto& stx = state.transactions[si]; + if (stx.type == "send" && send_i < 0) send_i = (int)si; + else if (stx.type == "receive" && recv_i < 0 && + !stx.address.empty() && stx.address[0] == 'z') recv_i = (int)si; + } + if (send_i >= 0 && recv_i >= 0) { isShieldLeg[send_i] = true; isShieldLeg[recv_i] = true; } + } + + for (size_t i = 0; i < state.transactions.size(); i++) { + if (isShieldLeg[i]) continue; // internal shielding move — not received or sent + const auto& tx = state.transactions[i]; if (tx.type == "receive") { recvCount++; recvTotal += std::abs(tx.amount);