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:
+48
-17
@@ -4410,21 +4410,10 @@ void testChooseDaemonStatus()
|
||||
EXPECT_TRUE(chooseDaemonStatus(false, false, idle) == DaemonStatusKind::Launching); // not bound yet
|
||||
}
|
||||
|
||||
// S10: every string added by the daemon-stop work exists in English and in all 8 languages, with the same
|
||||
// printf arguments (a mismatch is rejected at runtime and would fall back to English, or worse).
|
||||
void testDaemonStopStringsTranslated()
|
||||
// Every key exists in English and in all 8 languages, with the same printf arguments (a mismatch is rejected
|
||||
// at runtime and falls back to English, or worse).
|
||||
static void expectKeysTranslated(const std::vector<const char*>& keys)
|
||||
{
|
||||
const char* keys[] = {
|
||||
"shutdown_leave_running", "shutdown_leave_running_tip", "shutdown_kill_node", "shutdown_kill_node_tip",
|
||||
"shutdown_kill_confirm_title", "shutdown_kill_confirm_msg", "shutdown_kill_confirm_yes",
|
||||
"shutdown_stop_asking", "shutdown_stop_queued", "shutdown_stop_delivered", "shutdown_stop_cpu_suffix",
|
||||
"shutdown_stop_idle", "node_busy_rebuild_title", "node_busy_rebuild_desc", "node_busy_rebuild_reading",
|
||||
"node_busy_rebuild_extending", "node_busy_replay_title", "node_busy_replay_desc", "node_busy_rpc_title",
|
||||
"node_busy_rpc_desc", "node_starting_title", "node_starting_busy_desc", "node_starting_launch_desc",
|
||||
"sb_node_busy", "stopconfirm_rebuild_title", "stopconfirm_rebuild_body", "stopconfirm_busy_title",
|
||||
"stopconfirm_busy_body", "stopconfirm_stop_wait", "appx_daemon_reinstall_node_busy", "node_busy_running",
|
||||
"appx_node_busy_try_later", "appx_node_not_stopped",
|
||||
};
|
||||
// printf conversions in order, "%%" excluded: what must survive translation.
|
||||
auto conversions = [](const std::string& s) {
|
||||
std::string sig;
|
||||
@@ -4451,6 +4440,34 @@ void testDaemonStopStringsTranslated()
|
||||
}
|
||||
}
|
||||
|
||||
// S10: every string added by the daemon-stop work.
|
||||
void testDaemonStopStringsTranslated()
|
||||
{
|
||||
expectKeysTranslated({
|
||||
"shutdown_leave_running", "shutdown_leave_running_tip", "shutdown_kill_node", "shutdown_kill_node_tip",
|
||||
"shutdown_kill_confirm_title", "shutdown_kill_confirm_msg", "shutdown_kill_confirm_yes",
|
||||
"shutdown_stop_asking", "shutdown_stop_queued", "shutdown_stop_delivered", "shutdown_stop_cpu_suffix",
|
||||
"shutdown_stop_idle", "node_busy_rebuild_title", "node_busy_rebuild_desc", "node_busy_rebuild_reading",
|
||||
"node_busy_rebuild_extending", "node_busy_replay_title", "node_busy_replay_desc", "node_busy_rpc_title",
|
||||
"node_busy_rpc_desc", "node_starting_title", "node_starting_busy_desc", "node_starting_launch_desc",
|
||||
"sb_node_busy", "stopconfirm_rebuild_title", "stopconfirm_rebuild_body", "stopconfirm_busy_title",
|
||||
"stopconfirm_busy_body", "stopconfirm_stop_wait", "appx_daemon_reinstall_node_busy", "node_busy_running",
|
||||
"appx_node_busy_try_later", "appx_node_not_stopped",
|
||||
});
|
||||
}
|
||||
|
||||
// Mining addresses: the strings that explain transparent addresses only receive mining rewards.
|
||||
void testMiningAddressStringsTranslated()
|
||||
{
|
||||
expectKeysTranslated({
|
||||
"send_mining_addr_short", "send_mining_addr_blocked", "mining_payout_needs_shielded", "new_mining_address",
|
||||
"tt_new_mining_address", "mining_address", "addr_type_mining", "mining_rewards", "mining_rewards_unshielded",
|
||||
"baltab_mining_addresses_count", "baltab_mining_amount", "balance_mining_fmt", "mining_card_tooltip",
|
||||
"mining_card_click_to_shield", "contact_chip_mining", "failed_create_mining_address",
|
||||
"new_mining_address_created",
|
||||
});
|
||||
}
|
||||
|
||||
// S3: on Linux a node in its final shutdown phase reads comm "hush-shutoff"; its executable still says
|
||||
// dragonxd, so it must still count as running (it holds the datadir lock until it exits).
|
||||
void testDaemonProcessIdentity()
|
||||
@@ -7787,12 +7804,25 @@ void testAddressChecksumValidation()
|
||||
EXPECT_FALSE(isShieldedAddress(p2pkh));
|
||||
EXPECT_TRUE(isShieldedAddress(zaddr));
|
||||
EXPECT_FALSE(isTransparentAddress(zaddr));
|
||||
EXPECT_TRUE(isValidRecipientAddress(p2pkh));
|
||||
EXPECT_TRUE(isValidRecipientAddress(p2sh));
|
||||
// Both kinds are DragonX addresses, but only a shielded one can be paid: the chain is private, so
|
||||
// consensus rejects a payment to a transparent address (it only ever receives mining rewards).
|
||||
using dragonx::util::isDragonXAddress;
|
||||
using dragonx::util::classifyRecipient;
|
||||
using dragonx::util::RecipientKind;
|
||||
EXPECT_TRUE(isDragonXAddress(p2pkh));
|
||||
EXPECT_TRUE(isDragonXAddress(p2sh));
|
||||
EXPECT_TRUE(isDragonXAddress(zaddr));
|
||||
EXPECT_FALSE(isValidRecipientAddress(p2pkh));
|
||||
EXPECT_FALSE(isValidRecipientAddress(p2sh));
|
||||
EXPECT_TRUE(isValidRecipientAddress(zaddr));
|
||||
EXPECT_TRUE(classifyRecipient(zaddr) == RecipientKind::Shielded);
|
||||
EXPECT_TRUE(classifyRecipient(p2pkh) == RecipientKind::MiningOnly);
|
||||
EXPECT_TRUE(classifyRecipient(p2sh) == RecipientKind::MiningOnly);
|
||||
EXPECT_TRUE(classifyRecipient("x") == RecipientKind::Invalid);
|
||||
EXPECT_TRUE(classifyRecipient("zivks1qqqsyqcyq5rqwzqfpg9scrgwpugpzysn") == RecipientKind::Invalid);
|
||||
// A WIF spending key is NOT a recipient (33/34-byte payload, not 21); nor is a typo'd address.
|
||||
EXPECT_FALSE(isTransparentAddress("Up3W7uVYkLxCfH91APxjSpkkGBWJyBrm3tt1bCz64V5fpZK9ef3C"));
|
||||
EXPECT_FALSE(isValidRecipientAddress("R9NXAVJezHiBnT3ijTpg3JUZre7PxhJWtX")); // flipped checksum char
|
||||
EXPECT_FALSE(isDragonXAddress("R9NXAVJezHiBnT3ijTpg3JUZre7PxhJWtX")); // flipped checksum char
|
||||
EXPECT_FALSE(isValidRecipientAddress(""));
|
||||
}
|
||||
|
||||
@@ -9213,6 +9243,7 @@ int main()
|
||||
testOutputSinceAcrossTrim();
|
||||
testChooseDaemonStatus();
|
||||
testDaemonStopStringsTranslated();
|
||||
testMiningAddressStringsTranslated();
|
||||
#ifdef _WIN32
|
||||
testWindowsStopNeverKillsForeignDaemon();
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user