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:
2026-10-02 20:08:14 -05:00
co-authored by Claude Opus 5.5
parent a3361020d1
commit 5246898968
26 changed files with 468 additions and 162 deletions
+8 -6
View File
@@ -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")))