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::wallets::structures::Wallet; const TOKEN_NAME_BYTES: usize = 15; const HASH_BYTES: usize = 32; const CREATOR_BYTES: usize = Wallet::SHORT_ADDRESS_BYTES_LENGTH; const TOKEN_COUNT_BYTES: usize = 8; const TOKEN_SPREAD_BYTES: usize = 4; const HARD_LIMIT_BYTES: usize = 1; const ISSUE_COUNT_BYTES: usize = 4; const TOKEN_LOOKUP_FIXED_BYTES: usize = TOKEN_NAME_BYTES + HASH_BYTES + CREATOR_BYTES + TOKEN_COUNT_BYTES + TOKEN_SPREAD_BYTES + HARD_LIMIT_BYTES + ISSUE_COUNT_BYTES; #[tokio::main] async fn main() { // Command 35 asks a peer for token metadata by token name. let hashmap_key = generate_uid(); let rpc_command = 35; // The request encoder pads/truncates the token name to the fixed 15-byte field. let args: Vec = env::args().collect(); if args.len() != 2 { println!("Usage: ./lookup_token "); return; } let token_name = args[1].clone(); 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 token record 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, token_name.clone(), rpc_command, handshake::HandshakeWallet::WalletKey(encryption_key.clone()), hashmap_key, ) .await; match result { Ok(response) => { // Successful token lookups are binary records with variable issue/burn hash lists. if let Some(token_json) = decode_token_lookup(&response) { println!("{token_json}"); connected = true; } else { let response_text = String::from_utf8_lossy(&response); let trimmed = response_text.trim(); if !trimmed.is_empty() { println!("{trimmed}"); connected = true; } } } Err(_) => connected = false, } } if !connected { eprintln!("failed to connect"); } } fn decode_token_lookup(response: &[u8]) -> Option { // The fixed portion must be present before reading variable-length issue/burn lists. if response.len() < TOKEN_LOOKUP_FIXED_BYTES { return None; } // Decode the fixed-width token fields in the same order the RPC command writes them. let mut cursor = 0usize; let token_name = binary_to_string(response[cursor..cursor + TOKEN_NAME_BYTES].to_vec()) .trim() .to_string(); cursor += TOKEN_NAME_BYTES; let contract = hex::encode(&response[cursor..cursor + HASH_BYTES]); cursor += HASH_BYTES; let creator = Wallet::bytes_to_short_address(&response[cursor..cursor + CREATOR_BYTES])?; cursor += CREATOR_BYTES; let token_count = u64::from_le_bytes( response[cursor..cursor + TOKEN_COUNT_BYTES] .try_into() .ok()?, ); cursor += TOKEN_COUNT_BYTES; let token_spread = u32::from_le_bytes( response[cursor..cursor + TOKEN_SPREAD_BYTES] .try_into() .ok()?, ); cursor += TOKEN_SPREAD_BYTES; let hard_limit = response[cursor]; cursor += HARD_LIMIT_BYTES; let issue_count = u32::from_le_bytes( response[cursor..cursor + ISSUE_COUNT_BYTES] .try_into() .ok()?, ); cursor += ISSUE_COUNT_BYTES; // After the issue count, each issue hash is 32 bytes. let issue_bytes = issue_count as usize * 32; if response.len() < cursor + issue_bytes + 4 { return None; } let mut issued_hashes = Vec::with_capacity(issue_count as usize); for chunk in response[cursor..cursor + issue_bytes].chunks(32) { issued_hashes.push(hex::encode(chunk)); } cursor += issue_bytes; let burn_count = u32::from_le_bytes(response[cursor..cursor + 4].try_into().ok()?); cursor += 4; // The final section is a fixed number of 32-byte burn hashes. let burn_bytes = burn_count as usize * 32; if response.len() != cursor + burn_bytes { return None; } let mut burned_hashes = Vec::with_capacity(burn_count as usize); for chunk in response[cursor..cursor + burn_bytes].chunks(32) { burned_hashes.push(hex::encode(chunk)); } // Convert atomic token units and counters into user-facing JSON. let display_token_count = token_count as f64 / 100_000_000.0; let display_token_spread = format!("{token_spread} addresses"); let output = json!({ "token_name": token_name, "genesis": contract, "creator": creator, "token_count": display_token_count, "token_spread": display_token_spread, "hard_limit": hard_limit == 1, "issued_hashes": issued_hashes, "burned_hashes": burned_hashes }); serde_json::to_string_pretty(&output).ok() }