2026-05-26 06:24:57 +00:00
|
|
|
use blockchain::blocks::swap::UnsignedSwapTransaction;
|
2026-06-04 15:06:51 +00:00
|
|
|
use blockchain::common::cli_prompts::{
|
|
|
|
|
ask_yes_no_question, prompt_hidden_nonempty, prompt_wallet_path,
|
|
|
|
|
};
|
2026-05-26 06:24:57 +00:00
|
|
|
use blockchain::common::network_paths_and_settings::block_extension_and_paths;
|
2026-06-04 15:06:51 +00:00
|
|
|
|
2026-05-26 06:24:57 +00:00
|
|
|
use blockchain::env;
|
|
|
|
|
use blockchain::fs;
|
|
|
|
|
use blockchain::json;
|
|
|
|
|
use blockchain::read_to_string;
|
2026-06-04 15:06:51 +00:00
|
|
|
use blockchain::standalone_tools::vanity_resolver::resolve_wallet_address_input;
|
2026-05-26 06:24:57 +00:00
|
|
|
use blockchain::wallets::structures::Wallet;
|
|
|
|
|
use blockchain::Value;
|
|
|
|
|
|
|
|
|
|
// padd 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:<width$}"));
|
|
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::main]
|
|
|
|
|
async fn main() {
|
|
|
|
|
// Get the filename from the command line arguments
|
|
|
|
|
let args: Vec<String> = env::args().collect();
|
|
|
|
|
if args.len() != 2 {
|
|
|
|
|
println!("Usage:./sign_swap <path/to/file.json>");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let filename = &args[1];
|
2026-06-04 15:06:51 +00:00
|
|
|
let wallet_path = prompt_wallet_path().await;
|
2026-05-26 06:24:57 +00:00
|
|
|
let decryption_key = prompt_hidden_nonempty(
|
|
|
|
|
"What is your wallet decryption key? ",
|
|
|
|
|
"Wallet key cannot be empty. Please try again.",
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
|
2026-06-04 15:06:51 +00:00
|
|
|
let wallet = match Wallet::try_obtain_wallet(decryption_key, Some(&wallet_path)).await {
|
2026-05-26 06:24:57 +00:00
|
|
|
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<Value, _> = 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();
|
|
|
|
|
|
2026-06-04 15:06:51 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-05-26 06:24:57 +00:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2026-06-04 15:06:51 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-05-26 06:24:57 +00:00
|
|
|
|
|
|
|
|
// 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 padded_token_name1 = pad_to_width(&token_name1, 15);
|
|
|
|
|
let padded_token_name2 = pad_to_width(&token_name2, 15);
|
|
|
|
|
|
|
|
|
|
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!("Transaction: {output_str}");
|
|
|
|
|
}
|