fix(balance): stop the displayed balance cratering during a pending shielded send
Sending a small amount from an address holding a large balance made the displayed balance collapse to ~0 until the tx confirmed. A shielded spend consumes the whole source note; the change returns as a 0-confirmation note, and every balance query used the default minconf=1 — so the spent note dropped out and the change wasn't counted yet. Split every balance into two views: - DISPLAY (balance / privateBalance / transparentBalance / totalBalance) — now queried at minconf=0, so it INCLUDES the user's own pending change and no longer craters. This is what the Overview, balance tab, market portfolio and receive tab show. unconfirmedBalance is now populated (= total - spendable). - SPENDABLE (new spendableBalance / spendable*Balance) — confirmed (minconf>=1), what z_sendmany (run at minconf=1) can actually spend. The Send form's available/ Max/validation, the from-address selection, the drag-to-transfer dialog cap, the chat pay-from and the auto-shield gate all size off these, so they never offer 0-conf change the daemon would reject. Implementation: a single z_listunspent(0)/listunspent(0), partitioned per-note by "confirmations">=1; z_gettotalbalance called at minconf 0 (display) and 1 (spendable); the z_getbalance fallback queries both. applyPendingSendDelta (the optimistic post-send debit) now touches ONLY the spendable fields — debiting the display too would re-crater it on top of the honest minconf=0 RPC. Lite mirrors spendableBalance = balance (its per-address balance is already confirmed) so lite sends aren't zeroed. The confirmed-only gates (seed-migration/sweep z_gettotalbalance, sweep z_getbalance(addr,1), z_sendmany's minconf arg) are untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -137,13 +137,15 @@ static double GetAvailableBalance(App* app) {
|
||||
// not a stored list index. The index desyncs from s_from_address after an address-list
|
||||
// refresh, and is left at -1 when the source is chosen from another tab ("Send from this
|
||||
// address") — which previously made the sufficiency check see 0 and block a valid send.
|
||||
// CONFIRMED balance only — this is the spend ceiling (Max button, slider, validation, pre-broadcast
|
||||
// re-check). z_sendmany runs at minconf=1, so offering 0-conf change here would just make the send fail.
|
||||
if (s_from_address[0] != '\0') {
|
||||
for (const auto& a : state.addresses) {
|
||||
if (a.address == s_from_address) return a.balance;
|
||||
if (a.address == s_from_address) return a.spendableBalance;
|
||||
}
|
||||
}
|
||||
if (s_selected_from_idx >= 0 && s_selected_from_idx < static_cast<int>(state.addresses.size())) {
|
||||
return state.addresses[s_selected_from_idx].balance;
|
||||
return state.addresses[s_selected_from_idx].spendableBalance;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
@@ -252,7 +254,7 @@ static void RenderSourceDropdown(App* app, float width) {
|
||||
std::string trunc = util::truncateMiddle(addr.address,
|
||||
static_cast<int>(std::max(S.drawElement("tabs.send", "addr-preview-trunc-min").size, width / S.drawElement("tabs.send", "addr-preview-trunc-divisor").size)));
|
||||
snprintf(buf, sizeof(buf), "%s %s — %.8f %s",
|
||||
tag, trunc.c_str(), addr.balance, DRAGONX_TICKER);
|
||||
tag, trunc.c_str(), addr.spendableBalance, DRAGONX_TICKER);
|
||||
s_source_preview = buf;
|
||||
} else {
|
||||
s_source_preview = TR("send_select_source");
|
||||
@@ -282,7 +284,7 @@ static void RenderSourceDropdown(App* app, float width) {
|
||||
|
||||
std::string trunc = util::truncateMiddle(addr.address, (int)addrTruncLen);
|
||||
snprintf(buf, sizeof(buf), "%s %s — %.8f %s",
|
||||
tag, trunc.c_str(), addr.balance, DRAGONX_TICKER);
|
||||
tag, trunc.c_str(), addr.spendableBalance, DRAGONX_TICKER);
|
||||
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
if (ImGui::Selectable(buf, isCurrent)) {
|
||||
@@ -292,7 +294,7 @@ static void RenderSourceDropdown(App* app, float width) {
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
material::Tooltip("%s\nBalance: %.8f %s",
|
||||
addr.address.c_str(), addr.balance, DRAGONX_TICKER);
|
||||
addr.address.c_str(), addr.spendableBalance, DRAGONX_TICKER);
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user