Contractless/src/bin/verify_sign_swap_tx.rs

255 lines
7.9 KiB
Rust
Raw Normal View History

2026-05-26 06:24:57 +00:00
use blockchain::blocks::swap::UnsignedSwapTransaction;
2026-06-23 17:24:35 +00:00
use blockchain::common::asset_names::padded_normalized_asset_input;
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-05-26 06:24:57 +00:00
use blockchain::env;
use blockchain::fs;
use blockchain::json;
use blockchain::read_to_string;
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;
2026-06-23 17:24:35 +00:00
fn normalize_asset_or_base(input: &str, base_coin: &str) -> Option<String> {
if input.trim().eq_ignore_ascii_case(base_coin.trim()) {
Some(base_coin.to_string())
} else {
padded_normalized_asset_input(input.trim())
}
2026-05-26 06:24:57 +00:00
}
#[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];
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;
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();
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;
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;
}
2026-06-23 17:24:35 +00:00
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;
};
2026-05-26 06:24:57 +00:00
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}");
}