diff --git a/src/app.rs b/src/app.rs index c9aff302a..4cfb6c058 100644 --- a/src/app.rs +++ b/src/app.rs @@ -820,6 +820,7 @@ impl AppState { // Warm up the Halo 2 ProvingKey in a background thread (~30s build). // This ensures the key is ready for the user's first shielded operation. + #[cfg(not(feature = "testing"))] std::thread::spawn(|| { let _ = crate::context::shielded::get_proving_key(); tracing::info!("Halo 2 ProvingKey built and cached"); diff --git a/src/backend_task/core/mod.rs b/src/backend_task/core/mod.rs index 2136b401b..11e460815 100644 --- a/src/backend_task/core/mod.rs +++ b/src/backend_task/core/mod.rs @@ -197,10 +197,10 @@ impl AppContext { if let Some(task_err) = Self::chain_lock_rpc_error(active_config, e) { return Err(task_err); } - // Non-auth, non-connection error — show the actual error + // Non-auth, non-connection error — show a sanitized message // in the Networks page status display for debugging. tracing::warn!(network = ?self.network, error = %e, "Chain lock query failed on active network"); - Some(format!("RPC error: {e}")) + Some(sanitize_rpc_error(&e.to_string())) } else { // Successful chain lock fetch — clear any lingering RPC error // so the connection status recovers after a transient outage. @@ -697,17 +697,15 @@ impl AppContext { // Get UTXOs and change address from the wallet account let (utxos, change_index) = { - let managed_info = - wm.get_wallet_info(wallet_id) - .ok_or_else(|| TaskError::WalletPaymentFailed { - detail: "Wallet info unavailable".to_string(), - })?; + let managed_info = wm + .get_wallet_info(wallet_id) + .ok_or(TaskError::WalletInfoUnavailable)?; let account = managed_info .accounts() .standard_bip44_accounts .get(&DEFAULT_BIP44_ACCOUNT_INDEX) - .ok_or_else(|| TaskError::WalletPaymentFailed { - detail: "BIP44 account missing".to_string(), + .ok_or(TaskError::MissingBip44Account { + index: DEFAULT_BIP44_ACCOUNT_INDEX, })?; let utxos: Vec<_> = account.utxos.values().cloned().collect(); @@ -717,21 +715,17 @@ impl AppContext { let wallet = wm .get_wallet(wallet_id) - .ok_or_else(|| TaskError::WalletPaymentFailed { - detail: "Wallet object not found".to_string(), - })?; + .ok_or(TaskError::WalletInfoUnavailable)?; let wallet_account = wallet .accounts .standard_bip44_accounts .get(&DEFAULT_BIP44_ACCOUNT_INDEX) - .ok_or_else(|| TaskError::WalletPaymentFailed { - detail: "BIP44 wallet account missing".to_string(), + .ok_or(TaskError::MissingBip44Account { + index: DEFAULT_BIP44_ACCOUNT_INDEX, })?; let change_addr = wallet_account .derive_change_address(change_index) - .map_err(|e| TaskError::WalletPaymentFailed { - detail: format!("Failed to derive change address: {e}"), - })?; + .map_err(|e| TaskError::ChangeAddressDerivation { source: e })?; loop { let scaled_recipients: Vec<(Address, u64)> = recipients @@ -801,17 +795,15 @@ impl AppContext { account_index: u32, current_height: u32, ) -> Result { - let managed_info = - wm.get_wallet_info(wallet_id) - .ok_or_else(|| TaskError::WalletPaymentFailed { - detail: "Wallet info unavailable".to_string(), - })?; + let managed_info = wm + .get_wallet_info(wallet_id) + .ok_or(TaskError::WalletInfoUnavailable)?; let collection = managed_info.accounts(); let account = collection .standard_bip44_accounts .get(&account_index) - .ok_or_else(|| TaskError::WalletPaymentFailed { - detail: "BIP44 account missing".to_string(), + .ok_or(TaskError::MissingBip44Account { + index: account_index, })?; let mut spendable_total = 0u64; @@ -896,20 +888,16 @@ impl AppContext { ) -> Result { let wallet = wm .get_wallet(wallet_id) - .ok_or_else(|| TaskError::WalletPaymentFailed { - detail: "Wallet object not found".to_string(), - })?; - let managed_info = - wm.get_wallet_info(wallet_id) - .ok_or_else(|| TaskError::WalletPaymentFailed { - detail: "Wallet info unavailable".to_string(), - })?; + .ok_or(TaskError::WalletInfoUnavailable)?; + let managed_info = wm + .get_wallet_info(wallet_id) + .ok_or(TaskError::WalletInfoUnavailable)?; let accounts = managed_info.accounts(); let account = accounts .standard_bip44_accounts .get(&DEFAULT_BIP44_ACCOUNT_INDEX) - .ok_or_else(|| TaskError::WalletPaymentFailed { - detail: "BIP44 account missing".to_string(), + .ok_or(TaskError::MissingBip44Account { + index: DEFAULT_BIP44_ACCOUNT_INDEX, })?; let secp = Secp256k1::new(); @@ -999,3 +987,29 @@ impl AppContext { size } } + +/// Sanitize raw RPC error strings for display in connection status. +/// +/// Strips OS-level error details and transport/RPC wrappers, keeping only +/// the meaningful description for the Networks page status display. +fn sanitize_rpc_error(raw: &str) -> String { + let mut s = raw.to_string(); + + // Strip trailing OS error codes: "Connection refused (os error 111)" -> "Connection refused" + if let Some(pos) = s.find("(os error") { + s = s[..pos].trim_end().to_string(); + } + + // Strip nested wrapper prefixes to get the actual error message + for prefix in &["RPC error:", "transport error:", "JSON-RPC error:"] { + if let Some(pos) = s.find(prefix) { + s = s[pos + prefix.len()..].trim_start().to_string(); + } + } + + if s.is_empty() { + "Could not reach the node.".to_string() + } else { + s + } +} diff --git a/src/backend_task/error.rs b/src/backend_task/error.rs index 8e22bed9b..d7b3ab8dd 100644 --- a/src/backend_task/error.rs +++ b/src/backend_task/error.rs @@ -676,6 +676,21 @@ pub enum TaskError { #[error("Could not complete the payment. Please check your wallet balance and retry.")] WalletPaymentFailed { detail: String }, + /// Could not access wallet information from the SPV manager. + #[error("Your wallet is still loading. Please wait a moment and try again.")] + WalletInfoUnavailable, + + /// Expected BIP44 account not found at the given index. + #[error("Your wallet needs to be refreshed before sending. Please refresh and try again.")] + MissingBip44Account { index: u32 }, + + /// Could not derive a change address from the wallet account. + #[error("Could not prepare the transaction. Please refresh your wallet and try again.")] + ChangeAddressDerivation { + #[source] + source: dash_sdk::dpp::key_wallet::Error, + }, + // ────────────────────────────────────────────────────────────────────────── // Token query errors (identity / recipient validation) // ────────────────────────────────────────────────────────────────────────── diff --git a/src/ui/helpers.rs b/src/ui/helpers.rs index a522eb721..24768f296 100644 --- a/src/ui/helpers.rs +++ b/src/ui/helpers.rs @@ -1115,18 +1115,16 @@ pub fn show_group_token_success_screen_with_fee( /// Check if a string looks like a Platform Bech32m address. /// -/// Supports both the current prefix (`dash1`/`tdash1`) and the legacy -/// prefix (`evo1`/`tevo1`) so that old addresses stored in the DB or -/// copied from older tools continue to work. +/// Delegates to [`is_platform_address_string`] which uses the canonical +/// HRP constants and case-insensitive comparison. pub fn is_platform_address(s: &str) -> bool { - s.starts_with("dash1") - || s.starts_with("tdash1") - || s.starts_with("evo1") - || s.starts_with("tevo1") + is_platform_address_string(s) } /// Human-readable hint for Platform address input fields. -pub const PLATFORM_ADDRESS_HINT: &str = "dash1... or tdash1..."; +pub const PLATFORM_ADDRESS_HINT: &str = + "Enter a Platform address starting with \"dash1\" or \"tdash1\"."; /// Example Platform address prefixes for error messages. -pub const PLATFORM_ADDRESS_EXAMPLES: &str = "dash1.../tdash1..."; +pub const PLATFORM_ADDRESS_EXAMPLES: &str = + "Valid prefixes are \"dash1\" for mainnet and \"tdash1\" for testnet."; diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 1165e9e3a..317a200b1 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -429,13 +429,13 @@ impl PartialEq for ScreenType { (ScreenType::DashPayQRGenerator, ScreenType::DashPayQRGenerator) => true, (ScreenType::DashPayProfileSearch, ScreenType::DashPayProfileSearch) => true, // Shielded screens - (ScreenType::ShieldCreditsScreen(_), ScreenType::ShieldCreditsScreen(_)) => true, + (ScreenType::ShieldCreditsScreen(a), ScreenType::ShieldCreditsScreen(b)) => a == b, ( - ScreenType::ShieldFromAssetLockScreen(_), - ScreenType::ShieldFromAssetLockScreen(_), - ) => true, - (ScreenType::ShieldedSendScreen(_), ScreenType::ShieldedSendScreen(_)) => true, - (ScreenType::UnshieldCreditsScreen(_), ScreenType::UnshieldCreditsScreen(_)) => true, + ScreenType::ShieldFromAssetLockScreen(a), + ScreenType::ShieldFromAssetLockScreen(b), + ) => a == b, + (ScreenType::ShieldedSendScreen(a), ScreenType::ShieldedSendScreen(b)) => a == b, + (ScreenType::UnshieldCreditsScreen(a), ScreenType::UnshieldCreditsScreen(b)) => a == b, _ => false, } } diff --git a/src/ui/wallets/shielded_tab.rs b/src/ui/wallets/shielded_tab.rs index db916a140..ee29a20d1 100644 --- a/src/ui/wallets/shielded_tab.rs +++ b/src/ui/wallets/shielded_tab.rs @@ -59,6 +59,10 @@ impl ShieldedTabView { self.shielded_balance = 0; self.error_message = None; self.success_message = None; + self.initializing = false; + self.syncing = false; + self.pending_task = None; + self.address_count = 1; } }