feat(ui): treat transparent addresses as mining-only addresses
DragonX is a private chain (-ac_private=1): consensus rejects any
non-coinbase output to a transparent address, so a t-address can only
ever receive mining rewards, and its coins can only move to a shielded
address. The wallet treated them like ordinary recipients.
Block paying one:
- Send: a transparent recipient shows "Mining address - can't be paid"
and disables Review Send (tooltip explains; ask for a zs... address).
Typing, paste, contacts and payment URIs all go through this field.
- Overview drag-to-transfer: a mining-address row is never a transfer
target (it only reorders).
- Pool payout must be shielded (pools pay with ordinary transactions):
field tooltip, disabled Start button and the start check say why.
- isValidRecipientAddress() is now shielded-only; new isDragonXAddress()
and classifyRecipient() (Shielded / MiningOnly / Invalid).
Call them mining addresses:
- Overview: "Mining rewards" card (amber "Not yet shielded" when there
is a balance; hover explains; click opens the Shield dialog), "New
mining address" button with a tooltip, rows typed "Mining".
- QR / label dialogs, portfolio filter, contact badge ("Mining only"),
Receive notifications.
i18n: 17 new keys in all 8 languages, 12 dead keys removed, CJK subset
rebuilt. Tests: recipient classification, translation completeness.
UI sweep: new "send-mining-recipient" target.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+4
-2
@@ -2689,10 +2689,12 @@ void App::startPoolMining(int threads)
|
||||
// Validate the payout address at EVERY start entry point (manual Start button, idle auto-start, thread
|
||||
// scaling) — not just the UI gate — since a stale/hand-edited/wrong-chain address here silently loses
|
||||
// mining rewards. (M-01) worker_name IS the pool login the rewards are credited to (see below).
|
||||
// A pool pays with ordinary transactions, so only a shielded address can be paid (private chain).
|
||||
if (!cfg.worker_name.empty() && cfg.worker_name != "x" &&
|
||||
!util::isValidRecipientAddress(cfg.worker_name)) {
|
||||
ui::Notifications::instance().error(
|
||||
"Pool payout address is not a valid DragonX address — mining not started.");
|
||||
ui::Notifications::instance().error(util::isTransparentAddress(cfg.worker_name)
|
||||
? TR("mining_payout_needs_shielded")
|
||||
: "Pool payout address is not a valid DragonX address — mining not started.");
|
||||
return;
|
||||
}
|
||||
// The algo follows the pool: official pools use their own algo (pool.dragonx.cc
|
||||
|
||||
@@ -588,6 +588,11 @@ void App::buildSweepCatalog()
|
||||
[](App& a) { a.state_.connected = false; a.state_.encryption_state_known = false; },
|
||||
[](App& a) { a.applyHealthyDemoState(); });
|
||||
|
||||
// A mining (transparent) address as the recipient: shown as unpayable, Send disabled.
|
||||
add("send-mining-recipient", ui::NavPage::Send,
|
||||
[](App& a) { ui::SweepSetSendRecipient(&a, "R9NXAVJezHiBnT3ijTpg3JUZre7PxhJWti"); },
|
||||
[](App& a) { ui::SweepSetSendRecipient(&a, nullptr); });
|
||||
|
||||
// Send-confirm popup (state lives in send_tab statics → driven via a debug hook).
|
||||
add("popup-send-confirm", ui::NavPage::Send,
|
||||
[](App& a) { ui::SweepShowSendConfirm(&a, true); },
|
||||
|
||||
@@ -71,7 +71,7 @@ public:
|
||||
|
||||
// Address preview
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(),
|
||||
s_isZ ? TR("shielded_address") : TR("transparent_address"));
|
||||
s_isZ ? TR("shielded_address") : TR("mining_address"));
|
||||
ImGui::Spacing();
|
||||
{
|
||||
std::string display = s_address;
|
||||
|
||||
@@ -213,12 +213,13 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
});
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (TactileButton(TR("new_t_address"), ImVec2(buttonWidth, 0),
|
||||
if (TactileButton(TR("new_mining_address"), ImVec2(buttonWidth, 0),
|
||||
S.resolveFont(addrBtn.font.empty() ? "button" : addrBtn.font))) {
|
||||
app->createNewTAddress([](const std::string& addr) {
|
||||
DEBUG_LOGF("Created new t-address: %s\n", addr.c_str());
|
||||
});
|
||||
}
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_new_mining_address"));
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
@@ -402,13 +403,13 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
const auto& target = rows[s_dropTargetIdx];
|
||||
material::Tooltip("%s\n%s\n\n%s %s",
|
||||
truncateAddress(addr.address, 32).c_str(),
|
||||
row.isZ ? TR("shielded") : TR("transparent"),
|
||||
row.isZ ? TR("shielded") : TR("addr_type_mining"),
|
||||
TR("transfer_to"),
|
||||
truncateAddress(target.info->address, 32).c_str());
|
||||
} else {
|
||||
material::Tooltip("%s\n%s\n\n%s",
|
||||
truncateAddress(addr.address, 32).c_str(),
|
||||
row.isZ ? TR("shielded") : TR("transparent"),
|
||||
row.isZ ? TR("shielded") : TR("addr_type_mining"),
|
||||
TR("address_reorder_hint"));
|
||||
}
|
||||
}
|
||||
@@ -440,7 +441,8 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
if (s_dragActive && s_dragIdx != row_idx && material::IsRectHovered(rowPos, rowEnd)) {
|
||||
s_dropTargetIdx = row_idx;
|
||||
const float relY = rowH > 0.0f ? (mousePos.y - rowPos.y) / rowH : 0.5f;
|
||||
s_dropMode = (relY < 0.30f || relY > 0.70f) ? 1 : 0;
|
||||
// A mining (transparent) address can't be paid, so it is never a transfer target: reorder only.
|
||||
s_dropMode = (!row.isZ || relY < 0.30f || relY > 0.70f) ? 1 : 0;
|
||||
if (s_dropMode == 0) {
|
||||
// Transfer: highlight the whole target row.
|
||||
dl->AddRectFilled(rowPos, rowEnd, WithAlpha(Primary(), 40), 4.0f * dp);
|
||||
@@ -542,7 +544,7 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
// ---- Type label (first line) ----
|
||||
float labelX = rowLayout.labelX;
|
||||
{
|
||||
const char* typeLabel = row.isZ ? TR("shielded") : TR("transparent");
|
||||
const char* typeLabel = row.isZ ? TR("shielded") : TR("addr_type_mining");
|
||||
const char* hiddenTag = row.hidden ? TR("hidden_tag") : "";
|
||||
const char* miningTag = row.mining ? TR("mining_tag") : "";
|
||||
char typeBuf[64];
|
||||
@@ -682,7 +684,7 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
KeyExportDialog::show(addr.address, KeyExportDialog::KeyType::Viewing);
|
||||
}
|
||||
if (ImGui::MenuItem(TR("show_qr_code")))
|
||||
QRPopupDialog::show(addr.address, row.isZ ? TR("z_address") : TR("t_address"));
|
||||
QRPopupDialog::show(addr.address, row.isZ ? TR("z_address") : TR("mining_address"));
|
||||
ImGui::Separator();
|
||||
if (row.hidden) {
|
||||
if (ImGui::MenuItem(TR("restore_address")))
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "qr_popup_dialog.h"
|
||||
#include "address_label_dialog.h"
|
||||
#include "address_transfer_dialog.h"
|
||||
#include "shield_dialog.h"
|
||||
#include "send_tab.h"
|
||||
#include "../../app.h"
|
||||
#include "../../config/settings.h"
|
||||
@@ -531,7 +532,7 @@ static void RenderBalanceClassic(App* app)
|
||||
float cy = cMin.y + cardPadLg;
|
||||
|
||||
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(cx, cy),
|
||||
OnSurfaceMedium(), TR("transparent"));
|
||||
OnSurfaceMedium(), TR("mining_rewards"));
|
||||
cy += ovFont->LegacySize + ovGap;
|
||||
|
||||
snprintf(buf, sizeof(buf), "%.8f", s_dispTransparent);
|
||||
@@ -543,18 +544,24 @@ static void RenderBalanceClassic(App* app)
|
||||
OnSurfaceMedium(), DRAGONX_TICKER);
|
||||
cy += sub1->LegacySize + valGap;
|
||||
|
||||
snprintf(buf, sizeof(buf), TR("baltab_t_addresses_count"),
|
||||
(int)state.t_addresses.size());
|
||||
// Rewards waiting at a mining address: say so, and let a click shield them now.
|
||||
const bool unshielded = s_dispTransparent > 0.0;
|
||||
if (unshielded) snprintf(buf, sizeof(buf), "%s", TR("mining_rewards_unshielded"));
|
||||
else snprintf(buf, sizeof(buf), TR("baltab_mining_addresses_count"), (int)state.t_addresses.size());
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy),
|
||||
OnSurfaceDisabled(), buf);
|
||||
unshielded ? amberCol : OnSurfaceDisabled(), buf);
|
||||
|
||||
// Hover glow + click to Receive
|
||||
// Hover glow + explain; click shields waiting rewards (else opens Receive)
|
||||
if (material::IsRectHovered(cMin, cMax)) {
|
||||
dl->AddRect(cMin, cMax, IM_COL32(255, 255, 255, (int)S.drawElement("tabs.balance", "hover-glow-alpha").sizeOr(40.0f)),
|
||||
cardSpec.rounding, 0, S.drawElement("tabs.balance", "hover-glow-thickness").sizeOr(1.5f) * dp);
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
if (ImGui::IsMouseClicked(0))
|
||||
app->setCurrentPage(NavPage::Receive);
|
||||
material::Tooltip("%s%s%s", TR("mining_card_tooltip"), unshielded ? "\n\n" : "",
|
||||
unshielded ? TR("mining_card_click_to_shield") : "");
|
||||
if (ImGui::IsMouseClicked(0)) {
|
||||
if (unshielded) ShieldDialog::showShieldCoinbase();
|
||||
else app->setCurrentPage(NavPage::Receive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -803,7 +810,7 @@ static void RenderBalanceDonut(App* app) {
|
||||
|
||||
// Transparent legend
|
||||
dl->AddCircleFilled(ImVec2(legendX + 5 * dp, legendY + capFont->LegacySize * 0.5f), legendDotR, Warning());
|
||||
snprintf(buf, sizeof(buf), TR("baltab_transparent_amount"), s_dispTransparent);
|
||||
snprintf(buf, sizeof(buf), TR("baltab_mining_amount"), s_dispTransparent);
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(legendX + legendXOff, legendY), Warning(), buf);
|
||||
legendY += capFont->LegacySize + legendSectionGap;
|
||||
|
||||
@@ -976,7 +983,7 @@ static void RenderBalanceConsolidated(App* app) {
|
||||
// Transparent bar
|
||||
float transX = cardMin.x + pad * 2 + halfW;
|
||||
barY = divY + Layout::spacingSm();
|
||||
std::string transLabelUpper = TR("transparent");
|
||||
std::string transLabelUpper = TR("mining_rewards");
|
||||
std::transform(transLabelUpper.begin(), transLabelUpper.end(), transLabelUpper.begin(),
|
||||
[](unsigned char c){ return (char)std::toupper(c); });
|
||||
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(transX, barY), Warning(), transLabelUpper.c_str());
|
||||
@@ -1100,7 +1107,7 @@ static void RenderBalanceDashboard(App* app) {
|
||||
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c){ return (char)std::toupper(c); });
|
||||
return s;
|
||||
};
|
||||
std::string lblShielded = upperTR("shielded"), lblTransparent = upperTR("transparent");
|
||||
std::string lblShielded = upperTR("shielded"), lblTransparent = upperTR("mining_rewards");
|
||||
std::string lblQuickSend = upperTR("quick_send"), lblQuickReceive = upperTR("quick_receive");
|
||||
|
||||
TileInfo tiles[4] = {
|
||||
@@ -1240,7 +1247,7 @@ static void RenderBalanceVerticalStack(App* app) {
|
||||
RowInfo rowInfos[4] = {
|
||||
{TR("baltab_total_balance"), ICON_MD_ACCOUNT_BALANCE_WALLET, S.resolveColor("var(--accent-total)", OnSurface()), s_dispTotal, 1.0f},
|
||||
{TR("baltab_shielded"), ICON_MD_SHIELD, S.resolveColor("var(--accent-shielded)", Success()), s_dispShielded, shieldRatio},
|
||||
{TR("baltab_transparent"), ICON_MD_CIRCLE, S.resolveColor("var(--accent-transparent)", Warning()), s_dispTransparent, transRatio},
|
||||
{TR("mining_rewards"), ICON_MD_CIRCLE, S.resolveColor("var(--accent-transparent)", Warning()), s_dispTransparent, transRatio},
|
||||
{TR("baltab_market"), ICON_MD_TRENDING_UP, S.resolveColor("var(--accent-action)", Primary()), state.market.price_usd, 0.0f},
|
||||
};
|
||||
|
||||
@@ -1437,7 +1444,7 @@ static void RenderBalanceVertical2x2(App* app) {
|
||||
// Row 1: Market (left), Transparent (right)
|
||||
{
|
||||
{TR("baltab_market"), ICON_MD_TRENDING_UP, S.resolveColor("var(--accent-action)", Primary()), state.market.price_usd, 0.0f, true, false},
|
||||
{TR("baltab_transparent"), ICON_MD_CIRCLE, S.resolveColor("var(--accent-transparent)", Warning()), s_dispTransparent, transRatio, false, true},
|
||||
{TR("mining_rewards"), ICON_MD_CIRCLE, S.resolveColor("var(--accent-transparent)", Warning()), s_dispTransparent, transRatio, false, true},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1696,7 +1703,7 @@ static void RenderBalanceShield(App* app) {
|
||||
std::string shieldLabelUpper = TR("shielded");
|
||||
std::transform(shieldLabelUpper.begin(), shieldLabelUpper.end(), shieldLabelUpper.begin(),
|
||||
[](unsigned char c){ return (char)std::toupper(c); });
|
||||
std::string transLabelUpper = TR("transparent");
|
||||
std::string transLabelUpper = TR("mining_rewards");
|
||||
std::transform(transLabelUpper.begin(), transLabelUpper.end(), transLabelUpper.begin(),
|
||||
[](unsigned char c){ return (char)std::toupper(c); });
|
||||
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(infoX, infoY), Success(), shieldLabelUpper.c_str());
|
||||
@@ -1855,7 +1862,7 @@ static void RenderBalanceTimeline(App* app) {
|
||||
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c){ return (char)std::toupper(c); });
|
||||
return s;
|
||||
};
|
||||
std::string cShielded = upperTR("shielded"), cTransparent = upperTR("transparent"), cMarket = upperTR("market");
|
||||
std::string cShielded = upperTR("shielded"), cTransparent = upperTR("mining_rewards"), cMarket = upperTR("market");
|
||||
SumCard cards[3] = {
|
||||
{cShielded.c_str(), Success(), s_dispShielded, false},
|
||||
{cTransparent.c_str(), Warning(), s_dispTransparent, false},
|
||||
@@ -2137,7 +2144,7 @@ static void RenderBalanceMinimal(App* app) {
|
||||
snprintf(buf, sizeof(buf), TR("balance_shielded_fmt"), s_dispShielded);
|
||||
Type().textColored(TypeStyle::Caption, Success(), buf);
|
||||
ImGui::SameLine(0, Layout::spacingLg());
|
||||
snprintf(buf, sizeof(buf), TR("balance_transparent_fmt"), s_dispTransparent);
|
||||
snprintf(buf, sizeof(buf), TR("balance_mining_fmt"), s_dispTransparent);
|
||||
Type().textColored(TypeStyle::Caption, Warning(), buf);
|
||||
|
||||
// USD value
|
||||
|
||||
@@ -624,7 +624,7 @@ void RenderContactsTab(App* app)
|
||||
float cy = origin.y + std::max(0.0f, (a.y - blockH) * 0.42f);
|
||||
float cx = origin.x + a.x * 0.5f;
|
||||
struct Chip { bool sh; const char* lbl; };
|
||||
Chip chips[2] = { { true, TR("contact_avatar_shielded") }, { false, TR("contact_avatar_transparent") } };
|
||||
Chip chips[2] = { { true, TR("contact_avatar_shielded") }, { false, TR("contact_chip_mining") } };
|
||||
for (int i = 0; i < 2; i++) {
|
||||
float ccx = cx + (i == 0 ? -(chipR + chipGap * 0.5f) : (chipR + chipGap * 0.5f));
|
||||
ImVec2 cc(ccx, cy + chipR);
|
||||
|
||||
@@ -624,7 +624,7 @@ static void pfDrawAddressSection(App* app)
|
||||
// Funded (right). Select-all is deferred so it applies to the freshly-filtered set below.
|
||||
bool selAllClicked = false;
|
||||
{
|
||||
const char* flabels[3] = { TR("portfolio_select_all"), TR("shielded"), TR("transparent") };
|
||||
const char* flabels[3] = { TR("portfolio_select_all"), TR("shielded"), TR("addr_type_mining") };
|
||||
const char* selLbl = TR("portfolio_select_shown");
|
||||
const char* clrLbl = TR("portfolio_clear_sel");
|
||||
ImGuiStyle& st = ImGui::GetStyle();
|
||||
|
||||
@@ -957,7 +957,9 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
|
||||
else if (poolBlockedBySolo)
|
||||
material::Tooltip("%s", TR("mining_stop_solo_for_pool"));
|
||||
else if (poolNeedsPayout)
|
||||
material::Tooltip("%s", TR("mining_pool_needs_payout_tooltip"));
|
||||
material::Tooltip("%s", util::isTransparentAddress(poolPayoutStr)
|
||||
? TR("mining_payout_needs_shielded")
|
||||
: TR("mining_pool_needs_payout_tooltip"));
|
||||
else
|
||||
material::Tooltip("%s", isMiningActive ? TR("stop_mining") : TR("start_mining"));
|
||||
}
|
||||
|
||||
@@ -413,7 +413,10 @@ void RenderMiningModeToggle(App* app, const WalletState& state, const MiningInfo
|
||||
if (currentWorkerStr.empty()) {
|
||||
material::Tooltip("%s", TR("mining_generate_z_address_hint"));
|
||||
} else if (currentWorkerStr != "x" && !util::isValidRecipientAddress(currentWorkerStr)) {
|
||||
material::Tooltip("%s", TR("mining_payout_invalid")); // block start below too (M-01)
|
||||
// Block start below too (M-01). A pool pays with ordinary transactions, which can't reach
|
||||
// a transparent (mining) address on this private chain: say so rather than "invalid".
|
||||
material::Tooltip("%s", util::isTransparentAddress(currentWorkerStr)
|
||||
? TR("mining_payout_needs_shielded") : TR("mining_payout_invalid"));
|
||||
} else {
|
||||
material::Tooltip("%s", TR("mining_payout_tooltip"));
|
||||
}
|
||||
|
||||
@@ -325,10 +325,10 @@ static void RenderAddressDropdown(App* app, float width) {
|
||||
app->createNewTAddress([](const std::string& addr) {
|
||||
s_generating_address = false;
|
||||
if (addr.empty())
|
||||
Notifications::instance().error(TR("failed_create_transparent"));
|
||||
Notifications::instance().error(TR("failed_create_mining_address"));
|
||||
else {
|
||||
s_pending_select_address = addr;
|
||||
Notifications::instance().success(TR("new_transparent_created"));
|
||||
Notifications::instance().success(TR("new_mining_address_created"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -693,6 +693,14 @@ static void RenderTxProgress(ImDrawList* dl, float x, float y, float w,
|
||||
// ============================================================================
|
||||
// Confirmation dialog
|
||||
// ============================================================================
|
||||
void SweepSetSendRecipient(App* app, const char* to) {
|
||||
(void)app;
|
||||
std::snprintf(s_from_address, sizeof(s_from_address), "%s",
|
||||
to ? "zs1demoprimaryshieldedaddressforuisweep000000000000000000000000000" : "");
|
||||
std::snprintf(s_to_address, sizeof(s_to_address), "%s", to ? to : "");
|
||||
s_amount = to ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
void SweepShowSendConfirm(App* app, bool show) {
|
||||
(void)app;
|
||||
if (show) {
|
||||
@@ -975,7 +983,7 @@ static bool RenderZeroBalanceCTA(App* app, ImDrawList* dl, float width) {
|
||||
// Action buttons (Send + Clear + Undo)
|
||||
// ============================================================================
|
||||
static void RenderActionButtons(App* app, float width, float vScale,
|
||||
bool is_valid_address, double available,
|
||||
bool is_valid_address, bool is_mining_addr, double available,
|
||||
const char* suffix = "") {
|
||||
auto& S = schema::UI();
|
||||
const float dp = Layout::dpiScale();
|
||||
@@ -1027,6 +1035,8 @@ static void RenderActionButtons(App* app, float width, float vScale,
|
||||
material::Tooltip("%s", TR("send_tooltip_syncing"));
|
||||
else if (s_from_address[0] == '\0')
|
||||
material::Tooltip("%s", TR("send_tooltip_select_source"));
|
||||
else if (is_mining_addr)
|
||||
material::Tooltip("%s", TR("send_mining_addr_blocked"));
|
||||
else if (!is_valid_address)
|
||||
material::Tooltip("%s", TR("send_tooltip_invalid_address"));
|
||||
else if (s_amount <= 0)
|
||||
@@ -1255,8 +1265,10 @@ void RenderSendTab(App* app)
|
||||
|
||||
double available = GetAvailableBalance(app);
|
||||
bool is_valid_z = IsValidShieldedAddr(s_to_address);
|
||||
// A transparent address is a mining address: it only ever receives mining rewards, and the network
|
||||
// rejects a payment to it (DragonX is a private chain). Recognise it, but never send to it.
|
||||
bool is_valid_t = IsValidTransparentAddr(s_to_address);
|
||||
bool is_valid_address = is_valid_z || is_valid_t;
|
||||
bool is_valid_address = is_valid_z;
|
||||
float sectionGap = Layout::spacingXl() * vScale;
|
||||
|
||||
char buf[128];
|
||||
@@ -1365,7 +1377,9 @@ void RenderSendTab(App* app)
|
||||
if (vz)
|
||||
Type().textColored(TypeStyle::Caption, Success(), TR("send_valid_shielded"));
|
||||
else
|
||||
Type().textColored(TypeStyle::Caption, Warning(), TR("send_valid_transparent"));
|
||||
Type().textColored(TypeStyle::Caption, Error(), TR("send_mining_addr_short"));
|
||||
if (vt && ImGui::IsItemHovered())
|
||||
material::Tooltip("%s", TR("send_mining_addr_blocked"));
|
||||
} else if (!checkPreview) {
|
||||
ImGui::SameLine();
|
||||
Type().textColored(TypeStyle::Caption, Error(), TR("invalid_address"));
|
||||
@@ -1405,7 +1419,8 @@ void RenderSendTab(App* app)
|
||||
trimmed.erase(trimmed.begin());
|
||||
while (!trimmed.empty() && (trimmed.back() == ' ' || trimmed.back() == '\n' || trimmed.back() == '\r' || trimmed.back() == '\t'))
|
||||
trimmed.pop_back();
|
||||
bool looksValid = dragonx::util::isValidRecipientAddress(trimmed);
|
||||
// Preview any address, a mining one too: once pasted, the field explains why it can't be paid.
|
||||
bool looksValid = dragonx::util::isDragonXAddress(trimmed);
|
||||
if (looksValid && s_to_address[0] == '\0') {
|
||||
s_preview_text = trimmed;
|
||||
s_paste_previewing = true;
|
||||
@@ -1626,7 +1641,7 @@ void RenderSendTab(App* app)
|
||||
ImGui::Dummy(ImVec2(0, innerGap * 0.5f));
|
||||
|
||||
// ---- ACTION BUTTONS (full width) ----
|
||||
RenderActionButtons(app, innerW, vScale, is_valid_address, available, "##main");
|
||||
RenderActionButtons(app, innerW, vScale, is_valid_address, is_valid_t, available, "##main");
|
||||
|
||||
// Add bottom padding
|
||||
ImGui::Dummy(ImVec2(0, pad * vScale));
|
||||
|
||||
@@ -30,6 +30,8 @@ void RenderSendConfirmPopup(App* app);
|
||||
* the only way an external driver (the full UI sweep) can reveal it. Fires no send.
|
||||
*/
|
||||
void SweepShowSendConfirm(App* app, bool show);
|
||||
// UI sweep: put `to` (nullptr: clear) in the recipient field, e.g. a mining address to show it can't be paid.
|
||||
void SweepSetSendRecipient(App* app, const char* to);
|
||||
|
||||
/**
|
||||
* @brief Pre-fill the "from" address in the send tab
|
||||
|
||||
@@ -164,10 +164,22 @@ bool isShieldedAddress(const std::string& s)
|
||||
|| hrp == "zregtestsapling"; // regtest
|
||||
}
|
||||
|
||||
bool isValidRecipientAddress(const std::string& s)
|
||||
bool isDragonXAddress(const std::string& s)
|
||||
{
|
||||
return isTransparentAddress(s) || isShieldedAddress(s);
|
||||
}
|
||||
|
||||
bool isValidRecipientAddress(const std::string& s)
|
||||
{
|
||||
return isShieldedAddress(s);
|
||||
}
|
||||
|
||||
RecipientKind classifyRecipient(const std::string& s)
|
||||
{
|
||||
if (isShieldedAddress(s)) return RecipientKind::Shielded;
|
||||
if (isTransparentAddress(s)) return RecipientKind::MiningOnly;
|
||||
return RecipientKind::Invalid;
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace dragonx
|
||||
|
||||
@@ -45,8 +45,18 @@ bool isTransparentAddress(const std::string& s);
|
||||
// with a valid Bech32 checksum. Distinguishes a payment address from a viewing key (e.g. "zivks…").
|
||||
bool isShieldedAddress(const std::string& s);
|
||||
|
||||
// True if `s` is any address a payment can be sent to (transparent or shielded).
|
||||
// True if `s` is a DragonX address of either kind (transparent or shielded) — "is this an address at all".
|
||||
bool isDragonXAddress(const std::string& s);
|
||||
|
||||
// True if `s` is an address a payment can be sent to. DragonX is a private chain (-ac_private=1): consensus
|
||||
// rejects any non-coinbase output to a transparent address, so only a shielded address can be paid — sends,
|
||||
// payment requests and pool payouts included. A transparent address only ever receives mining rewards.
|
||||
bool isValidRecipientAddress(const std::string& s);
|
||||
|
||||
// What sending to `s` would mean: a shielded address (payable), a mining address (transparent: valid, but
|
||||
// it can only receive mining rewards, so a payment to it is rejected), or not an address.
|
||||
enum class RecipientKind { Invalid, Shielded, MiningOnly };
|
||||
RecipientKind classifyRecipient(const std::string& s);
|
||||
|
||||
} // namespace util
|
||||
} // namespace dragonx
|
||||
|
||||
+17
-12
@@ -1031,9 +1031,7 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["copied"] = "Copied!";
|
||||
strings_["hidden_tag"] = " (hidden)";
|
||||
strings_["z_address"] = "Z-Address";
|
||||
strings_["t_address"] = "T-Address";
|
||||
strings_["shielded_address"] = "Shielded Address";
|
||||
strings_["transparent_address"] = "Transparent Address";
|
||||
strings_["label_placeholder"] = "e.g. Savings, Mining...";
|
||||
strings_["choose_icon"] = "Choose Icon";
|
||||
strings_["clear_icon"] = "Clear Icon";
|
||||
@@ -1169,7 +1167,6 @@ void I18n::loadBuiltinEnglish()
|
||||
// Balance Tab
|
||||
strings_["summary"] = "Summary";
|
||||
strings_["shielded"] = "Shielded";
|
||||
strings_["transparent"] = "Transparent";
|
||||
strings_["total"] = "Total";
|
||||
strings_["unconfirmed"] = "Unconfirmed";
|
||||
strings_["recent_transactions"] = "RECENT TRANSACTIONS";
|
||||
@@ -1180,7 +1177,6 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["privacy_low"] = "Low privacy — shield funds";
|
||||
strings_["balance_history_collecting"] = "Balance history — collecting data...";
|
||||
strings_["balance_shielded_fmt"] = "Shielded: %.8f";
|
||||
strings_["balance_transparent_fmt"] = "Transparent: %.8f";
|
||||
strings_["total_balance_label"] = "Total Balance";
|
||||
strings_["balance_syncing_pct"] = "Syncing %.1f%%";
|
||||
strings_["balance_mining_rate"] = "Mining %s";
|
||||
@@ -1194,7 +1190,6 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["t_addresses"] = "T-Addresses";
|
||||
strings_["no_addresses"] = "No addresses found. Create one using the buttons above.";
|
||||
strings_["new_z_address"] = "New Z-Address";
|
||||
strings_["new_t_address"] = "New T-Address";
|
||||
strings_["type"] = "Type";
|
||||
strings_["address"] = "Address";
|
||||
strings_["copy_address"] = "Copy Full Address";
|
||||
@@ -1792,7 +1787,6 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["contact_avatar_image"] = "Image";
|
||||
strings_["contact_avatar_badge_hint"] = "The badge is chosen automatically from the address type.";
|
||||
strings_["contact_avatar_shielded"] = "Shielded";
|
||||
strings_["contact_avatar_transparent"] = "Transparent";
|
||||
strings_["contact_avatar_choose"] = "Choose image\xE2\x80\xA6";
|
||||
strings_["contact_avatar_remove"] = "Remove";
|
||||
strings_["contact_avatar_image_hint"] = "The image is copied into the app so it stays available if the original moves.";
|
||||
@@ -2170,6 +2164,23 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["mining_payout_address"] = "Payout Address";
|
||||
strings_["mining_payout_tooltip"] = "Address to receive mining rewards";
|
||||
strings_["mining_payout_invalid"] = "Not a valid DragonX address — fix it before starting, or mining rewards are lost.";
|
||||
strings_["send_mining_addr_short"] = "Mining address — can't be paid";
|
||||
strings_["send_mining_addr_blocked"] = "This is a mining (transparent) address. On DragonX these can only receive mining rewards — the network rejects payments to them. Ask the recipient for a shielded address (it starts with zs).";
|
||||
strings_["mining_payout_needs_shielded"] = "Pools pay out with ordinary transactions, which can't reach a mining (transparent, R…) address on DragonX. Use a shielded address (it starts with zs) as your payout address.";
|
||||
strings_["new_mining_address"] = "New mining address";
|
||||
strings_["tt_new_mining_address"] = "Creates a transparent (R…) address for solo-mining rewards. It can't receive payments — share a shielded address for those.";
|
||||
strings_["mining_address"] = "Mining address";
|
||||
strings_["addr_type_mining"] = "Mining";
|
||||
strings_["mining_rewards"] = "Mining rewards";
|
||||
strings_["mining_rewards_unshielded"] = "Not yet shielded";
|
||||
strings_["baltab_mining_addresses_count"] = "%d mining addresses";
|
||||
strings_["baltab_mining_amount"] = "Mining rewards %.8f";
|
||||
strings_["balance_mining_fmt"] = "Mining rewards: %.8f";
|
||||
strings_["mining_card_tooltip"] = "Solo-mining rewards arrive at your mining (transparent, R…) addresses. Those addresses can't receive payments. Rewards are shielded automatically once they mature.";
|
||||
strings_["mining_card_click_to_shield"] = "Click to shield matured rewards now.";
|
||||
strings_["contact_chip_mining"] = "Mining only";
|
||||
strings_["failed_create_mining_address"] = "Failed to create mining address";
|
||||
strings_["new_mining_address_created"] = "New mining address created";
|
||||
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_pool_needs_payout_tooltip"] = "Enter a payout address first (generate a Z address)";
|
||||
@@ -2388,9 +2399,7 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["click_copy_uri"] = "Click to copy URI";
|
||||
strings_["generating"] = "Generating";
|
||||
strings_["failed_create_shielded"] = "Failed to create shielded address";
|
||||
strings_["failed_create_transparent"] = "Failed to create transparent address";
|
||||
strings_["new_shielded_created"] = "New shielded address created";
|
||||
strings_["new_transparent_created"] = "New transparent address created";
|
||||
strings_["payment_request_copied"] = "Payment request copied";
|
||||
strings_["payment_uri_copied"] = "Payment URI copied";
|
||||
|
||||
@@ -2453,7 +2462,6 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["send_txid_copied"] = "TxID copied to clipboard";
|
||||
strings_["send_txid_label"] = "TxID: %s";
|
||||
strings_["send_valid_shielded"] = "Valid shielded address";
|
||||
strings_["send_valid_transparent"] = "Valid transparent address";
|
||||
strings_["send_wallet_empty"] = "Your wallet is empty";
|
||||
strings_["send_yes_clear"] = "Yes, Clear";
|
||||
|
||||
@@ -2708,15 +2716,12 @@ void I18n::loadBuiltinEnglish()
|
||||
|
||||
// balance_tab.cpp
|
||||
strings_["baltab_shielded_amount"] = "Shielded %.8f";
|
||||
strings_["baltab_transparent_amount"] = "Transparent %.8f";
|
||||
strings_["baltab_market_price_4dp"] = "Market: $%.4f";
|
||||
strings_["baltab_market_price_8dp"] = "Market: $%.8f";
|
||||
strings_["baltab_pct_of_total_zaddr"] = "%.0f%% of total · %d Z-addr";
|
||||
strings_["baltab_t_addresses_count"] = "%d T-addresses";
|
||||
strings_["baltab_pct_change_24h"] = "%s%.1f%% 24h";
|
||||
strings_["baltab_total_balance"] = "Total Balance";
|
||||
strings_["baltab_shielded"] = "Shielded";
|
||||
strings_["baltab_transparent"] = "Transparent";
|
||||
strings_["baltab_market"] = "Market";
|
||||
|
||||
// settings_window.cpp
|
||||
|
||||
Reference in New Issue
Block a user