1use crate::{chain::client::ChainConfig, error::Result, log_error, log_verbose};
3use colored::Colorize;
4use sp_core::crypto::{AccountId32, Ss58Codec};
5use subxt::{
6 tx::{TxProgress, TxStatus},
7 OnlineClient,
8};
9
10pub fn resolve_address(address_or_wallet_name: &str) -> Result<String> {
13 if AccountId32::from_ss58check_with_version(address_or_wallet_name).is_ok() {
15 return Ok(address_or_wallet_name.to_string());
17 }
18
19 let wallet_manager = crate::wallet::WalletManager::new()?;
21 if let Some(wallet_address) = wallet_manager.find_wallet_address(address_or_wallet_name)? {
22 log_verbose!(
23 "π Found wallet '{}' with address: {}",
24 address_or_wallet_name.bright_cyan(),
25 wallet_address.bright_green()
26 );
27 return Ok(wallet_address);
28 }
29
30 Err(crate::error::QuantusError::Generic(format!(
32 "Invalid destination: '{address_or_wallet_name}' is neither a valid SS58 address nor a known wallet name"
33 )))
34}
35
36pub async fn get_fresh_nonce_with_client(
40 quantus_client: &crate::chain::client::QuantusClient,
41 from_keypair: &crate::wallet::QuantumKeyPair,
42) -> Result<u64> {
43 let (from_account_id, _version) =
44 AccountId32::from_ss58check_with_version(&from_keypair.to_account_id_ss58check()).map_err(
45 |e| crate::error::QuantusError::NetworkError(format!("Invalid from address: {e:?}")),
46 )?;
47
48 let latest_nonce = quantus_client
50 .get_account_nonce_from_best_block(&from_account_id)
51 .await
52 .map_err(|e| {
53 crate::error::QuantusError::NetworkError(format!(
54 "Failed to get account nonce from best block: {e:?}"
55 ))
56 })?;
57
58 log_verbose!("π’ Using fresh nonce from latest block: {}", latest_nonce);
59
60 let finalized_nonce = quantus_client
62 .client()
63 .tx()
64 .account_nonce(&from_account_id)
65 .await
66 .map_err(|e| {
67 crate::error::QuantusError::NetworkError(format!(
68 "Failed to get account nonce from finalized block: {e:?}"
69 ))
70 })?;
71
72 if latest_nonce != finalized_nonce {
73 log_verbose!(
74 "β οΈ Nonce difference detected! Latest: {}, Finalized: {}",
75 latest_nonce,
76 finalized_nonce
77 );
78 }
79
80 Ok(latest_nonce)
81}
82
83pub async fn get_incremented_nonce_with_client(
86 quantus_client: &crate::chain::client::QuantusClient,
87 from_keypair: &crate::wallet::QuantumKeyPair,
88 base_nonce: u64,
89) -> Result<u64> {
90 let (from_account_id, _version) =
91 AccountId32::from_ss58check_with_version(&from_keypair.to_account_id_ss58check()).map_err(
92 |e| crate::error::QuantusError::NetworkError(format!("Invalid from address: {e:?}")),
93 )?;
94
95 let current_nonce = quantus_client
97 .get_account_nonce_from_best_block(&from_account_id)
98 .await
99 .map_err(|e| {
100 crate::error::QuantusError::NetworkError(format!(
101 "Failed to get account nonce from best block: {e:?}"
102 ))
103 })?;
104
105 let incremented_nonce = std::cmp::max(current_nonce, base_nonce + 1);
107 log_verbose!(
108 "π’ Using incremented nonce: {} (base: {}, current from latest block: {})",
109 incremented_nonce,
110 base_nonce,
111 current_nonce
112 );
113 Ok(incremented_nonce)
114}
115
116pub async fn submit_transaction<Call>(
121 quantus_client: &crate::chain::client::QuantusClient,
122 from_keypair: &crate::wallet::QuantumKeyPair,
123 call: Call,
124 tip: Option<u128>,
125 finalized: bool,
126) -> crate::error::Result<subxt::utils::H256>
127where
128 Call: subxt::tx::Payload,
129{
130 let signer = from_keypair.to_subxt_signer().map_err(|e| {
131 crate::error::QuantusError::NetworkError(format!("Failed to convert keypair: {e:?}"))
132 })?;
133
134 let mut attempt = 0;
136 let mut current_nonce = None;
137
138 loop {
139 attempt += 1;
140
141 let nonce = if let Some(prev_nonce) = current_nonce {
143 let incremented_nonce =
145 get_incremented_nonce_with_client(quantus_client, from_keypair, prev_nonce).await?;
146 log_verbose!(
147 "π’ Using incremented nonce from best block: {} (previous: {})",
148 incremented_nonce,
149 prev_nonce
150 );
151 incremented_nonce
152 } else {
153 let fresh_nonce = get_fresh_nonce_with_client(quantus_client, from_keypair).await?;
155 log_verbose!("π’ Using fresh nonce from best block: {}", fresh_nonce);
156 fresh_nonce
157 };
158 current_nonce = Some(nonce);
159
160 let latest_block_hash = quantus_client.get_latest_block().await.map_err(|e| {
162 crate::error::QuantusError::NetworkError(format!("Failed to get latest block: {e:?}"))
163 })?;
164
165 log_verbose!("π Latest block hash: {:?}", latest_block_hash);
166
167 use subxt::config::DefaultExtrinsicParamsBuilder;
169 let mut params_builder = DefaultExtrinsicParamsBuilder::new()
170 .mortal(256) .nonce(nonce);
172
173 if let Some(tip_amount) = tip {
174 params_builder = params_builder.tip(tip_amount);
175 log_verbose!("π° Using tip: {} to increase priority", tip_amount);
176 } else {
177 log_verbose!("π° No tip specified, using default priority");
178 }
179
180 let genesis_hash = quantus_client.get_genesis_hash().await?;
182 let (spec_version, transaction_version) = quantus_client.get_runtime_version().await?;
183
184 log_verbose!("π Chain parameters:");
185 log_verbose!(" Genesis hash: {:?}", genesis_hash);
186 log_verbose!(" Spec version: {}", spec_version);
187 log_verbose!(" Transaction version: {}", transaction_version);
188
189 let params = params_builder.build();
191
192 log_verbose!("π Transaction parameters:");
194 log_verbose!(" Nonce: {}", nonce);
195 log_verbose!(" Tip: {:?}", tip);
196 log_verbose!(" Latest block hash: {:?}", latest_block_hash);
197
198 log_verbose!(" Era: Using default era from SubXT");
200 log_verbose!(" Genesis hash: Using default from SubXT");
201 log_verbose!(" Spec version: Using default from SubXT");
202
203 log_verbose!("π Additional debugging:");
205 log_verbose!(" Call type: {:?}", std::any::type_name::<Call>());
206
207 match quantus_client
209 .client()
210 .tx()
211 .sign_and_submit_then_watch(&call, &signer, params)
212 .await
213 {
214 Ok(mut tx_progress) => {
215 crate::log_verbose!("π Transaction submitted: {:?}", tx_progress);
216
217 let tx_hash = tx_progress.extrinsic_hash();
218 wait_tx_inclusion(&mut tx_progress, finalized).await?;
219
220 return Ok(tx_hash);
221 },
222 Err(e) => {
223 let error_msg = format!("{e:?}");
224
225 let is_retryable = error_msg.contains("Priority is too low") ||
227 error_msg.contains("Transaction is outdated") ||
228 error_msg.contains("Transaction is temporarily banned") ||
229 error_msg.contains("Transaction has a bad signature") ||
230 error_msg.contains("Invalid Transaction");
231
232 if is_retryable && attempt < 5 {
233 log_verbose!(
234 "β οΈ Transaction error detected (attempt {}/5): {}",
235 attempt,
236 error_msg
237 );
238
239 let delay = std::cmp::min(2u64.pow(attempt as u32), 16);
241 log_verbose!("β³ Waiting {} seconds before retry...", delay);
242 tokio::time::sleep(tokio::time::Duration::from_secs(delay)).await;
243 continue;
244 } else {
245 log_verbose!("β Final error after {} attempts: {}", attempt, error_msg);
246 return Err(crate::error::QuantusError::NetworkError(format!(
247 "Failed to submit transaction: {e:?}"
248 )));
249 }
250 },
251 }
252 }
253}
254
255pub async fn submit_transaction_with_nonce<Call>(
257 quantus_client: &crate::chain::client::QuantusClient,
258 from_keypair: &crate::wallet::QuantumKeyPair,
259 call: Call,
260 tip: Option<u128>,
261 nonce: u32,
262 finalized: bool,
263) -> crate::error::Result<subxt::utils::H256>
264where
265 Call: subxt::tx::Payload,
266{
267 let signer = from_keypair.to_subxt_signer().map_err(|e| {
268 crate::error::QuantusError::NetworkError(format!("Failed to convert keypair: {e:?}"))
269 })?;
270
271 let latest_block_hash = quantus_client.get_latest_block().await.map_err(|e| {
273 crate::error::QuantusError::NetworkError(format!("Failed to get latest block: {e:?}"))
274 })?;
275
276 log_verbose!("π Latest block hash: {:?}", latest_block_hash);
277
278 use subxt::config::DefaultExtrinsicParamsBuilder;
280 let mut params_builder = DefaultExtrinsicParamsBuilder::new()
281 .mortal(256) .nonce(nonce.into());
283
284 if let Some(tip_amount) = tip {
285 params_builder = params_builder.tip(tip_amount);
286 log_verbose!("π° Using tip: {}", tip_amount);
287 }
288
289 let params = params_builder.build();
290
291 log_verbose!("π’ Using manual nonce: {}", nonce);
292 log_verbose!("π€ Submitting transaction with manual nonce...");
293
294 match quantus_client
296 .client()
297 .tx()
298 .sign_and_submit_then_watch(&call, &signer, params)
299 .await
300 {
301 Ok(mut tx_progress) => {
302 let tx_hash = tx_progress.extrinsic_hash();
303 log_verbose!("β
Transaction submitted successfully: {:?}", tx_hash);
304 wait_tx_inclusion(&mut tx_progress, finalized).await?;
305 Ok(tx_hash)
306 },
307 Err(e) => {
308 log_error!("β Failed to submit transaction with manual nonce {}: {e:?}", nonce);
309 Err(crate::error::QuantusError::NetworkError(format!(
310 "Failed to submit transaction with nonce {nonce}: {e:?}"
311 )))
312 },
313 }
314}
315
316async fn wait_tx_inclusion(
322 tx_progress: &mut TxProgress<ChainConfig, OnlineClient<ChainConfig>>,
323 finalized: bool,
324) -> Result<()> {
325 use indicatif::{ProgressBar, ProgressStyle};
326
327 let spinner = if !crate::log::is_verbose() {
329 let pb = ProgressBar::new_spinner();
330 pb.set_style(
331 ProgressStyle::default_spinner()
332 .tick_chars("β β β Ήβ Έβ Όβ ΄β ¦β §β β ")
333 .template("{spinner:.cyan} {msg} {elapsed:.dim}")
334 .unwrap(),
335 );
336
337 if finalized {
338 pb.set_message("Waiting for finalized block...");
339 } else {
340 pb.set_message("Waiting for block inclusion...");
341 }
342
343 pb.enable_steady_tick(std::time::Duration::from_millis(100));
344 Some(pb)
345 } else {
346 None
347 };
348
349 while let Some(Ok(status)) = tx_progress.next().await {
350 crate::log_verbose!(" Transaction status: {:?}", status);
351
352 match status {
353 TxStatus::Validated =>
354 if let Some(ref pb) = spinner {
355 pb.set_message("Transaction validated β");
356 },
357 TxStatus::InBestBlock(block_hash) => {
358 crate::log_verbose!(" Transaction included in block: {:?}", block_hash);
359 if finalized {
360 if let Some(ref pb) = spinner {
361 pb.set_message("In best block, waiting for finalization...");
362 }
363 continue;
364 } else {
365 if let Some(pb) = spinner {
366 pb.finish_with_message("β
Transaction included in block!");
367 }
368 break;
369 };
370 },
371 TxStatus::InFinalizedBlock(block_hash) => {
372 crate::log_verbose!(" Transaction finalized in block: {:?}", block_hash);
373 if let Some(pb) = spinner {
374 pb.finish_with_message("β
Transaction finalized!");
375 }
376 break;
377 },
378 TxStatus::Error { message } | TxStatus::Invalid { message } => {
379 crate::log_error!(" Transaction error: {}", message);
380 if let Some(pb) = spinner {
381 pb.finish_with_message("β Transaction error!");
382 }
383 break;
384 },
385 _ => continue,
386 }
387 }
388
389 Ok(())
390}