Contractless/src/bin/create_transfer_tx.rs

207 lines
6.2 KiB
Rust
Raw Normal View History

2026-05-24 17:56:57 +00:00
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:<width$}"),
);
result
}
fn display_fee(value: u64) -> f64 {
value as f64 / 100_000_000.0
}
fn normalize_short_address_input(address: &str) -> Result<String, String> {
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<String> = env::args().collect();
if args.len() > 1 && args.len() != 6 {
println!("Usage: ./create_transfer_tx <coin> <value> <receiver> <txfee> <nft_series>");
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}");
}