feat(ui): explain mining addresses everywhere and stop offering them for payment
Follow-up to 5246898. A transparent address only ever receives mining
rewards on DragonX (private chain), so:
- One explainer ("Receives solo-mining rewards only - the network rejects
payments to it. Rewards are shielded automatically once they mature.")
on the Overview row hover, the Receive picker hover, the Receive card
and the Validate Address result (now typed "Mining address
(transparent)").
- Receive: a selected mining address shows the explainer and a "Use a
shielded address" button (creates one if none exists) instead of the
payment request, and a "Not for payments" panel instead of a QR code.
The default selection prefers a shielded address; the "All" filter
hides empty mining addresses unless solo mining or marked as a mining
address; the filter reads All / Shielded / Mining.
- Request Payment: no transparent section, never prefilled with one.
- Overview: no "Mining - Mining" tag on a marked transparent row.
- FAQ: address answers rewritten; the send and memo answers no longer
imply you can pay a transparent address. All 8 languages.
i18n: 4 new keys, 6 FAQ answers updated in place, 2 dead keys removed,
CJK subset rebuilt. UI sweep: receive-mining-address and
modal-validate-mining targets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+9
-1
@@ -19,6 +19,7 @@
|
||||
#include "ui/notifications.h"
|
||||
#include "ui/sidebar.h"
|
||||
#include "ui/windows/send_tab.h"
|
||||
#include "ui/windows/receive_tab.h"
|
||||
#include "ui/windows/wallets_dialog.h"
|
||||
#include "ui/windows/export_transactions_dialog.h"
|
||||
#include "ui/windows/export_all_keys_dialog.h"
|
||||
@@ -102,7 +103,7 @@ void seedSweepContacts(App& a)
|
||||
s_contactsSweepBackup = a.addressBook().entries();
|
||||
data::AddressBookEntry e1("drgx pool payout address", kDemoZAddr, "mining pool payouts"); // Z, global
|
||||
e1.avatar = "icon:account_balance"; // icon avatar (verifies the icon-badge render path)
|
||||
data::AddressBookEntry e2("exchange deposit", "t1DemoTransparentAddressForUiSweep00000", ""); // T
|
||||
data::AddressBookEntry e2("mining rig", "t1DemoTransparentAddressForUiSweep00000", ""); // T (mining only)
|
||||
// Scope to the active wallet so it's visible (the tab hides contacts not in the active wallet);
|
||||
// non-empty hash also keeps it non-global -> no globe badge, so the list shows a global/non-global mix.
|
||||
e2.scope = a.activeWalletIdentityHash();
|
||||
@@ -370,6 +371,13 @@ void App::buildSweepCatalog()
|
||||
add("modal-validate-address", ui::NavPage::Receive,
|
||||
[](App&) { ui::ValidateAddressDialog::show(); },
|
||||
[](App&) { ui::ValidateAddressDialog::hide(); });
|
||||
add("modal-validate-mining", ui::NavPage::Receive,
|
||||
[](App&) { ui::ValidateAddressDialog::sweepShowMiningResult("t1DemoTransparentAddressForUiSweep00000"); },
|
||||
[](App&) { ui::ValidateAddressDialog::hide(); });
|
||||
// A mining (transparent) address selected on Receive: no payment request, no QR — the explainer instead.
|
||||
add("receive-mining-address", ui::NavPage::Receive,
|
||||
[](App& a) { ui::SweepSelectReceiveAddress(&a, "t1DemoTransparentAddressForUiSweep00000"); },
|
||||
[](App& a) { ui::SweepSelectReceiveAddress(&a, nullptr); });
|
||||
add("modal-address-label", ui::NavPage::Overview,
|
||||
[](App& a) { ui::AddressLabelDialog::show(&a, kDemoZAddr, true); },
|
||||
[](App&) { ui::AddressLabelDialog::hide(); });
|
||||
|
||||
@@ -546,7 +546,8 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
{
|
||||
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") : "";
|
||||
// A transparent row is already typed "Mining"; the tag is for shielded payout addresses.
|
||||
const char* miningTag = (row.mining && row.isZ) ? TR("mining_tag") : "";
|
||||
char typeBuf[64];
|
||||
snprintf(typeBuf, sizeof(typeBuf), "%s%s%s", typeLabel, hiddenTag, miningTag);
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(labelX, cy), typeCol, typeBuf);
|
||||
@@ -655,7 +656,8 @@ void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
ImGui::SetCursorScreenPos(ImVec2(rowPos.x, rowY[row_idx]));
|
||||
ImGui::InvisibleButton("##addr", ImVec2(innerW, rowH));
|
||||
if (ImGui::IsItemHovered() && !s_dragActive) {
|
||||
material::Tooltip("%s", addr.address.c_str());
|
||||
if (row.isZ) material::Tooltip("%s", addr.address.c_str());
|
||||
else material::Tooltip("%s\n\n%s", addr.address.c_str(), TR("mining_addr_explainer"));
|
||||
}
|
||||
|
||||
// Context menu
|
||||
|
||||
+299
-207
@@ -130,13 +130,17 @@ static void RenderAddressDropdown(App* app, float width) {
|
||||
// Header row: label + address type toggle buttons
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("address_upper"));
|
||||
|
||||
// All / Shielded / Mining: each segment as wide as the widest label (they are words, not Z/T).
|
||||
const char* filterLabels[] = { TR("all_filter"), TR("shielded"), TR("addr_type_mining") };
|
||||
float toggleBtnW = std::max(schema::UI().drawElement("tabs.receive", "toggle-btn-min-width").size, schema::UI().drawElement("tabs.receive", "toggle-btn-width").size * Layout::hScale(width));
|
||||
for (const char* lbl : filterLabels)
|
||||
toggleBtnW = std::max(toggleBtnW, Type().caption()->CalcTextSizeA(Type().caption()->LegacySize, FLT_MAX, 0, lbl).x +
|
||||
2.0f * Layout::spacingMd());
|
||||
float toggleGap = schema::UI().drawElement("tabs.receive", "toggle-gap").size;
|
||||
float toggleTotalW = toggleBtnW * 3 + toggleGap * 2;
|
||||
float segH = ImGui::GetFrameHeight();
|
||||
ImGui::SameLine(width - toggleTotalW);
|
||||
ImVec2 segOrigin = ImGui::GetCursorScreenPos();
|
||||
const char* filterLabels[] = { TR("all_filter"), "Z", "T" };
|
||||
int segClk = material::SegmentedControl(ImGui::GetWindowDrawList(), segOrigin, toggleTotalW, segH,
|
||||
filterLabels, 3, s_addr_type_filter, Type().caption(),
|
||||
"##addrFilter", Layout::dpiScale());
|
||||
@@ -147,14 +151,18 @@ static void RenderAddressDropdown(App* app, float width) {
|
||||
|
||||
TrackNewAddresses(state);
|
||||
|
||||
// Auto-select address with the largest balance on first load
|
||||
// Auto-select the shielded address with the largest balance on first load. A mining (transparent)
|
||||
// address can't be paid, so it is picked only when the wallet has no shielded address yet.
|
||||
if (!s_auto_selected && app->isConnected() && !state.addresses.empty()) {
|
||||
int bestIdx = -1;
|
||||
double bestBal = -1.0;
|
||||
bool bestIsZ = false;
|
||||
for (size_t i = 0; i < state.addresses.size(); i++) {
|
||||
if (state.addresses[i].balance > bestBal) {
|
||||
const bool z = state.addresses[i].type == "shielded";
|
||||
if ((z && !bestIsZ) || (z == bestIsZ && state.addresses[i].balance > bestBal)) {
|
||||
bestBal = state.addresses[i].balance;
|
||||
bestIdx = static_cast<int>(i);
|
||||
bestIsZ = z;
|
||||
}
|
||||
}
|
||||
if (bestIdx >= 0) {
|
||||
@@ -222,6 +230,12 @@ static void RenderAddressDropdown(App* app, float width) {
|
||||
bool isZ = state.addresses[i].type == "shielded";
|
||||
if (s_addr_type_filter == 1 && !isZ) continue;
|
||||
if (s_addr_type_filter == 2 && isZ) continue;
|
||||
// "All" lists a mining address only when it matters: it holds rewards, it is marked as a
|
||||
// mining address, we are solo mining, or it is the one selected. The Mining filter lists all.
|
||||
if (s_addr_type_filter == 0 && !isZ && state.addresses[i].balance < 1e-9 &&
|
||||
!app->isMiningAddress(state.addresses[i].address) && !state.mining.generate &&
|
||||
s_selected_address_idx != static_cast<int>(i))
|
||||
continue;
|
||||
sortedIdx.push_back(i);
|
||||
}
|
||||
std::sort(sortedIdx.begin(), sortedIdx.end(),
|
||||
@@ -272,9 +286,12 @@ static void RenderAddressDropdown(App* app, float width) {
|
||||
s_cached_qr_data.clear(); // Force QR regeneration
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
material::Tooltip(TR("grpb_tooltip_address_balance"),
|
||||
addr.address.c_str(), addr.balance, DRAGONX_TICKER,
|
||||
isCurrent ? TR("grpb_selected_suffix") : "");
|
||||
char tip[512];
|
||||
snprintf(tip, sizeof(tip), TR("grpb_tooltip_address_balance"),
|
||||
addr.address.c_str(), addr.balance, DRAGONX_TICKER,
|
||||
isCurrent ? TR("grpb_selected_suffix") : "");
|
||||
if (isZ) material::Tooltip("%s", tip);
|
||||
else material::Tooltip("%s\n\n%s", tip, TR("mining_addr_explainer"));
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
@@ -337,6 +354,60 @@ static void RenderAddressDropdown(App* app, float width) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
void SweepSelectReceiveAddress(App* app, const char* address) {
|
||||
(void)app;
|
||||
if (address) {
|
||||
s_pending_select_address = address;
|
||||
s_addr_type_filter = 0;
|
||||
} else {
|
||||
s_auto_selected = false; // re-run the default (shielded-first) choice
|
||||
}
|
||||
s_cached_qr_data.clear();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mining address notice (instead of a payment request)
|
||||
// ============================================================================
|
||||
// A mining (transparent) address only ever receives mining rewards: the network rejects payments to it.
|
||||
// So it gets no payment request and no QR code — just what it is for, and a way to a shielded address.
|
||||
static void RenderMiningAddressNotice(App* app, const WalletState& state, float width) {
|
||||
Type().textColored(TypeStyle::Overline, Warning(), TR("mining_address"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + width);
|
||||
Type().textWrapped(TypeStyle::Body2, TR("mining_addr_explainer"));
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
|
||||
|
||||
int bestZ = -1;
|
||||
double bestBal = -1.0;
|
||||
for (size_t i = 0; i < state.addresses.size(); i++) {
|
||||
if (state.addresses[i].type == "shielded" && state.addresses[i].balance > bestBal) {
|
||||
bestBal = state.addresses[i].balance;
|
||||
bestZ = static_cast<int>(i);
|
||||
}
|
||||
}
|
||||
ImGui::BeginDisabled(bestZ < 0 && (!app->isConnected() || s_generating_address));
|
||||
if (TactileButton(TrId("receive_use_shielded", "mining").c_str(), ImVec2(0, 0), schema::UI().resolveFont("button"))) {
|
||||
if (bestZ >= 0) {
|
||||
s_selected_address_idx = bestZ;
|
||||
s_cached_qr_data.clear();
|
||||
} else {
|
||||
s_generating_address = true; // no shielded address yet: make one, as "New" does
|
||||
app->createNewZAddress([](const std::string& addr) {
|
||||
s_generating_address = false;
|
||||
if (addr.empty())
|
||||
Notifications::instance().error(TR("failed_create_shielded"));
|
||||
else {
|
||||
s_pending_select_address = addr;
|
||||
Notifications::instance().success(TR("new_shielded_created"));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::Dummy(ImVec2(width, Layout::spacingSm()));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Helpers: DrawRecvIcon (local copy — original is static in send_tab).
|
||||
// Relative time uses the shared util::formatTimeAgoShort ("14d ago"), matching Overview/Send.
|
||||
@@ -580,9 +651,9 @@ void RenderReceiveTab(App* app)
|
||||
const AddressInfo& selected = state.addresses[s_selected_address_idx];
|
||||
bool isZ = selected.type == "shielded";
|
||||
|
||||
// Generate QR data
|
||||
std::string qr_data = selected.address;
|
||||
if (s_request_amount > 0) {
|
||||
// Generate QR data — shielded only: a mining (transparent) address can't be paid, so it gets no QR.
|
||||
std::string qr_data = isZ ? selected.address : std::string();
|
||||
if (isZ && s_request_amount > 0) {
|
||||
// Canonical, URL-encoded "drgx:" URI via the shared builder. The old inline code emitted an
|
||||
// unparseable "dragonx:" scheme and appended the memo raw (spaces/&/= corrupted the QR).
|
||||
qr_data = util::buildPaymentUri(selected.address, s_request_amount, "",
|
||||
@@ -593,8 +664,10 @@ void RenderReceiveTab(App* app)
|
||||
FreeQRTexture(s_qr_texture);
|
||||
s_qr_texture = 0;
|
||||
}
|
||||
int w, h;
|
||||
s_qr_texture = GenerateQRTexture(qr_data.c_str(), &w, &h);
|
||||
if (!qr_data.empty()) {
|
||||
int w, h;
|
||||
s_qr_texture = GenerateQRTexture(qr_data.c_str(), &w, &h);
|
||||
}
|
||||
s_cached_qr_data = qr_data;
|
||||
}
|
||||
|
||||
@@ -644,205 +717,210 @@ void RenderReceiveTab(App* app)
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
|
||||
|
||||
// ---- PAYMENT REQUEST ----
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("payment_request"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// Amount input with currency toggle
|
||||
float toggleW = S.drawElement("tabs.receive", "currency-toggle-width").size * Layout::dpiScale();
|
||||
float amtInputW = addrColW - toggleW - Layout::spacingMd();
|
||||
if (amtInputW < S.drawElement("tabs.receive", "amount-input-min-width").size) amtInputW = S.drawElement("tabs.receive", "amount-input-min-width").size;
|
||||
double usd_price = state.market.price_usd;
|
||||
|
||||
ImGui::PushFont(body2);
|
||||
if (s_request_usd_mode && usd_price > 0) {
|
||||
ImGui::PushItemWidth(amtInputW);
|
||||
if (ImGui::InputDouble("##RequestAmountUSD", &s_request_usd_amount, 0, 0, "$%.2f")) {
|
||||
if (s_request_usd_amount < 0) s_request_usd_amount = 0;
|
||||
s_request_amount = s_request_usd_amount / usd_price;
|
||||
}
|
||||
{
|
||||
ImVec2 iMin = ImGui::GetItemRectMin();
|
||||
ImVec2 iMax = ImGui::GetItemRectMax();
|
||||
snprintf(buf, sizeof(buf), "\xe2\x89\x88 %.4f %s", s_request_amount, DRAGONX_TICKER);
|
||||
ImVec2 sz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
|
||||
float tx = iMax.x - sz.x - ImGui::GetStyle().FramePadding.x;
|
||||
float ty = iMin.y + (iMax.y - iMin.y - sz.y) * 0.5f;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(tx, ty),
|
||||
IM_COL32(255, 255, 255, (int)S.drawElement("tabs.receive", "input-overlay-text-alpha").size), buf);
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
} else {
|
||||
ImGui::PushItemWidth(amtInputW);
|
||||
if (ImGui::InputDouble("##RequestAmount", &s_request_amount, 0, 0, "%.8f")) {
|
||||
if (s_request_amount < 0) s_request_amount = 0;
|
||||
if (usd_price > 0)
|
||||
s_request_usd_amount = s_request_amount * usd_price;
|
||||
}
|
||||
if (usd_price > 0 && s_request_amount > 0) {
|
||||
ImVec2 iMin = ImGui::GetItemRectMin();
|
||||
ImVec2 iMax = ImGui::GetItemRectMax();
|
||||
double usd_value = s_request_amount * usd_price;
|
||||
if (usd_value >= 0.01)
|
||||
snprintf(buf, sizeof(buf), "\xe2\x89\x88 $%.2f", usd_value);
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "\xe2\x89\x88 $%.6f", usd_value);
|
||||
ImVec2 sz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
|
||||
float tx = iMax.x - sz.x - ImGui::GetStyle().FramePadding.x;
|
||||
float ty = iMin.y + (iMax.y - iMin.y - sz.y) * 0.5f;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(tx, ty),
|
||||
IM_COL32(255, 255, 255, (int)S.drawElement("tabs.receive", "input-overlay-text-alpha").size), buf);
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
|
||||
// Toggle button
|
||||
ImGui::SameLine(0, Layout::spacingMd());
|
||||
{
|
||||
const char* currLabel = s_request_usd_mode ? "DRGX" : "USD";
|
||||
bool canToggle = (usd_price > 0);
|
||||
ImGui::BeginDisabled(!canToggle);
|
||||
if (TactileButton("##ToggleCurrencyRecv", ImVec2(toggleW, 0), S.resolveFont("button"))) {
|
||||
s_request_usd_mode = !s_request_usd_mode;
|
||||
if (s_request_usd_mode && usd_price > 0)
|
||||
s_request_usd_amount = s_request_amount * usd_price;
|
||||
}
|
||||
{
|
||||
ImVec2 bMin = ImGui::GetItemRectMin();
|
||||
ImVec2 bMax = ImGui::GetItemRectMax();
|
||||
float bH = bMax.y - bMin.y;
|
||||
ImFont* font = ImGui::GetFont();
|
||||
ImVec2 textSz = font->CalcTextSizeA(font->LegacySize, 10000, 0, currLabel);
|
||||
float iconW = schema::UI().drawElement("tabs.receive", "currency-icon-width").size * Layout::dpiScale();
|
||||
float iconGap2 = schema::UI().drawElement("tabs.receive", "currency-icon-gap").size * Layout::dpiScale();
|
||||
float totalW2 = iconW + iconGap2 + textSz.x;
|
||||
float startX = bMin.x + ((bMax.x - bMin.x) - totalW2) * 0.5f;
|
||||
float cy = bMin.y + bH * 0.5f;
|
||||
ImU32 iconCol = ImGui::GetColorU32(ImGuiCol_Text);
|
||||
float ss = iconW * 0.5f;
|
||||
float cx = startX + ss;
|
||||
float ag = S.drawElement("tabs.receive", "swap-icon-arrow-gap").size * cardDp;
|
||||
float al = ss * S.drawElement("tabs.receive", "swap-icon-arrow-length-ratio").size;
|
||||
float hss = S.drawElement("tabs.receive", "swap-icon-arrowhead-size").size * cardDp;
|
||||
float ay1 = cy - ag;
|
||||
dl->AddLine(ImVec2(cx - al, ay1), ImVec2(cx + al, ay1), iconCol, S.drawElement("tabs.receive", "swap-icon-line-thickness").size * cardDp);
|
||||
dl->AddTriangleFilled(
|
||||
ImVec2(cx + al, ay1),
|
||||
ImVec2(cx + al - hss, ay1 - hss),
|
||||
ImVec2(cx + al - hss, ay1 + hss), iconCol);
|
||||
float ay2 = cy + ag;
|
||||
dl->AddLine(ImVec2(cx + al, ay2), ImVec2(cx - al, ay2), iconCol, S.drawElement("tabs.receive", "swap-icon-line-thickness").size * cardDp);
|
||||
dl->AddTriangleFilled(
|
||||
ImVec2(cx - al, ay2),
|
||||
ImVec2(cx - al + hss, ay2 - hss),
|
||||
ImVec2(cx - al + hss, ay2 + hss), iconCol);
|
||||
float tx = startX + iconW + iconGap2;
|
||||
float ty = cy - textSz.y * 0.5f;
|
||||
dl->AddText(font, font->LegacySize, ImVec2(tx, ty), iconCol, currLabel);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
ImGui::PopFont();
|
||||
|
||||
// Preset amount chips
|
||||
{
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
float chipRound = schema::UI().drawElement("tabs.receive", "chip-rounding").size;
|
||||
float chipGap = schema::UI().drawElement("tabs.receive", "chip-gap").size * cardDp;
|
||||
float chipH = schema::UI().drawElement("tabs.receive", "chip-height").size * cardDp;
|
||||
|
||||
struct Preset { const char* label; double amount; };
|
||||
Preset presets[] = {
|
||||
{ "0.1", 0.1 },
|
||||
{ "1", 1.0 },
|
||||
{ "10", 10.0 },
|
||||
{ "100", 100.0 },
|
||||
{ "1K", 1000.0 },
|
||||
{ "10K", 10000.0 },
|
||||
{ "50K", 50000.0 },
|
||||
{ "100K", 100000.0 },
|
||||
};
|
||||
constexpr int presetCount = 8;
|
||||
|
||||
// Calculate equal chip widths to span full addrColW
|
||||
float totalGap = chipGap * (presetCount - 1);
|
||||
float chipW = (addrColW - totalGap) / presetCount;
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, chipRound);
|
||||
|
||||
ImFont* chipFont = S.resolveFont("button");
|
||||
|
||||
for (int ci = 0; ci < presetCount; ci++) {
|
||||
if (ci > 0) ImGui::SameLine(0, chipGap);
|
||||
|
||||
bool active = false;
|
||||
if (s_request_usd_mode) {
|
||||
active = (usd_price > 0 && std::abs(s_request_usd_amount - presets[ci].amount) < 0.001);
|
||||
} else {
|
||||
active = (std::abs(s_request_amount - presets[ci].amount) < 1e-8);
|
||||
}
|
||||
|
||||
if (active) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::ColorConvertU32ToFloat4(IM_COL32(255, 255, 255, (int)S.drawElement("tabs.receive", "chip-active-bg-alpha").size)));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Primary()));
|
||||
}
|
||||
|
||||
char chipId[32];
|
||||
snprintf(chipId, sizeof(chipId), "%s##presetAmt%d", presets[ci].label, ci);
|
||||
if (TactileButton(chipId, ImVec2(chipW, chipH), chipFont)) {
|
||||
if (s_request_usd_mode) {
|
||||
s_request_usd_amount = presets[ci].amount;
|
||||
if (usd_price > 0)
|
||||
s_request_amount = s_request_usd_amount / usd_price;
|
||||
} else {
|
||||
s_request_amount = presets[ci].amount;
|
||||
if (usd_price > 0)
|
||||
s_request_usd_amount = s_request_amount * usd_price;
|
||||
}
|
||||
}
|
||||
|
||||
if (active) ImGui::PopStyleColor(2);
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
// Memo (z-addresses only)
|
||||
if (isZ) {
|
||||
// ---- PAYMENT REQUEST ----
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("payment_request"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("memo_optional"));
|
||||
ImGui::Dummy(ImVec2(0, S.drawElement("tabs.receive", "memo-label-gap").size));
|
||||
|
||||
float memoInputH = std::max(schema::UI().drawElement("tabs.receive", "memo-input-min-height").size, schema::UI().drawElement("tabs.receive", "memo-input-height").size * vScale);
|
||||
ImGui::PushItemWidth(addrColW);
|
||||
ImGui::InputTextMultiline("##RequestMemo", s_request_memo, sizeof(s_request_memo),
|
||||
ImVec2(addrColW, memoInputH));
|
||||
ImGui::PopItemWidth();
|
||||
// Amount input with currency toggle
|
||||
float toggleW = S.drawElement("tabs.receive", "currency-toggle-width").size * Layout::dpiScale();
|
||||
float amtInputW = addrColW - toggleW - Layout::spacingMd();
|
||||
if (amtInputW < S.drawElement("tabs.receive", "amount-input-min-width").size) amtInputW = S.drawElement("tabs.receive", "amount-input-min-width").size;
|
||||
double usd_price = state.market.price_usd;
|
||||
|
||||
// The Sapling memo limit is in BYTES and matches the enforced buffer size
|
||||
// (InputTextMultiline caps at sizeof(s_request_memo)); count against that, not the
|
||||
// old display-only 256 value, and warn in colour once the field is at its cap.
|
||||
size_t memo_len = strlen(s_request_memo);
|
||||
size_t memoMax = (size_t)S.drawElement("business", "memo-max-length").size;
|
||||
bool memoAtCap = memo_len + 1 >= memoMax;
|
||||
snprintf(buf, sizeof(buf), TR("byte_count_fmt"), memo_len, memoMax);
|
||||
Type().textColored(TypeStyle::Caption, memoAtCap ? Warning() : OnSurfaceDisabled(), buf);
|
||||
}
|
||||
ImGui::PushFont(body2);
|
||||
if (s_request_usd_mode && usd_price > 0) {
|
||||
ImGui::PushItemWidth(amtInputW);
|
||||
if (ImGui::InputDouble("##RequestAmountUSD", &s_request_usd_amount, 0, 0, "$%.2f")) {
|
||||
if (s_request_usd_amount < 0) s_request_usd_amount = 0;
|
||||
s_request_amount = s_request_usd_amount / usd_price;
|
||||
}
|
||||
{
|
||||
ImVec2 iMin = ImGui::GetItemRectMin();
|
||||
ImVec2 iMax = ImGui::GetItemRectMax();
|
||||
snprintf(buf, sizeof(buf), "\xe2\x89\x88 %.4f %s", s_request_amount, DRAGONX_TICKER);
|
||||
ImVec2 sz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
|
||||
float tx = iMax.x - sz.x - ImGui::GetStyle().FramePadding.x;
|
||||
float ty = iMin.y + (iMax.y - iMin.y - sz.y) * 0.5f;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(tx, ty),
|
||||
IM_COL32(255, 255, 255, (int)S.drawElement("tabs.receive", "input-overlay-text-alpha").size), buf);
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
} else {
|
||||
ImGui::PushItemWidth(amtInputW);
|
||||
if (ImGui::InputDouble("##RequestAmount", &s_request_amount, 0, 0, "%.8f")) {
|
||||
if (s_request_amount < 0) s_request_amount = 0;
|
||||
if (usd_price > 0)
|
||||
s_request_usd_amount = s_request_amount * usd_price;
|
||||
}
|
||||
if (usd_price > 0 && s_request_amount > 0) {
|
||||
ImVec2 iMin = ImGui::GetItemRectMin();
|
||||
ImVec2 iMax = ImGui::GetItemRectMax();
|
||||
double usd_value = s_request_amount * usd_price;
|
||||
if (usd_value >= 0.01)
|
||||
snprintf(buf, sizeof(buf), "\xe2\x89\x88 $%.2f", usd_value);
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "\xe2\x89\x88 $%.6f", usd_value);
|
||||
ImVec2 sz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
|
||||
float tx = iMax.x - sz.x - ImGui::GetStyle().FramePadding.x;
|
||||
float ty = iMin.y + (iMax.y - iMin.y - sz.y) * 0.5f;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(tx, ty),
|
||||
IM_COL32(255, 255, 255, (int)S.drawElement("tabs.receive", "input-overlay-text-alpha").size), buf);
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
|
||||
// URI preview
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
if (s_request_amount > 0 && !s_cached_qr_data.empty()) {
|
||||
float uriWrapW = addrColW;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImGui::GetCursorScreenPos(),
|
||||
OnSurfaceDisabled(), s_cached_qr_data.c_str(), nullptr, uriWrapW);
|
||||
ImVec2 uriSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX,
|
||||
uriWrapW, s_cached_qr_data.c_str());
|
||||
ImGui::Dummy(ImVec2(addrColW, uriSz.y + Layout::spacingSm()));
|
||||
// Toggle button
|
||||
ImGui::SameLine(0, Layout::spacingMd());
|
||||
{
|
||||
const char* currLabel = s_request_usd_mode ? "DRGX" : "USD";
|
||||
bool canToggle = (usd_price > 0);
|
||||
ImGui::BeginDisabled(!canToggle);
|
||||
if (TactileButton("##ToggleCurrencyRecv", ImVec2(toggleW, 0), S.resolveFont("button"))) {
|
||||
s_request_usd_mode = !s_request_usd_mode;
|
||||
if (s_request_usd_mode && usd_price > 0)
|
||||
s_request_usd_amount = s_request_amount * usd_price;
|
||||
}
|
||||
{
|
||||
ImVec2 bMin = ImGui::GetItemRectMin();
|
||||
ImVec2 bMax = ImGui::GetItemRectMax();
|
||||
float bH = bMax.y - bMin.y;
|
||||
ImFont* font = ImGui::GetFont();
|
||||
ImVec2 textSz = font->CalcTextSizeA(font->LegacySize, 10000, 0, currLabel);
|
||||
float iconW = schema::UI().drawElement("tabs.receive", "currency-icon-width").size * Layout::dpiScale();
|
||||
float iconGap2 = schema::UI().drawElement("tabs.receive", "currency-icon-gap").size * Layout::dpiScale();
|
||||
float totalW2 = iconW + iconGap2 + textSz.x;
|
||||
float startX = bMin.x + ((bMax.x - bMin.x) - totalW2) * 0.5f;
|
||||
float cy = bMin.y + bH * 0.5f;
|
||||
ImU32 iconCol = ImGui::GetColorU32(ImGuiCol_Text);
|
||||
float ss = iconW * 0.5f;
|
||||
float cx = startX + ss;
|
||||
float ag = S.drawElement("tabs.receive", "swap-icon-arrow-gap").size * cardDp;
|
||||
float al = ss * S.drawElement("tabs.receive", "swap-icon-arrow-length-ratio").size;
|
||||
float hss = S.drawElement("tabs.receive", "swap-icon-arrowhead-size").size * cardDp;
|
||||
float ay1 = cy - ag;
|
||||
dl->AddLine(ImVec2(cx - al, ay1), ImVec2(cx + al, ay1), iconCol, S.drawElement("tabs.receive", "swap-icon-line-thickness").size * cardDp);
|
||||
dl->AddTriangleFilled(
|
||||
ImVec2(cx + al, ay1),
|
||||
ImVec2(cx + al - hss, ay1 - hss),
|
||||
ImVec2(cx + al - hss, ay1 + hss), iconCol);
|
||||
float ay2 = cy + ag;
|
||||
dl->AddLine(ImVec2(cx + al, ay2), ImVec2(cx - al, ay2), iconCol, S.drawElement("tabs.receive", "swap-icon-line-thickness").size * cardDp);
|
||||
dl->AddTriangleFilled(
|
||||
ImVec2(cx - al, ay2),
|
||||
ImVec2(cx - al + hss, ay2 - hss),
|
||||
ImVec2(cx - al + hss, ay2 + hss), iconCol);
|
||||
float tx = startX + iconW + iconGap2;
|
||||
float ty = cy - textSz.y * 0.5f;
|
||||
dl->AddText(font, font->LegacySize, ImVec2(tx, ty), iconCol, currLabel);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
ImGui::PopFont();
|
||||
|
||||
// Preset amount chips
|
||||
{
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
float chipRound = schema::UI().drawElement("tabs.receive", "chip-rounding").size;
|
||||
float chipGap = schema::UI().drawElement("tabs.receive", "chip-gap").size * cardDp;
|
||||
float chipH = schema::UI().drawElement("tabs.receive", "chip-height").size * cardDp;
|
||||
|
||||
struct Preset { const char* label; double amount; };
|
||||
Preset presets[] = {
|
||||
{ "0.1", 0.1 },
|
||||
{ "1", 1.0 },
|
||||
{ "10", 10.0 },
|
||||
{ "100", 100.0 },
|
||||
{ "1K", 1000.0 },
|
||||
{ "10K", 10000.0 },
|
||||
{ "50K", 50000.0 },
|
||||
{ "100K", 100000.0 },
|
||||
};
|
||||
constexpr int presetCount = 8;
|
||||
|
||||
// Calculate equal chip widths to span full addrColW
|
||||
float totalGap = chipGap * (presetCount - 1);
|
||||
float chipW = (addrColW - totalGap) / presetCount;
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, chipRound);
|
||||
|
||||
ImFont* chipFont = S.resolveFont("button");
|
||||
|
||||
for (int ci = 0; ci < presetCount; ci++) {
|
||||
if (ci > 0) ImGui::SameLine(0, chipGap);
|
||||
|
||||
bool active = false;
|
||||
if (s_request_usd_mode) {
|
||||
active = (usd_price > 0 && std::abs(s_request_usd_amount - presets[ci].amount) < 0.001);
|
||||
} else {
|
||||
active = (std::abs(s_request_amount - presets[ci].amount) < 1e-8);
|
||||
}
|
||||
|
||||
if (active) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::ColorConvertU32ToFloat4(IM_COL32(255, 255, 255, (int)S.drawElement("tabs.receive", "chip-active-bg-alpha").size)));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Primary()));
|
||||
}
|
||||
|
||||
char chipId[32];
|
||||
snprintf(chipId, sizeof(chipId), "%s##presetAmt%d", presets[ci].label, ci);
|
||||
if (TactileButton(chipId, ImVec2(chipW, chipH), chipFont)) {
|
||||
if (s_request_usd_mode) {
|
||||
s_request_usd_amount = presets[ci].amount;
|
||||
if (usd_price > 0)
|
||||
s_request_amount = s_request_usd_amount / usd_price;
|
||||
} else {
|
||||
s_request_amount = presets[ci].amount;
|
||||
if (usd_price > 0)
|
||||
s_request_usd_amount = s_request_amount * usd_price;
|
||||
}
|
||||
}
|
||||
|
||||
if (active) ImGui::PopStyleColor(2);
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
// Memo (z-addresses only)
|
||||
if (isZ) {
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("memo_optional"));
|
||||
ImGui::Dummy(ImVec2(0, S.drawElement("tabs.receive", "memo-label-gap").size));
|
||||
|
||||
float memoInputH = std::max(schema::UI().drawElement("tabs.receive", "memo-input-min-height").size, schema::UI().drawElement("tabs.receive", "memo-input-height").size * vScale);
|
||||
ImGui::PushItemWidth(addrColW);
|
||||
ImGui::InputTextMultiline("##RequestMemo", s_request_memo, sizeof(s_request_memo),
|
||||
ImVec2(addrColW, memoInputH));
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
// The Sapling memo limit is in BYTES and matches the enforced buffer size
|
||||
// (InputTextMultiline caps at sizeof(s_request_memo)); count against that, not the
|
||||
// old display-only 256 value, and warn in colour once the field is at its cap.
|
||||
size_t memo_len = strlen(s_request_memo);
|
||||
size_t memoMax = (size_t)S.drawElement("business", "memo-max-length").size;
|
||||
bool memoAtCap = memo_len + 1 >= memoMax;
|
||||
snprintf(buf, sizeof(buf), TR("byte_count_fmt"), memo_len, memoMax);
|
||||
Type().textColored(TypeStyle::Caption, memoAtCap ? Warning() : OnSurfaceDisabled(), buf);
|
||||
}
|
||||
|
||||
// URI preview
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
if (s_request_amount > 0 && !s_cached_qr_data.empty()) {
|
||||
float uriWrapW = addrColW;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImGui::GetCursorScreenPos(),
|
||||
OnSurfaceDisabled(), s_cached_qr_data.c_str(), nullptr, uriWrapW);
|
||||
ImVec2 uriSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX,
|
||||
uriWrapW, s_cached_qr_data.c_str());
|
||||
ImGui::Dummy(ImVec2(addrColW, uriSz.y + Layout::spacingSm()));
|
||||
} else {
|
||||
ImGui::Dummy(ImVec2(addrColW, capFont->LegacySize + Layout::spacingSm()));
|
||||
}
|
||||
} else {
|
||||
ImGui::Dummy(ImVec2(addrColW, capFont->LegacySize + Layout::spacingSm()));
|
||||
// A mining (transparent) address can't be paid: no payment request and no QR code.
|
||||
RenderMiningAddressNotice(app, state, addrColW);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -874,7 +952,21 @@ void RenderReceiveTab(App* app)
|
||||
DrawGlassPanel(dl, qrPanelMin, qrPanelMax, qrGlass);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(qrPanelMin.x + qrPadding, qrPanelMin.y + qrPadding));
|
||||
if (s_qr_texture) {
|
||||
if (!isZ) {
|
||||
// A mining address: nothing to scan. Mark the panel so the empty square isn't a puzzle.
|
||||
ImGui::Dummy(ImVec2(qrSize, qrSize));
|
||||
ImFont* iconF = Type().iconXL();
|
||||
const char* noPay = TR("receive_not_for_payments");
|
||||
ImVec2 iSz = iconF->CalcTextSizeA(iconF->LegacySize, FLT_MAX, 0, ICON_MD_CONSTRUCTION);
|
||||
ImVec2 tSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, qrSize, noPay);
|
||||
const float blockH = iSz.y + Layout::spacingSm() + tSz.y;
|
||||
const float cx = qrPanelMin.x + totalQrSize * 0.5f;
|
||||
const float top = qrPanelMin.y + (totalQrSize - blockH) * 0.5f;
|
||||
dl->AddText(iconF, iconF->LegacySize, ImVec2(cx - iSz.x * 0.5f, top), WithAlpha(Warning(), 200),
|
||||
ICON_MD_CONSTRUCTION);
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx - tSz.x * 0.5f, top + iSz.y + Layout::spacingSm()),
|
||||
OnSurfaceDisabled(), noPay, nullptr, qrSize);
|
||||
} else if (s_qr_texture) {
|
||||
RenderQRCode(s_qr_texture, qrSize);
|
||||
} else {
|
||||
ImGui::Dummy(ImVec2(qrSize, qrSize));
|
||||
@@ -886,12 +978,12 @@ void RenderReceiveTab(App* app)
|
||||
|
||||
ImGui::SetCursorScreenPos(qrPanelMin);
|
||||
ImGui::InvisibleButton("##QRClickCopy", ImVec2(totalQrSize, totalQrSize));
|
||||
if (ImGui::IsItemHovered()) {
|
||||
if (isZ && ImGui::IsItemHovered()) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
material::Tooltip("%s",
|
||||
s_request_amount > 0 ? TR("click_copy_uri") : TR("click_copy_address"));
|
||||
}
|
||||
if (ImGui::IsItemClicked()) {
|
||||
if (isZ && ImGui::IsItemClicked()) {
|
||||
ImGui::SetClipboardText(qr_data.c_str());
|
||||
Notifications::instance().info(s_request_amount > 0
|
||||
? TR("payment_uri_copied") : TR("address_copied"));
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace ui {
|
||||
* Shows addresses for receiving funds with QR codes
|
||||
*/
|
||||
void RenderReceiveTab(App* app);
|
||||
// UI sweep: select `address` on the Receive tab (nullptr: back to the default choice).
|
||||
void SweepSelectReceiveAddress(App* app, const char* address);
|
||||
|
||||
} // namespace ui
|
||||
} // namespace dragonx
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "../../app.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../../util/payment_uri.h"
|
||||
#include "../../util/address_validation.h"
|
||||
#include "../notifications.h"
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../widgets/qr_code.h"
|
||||
@@ -47,8 +48,10 @@ void RequestPaymentDialog::show(const std::string& address)
|
||||
s_label[0] = '\0';
|
||||
s_uri_dirty = true;
|
||||
|
||||
if (!address.empty()) {
|
||||
// Only a shielded address can be paid (DragonX is a private chain): never prefill a mining address.
|
||||
if (!address.empty() && !util::isTransparentAddress(address)) {
|
||||
strncpy(s_address, address.c_str(), sizeof(s_address) - 1);
|
||||
s_address[sizeof(s_address) - 1] = '\0';
|
||||
} else {
|
||||
s_address[0] = '\0';
|
||||
}
|
||||
@@ -78,9 +81,6 @@ void RequestPaymentDialog::render(App* app)
|
||||
auto zAddrLbl = S.label("dialogs.request-payment", "z-addr-label");
|
||||
auto zAddrFrontLbl = S.label("dialogs.request-payment", "z-addr-front-label");
|
||||
auto zAddrBackLbl = S.label("dialogs.request-payment", "z-addr-back-label");
|
||||
auto tAddrLbl = S.label("dialogs.request-payment", "t-addr-label");
|
||||
auto tAddrFrontLbl = S.label("dialogs.request-payment", "t-addr-front-label");
|
||||
auto tAddrBackLbl = S.label("dialogs.request-payment", "t-addr-back-label");
|
||||
auto amountInput = S.input("dialogs.request-payment", "amount-input");
|
||||
auto memoInput = S.input("dialogs.request-payment", "memo-input");
|
||||
auto qr = S.drawElement("dialogs.request-payment", "qr-code");
|
||||
@@ -124,22 +124,8 @@ void RequestPaymentDialog::render(App* app)
|
||||
}
|
||||
}
|
||||
|
||||
// T-addresses
|
||||
if (!state.t_addresses.empty()) {
|
||||
ImGui::TextDisabled("%s", TR("request_transparent_addrs"));
|
||||
for (size_t i = 0; i < state.t_addresses.size(); i++) {
|
||||
const auto& addr = state.t_addresses[i];
|
||||
std::string label = addr.address;
|
||||
if (label.length() > static_cast<size_t>(tAddrLbl.truncate)) {
|
||||
label = label.substr(0, tAddrFrontLbl.truncate) + "..." + label.substr(label.length() - tAddrBackLbl.truncate);
|
||||
}
|
||||
|
||||
if (ImGui::Selectable(label.c_str(), addr.address == s_address)) {
|
||||
strncpy(s_address, addr.address.c_str(), sizeof(s_address) - 1);
|
||||
s_uri_dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// No mining (transparent) addresses: they only receive mining rewards, so a request to one
|
||||
// could never be paid.
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ bool ValidateAddressDialog::s_validating = false;
|
||||
char ValidateAddressDialog::s_address_input[512] = "";
|
||||
bool ValidateAddressDialog::s_is_valid = false;
|
||||
bool ValidateAddressDialog::s_is_mine = false;
|
||||
bool ValidateAddressDialog::s_is_mining = false;
|
||||
std::string ValidateAddressDialog::s_address_type;
|
||||
std::string ValidateAddressDialog::s_error_message;
|
||||
|
||||
@@ -33,10 +34,22 @@ void ValidateAddressDialog::show()
|
||||
s_address_input[0] = '\0';
|
||||
s_is_valid = false;
|
||||
s_is_mine = false;
|
||||
s_is_mining = false;
|
||||
s_address_type.clear();
|
||||
s_error_message.clear();
|
||||
}
|
||||
|
||||
void ValidateAddressDialog::sweepShowMiningResult(const char* address)
|
||||
{
|
||||
show();
|
||||
snprintf(s_address_input, sizeof(s_address_input), "%s", address);
|
||||
s_validated = true;
|
||||
s_is_valid = true;
|
||||
s_is_mine = true;
|
||||
s_is_mining = true;
|
||||
s_address_type = TR("validate_mining_type");
|
||||
}
|
||||
|
||||
bool ValidateAddressDialog::isOpen()
|
||||
{
|
||||
return s_open;
|
||||
@@ -112,6 +125,7 @@ void ValidateAddressDialog::render(App* app)
|
||||
s_is_valid = valid;
|
||||
s_is_mine = mine;
|
||||
s_address_type = TR("validate_shielded_type");
|
||||
s_is_mining = false;
|
||||
} else {
|
||||
s_error_message = error;
|
||||
s_is_valid = false;
|
||||
@@ -138,7 +152,8 @@ void ValidateAddressDialog::render(App* app)
|
||||
if (error.empty()) {
|
||||
s_is_valid = valid;
|
||||
s_is_mine = mine;
|
||||
s_address_type = TR("validate_transparent_type");
|
||||
s_address_type = TR("validate_mining_type");
|
||||
s_is_mining = true;
|
||||
} else {
|
||||
s_error_message = error;
|
||||
s_is_valid = false;
|
||||
@@ -204,6 +219,12 @@ void ValidateAddressDialog::render(App* app)
|
||||
} else {
|
||||
ImGui::TextDisabled("%s", TR("validate_not_mine"));
|
||||
}
|
||||
if (s_is_mining) { // valid, but not for payments
|
||||
ImGui::Spacing();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(material::Warning()));
|
||||
ImGui::TextWrapped("%s", TR("mining_addr_explainer"));
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!app->isConnected()) {
|
||||
|
||||
@@ -38,6 +38,9 @@ public:
|
||||
*/
|
||||
static void hide();
|
||||
|
||||
/** UI sweep: show the result for a valid mining (transparent) address of ours. */
|
||||
static void sweepShowMiningResult(const char* address);
|
||||
|
||||
private:
|
||||
static bool s_open;
|
||||
static bool s_validated;
|
||||
@@ -47,6 +50,7 @@ private:
|
||||
// Validation results
|
||||
static bool s_is_valid;
|
||||
static bool s_is_mine;
|
||||
static bool s_is_mining; // a valid transparent (mining) address: explain it can't be paid
|
||||
static std::string s_address_type;
|
||||
static std::string s_error_message;
|
||||
};
|
||||
|
||||
+10
-8
@@ -1632,21 +1632,21 @@ void I18n::loadBuiltinEnglish()
|
||||
|
||||
// Wallet > Addresses & Privacy
|
||||
strings_["faq_w_addr_1_q"] = "What's the difference between transparent and shielded addresses?";
|
||||
strings_["faq_w_addr_1_a"] = "Transparent addresses (they start with R or t) work like most coins: the amounts and addresses are public on the blockchain.\n\nShielded addresses (z-addresses, starting with zs) use zero-knowledge cryptography \xE2\x80\x94 the amount, sender, and receiver are encrypted on-chain. Balances held in shielded addresses are private.";
|
||||
strings_["faq_w_addr_1_a"] = "Shielded addresses (z-addresses, starting with zs) are the ones you use on DragonX. They use zero-knowledge cryptography, so the amount, sender and receiver are encrypted on-chain and your balance stays private.\n\nTransparent addresses (starting with R) are mining addresses. DragonX is a fully private chain, so they can only receive mining rewards — the network rejects any payment sent to one. Rewards that arrive there are public until they are shielded, which the wallet does automatically.";
|
||||
strings_["faq_w_addr_2_q"] = "Which address type should I use?";
|
||||
strings_["faq_w_addr_2_a"] = "Prefer shielded (z) addresses whenever possible \xE2\x80\x94 they keep your balance and payment history private. DragonX is a privacy coin and shielded is the default for received funds.\n\nUse a transparent address only when a service you interact with cannot handle shielded addresses.";
|
||||
strings_["faq_w_addr_2_a"] = "Use a shielded (zs) address for everything you send and receive — on DragonX it is the only kind that can be paid.\n\nTransparent (mining) addresses are only for solo-mining rewards. Never give one out for a payment: the network will reject it.";
|
||||
strings_["faq_w_addr_3_q"] = "Should I reuse an address or make a new one?";
|
||||
strings_["faq_w_addr_3_a"] = "For shielded addresses, reuse is fine and does not leak history. For transparent addresses, using a fresh address per payment improves privacy. Create new addresses from the Receive tab.";
|
||||
strings_["faq_w_addr_3_a"] = "Reusing a shielded address is fine and does not leak your history. You can still create new ones from the Receive tab, for example one per contact to see who paid you. Mining addresses are only for mining rewards; you rarely need a new one.";
|
||||
strings_["faq_w_addr_4_q"] = "What is 'shielding'?";
|
||||
strings_["faq_w_addr_4_a"] = "Shielding moves coins from a transparent address into a shielded one, making them private. Newly mined coins arrive transparent and are shielded for you automatically.\n\nYou can also shield manually from the wallet; a shield is just a special transaction, so it takes a normal confirmation time to complete.";
|
||||
strings_["faq_w_addr_4_a"] = "Shielding moves coins from a transparent (mining) address into a shielded one, making them private. Newly mined coins arrive at a mining address and are shielded for you automatically once they mature.\n\nYou can also shield manually from the wallet; a shield is just a special transaction, so it takes a normal confirmation time to complete.";
|
||||
|
||||
// Wallet > Sending & Receiving
|
||||
strings_["faq_w_send_1_q"] = "How do I send funds?";
|
||||
strings_["faq_w_send_1_a"] = "Open the Send tab, paste the recipient's address, enter an amount, and confirm. If the wallet is encrypted you'll be asked to unlock it for the transaction.\n\nSends to shielded addresses are private; sends to transparent addresses are public.";
|
||||
strings_["faq_w_send_1_a"] = "Open the Send tab, paste the recipient's address, enter an amount, and confirm. If the wallet is encrypted you'll be asked to unlock it for the transaction.\n\nPayments go to shielded (zs) addresses and stay private. The wallet won't send to a transparent (mining) address: the network rejects payments to one.";
|
||||
strings_["faq_w_send_2_q"] = "What fee do I pay?";
|
||||
strings_["faq_w_send_2_a"] = "DragonX fees are very low and are set automatically. The fee is shown before you confirm. You do not normally need to change it.";
|
||||
strings_["faq_w_send_3_q"] = "Can I attach a message to a payment?";
|
||||
strings_["faq_w_send_3_a"] = "Yes \xE2\x80\x94 when sending to a shielded (z) address you can include an encrypted memo. Only the recipient can read it. Transparent addresses do not support memos.";
|
||||
strings_["faq_w_send_3_a"] = "Yes — when sending to a shielded (z) address you can include an encrypted memo. Only the recipient can read it.";
|
||||
strings_["faq_w_send_4_q"] = "I sent a payment but it says pending \xE2\x80\x94 why?";
|
||||
strings_["faq_w_send_4_a"] = "A transaction is 'pending' until it is mined into a block and gains confirmations. This usually takes a minute or two. Shielded transactions also need your wallet to be synced to build the proof.\n\nIf a send stays pending unusually long, check that your wallet is connected and synced (see the status bar at the bottom of the window).";
|
||||
|
||||
@@ -2181,6 +2181,10 @@ void I18n::loadBuiltinEnglish()
|
||||
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_addr_explainer"] = "Receives solo-mining rewards only — the network rejects payments to it. Rewards are shielded automatically once they mature.";
|
||||
strings_["receive_use_shielded"] = "Use a shielded address";
|
||||
strings_["receive_not_for_payments"] = "Not for payments";
|
||||
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_pool_needs_payout_tooltip"] = "Enter a payout address first (generate a Z address)";
|
||||
@@ -2414,7 +2418,6 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["request_select_address"] = "Select address...";
|
||||
strings_["request_shielded_addrs"] = "-- Shielded Addresses --";
|
||||
strings_["request_title"] = "Request Payment";
|
||||
strings_["request_transparent_addrs"] = "-- Transparent Addresses --";
|
||||
strings_["request_uri_copied"] = "Payment URI copied to clipboard";
|
||||
|
||||
// --- Send Tab ---
|
||||
@@ -2542,7 +2545,6 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["validate_shielded_type"] = "Shielded (z-address)";
|
||||
strings_["validate_status"] = "Status:";
|
||||
strings_["validate_title"] = "Validate Address";
|
||||
strings_["validate_transparent_type"] = "Transparent (t-address)";
|
||||
strings_["validate_type"] = "Type:";
|
||||
strings_["validate_valid"] = "VALID";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user