1use crate::{
2 chain::quantus_subxt,
3 cli::{address_format::QuantusSS58, common::resolve_address},
4 error::Result,
5 log_info, log_print, log_verbose,
6};
7use clap::Subcommand;
8use colored::Colorize;
9use sp_core::crypto::{AccountId32 as SpAccountId32, Ss58Codec};
10use std::str::FromStr;
11
12#[derive(Subcommand, Debug)]
14pub enum ReversibleCommands {
15 ScheduleTransfer {
17 #[arg(short, long)]
19 to: String,
20
21 #[arg(short, long)]
23 amount: String,
24
25 #[arg(short, long)]
27 from: String,
28
29 #[arg(short, long)]
31 password: Option<String>,
32
33 #[arg(long)]
35 password_file: Option<String>,
36 },
37
38 ScheduleTransferWithDelay {
40 #[arg(short, long)]
42 to: String,
43
44 #[arg(short, long)]
46 amount: String,
47
48 #[arg(short, long)]
50 delay: u64,
51
52 #[arg(long)]
54 unit_blocks: bool,
55
56 #[arg(short, long)]
58 from: String,
59
60 #[arg(short, long)]
62 password: Option<String>,
63
64 #[arg(long)]
66 password_file: Option<String>,
67 },
68
69 Cancel {
71 #[arg(long)]
73 tx_id: String,
74
75 #[arg(short, long)]
77 from: String,
78
79 #[arg(short, long)]
81 password: Option<String>,
82
83 #[arg(long)]
85 password_file: Option<String>,
86 },
87
88 ListPending {
90 #[arg(short, long)]
92 address: Option<String>,
93
94 #[arg(short, long)]
96 from: Option<String>,
97
98 #[arg(short, long)]
100 password: Option<String>,
101
102 #[arg(long)]
104 password_file: Option<String>,
105 },
106}
107
108pub async fn schedule_transfer(
110 quantus_client: &crate::chain::client::QuantusClient,
111 from_keypair: &crate::wallet::QuantumKeyPair,
112 to_address: &str,
113 amount: u128,
114 finalized: bool,
115) -> Result<subxt::utils::H256> {
116 log_verbose!("🔄 Creating reversible transfer...");
117 log_verbose!(" From: {}", from_keypair.to_account_id_ss58check().bright_cyan());
118 log_verbose!(" To: {}", to_address.bright_green());
119 log_verbose!(" Amount: {}", amount);
120
121 let (to_account_id_sp, _version) = SpAccountId32::from_ss58check_with_version(to_address)
123 .map_err(|e| {
124 crate::error::QuantusError::NetworkError(format!("Invalid destination address: {e:?}"))
125 })?;
126
127 let to_account_id_bytes: [u8; 32] = *to_account_id_sp.as_ref();
129 let to_account_id = subxt::ext::subxt_core::utils::AccountId32::from(to_account_id_bytes);
130
131 log_verbose!("✍️ Creating reversible transfer extrinsic...");
132
133 let transfer_call = quantus_subxt::api::tx()
135 .reversible_transfers()
136 .schedule_transfer(subxt::ext::subxt_core::utils::MultiAddress::Id(to_account_id), amount);
137
138 let tx_hash = crate::cli::common::submit_transaction(
140 quantus_client,
141 from_keypair,
142 transfer_call,
143 None,
144 finalized,
145 )
146 .await?;
147
148 log_verbose!("📋 Reversible transfer submitted: {:?}", tx_hash);
149
150 Ok(tx_hash)
151}
152
153pub async fn cancel_transaction(
155 quantus_client: &crate::chain::client::QuantusClient,
156 from_keypair: &crate::wallet::QuantumKeyPair,
157 tx_id: &str,
158 finalized: bool,
159) -> Result<subxt::utils::H256> {
160 log_verbose!("❌ Cancelling reversible transfer...");
161 log_verbose!(" Transaction ID: {}", tx_id.bright_yellow());
162
163 let tx_hash = subxt::utils::H256::from_str(tx_id).map_err(|e| {
165 crate::error::QuantusError::Generic(format!("Invalid transaction ID: {e:?}"))
166 })?;
167
168 log_verbose!("✍️ Creating cancel transaction extrinsic...");
169
170 let cancel_call = quantus_subxt::api::tx().reversible_transfers().cancel(tx_hash);
172
173 let tx_hash_result = crate::cli::common::submit_transaction(
175 quantus_client,
176 from_keypair,
177 cancel_call,
178 None,
179 finalized,
180 )
181 .await?;
182
183 log_verbose!("📋 Cancel transaction submitted: {:?}", tx_hash_result);
184
185 Ok(tx_hash_result)
186}
187
188pub async fn schedule_transfer_with_delay(
190 quantus_client: &crate::chain::client::QuantusClient,
191 from_keypair: &crate::wallet::QuantumKeyPair,
192 to_address: &str,
193 amount: u128,
194 delay: u64,
195 unit_blocks: bool,
196 finalized: bool,
197) -> Result<subxt::utils::H256> {
198 let unit_str = if unit_blocks { "blocks" } else { "seconds" };
199 log_verbose!("🔄 Creating reversible transfer with custom delay ...");
200 log_verbose!(" From: {}", from_keypair.to_account_id_ss58check().bright_cyan());
201 log_verbose!(" To: {}", to_address.bright_green());
202 log_verbose!(" Amount: {}", amount);
203 log_verbose!(" Delay: {} {}", delay, unit_str);
204
205 let to_account_id_sp = SpAccountId32::from_ss58check(to_address).map_err(|e| {
207 crate::error::QuantusError::NetworkError(format!("Invalid destination address: {e:?}"))
208 })?;
209 let to_account_id_bytes: [u8; 32] = *to_account_id_sp.as_ref();
210 let to_account_id_subxt = subxt::ext::subxt_core::utils::AccountId32::from(to_account_id_bytes);
211
212 let delay_value = if unit_blocks {
214 quantus_subxt::api::reversible_transfers::calls::types::schedule_transfer_with_delay::Delay::BlockNumber(delay as u32)
215 } else {
216 quantus_subxt::api::reversible_transfers::calls::types::schedule_transfer_with_delay::Delay::Timestamp(delay * 1000)
218 };
219
220 log_verbose!("✍️ Creating schedule_transfer_with_delay extrinsic...");
221
222 let transfer_call =
224 quantus_subxt::api::tx().reversible_transfers().schedule_transfer_with_delay(
225 subxt::ext::subxt_core::utils::MultiAddress::Id(to_account_id_subxt),
226 amount,
227 delay_value,
228 );
229
230 let tx_hash = crate::cli::common::submit_transaction(
232 quantus_client,
233 from_keypair,
234 transfer_call,
235 None,
236 finalized,
237 )
238 .await?;
239
240 log_verbose!("📋 Reversible transfer with custom delay submitted: {:?}", tx_hash);
241
242 Ok(tx_hash)
243}
244
245pub async fn handle_reversible_command(
247 command: ReversibleCommands,
248 node_url: &str,
249 finalized: bool,
250) -> Result<()> {
251 log_print!("🔄 Reversible Transfers");
252
253 let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
254
255 match command {
256 ReversibleCommands::ListPending { address, from, password, password_file } =>
257 list_pending_transactions(&quantus_client, address, from, password, password_file).await,
258 ReversibleCommands::ScheduleTransfer { to, amount, from, password, password_file } => {
259 let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
261 let (raw_amount, formatted_amount) =
262 crate::cli::send::validate_and_format_amount(&quantus_client, &amount).await?;
263
264 let resolved_address = resolve_address(&to)?;
266
267 log_info!(
268 "🔄 Scheduling reversible transfer of {} to {}",
269 formatted_amount,
270 resolved_address
271 );
272 log_verbose!(
273 "🚀 {} Scheduling reversible transfer {} to {} ()",
274 "REVERSIBLE".bright_cyan().bold(),
275 formatted_amount.bright_yellow().bold(),
276 resolved_address.bright_green()
277 );
278
279 log_verbose!("📦 Using wallet: {}", from.bright_blue().bold());
281 let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
282
283 let tx_hash = schedule_transfer(
285 &quantus_client,
286 &keypair,
287 &resolved_address,
288 raw_amount,
289 finalized,
290 )
291 .await?;
292
293 log_print!(
294 "✅ {} Reversible transfer scheduled! Hash: {:?}",
295 "SUCCESS".bright_green().bold(),
296 tx_hash
297 );
298
299 Ok(())
300 },
301 ReversibleCommands::Cancel { tx_id, from, password, password_file } => {
302 log_verbose!(
303 "❌ {} Cancelling reversible transfer {} ()",
304 "CANCEL".bright_red().bold(),
305 tx_id.bright_yellow().bold()
306 );
307
308 log_verbose!("📦 Using wallet: {}", from.bright_blue().bold());
310 let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
311
312 let tx_hash = cancel_transaction(&quantus_client, &keypair, &tx_id, finalized).await?;
314
315 log_print!(
316 "✅ {} Cancel transaction submitted! Hash: {:?}",
317 "SUCCESS".bright_green().bold(),
318 tx_hash
319 );
320
321 Ok(())
322 },
323
324 ReversibleCommands::ScheduleTransferWithDelay {
325 to,
326 amount,
327 delay,
328 unit_blocks,
329 from,
330 password,
331 password_file,
332 } => {
333 let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
335 let (raw_amount, formatted_amount) =
336 crate::cli::send::validate_and_format_amount(&quantus_client, &amount).await?;
337
338 let resolved_address = resolve_address(&to)?;
340
341 let unit_str = if unit_blocks { "blocks" } else { "seconds" };
342 log_verbose!(
343 "🚀 {} Scheduling reversible transfer {} to {} with {} {} delay ()",
344 "REVERSIBLE".bright_cyan().bold(),
345 formatted_amount.bright_yellow().bold(),
346 resolved_address.bright_green(),
347 delay.to_string().bright_magenta(),
348 unit_str
349 );
350
351 log_verbose!("📦 Using wallet: {}", from.bright_blue().bold());
353 let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
354
355 let tx_hash = schedule_transfer_with_delay(
357 &quantus_client,
358 &keypair,
359 &resolved_address,
360 raw_amount,
361 delay,
362 unit_blocks,
363 finalized,
364 )
365 .await?;
366
367 log_print!(
368 "✅ {} Reversible transfer with custom delay scheduled! Hash: {:?}",
369 "SUCCESS".bright_green().bold(),
370 tx_hash
371 );
372
373 Ok(())
374 },
375 }
376}
377
378async fn list_pending_transactions(
380 quantus_client: &crate::chain::client::QuantusClient,
381 address: Option<String>,
382 wallet_name: Option<String>,
383 password: Option<String>,
384 password_file: Option<String>,
385) -> Result<()> {
386 log_print!("📋 Listing pending reversible transactions");
387
388 let target_address = match (address, wallet_name) {
390 (Some(addr), _) => {
391 SpAccountId32::from_ss58check(&addr).map_err(|e| {
393 crate::error::QuantusError::Generic(format!("Invalid address: {e:?}"))
394 })?;
395 addr
396 },
397 (None, Some(wallet)) => {
398 let keypair =
400 crate::wallet::load_keypair_from_wallet(&wallet, password, password_file)?;
401 keypair.to_account_id_ss58check()
402 },
403 (None, None) => {
404 return Err(crate::error::QuantusError::Generic(
405 "Either --address or --from must be provided".to_string(),
406 ));
407 },
408 };
409
410 let account_id_sp = SpAccountId32::from_ss58check(&target_address)
412 .map_err(|e| crate::error::QuantusError::Generic(format!("Invalid address: {e:?}")))?;
413 let account_id_bytes: [u8; 32] = *account_id_sp.as_ref();
414 let account_id = subxt::ext::subxt_core::utils::AccountId32::from(account_id_bytes);
415
416 log_verbose!("🔍 Querying pending transfers for: {}", target_address);
417
418 let sender_storage_address = crate::chain::quantus_subxt::api::storage()
420 .reversible_transfers()
421 .pending_transfers_by_sender(account_id.clone());
422
423 let latest_block_hash = quantus_client.get_latest_block().await?;
425
426 let outgoing_transfers = quantus_client
427 .client()
428 .storage()
429 .at(latest_block_hash)
430 .fetch(&sender_storage_address)
431 .await
432 .map_err(|e| crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}")))?;
433
434 let recipient_storage_address = crate::chain::quantus_subxt::api::storage()
436 .reversible_transfers()
437 .pending_transfers_by_recipient(account_id);
438
439 let incoming_transfers = quantus_client
440 .client()
441 .storage()
442 .at(latest_block_hash)
443 .fetch(&recipient_storage_address)
444 .await
445 .map_err(|e| crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}")))?;
446
447 let mut total_transfers = 0;
448
449 if let Some(outgoing_hashes) = outgoing_transfers {
451 if !outgoing_hashes.0.is_empty() {
452 log_print!("📤 Outgoing pending transfers:");
453 for (i, hash) in outgoing_hashes.0.iter().enumerate() {
454 total_transfers += 1;
455 log_print!(" {}. 0x{}", i + 1, hex::encode(hash.as_ref()));
456
457 let transfer_storage_address = crate::chain::quantus_subxt::api::storage()
459 .reversible_transfers()
460 .pending_transfers(*hash);
461
462 if let Ok(Some(transfer_details)) = quantus_client
463 .client()
464 .storage()
465 .at(latest_block_hash)
466 .fetch(&transfer_storage_address)
467 .await
468 .map_err(|e| {
469 crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}"))
470 }) {
471 let formatted_amount = format_amount(transfer_details.amount);
472 log_print!(" 👤 To: {}", transfer_details.to.to_quantus_ss58());
473 log_print!(" 💰 Amount: {}", formatted_amount);
474 log_print!(
475 " 🔄 Interceptor: {}",
476 transfer_details.interceptor.to_quantus_ss58()
477 );
478 }
479 }
480 }
481 }
482
483 if let Some(incoming_hashes) = incoming_transfers {
485 if !incoming_hashes.0.is_empty() {
486 if total_transfers > 0 {
487 log_print!("");
488 }
489 log_print!("📥 Incoming pending transfers:");
490 for (i, hash) in incoming_hashes.0.iter().enumerate() {
491 total_transfers += 1;
492 log_print!(" {}. 0x{}", i + 1, hex::encode(hash.as_ref()));
493
494 let transfer_storage_address = crate::chain::quantus_subxt::api::storage()
496 .reversible_transfers()
497 .pending_transfers(*hash);
498
499 if let Ok(Some(transfer_details)) = quantus_client
500 .client()
501 .storage()
502 .at(latest_block_hash)
503 .fetch(&transfer_storage_address)
504 .await
505 .map_err(|e| {
506 crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}"))
507 }) {
508 let formatted_amount = format_amount(transfer_details.amount);
509 log_print!(" 👤 From: {}", transfer_details.from.to_quantus_ss58());
510 log_print!(" 💰 Amount: {}", formatted_amount);
511 log_print!(
512 " 🔄 Interceptor: {}",
513 transfer_details.interceptor.to_quantus_ss58()
514 );
515 }
516 }
517 }
518 }
519
520 if total_transfers == 0 {
521 log_print!("📝 No pending transfers found for account: {}", target_address);
522 } else {
523 log_print!("");
524 log_print!("📊 Total pending transfers: {}", total_transfers);
525 log_print!("💡 Use transaction hash with 'quantus reversible cancel --tx-id <hash>' to cancel outgoing transfers");
526 }
527
528 Ok(())
529}
530
531fn format_amount(amount: u128) -> String {
533 const QUAN_DECIMALS: u128 = 1_000_000_000_000; if amount >= QUAN_DECIMALS {
536 let whole = amount / QUAN_DECIMALS;
537 let fractional = amount % QUAN_DECIMALS;
538
539 if fractional == 0 {
540 format!("{whole} QUAN")
541 } else {
542 let fractional_str = format!("{fractional:012}");
544 let trimmed = fractional_str.trim_end_matches('0');
545 format!("{whole}.{trimmed} QUAN")
546 }
547 } else {
548 format!("{amount} pico-QUAN")
549 }
550}