1use crate::{chain::client::ChainConfig, error::Result, log_error, log_verbose};
3use colored::Colorize;
4use hex;
5use sp_core::crypto::{AccountId32, Ss58Codec};
6use subxt::{
7 tx::{TxProgress, TxStatus},
8 OnlineClient,
9};
10
11#[derive(Debug, Clone, Copy, Default)]
12pub struct ExecutionMode {
13 pub finalized: bool,
14 pub wait_for_transaction: bool,
15}
16
17pub fn resolve_address(address_or_wallet_name: &str) -> Result<String> {
20 if AccountId32::from_ss58check_with_version(address_or_wallet_name).is_ok() {
22 return Ok(address_or_wallet_name.to_string());
24 }
25
26 let wallet_manager = crate::wallet::WalletManager::new()?;
28 if let Some(wallet_address) = wallet_manager.find_wallet_address(address_or_wallet_name)? {
29 log_verbose!(
30 "π Found wallet '{}' with address: {}",
31 address_or_wallet_name.bright_cyan(),
32 wallet_address.bright_green()
33 );
34 return Ok(wallet_address);
35 }
36
37 Err(crate::error::QuantusError::Generic(format!(
39 "Invalid destination: '{address_or_wallet_name}' is neither a valid SS58 address nor a known wallet name"
40 )))
41}
42
43pub async fn get_fresh_nonce_with_client(
47 quantus_client: &crate::chain::client::QuantusClient,
48 from_keypair: &crate::wallet::QuantumKeyPair,
49) -> Result<u64> {
50 let (from_account_id, _version) =
51 AccountId32::from_ss58check_with_version(&from_keypair.to_account_id_ss58check()).map_err(
52 |e| crate::error::QuantusError::NetworkError(format!("Invalid from address: {e:?}")),
53 )?;
54
55 let latest_nonce = quantus_client
57 .get_account_nonce_from_best_block(&from_account_id)
58 .await
59 .map_err(|e| {
60 crate::error::QuantusError::NetworkError(format!(
61 "Failed to get account nonce from best block: {e:?}"
62 ))
63 })?;
64
65 log_verbose!("π’ Using fresh nonce from latest block: {}", latest_nonce);
66
67 let finalized_nonce = quantus_client
69 .client()
70 .tx()
71 .account_nonce(&from_account_id)
72 .await
73 .map_err(|e| {
74 crate::error::QuantusError::NetworkError(format!(
75 "Failed to get account nonce from finalized block: {e:?}"
76 ))
77 })?;
78
79 if latest_nonce != finalized_nonce {
80 log_verbose!(
81 "β οΈ Nonce difference detected! Latest: {}, Finalized: {}",
82 latest_nonce,
83 finalized_nonce
84 );
85 }
86
87 Ok(latest_nonce)
88}
89
90pub async fn get_incremented_nonce_with_client(
93 quantus_client: &crate::chain::client::QuantusClient,
94 from_keypair: &crate::wallet::QuantumKeyPair,
95 base_nonce: u64,
96) -> Result<u64> {
97 let (from_account_id, _version) =
98 AccountId32::from_ss58check_with_version(&from_keypair.to_account_id_ss58check()).map_err(
99 |e| crate::error::QuantusError::NetworkError(format!("Invalid from address: {e:?}")),
100 )?;
101
102 let current_nonce = quantus_client
104 .get_account_nonce_from_best_block(&from_account_id)
105 .await
106 .map_err(|e| {
107 crate::error::QuantusError::NetworkError(format!(
108 "Failed to get account nonce from best block: {e:?}"
109 ))
110 })?;
111
112 let incremented_nonce = std::cmp::max(current_nonce, base_nonce + 1);
114 log_verbose!(
115 "π’ Using incremented nonce: {} (base: {}, current from latest block: {})",
116 incremented_nonce,
117 base_nonce,
118 current_nonce
119 );
120 Ok(incremented_nonce)
121}
122
123pub async fn submit_transaction<Call>(
129 quantus_client: &crate::chain::client::QuantusClient,
130 from_keypair: &crate::wallet::QuantumKeyPair,
131 call: Call,
132 tip: Option<u128>,
133 execution_mode: ExecutionMode,
134) -> crate::error::Result<subxt::utils::H256>
135where
136 Call: subxt::tx::Payload,
137{
138 let signer = from_keypair.to_subxt_signer().map_err(|e| {
139 crate::error::QuantusError::NetworkError(format!("Failed to convert keypair: {e:?}"))
140 })?;
141
142 let mut attempt = 0;
144 let mut current_nonce = None;
145
146 loop {
147 attempt += 1;
148 let nonce = if let Some(prev_nonce) = current_nonce {
150 let incremented_nonce =
152 get_incremented_nonce_with_client(quantus_client, from_keypair, prev_nonce).await?;
153 log_verbose!(
154 "π’ Using incremented nonce from best block: {} (previous: {})",
155 incremented_nonce,
156 prev_nonce
157 );
158 incremented_nonce
159 } else {
160 let fresh_nonce = get_fresh_nonce_with_client(quantus_client, from_keypair).await?;
162 log_verbose!("π’ Using fresh nonce from best block: {}", fresh_nonce);
163 fresh_nonce
164 };
165 current_nonce = Some(nonce);
166
167 let latest_block_hash = quantus_client.get_latest_block().await.map_err(|e| {
169 crate::error::QuantusError::NetworkError(format!("Failed to get latest block: {e:?}"))
170 })?;
171
172 log_verbose!("π Latest block hash: {:?}", latest_block_hash);
173
174 use subxt::config::DefaultExtrinsicParamsBuilder;
176 let mut params_builder = DefaultExtrinsicParamsBuilder::new()
177 .mortal(256) .nonce(nonce);
179
180 if let Some(tip_amount) = tip {
181 params_builder = params_builder.tip(tip_amount);
182 log_verbose!("π° Using tip: {} to increase priority", tip_amount);
183 } else {
184 log_verbose!("π° No tip specified, using default priority");
185 }
186
187 let params = params_builder.build();
198
199 log_verbose!("π Transaction parameters:");
201 log_verbose!(" Nonce: {}", nonce);
202 log_verbose!(" Tip: {:?}", tip);
203 log_verbose!(" Latest block hash: {:?}", latest_block_hash);
204
205 log_verbose!(" Era: Using default era from SubXT");
207 log_verbose!(" Genesis hash: Using default from SubXT");
208 log_verbose!(" Spec version: Using default from SubXT");
209
210 log_verbose!("π Additional debugging:");
212 log_verbose!(" Call type: {:?}", std::any::type_name::<Call>());
213
214 let metadata = quantus_client.client().metadata();
215 let encoded_call =
216 <_ as subxt::tx::Payload>::encode_call_data(&call, &metadata).map_err(|e| {
217 crate::error::QuantusError::NetworkError(format!("Failed to encode call: {:?}", e))
218 })?;
219 crate::log_verbose!("π Encoded call: 0x{}", hex::encode(&encoded_call));
220 crate::log_print!("π Encoded call size: {} bytes", encoded_call.len());
221
222 if execution_mode.wait_for_transaction {
223 match quantus_client
224 .client()
225 .tx()
226 .sign_and_submit_then_watch(&call, &signer, params)
227 .await
228 {
229 Ok(mut tx_progress) => {
230 crate::log_verbose!("π Transaction submitted: {:?}", tx_progress);
231
232 let tx_hash = tx_progress.extrinsic_hash();
233
234 if !execution_mode.wait_for_transaction {
235 return Ok(tx_hash);
236 }
237
238 wait_tx_inclusion(
239 &mut tx_progress,
240 quantus_client.client(),
241 &tx_hash,
242 execution_mode.finalized,
243 )
244 .await?;
245
246 return Ok(tx_hash);
247 },
248 Err(e) => {
249 let error_msg = format!("{e:?}");
250
251 let is_retryable = error_msg.contains("Priority is too low") ||
253 error_msg.contains("Transaction is outdated") ||
254 error_msg.contains("Transaction is temporarily banned") ||
255 error_msg.contains("Transaction has a bad signature") ||
256 error_msg.contains("Invalid Transaction");
257
258 if is_retryable && attempt < 5 {
259 log_verbose!(
260 "β οΈ Transaction error detected (attempt {}/5): {}",
261 attempt,
262 error_msg
263 );
264
265 let delay = std::cmp::min(2u64.pow(attempt as u32), 16);
267 log_verbose!("β³ Waiting {} seconds before retry...", delay);
268 tokio::time::sleep(tokio::time::Duration::from_secs(delay)).await;
269 continue;
270 } else {
271 log_verbose!("β Final error after {} attempts: {}", attempt, error_msg);
272 return Err(crate::error::QuantusError::NetworkError(format!(
273 "Failed to submit transaction: {e:?}"
274 )));
275 }
276 },
277 }
278 } else {
279 match quantus_client.client().tx().sign_and_submit(&call, &signer, params).await {
280 Ok(tx_hash) => {
281 crate::log_print!("β
Transaction submitted: {:?}", tx_hash);
282 return Ok(tx_hash);
283 },
284 Err(e) => {
285 log_error!("β Failed to submit transaction: {e:?}");
286 return Err(crate::error::QuantusError::NetworkError(format!(
287 "Failed to submit transaction: {e:?}"
288 )));
289 },
290 }
291 }
292 }
293}
294
295pub async fn submit_transaction_with_nonce<Call>(
297 quantus_client: &crate::chain::client::QuantusClient,
298 from_keypair: &crate::wallet::QuantumKeyPair,
299 call: Call,
300 tip: Option<u128>,
301 nonce: u32,
302 execution_mode: ExecutionMode,
303) -> crate::error::Result<subxt::utils::H256>
304where
305 Call: subxt::tx::Payload,
306{
307 let signer = from_keypair.to_subxt_signer().map_err(|e| {
308 crate::error::QuantusError::NetworkError(format!("Failed to convert keypair: {e:?}"))
309 })?;
310
311 let latest_block_hash = quantus_client.get_latest_block().await.map_err(|e| {
313 crate::error::QuantusError::NetworkError(format!("Failed to get latest block: {e:?}"))
314 })?;
315
316 log_verbose!("π Latest block hash: {:?}", latest_block_hash);
317
318 use subxt::config::DefaultExtrinsicParamsBuilder;
320 let mut params_builder = DefaultExtrinsicParamsBuilder::new()
321 .mortal(256) .nonce(nonce.into());
323
324 if let Some(tip_amount) = tip {
325 params_builder = params_builder.tip(tip_amount);
326 log_verbose!("π° Using tip: {}", tip_amount);
327 }
328
329 let params = params_builder.build();
330
331 log_verbose!("π’ Using manual nonce: {}", nonce);
332 log_verbose!("π€ Submitting transaction with manual nonce...");
333
334 crate::log_print!("submit with wait for transaction: {}", execution_mode.wait_for_transaction);
335 if execution_mode.wait_for_transaction {
338 match quantus_client
339 .client()
340 .tx()
341 .sign_and_submit_then_watch(&call, &signer, params)
342 .await
343 {
344 Ok(mut tx_progress) => {
345 let tx_hash = tx_progress.extrinsic_hash();
346 crate::log_print!("β
Transaction submitted: {:?}", tx_hash);
347 wait_tx_inclusion(
348 &mut tx_progress,
349 quantus_client.client(),
350 &tx_hash,
351 execution_mode.finalized,
352 )
353 .await?;
354 Ok(tx_hash)
355 },
356 Err(e) => {
357 log_error!("β Failed to submit transaction with manual nonce {}: {e:?}", nonce);
358 Err(crate::error::QuantusError::NetworkError(format!(
359 "Failed to submit transaction with nonce {nonce}: {e:?}"
360 )))
361 },
362 }
363 } else {
364 match quantus_client.client().tx().sign_and_submit(&call, &signer, params).await {
365 Ok(tx_hash) => {
366 crate::log_print!("β
Transaction submitted: {:?}", tx_hash);
367 Ok(tx_hash)
368 },
369 Err(e) => {
370 log_error!("β Failed to submit transaction: {e:?}");
371 Err(crate::error::QuantusError::NetworkError(format!(
372 "Failed to submit transaction: {e:?}"
373 )))
374 },
375 }
376 }
377}
378
379async fn wait_tx_inclusion(
385 tx_progress: &mut TxProgress<ChainConfig, OnlineClient<ChainConfig>>,
386 client: &OnlineClient<ChainConfig>,
387 tx_hash: &subxt::utils::H256,
388 finalized: bool,
389) -> Result<()> {
390 use indicatif::{ProgressBar, ProgressStyle};
391
392 let start_time = std::time::Instant::now();
393
394 let spinner = if !crate::log::is_verbose() {
395 let pb = ProgressBar::new_spinner();
396 pb.set_style(
397 ProgressStyle::default_spinner()
398 .tick_chars("β β β Ήβ Έβ Όβ ΄β ¦β §β β ")
399 .template("{spinner:.cyan} {msg}")
400 .unwrap(),
401 );
402
403 if finalized {
404 pb.set_message("Waiting for finalized block... (0s)");
405 } else {
406 pb.set_message("Waiting for block inclusion... (0s)");
407 }
408
409 pb.enable_steady_tick(std::time::Duration::from_millis(500));
410 Some(pb)
411 } else {
412 None
413 };
414
415 while let Some(Ok(status)) = tx_progress.next().await {
416 let elapsed_secs = start_time.elapsed().as_secs();
417 crate::log_verbose!(" Transaction status: {:?} (elapsed: {}s)", status, elapsed_secs);
418
419 match status {
420 TxStatus::Validated =>
421 if let Some(ref pb) = spinner {
422 pb.set_message(format!("Transaction validated β ({}s)", elapsed_secs));
423 },
424 TxStatus::InBestBlock(tx_in_block) => {
425 let block_hash = tx_in_block.block_hash();
426 crate::log_verbose!(" Transaction included in block: {:?}", block_hash);
427 check_execution_success(client, &block_hash, tx_hash).await?;
428 if finalized {
429 if let Some(ref pb) = spinner {
430 pb.set_message(format!(
431 "In best block, waiting for finalization... ({}s)",
432 elapsed_secs
433 ));
434 }
435 continue;
436 } else {
437 if let Some(pb) = spinner {
438 pb.finish_with_message(format!(
439 "β
Transaction included in block! ({}s)",
440 elapsed_secs
441 ));
442 }
443 break;
444 };
445 },
446 TxStatus::InFinalizedBlock(tx_in_block) => {
447 let block_hash = tx_in_block.block_hash();
448 crate::log_verbose!(" Transaction finalized in block: {:?}", block_hash);
449 check_execution_success(client, &block_hash, tx_hash).await?;
450 if let Some(pb) = spinner {
451 pb.finish_with_message(format!(
452 "β
Transaction finalized! ({}s)",
453 elapsed_secs
454 ));
455 }
456 break;
457 },
458 TxStatus::Error { message } | TxStatus::Invalid { message } => {
459 crate::log_error!(" Transaction error: {} (elapsed: {}s)", message, elapsed_secs);
460 if let Some(pb) = spinner {
461 pb.finish_with_message(format!("β Transaction error! ({}s)", elapsed_secs));
462 }
463 break;
464 },
465 _ => {
466 if let Some(ref pb) = spinner {
467 if finalized {
468 pb.set_message(format!(
469 "Waiting for finalized block... ({}s)",
470 elapsed_secs
471 ));
472 } else {
473 pb.set_message(format!(
474 "Waiting for block inclusion... ({}s)",
475 elapsed_secs
476 ));
477 }
478 }
479 continue;
480 },
481 }
482 }
483
484 Ok(())
485}
486
487fn format_dispatch_error(
488 error: &crate::chain::quantus_subxt::api::runtime_types::sp_runtime::DispatchError,
489 metadata: &subxt::Metadata,
490) -> String {
491 use crate::chain::quantus_subxt::api::runtime_types::sp_runtime::DispatchError;
492
493 match error {
494 DispatchError::Module(module_error) => {
495 let pallet_index = module_error.index;
496 let error_index = module_error.error[0];
497
498 if let Some(pallet) = metadata.pallet_by_index(pallet_index) {
500 let pallet_name = pallet.name();
501 if let Some(variant) = pallet.error_variant_by_index(error_index) {
503 let error_name = &variant.name;
504 let docs = variant.docs.join(" ");
505 if docs.is_empty() {
506 format!("{}::{}", pallet_name, error_name)
507 } else {
508 format!("{}::{} - {}", pallet_name, error_name, docs)
509 }
510 } else {
511 format!("{}::Error[{}]", pallet_name, error_index)
512 }
513 } else {
514 format!("Pallet[{}]::Error[{}]", pallet_index, error_index)
515 }
516 },
517 DispatchError::BadOrigin => "BadOrigin".to_string(),
518 DispatchError::CannotLookup => "CannotLookup".to_string(),
519 DispatchError::Other => "Other".to_string(),
520 _ => format!("{:?}", error),
521 }
522}
523
524async fn check_execution_success(
525 client: &OnlineClient<ChainConfig>,
526 block_hash: &subxt::utils::H256,
527 tx_hash: &subxt::utils::H256,
528) -> Result<()> {
529 use crate::chain::quantus_subxt::api::system::events::ExtrinsicFailed;
530
531 let block = client.blocks().at(*block_hash).await.map_err(|e| {
532 crate::error::QuantusError::NetworkError(format!("Failed to get block: {e:?}"))
533 })?;
534
535 let extrinsics = block.extrinsics().await.map_err(|e| {
536 crate::error::QuantusError::NetworkError(format!("Failed to get extrinsics: {e:?}"))
537 })?;
538
539 let our_extrinsic_index = extrinsics
540 .iter()
541 .enumerate()
542 .find(|(_, ext)| ext.hash() == *tx_hash)
543 .map(|(idx, _)| idx);
544
545 let events = block.events().await.map_err(|e| {
546 crate::error::QuantusError::NetworkError(format!("Failed to fetch events: {e:?}"))
547 })?;
548
549 let metadata = client.metadata();
550 if let Some(ext_idx) = our_extrinsic_index {
551 for event_result in events.iter() {
552 let event = event_result.map_err(|e| {
553 crate::error::QuantusError::NetworkError(format!("Failed to decode event: {e:?}"))
554 })?;
555
556 if let subxt::events::Phase::ApplyExtrinsic(event_ext_idx) = event.phase() {
557 if event_ext_idx == ext_idx as u32 {
558 if let Ok(Some(ExtrinsicFailed { dispatch_error, .. })) =
559 event.as_event::<ExtrinsicFailed>()
560 {
561 let error_msg = format_dispatch_error(&dispatch_error, &metadata);
562 crate::log_error!(" Transaction failed: {}", error_msg);
563 return Err(crate::error::QuantusError::NetworkError(format!(
564 "Transaction execution failed: {}",
565 error_msg
566 )));
567 }
568 }
569 }
570 }
571 }
572
573 Ok(())
574}