diff --git a/src/cli/common.rs b/src/cli/common.rs index 74fd89f..32cda72 100644 --- a/src/cli/common.rs +++ b/src/cli/common.rs @@ -1,6 +1,7 @@ //! Common SubXT utilities and functions shared across CLI commands use crate::{chain::client::ChainConfig, error::Result, log_error, log_verbose}; use colored::Colorize; +use hex; use sp_core::crypto::{AccountId32, Ss58Codec}; use subxt::{ tx::{TxProgress, TxStatus}, @@ -210,6 +211,14 @@ where log_verbose!("🔍 Additional debugging:"); log_verbose!(" Call type: {:?}", std::any::type_name::()); + let metadata = quantus_client.client().metadata(); + let encoded_call = + <_ as subxt::tx::Payload>::encode_call_data(&call, &metadata).map_err(|e| { + crate::error::QuantusError::NetworkError(format!("Failed to encode call: {:?}", e)) + })?; + crate::log_print!("📝 Encoded call: 0x{}", hex::encode(&encoded_call)); + crate::log_print!("📝 Encoded call size: {} bytes", encoded_call.len()); + if execution_mode.wait_for_transaction { match quantus_client .client() @@ -226,7 +235,13 @@ where return Ok(tx_hash); } - wait_tx_inclusion(&mut tx_progress, execution_mode.finalized).await?; + wait_tx_inclusion( + &mut tx_progress, + quantus_client.client(), + &tx_hash, + execution_mode.finalized, + ) + .await?; return Ok(tx_hash); }, @@ -329,7 +344,13 @@ where Ok(mut tx_progress) => { let tx_hash = tx_progress.extrinsic_hash(); crate::log_print!("✅ Transaction submitted: {:?}", tx_hash); - wait_tx_inclusion(&mut tx_progress, execution_mode.finalized).await?; + wait_tx_inclusion( + &mut tx_progress, + quantus_client.client(), + &tx_hash, + execution_mode.finalized, + ) + .await?; Ok(tx_hash) }, Err(e) => { @@ -362,6 +383,8 @@ where /// it up to the user to check the status of the transaction. async fn wait_tx_inclusion( tx_progress: &mut TxProgress>, + client: &OnlineClient, + tx_hash: &subxt::utils::H256, finalized: bool, ) -> Result<()> { use indicatif::{ProgressBar, ProgressStyle}; @@ -398,8 +421,10 @@ async fn wait_tx_inclusion( if let Some(ref pb) = spinner { pb.set_message(format!("Transaction validated ✓ ({}s)", elapsed_secs)); }, - TxStatus::InBestBlock(block_hash) => { + TxStatus::InBestBlock(tx_in_block) => { + let block_hash = tx_in_block.block_hash(); crate::log_verbose!(" Transaction included in block: {:?}", block_hash); + check_execution_success(client, &block_hash, tx_hash).await?; if finalized { if let Some(ref pb) = spinner { pb.set_message(format!( @@ -418,8 +443,10 @@ async fn wait_tx_inclusion( break; }; }, - TxStatus::InFinalizedBlock(block_hash) => { + TxStatus::InFinalizedBlock(tx_in_block) => { + let block_hash = tx_in_block.block_hash(); crate::log_verbose!(" Transaction finalized in block: {:?}", block_hash); + check_execution_success(client, &block_hash, tx_hash).await?; if let Some(pb) = spinner { pb.finish_with_message(format!( "✅ Transaction finalized! ({}s)", @@ -456,3 +483,77 @@ async fn wait_tx_inclusion( Ok(()) } + +fn format_dispatch_error( + error: &crate::chain::quantus_subxt::api::runtime_types::sp_runtime::DispatchError, + metadata: &subxt::Metadata, +) -> String { + use crate::chain::quantus_subxt::api::runtime_types::sp_runtime::DispatchError; + + match error { + DispatchError::Module(module_error) => { + let pallet_name = metadata + .pallet_by_index(module_error.index) + .map(|p| p.name()) + .unwrap_or("Unknown"); + let error_index = module_error.error[0]; + format!("{}::Error[{}]", pallet_name, error_index) + }, + DispatchError::BadOrigin => "BadOrigin".to_string(), + DispatchError::CannotLookup => "CannotLookup".to_string(), + DispatchError::Other => "Other".to_string(), + _ => format!("{:?}", error), + } +} + +async fn check_execution_success( + client: &OnlineClient, + block_hash: &subxt::utils::H256, + tx_hash: &subxt::utils::H256, +) -> Result<()> { + use crate::chain::quantus_subxt::api::system::events::ExtrinsicFailed; + + let block = client.blocks().at(*block_hash).await.map_err(|e| { + crate::error::QuantusError::NetworkError(format!("Failed to get block: {e:?}")) + })?; + + let extrinsics = block.extrinsics().await.map_err(|e| { + crate::error::QuantusError::NetworkError(format!("Failed to get extrinsics: {e:?}")) + })?; + + let our_extrinsic_index = extrinsics + .iter() + .enumerate() + .find(|(_, ext)| ext.hash() == *tx_hash) + .map(|(idx, _)| idx); + + let events = block.events().await.map_err(|e| { + crate::error::QuantusError::NetworkError(format!("Failed to fetch events: {e:?}")) + })?; + + let metadata = client.metadata(); + if let Some(ext_idx) = our_extrinsic_index { + for event_result in events.iter() { + let event = event_result.map_err(|e| { + crate::error::QuantusError::NetworkError(format!("Failed to decode event: {e:?}")) + })?; + + if let subxt::events::Phase::ApplyExtrinsic(event_ext_idx) = event.phase() { + if event_ext_idx == ext_idx as u32 { + if let Ok(Some(ExtrinsicFailed { dispatch_error, .. })) = + event.as_event::() + { + let error_msg = format_dispatch_error(&dispatch_error, &metadata); + crate::log_error!(" Transaction failed: {}", error_msg); + return Err(crate::error::QuantusError::NetworkError(format!( + "Transaction execution failed: {}", + error_msg + ))); + } + } + } + } + } + + Ok(()) +} diff --git a/src/cli/high_security.rs b/src/cli/high_security.rs index ac971d2..d03f726 100644 --- a/src/cli/high_security.rs +++ b/src/cli/high_security.rs @@ -42,6 +42,13 @@ pub enum HighSecurityCommands { #[arg(long)] password_file: Option, }, + + /// Show all accounts that this account is guardian for + Entrusted { + /// Guardian account address (SS58 or wallet name) + #[arg(short, long)] + from: String, + }, } /// Handle high security commands @@ -173,5 +180,56 @@ pub async fn handle_high_security_command( Ok(()) }, + + HighSecurityCommands::Entrusted { from } => { + log_print!("🔍 Checking entrusted accounts"); + + // Resolve guardian account address + let guardian_resolved = crate::cli::common::resolve_address(&from)?; + let guardian_sp = SpAccountId32::from_ss58check(&guardian_resolved).map_err(|e| { + crate::error::QuantusError::Generic(format!( + "Invalid guardian address '{guardian_resolved}': {e:?}" + )) + })?; + let guardian_bytes: [u8; 32] = *guardian_sp.as_ref(); + let guardian_account = subxt::ext::subxt_core::utils::AccountId32::from(guardian_bytes); + + // Convert guardian to Quantus SS58 format + let guardian_ss58 = guardian_account.to_quantus_ss58(); + + // Query storage for entrusted accounts + let storage_addr = quantus_subxt::api::storage() + .reversible_transfers() + .interceptor_index(guardian_account); + let latest = quantus_client.get_latest_block().await?; + let value = quantus_client + .client() + .storage() + .at(latest) + .fetch(&storage_addr) + .await + .map_err(|e| { + crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}")) + })?; + + log_print!("🛡️ Guardian: {}", guardian_ss58.bright_cyan()); + + if let Some(entrusted_accounts) = value { + if entrusted_accounts.0.is_empty() { + log_print!("📋 No entrusted accounts found."); + } else { + log_success!("✅ Found {} entrusted account(s):", entrusted_accounts.0.len()); + + for (index, account_id) in entrusted_accounts.0.iter().enumerate() { + let account_ss58 = account_id.to_quantus_ss58(); + log_print!(" {}. {}", index + 1, account_ss58.bright_green()); + } + } + } else { + log_print!("📋 No entrusted accounts found."); + } + + Ok(()) + }, } }