2026-05-24 17:56:57 +00:00
|
|
|
use crate::blocks::token::CreateTokenTransaction;
|
|
|
|
|
use crate::common::binary_conversions::binary_to_string;
|
2026-05-26 06:24:57 +00:00
|
|
|
use crate::fs;
|
2026-05-24 17:56:57 +00:00
|
|
|
use crate::records::balance_sheet::pathing::{balance_asset_segments, balance_root_path};
|
|
|
|
|
use crate::records::balance_sheet::tokens_to_lower::strip_spaces_and_lowercase;
|
|
|
|
|
use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid;
|
|
|
|
|
use crate::rpc::responses::RpcResponse;
|
|
|
|
|
use crate::sled::{Db, Tree};
|
2026-05-26 06:24:57 +00:00
|
|
|
use crate::wallets::structures::Wallet;
|
2026-05-24 17:56:57 +00:00
|
|
|
use crate::PathBuf;
|
2026-05-26 06:24:57 +00:00
|
|
|
use crate::{decode, encode};
|
2026-05-24 17:56:57 +00:00
|
|
|
|
|
|
|
|
fn parse_token_supply(value: &[u8]) -> Option<u64> {
|
|
|
|
|
// Token supply may be stored either as raw bytes or as a decimal string.
|
|
|
|
|
if value.len() == 8 {
|
|
|
|
|
let mut buffer = [0u8; 8];
|
|
|
|
|
buffer.copy_from_slice(value);
|
|
|
|
|
return Some(u64::from_le_bytes(buffer));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let value_str = binary_to_string(value.to_vec());
|
|
|
|
|
value_str.trim().parse::<u64>().ok()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn count_wallets_holding(normalized_token: &str) -> u64 {
|
|
|
|
|
// Spread is computed by scanning address balance files for non-zero
|
|
|
|
|
// holdings of the requested token.
|
|
|
|
|
let mut count = 0_u64;
|
|
|
|
|
let root = balance_root_path();
|
|
|
|
|
|
|
|
|
|
let addresses = match fs::read_dir(&root) {
|
|
|
|
|
Ok(entries) => entries,
|
|
|
|
|
Err(_) => return 0,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for entry in addresses.flatten() {
|
|
|
|
|
let entry_path = entry.path();
|
|
|
|
|
if !entry_path.is_dir() {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Balance files are sharded under hashed asset path segments, so
|
|
|
|
|
// reconstruct the expected token balance path for each address.
|
|
|
|
|
let mut balance_path: PathBuf = entry_path.clone();
|
|
|
|
|
for segment in balance_asset_segments(normalized_token) {
|
|
|
|
|
balance_path.push(segment);
|
|
|
|
|
}
|
|
|
|
|
balance_path.push("wallet.bal");
|
|
|
|
|
|
|
|
|
|
let bytes = match fs::read(&balance_path) {
|
|
|
|
|
Ok(bytes) => bytes,
|
|
|
|
|
Err(_) => continue,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if bytes.len() < 8 {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut buffer = [0u8; 8];
|
|
|
|
|
buffer.copy_from_slice(&bytes[..8]);
|
|
|
|
|
if u64::from_le_bytes(buffer) > 0 {
|
|
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn find_origin_hash(
|
|
|
|
|
db: &Db,
|
|
|
|
|
origins_tree: &Tree,
|
|
|
|
|
matched_ticker: &str,
|
|
|
|
|
normalized_query: &str,
|
|
|
|
|
) -> Option<String> {
|
|
|
|
|
// 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 txid_tree = db.open_tree("txid").ok()?;
|
|
|
|
|
for entry in txid_tree.iter() {
|
|
|
|
|
let (txid_bytes, _) = match entry {
|
|
|
|
|
Ok(pair) => pair,
|
|
|
|
|
Err(_) => continue,
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-26 06:24:57 +00:00
|
|
|
let RpcResponse::Binary(tx_bytes) =
|
|
|
|
|
request_transaction_by_txid(db, txid_bytes.to_vec()).await;
|
2026-05-24 17:56:57 +00:00
|
|
|
|
|
|
|
|
// The fallback only cares about create-token transactions because
|
|
|
|
|
// those define the origin hash for a token.
|
|
|
|
|
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,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if strip_spaces_and_lowercase(&contract.unsigned_create_token.ticker) == normalized_query {
|
|
|
|
|
return Some(encode(&txid_bytes));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn find_matching_ticker(
|
|
|
|
|
tokens_tree: &Tree,
|
|
|
|
|
origins_tree: &Tree,
|
|
|
|
|
normalized_query: &str,
|
|
|
|
|
) -> Option<String> {
|
|
|
|
|
// Prefer live token rows, but fall back to the origin registry so
|
|
|
|
|
// fully burned tokens can still be looked up historically.
|
|
|
|
|
for entry in tokens_tree.iter() {
|
|
|
|
|
let (key, _) = match entry {
|
|
|
|
|
Ok(pair) => pair,
|
|
|
|
|
Err(_) => continue,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let stored_ticker = binary_to_string(key.to_vec());
|
|
|
|
|
if strip_spaces_and_lowercase(&stored_ticker) == normalized_query {
|
|
|
|
|
return Some(stored_ticker);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for entry in origins_tree.iter() {
|
|
|
|
|
let (key, _) = match entry {
|
|
|
|
|
Ok(pair) => pair,
|
|
|
|
|
Err(_) => continue,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let stored_ticker = binary_to_string(key.to_vec());
|
|
|
|
|
if strip_spaces_and_lowercase(&stored_ticker) == normalized_query {
|
|
|
|
|
return Some(stored_ticker);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn collect_token_history(
|
|
|
|
|
db: &Db,
|
|
|
|
|
ticker: &str,
|
|
|
|
|
origin_hash_bytes: &[u8],
|
|
|
|
|
) -> (Vec<[u8; 32]>, Vec<[u8; 32]>) {
|
|
|
|
|
// Token lookup expands saved token history into separate issue and
|
|
|
|
|
// burn txid lists so the current supply can be audited from history.
|
|
|
|
|
let history_tree = match db.open_tree("token_history") {
|
|
|
|
|
Ok(tree) => tree,
|
|
|
|
|
Err(_) => return (Vec::new(), Vec::new()),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut issued_hashes = Vec::new();
|
|
|
|
|
let mut burned_hashes = Vec::new();
|
|
|
|
|
let Some(history_bytes) = history_tree
|
|
|
|
|
.get(ticker.as_bytes())
|
|
|
|
|
.ok()
|
|
|
|
|
.flatten()
|
|
|
|
|
.map(|bytes| bytes.to_vec())
|
|
|
|
|
else {
|
|
|
|
|
return (issued_hashes, burned_hashes);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for chunk in history_bytes.chunks(32) {
|
|
|
|
|
if chunk.len() != 32 || chunk == origin_hash_bytes {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let RpcResponse::Binary(tx_bytes) = request_transaction_by_txid(db, chunk.to_vec()).await;
|
|
|
|
|
|
|
|
|
|
if tx_bytes.is_empty() {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut hash = [0u8; 32];
|
|
|
|
|
hash.copy_from_slice(chunk);
|
|
|
|
|
|
|
|
|
|
// Token history stores txids only; inspect each transaction type
|
|
|
|
|
// to divide the history into issue and burn lists.
|
|
|
|
|
match tx_bytes[0] {
|
|
|
|
|
10 => burned_hashes.push(hash),
|
|
|
|
|
11 => issued_hashes.push(hash),
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(issued_hashes, burned_hashes)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn lookup_token_details(db: &Db, token_name: String) -> RpcResponse {
|
|
|
|
|
// Build the token-details response from token supply, origin txid,
|
|
|
|
|
// creator wallet, holder spread, and related issuance and burn txids.
|
|
|
|
|
let normalized_query = strip_spaces_and_lowercase(&token_name);
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
let matched_ticker = match find_matching_ticker(&tokens_tree, &origins_tree, &normalized_query)
|
|
|
|
|
{
|
|
|
|
|
Some(ticker) => ticker,
|
|
|
|
|
None => return RpcResponse::Binary(b"error: Token not found".to_vec()),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Current supply comes from the live token registry, but fully
|
|
|
|
|
// burned tokens still resolve historically through the origin tree.
|
|
|
|
|
let token_count = tokens_tree
|
|
|
|
|
.get(matched_ticker.as_bytes())
|
|
|
|
|
.ok()
|
|
|
|
|
.flatten()
|
|
|
|
|
.and_then(|value| parse_token_supply(value.as_ref()));
|
|
|
|
|
|
|
|
|
|
let origin_hash =
|
|
|
|
|
match find_origin_hash(db, &origins_tree, &matched_ticker, &normalized_query).await {
|
|
|
|
|
Some(hash) => hash,
|
|
|
|
|
None => return RpcResponse::Binary(b"error: Token origin not found".to_vec()),
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-26 06:24:57 +00:00
|
|
|
let RpcResponse::Binary(tx_bytes) =
|
|
|
|
|
request_transaction_by_txid(db, decode(&origin_hash).unwrap_or_default()).await;
|
2026-05-24 17:56:57 +00:00
|
|
|
|
|
|
|
|
if tx_bytes.is_empty() {
|
|
|
|
|
return RpcResponse::Binary(b"error: Token contract transaction not found".to_vec());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let txtype = tx_bytes[0];
|
|
|
|
|
let body = &tx_bytes[1..];
|
|
|
|
|
let contract = match CreateTokenTransaction::from_bytes(txtype, body).await {
|
|
|
|
|
Ok(tx) => tx,
|
|
|
|
|
Err(_) => {
|
|
|
|
|
return RpcResponse::Binary(b"error: Failed to parse token contract".to_vec());
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let spread = count_wallets_holding(&normalized_query);
|
|
|
|
|
let token_count = token_count.unwrap_or(0);
|
|
|
|
|
let contract_hash_bytes = match decode(&origin_hash) {
|
|
|
|
|
Ok(bytes) if bytes.len() == 32 => bytes,
|
|
|
|
|
_ => return RpcResponse::Binary(b"error: Failed to decode token origin".to_vec()),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let creator_bytes =
|
|
|
|
|
match Wallet::short_address_to_bytes(&contract.unsigned_create_token.creator) {
|
|
|
|
|
Some(bytes) => bytes,
|
|
|
|
|
None => return RpcResponse::Binary(b"error: Failed to encode token creator".to_vec()),
|
|
|
|
|
};
|
|
|
|
|
let ticker_bytes = contract.unsigned_create_token.ticker.as_bytes().to_vec();
|
|
|
|
|
if ticker_bytes.len() != 15 || creator_bytes.len() != Wallet::SHORT_ADDRESS_BYTES_LENGTH {
|
|
|
|
|
return RpcResponse::Binary(b"error: Invalid token lookup response size".to_vec());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let hard_limit = limits_tree
|
|
|
|
|
.get(matched_ticker.as_bytes())
|
|
|
|
|
.ok()
|
|
|
|
|
.flatten()
|
|
|
|
|
.and_then(|bytes| bytes.first().copied())
|
|
|
|
|
.unwrap_or(contract.unsigned_create_token.hard_limit);
|
|
|
|
|
|
|
|
|
|
let (issued_hashes, burned_hashes) =
|
|
|
|
|
collect_token_history(db, &matched_ticker, &contract_hash_bytes).await;
|
|
|
|
|
|
|
|
|
|
// Response layout: ticker, origin hash, creator, live supply, holder
|
|
|
|
|
// spread, hard-limit flag, issue-count/list, burn-count/list.
|
|
|
|
|
let mut response = Vec::with_capacity(
|
|
|
|
|
15 + 32
|
|
|
|
|
+ Wallet::SHORT_ADDRESS_BYTES_LENGTH
|
|
|
|
|
+ 8
|
|
|
|
|
+ 4
|
|
|
|
|
+ 1
|
|
|
|
|
+ 4
|
|
|
|
|
+ (issued_hashes.len() * 32)
|
|
|
|
|
+ 4
|
|
|
|
|
+ (burned_hashes.len() * 32),
|
|
|
|
|
);
|
|
|
|
|
response.extend_from_slice(&ticker_bytes);
|
|
|
|
|
response.extend_from_slice(&contract_hash_bytes);
|
|
|
|
|
response.extend_from_slice(&creator_bytes);
|
|
|
|
|
response.extend_from_slice(&token_count.to_le_bytes());
|
|
|
|
|
response.extend_from_slice(&(spread as u32).to_le_bytes());
|
|
|
|
|
response.push(hard_limit);
|
|
|
|
|
response.extend_from_slice(&(issued_hashes.len() as u32).to_le_bytes());
|
|
|
|
|
for hash in issued_hashes {
|
|
|
|
|
response.extend_from_slice(&hash);
|
|
|
|
|
}
|
|
|
|
|
response.extend_from_slice(&(burned_hashes.len() as u32).to_le_bytes());
|
|
|
|
|
for hash in burned_hashes {
|
|
|
|
|
response.extend_from_slice(&hash);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RpcResponse::Binary(response)
|
|
|
|
|
}
|
2026-06-27 16:28:13 +00:00
|
|
|
|
|
|
|
|
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(),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut rows = Vec::new();
|
|
|
|
|
|
|
|
|
|
for entry in origins_tree.iter() {
|
|
|
|
|
let (ticker_key, origin_value) = match entry {
|
|
|
|
|
Ok(pair) => pair,
|
|
|
|
|
Err(_) => continue,
|
|
|
|
|
};
|
|
|
|
|
let ticker = binary_to_string(ticker_key.to_vec());
|
|
|
|
|
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 {
|
|
|
|
|
let RpcResponse::Binary(burn_bytes) =
|
|
|
|
|
request_transaction_by_txid(db, hash.to_vec()).await;
|
|
|
|
|
if burn_bytes.is_empty() || burn_bytes[0] != 10 {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
let Ok(burn) =
|
|
|
|
|
crate::blocks::burn::BurnTransaction::from_bytes(burn_bytes[0], &burn_bytes[1..])
|
|
|
|
|
.await
|
|
|
|
|
else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
if burn.unsigned_burn.nft_series == 0
|
|
|
|
|
&& strip_spaces_and_lowercase(&burn.unsigned_burn.coin) == normalized_ticker
|
|
|
|
|
{
|
|
|
|
|
total_burned = total_burned.saturating_add(burn.unsigned_burn.value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rows.push((
|
|
|
|
|
padded_ticker,
|
|
|
|
|
origin_hash_bytes,
|
|
|
|
|
creator_bytes,
|
|
|
|
|
contract.unsigned_create_token.time,
|
|
|
|
|
contract.unsigned_create_token.number,
|
|
|
|
|
current_supply,
|
|
|
|
|
total_burned,
|
|
|
|
|
count_wallets_holding(&normalized_ticker) as u32,
|
|
|
|
|
hard_limit,
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rows.sort_by(|left, right| {
|
|
|
|
|
binary_to_string(left.0.clone())
|
|
|
|
|
.to_ascii_lowercase()
|
|
|
|
|
.cmp(&binary_to_string(right.0.clone()).to_ascii_lowercase())
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let mut response = Vec::with_capacity(4 + rows.len() * 102);
|
|
|
|
|
response.extend_from_slice(&(rows.len() as u32).to_le_bytes());
|
|
|
|
|
for (
|
|
|
|
|
ticker,
|
|
|
|
|
origin_hash,
|
|
|
|
|
creator,
|
|
|
|
|
created_timestamp,
|
|
|
|
|
initial_supply,
|
|
|
|
|
current_supply,
|
|
|
|
|
total_burned,
|
|
|
|
|
holder_count,
|
|
|
|
|
hard_limit,
|
|
|
|
|
) in rows
|
|
|
|
|
{
|
|
|
|
|
response.extend_from_slice(&ticker);
|
|
|
|
|
response.extend_from_slice(&origin_hash);
|
|
|
|
|
response.extend_from_slice(&creator);
|
|
|
|
|
response.extend_from_slice(&created_timestamp.to_le_bytes());
|
|
|
|
|
response.extend_from_slice(&initial_supply.to_le_bytes());
|
|
|
|
|
response.extend_from_slice(¤t_supply.to_le_bytes());
|
|
|
|
|
response.extend_from_slice(&total_burned.to_le_bytes());
|
|
|
|
|
response.extend_from_slice(&holder_count.to_le_bytes());
|
|
|
|
|
response.push(hard_limit);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RpcResponse::Binary(response)
|
|
|
|
|
}
|