148 lines
5.0 KiB
Rust
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}");
|
||
|
|
}
|