use blockchain::blocks::transfer::{TransferTransaction, UnsignedTransferTransaction}; use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible}; use blockchain::common::network_paths_and_settings::block_extension_and_paths; use blockchain::common::types::{NON_BASE_TRANSFER_MIN_FEE, TRANSFER_FEE}; use blockchain::env; use blockchain::json; use blockchain::records::wallet_registry::resolve_local_input_short_address; use blockchain::wallets::structures::Wallet; use blockchain::File; use blockchain::Utc; use blockchain::{create_dir_all, AsyncWriteExt}; // pad the coin to ensure 15 characters fn pad_to_width(input: &str, width: usize) -> String { let mut result = String::with_capacity(width); // Pre-allocate string with capacity let _ = std::fmt::write(&mut result, format_args!("{input: f64 { value as f64 / 100_000_000.0 } fn normalize_short_address_input(address: &str) -> Result { resolve_local_input_short_address(address.trim()) } #[tokio::main] async fn main() { let minimum_transfer_fee_percent = TRANSFER_FEE * 100.0; // set type and timestamp let txtype = 2; let timestamp = Utc::now().timestamp() as u32; let args: Vec = env::args().collect(); if args.len() > 1 && args.len() != 6 { println!("Usage: ./create_transfer_tx "); return; } let coin_name = if args.len() > 1 { args[1].clone() } else { prompt_visible("Please enter the coin or token you wish to transfer: ").await }; let coin = pad_to_width(&coin_name, 15); let is_base_coin = coin.trim().to_lowercase() == block_extension_and_paths().1.trim().to_lowercase(); let value_string = if args.len() > 2 { args[2].clone() } else { prompt_visible("Please enter the amount of coins or tokens to send: (eg. 23.4): ").await }; let value_f32: f32 = value_string .trim() .parse() .expect("Please enter a valid amount."); let value = ((value_f32 as f64) * 100_000_000.0).round() as u64; let receiver_input = if args.len() > 3 { args[3].clone() } else { prompt_visible("Please enter the receiver wallet address: ").await }; let receiver = match normalize_short_address_input(&receiver_input) { Ok(address) => address, Err(_) => { println!("reciver wallet is not valid"); return; } }; let txfee_string = if args.len() > 4 { args[4].clone() } else { let prompt = if is_base_coin { format!( "Please enter the amount for the fee: (eg. 1.09, minimum fee {minimum_transfer_fee_percent:.0}% of transaction):" ) } else { format!( "Please enter the amount for the fee: (eg. 0.0001, minimum fee {:.8} for tokens/NFTs):", display_fee(NON_BASE_TRANSFER_MIN_FEE) ) }; prompt_visible(&format!("{prompt} ")).await }; let txfee_f32: f32 = txfee_string .trim() .parse() .expect("Please enter a valid fee."); let txfee = ((txfee_f32 as f64) * 100_000_000.0).round() as u64; let nft_series_string = if args.len() > 5 { args[5].clone() } else { prompt_visible( "Please enter the NFT series number to transfer, or 0 for coins/tokens/1of1 NFTs: ", ) .await }; let nft_series: u32 = nft_series_string .trim() .parse() .expect("Please enter a valid NFT series number."); let decryption_key = prompt_hidden_nonempty( "What is your wallet decryption key? ", "Wallet key cannot be empty. Please try again.", ) .await; let wallet = match Wallet::try_obtain_wallet(decryption_key, None).await { Ok(wallet) => wallet, Err(err) => { eprintln!("Wallet decryption failed: {err}"); return; } }; let private_key = &wallet.saved.private_key; let short_address = &wallet.saved.short_address; if !Wallet::short_address_validation(short_address.trim()) { println!("sender wallet invalid: {short_address}"); return; } if short_address.trim() == receiver.trim() { println!("You cannot send funds to yourself"); return; } if nft_series > 0 && value != 100_000_000 { println!("Series NFT transfers must have a value of exactly 1.0"); return; } let unsigned_transfer = UnsignedTransferTransaction::new( txtype, timestamp, value, &coin, nft_series, short_address.trim(), &receiver, txfee, ) .await; let transfer = match TransferTransaction::new(unsigned_transfer, private_key).await { Ok(tx) => tx, Err(err) => { eprintln!("Failed to sign transfer transaction: {err}"); return; } }; let hashed_data = transfer.unsigned_transfer.hash().await; let signature = transfer.signature.clone(); let output = json!({ "txtype": txtype, "timestamp": timestamp, "value": value, "coin": coin, "nft_series": nft_series, "sender": short_address.trim(), "receiver": receiver, "txfee": txfee, "hash": hashed_data, "signature": signature, }); let output_str = serde_json::to_string_pretty(&output).expect("Failed to serialize JSON"); // Define the directory path let dir_path = "./transactions"; // Create the directory if it doesn't exist if let Err(e) = create_dir_all(&dir_path).await { eprintln!("Failed to create directory: {e}"); return; } // Define the file path let file_path = format!("{dir_path}/{hashed_data}.json"); // Open the file for writing asynchronously let mut file = File::create(&file_path) .await .expect("Failed to create file"); // Write the JSON string to the file asynchronously if let Err(e) = file.write_all(output_str.as_bytes()).await { eprintln!("Failed to write file: {e}"); return; } println!("transaction: {output_str}"); }