From eef98c4a00fe7f81663a706ec47cb16fcf094fc4 Mon Sep 17 00:00:00 2001 From: viraladmin <00purple@gmail.com> Date: Sat, 27 Jun 2026 14:14:01 -0600 Subject: [PATCH] rpc changes for asset reporting --- src/rpc/commands/token_list.rs | 10 +- src/rpc/commands/token_lookup.rs | 192 +++++++++++++++++-------------- 2 files changed, 115 insertions(+), 87 deletions(-) diff --git a/src/rpc/commands/token_list.rs b/src/rpc/commands/token_list.rs index 578b10f..fe3bdcf 100644 --- a/src/rpc/commands/token_list.rs +++ b/src/rpc/commands/token_list.rs @@ -1,4 +1,5 @@ use crate::common::binary_conversions::binary_to_string; +use crate::encode; use crate::rpc::responses::RpcResponse; use crate::sled::Db; @@ -24,7 +25,14 @@ pub async fn get_tokens(db: &Db) -> RpcResponse { .get(&key) .ok() .flatten() - .map(|bytes| bytes.to_vec()) + .map(|bytes| { + let bytes = bytes.to_vec(); + if bytes.len() == 32 { + encode(&bytes).into_bytes() + } else { + bytes + } + }) .unwrap_or_default(); if hash.len() != 64 { diff --git a/src/rpc/commands/token_lookup.rs b/src/rpc/commands/token_lookup.rs index 116cf5f..a96f37b 100644 --- a/src/rpc/commands/token_lookup.rs +++ b/src/rpc/commands/token_lookup.rs @@ -75,7 +75,11 @@ async fn find_origin_hash( // Prefer the dedicated token-origins tree, but fall back to scanning // saved txids if the origin mapping is missing. if let Ok(Some(bytes)) = origins_tree.get(matched_ticker.as_bytes()) { - return Some(binary_to_string(bytes.to_vec())); + let origin_bytes = bytes.to_vec(); + if origin_bytes.len() == 32 { + return Some(encode(&origin_bytes)); + } + return Some(binary_to_string(origin_bytes)); } let txid_tree = db.open_tree("txid").ok()?; @@ -197,7 +201,6 @@ pub async fn lookup_token_details(db: &Db, token_name: String) -> RpcResponse { if normalized_query.is_empty() { return RpcResponse::Binary(b"error: Token name required".to_vec()); } - let tokens_tree = db.open_tree("tokens").unwrap(); let origins_tree = db.open_tree("token_origins").unwrap(); let limits_tree = db.open_tree("token_limits").unwrap(); @@ -297,85 +300,100 @@ pub async fn lookup_token_details(db: &Db, token_name: String) -> RpcResponse { } pub async fn token_catalog(db: &Db) -> RpcResponse { - let tokens_tree = match db.open_tree("tokens") { - Ok(tree) => tree, - Err(err) => { - return RpcResponse::Binary( - format!("error: Failed to open tokens tree: {err}").into_bytes(), - ) - } - }; - let origins_tree = match db.open_tree("token_origins") { - Ok(tree) => tree, - Err(err) => { - return RpcResponse::Binary( - format!("error: Failed to open token origins tree: {err}").into_bytes(), - ) - } - }; - let limits_tree = match db.open_tree("token_limits") { - Ok(tree) => tree, - Err(err) => { - return RpcResponse::Binary( - format!("error: Failed to open token limits tree: {err}").into_bytes(), - ) - } - }; + const TOKEN_LIST_ROW_BYTES: usize = 15 + 64; + const TOKEN_NAME_BYTES: usize = 15; + const TOKEN_HASH_BYTES: usize = 32; + const TOKEN_COUNT_BYTES: usize = 8; + const TOKEN_SPREAD_BYTES: usize = 4; + const ISSUE_COUNT_BYTES: usize = 4; + const BURN_COUNT_BYTES: usize = 4; let mut rows = Vec::new(); + let RpcResponse::Binary(token_list_bytes) = crate::rpc::commands::token_list::get_tokens(db).await; - for entry in origins_tree.iter() { - let (ticker_key, origin_value) = match entry { - Ok(pair) => pair, + if token_list_bytes.len() % TOKEN_LIST_ROW_BYTES != 0 { + return RpcResponse::Binary(b"error: Token list response had an unexpected byte length".to_vec()); + } + + for token_row in token_list_bytes.chunks_exact(TOKEN_LIST_ROW_BYTES) { + let ticker = binary_to_string(token_row[0..TOKEN_NAME_BYTES].to_vec()); + if ticker.trim().is_empty() { + continue; + } + + let RpcResponse::Binary(details) = lookup_token_details(db, ticker.clone()).await; + if details.starts_with(b"error:") { + continue; + } + + let fixed_details_len = TOKEN_NAME_BYTES + + TOKEN_HASH_BYTES + + Wallet::SHORT_ADDRESS_BYTES_LENGTH + + TOKEN_COUNT_BYTES + + TOKEN_SPREAD_BYTES + + 1 + + ISSUE_COUNT_BYTES; + if details.len() < fixed_details_len { + continue; + } + + let mut offset = 0usize; + let ticker_bytes = details[offset..offset + TOKEN_NAME_BYTES].to_vec(); + offset += TOKEN_NAME_BYTES; + + let origin_hash = details[offset..offset + TOKEN_HASH_BYTES].to_vec(); + offset += TOKEN_HASH_BYTES; + + let creator = details[offset..offset + Wallet::SHORT_ADDRESS_BYTES_LENGTH].to_vec(); + offset += Wallet::SHORT_ADDRESS_BYTES_LENGTH; + + let current_supply = match details[offset..offset + TOKEN_COUNT_BYTES].try_into() { + Ok(bytes) => u64::from_le_bytes(bytes), Err(_) => continue, }; - let ticker = binary_to_string(ticker_key.to_vec()); + offset += TOKEN_COUNT_BYTES; + + let holder_count = match details[offset..offset + TOKEN_SPREAD_BYTES].try_into() { + Ok(bytes) => u32::from_le_bytes(bytes), + Err(_) => continue, + }; + offset += TOKEN_SPREAD_BYTES; + + let hard_limit = details[offset]; + offset += 1; + + let issue_count = match details[offset..offset + ISSUE_COUNT_BYTES].try_into() { + Ok(bytes) => u32::from_le_bytes(bytes) as usize, + Err(_) => continue, + }; + offset += ISSUE_COUNT_BYTES; + + let Some(issue_hash_bytes) = issue_count.checked_mul(TOKEN_HASH_BYTES) else { + continue; + }; + if details.len().saturating_sub(offset) < issue_hash_bytes + BURN_COUNT_BYTES { + continue; + } + offset += issue_hash_bytes; + + let burn_count = match details[offset..offset + BURN_COUNT_BYTES].try_into() { + Ok(bytes) => u32::from_le_bytes(bytes) as usize, + Err(_) => continue, + }; + offset += BURN_COUNT_BYTES; + + let Some(burn_hash_bytes) = burn_count.checked_mul(TOKEN_HASH_BYTES) else { + continue; + }; + if details.len().saturating_sub(offset) != burn_hash_bytes { + continue; + } + let normalized_ticker = strip_spaces_and_lowercase(&ticker); - if normalized_ticker.is_empty() { - continue; - } - - let origin_hash = binary_to_string(origin_value.to_vec()); - let origin_hash_bytes = match decode(&origin_hash) { - Ok(bytes) if bytes.len() == 32 => bytes, - _ => continue, - }; - - let RpcResponse::Binary(tx_bytes) = - request_transaction_by_txid(db, origin_hash_bytes.clone()).await; - if tx_bytes.is_empty() || tx_bytes[0] != 3 { - continue; - } - - let contract = match CreateTokenTransaction::from_bytes(tx_bytes[0], &tx_bytes[1..]).await { - Ok(tx) => tx, - Err(_) => continue, - }; - - let creator_bytes = - match Wallet::short_address_to_bytes(&contract.unsigned_create_token.creator) { - Some(bytes) => bytes, - None => continue, - }; - - let current_supply = tokens_tree - .get(ticker_key.as_ref()) - .ok() - .flatten() - .and_then(|value| parse_token_supply(value.as_ref())) - .unwrap_or(0); - let hard_limit = limits_tree - .get(ticker_key.as_ref()) - .ok() - .flatten() - .and_then(|bytes| bytes.first().copied()) - .unwrap_or(contract.unsigned_create_token.hard_limit); - - let (_, burned_hashes) = collect_token_history(db, &ticker, &origin_hash_bytes).await; let mut total_burned = 0_u64; - for hash in burned_hashes { + for burn_hash in details[offset..].chunks_exact(TOKEN_HASH_BYTES) { let RpcResponse::Binary(burn_bytes) = - request_transaction_by_txid(db, hash.to_vec()).await; + request_transaction_by_txid(db, burn_hash.to_vec()).await; if burn_bytes.is_empty() || burn_bytes[0] != 10 { continue; } @@ -392,26 +410,28 @@ pub async fn token_catalog(db: &Db) -> RpcResponse { } } - let mut padded_ticker = ticker.as_bytes().to_vec(); - if padded_ticker.len() < 15 { - padded_ticker.resize(15, b' '); - } - if padded_ticker.len() != 15 - || creator_bytes.len() != Wallet::SHORT_ADDRESS_BYTES_LENGTH - || origin_hash_bytes.len() != 32 - { + let RpcResponse::Binary(origin_tx_bytes) = + request_transaction_by_txid(db, origin_hash.clone()).await; + let contract = if !origin_tx_bytes.is_empty() && origin_tx_bytes[0] == 3 { + CreateTokenTransaction::from_bytes(origin_tx_bytes[0], &origin_tx_bytes[1..]) + .await + .ok() + } else { + None + }; + let Some(contract) = contract else { continue; - } + }; rows.push(( - padded_ticker, - origin_hash_bytes, - creator_bytes, + ticker_bytes, + origin_hash, + creator, contract.unsigned_create_token.time, contract.unsigned_create_token.number, current_supply, total_burned, - count_wallets_holding(&normalized_ticker) as u32, + holder_count, hard_limit, )); }