Contractless/src/bin/create_burn_tx.rs

148 lines
5.0 KiB
Rust

use blockchain::blocks::burn::{BurnTransaction, UnsignedBurnTransaction};
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::BURN_FEE;
use blockchain::json;
use blockchain::wallets::structures::Wallet;
use blockchain::File;
use blockchain::Utc;
use blockchain::{create_dir_all, AsyncWriteExt};
// Pad the asset name so it matches the 15-byte on-chain asset format.
fn pad_to_width(input: &str, width: usize) -> String {
let mut result = String::with_capacity(width);
let _ = std::fmt::write(
&mut result,
format_args!("{input:<width$}"),
);
result
}
fn display_fee(value: u64) -> f64 {
// Fees are stored as atomic units and displayed as whole-coin decimals for the CLI.
value as f64 / 100_000_000.0
}
#[tokio::main]
async fn main() {
// Burn transactions use type 10 and the current local timestamp.
let txtype = 10;
let timestamp = Utc::now().timestamp() as u32;
// Burnable assets are tokens or NFTs, never the base coin for this transaction type.
let coin_name = prompt_visible("Please enter the token or NFT name you wish to burn: ").await;
let coin = pad_to_width(&coin_name.trim().to_lowercase(), 15);
if coin.trim().to_lowercase() == block_extension_and_paths().1.trim().to_lowercase() {
println!("Base coin cannot be burned with this transaction type.");
return;
}
// Series 0 is used for fungible tokens and 1/1 NFTs; series NFTs use their item number.
let nft_series_string = prompt_visible(
"Please enter the NFT series number to burn, or 0 for standard tokens / 1of1 NFTs: ",
)
.await;
let nft_series: u32 = nft_series_string
.trim()
.parse()
.expect("Please enter a valid NFT series number.");
// Values are entered as display units and saved as atomic units.
let value_string = prompt_visible("Please enter the burn amount: (use 1.0 for NFTs) ").await;
let value_f64: f64 = value_string
.trim()
.parse()
.expect("Please enter a valid amount.");
let value = (value_f64 * 100_000_000.0).round() as u64;
let txfee_prompt = format!(
"Please enter the amount for the fee: (eg. 0.0001, minimum fee {:.8})",
display_fee(BURN_FEE)
);
let txfee_string = prompt_visible(&format!("{txfee_prompt}: ")).await;
let txfee_f64: f64 = txfee_string
.trim()
.parse()
.expect("Please enter a valid fee.");
let txfee = (txfee_f64 * 100_000_000.0).round() as u64;
let decryption_key = prompt_hidden_nonempty(
"What is your wallet decryption key? ",
"Wallet key cannot be empty. Please try again.",
)
.await;
// Load the wallet that owns the asset being burned.
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 address = &wallet.saved.short_address;
// The burn address must be a valid current-network short address.
if !Wallet::short_address_validation(address.trim_matches('"')) {
println!("burner wallet invalid: {}", &address);
return;
}
// Build and sign the burn transaction before writing the broadcast JSON.
let unsigned_burn = UnsignedBurnTransaction::new(
txtype,
timestamp,
address.trim_matches('"'),
&coin,
nft_series,
value,
txfee,
)
.await;
let burn = match BurnTransaction::new(unsigned_burn, private_key).await {
Ok(tx) => tx,
Err(err) => {
eprintln!("Failed to sign burn transaction: {err}");
return;
}
};
let hashed_data = burn.unsigned_burn.hash().await;
let signature = burn.signature.clone();
// Save the signed transaction as JSON so broadcast_transaction can submit it later.
let output = json!({
"txtype": txtype,
"timestamp": timestamp,
"address": address.trim_matches('"'),
"coin": coin,
"nft_series": nft_series,
"value": value,
"txfee": txfee,
"hash": hashed_data,
"signature": signature,
});
let output_str = serde_json::to_string_pretty(&output).expect("Failed to serialize JSON");
// Save the transaction JSON into the standard transactions folder for broadcasting.
let dir_path = "./transactions";
if let Err(error) = create_dir_all(&dir_path).await {
eprintln!("Failed to create directory: {error}");
return;
}
let file_path = format!("{dir_path}/{hashed_data}.json");
let mut file = File::create(&file_path)
.await
.expect("Failed to create file");
if let Err(error) = file.write_all(output_str.as_bytes()).await {
eprintln!("Failed to write file: {error}");
return;
}
println!("transaction: {output_str}");
}