2026-01-21 12:34:59 +01:00
|
|
|
// Copyright (C) 2024-2026 Whiterun LLC
|
2024-10-31 10:55:29 +00:00
|
|
|
//
|
|
|
|
|
// This software is licensed under the GNU Affero General Public License (AGPL), version 3.0 or later.
|
|
|
|
|
// A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html
|
|
|
|
|
|
|
|
|
|
use anyhow::{Context, Result};
|
|
|
|
|
use bitcoin_hashes::hex::FromHex;
|
|
|
|
|
use bitcoincash::TokenID;
|
|
|
|
|
use r2d2::Pool;
|
|
|
|
|
use r2d2_sqlite::SqliteConnectionManager;
|
|
|
|
|
use rayon::prelude::*;
|
|
|
|
|
use rusqlite::params;
|
|
|
|
|
use rusqlite::Connection;
|
|
|
|
|
|
|
|
|
|
type TokenBasicInfo = (String, Option<String>, Option<String>);
|
|
|
|
|
type TokenVolumeInfo = (String, Option<String>, Option<String>, u64);
|
|
|
|
|
|
2025-09-05 13:53:10 +00:00
|
|
|
use crate::db::bcmr::{get_token_bcmr, get_well_known_bcmr};
|
|
|
|
|
use crate::db::cauldron::tokenlist::db_utils::order_clause;
|
|
|
|
|
use crate::db::cauldron::tokenlist::db_utils::CachedSort;
|
|
|
|
|
use crate::db::cauldron::tokenlist::list_cached::TokenListItemCached;
|
|
|
|
|
|
2024-10-31 10:55:29 +00:00
|
|
|
fn search_bcmr(bcmr_conn: &Connection, search_query: &str) -> Result<Vec<TokenBasicInfo>> {
|
|
|
|
|
let is_full_hex_token_id = TokenID::from_hex(search_query).is_ok();
|
|
|
|
|
// Fetch the latest (highest) record that has BCMR data.
|
|
|
|
|
let sql = if is_full_hex_token_id {
|
Convert hash columns from TEXT to BLOB storage
Migrate all 32-byte hash fields from TEXT (64-char hex) to BLOB
(32 bytes) for ~50% storage savings on hash columns.
Changes:
- Add src/db/blob.rs with ToBlob/FromBlob traits for hash types
- Update all table schemas to use BLOB for hash columns
- Replace .to_hex() with .to_blob() for DB inserts
- Replace ::from_hex() with ::from_blob() for DB reads
- Use SQL hex() function where API responses need hex strings
- Bump DB_VERSION from 4 to 5
Affected tables: tx, pool, pool_history_entry, utxo_spending,
utxo_funding, user_action, auth_chain_entry, bcmr_data,
bcmr_failure, bcmr_well_known, crc20, crc20_candidates,
delphi_entry, cached_token_metrics
Fixes #1
2026-01-21 13:25:35 +01:00
|
|
|
"SELECT hex(token_id), name, symbol
|
2024-10-31 10:55:29 +00:00
|
|
|
FROM (
|
|
|
|
|
SELECT a.token_id, b.name, b.symbol, a.height,
|
|
|
|
|
ROW_NUMBER() OVER (PARTITION BY a.token_id ORDER BY a.height DESC) AS rn
|
|
|
|
|
FROM auth_chain_entry a
|
|
|
|
|
LEFT JOIN bcmr_data b ON a.utxo = b.utxo
|
|
|
|
|
) subquery
|
|
|
|
|
WHERE rn = 1
|
|
|
|
|
AND (token_id = ?1);"
|
|
|
|
|
} else {
|
Convert hash columns from TEXT to BLOB storage
Migrate all 32-byte hash fields from TEXT (64-char hex) to BLOB
(32 bytes) for ~50% storage savings on hash columns.
Changes:
- Add src/db/blob.rs with ToBlob/FromBlob traits for hash types
- Update all table schemas to use BLOB for hash columns
- Replace .to_hex() with .to_blob() for DB inserts
- Replace ::from_hex() with ::from_blob() for DB reads
- Use SQL hex() function where API responses need hex strings
- Bump DB_VERSION from 4 to 5
Affected tables: tx, pool, pool_history_entry, utxo_spending,
utxo_funding, user_action, auth_chain_entry, bcmr_data,
bcmr_failure, bcmr_well_known, crc20, crc20_candidates,
delphi_entry, cached_token_metrics
Fixes #1
2026-01-21 13:25:35 +01:00
|
|
|
"SELECT hex(token_id), name, symbol
|
2024-10-31 10:55:29 +00:00
|
|
|
FROM (
|
|
|
|
|
SELECT a.token_id, b.name, b.symbol, a.height,
|
|
|
|
|
ROW_NUMBER() OVER (PARTITION BY a.token_id ORDER BY a.height DESC) AS rn
|
|
|
|
|
FROM auth_chain_entry a
|
|
|
|
|
LEFT JOIN bcmr_data b ON a.utxo = b.utxo
|
|
|
|
|
WHERE a.bcmr_data IS NOT NULL
|
|
|
|
|
) subquery
|
|
|
|
|
WHERE rn = 1
|
|
|
|
|
AND (name LIKE ?1 OR symbol LIKE ?1);"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut statement = bcmr_conn.prepare(sql)?;
|
|
|
|
|
let mut bcmr_data: Vec<TokenBasicInfo> = Vec::new();
|
|
|
|
|
|
Convert hash columns from TEXT to BLOB storage
Migrate all 32-byte hash fields from TEXT (64-char hex) to BLOB
(32 bytes) for ~50% storage savings on hash columns.
Changes:
- Add src/db/blob.rs with ToBlob/FromBlob traits for hash types
- Update all table schemas to use BLOB for hash columns
- Replace .to_hex() with .to_blob() for DB inserts
- Replace ::from_hex() with ::from_blob() for DB reads
- Use SQL hex() function where API responses need hex strings
- Bump DB_VERSION from 4 to 5
Affected tables: tx, pool, pool_history_entry, utxo_spending,
utxo_funding, user_action, auth_chain_entry, bcmr_data,
bcmr_failure, bcmr_well_known, crc20, crc20_candidates,
delphi_entry, cached_token_metrics
Fixes #1
2026-01-21 13:25:35 +01:00
|
|
|
if is_full_hex_token_id {
|
|
|
|
|
let token_blob = hex::decode(search_query).context("invalid token hex")?;
|
|
|
|
|
let mut query = statement.query([&token_blob as &dyn rusqlite::ToSql])?;
|
|
|
|
|
while let Some(result) = query.next()? {
|
|
|
|
|
let token_id: String = result.get(0)?;
|
|
|
|
|
let name: Option<String> = result.get(1)?;
|
|
|
|
|
let ticker: Option<String> = result.get(2)?;
|
|
|
|
|
bcmr_data.push((token_id.to_lowercase(), name, ticker));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
let search_pattern = format!("%{search_query}%");
|
|
|
|
|
let mut query = statement.query([&search_pattern as &dyn rusqlite::ToSql])?;
|
|
|
|
|
while let Some(result) = query.next()? {
|
|
|
|
|
let token_id: String = result.get(0)?;
|
|
|
|
|
let name: Option<String> = result.get(1)?;
|
|
|
|
|
let ticker: Option<String> = result.get(2)?;
|
|
|
|
|
bcmr_data.push((token_id.to_lowercase(), name, ticker));
|
|
|
|
|
}
|
2024-10-31 10:55:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(bcmr_data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn search_crc20(crc20_conn: &Connection, search_query: &str) -> Result<Vec<TokenBasicInfo>> {
|
|
|
|
|
let is_full_hex_token_id = TokenID::from_hex(search_query).is_ok();
|
|
|
|
|
|
|
|
|
|
let sql = if is_full_hex_token_id {
|
|
|
|
|
// Search by token_id if it's a full hex token ID
|
|
|
|
|
"
|
Convert hash columns from TEXT to BLOB storage
Migrate all 32-byte hash fields from TEXT (64-char hex) to BLOB
(32 bytes) for ~50% storage savings on hash columns.
Changes:
- Add src/db/blob.rs with ToBlob/FromBlob traits for hash types
- Update all table schemas to use BLOB for hash columns
- Replace .to_hex() with .to_blob() for DB inserts
- Replace ::from_hex() with ::from_blob() for DB reads
- Use SQL hex() function where API responses need hex strings
- Bump DB_VERSION from 4 to 5
Affected tables: tx, pool, pool_history_entry, utxo_spending,
utxo_funding, user_action, auth_chain_entry, bcmr_data,
bcmr_failure, bcmr_well_known, crc20, crc20_candidates,
delphi_entry, cached_token_metrics
Fixes #1
2026-01-21 13:25:35 +01:00
|
|
|
SELECT hex(token_id), name, symbol
|
2024-10-31 10:55:29 +00:00
|
|
|
FROM crc20
|
|
|
|
|
WHERE token_id = ?1;
|
|
|
|
|
"
|
|
|
|
|
} else {
|
|
|
|
|
// Only search by name or symbol otherwise
|
|
|
|
|
"
|
Convert hash columns from TEXT to BLOB storage
Migrate all 32-byte hash fields from TEXT (64-char hex) to BLOB
(32 bytes) for ~50% storage savings on hash columns.
Changes:
- Add src/db/blob.rs with ToBlob/FromBlob traits for hash types
- Update all table schemas to use BLOB for hash columns
- Replace .to_hex() with .to_blob() for DB inserts
- Replace ::from_hex() with ::from_blob() for DB reads
- Use SQL hex() function where API responses need hex strings
- Bump DB_VERSION from 4 to 5
Affected tables: tx, pool, pool_history_entry, utxo_spending,
utxo_funding, user_action, auth_chain_entry, bcmr_data,
bcmr_failure, bcmr_well_known, crc20, crc20_candidates,
delphi_entry, cached_token_metrics
Fixes #1
2026-01-21 13:25:35 +01:00
|
|
|
SELECT hex(token_id), name, symbol
|
2024-10-31 10:55:29 +00:00
|
|
|
FROM crc20
|
|
|
|
|
WHERE name LIKE ?1 OR symbol LIKE ?1;
|
|
|
|
|
"
|
|
|
|
|
};
|
|
|
|
|
let mut statement = crc20_conn.prepare(sql)?;
|
|
|
|
|
let mut crc20_data: Vec<TokenBasicInfo> = Vec::new();
|
|
|
|
|
|
Convert hash columns from TEXT to BLOB storage
Migrate all 32-byte hash fields from TEXT (64-char hex) to BLOB
(32 bytes) for ~50% storage savings on hash columns.
Changes:
- Add src/db/blob.rs with ToBlob/FromBlob traits for hash types
- Update all table schemas to use BLOB for hash columns
- Replace .to_hex() with .to_blob() for DB inserts
- Replace ::from_hex() with ::from_blob() for DB reads
- Use SQL hex() function where API responses need hex strings
- Bump DB_VERSION from 4 to 5
Affected tables: tx, pool, pool_history_entry, utxo_spending,
utxo_funding, user_action, auth_chain_entry, bcmr_data,
bcmr_failure, bcmr_well_known, crc20, crc20_candidates,
delphi_entry, cached_token_metrics
Fixes #1
2026-01-21 13:25:35 +01:00
|
|
|
if is_full_hex_token_id {
|
|
|
|
|
let token_blob = hex::decode(search_query).context("invalid token hex")?;
|
|
|
|
|
let mut query = statement.query([&token_blob as &dyn rusqlite::ToSql])?;
|
|
|
|
|
while let Some(result) = query.next()? {
|
|
|
|
|
let token_id: String = result.get(0)?;
|
|
|
|
|
let name: Option<String> = result.get(1)?;
|
|
|
|
|
let ticker: Option<String> = result.get(2)?;
|
|
|
|
|
crc20_data.push((token_id.to_lowercase(), name, ticker));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
let search_pattern = format!("%{search_query}%");
|
|
|
|
|
let mut query = statement.query([&search_pattern as &dyn rusqlite::ToSql])?;
|
|
|
|
|
while let Some(result) = query.next()? {
|
|
|
|
|
let token_id: String = result.get(0)?;
|
|
|
|
|
let name: Option<String> = result.get(1)?;
|
|
|
|
|
let ticker: Option<String> = result.get(2)?;
|
|
|
|
|
crc20_data.push((token_id.to_lowercase(), name, ticker));
|
|
|
|
|
}
|
2024-10-31 10:55:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(crc20_data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn token_volume(
|
|
|
|
|
cauldron_pool: Pool<SqliteConnectionManager>,
|
|
|
|
|
tokens: Vec<TokenBasicInfo>,
|
|
|
|
|
) -> Result<Vec<TokenVolumeInfo>> {
|
|
|
|
|
let result: Vec<TokenVolumeInfo> = tokens
|
2025-08-18 22:30:10 +02:00
|
|
|
.into_par_iter()
|
|
|
|
|
.map(|(token_id, name, ticker)| {
|
|
|
|
|
let conn = cauldron_pool
|
|
|
|
|
.get()
|
|
|
|
|
.context("Failed to get connection from pool")?;
|
|
|
|
|
|
|
|
|
|
let mut statement = conn
|
|
|
|
|
.prepare(
|
|
|
|
|
"
|
2024-10-31 10:55:29 +00:00
|
|
|
WITH TradeData AS (
|
|
|
|
|
SELECT
|
2025-08-15 17:48:25 +02:00
|
|
|
p.token_id,
|
|
|
|
|
ABS(phe.sats_delta) as trade_volume
|
|
|
|
|
FROM pool_history_entry phe
|
|
|
|
|
JOIN pool p ON phe.pool = p.creation_utxo
|
|
|
|
|
JOIN tx ON phe.txid = tx.txid
|
2025-08-18 22:30:10 +02:00
|
|
|
WHERE tx.effective_timestamp >= (strftime('%s', 'now') - 2592000)
|
2025-08-15 17:48:25 +02:00
|
|
|
AND p.token_id = ?
|
2024-10-31 10:55:29 +00:00
|
|
|
)
|
|
|
|
|
SELECT
|
Convert hash columns from TEXT to BLOB storage
Migrate all 32-byte hash fields from TEXT (64-char hex) to BLOB
(32 bytes) for ~50% storage savings on hash columns.
Changes:
- Add src/db/blob.rs with ToBlob/FromBlob traits for hash types
- Update all table schemas to use BLOB for hash columns
- Replace .to_hex() with .to_blob() for DB inserts
- Replace ::from_hex() with ::from_blob() for DB reads
- Use SQL hex() function where API responses need hex strings
- Bump DB_VERSION from 4 to 5
Affected tables: tx, pool, pool_history_entry, utxo_spending,
utxo_funding, user_action, auth_chain_entry, bcmr_data,
bcmr_failure, bcmr_well_known, crc20, crc20_candidates,
delphi_entry, cached_token_metrics
Fixes #1
2026-01-21 13:25:35 +01:00
|
|
|
hex(token_id),
|
2024-10-31 10:55:29 +00:00
|
|
|
COALESCE(SUM(trade_volume), 0) as total_trade_volume
|
|
|
|
|
FROM TradeData;
|
|
|
|
|
",
|
2025-08-18 22:30:10 +02:00
|
|
|
)
|
|
|
|
|
.context("Failed to prepare statement")?;
|
Convert hash columns from TEXT to BLOB storage
Migrate all 32-byte hash fields from TEXT (64-char hex) to BLOB
(32 bytes) for ~50% storage savings on hash columns.
Changes:
- Add src/db/blob.rs with ToBlob/FromBlob traits for hash types
- Update all table schemas to use BLOB for hash columns
- Replace .to_hex() with .to_blob() for DB inserts
- Replace ::from_hex() with ::from_blob() for DB reads
- Use SQL hex() function where API responses need hex strings
- Bump DB_VERSION from 4 to 5
Affected tables: tx, pool, pool_history_entry, utxo_spending,
utxo_funding, user_action, auth_chain_entry, bcmr_data,
bcmr_failure, bcmr_well_known, crc20, crc20_candidates,
delphi_entry, cached_token_metrics
Fixes #1
2026-01-21 13:25:35 +01:00
|
|
|
let token_blob = hex::decode(&token_id).context("invalid token hex")?;
|
2025-08-18 22:30:10 +02:00
|
|
|
let trade_volume: u64 = statement
|
Convert hash columns from TEXT to BLOB storage
Migrate all 32-byte hash fields from TEXT (64-char hex) to BLOB
(32 bytes) for ~50% storage savings on hash columns.
Changes:
- Add src/db/blob.rs with ToBlob/FromBlob traits for hash types
- Update all table schemas to use BLOB for hash columns
- Replace .to_hex() with .to_blob() for DB inserts
- Replace ::from_hex() with ::from_blob() for DB reads
- Use SQL hex() function where API responses need hex strings
- Bump DB_VERSION from 4 to 5
Affected tables: tx, pool, pool_history_entry, utxo_spending,
utxo_funding, user_action, auth_chain_entry, bcmr_data,
bcmr_failure, bcmr_well_known, crc20, crc20_candidates,
delphi_entry, cached_token_metrics
Fixes #1
2026-01-21 13:25:35 +01:00
|
|
|
.query_row(params![&token_blob], |row| row.get(1))
|
2025-08-18 22:30:10 +02:00
|
|
|
.unwrap_or(0);
|
2024-10-31 10:55:29 +00:00
|
|
|
|
2025-08-18 22:30:10 +02:00
|
|
|
// Return result as Ok tuple for successful case
|
|
|
|
|
Ok((token_id, name, ticker, trade_volume))
|
|
|
|
|
})
|
|
|
|
|
.collect::<Result<Vec<_>>>()?; // Collect results into a Vec, propagating errors
|
2024-10-31 10:55:29 +00:00
|
|
|
|
|
|
|
|
Ok(result)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn search_tokens_by_volume(
|
|
|
|
|
cauldron_pool: &Pool<SqliteConnectionManager>,
|
|
|
|
|
bcmr_conn: &Connection,
|
|
|
|
|
crc20_conn: &Connection,
|
|
|
|
|
search_query: &str,
|
|
|
|
|
) -> Result<Vec<TokenVolumeInfo>> {
|
2025-09-16 09:10:54 +00:00
|
|
|
// Collect BCMR + CRC20 matches (by text or exact ID)
|
|
|
|
|
let mut combined_tokens = [
|
|
|
|
|
search_bcmr(bcmr_conn, search_query)?,
|
|
|
|
|
search_crc20(crc20_conn, search_query)?,
|
|
|
|
|
]
|
|
|
|
|
.concat();
|
|
|
|
|
|
2026-01-21 15:21:43 +01:00
|
|
|
// Sort by token_id for grouping (case-insensitive)
|
|
|
|
|
combined_tokens.sort_by(|a, b| a.0.to_lowercase().cmp(&b.0.to_lowercase()));
|
|
|
|
|
|
|
|
|
|
// Custom dedup: prefer entry with name/symbol
|
|
|
|
|
combined_tokens.dedup_by(|a, b| {
|
|
|
|
|
if a.0.eq_ignore_ascii_case(&b.0) {
|
|
|
|
|
// If b has no name but a does, copy a's data into b (b is kept)
|
|
|
|
|
if b.1.is_none() && a.1.is_some() {
|
|
|
|
|
b.1 = a.1.take();
|
|
|
|
|
b.2 = a.2.take();
|
|
|
|
|
}
|
|
|
|
|
true // They are duplicates, remove a
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-09-16 09:10:54 +00:00
|
|
|
|
|
|
|
|
// Compute last-30d volume
|
2024-10-31 10:55:29 +00:00
|
|
|
let mut result = token_volume(cauldron_pool.clone(), combined_tokens)?;
|
|
|
|
|
|
2025-09-16 09:10:54 +00:00
|
|
|
// Sort by volume desc
|
2024-10-31 10:55:29 +00:00
|
|
|
result.sort_by(|a, b| b.3.cmp(&a.3));
|
|
|
|
|
Ok(result)
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-05 13:53:10 +00:00
|
|
|
/// Search the cached list by token_id (exact), or display_name/symbol (LIKE),
|
|
|
|
|
/// then sort by *whatever* using CachedSort.
|
|
|
|
|
///
|
|
|
|
|
/// - `query`:
|
|
|
|
|
/// - empty/whitespace => returns everything
|
|
|
|
|
/// - full 32-byte hex => exact token_id match
|
|
|
|
|
/// - otherwise => display_name LIKE or display_symbol LIKE
|
|
|
|
|
/// - `filter_zero_tvl`: skip rows with tvl_sats=0 (usually you want `true`)
|
|
|
|
|
pub fn db_search_tokens_cached(
|
|
|
|
|
cauldron_conn: &Connection,
|
|
|
|
|
bcmr_conn: &Connection,
|
|
|
|
|
q: &str,
|
|
|
|
|
sort: CachedSort,
|
|
|
|
|
limit: usize,
|
|
|
|
|
offset: usize,
|
|
|
|
|
) -> Result<Vec<TokenListItemCached>> {
|
|
|
|
|
use rusqlite::types::ToSql;
|
|
|
|
|
|
|
|
|
|
let order_sql = order_clause(sort);
|
|
|
|
|
|
|
|
|
|
enum Filter {
|
2025-09-16 09:10:54 +00:00
|
|
|
None, // default list
|
|
|
|
|
Id(String), // exact 32-byte hex token_id
|
|
|
|
|
Pat(String), // name/symbol LIKE
|
2025-09-05 13:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut where_sql = String::new();
|
|
|
|
|
let filter = {
|
|
|
|
|
let q = q.trim();
|
|
|
|
|
if q.is_empty() {
|
|
|
|
|
Filter::None
|
|
|
|
|
} else if TokenID::from_hex(q).is_ok() {
|
2025-09-16 09:10:54 +00:00
|
|
|
// exact token_id lookup (allow zero TVL)
|
2025-09-05 13:53:10 +00:00
|
|
|
where_sql.push_str("WHERE token_id = :id");
|
|
|
|
|
Filter::Id(q.to_lowercase())
|
|
|
|
|
} else {
|
2025-09-16 09:10:54 +00:00
|
|
|
// name/symbol search (hide zero TVL)
|
2025-09-05 13:53:10 +00:00
|
|
|
where_sql.push_str(
|
|
|
|
|
"WHERE (display_name LIKE :pat COLLATE NOCASE OR display_symbol LIKE :pat COLLATE NOCASE)"
|
|
|
|
|
);
|
|
|
|
|
Filter::Pat(format!("%{}%", q))
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-16 09:10:54 +00:00
|
|
|
// TVL gate:
|
|
|
|
|
// - default list (None): hide zero TVL
|
|
|
|
|
// - name/symbol LIKE (Pat): hide zero TVL
|
|
|
|
|
// - exact id (Id): DO NOT gate (allow zero TVL)
|
|
|
|
|
if matches!(filter, Filter::None | Filter::Pat(_)) {
|
|
|
|
|
if where_sql.is_empty() {
|
|
|
|
|
where_sql.push_str("WHERE tvl_sats > 0");
|
|
|
|
|
} else {
|
|
|
|
|
where_sql.push_str(" AND tvl_sats > 0");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-05 13:53:10 +00:00
|
|
|
let sql = format!(
|
|
|
|
|
"SELECT
|
Convert hash columns from TEXT to BLOB storage
Migrate all 32-byte hash fields from TEXT (64-char hex) to BLOB
(32 bytes) for ~50% storage savings on hash columns.
Changes:
- Add src/db/blob.rs with ToBlob/FromBlob traits for hash types
- Update all table schemas to use BLOB for hash columns
- Replace .to_hex() with .to_blob() for DB inserts
- Replace ::from_hex() with ::from_blob() for DB reads
- Use SQL hex() function where API responses need hex strings
- Bump DB_VERSION from 4 to 5
Affected tables: tx, pool, pool_history_entry, utxo_spending,
utxo_funding, user_action, auth_chain_entry, bcmr_data,
bcmr_failure, bcmr_well_known, crc20, crc20_candidates,
delphi_entry, cached_token_metrics
Fixes #1
2026-01-21 13:25:35 +01:00
|
|
|
hex(token_id) as token_id,
|
2025-09-05 13:53:10 +00:00
|
|
|
trade_volume,
|
|
|
|
|
tvl_sats,
|
|
|
|
|
tvl_tokens,
|
|
|
|
|
score,
|
|
|
|
|
price_now,
|
|
|
|
|
price_24h,
|
|
|
|
|
price_7d,
|
|
|
|
|
change_24h_bp,
|
|
|
|
|
change_7d_bp,
|
|
|
|
|
display_name,
|
|
|
|
|
display_symbol,
|
|
|
|
|
price_now_usd,
|
|
|
|
|
price_24h_usd,
|
|
|
|
|
price_7d_usd,
|
|
|
|
|
change_24h_usd_bp,
|
|
|
|
|
change_7d_usd_bp,
|
|
|
|
|
apy_30d_bp,
|
|
|
|
|
score_rank
|
|
|
|
|
FROM cached_token_metrics
|
|
|
|
|
{where_sql}
|
|
|
|
|
{order_sql}
|
|
|
|
|
LIMIT :limit OFFSET :offset"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let mut stmt = cauldron_conn.prepare(&sql)?;
|
|
|
|
|
|
|
|
|
|
let limit_i64 = limit as i64;
|
|
|
|
|
let offset_i64 = offset as i64;
|
Convert hash columns from TEXT to BLOB storage
Migrate all 32-byte hash fields from TEXT (64-char hex) to BLOB
(32 bytes) for ~50% storage savings on hash columns.
Changes:
- Add src/db/blob.rs with ToBlob/FromBlob traits for hash types
- Update all table schemas to use BLOB for hash columns
- Replace .to_hex() with .to_blob() for DB inserts
- Replace ::from_hex() with ::from_blob() for DB reads
- Use SQL hex() function where API responses need hex strings
- Bump DB_VERSION from 4 to 5
Affected tables: tx, pool, pool_history_entry, utxo_spending,
utxo_funding, user_action, auth_chain_entry, bcmr_data,
bcmr_failure, bcmr_well_known, crc20, crc20_candidates,
delphi_entry, cached_token_metrics
Fixes #1
2026-01-21 13:25:35 +01:00
|
|
|
let id_blob: Option<Vec<u8>> = match &filter {
|
|
|
|
|
Filter::Id(s) => Some(hex::decode(s).context("invalid token hex")?),
|
|
|
|
|
_ => None,
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-05 13:53:10 +00:00
|
|
|
let mut params_vec: Vec<(&str, &dyn ToSql)> = Vec::with_capacity(3);
|
|
|
|
|
|
|
|
|
|
match &filter {
|
Convert hash columns from TEXT to BLOB storage
Migrate all 32-byte hash fields from TEXT (64-char hex) to BLOB
(32 bytes) for ~50% storage savings on hash columns.
Changes:
- Add src/db/blob.rs with ToBlob/FromBlob traits for hash types
- Update all table schemas to use BLOB for hash columns
- Replace .to_hex() with .to_blob() for DB inserts
- Replace ::from_hex() with ::from_blob() for DB reads
- Use SQL hex() function where API responses need hex strings
- Bump DB_VERSION from 4 to 5
Affected tables: tx, pool, pool_history_entry, utxo_spending,
utxo_funding, user_action, auth_chain_entry, bcmr_data,
bcmr_failure, bcmr_well_known, crc20, crc20_candidates,
delphi_entry, cached_token_metrics
Fixes #1
2026-01-21 13:25:35 +01:00
|
|
|
Filter::Id(_) => {
|
|
|
|
|
if let Some(ref blob) = id_blob {
|
|
|
|
|
params_vec.push((":id", blob as &dyn ToSql));
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-05 13:53:10 +00:00
|
|
|
Filter::Pat(s) => params_vec.push((":pat", s as &dyn ToSql)),
|
|
|
|
|
Filter::None => {}
|
|
|
|
|
}
|
|
|
|
|
params_vec.push((":limit", &limit_i64));
|
|
|
|
|
params_vec.push((":offset", &offset_i64));
|
|
|
|
|
|
|
|
|
|
let mut rows = stmt.query(¶ms_vec[..])?;
|
|
|
|
|
let mut out = Vec::with_capacity(limit);
|
|
|
|
|
|
|
|
|
|
while let Some(row) = rows.next()? {
|
Convert hash columns from TEXT to BLOB storage
Migrate all 32-byte hash fields from TEXT (64-char hex) to BLOB
(32 bytes) for ~50% storage savings on hash columns.
Changes:
- Add src/db/blob.rs with ToBlob/FromBlob traits for hash types
- Update all table schemas to use BLOB for hash columns
- Replace .to_hex() with .to_blob() for DB inserts
- Replace ::from_hex() with ::from_blob() for DB reads
- Use SQL hex() function where API responses need hex strings
- Bump DB_VERSION from 4 to 5
Affected tables: tx, pool, pool_history_entry, utxo_spending,
utxo_funding, user_action, auth_chain_entry, bcmr_data,
bcmr_failure, bcmr_well_known, crc20, crc20_candidates,
delphi_entry, cached_token_metrics
Fixes #1
2026-01-21 13:25:35 +01:00
|
|
|
let token_id: String = row.get::<_, String>("token_id")?.to_lowercase();
|
2025-09-11 07:36:06 +00:00
|
|
|
let trade_volume: u64 = row.get("trade_volume")?;
|
|
|
|
|
let tvl_sats: u64 = row.get::<_, i64>("tvl_sats")? as u64;
|
|
|
|
|
let tvl_tokens: u64 = row.get::<_, i64>("tvl_tokens")? as u64;
|
|
|
|
|
let score: i64 = row.get("score")?;
|
|
|
|
|
let score_rank: i64 = row.get("score_rank")?;
|
|
|
|
|
|
|
|
|
|
let price_now: f64 = row.get("price_now")?;
|
|
|
|
|
let price_24h: Option<f64> = row.get("price_24h")?;
|
|
|
|
|
let price_7d: Option<f64> = row.get("price_7d")?;
|
|
|
|
|
let change_24h_bp: Option<i64> = row.get("change_24h_bp")?;
|
|
|
|
|
let change_7d_bp: Option<i64> = row.get("change_7d_bp")?;
|
|
|
|
|
|
|
|
|
|
let display_name: Option<String> = row.get("display_name")?;
|
|
|
|
|
let display_symbol: Option<String> = row.get("display_symbol")?;
|
|
|
|
|
|
|
|
|
|
let price_now_usd: f64 = row.get("price_now_usd")?;
|
|
|
|
|
let price_24h_usd: Option<f64> = row.get("price_24h_usd")?;
|
|
|
|
|
let price_7d_usd: Option<f64> = row.get("price_7d_usd")?;
|
|
|
|
|
let change_24h_usd_bp: Option<i64> = row.get("change_24h_usd_bp")?;
|
|
|
|
|
let change_7d_usd_bp: Option<i64> = row.get("change_7d_usd_bp")?;
|
|
|
|
|
|
|
|
|
|
let apy_30d_bp: Option<i64> = row.get("apy_30d_bp")?;
|
2025-09-05 13:53:10 +00:00
|
|
|
let bcmr = get_token_bcmr(bcmr_conn, &token_id)?;
|
|
|
|
|
let bcmr_well_known = get_well_known_bcmr(bcmr_conn, &token_id)?;
|
|
|
|
|
|
|
|
|
|
out.push(TokenListItemCached {
|
|
|
|
|
token_id,
|
|
|
|
|
trade_volume,
|
|
|
|
|
tvl_sats,
|
|
|
|
|
tvl_tokens,
|
|
|
|
|
bcmr,
|
|
|
|
|
bcmr_well_known,
|
|
|
|
|
trade_count: 0,
|
2025-09-11 07:36:06 +00:00
|
|
|
score,
|
2025-09-05 13:53:10 +00:00
|
|
|
score_rank,
|
|
|
|
|
price_now,
|
|
|
|
|
price_24h,
|
|
|
|
|
price_7d,
|
|
|
|
|
change_24h_bp,
|
|
|
|
|
change_7d_bp,
|
|
|
|
|
display_name,
|
|
|
|
|
display_symbol,
|
|
|
|
|
price_now_usd,
|
|
|
|
|
price_24h_usd,
|
|
|
|
|
price_7d_usd,
|
|
|
|
|
change_24h_usd_bp,
|
|
|
|
|
change_7d_usd_bp,
|
|
|
|
|
apy_30d_bp,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(out)
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-31 10:55:29 +00:00
|
|
|
#[cfg(test)]
|
2025-09-11 07:36:06 +00:00
|
|
|
mod test;
|