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>(
118 quantus_client: &crate::chain::client::QuantusClient,
119 from_keypair: &crate::wallet::QuantumKeyPair,
120 call: Call,
121 tip: Option<u128>,
122) -> crate::error::Result<subxt::utils::H256>
123where
124 Call: subxt::tx::Payload,
125{
126 submit_transaction_with_finalization(quantus_client, from_keypair, call, tip, false).await
127}
128
129pub async fn submit_transaction_with_finalization<Call>(
131 quantus_client: &crate::chain::client::QuantusClient,
132 from_keypair: &crate::wallet::QuantumKeyPair,
133 call: Call,
134 tip: Option<u128>,
135 finalized: bool,
136) -> crate::error::Result<subxt::utils::H256>
137where
138 Call: subxt::tx::Payload,
139{
140 let signer = from_keypair.to_subxt_signer().map_err(|e| {
141 crate::error::QuantusError::NetworkError(format!("Failed to convert keypair: {e:?}"))
142 })?;
143
144 let mut attempt = 0;
146 let mut current_nonce = None;
147
148 loop {
149 attempt += 1;
150
151 let nonce = if let Some(prev_nonce) = current_nonce {
153 let incremented_nonce =
155 get_incremented_nonce_with_client(quantus_client, from_keypair, prev_nonce).await?;
156 log_verbose!(
157 "🔢 Using incremented nonce from best block: {} (previous: {})",
158 incremented_nonce,
159 prev_nonce
160 );
161 incremented_nonce
162 } else {
163 let fresh_nonce = get_fresh_nonce_with_client(quantus_client, from_keypair).await?;
165 log_verbose!("🔢 Using fresh nonce from best block: {}", fresh_nonce);
166 fresh_nonce
167 };
168 current_nonce = Some(nonce);
169
170 let latest_block_hash = quantus_client.get_latest_block().await.map_err(|e| {
172 crate::error::QuantusError::NetworkError(format!("Failed to get latest block: {e:?}"))
173 })?;
174
175 log_verbose!("🔗 Latest block hash: {:?}", latest_block_hash);
176
177 use subxt::config::DefaultExtrinsicParamsBuilder;
179 let mut params_builder = DefaultExtrinsicParamsBuilder::new()
180 .mortal(256) .nonce(nonce);
182
183 if let Some(tip_amount) = tip {
184 params_builder = params_builder.tip(tip_amount);
185 log_verbose!("💰 Using tip: {} to increase priority", tip_amount);
186 } else {
187 log_verbose!("💰 No tip specified, using default priority");
188 }
189
190 let genesis_hash = quantus_client.get_genesis_hash().await?;
192 let (spec_version, transaction_version) = quantus_client.get_runtime_version().await?;
193
194 log_verbose!("🔍 Chain parameters:");
195 log_verbose!(" Genesis hash: {:?}", genesis_hash);
196 log_verbose!(" Spec version: {}", spec_version);
197 log_verbose!(" Transaction version: {}", transaction_version);
198
199 let params = params_builder.build();
201
202 log_verbose!("🔍 Transaction parameters:");
204 log_verbose!(" Nonce: {}", nonce);
205 log_verbose!(" Tip: {:?}", tip);
206 log_verbose!(" Latest block hash: {:?}", latest_block_hash);
207
208 log_verbose!(" Era: Using default era from SubXT");
210 log_verbose!(" Genesis hash: Using default from SubXT");
211 log_verbose!(" Spec version: Using default from SubXT");
212
213 log_verbose!("🔍 Additional debugging:");
215 log_verbose!(" Call type: {:?}", std::any::type_name::<Call>());
216
217 match quantus_client
219 .client()
220 .tx()
221 .sign_and_submit_then_watch(&call, &signer, params)
222 .await
223 {
224 Ok(mut tx_progress) => {
225 crate::log_verbose!("📋 Transaction submitted: {:?}", tx_progress);
226
227 let tx_hash = tx_progress.extrinsic_hash();
228 wait_tx_inclusion(&mut tx_progress, finalized).await?;
229
230 return Ok(tx_hash);
231 },
232 Err(e) => {
233 let error_msg = format!("{e:?}");
234
235 let is_retryable = error_msg.contains("Priority is too low") ||
237 error_msg.contains("Transaction is outdated") ||
238 error_msg.contains("Transaction is temporarily banned") ||
239 error_msg.contains("Transaction has a bad signature") ||
240 error_msg.contains("Invalid Transaction");
241
242 if is_retryable && attempt < 5 {
243 log_verbose!(
244 "⚠️ Transaction error detected (attempt {}/5): {}",
245 attempt,
246 error_msg
247 );
248
249 let delay = std::cmp::min(2u64.pow(attempt as u32), 16);
251 log_verbose!("⏳ Waiting {} seconds before retry...", delay);
252 tokio::time::sleep(tokio::time::Duration::from_secs(delay)).await;
253 continue;
254 } else {
255 log_verbose!("❌ Final error after {} attempts: {}", attempt, error_msg);
256 return Err(crate::error::QuantusError::NetworkError(format!(
257 "Failed to submit transaction: {e:?}"
258 )));
259 }
260 },
261 }
262 }
263}
264
265pub async fn submit_transaction_with_nonce<Call>(
267 quantus_client: &crate::chain::client::QuantusClient,
268 from_keypair: &crate::wallet::QuantumKeyPair,
269 call: Call,
270 tip: Option<u128>,
271 nonce: u32,
272 finalized: bool,
273) -> crate::error::Result<subxt::utils::H256>
274where
275 Call: subxt::tx::Payload,
276{
277 let signer = from_keypair.to_subxt_signer().map_err(|e| {
278 crate::error::QuantusError::NetworkError(format!("Failed to convert keypair: {e:?}"))
279 })?;
280
281 let latest_block_hash = quantus_client.get_latest_block().await.map_err(|e| {
283 crate::error::QuantusError::NetworkError(format!("Failed to get latest block: {e:?}"))
284 })?;
285
286 log_verbose!("🔗 Latest block hash: {:?}", latest_block_hash);
287
288 use subxt::config::DefaultExtrinsicParamsBuilder;
290 let mut params_builder = DefaultExtrinsicParamsBuilder::new()
291 .mortal(256) .nonce(nonce.into());
293
294 if let Some(tip_amount) = tip {
295 params_builder = params_builder.tip(tip_amount);
296 log_verbose!("💰 Using tip: {}", tip_amount);
297 }
298
299 let params = params_builder.build();
300
301 log_verbose!("🔢 Using manual nonce: {}", nonce);
302 log_verbose!("📤 Submitting transaction with manual nonce...");
303
304 match quantus_client
306 .client()
307 .tx()
308 .sign_and_submit_then_watch(&call, &signer, params)
309 .await
310 {
311 Ok(mut tx_progress) => {
312 let tx_hash = tx_progress.extrinsic_hash();
313 log_verbose!("✅ Transaction submitted successfully: {:?}", tx_hash);
314 wait_tx_inclusion(&mut tx_progress, finalized).await?;
315 Ok(tx_hash)
316 },
317 Err(e) => {
318 log_error!("❌ Failed to submit transaction with manual nonce {}: {e:?}", nonce);
319 Err(crate::error::QuantusError::NetworkError(format!(
320 "Failed to submit transaction with nonce {nonce}: {e:?}"
321 )))
322 },
323 }
324}
325
326async fn wait_tx_inclusion(
332 tx_progress: &mut TxProgress<ChainConfig, OnlineClient<ChainConfig>>,
333 finalized: bool,
334) -> Result<()> {
335 while let Some(Ok(status)) = tx_progress.next().await {
336 crate::log_verbose!(" Transaction status: {:?}", status);
337 match status {
338 TxStatus::InBestBlock(block_hash) => {
339 crate::log_verbose!(" Transaction included in block: {:?}", block_hash);
340 if finalized {
341 continue;
342 } else {
343 break;
344 };
345 },
346 TxStatus::InFinalizedBlock(block_hash) => {
347 crate::log_verbose!(" Transaction finalized in block: {:?}", block_hash);
348 break;
349 },
350 TxStatus::Error { message } | TxStatus::Invalid { message } => {
351 crate::log_error!(" Transaction error: {}", message);
352 break;
353 },
354 _ => continue,
355 }
356 }
357
358 Ok(())
359}