173 lines
5.6 KiB
Rust
173 lines
5.6 KiB
Rust
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<String> = env::args().collect();
|
|
if args.len() != 2 {
|
|
println!("Usage: ./lookup_token <token_name>");
|
|
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<String> {
|
|
// 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()
|
|
}
|