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::common::binary_conversions::binary_to_string;
use crate::encode;
use crate::rpc::responses::RpcResponse; use crate::rpc::responses::RpcResponse;
use crate::sled::Db; use crate::sled::Db;
@ -24,7 +25,14 @@ pub async fn get_tokens(db: &Db) -> RpcResponse {
.get(&key) .get(&key)
.ok() .ok()
.flatten() .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(); .unwrap_or_default();
if hash.len() != 64 { 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 // Prefer the dedicated token-origins tree, but fall back to scanning
// saved txids if the origin mapping is missing. // saved txids if the origin mapping is missing.
if let Ok(Some(bytes)) = origins_tree.get(matched_ticker.as_bytes()) { 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()?; 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() { if normalized_query.is_empty() {
return RpcResponse::Binary(b"error: Token name required".to_vec()); return RpcResponse::Binary(b"error: Token name required".to_vec());
} }
let tokens_tree = db.open_tree("tokens").unwrap(); let tokens_tree = db.open_tree("tokens").unwrap();
let origins_tree = db.open_tree("token_origins").unwrap(); let origins_tree = db.open_tree("token_origins").unwrap();
let limits_tree = db.open_tree("token_limits").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 { pub async fn token_catalog(db: &Db) -> RpcResponse {
let tokens_tree = match db.open_tree("tokens") { const TOKEN_LIST_ROW_BYTES: usize = 15 + 64;
Ok(tree) => tree, const TOKEN_NAME_BYTES: usize = 15;
Err(err) => { const TOKEN_HASH_BYTES: usize = 32;
return RpcResponse::Binary( const TOKEN_COUNT_BYTES: usize = 8;
format!("error: Failed to open tokens tree: {err}").into_bytes(), const TOKEN_SPREAD_BYTES: usize = 4;
) const ISSUE_COUNT_BYTES: usize = 4;
} const BURN_COUNT_BYTES: usize = 4;
};
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(),
)
}
};
let mut rows = Vec::new(); 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() { if token_list_bytes.len() % TOKEN_LIST_ROW_BYTES != 0 {
let (ticker_key, origin_value) = match entry { return RpcResponse::Binary(b"error: Token list response had an unexpected byte length".to_vec());
Ok(pair) => pair, }
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, 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); 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; 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) = 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 { if burn_bytes.is_empty() || burn_bytes[0] != 10 {
continue; continue;
} }
@ -392,26 +410,28 @@ pub async fn token_catalog(db: &Db) -> RpcResponse {
} }
} }
let mut padded_ticker = ticker.as_bytes().to_vec(); let RpcResponse::Binary(origin_tx_bytes) =
if padded_ticker.len() < 15 { request_transaction_by_txid(db, origin_hash.clone()).await;
padded_ticker.resize(15, b' '); let contract = if !origin_tx_bytes.is_empty() && origin_tx_bytes[0] == 3 {
} CreateTokenTransaction::from_bytes(origin_tx_bytes[0], &origin_tx_bytes[1..])
if padded_ticker.len() != 15 .await
|| creator_bytes.len() != Wallet::SHORT_ADDRESS_BYTES_LENGTH .ok()
|| origin_hash_bytes.len() != 32 } else {
{ None
};
let Some(contract) = contract else {
continue; continue;
} };
rows.push(( rows.push((
padded_ticker, ticker_bytes,
origin_hash_bytes, origin_hash,
creator_bytes, creator,
contract.unsigned_create_token.time, contract.unsigned_create_token.time,
contract.unsigned_create_token.number, contract.unsigned_create_token.number,
current_supply, current_supply,
total_burned, total_burned,
count_wallets_holding(&normalized_ticker) as u32, holder_count,
hard_limit, hard_limit,
)); ));
} }