riftenlabs-indexer/src/db/search/mod.rs

808 lines
29 KiB
Rust
Raw Normal View History

2024-10-31 10:55:29 +00:00
// Copyright (C) 2024 Riften Labs AS
//
// 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);
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 {
"SELECT token_id, name, symbol
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 {
"SELECT token_id, name, symbol
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 search_pattern = if is_full_hex_token_id {
search_query.to_string()
} else {
format!("%{}%", search_query)
};
let mut bcmr_data: Vec<TokenBasicInfo> = Vec::new();
let mut query = statement.query([search_pattern.as_str()])?;
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, name, ticker));
}
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
"
SELECT token_id, name, symbol
FROM crc20
WHERE token_id = ?1;
"
} else {
// Only search by name or symbol otherwise
"
SELECT token_id, name, symbol
FROM crc20
WHERE name LIKE ?1 OR symbol LIKE ?1;
"
};
let mut statement = crc20_conn.prepare(sql)?;
let search_pattern = if is_full_hex_token_id {
search_query.to_string()
} else {
format!("%{}%", search_query)
};
let mut crc20_data: Vec<TokenBasicInfo> = Vec::new();
let mut query = statement.query([&search_pattern])?;
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, name, ticker));
}
Ok(crc20_data)
}
fn token_volume(
cauldron_pool: Pool<SqliteConnectionManager>,
tokens: Vec<TokenBasicInfo>,
) -> Result<Vec<TokenVolumeInfo>> {
let result: Vec<TokenVolumeInfo> = tokens
.into_par_iter()
.map(|(token_id, name, ticker)| {
2025-02-14 18:02:48 +01:00
let conn = cauldron_pool
2024-10-31 10:55:29 +00:00
.get()
.context("Failed to get connection from pool")?;
let mut statement = conn.prepare(
"
WITH TradeData AS (
SELECT
uf1.token_id,
ABS(uf1.sats - COALESCE(uf2.sats, 0)) as trade_volume
FROM utxo_funding uf1
INNER JOIN utxo_funding uf2 ON uf1.spent_utxo_hash = uf2.new_utxo_hash
JOIN tx ON uf1.txid = tx.txid
WHERE COALESCE(tx.first_seen_timestamp, tx.mtp_timestamp) >= (strftime('%s', 'now') - 2592000)
AND uf1.token_id = ?
)
SELECT
token_id,
COALESCE(SUM(trade_volume), 0) as total_trade_volume
FROM TradeData;
",
).context("Failed to prepare statement")?;
let trade_volume: u64 = statement
.query_row(params![&token_id], |row| row.get(1))
.unwrap_or(0);
// 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
Ok(result)
}
pub fn search_tokens_by_volume(
cauldron_pool: &Pool<SqliteConnectionManager>,
bcmr_conn: &Connection,
crc20_conn: &Connection,
search_query: &str,
) -> Result<Vec<TokenVolumeInfo>> {
let bcmr_data = search_bcmr(bcmr_conn, search_query)?;
let crc20_data = search_crc20(crc20_conn, search_query)?;
let combined_tokens = [bcmr_data, crc20_data].concat();
let mut result = token_volume(cauldron_pool.clone(), combined_tokens)?;
result.sort_by(|a, b| b.3.cmp(&a.3));
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bcmr::parsedbcmr::{FileMeta, ParsedBCMR, Token, Uris};
use crate::db::bcmr::{
insert_authheader, insert_bcmr_data, prepare_tables as bcmr_prepare_tables,
};
use crate::db::cauldron::pool::{dummy_init_seq, insert_new_pool, insert_pool_history_entry};
use crate::db::cauldron::prepare_tables as cauldron_prepare_tables;
use crate::db::cauldron::tx::{insert_block_tx, insert_mempool_tx};
use crate::db::cauldron::utxo_funding::insert_utxo_funding;
use crate::db::crc20::prepare_tables as crc20_prepare_tables;
use crate::utiltest::mock_db_pool;
use bitcoin_hashes::hex::{FromHex, ToHex};
use bitcoin_hashes::Hash;
use bitcoincash::{BlockHash, PubkeyHash, TokenID, Txid};
use riftenlabs_defi::cauldron::ParsedContract;
use riftenlabs_defi::chainutil::OutPointHash;
use rusqlite::{params, Connection};
use std::time::{SystemTime, UNIX_EPOCH};
fn setup_mock_db(conn: &Connection) {
cauldron_prepare_tables(conn);
bcmr_prepare_tables(conn);
crc20_prepare_tables(conn);
}
#[test]
// Ensure that we are NOT looking for token_id when using incomplete hex.
fn test_search_token_by_inexact_hex_id() {
let mock_db = mock_db_pool(setup_mock_db);
let write_conn = mock_db.cauldron_w.get().unwrap();
match insert_mock_data(&write_conn) {
Ok(_) => println!("Mock data inserted successfully."),
Err(e) => println!("Failed to insert mock data: {:?}", e),
}
let token_id_hex = "dadadadadadadadadadadadadadadadada";
let result = search_tokens_by_volume(
&mock_db.cauldron_r,
&mock_db.bcmr_r.get().unwrap(),
&mock_db.crc20_r.get().unwrap(),
token_id_hex,
)
.expect("Failed to search tokens by volume");
assert_eq!(result.len(), 0);
}
#[test]
fn test_search_token_by_exact_hex_id() {
let mock_db = mock_db_pool(setup_mock_db);
let write_conn = mock_db.cauldron_w.get().unwrap();
match insert_mock_data(&write_conn) {
Ok(_) => println!("Mock data inserted successfully."),
Err(e) => println!("Failed to insert mock data: {:?}", e),
}
let token_id_hex = "dadadadadadadadadadadadadadadadadadadadadadadadadadadadadadadada";
let result = search_tokens_by_volume(
&mock_db.cauldron_r,
&mock_db.bcmr_r.get().unwrap(),
&mock_db.crc20_r.get().unwrap(),
token_id_hex,
)
.expect("Failed to search tokens by volume");
assert_eq!(result.len(), 1);
assert_eq!(result[0].1, Some("TokenOne".to_string())); // Ensure it matches "TokenOne"
}
#[test]
fn test_search_tokens_by_volume_with_bcmr_and_crc20() {
let mock_db = mock_db_pool(setup_mock_db);
let write_conn = mock_db.cauldron_w.get().unwrap();
match insert_mock_data(&write_conn) {
Ok(_) => println!("Mock data inserted successfully."),
Err(e) => println!("Failed to insert mock data: {:?}", e),
}
let result: Vec<(String, Option<String>, Option<String>, u64)> = search_tokens_by_volume(
&mock_db.cauldron_r,
&mock_db.bcmr_r.get().unwrap(),
&mock_db.crc20_r.get().unwrap(),
"",
)
.expect("Failed to search tokens by volume");
println!("Test Results: {:?}", result);
assert_eq!(result.len(), 4); // We expect 4 tokens in total (3 BCMR + 1 CRC20)
// Assert ordering by volume.
assert_eq!(result[0].1, Some("TokenTwo".to_string()));
assert_eq!(result[1].1, Some("TokenFour".to_string()));
assert_eq!(result[2].1, Some("TokenOne".to_string()));
assert_eq!(result[3].1, Some("TokenThree".to_string()));
}
#[test]
fn test_search_tokens_by_volume_no_results() {
let mock_db = mock_db_pool(setup_mock_db);
let write_conn = mock_db.cauldron_w.get().unwrap();
match insert_mock_data(&write_conn) {
Ok(_) => println!("Mock data inserted successfully."),
Err(e) => println!("Failed to insert mock data: {:?}", e),
}
let result = search_tokens_by_volume(
&mock_db.cauldron_r,
&mock_db.bcmr_r.get().unwrap(),
&mock_db.crc20_r.get().unwrap(),
"doesNotExist",
)
.expect("Failed to search tokens by volume");
assert_eq!(result.len(), 0);
}
fn dummy_cauldron(
txid: &Txid,
utxo: &OutPointHash,
token: &TokenID,
sats: u64,
tokens: i64,
pkh: &PubkeyHash,
) -> ParsedContract {
ParsedContract {
pkh: pkh.clone(),
is_withdrawn: false,
spent_utxo_hash: OutPointHash::all_zeros(),
new_utxo_hash: Some(utxo.clone()),
new_utxo_txid: Some(txid.clone()),
new_utxo_n: Some(0),
token_id: Some(token.clone()),
sats: Some(sats),
token_amount: Some(tokens),
}
}
#[test]
fn test_search_token_by_name_symbol_and_id() {
let mock_db = mock_db_pool(setup_mock_db);
let write_conn = mock_db.cauldron_w.get().unwrap();
match insert_mock_data(&write_conn) {
Ok(_) => println!("Mock data inserted successfully."),
Err(e) => println!("Failed to insert mock data: {:?}", e),
}
let token_tests = vec![
(
"TokenOne",
"TONE",
"dadadadadadadadadadadadadadadadadadadadadadadadadadadadadadadada",
"TokenOne",
),
(
"TokenTwo",
"TWO",
"dbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdbdb",
"TokenTwo",
),
(
"TokenThree",
"TN3",
"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
"TokenThree",
),
(
"TokenFour",
"TFOUR",
"dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc",
"TokenFour",
),
];
for (name, symbol, token_id, expected_name) in token_tests {
let result = search_tokens_by_volume(
&mock_db.cauldron_r,
&mock_db.bcmr_r.get().unwrap(),
&mock_db.crc20_r.get().unwrap(),
name,
)
.expect("Failed to search tokens by volume");
assert_eq!(result.len(), 1);
assert_eq!(result[0].1, Some(expected_name.to_string()));
let result = search_tokens_by_volume(
&mock_db.cauldron_r,
&mock_db.bcmr_r.get().unwrap(),
&mock_db.crc20_r.get().unwrap(),
symbol,
)
.expect("Failed to search tokens by volume");
assert_eq!(result.len(), 1);
assert_eq!(result[0].1, Some(expected_name.to_string()));
let result = search_tokens_by_volume(
&mock_db.cauldron_r,
&mock_db.bcmr_r.get().unwrap(),
&mock_db.crc20_r.get().unwrap(),
token_id,
)
.expect("Failed to search tokens by volume");
assert_eq!(result.len(), 1);
assert_eq!(result[0].1, Some(expected_name.to_string()));
}
}
fn insert_mock_data(conn: &Connection) -> Result<(), Box<dyn std::error::Error>> {
dummy_init_seq();
let current_timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64;
let thirty_days_ago = current_timestamp - 30 * 24 * 60 * 60;
let owner_pkh = PubkeyHash::from_inner([0xca; 20]);
let block_zero = BlockHash::all_zeros();
// ================== TOKEN 1 (BCMR Token with Volume) ==================
let utxo1 = OutPointHash::from_hex(
"a3f1d42e2a5c9f2b5f1b9d7f7c2b19e7a3b1d2c3f4a5e6f2d1c3e4f5a1b2c3d4",
)?;
// Step 1: Prepare and insert BCMR data for token1 with non-null fields
let token1 = Token {
category: "asset_category".to_string(),
symbol: "TONE".to_string(),
decimals: 8,
};
let uris1 = Uris {
icon: Some("https://example.com/icon.png".to_string()),
web: Some("https://example.com".to_string()),
};
let filemeta1 = FileMeta {
expected_hash: Some("expected_hash1".to_string()),
actual_hash: Some("actual_hash1".to_string()),
source: "source_url".to_string(),
};
let parsed_bcmr1 = ParsedBCMR {
name: "TokenOne".to_string(),
description: "Test Token 1".to_string(),
token: token1,
uris: uris1,
filemeta: filemeta1,
};
if let Err(e) = insert_bcmr_data(conn, &utxo1, &parsed_bcmr1) {
println!("Failed to insert BCMR data for token1: {:?}", e);
}
// Step 2: Insert Pool for token1
let txid1 = Txid::from_inner([0xf0; 32]);
let token_id1: TokenID = TokenID::from_inner([0xda; 32]);
let cauldron1 = dummy_cauldron(&txid1, &utxo1, &token_id1, 1000, 500, &owner_pkh);
if let Err(e) = insert_new_pool(conn, &cauldron1) {
println!("Failed to insert pool for token1: {:?}", e);
}
// Step 3: Insert auth_chain_entry for token1
if let Err(e) = insert_authheader(
conn,
&utxo1,
&BlockHash::all_zeros(),
&txid1,
&token_id1,
10,
Some(Vec::from("bcmr_data1".as_bytes())),
) {
println!("Failed to insert auth_chain_entry for token1: {:?}", e);
}
// Step 4: Insert the initial UTXO funding for token1
if let Err(e) = insert_utxo_funding(conn, &vec![cauldron1.clone()], &txid1, true) {
println!("Failed to insert initial UTXO funding for token1: {:?}", e);
}
// Step 5: Insert the transaction for utxo1
if let Err(e) = insert_block_tx(conn, &txid1, &block_zero, thirty_days_ago) {
println!("Failed to insert block transaction for utxo1: {:?}", e);
}
if let Err(e) = insert_mempool_tx(conn, &txid1, thirty_days_ago as u64) {
println!("Failed to insert mempool transaction for utxo1: {:?}", e);
}
// Step 6: Simulate Volume - Insert second funding for token1
let txid2 = Txid::from_inner([0xf1; 32]);
let utxo2 = OutPointHash::from_inner([0xe1; 32]);
let cauldron2 = ParsedContract {
pkh: owner_pkh.clone(),
is_withdrawn: false,
spent_utxo_hash: utxo1.clone(),
new_utxo_hash: Some(utxo2.clone()),
new_utxo_txid: Some(txid2.clone()),
new_utxo_n: Some(0),
token_id: Some(token_id1.clone()),
sats: Some(2000),
token_amount: Some(1000),
};
if let Err(e) = insert_utxo_funding(conn, &vec![cauldron2.clone()], &txid2, true) {
println!("Failed to insert second UTXO funding for token1: {:?}", e);
}
// Step 7: Insert the transaction for utxo2 (spending utxo1)
if let Err(e) = insert_block_tx(conn, &txid2, &block_zero, current_timestamp) {
println!("Failed to insert block transaction for utxo2: {:?}", e);
}
if let Err(e) = insert_mempool_tx(conn, &txid2, current_timestamp as u64) {
println!("Failed to insert mempool transaction for utxo2: {:?}", e);
}
// Step 8: Insert pool history entries for initial funding and spending
if let Err(e) = insert_pool_history_entry(
conn,
&utxo1,
&cauldron1.clone(),
Some(thirty_days_ago as u64),
Some(thirty_days_ago as u64),
) {
println!(
"Failed to insert pool history entry for initial funding of token1: {:?}",
e
);
}
if let Err(e) = insert_pool_history_entry(
conn,
&utxo1,
&cauldron2,
Some(current_timestamp as u64),
Some(current_timestamp as u64),
) {
println!(
"Failed to insert pool history entry for spending of token1: {:?}",
e
);
}
// ================== TOKEN 2 (BCMR Token) ==================
let utxo2 = OutPointHash::from_hex(
"c3d2e1f4b6a7c8d1e2f5d7c3b1a9e4f2b1d3f4e6c2b9f3a8d1e4f5b6c3d2e7a4",
)?;
// Step 1: Prepare and insert BCMR data for token2 with unique values
let token2 = Token {
category: "asset_category_2".to_string(),
symbol: "TWO".to_string(),
decimals: 8,
};
let uris2 = Uris {
icon: Some("https://example.com/icon2.png".to_string()),
web: Some("https://example.com/two".to_string()),
};
let filemeta2 = FileMeta {
expected_hash: Some("expected_hash2".to_string()),
actual_hash: Some("actual_hash2".to_string()),
source: "source_url_2".to_string(),
};
let parsed_bcmr2 = ParsedBCMR {
name: "TokenTwo".to_string(),
description: "Test Token 2".to_string(),
token: token2,
uris: uris2,
filemeta: filemeta2,
};
if let Err(e) = insert_bcmr_data(conn, &utxo2, &parsed_bcmr2) {
println!("Failed to insert BCMR data for token2: {:?}", e);
}
// Step 2: Insert Pool for token2
let token_id2 = TokenID::from_inner([0xdb; 32]);
let cauldron2 = dummy_cauldron(
&Txid::from_inner([0xf2; 32]),
&utxo2,
&token_id2,
1500,
700,
&owner_pkh,
);
insert_new_pool(conn, &cauldron2)?;
// Step 9: Insert auth_chain_entry for token2
if let Err(e) = insert_authheader(
conn,
&utxo2,
&BlockHash::all_zeros(),
&txid2,
&token_id2,
15,
Some(Vec::from("bcmr_data2".as_bytes())),
) {
println!("Failed to insert auth_chain_entry for TOKEN 3: {:?}", e);
};
// Step 3: Insert the initial UTXO funding for token2
insert_utxo_funding(
conn,
&vec![cauldron2.clone()],
&Txid::from_inner([0xf2; 32]),
true,
)?;
// Step 4: Insert the transaction for utxo2
let txid2 = Txid::from_inner([0xf2; 32]);
insert_block_tx(conn, &txid2, &block_zero, thirty_days_ago)?;
insert_mempool_tx(conn, &txid2, thirty_days_ago as u64)?;
// Step 5: Define cauldron3 for token2 with spent_utxo_hash referring to utxo2, then insert it
let txid3 = Txid::from_inner([0xf3; 32]);
let utxo3 = OutPointHash::from_inner([0xe2; 32]);
let cauldron3 = ParsedContract {
pkh: owner_pkh.clone(),
is_withdrawn: false,
spent_utxo_hash: utxo2.clone(),
new_utxo_hash: Some(utxo3.clone()),
new_utxo_txid: Some(txid3.clone()),
new_utxo_n: Some(0),
token_id: Some(token_id2.clone()),
sats: Some(3000),
token_amount: Some(1500),
};
// Insert the second funding with `spent_utxo_hash` correctly set
insert_utxo_funding(conn, &vec![cauldron3.clone()], &txid3, true)?;
// Step 6: Insert the transaction for utxo3 (the transaction that spent utxo2)
insert_block_tx(conn, &txid3, &block_zero, current_timestamp)?;
insert_mempool_tx(conn, &txid3, current_timestamp as u64)?;
// Step 7: Insert pool history entry for the initial UTXO funding (utxo2)
insert_pool_history_entry(
conn,
&utxo2,
&cauldron2.clone(),
Some(thirty_days_ago as u64),
Some(thirty_days_ago as u64),
)?;
// Step 8: Insert pool history entry for the spending of utxo2 (creation of utxo3)
insert_pool_history_entry(
conn,
&utxo2,
&cauldron3,
Some(current_timestamp as u64),
Some(current_timestamp as u64),
)?;
// ================== TOKEN 3 (BCMR Token with No Volume) ==================
let utxo3 = OutPointHash::from_hex(
"d5e1f2a3b4c3d2f5b6a8e7d3c2f4b9a6d2e3f1c4b5a9e3d1b2c5f7a3d4b8e2c3",
)?;
// Prepare and insert BCMR data for TOKEN 3 with unique values
let token3 = Token {
category: "asset_category_3".to_string(),
symbol: "TN3".to_string(),
decimals: 8,
};
let uris3 = Uris {
icon: Some("https://example.com/icon3.png".to_string()),
web: Some("https://example.com/three".to_string()),
};
let filemeta3 = FileMeta {
expected_hash: Some("expected_hash3".to_string()),
actual_hash: Some("actual_hash3".to_string()),
source: "source_url_3".to_string(),
};
let parsed_bcmr3 = ParsedBCMR {
name: "TokenThree".to_string(),
description: "Test Token 3 without Volume".to_string(),
token: token3,
uris: uris3,
filemeta: filemeta3,
};
if let Err(e) = insert_bcmr_data(conn, &utxo3, &parsed_bcmr3) {
println!("Failed to insert BCMR data for TOKEN 3: {:?}", e);
}
// Insert Pool for TOKEN 3
let txid3_initial = Txid::from_inner([0xf6; 32]);
let token_id3 = TokenID::from_inner([0xdd; 32]);
let cauldron3_initial =
dummy_cauldron(&txid3_initial, &utxo3, &token_id3, 1000, 500, &owner_pkh);
if let Err(e) = insert_new_pool(conn, &cauldron3_initial) {
println!("Failed to insert pool for TOKEN 3: {:?}", e);
}
// Insert auth_chain_entry for TOKEN 3
if let Err(e) = insert_authheader(
conn,
&utxo3,
&BlockHash::all_zeros(),
&txid3,
&token_id3,
20,
Some(Vec::from("bcmr_data3".as_bytes())),
) {
println!("Failed to insert auth_chain_entry for TOKEN 3: {:?}", e);
}
// Insert the initial UTXO funding for TOKEN 3 (No additional funding to keep volume at 0)
if let Err(e) =
insert_utxo_funding(conn, &vec![cauldron3_initial.clone()], &txid3_initial, true)
{
println!("Failed to insert initial UTXO funding for TOKEN 3: {:?}", e);
}
// Insert transaction for the initial funding without spending
if let Err(e) = insert_block_tx(conn, &txid3_initial, &block_zero, thirty_days_ago) {
println!(
"Failed to insert block transaction for TOKEN 3 initial funding: {:?}",
e
);
}
if let Err(e) = insert_mempool_tx(conn, &txid3_initial, thirty_days_ago as u64) {
println!(
"Failed to insert mempool transaction for TOKEN 3 initial funding: {:?}",
e
);
}
// Insert pool history entry for the initial funding of TOKEN 3 (no spending history)
if let Err(e) = insert_pool_history_entry(
conn,
&utxo3,
&cauldron3_initial,
Some(thirty_days_ago as u64),
Some(thirty_days_ago as u64),
) {
println!(
"Failed to insert pool history entry for TOKEN 3 initial funding: {:?}",
e
);
}
// ================== TOKEN 4 (CRC20 Token with Volume) ==================
let utxo4_initial = OutPointHash::from_hex(
"f1d4e2a3b5c4d2f7b6a8e7d3c2f4b9a6d3e1f1c4b7a8e2d3b6c7f9a5d4b1e6c3",
)?;
let token_id4 = TokenID::from_inner([0xdc; 32]); // Unique ID for TOKEN 4
// Insert CRC20 data for TOKEN 4
conn.execute(
"INSERT INTO crc20 (token_id, name, symbol, decimals) VALUES (?, ?, ?, ?)",
params![&token_id4.to_hex(), "TokenFour", "TFOUR", 18],
)?;
// Step 1: Insert initial funding for TOKEN 4
let txid4_initial = Txid::from_inner([0xf4; 32]);
let cauldron4_initial = dummy_cauldron(
&txid4_initial,
&utxo4_initial,
&token_id4,
3600,
1200,
&owner_pkh,
);
if let Err(e) =
insert_utxo_funding(conn, &vec![cauldron4_initial.clone()], &txid4_initial, true)
{
println!("Failed to insert initial UTXO funding for TOKEN 4: {:?}", e);
}
// Insert transaction for the initial funding
if let Err(e) = insert_block_tx(conn, &txid4_initial, &block_zero, thirty_days_ago) {
println!(
"Failed to insert block transaction for TOKEN 4 initial funding: {:?}",
e
);
}
if let Err(e) = insert_mempool_tx(conn, &txid4_initial, thirty_days_ago as u64) {
println!(
"Failed to insert mempool transaction for TOKEN 4 initial funding: {:?}",
e
);
}
// Step 2: Insert second funding entry to simulate volume
let txid4_spend = Txid::from_inner([0xf5; 32]);
let utxo4_spent = OutPointHash::from_inner([0xe4; 32]);
let cauldron4_spent = ParsedContract {
pkh: owner_pkh.clone(),
is_withdrawn: false,
spent_utxo_hash: utxo4_initial.clone(),
new_utxo_hash: Some(utxo4_spent.clone()),
new_utxo_txid: Some(txid4_spend.clone()),
new_utxo_n: Some(0),
token_id: Some(token_id4.clone()),
sats: Some(5000),
token_amount: Some(1500),
};
if let Err(e) =
insert_utxo_funding(conn, &vec![cauldron4_spent.clone()], &txid4_spend, true)
{
println!("Failed to insert spent UTXO funding for TOKEN 4: {:?}", e);
}
// Insert transaction for the spending UTXO
if let Err(e) = insert_block_tx(conn, &txid4_spend, &block_zero, current_timestamp) {
println!(
"Failed to insert block transaction for TOKEN 4 spending: {:?}",
e
);
}
if let Err(e) = insert_mempool_tx(conn, &txid4_spend, current_timestamp as u64) {
println!(
"Failed to insert mempool transaction for TOKEN 4 spending: {:?}",
e
);
}
// Step 3: Insert pool history entries for funding and spending
if let Err(e) = insert_pool_history_entry(
conn,
&utxo4_initial,
&cauldron4_initial,
Some(thirty_days_ago as u64),
Some(thirty_days_ago as u64),
) {
println!(
"Failed to insert pool history entry for TOKEN 4 initial funding: {:?}",
e
);
}
if let Err(e) = insert_pool_history_entry(
conn,
&utxo4_initial,
&cauldron4_spent,
Some(current_timestamp as u64),
Some(current_timestamp as u64),
) {
println!(
"Failed to insert pool history entry for TOKEN 4 spending: {:?}",
e
);
}
Ok(())
}
}