feat(shield): pick the mining address from a dropdown + show a confirm preview
Shield-coinbase replaced its free-text "*"-wildcard address box with a dropdown
of the wallet's mining (transparent) addresses and their balances ("All mining
addresses" default), and the confirm step now previews "Shield up to X DRGX from
N mining address(es)… to <label>? Network fee: Y" instead of only relabeling the
button. Also blocks submitting from an empty source or one with no funds. The
z_shieldcoinbase call (args/fee/limit/destination) is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -204,6 +204,25 @@ static double srcAmount() { return s_src == 0 ? s_t_amount : s_src == 1 ? s_z_am
|
||||
|
||||
static std::string fmtAmt(double v) { char b[48]; std::snprintf(b, sizeof(b), "%.4f", v); return b; }
|
||||
|
||||
// Shield-coinbase source helpers. "*" = all spendable mining (transparent) addresses; else one address.
|
||||
// Balances are DISPLAY totals (may include immature/non-coinbase), so the UI wording says "up to".
|
||||
static double miningBalance(const WalletState& st, const char* from) {
|
||||
if (std::strcmp(from, "*") == 0) {
|
||||
double sum = 0.0;
|
||||
for (const auto& a : st.t_addresses) if (a.isSpendable()) sum += a.balance;
|
||||
return sum;
|
||||
}
|
||||
const int i = addressIndexByString(st.t_addresses, from);
|
||||
return i >= 0 ? st.t_addresses[i].balance : 0.0;
|
||||
}
|
||||
static int miningAddrCount(const WalletState& st, const char* from) {
|
||||
if (std::strcmp(from, "*") == 0) {
|
||||
int n = 0; for (const auto& a : st.t_addresses) if (a.isSpendable() && a.balance > 0.0) ++n; return n;
|
||||
}
|
||||
const int i = addressIndexByString(st.t_addresses, from);
|
||||
return (i >= 0 && st.t_addresses[i].balance > 0.0) ? 1 : 0;
|
||||
}
|
||||
|
||||
// A caption-style line that wraps at the dialog edge (Type().textColored doesn't wrap).
|
||||
static void wrappedCaption(ImU32 color, const char* text)
|
||||
{
|
||||
@@ -567,8 +586,32 @@ void ShieldDialog::render(App* app)
|
||||
|
||||
// ── Shield coinbase: from address ───────────────────────────────────────────────────────────
|
||||
if (!isMerge && !opInFlight) {
|
||||
material::LabeledInput(TR("shield_from_address"), "##FromAddr", s_from_address, sizeof(s_from_address));
|
||||
ImGui::TextDisabled("%s", TR("shield_wildcard_hint"));
|
||||
// Pick the mining (transparent) address to shield from a dropdown with balances, instead of a
|
||||
// free-text box with a "*" wildcard. "*" shields all of them.
|
||||
ImGui::TextUnformatted(TR("shield_from_address"));
|
||||
const bool allFrom = std::strcmp(s_from_address, "*") == 0;
|
||||
const std::string fromLbl = allFrom ? std::string(TR("shield_all_mining_addrs"))
|
||||
: (app->getAddressLabel(s_from_address).empty() ? shortAddr(s_from_address)
|
||||
: app->getAddressLabel(s_from_address));
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
if (ImGui::BeginCombo("##FromAddr", fromLbl.c_str())) {
|
||||
if (ImGui::Selectable(TR("shield_all_mining_addrs"), allFrom))
|
||||
strncpy(s_from_address, "*", sizeof(s_from_address));
|
||||
for (const auto& a : state.t_addresses) {
|
||||
if (!a.isSpendable()) continue;
|
||||
const std::string lbl = app->getAddressLabel(a.address);
|
||||
char item[192];
|
||||
std::snprintf(item, sizeof(item), "%s (%s DRGX)",
|
||||
lbl.empty() ? shortAddr(a.address).c_str() : lbl.c_str(),
|
||||
fmtAmt(a.balance).c_str());
|
||||
const bool sel = std::strcmp(s_from_address, a.address.c_str()) == 0;
|
||||
if (ImGui::Selectable(item, sel)) {
|
||||
strncpy(s_from_address, a.address.c_str(), sizeof(s_from_address) - 1);
|
||||
s_from_address[sizeof(s_from_address) - 1] = '\0';
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
@@ -833,9 +876,24 @@ void ShieldDialog::render(App* app)
|
||||
fmtAmt(srcAmount()).c_str(), srcCount(), shortAddr(s_to_address).c_str());
|
||||
ImGui::TextWrapped("%s", cb);
|
||||
ImGui::Spacing();
|
||||
} else if (s_confirm && !isMerge) {
|
||||
// Shield-coinbase preview: amount / address count / fee / destination (no amount/count was shown before).
|
||||
const bool allFrom = std::strcmp(s_from_address, "*") == 0;
|
||||
const std::string toLbl = app->getAddressLabel(s_to_address);
|
||||
char cb[360];
|
||||
std::snprintf(cb, sizeof(cb), TR("shield_confirm_fmt"),
|
||||
fmtAmt(miningBalance(state, s_from_address)).c_str(),
|
||||
allFrom ? miningAddrCount(state, s_from_address) : 1,
|
||||
s_utxo_limit,
|
||||
toLbl.empty() ? shortAddr(s_to_address).c_str() : toLbl.c_str(),
|
||||
fmtAmt(s_fee).c_str());
|
||||
ImGui::TextWrapped("%s", cb);
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
bool can_submit = haveDest && connected && !syncing;
|
||||
// Block a shield from an empty source or one with no funds (the free-text box allowed both).
|
||||
if (!isMerge && (s_from_address[0] == '\0' || miningBalance(state, s_from_address) <= 0.0)) can_submit = false;
|
||||
const bool scopeLoaded = s_scan.phase == services::ScopePhase::Loaded;
|
||||
if (isMerge && scopeLoaded && srcCount() == 0) can_submit = false;
|
||||
if (perAddress) can_submit = connected && !syncing && scopeLoaded && !selectedGroups().empty();
|
||||
|
||||
+3
-1
@@ -2621,7 +2621,9 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["shield_check_status"] = "Check Status";
|
||||
strings_["shield_completed"] = "Operation completed successfully!";
|
||||
strings_["shield_description"] = "Shield your mining rewards by sending coinbase outputs from transparent addresses to a shielded address. This improves privacy by hiding your mining income.";
|
||||
strings_["shield_from_address"] = "From Address:";
|
||||
strings_["shield_from_address"] = "From mining address:";
|
||||
strings_["shield_all_mining_addrs"] = "All mining addresses";
|
||||
strings_["shield_confirm_fmt"] = "Shield up to %s DRGX from %d mining address(es) (at most %d rewards per run) to %s? Network fee: %s DRGX.";
|
||||
strings_["shield_funds"] = "Shield Funds";
|
||||
strings_["shield_in_progress"] = "Operation in progress...";
|
||||
strings_["shield_max_utxos"] = "Max UTXOs per operation";
|
||||
|
||||
Reference in New Issue
Block a user