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},
OnlineClient,
};
#[derive(Debug, Clone, Copy, Default)]
pub struct ExecutionMode {
pub finalized: bool,
pub wait_for_transaction: bool,
}
pub fn resolve_address(address_or_wallet_name: &str) -> Result<String> {
if AccountId32::from_ss58check_with_version(address_or_wallet_name).is_ok() {
return Ok(address_or_wallet_name.to_string());
}
let wallet_manager = crate::wallet::WalletManager::new()?;
if let Some(wallet_address) = wallet_manager.find_wallet_address(address_or_wallet_name)? {
log_verbose!(
"🔍 Found wallet '{}' with address: {}",
address_or_wallet_name.bright_cyan(),
wallet_address.bright_green()
);
return Ok(wallet_address);
}
Err(crate::error::QuantusError::Generic(format!(
"Invalid destination: '{address_or_wallet_name}' is neither a valid SS58 address nor a known wallet name"
)))
}
pub async fn get_fresh_nonce_with_client(
quantus_client: &crate::chain::client::QuantusClient,
from_keypair: &crate::wallet::QuantumKeyPair,
) -> Result<u64> {
let (from_account_id, _version) =
AccountId32::from_ss58check_with_version(&from_keypair.to_account_id_ss58check()).map_err(
|e| crate::error::QuantusError::NetworkError(format!("Invalid from address: {e:?}")),
)?;
let latest_nonce = quantus_client
.get_account_nonce_from_best_block(&from_account_id)
.await
.map_err(|e| {
crate::error::QuantusError::NetworkError(format!(
"Failed to get account nonce from best block: {e:?}"
))
})?;
log_verbose!("🔢 Using fresh nonce from latest block: {}", latest_nonce);
let finalized_nonce = quantus_client
.client()
.tx()
.account_nonce(&from_account_id)
.await
.map_err(|e| {
crate::error::QuantusError::NetworkError(format!(
"Failed to get account nonce from finalized block: {e:?}"
))
})?;
if latest_nonce != finalized_nonce {
log_verbose!(
"⚠️ Nonce difference detected! Latest: {}, Finalized: {}",
latest_nonce,
finalized_nonce
);
}
Ok(latest_nonce)
}
pub async fn get_incremented_nonce_with_client(
quantus_client: &crate::chain::client::QuantusClient,
from_keypair: &crate::wallet::QuantumKeyPair,
base_nonce: u64,
) -> Result<u64> {
let (from_account_id, _version) =
AccountId32::from_ss58check_with_version(&from_keypair.to_account_id_ss58check()).map_err(
|e| crate::error::QuantusError::NetworkError(format!("Invalid from address: {e:?}")),
)?;
let current_nonce = quantus_client
.get_account_nonce_from_best_block(&from_account_id)
.await
.map_err(|e| {
crate::error::QuantusError::NetworkError(format!(
"Failed to get account nonce from best block: {e:?}"
))
})?;
let incremented_nonce = std::cmp::max(current_nonce, base_nonce + 1);
log_verbose!(
"🔢 Using incremented nonce: {} (base: {}, current from latest block: {})",
incremented_nonce,
base_nonce,
current_nonce
);
Ok(incremented_nonce)
}
pub async fn submit_transaction<Call>(
quantus_client: &crate::chain::client::QuantusClient,
from_keypair: &crate::wallet::QuantumKeyPair,
call: Call,
tip: Option<u128>,
execution_mode: ExecutionMode,
) -> crate::error::Result<subxt::utils::H256>
where
Call: subxt::tx::Payload,
{
let signer = from_keypair.to_subxt_signer().map_err(|e| {
crate::error::QuantusError::NetworkError(format!("Failed to convert keypair: {e:?}"))
})?;
let mut attempt = 0;
let mut current_nonce = None;
loop {
attempt += 1;
let nonce = if let Some(prev_nonce) = current_nonce {
let incremented_nonce =
get_incremented_nonce_with_client(quantus_client, from_keypair, prev_nonce).await?;
log_verbose!(
"🔢 Using incremented nonce from best block: {} (previous: {})",
incremented_nonce,
prev_nonce
);
incremented_nonce
} else {
let fresh_nonce = get_fresh_nonce_with_client(quantus_client, from_keypair).await?;
log_verbose!("🔢 Using fresh nonce from best block: {}", fresh_nonce);
fresh_nonce
};
current_nonce = Some(nonce);
let latest_block_hash = quantus_client.get_latest_block().await.map_err(|e| {
crate::error::QuantusError::NetworkError(format!("Failed to get latest block: {e:?}"))
})?;
log_verbose!("🔗 Latest block hash: {:?}", latest_block_hash);
use subxt::config::DefaultExtrinsicParamsBuilder;
let mut params_builder = DefaultExtrinsicParamsBuilder::new()
.mortal(256) .nonce(nonce);
if let Some(tip_amount) = tip {
params_builder = params_builder.tip(tip_amount);
log_verbose!("💰 Using tip: {} to increase priority", tip_amount);
} else {
log_verbose!("💰 No tip specified, using default priority");
}
let params = params_builder.build();
log_verbose!("🔍 Transaction parameters:");
log_verbose!(" Nonce: {}", nonce);
log_verbose!(" Tip: {:?}", tip);
log_verbose!(" Latest block hash: {:?}", latest_block_hash);
log_verbose!(" Era: Using default era from SubXT");
log_verbose!(" Genesis hash: Using default from SubXT");
log_verbose!(" Spec version: Using default from SubXT");
log_verbose!("🔍 Additional debugging:");
log_verbose!(" Call type: {:?}", std::any::type_name::<Call>());
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_verbose!("📝 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()
.tx()
.sign_and_submit_then_watch(&call, &signer, params)
.await
{
Ok(mut tx_progress) => {
crate::log_verbose!("📋 Transaction submitted: {:?}", tx_progress);
let tx_hash = tx_progress.extrinsic_hash();
if !execution_mode.wait_for_transaction {
return Ok(tx_hash);
}
wait_tx_inclusion(
&mut tx_progress,
quantus_client.client(),
&tx_hash,
execution_mode.finalized,
)
.await?;
return Ok(tx_hash);
},
Err(e) => {
let error_msg = format!("{e:?}");
let is_retryable = error_msg.contains("Priority is too low") ||
error_msg.contains("Transaction is outdated") ||
error_msg.contains("Transaction is temporarily banned") ||
error_msg.contains("Transaction has a bad signature") ||
error_msg.contains("Invalid Transaction");
if is_retryable && attempt < 5 {
log_verbose!(
"⚠️ Transaction error detected (attempt {}/5): {}",
attempt,
error_msg
);
let delay = std::cmp::min(2u64.pow(attempt as u32), 16);
log_verbose!("⏳ Waiting {} seconds before retry...", delay);
tokio::time::sleep(tokio::time::Duration::from_secs(delay)).await;
continue;
} else {
log_verbose!("❌ Final error after {} attempts: {}", attempt, error_msg);
return Err(crate::error::QuantusError::NetworkError(format!(
"Failed to submit transaction: {e:?}"
)));
}
},
}
} else {
match quantus_client.client().tx().sign_and_submit(&call, &signer, params).await {
Ok(tx_hash) => {
crate::log_print!("✅ Transaction submitted: {:?}", tx_hash);
return Ok(tx_hash);
},
Err(e) => {
log_error!("❌ Failed to submit transaction: {e:?}");
return Err(crate::error::QuantusError::NetworkError(format!(
"Failed to submit transaction: {e:?}"
)));
},
}
}
}
}
pub async fn submit_transaction_with_nonce<Call>(
quantus_client: &crate::chain::client::QuantusClient,
from_keypair: &crate::wallet::QuantumKeyPair,
call: Call,
tip: Option<u128>,
nonce: u32,
execution_mode: ExecutionMode,
) -> crate::error::Result<subxt::utils::H256>
where
Call: subxt::tx::Payload,
{
let signer = from_keypair.to_subxt_signer().map_err(|e| {
crate::error::QuantusError::NetworkError(format!("Failed to convert keypair: {e:?}"))
})?;
let latest_block_hash = quantus_client.get_latest_block().await.map_err(|e| {
crate::error::QuantusError::NetworkError(format!("Failed to get latest block: {e:?}"))
})?;
log_verbose!("🔗 Latest block hash: {:?}", latest_block_hash);
use subxt::config::DefaultExtrinsicParamsBuilder;
let mut params_builder = DefaultExtrinsicParamsBuilder::new()
.mortal(256) .nonce(nonce.into());
if let Some(tip_amount) = tip {
params_builder = params_builder.tip(tip_amount);
log_verbose!("💰 Using tip: {}", tip_amount);
}
let params = params_builder.build();
log_verbose!("🔢 Using manual nonce: {}", nonce);
log_verbose!("📤 Submitting transaction with manual nonce...");
crate::log_print!("submit with wait for transaction: {}", execution_mode.wait_for_transaction);
if execution_mode.wait_for_transaction {
match quantus_client
.client()
.tx()
.sign_and_submit_then_watch(&call, &signer, params)
.await
{
Ok(mut tx_progress) => {
let tx_hash = tx_progress.extrinsic_hash();
crate::log_print!("✅ Transaction submitted: {:?}", tx_hash);
wait_tx_inclusion(
&mut tx_progress,
quantus_client.client(),
&tx_hash,
execution_mode.finalized,
)
.await?;
Ok(tx_hash)
},
Err(e) => {
log_error!("❌ Failed to submit transaction with manual nonce {}: {e:?}", nonce);
Err(crate::error::QuantusError::NetworkError(format!(
"Failed to submit transaction with nonce {nonce}: {e:?}"
)))
},
}
} else {
match quantus_client.client().tx().sign_and_submit(&call, &signer, params).await {
Ok(tx_hash) => {
crate::log_print!("✅ Transaction submitted: {:?}", tx_hash);
Ok(tx_hash)
},
Err(e) => {
log_error!("❌ Failed to submit transaction: {e:?}");
Err(crate::error::QuantusError::NetworkError(format!(
"Failed to submit transaction: {e:?}"
)))
},
}
}
}
async fn wait_tx_inclusion(
tx_progress: &mut TxProgress<ChainConfig, OnlineClient<ChainConfig>>,
client: &OnlineClient<ChainConfig>,
tx_hash: &subxt::utils::H256,
finalized: bool,
) -> Result<()> {
use indicatif::{ProgressBar, ProgressStyle};
let start_time = std::time::Instant::now();
let spinner = if !crate::log::is_verbose() {
let pb = ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::default_spinner()
.tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
.template("{spinner:.cyan} {msg}")
.unwrap(),
);
if finalized {
pb.set_message("Waiting for finalized block... (0s)");
} else {
pb.set_message("Waiting for block inclusion... (0s)");
}
pb.enable_steady_tick(std::time::Duration::from_millis(500));
Some(pb)
} else {
None
};
while let Some(Ok(status)) = tx_progress.next().await {
let elapsed_secs = start_time.elapsed().as_secs();
crate::log_verbose!(" Transaction status: {:?} (elapsed: {}s)", status, elapsed_secs);
match status {
TxStatus::Validated =>
if let Some(ref pb) = spinner {
pb.set_message(format!("Transaction validated ✓ ({}s)", elapsed_secs));
},
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!(
"In best block, waiting for finalization... ({}s)",
elapsed_secs
));
}
continue;
} else {
if let Some(pb) = spinner {
pb.finish_with_message(format!(
"✅ Transaction included in block! ({}s)",
elapsed_secs
));
}
break;
};
},
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)",
elapsed_secs
));
}
break;
},
TxStatus::Error { message } | TxStatus::Invalid { message } => {
crate::log_error!(" Transaction error: {} (elapsed: {}s)", message, elapsed_secs);
if let Some(pb) = spinner {
pb.finish_with_message(format!("❌ Transaction error! ({}s)", elapsed_secs));
}
break;
},
_ => {
if let Some(ref pb) = spinner {
if finalized {
pb.set_message(format!(
"Waiting for finalized block... ({}s)",
elapsed_secs
));
} else {
pb.set_message(format!(
"Waiting for block inclusion... ({}s)",
elapsed_secs
));
}
}
continue;
},
}
}
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_index = module_error.index;
let error_index = module_error.error[0];
if let Some(pallet) = metadata.pallet_by_index(pallet_index) {
let pallet_name = pallet.name();
if let Some(variant) = pallet.error_variant_by_index(error_index) {
let error_name = &variant.name;
let docs = variant.docs.join(" ");
if docs.is_empty() {
format!("{}::{}", pallet_name, error_name)
} else {
format!("{}::{} - {}", pallet_name, error_name, docs)
}
} else {
format!("{}::Error[{}]", pallet_name, error_index)
}
} else {
format!("Pallet[{}]::Error[{}]", pallet_index, error_index)
}
},
DispatchError::BadOrigin => "BadOrigin".to_string(),
DispatchError::CannotLookup => "CannotLookup".to_string(),
DispatchError::Other => "Other".to_string(),
_ => format!("{:?}", error),
}
}
pub async fn submit_preimage(
quantus_client: &crate::chain::client::QuantusClient,
keypair: &crate::wallet::QuantumKeyPair,
encoded_call: Vec<u8>,
execution_mode: ExecutionMode,
) -> Result<()> {
type PreimageBytes =
crate::chain::quantus_subxt::api::preimage::calls::types::note_preimage::Bytes;
let bounded_bytes: PreimageBytes = encoded_call;
crate::log_print!("📝 Submitting preimage...");
let note_preimage_tx =
crate::chain::quantus_subxt::api::tx().preimage().note_preimage(bounded_bytes);
let wait_mode = ExecutionMode { wait_for_transaction: true, ..execution_mode };
match submit_transaction(quantus_client, keypair, note_preimage_tx, None, wait_mode).await {
Ok(_) => {
crate::log_success!("Preimage submitted");
},
Err(e) if e.to_string().contains("AlreadyNoted") => {
crate::log_print!(
"✅ {} Preimage already exists on-chain, continuing",
"OK".bright_green().bold()
);
},
Err(e) => return Err(e),
}
Ok(())
}
async fn check_execution_success(
client: &OnlineClient<ChainConfig>,
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::<ExtrinsicFailed>()
{
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(())
}