diff --git a/docs/ai-design/2026-02-24-mine-blocks-dialog/manual-test-scenarios.md b/docs/ai-design/2026-02-24-mine-blocks-dialog/manual-test-scenarios.md new file mode 100644 index 000000000..bb2b626e9 --- /dev/null +++ b/docs/ai-design/2026-02-24-mine-blocks-dialog/manual-test-scenarios.md @@ -0,0 +1,408 @@ +# Manual Test Scenarios: Mine Blocks Dialog (PR #638) + +## Overview + +PR #638 adds a "Mine" button to the wallet toolbar that opens a dialog for mining blocks to a selected wallet address. The feature is restricted to developer mode on Regtest/Devnet networks with RPC backend. + +--- + +## Scenario 1: Mine button visibility -- Regtest + Developer Mode + RPC + +### Preconditions +- Application connected to a **Regtest** network +- **Developer mode** enabled (Settings) +- Core backend mode set to **RPC** (not SPV) +- At least one wallet loaded + +### Steps +1. Navigate to the Wallets screen +2. Select a wallet from the wallet list +3. Observe the toolbar buttons (Send, Receive, etc.) + +### Expected Results +- A "Mine" button is visible in the toolbar, alongside Send and Receive buttons + +--- + +## Scenario 2: Mine button visibility -- Devnet + Developer Mode + RPC + +### Preconditions +- Application connected to a **Devnet** network +- **Developer mode** enabled +- Core backend mode set to **RPC** +- At least one wallet loaded + +### Steps +1. Navigate to the Wallets screen +2. Select a wallet from the wallet list +3. Observe the toolbar buttons + +### Expected Results +- The "Mine" button is visible in the toolbar + +--- + +## Scenario 3: Mine button hidden -- Mainnet + +### Preconditions +- Application connected to **Mainnet** +- Developer mode enabled +- Core backend mode set to RPC + +### Steps +1. Navigate to the Wallets screen +2. Select a wallet +3. Observe the toolbar buttons + +### Expected Results +- The "Mine" button is **not** visible + +--- + +## Scenario 4: Mine button hidden -- Testnet + +### Preconditions +- Application connected to **Testnet** +- Developer mode enabled +- Core backend mode set to RPC + +### Steps +1. Navigate to the Wallets screen +2. Select a wallet +3. Observe the toolbar buttons + +### Expected Results +- The "Mine" button is **not** visible + +--- + +## Scenario 5: Mine button hidden -- Developer mode disabled + +### Preconditions +- Application connected to **Regtest** network +- **Developer mode disabled** +- Core backend mode set to RPC + +### Steps +1. Navigate to the Wallets screen +2. Select a wallet +3. Observe the toolbar buttons + +### Expected Results +- The "Mine" button is **not** visible + +--- + +## Scenario 6: Mine button hidden -- SPV backend mode + +### Preconditions +- Application connected to **Regtest** network +- Developer mode enabled +- Core backend mode set to **SPV** (not RPC) + +### Steps +1. Navigate to the Wallets screen +2. Select a wallet +3. Observe the toolbar buttons + +### Expected Results +- The "Mine" button is **not** visible (mining requires RPC `generate_to_address`) + +--- + +## Scenario 7: Open and close Mine dialog + +### Preconditions +- Mine button is visible (Regtest + dev mode + RPC) +- A wallet is selected + +### Steps +1. Click the "Mine" button in the toolbar +2. Observe the dialog that appears +3. Click "Cancel" + +### Expected Results +- Step 2: A modal dialog titled "Mine Blocks" appears with: + - A label "Mine blocks to a wallet address:" + - An address dropdown selector showing wallet addresses with balances + - A "Number of blocks:" text input defaulting to "1" + - "Cancel" and "Mine" buttons +- Step 3: The dialog closes and no mining occurs + +--- + +## Scenario 8: Close dialog via window close button + +### Preconditions +- Mine dialog is open + +### Steps +1. Click the X (close) button on the dialog window + +### Expected Results +- The dialog closes, state is reset, no mining occurs + +--- + +## Scenario 9: Mine a single block successfully + +### Preconditions +- Mine button is visible (Regtest + dev mode + RPC) +- A wallet is selected with at least one core address +- Dash Core is running and accepting RPC commands + +### Steps +1. Click "Mine" +2. Leave block count as "1" +3. Observe the selected address in the dropdown +4. Click "Mine" button in the dialog + +### Expected Results +- The dialog closes +- A success banner appears: "Mined 1 block(s)" +- The wallet balance updates to reflect the newly mined coinbase reward + +--- + +## Scenario 10: Mine multiple blocks + +### Preconditions +- Same as Scenario 9 + +### Steps +1. Click "Mine" +2. Change block count to "10" +3. Click "Mine" button in the dialog + +### Expected Results +- The dialog closes +- A success banner appears: "Mined 10 block(s)" +- The wallet balance increases (note: coinbase outputs require 100 confirmations to be spendable on Regtest) + +--- + +## Scenario 11: Address selector shows multiple addresses + +### Preconditions +- Selected wallet has multiple BIP44 external (core receive) addresses +- Mine button is visible + +### Steps +1. Click "Mine" +2. Open the address dropdown selector +3. Observe the listed addresses + +### Expected Results +- All BIP44 external addresses from the wallet are listed +- Each address shows a truncated address string (first 12 chars) and balance in DASH format (e.g., "yXaBcDeFgHiJ... (0.0000 DASH)") +- The first address is selected by default + +--- + +## Scenario 12: Select a different address for mining + +### Preconditions +- Wallet has multiple core addresses + +### Steps +1. Open the Mine dialog +2. Open the address dropdown +3. Select a different address than the default +4. Enter block count "1" +5. Click "Mine" + +### Expected Results +- Mining reward is sent to the newly selected address +- Success message appears +- Balance for the selected address increases after sufficient confirmations + +--- + +## Scenario 13: Invalid block count -- zero + +### Preconditions +- Mine dialog is open + +### Steps +1. Clear the block count field +2. Enter "0" +3. Click "Mine" + +### Expected Results +- An error message appears in the dialog: "Enter a valid number of blocks (> 0)" +- The dialog remains open; no mining occurs + +--- + +## Scenario 14: Invalid block count -- negative number + +### Preconditions +- Mine dialog is open + +### Steps +1. Clear the block count field +2. Enter "-5" +3. Click "Mine" + +### Expected Results +- An error message appears: "Enter a valid number of blocks (> 0)" +- The dialog remains open + +--- + +## Scenario 15: Invalid block count -- non-numeric input + +### Preconditions +- Mine dialog is open + +### Steps +1. Clear the block count field +2. Enter "abc" +3. Click "Mine" + +### Expected Results +- An error message appears: "Enter a valid number of blocks (> 0)" +- The dialog remains open + +--- + +## Scenario 16: Invalid block count -- empty field + +### Preconditions +- Mine dialog is open + +### Steps +1. Clear the block count field entirely (empty string) +2. Click "Mine" + +### Expected Results +- An error message appears: "Enter a valid number of blocks (> 0)" +- The dialog remains open + +--- + +## Scenario 17: Invalid block count -- decimal number + +### Preconditions +- Mine dialog is open + +### Steps +1. Enter "1.5" in the block count field +2. Click "Mine" + +### Expected Results +- An error message appears: "Enter a valid number of blocks (> 0)" +- The dialog remains open (the field parses as u64, so decimals fail) + +--- + +## Scenario 18: Wallet with no existing core addresses + +### Preconditions +- A newly created wallet with no BIP44 external addresses yet +- Mine button is visible + +### Steps +1. Select the new wallet +2. Click "Mine" + +### Expected Results +- The dialog opens +- A new core receive address is automatically generated for the wallet +- The new address appears in the address dropdown +- Mining can proceed normally + +--- + +## Scenario 19: No wallet selected + +### Preconditions +- Mine button is visible +- No wallet is currently selected + +### Steps +1. Click "Mine" + +### Expected Results +- The dialog opens with an error message: "Select a wallet first" +- No addresses are available in the dropdown + +--- + +## Scenario 20: RPC connection failure during mining + +### Preconditions +- Mine dialog is open with valid inputs +- Dash Core RPC becomes unreachable (e.g., Core node stopped) + +### Steps +1. Stop Dash Core (or disconnect RPC) +2. Enter block count "1" in the Mine dialog +3. Click "Mine" + +### Expected Results +- The dialog closes +- An error banner appears with an RPC error message +- No blocks are mined; wallet balance unchanged + +--- + +## Scenario 21: Wallet balance refreshes after mining + +### Preconditions +- Mine button is visible, wallet selected, Dash Core running + +### Steps +1. Note the current wallet balance +2. Open the Mine dialog +3. Mine 1 block +4. Observe the wallet balance on the Wallets screen + +### Expected Results +- After the success message, the wallet balance is refreshed automatically +- The balance reflects the new coinbase reward (the backend calls `refresh_wallet_info` after mining) + +--- + +## Scenario 22: Very large block count + +### Preconditions +- Mine dialog is open + +### Steps +1. Enter a very large number (e.g., "999999999999999999") +2. Click "Mine" + +### Expected Results +- If the value fits in u64, the RPC call is made (Dash Core may take a long time or return an error depending on its limits) +- If it overflows u64, validation error is shown: "Enter a valid number of blocks (> 0)" + +--- + +## Scenario 23: Toggle developer mode while on Wallets screen + +### Preconditions +- Application on Regtest with RPC backend +- Developer mode enabled, Mine button visible + +### Steps +1. Observe the Mine button is present +2. Navigate to Settings and disable developer mode +3. Return to the Wallets screen + +### Expected Results +- The Mine button is no longer visible in the toolbar + +--- + +## Scenario 24: Switch network from Regtest to Testnet + +### Preconditions +- Application on Regtest with dev mode + RPC, Mine button visible + +### Steps +1. Switch network to Testnet +2. Navigate to the Wallets screen + +### Expected Results +- The Mine button is no longer visible (Testnet is not Regtest/Devnet) diff --git a/src/backend_task/core/mod.rs b/src/backend_task/core/mod.rs index 797af5348..84b762fb6 100644 --- a/src/backend_task/core/mod.rs +++ b/src/backend_task/core/mod.rs @@ -71,6 +71,11 @@ pub enum CoreTask { request: WalletPaymentRequest, }, RecoverAssetLocks(Arc>), + MineBlocks { + block_count: u64, + address: Address, + wallet: Arc>, + }, } impl PartialEq for CoreTask { fn eq(&self, other: &Self) -> bool { @@ -110,6 +115,7 @@ impl PartialEq for CoreTask { CoreTask::RecoverAssetLocks(_), CoreTask::RecoverAssetLocks(_), ) + | (CoreTask::MineBlocks { .. }, CoreTask::MineBlocks { .. }) ) } } @@ -275,6 +281,36 @@ impl AppContext { .await .map_err(|e| format!("Task join error: {}", e))? } + CoreTask::MineBlocks { + block_count, + address, + wallet, + } => { + if !matches!(self.network, Network::Regtest | Network::Devnet) { + return Err("Mining is only available on Regtest and Devnet".to_string()); + } + let ctx = self.clone(); + let mined = tokio::task::spawn_blocking(move || { + ctx.core_client + .read() + .map_err(|e| format!("Core client lock was poisoned: {}", e))? + .generate_to_address(block_count, &address) + .map_err(|e| e.to_string()) + }) + .await + .map_err(|e| format!("Task join error: {}", e))??; + + let mined_count = mined.len() as u64; + + // Refresh wallet balances via RPC so the UI reflects the new coins + let refresh_ctx = self.clone(); + tokio::task::spawn_blocking(move || refresh_ctx.refresh_wallet_info(wallet)) + .await + .map_err(|e| format!("Task join error: {}", e))? + .map_err(|e| format!("Error refreshing wallet after mining: {}", e))?; + + Ok(BackendTaskSuccessResult::MineBlocksSuccess(mined_count)) + } } } diff --git a/src/backend_task/mod.rs b/src/backend_task/mod.rs index 656c0d2a9..5786b1dca 100644 --- a/src/backend_task/mod.rs +++ b/src/backend_task/mod.rs @@ -266,6 +266,9 @@ pub enum BackendTaskSuccessResult { // Broadcast results BroadcastedStateTransition, + + // Mining results (dev mode, Regtest/Devnet only) + MineBlocksSuccess(u64), } impl BackendTaskSuccessResult {} diff --git a/src/ui/wallets/wallets_screen/dialogs.rs b/src/ui/wallets/wallets_screen/dialogs.rs index 5c9b640d2..acf0c2a07 100644 --- a/src/ui/wallets/wallets_screen/dialogs.rs +++ b/src/ui/wallets/wallets_screen/dialogs.rs @@ -4,6 +4,7 @@ use crate::backend_task::core::{CoreTask, PaymentRecipient, WalletPaymentRequest use crate::backend_task::wallet::WalletTask; use crate::model::amount::Amount; use crate::model::wallet::{DerivationPathHelpers, Wallet}; +use crate::ui::MessageType; use crate::ui::components::amount_input::AmountInput; use crate::ui::components::component_trait::{Component, ComponentResponse}; use crate::ui::helpers::copy_text_to_clipboard; @@ -80,6 +81,16 @@ pub(super) struct FundPlatformAddressDialogState { pub pending_fund_after_unlock: bool, } +/// State for the Mine Blocks dialog (dev mode, Regtest/Devnet only) +#[derive(Default)] +pub(super) struct MineDialogState { + pub is_open: bool, + pub core_addresses: Vec<(String, u64)>, + pub selected_address_index: usize, + pub block_count_str: String, + pub error: Option, +} + /// State for the Private Key dialog #[derive(Default)] pub(super) struct PrivateKeyDialogState { @@ -1118,19 +1129,14 @@ impl WalletsBalancesScreen { AppAction::None } - /// Load Core addresses into the receive dialog - fn load_core_addresses_for_receive(&mut self, wallet: &Arc>) { - let wallet_guard = match wallet.read() { - Ok(guard) => guard, - Err(err) => { - self.receive_dialog.status = Some(err.to_string()); - return; - } - }; - - // Collect all BIP44 external (receive) addresses with their balances + /// Load BIP44 external addresses with balances from a wallet. + fn load_bip44_external_addresses( + &self, + wallet: &Arc>, + ) -> Result, String> { + let wallet_guard = wallet.read().map_err(|e| e.to_string())?; let network = self.app_context.network; - let core_addresses: Vec<(String, u64)> = wallet_guard + let addresses: Vec<(String, u64)> = wallet_guard .watched_addresses .iter() .filter(|(path, _)| path.is_bip44_external(network)) @@ -1143,24 +1149,31 @@ impl WalletsBalancesScreen { (info.address.to_string(), balance) }) .collect(); + Ok(addresses) + } - drop(wallet_guard); - - if core_addresses.is_empty() { - // Generate a new Core address if none exists - match self.generate_new_core_receive_address(wallet) { - Ok((address, balance)) => { - self.receive_dialog.core_addresses = vec![(address, balance)]; - self.receive_dialog.selected_core_index = 0; - } - Err(err) => { - self.receive_dialog.status = Some(err); - self.receive_dialog.core_addresses.clear(); + /// Load Core addresses into the receive dialog + fn load_core_addresses_for_receive(&mut self, wallet: &Arc>) { + match self.load_bip44_external_addresses(wallet) { + Ok(addresses) if addresses.is_empty() => { + match self.generate_new_core_receive_address(wallet) { + Ok((address, balance)) => { + self.receive_dialog.core_addresses = vec![(address, balance)]; + self.receive_dialog.selected_core_index = 0; + } + Err(err) => { + self.receive_dialog.status = Some(err); + self.receive_dialog.core_addresses.clear(); + } } } - } else { - self.receive_dialog.core_addresses = core_addresses; - self.receive_dialog.selected_core_index = 0; + Ok(addresses) => { + self.receive_dialog.core_addresses = addresses; + self.receive_dialog.selected_core_index = 0; + } + Err(err) => { + self.receive_dialog.status = Some(err); + } } } @@ -1222,4 +1235,243 @@ impl WalletsBalancesScreen { let private_key = wallet.private_key_at_derivation_path(path, self.app_context.network)?; Ok(private_key.to_wif()) } + + pub(super) fn open_mine_dialog(&mut self) { + let Some(wallet) = self.selected_wallet.clone() else { + self.set_message("Select a wallet first".to_string(), MessageType::Error); + return; + }; + + self.mine_dialog = MineDialogState { + is_open: true, + block_count_str: "1".to_string(), + ..Default::default() + }; + + // Reuse the same address loading pattern as receive dialog + self.load_core_addresses_for_mine(&wallet); + } + + fn load_core_addresses_for_mine(&mut self, wallet: &Arc>) { + match self.load_bip44_external_addresses(wallet) { + Ok(addresses) if addresses.is_empty() => { + match self.generate_new_core_receive_address(wallet) { + Ok((address, balance)) => { + self.mine_dialog.core_addresses = vec![(address, balance)]; + self.mine_dialog.selected_address_index = 0; + } + Err(err) => { + self.mine_dialog.error = Some(err); + self.mine_dialog.core_addresses.clear(); + } + } + } + Ok(addresses) => { + self.mine_dialog.core_addresses = addresses; + self.mine_dialog.selected_address_index = 0; + } + Err(err) => { + self.mine_dialog.error = Some(err); + } + } + } + + pub(super) fn render_mine_dialog(&mut self, ctx: &Context) -> AppAction { + if !self.mine_dialog.is_open { + return AppAction::None; + } + + let mut action = AppAction::None; + let mut open = self.mine_dialog.is_open; + let dark_mode = ctx.style().visuals.dark_mode; + + Self::draw_modal_overlay(ctx, "mine_dialog_overlay"); + + egui::Window::new("Mine Blocks") + .collapsible(false) + .resizable(false) + .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO) + .open(&mut open) + .frame(Self::modal_frame(ctx)) + .show(ctx, |ui| { + ui.set_min_width(350.0); + ui.vertical(|ui| { + ui.label( + RichText::new("Mine blocks to a wallet address:") + .color(DashColors::text_primary(dark_mode)), + ); + ui.add_space(10.0); + + // Address selector + if !self.mine_dialog.core_addresses.is_empty() { + ui.label("Address:"); + ComboBox::from_id_salt("mine_addr_selector") + .selected_text( + self.mine_dialog + .core_addresses + .get(self.mine_dialog.selected_address_index) + .map(|(addr, balance)| { + let balance_dash = *balance as f64 / 1e8; + format!( + "{}... ({:.4} DASH)", + &addr[..12.min(addr.len())], + balance_dash + ) + }) + .unwrap_or_default(), + ) + .width(ui.available_width() - 16.0) + .show_ui(ui, |ui| { + for (idx, (addr, balance)) in + self.mine_dialog.core_addresses.iter().enumerate() + { + let balance_dash = *balance as f64 / 1e8; + let label = format!( + "{}... ({:.4} DASH)", + &addr[..12.min(addr.len())], + balance_dash + ); + if ui + .selectable_label( + idx == self.mine_dialog.selected_address_index, + label, + ) + .clicked() + { + self.mine_dialog.selected_address_index = idx; + } + } + }); + } + + ui.add_space(10.0); + + // Block count input + ui.label("Number of blocks:"); + ui.add( + egui::TextEdit::singleline(&mut self.mine_dialog.block_count_str) + .hint_text("1") + .desired_width(100.0), + ); + self.mine_dialog + .block_count_str + .retain(|c| c.is_ascii_digit()); + + // Error display + if let Some(error) = self.mine_dialog.error.clone() { + ui.add_space(8.0); + let error_color = DashColors::ERROR; + Frame::new() + .fill(error_color.gamma_multiply(0.1)) + .inner_margin(Margin::symmetric(10, 8)) + .corner_radius(5.0) + .stroke(egui::Stroke::new(1.0, error_color)) + .show(ui, |ui| { + ui.horizontal(|ui| { + ui.label( + RichText::new(format!("Error: {}", error)) + .color(error_color), + ); + ui.add_space(10.0); + if ui.small_button("Dismiss").clicked() { + self.mine_dialog.error = None; + } + }); + }); + } + + ui.add_space(15.0); + + // Buttons + ui.horizontal(|ui| { + let cancel_button = egui::Button::new( + RichText::new("Cancel").color(DashColors::text_primary(dark_mode)), + ) + .fill(egui::Color32::TRANSPARENT) + .stroke(egui::Stroke::new( + 1.0, + DashColors::text_secondary(dark_mode), + )) + .corner_radius(egui::CornerRadius::same(4)) + .min_size(egui::Vec2::new(80.0, 32.0)); + + if ui.add(cancel_button).clicked() { + self.mine_dialog = MineDialogState::default(); + } + + ui.add_space(8.0); + + let mine_button = + egui::Button::new(RichText::new("Mine").color(egui::Color32::WHITE)) + .fill(DashColors::DASH_BLUE) + .corner_radius(egui::CornerRadius::same(4)) + .min_size(egui::Vec2::new(80.0, 32.0)); + + if ui.add(mine_button).clicked() { + // Validate and dispatch + const MAX_MINE_BLOCKS: u64 = 1_000; + let block_count: u64 = + match self.mine_dialog.block_count_str.trim().parse() { + Ok(n) if n > 0 && n <= MAX_MINE_BLOCKS => n, + Ok(n) if n > MAX_MINE_BLOCKS => { + self.mine_dialog.error = Some(format!( + "Maximum {} blocks at a time", + MAX_MINE_BLOCKS + )); + return; + } + _ => { + self.mine_dialog.error = Some( + "Enter a valid number of blocks (> 0)".to_string(), + ); + return; + } + }; + + let Some((addr_str, _)) = self + .mine_dialog + .core_addresses + .get(self.mine_dialog.selected_address_index) + else { + self.mine_dialog.error = Some("No address selected".to_string()); + return; + }; + + let address = match addr_str + .parse::>() + .and_then(|a| a.require_network(self.app_context.network)) + { + Ok(addr) => addr, + Err(e) => { + self.mine_dialog.error = + Some(format!("Invalid address: {}", e)); + return; + } + }; + + let Some(wallet) = self.selected_wallet.clone() else { + self.mine_dialog.error = Some("No wallet selected".to_string()); + return; + }; + + action = AppAction::BackendTask(BackendTask::CoreTask( + CoreTask::MineBlocks { + block_count, + address, + wallet, + }, + )); + self.mine_dialog = MineDialogState::default(); + } + }); + }); + }); + + // X button sets `open` to false; Cancel/Mine reset dialog state + // (which sets is_open to false) inside the closure. + if !open || !self.mine_dialog.is_open { + self.mine_dialog = MineDialogState::default(); + } + action + } } diff --git a/src/ui/wallets/wallets_screen/mod.rs b/src/ui/wallets/wallets_screen/mod.rs index d6bf5a002..d5d5df75b 100644 --- a/src/ui/wallets/wallets_screen/mod.rs +++ b/src/ui/wallets/wallets_screen/mod.rs @@ -33,7 +33,8 @@ use std::sync::{Arc, RwLock}; use crate::model::wallet::single_key::SingleKeyWallet; use address_table::{SortColumn, SortOrder}; use dialogs::{ - FundPlatformAddressDialogState, PrivateKeyDialogState, ReceiveDialogState, SendDialogState, + FundPlatformAddressDialogState, MineDialogState, PrivateKeyDialogState, ReceiveDialogState, + SendDialogState, }; /// Refresh mode for dev mode dropdown - controls what gets refreshed @@ -100,6 +101,7 @@ pub struct WalletsBalancesScreen { receive_dialog: ReceiveDialogState, fund_platform_dialog: FundPlatformAddressDialogState, private_key_dialog: PrivateKeyDialogState, + mine_dialog: MineDialogState, selected_account: Option<(AccountCategory, Option)>, show_zero_balance_addresses: bool, /// Pending refresh of platform address balances (triggered after transfers) @@ -193,6 +195,7 @@ impl WalletsBalancesScreen { receive_dialog: ReceiveDialogState::default(), fund_platform_dialog: FundPlatformAddressDialogState::default(), private_key_dialog: PrivateKeyDialogState::default(), + mine_dialog: MineDialogState::default(), selected_account: None, show_zero_balance_addresses: false, pending_platform_balance_refresh: None, @@ -892,6 +895,23 @@ impl WalletsBalancesScreen { { action |= self.open_receive_dialog(ctx); } + + if matches!( + self.app_context.network, + dash_sdk::dpp::dashcore::Network::Regtest + | dash_sdk::dpp::dashcore::Network::Devnet + ) && self.app_context.is_developer_mode() + && self.app_context.core_backend_mode() == CoreBackendMode::Rpc + && ui + .button( + RichText::new("Mine") + .color(DashColors::text_primary(dark_mode)) + .strong(), + ) + .clicked() + { + self.open_mine_dialog(); + } }); action } @@ -1453,6 +1473,7 @@ impl ScreenLike for WalletsBalancesScreen { action |= self.render_send_dialog(ctx); action |= self.render_receive_dialog(ctx); action |= self.render_fund_platform_dialog(ctx); + action |= self.render_mine_dialog(ctx); self.render_private_key_dialog(ctx); // Rename dialog @@ -1914,6 +1935,10 @@ impl ScreenLike for WalletsBalancesScreen { self.refreshing = false; self.display_message(&msg, MessageType::Success); } + crate::ui::BackendTaskSuccessResult::MineBlocksSuccess(count) => { + self.refreshing = false; + self.display_message(&format!("Mined {} block(s)", count), MessageType::Success); + } _ => {} } }