feat(mining): explain solo/pool, add a payout-address create path, surface the miner download
New-user mining cluster from the UX audit: - Solo/Pool toggle: hover tooltips on each half explaining the trade-off (solo = no fees but long waits on a small CPU; pool = frequent small payouts, needs the miner + a shielded payout address). - Payout field: when it's empty (new wallets have no z-address, and a pool payout must be shielded) the bookmark button is replaced by a one-click "Generate a shielded payout address" button (createNewZAddress → fills the field, persists, toasts), instead of only a hover hint to the Receive tab. - Miner updater pill: when no miner is installed it becomes a prominent accent "Download miner" call-to-action with a "pool mining needs DRG-XMRig" tooltip, instead of a subtle grey "Update" pill that's easy to miss. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -579,6 +579,9 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
|
||||
std::string curVer = s_live_miner_ver;
|
||||
if (curVer.empty()) curVer = app->poolMiningInstalledVersion();
|
||||
if (curVer.empty()) curVer = app->settings()->getXmrigVersion();
|
||||
// No miner installed at all → pool mining can't run. Make this a prominent
|
||||
// "Download miner" call-to-action instead of a subtle grey "Update" pill.
|
||||
const bool minerMissing = curVer.empty();
|
||||
// Version state: subtle GREEN + "xmrig releases" when up to date, subtle ORANGE +
|
||||
// "Update <latest>" when an update is available, neutral when either version is unknown.
|
||||
auto verNum = [](std::string s) -> std::vector<int> {
|
||||
@@ -609,7 +612,9 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
|
||||
const ImU32 subtleOrange = IM_COL32(214, 158, 74, 255);
|
||||
|
||||
char xbtn[64];
|
||||
if (upToDate)
|
||||
if (minerMissing)
|
||||
snprintf(xbtn, sizeof(xbtn), "%s", TR("xmrig_download_miner"));
|
||||
else if (upToDate)
|
||||
snprintf(xbtn, sizeof(xbtn), "%s", TR("xmrig_releases"));
|
||||
else if (!s_xmrig_latest_tag.empty())
|
||||
snprintf(xbtn, sizeof(xbtn), "%s %s", TR("xmrig_update_short"), s_xmrig_latest_tag.c_str());
|
||||
@@ -629,18 +634,21 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
|
||||
bool xhov = ImGui::IsItemHovered();
|
||||
bool xclk = ImGui::IsItemClicked();
|
||||
ImU32 xpill = minerBusy ? WithAlpha(OnSurface(), 12)
|
||||
: (xhov ? StateHover() : WithAlpha(OnSurface(), 28));
|
||||
: (minerMissing ? WithAlpha(Primary(), xhov ? 70 : 45)
|
||||
: (xhov ? StateHover() : WithAlpha(OnSurface(), 28)));
|
||||
dl->AddRectFilled(ImVec2(xbtnX, xbtnY), ImVec2(xbtnX + xbtnW, xbtnY + xbtnH),
|
||||
xpill, xbtnH * 0.3f);
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(xbtnX + xpadX, xbtnY + (xbtnH - xlblSz.y) * 0.5f),
|
||||
minerBusy ? OnSurfaceDisabled()
|
||||
: (upToDate ? subtleGreen : outdated ? subtleOrange : OnSurfaceMedium()),
|
||||
: (minerMissing ? Primary()
|
||||
: (upToDate ? subtleGreen : outdated ? subtleOrange : OnSurfaceMedium())),
|
||||
xbtn);
|
||||
if (xhov) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
material::Tooltip("%s", minerBusy ? TR("xmrig_stop_mining_first")
|
||||
: TR("xmrig_update_title"));
|
||||
: (minerMissing ? TR("xmrig_download_miner_tip")
|
||||
: TR("xmrig_update_title")));
|
||||
}
|
||||
if (xclk && !minerBusy) XmrigDownloadDialog::show(app);
|
||||
|
||||
|
||||
@@ -139,6 +139,8 @@ void RenderMiningModeToggle(App* app, const WalletState& state, const MiningInfo
|
||||
if (soloMiningAvailable && poolHov && !soloMiningActive) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
if (poolHov && soloMiningActive && !s_pool_mode) {
|
||||
material::Tooltip("%s", TR("mining_stop_solo_for_pool"));
|
||||
} else if (poolHov && !soloMiningActive) {
|
||||
material::Tooltip("%s", TR("mining_pool_tip"));
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(tMin.x, tMax.y));
|
||||
@@ -439,21 +441,51 @@ void RenderMiningModeToggle(App* app, const WalletState& state, const MiningInfo
|
||||
}
|
||||
}
|
||||
|
||||
// --- Worker: Bookmark button ---
|
||||
// --- Worker: Generate (when empty) / Bookmark button ---
|
||||
// A new wallet has no z-address, and a pool payout MUST be shielded. When the field is empty
|
||||
// show a one-click "generate a shielded address" button in place of the (useless-when-empty)
|
||||
// bookmark, instead of only a hover hint pointing at the Receive tab.
|
||||
ImGui::SameLine(0, 0);
|
||||
{
|
||||
std::string currentWorker(s_pool_worker);
|
||||
bool alreadySaved = miningValueAlreadySaved(app->settings()->getSavedPoolWorkers(), currentWorker);
|
||||
material::IconButtonStyle st;
|
||||
st.color = alreadySaved ? Primary() : OnSurfaceMedium();
|
||||
st.hoverBg = StateHover();
|
||||
st.bgRounding = 4.0f * dp;
|
||||
st.tooltip = alreadySaved ? TR("mining_already_saved") : TR("mining_save_payout_address");
|
||||
if (material::IconButton("##SaveWorkerAddr", alreadySaved ? ICON_MD_BOOKMARK : ICON_MD_BOOKMARK_BORDER,
|
||||
Type().iconSmall(), ImVec2(iconBtnW, inputFrameH2), st)
|
||||
&& !currentWorker.empty() && currentWorker != "x" && !alreadySaved) {
|
||||
app->settings()->addSavedPoolWorker(currentWorker);
|
||||
app->settings()->save();
|
||||
const bool noAddr = currentWorker.empty() || currentWorker == "x";
|
||||
if (noAddr) {
|
||||
material::IconButtonStyle st;
|
||||
st.color = Primary();
|
||||
st.hoverBg = StateHover();
|
||||
st.bgRounding = 4.0f * dp;
|
||||
st.tooltip = TR("mining_generate_z_address");
|
||||
if (material::IconButton("##GenZAddr", ICON_MD_ADD, Type().iconSmall(),
|
||||
ImVec2(iconBtnW, inputFrameH2), st)) {
|
||||
if (!app->isLiteBuild() && !app->isConnected()) {
|
||||
ui::Notifications::instance().warning(TR("toast_not_connected_daemon"));
|
||||
} else {
|
||||
char* wp = &s_pool_worker[0]; const size_t wn = sizeof(s_pool_worker);
|
||||
app->createNewZAddress([app, wp, wn](const std::string& addr) {
|
||||
if (addr.empty()) {
|
||||
ui::Notifications::instance().error(TR("mining_generate_z_failed"));
|
||||
return;
|
||||
}
|
||||
snprintf(wp, wn, "%s", addr.c_str()); // wp → the static s_pool_worker storage
|
||||
app->settings()->setPoolWorker(addr);
|
||||
app->settings()->save();
|
||||
ui::Notifications::instance().success(TR("mining_generate_z_ok"));
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bool alreadySaved = miningValueAlreadySaved(app->settings()->getSavedPoolWorkers(), currentWorker);
|
||||
material::IconButtonStyle st;
|
||||
st.color = alreadySaved ? Primary() : OnSurfaceMedium();
|
||||
st.hoverBg = StateHover();
|
||||
st.bgRounding = 4.0f * dp;
|
||||
st.tooltip = alreadySaved ? TR("mining_already_saved") : TR("mining_save_payout_address");
|
||||
if (material::IconButton("##SaveWorkerAddr", alreadySaved ? ICON_MD_BOOKMARK : ICON_MD_BOOKMARK_BORDER,
|
||||
Type().iconSmall(), ImVec2(iconBtnW, inputFrameH2), st)
|
||||
&& !alreadySaved) {
|
||||
app->settings()->addSavedPoolWorker(currentWorker);
|
||||
app->settings()->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2289,6 +2289,9 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["validate_mining_type"] = "Mining address (transparent)";
|
||||
strings_["mining_est_daily_pool_sub"] = "rough solo-equivalent, before pool fee";
|
||||
strings_["mining_generate_z_address_hint"] = "Generate a Z address in the Receive tab to use as your payout address";
|
||||
strings_["mining_generate_z_address"] = "Generate a shielded payout address";
|
||||
strings_["mining_generate_z_ok"] = "Created a shielded payout address.";
|
||||
strings_["mining_generate_z_failed"] = "Couldn't create a shielded address — try the Receive tab.";
|
||||
strings_["mining_pool_needs_payout_tooltip"] = "Enter a payout address first (generate a Z address)";
|
||||
strings_["mining_pool"] = "Pool";
|
||||
strings_["mining_payout_foreign"] = "⚠ This payout address isn't in your current wallet — mined rewards would go to a different wallet. Update it if you switched wallets.";
|
||||
@@ -2328,6 +2331,8 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["mining_starting_tooltip"] = "Miner is starting...";
|
||||
strings_["mining_stop"] = "Stop";
|
||||
strings_["mining_stop_solo_for_pool"] = "Stop solo mining before starting pool mining";
|
||||
strings_["mining_solo_tip"] = "Solo: your node mines on its own. No pool fees, but a small CPU may wait a long time between rewards.";
|
||||
strings_["mining_pool_tip"] = "Pool: team up with other miners for smaller, more frequent payouts. Needs the DRG-XMRig miner and a shielded payout address.";
|
||||
strings_["mining_stop_solo_for_pool_settings"] = "Stop solo mining to change pool settings";
|
||||
strings_["mining_stopping"] = "Stopping...";
|
||||
strings_["mining_stopping_tooltip"] = "Miner is stopping...";
|
||||
@@ -2339,6 +2344,8 @@ void I18n::loadBuiltinEnglish()
|
||||
|
||||
// --- Miner (xmrig) updater ---
|
||||
strings_["xmrig_update_button"] = "Update miner…";
|
||||
strings_["xmrig_download_miner"] = "Download miner";
|
||||
strings_["xmrig_download_miner_tip"] = "Pool mining needs the DRG-XMRig miner. Click to download and install it.";
|
||||
strings_["xmrig_update_short"] = "Update";
|
||||
strings_["xmrig_releases"] = "xmrig releases";
|
||||
strings_["xmrig_current"] = "Current:";
|
||||
|
||||
Reference in New Issue
Block a user