use crate::{
chain::quantus_subxt,
cli::{address_format::QuantusSS58, common::resolve_address},
error::Result,
log_info, log_print, log_verbose,
};
use clap::Subcommand;
use colored::Colorize;
use sp_core::crypto::{AccountId32 as SpAccountId32, Ss58Codec};
use std::str::FromStr;
#[derive(Subcommand, Debug)]
pub enum ReversibleCommands {
ScheduleTransfer {
#[arg(short, long)]
to: String,
#[arg(short, long)]
amount: String,
#[arg(short, long)]
from: String,
#[arg(short, long)]
password: Option<String>,
#[arg(long)]
password_file: Option<String>,
},
ScheduleTransferWithDelay {
#[arg(short, long)]
to: String,
#[arg(short, long)]
amount: String,
#[arg(short, long)]
delay: u64,
#[arg(long)]
unit_blocks: bool,
#[arg(short, long)]
from: String,
#[arg(short, long)]
password: Option<String>,
#[arg(long)]
password_file: Option<String>,
},
Cancel {
#[arg(long)]
tx_id: String,
#[arg(short, long)]
from: String,
#[arg(short, long)]
password: Option<String>,
#[arg(long)]
password_file: Option<String>,
},
ListPending {
#[arg(short, long)]
address: Option<String>,
#[arg(short, long)]
from: Option<String>,
#[arg(short, long)]
password: Option<String>,
#[arg(long)]
password_file: Option<String>,
},
}
pub async fn schedule_transfer(
quantus_client: &crate::chain::client::QuantusClient,
from_keypair: &crate::wallet::QuantumKeyPair,
to_address: &str,
amount: u128,
execution_mode: crate::cli::common::ExecutionMode,
) -> Result<subxt::utils::H256> {
log_verbose!("🔄 Creating reversible transfer...");
log_verbose!(" From: {}", from_keypair.to_account_id_ss58check().bright_cyan());
log_verbose!(" To: {}", to_address.bright_green());
log_verbose!(" Amount: {}", amount);
let (to_account_id_sp, _version) = SpAccountId32::from_ss58check_with_version(to_address)
.map_err(|e| {
crate::error::QuantusError::NetworkError(format!("Invalid destination address: {e:?}"))
})?;
let to_account_id_bytes: [u8; 32] = *to_account_id_sp.as_ref();
let to_account_id = subxt::ext::subxt_core::utils::AccountId32::from(to_account_id_bytes);
log_verbose!("✍️ Creating reversible transfer extrinsic...");
let transfer_call = quantus_subxt::api::tx()
.reversible_transfers()
.schedule_transfer(subxt::ext::subxt_core::utils::MultiAddress::Id(to_account_id), amount);
let tx_hash = crate::cli::common::submit_transaction(
quantus_client,
from_keypair,
transfer_call,
None,
execution_mode,
)
.await?;
log_verbose!("📋 Reversible transfer submitted: {:?}", tx_hash);
Ok(tx_hash)
}
pub async fn cancel_transaction(
quantus_client: &crate::chain::client::QuantusClient,
from_keypair: &crate::wallet::QuantumKeyPair,
tx_id: &str,
execution_mode: crate::cli::common::ExecutionMode,
) -> Result<subxt::utils::H256> {
log_verbose!("❌ Cancelling reversible transfer...");
log_verbose!(" Transaction ID: {}", tx_id.bright_yellow());
let tx_hash = subxt::utils::H256::from_str(tx_id).map_err(|e| {
crate::error::QuantusError::Generic(format!("Invalid transaction ID: {e:?}"))
})?;
log_verbose!("✍️ Creating cancel transaction extrinsic...");
let cancel_call = quantus_subxt::api::tx().reversible_transfers().cancel(tx_hash);
let tx_hash_result = crate::cli::common::submit_transaction(
quantus_client,
from_keypair,
cancel_call,
None,
execution_mode,
)
.await?;
log_verbose!("📋 Cancel transaction submitted: {:?}", tx_hash_result);
Ok(tx_hash_result)
}
pub async fn schedule_transfer_with_delay(
quantus_client: &crate::chain::client::QuantusClient,
from_keypair: &crate::wallet::QuantumKeyPair,
to_address: &str,
amount: u128,
delay: u64,
unit_blocks: bool,
execution_mode: crate::cli::common::ExecutionMode,
) -> Result<subxt::utils::H256> {
let unit_str = if unit_blocks { "blocks" } else { "seconds" };
log_verbose!("🔄 Creating reversible transfer with custom delay ...");
log_verbose!(" From: {}", from_keypair.to_account_id_ss58check().bright_cyan());
log_verbose!(" To: {}", to_address.bright_green());
log_verbose!(" Amount: {}", amount);
log_verbose!(" Delay: {} {}", delay, unit_str);
let to_account_id_sp = SpAccountId32::from_ss58check(to_address).map_err(|e| {
crate::error::QuantusError::NetworkError(format!("Invalid destination address: {e:?}"))
})?;
let to_account_id_bytes: [u8; 32] = *to_account_id_sp.as_ref();
let to_account_id_subxt = subxt::ext::subxt_core::utils::AccountId32::from(to_account_id_bytes);
let delay_value = if unit_blocks {
quantus_subxt::api::reversible_transfers::calls::types::schedule_transfer_with_delay::Delay::BlockNumber(delay as u32)
} else {
quantus_subxt::api::reversible_transfers::calls::types::schedule_transfer_with_delay::Delay::Timestamp(delay * 1000)
};
log_verbose!("✍️ Creating schedule_transfer_with_delay extrinsic...");
let transfer_call =
quantus_subxt::api::tx().reversible_transfers().schedule_transfer_with_delay(
subxt::ext::subxt_core::utils::MultiAddress::Id(to_account_id_subxt),
amount,
delay_value,
);
let tx_hash = crate::cli::common::submit_transaction(
quantus_client,
from_keypair,
transfer_call,
None,
execution_mode,
)
.await?;
log_verbose!("📋 Reversible transfer with custom delay submitted: {:?}", tx_hash);
Ok(tx_hash)
}
pub async fn handle_reversible_command(
command: ReversibleCommands,
node_url: &str,
execution_mode: crate::cli::common::ExecutionMode,
) -> Result<()> {
log_print!("🔄 Reversible Transfers");
let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
match command {
ReversibleCommands::ListPending { address, from, password, password_file } =>
list_pending_transactions(&quantus_client, address, from, password, password_file).await,
ReversibleCommands::ScheduleTransfer { to, amount, from, password, password_file } => {
let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
let (raw_amount, formatted_amount) =
crate::cli::send::validate_and_format_amount(&quantus_client, &amount).await?;
let resolved_address = resolve_address(&to)?;
log_info!(
"🔄 Scheduling reversible transfer of {} to {}",
formatted_amount,
resolved_address
);
log_verbose!(
"🚀 {} Scheduling reversible transfer {} to {} ()",
"REVERSIBLE".bright_cyan().bold(),
formatted_amount.bright_yellow().bold(),
resolved_address.bright_green()
);
log_verbose!("📦 Using wallet: {}", from.bright_blue().bold());
let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
let tx_hash = schedule_transfer(
&quantus_client,
&keypair,
&resolved_address,
raw_amount,
execution_mode,
)
.await?;
log_print!(
"✅ {} Reversible transfer scheduled! Hash: {:?}",
"SUCCESS".bright_green().bold(),
tx_hash
);
Ok(())
},
ReversibleCommands::Cancel { tx_id, from, password, password_file } => {
log_verbose!(
"❌ {} Cancelling reversible transfer {} ()",
"CANCEL".bright_red().bold(),
tx_id.bright_yellow().bold()
);
log_verbose!("📦 Using wallet: {}", from.bright_blue().bold());
let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
let tx_hash =
cancel_transaction(&quantus_client, &keypair, &tx_id, execution_mode).await?;
log_print!(
"✅ {} Cancel transaction submitted! Hash: {:?}",
"SUCCESS".bright_green().bold(),
tx_hash
);
Ok(())
},
ReversibleCommands::ScheduleTransferWithDelay {
to,
amount,
delay,
unit_blocks,
from,
password,
password_file,
} => {
let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
let (raw_amount, formatted_amount) =
crate::cli::send::validate_and_format_amount(&quantus_client, &amount).await?;
let resolved_address = resolve_address(&to)?;
let unit_str = if unit_blocks { "blocks" } else { "seconds" };
log_verbose!(
"🚀 {} Scheduling reversible transfer {} to {} with {} {} delay ()",
"REVERSIBLE".bright_cyan().bold(),
formatted_amount.bright_yellow().bold(),
resolved_address.bright_green(),
delay.to_string().bright_magenta(),
unit_str
);
log_verbose!("📦 Using wallet: {}", from.bright_blue().bold());
let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
let tx_hash = schedule_transfer_with_delay(
&quantus_client,
&keypair,
&resolved_address,
raw_amount,
delay,
unit_blocks,
execution_mode,
)
.await?;
log_print!(
"✅ {} Reversible transfer with custom delay scheduled! Hash: {:?}",
"SUCCESS".bright_green().bold(),
tx_hash
);
Ok(())
},
}
}
async fn list_pending_transactions(
quantus_client: &crate::chain::client::QuantusClient,
address: Option<String>,
wallet_name: Option<String>,
password: Option<String>,
password_file: Option<String>,
) -> Result<()> {
log_print!("📋 Listing pending reversible transactions");
let target_address = match (address, wallet_name) {
(Some(addr), _) => {
SpAccountId32::from_ss58check(&addr).map_err(|e| {
crate::error::QuantusError::Generic(format!("Invalid address: {e:?}"))
})?;
addr
},
(None, Some(wallet)) => {
let keypair =
crate::wallet::load_keypair_from_wallet(&wallet, password, password_file)?;
keypair.to_account_id_ss58check()
},
(None, None) => {
return Err(crate::error::QuantusError::Generic(
"Either --address or --from must be provided".to_string(),
));
},
};
let account_id_sp = SpAccountId32::from_ss58check(&target_address)
.map_err(|e| crate::error::QuantusError::Generic(format!("Invalid address: {e:?}")))?;
let account_id_bytes: [u8; 32] = *account_id_sp.as_ref();
let account_id = subxt::ext::subxt_core::utils::AccountId32::from(account_id_bytes);
log_verbose!("🔍 Querying pending transfers for: {}", target_address);
let sender_storage_address = crate::chain::quantus_subxt::api::storage()
.reversible_transfers()
.pending_transfers_by_sender(account_id);
let latest_block_hash = quantus_client.get_latest_block().await?;
let outgoing_transfers = quantus_client
.client()
.storage()
.at(latest_block_hash)
.fetch(&sender_storage_address)
.await
.map_err(|e| crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}")))?;
let mut total_transfers = 0;
if let Some(outgoing_hashes) = outgoing_transfers {
if !outgoing_hashes.0.is_empty() {
log_print!("📤 Outgoing pending transfers:");
for (i, hash) in outgoing_hashes.0.iter().enumerate() {
total_transfers += 1;
log_print!(" {}. 0x{}", i + 1, hex::encode(hash.as_ref()));
let transfer_storage_address = crate::chain::quantus_subxt::api::storage()
.reversible_transfers()
.pending_transfers(*hash);
if let Ok(Some(transfer_details)) = quantus_client
.client()
.storage()
.at(latest_block_hash)
.fetch(&transfer_storage_address)
.await
.map_err(|e| {
crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}"))
}) {
let formatted_amount = format_amount(transfer_details.amount);
log_print!(" 👤 To: {}", transfer_details.to.to_quantus_ss58());
log_print!(" 💰 Amount: {}", formatted_amount);
log_print!(
" 🔄 Interceptor: {}",
transfer_details.interceptor.to_quantus_ss58()
);
}
}
}
}
if total_transfers == 0 {
log_print!("📝 No pending transfers found for account: {}", target_address);
} else {
log_print!("");
log_print!("📊 Total pending transfers: {}", total_transfers);
log_print!("💡 Use transaction hash with 'quantus reversible cancel --tx-id <hash>' to cancel outgoing transfers");
}
Ok(())
}
fn format_amount(amount: u128) -> String {
const QUAN_DECIMALS: u128 = 1_000_000_000_000;
if amount >= QUAN_DECIMALS {
let whole = amount / QUAN_DECIMALS;
let fractional = amount % QUAN_DECIMALS;
if fractional == 0 {
format!("{whole} QUAN")
} else {
let fractional_str = format!("{fractional:012}");
let trimmed = fractional_str.trim_end_matches('0');
format!("{whole}.{trimmed} QUAN")
}
} else {
format!("{amount} pico-QUAN")
}
}