use blockchain::blocks::nft::{CreateNftTransaction, UnsignedCreateNftTransaction}; use blockchain::common::asset_names::padded_normalized_asset_input; use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path}; use blockchain::common::types::CREATE_NFT_FEE; use blockchain::json; 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: f64 { value as f64 / 100_000_000.0 } #[tokio::main] async fn main() { // get user input series let series_data = prompt_visible("Are you creating a 1/1 or a collection? ").await; let series = if series_data.trim() == "collection" { 1 } else { 0 }; // get user input ticker let nft_ticker_name = prompt_visible("Please enter the name for your NFT. This can be 3 to 15 characters and must be 100% unique. First come first serve: ").await; let Some(nft_name) = padded_normalized_asset_input(nft_ticker_name.trim()) else { println!("NFT name must normalize to 3-15 alphanumeric characters."); return; }; // get the padded CID string let ipfs_hash = prompt_visible("Enter your IPFS CID for your NFT. This will be stored in a fixed 100 character field, so shorter CIDs are padded with spaces: ").await; let item_ipfs = pad_to_width(ipfs_hash.trim(), 100); // how many items in the collection or 1 for 1/1s let count_string = prompt_visible("Please enter the number of items in your collection, enter 1 for 1/1s: ") .await; let count: u32 = count_string .trim() .parse() .expect("Please enter a valid number"); // get the nft desc let nft_desc = prompt_visible("Enter your nft description, A max of 100 characters: ").await; let desc = pad_to_width(nft_desc.trim(), 100); // get user input txfee let txfee_prompt = format!( "Please enter the amount for the fee: (eg. 0.005, minimum fee {:.8})", display_fee(CREATE_NFT_FEE) ); let txfee_string = prompt_visible(&format!("{txfee_prompt}: ")).await; let txfee_f32: f32 = txfee_string .trim() .parse() .expect("Please enter a valid value."); let txfee = ((txfee_f32 as f64) * (100000000_f64)).round() as u64; let wallet_path = prompt_wallet_path().await; 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 { Ok(wallet) => wallet, Err(err) => { eprintln!("Wallet decryption failed: {err}"); return; } }; let private_key = &wallet.saved.private_key; let address = &wallet.saved.short_address; // set type and timestampe let txtype = 4; let timestamp = Utc::now().timestamp() as u32; // validate wallets if !Wallet::short_address_validation(address.trim_matches('"')) { println!("creator wallet invalid: {}", &address); return; } let unsigned_create_nft = UnsignedCreateNftTransaction::new( txtype, timestamp, address.trim_matches('"'), series, &nft_name, &item_ipfs, count, &desc, txfee, ) .await; let create_nft = match CreateNftTransaction::new(unsigned_create_nft, private_key).await { Ok(tx) => tx, Err(err) => { eprintln!("Failed to sign NFT transaction: {err}"); return; } }; let hashed_data = create_nft.unsigned_create_nft.hash().await; let signature = create_nft.signature.clone(); //add the signature and hash let output = json!({ "txtype": txtype, "timestamp": timestamp, "creator": address.trim_matches('"'), "series": series, "nft_name": nft_name, "item_ipfs": item_ipfs, "count": count, "desc": desc, "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}"); }