use blockchain::common::binary_conversions::binary_to_string; use blockchain::common::cli_prompts::prompt_hidden_nonempty; use blockchain::common::network_startup::get_connections; use blockchain::env; use blockchain::json; use blockchain::records::memory::response_channels::generate_uid; use blockchain::standalone_tools::connections::handshake; use blockchain::to_string_pretty; use blockchain::{Map, Value}; fn decode_nft_list(response: &[u8]) -> Option { // An empty binary payload means the peer has no NFT index entries. if response.is_empty() { return Some("{\n \"nfts\": {}\n}".to_string()); } // Each NFT-list row is 32 bytes hash, 15 bytes name, and 4 bytes series. if response.len() % 51 != 0 { return None; } let mut grouped = Map::new(); let mut offset = 0; while offset + 51 <= response.len() { // Group entries under their genesis hash so a collection can contain multiple series items. let hash = binary_to_string(response[offset..offset + 32].to_vec()) .trim() .to_string(); let nft_name = binary_to_string(response[offset + 32..offset + 47].to_vec()) .trim() .to_string(); let series = u32::from_le_bytes(response[offset + 47..offset + 51].try_into().ok()?); if !hash.is_empty() && !nft_name.is_empty() { let entry = grouped .entry(hash) .or_insert_with(|| Value::Array(Vec::new())); if let Value::Array(items) = entry { items.push(json!({ "nft_name": nft_name, "series": series, })); } } offset += 51; } let output = json!({ "nfts": Value::Object(grouped), }); to_string_pretty(&output).ok() } #[tokio::main] async fn main() { // Command 32 asks a peer for the full NFT list. let hashmap_key = generate_uid(); let rpc_command = 32; // This lookup takes no user arguments beyond the wallet key used for handshake auth. let args: Vec = env::args().collect(); if args.len() != 1 { println!("Usage: ./nft_list"); return; } let encryption_key = prompt_hidden_nonempty( "What is your wallet decryption key? ", "Wallet key cannot be empty. Please try again.", ) .await; // Try each configured peer until one returns a parsable NFT list or text error. let connections = get_connections().await; let mut connected = false; for conn in connections { if connected { break; } let socket_address = conn.parse().expect("Failed to parse the socket address"); let result = handshake::connect_and_handshake( socket_address, "".to_string(), rpc_command, handshake::HandshakeWallet::WalletKey(encryption_key.clone()), hashmap_key, ) .await; match result { Ok(response) => { // Prefer binary NFT-list decoding; otherwise print a text error if one was returned. if let Some(output) = decode_nft_list(&response) { println!("{output}"); connected = true; } else { let response_text = String::from_utf8_lossy(&response); let trimmed = response_text.trim(); if !trimmed.is_empty() { println!("{trimmed}"); connected = true; } else { connected = false; } } } Err(_) => { connected = false; } } } if !connected { eprintln!("failed to connect"); } }