use blockchain::blocks::swap::UnsignedSwapTransaction; use blockchain::common::asset_names::padded_normalized_asset_input; use blockchain::common::cli_prompts::{ ask_yes_no_question, prompt_hidden_nonempty, prompt_wallet_path, }; use blockchain::common::network_paths_and_settings::block_extension_and_paths; use blockchain::env; use blockchain::fs; use blockchain::json; use blockchain::read_to_string; use blockchain::standalone_tools::vanity_resolver::resolve_wallet_address_input; use blockchain::wallets::structures::Wallet; use blockchain::Value; fn normalize_asset_or_base(input: &str, base_coin: &str) -> Option { if input.trim().eq_ignore_ascii_case(base_coin.trim()) { Some(base_coin.to_string()) } else { padded_normalized_asset_input(input.trim()) } } #[tokio::main] async fn main() { // Get the filename from the command line arguments let args: Vec = env::args().collect(); if args.len() != 2 { println!("Usage:./sign_swap "); return; } let filename = &args[1]; let wallet_path = prompt_wallet_path().await; 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, Some(&wallet_path)).await { Ok(wallet) => wallet, Err(err) => { eprintln!("Wallet decryption failed: {err}"); return; } }; let private_key = &wallet.saved.private_key; let address = &wallet.saved.short_address; // Read the contents of the file let contents = match read_to_string(filename).await { Ok(contents) => contents, Err(_) => { println!("Error reading file: {filename}"); return; } }; // Parse the JSON let json: Result = serde_json::from_str(&contents); let json = match json { Ok(json) => json, Err(_) => { println!("Error parsing JSON in file: {filename}"); return; } }; let txtype = 6; let timestamp = json["timestamp"].as_u64().unwrap_or_default() as u32; let offer_expiration = json["offer_expiration"].as_u64().unwrap_or_default() as u32; // get values from transaction json let token_name1 = json["ticker1"] .as_str() .unwrap_or_default() .trim() .to_lowercase(); let nft_series1: u32 = json["nft_series1"].as_u64().unwrap_or_default() as u32; let value1: u64 = json["value1"].as_u64().unwrap_or_default(); let receive_amount = value1 as f64 / 100000000.0; let token_name2 = json["ticker2"] .as_str() .unwrap_or_default() .trim() .to_lowercase(); let nft_series2: u32 = json["nft_series2"].as_u64().unwrap_or_default() as u32; let value2: u64 = json["value2"].as_u64().unwrap_or_default(); let send_amount = value2 as f64 / 100000000.0; let txfee1_value: u64 = json["txfee1"].as_u64().unwrap_or_default(); let tip1_value: u64 = json["tip1"].as_u64().unwrap_or_default(); let sender1 = match resolve_wallet_address_input(json["sender1"].as_str().unwrap_or_default(), &wallet) .await { Ok(address) => address, Err(err) => { println!("sender1 wallet invalid: {err}"); return; } }; let txfee2_value: u64 = json["txfee2"].as_u64().unwrap_or_default(); let txfee2 = txfee2_value as f64 / 100000000.0; let tip2_value: u64 = json["tip2"].as_u64().unwrap_or_default(); let tip2 = tip2_value as f64 / 100000000.0; let sender2 = match resolve_wallet_address_input(json["sender2"].as_str().unwrap_or_default(), &wallet) .await { Ok(address) => address, Err(err) => { println!("sender2 wallet invalid: {err}"); return; } }; // ensure wallet and sender2 match if sender2 != address.trim() { println!( "Transaction is not valid for your wallet address. Expected {sender2} found {address}" ); return; } let ( _network_name, network_coin, _suffix, _torrent_path, _wallet_path, _blockpath, _db_path, _balance_path, _log_path, ) = block_extension_and_paths(); // setup validation questions let question1 = format!("Are you expecting to receive {receive_amount} {token_name1}?"); let question2 = format!("Are you expecting to send {send_amount} {token_name2}?"); let question3 = format!("Are you willing to spend {txfee2} {network_coin} in fees?"); let question4 = format!("Are you willing to tip {tip2} {token_name2}?"); // ask validation questions if !ask_yes_no_question(&question1).await { println!("Transaction is not valid"); return; } if !ask_yes_no_question(&question2).await { println!("Transaction is not valid"); return; } if !ask_yes_no_question(&question3).await { println!("Transaction is not valid"); return; } if !ask_yes_no_question(&question4).await { println!("Transaction is not valid"); return; } let Some(padded_token_name1) = normalize_asset_or_base(&token_name1, &network_coin) else { println!("Ticker1 must be the base coin or normalize to 3-15 alphanumeric characters."); return; }; let Some(padded_token_name2) = normalize_asset_or_base(&token_name2, &network_coin) else { println!("Ticker2 must be the base coin or normalize to 3-15 alphanumeric characters."); return; }; let unsigned_swap = UnsignedSwapTransaction::new( txtype, timestamp, offer_expiration, &padded_token_name1, nft_series1, value1, &padded_token_name2, nft_series2, value2, &sender1, address.trim(), tip1_value, tip2_value, txfee1_value, txfee2_value, ) .await; let hashed_data = unsigned_swap.hash().await; let original_hash = &json["hash"].as_str().unwrap_or_default().trim(); let signature1 = &json["signature1"].as_str().unwrap_or_default().trim(); let signature2 = if hashed_data == *original_hash.to_string() { match unsigned_swap.hash_and_sign(&private_key.to_string()).await { Ok(signature) => signature, Err(err) => { println!("Signing transaction failed: {err}"); return; } } } else { println!("Signing transaction failed. The included hash was incorrect."); return; }; let output = json!({ "txtype": txtype, "timestamp": timestamp, "offer_expiration": offer_expiration, "ticker1": padded_token_name1, "nft_series1": nft_series1, "value1": value1, "ticker2": padded_token_name2, "nft_series2": nft_series2, "value2": value2, "sender1": sender1, "sender2": address.trim(), "tip1": tip1_value, "tip2": tip2_value, "txfee1": txfee1_value, "txfee2": txfee2_value, "hash": hashed_data, "signature1": signature1, "signature2": signature2 }); 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) = fs::create_dir_all(dir_path) { eprintln!("Failed to create directory: {e}"); return; } // Define the file path let file_path = format!("{dir_path}/{hashed_data}.json"); // Write the JSON string to the file if let Err(e) = fs::write(&file_path, &output_str) { eprintln!("Failed to write file: {e}"); return; } println!("{output_str}"); }