rpc changes for asset reporting

This commit is contained in:
viraladmin 2026-06-27 14:14:01 -06:00
parent 61bc832ff4
commit eef98c4a00
2 changed files with 115 additions and 87 deletions

View File

@ -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 {

View File

@ -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,
));
}