Contractless/src/bin/lookup_nft_list.rs

125 lines
4.0 KiB
Rust

use blockchain::common::binary_conversions::binary_to_string;
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
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<String> {
// 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<String> = env::args().collect();
if args.len() != 1 {
println!("Usage: ./nft_list");
return;
}
let wallet_path = prompt_wallet_path().await;
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: encryption_key.clone(),
wallet_path: wallet_path.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");
}
}