// Copyright (C) 2025 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 crate::db::cauldron::pool::{get_token_volume_sats, get_total_volume_sats}; use crate::db::DB; use crate::rpc::err::{db_error, ApiResult}; use crate::timeutil::time_now; use rocket::get; use rocket::serde::json::{json, Json, Value}; use rocket::State; /// Gives total volume for all tokens in a time period. /// Status: Stable /// /// - start: Unix timestamp for start of period (optional, defaults to 24 hours ago) /// - end: Unix timestamp for end of period (optional, defaults to now) /// /// **Response Example:** /// /// ```json /// { /// "total_volume_sats": 1459676788, /// "period_start": 1640995200, /// "period_end": 1641081600 /// } /// ``` #[get("/volume?&")] pub fn volume_all(start: Option, end: Option, conn: &State) -> ApiResult { let db = conn.cauldron_r.get().map_err(db_error)?; let end_timestamp = end.unwrap_or_else(|| time_now() as usize); let start_timestamp = start.unwrap_or_else(|| { end_timestamp.saturating_sub(86400) // 24 hours ago }); let total_volume = get_total_volume_sats(&db, start_timestamp as u64, end_timestamp as u64) .map_err(db_error)?; Ok(Json(json!({ "total_volume_sats": total_volume, "period_start": start_timestamp, "period_end": end_timestamp }))) } /// Gives volume for a specific token in a time period. /// Status: Stable /// /// - token: Token identifier / category. /// - start: Unix timestamp for start of period (optional, defaults to 24 hours ago) /// - end: Unix timestamp for end of period (optional, defaults to now) /// /// **Response Example:** /// /// ```json /// { /// "volume_sats": 1459676788, /// "volume_tokens": 12345, /// "token_id": "b79bfc8246b5fc4707e7c7dedcb6619ef1ab91f494a790c20b0f4c422ed95b92", /// "period_start": 1640995200, /// "period_end": 1641081600 /// } /// ``` #[get("/volume/?&")] pub fn volume_token( token: &str, start: Option, end: Option, conn: &State, ) -> ApiResult { let db = conn.cauldron_r.get().map_err(db_error)?; let end_timestamp = end.unwrap_or_else(|| time_now() as usize); let start_timestamp = start.unwrap_or_else(|| { end_timestamp.saturating_sub(86400) // 24 hours ago }); let (sats_volume, token_volume) = get_token_volume_sats(&db, start_timestamp as u64, end_timestamp as u64, token) .map_err(db_error)?; Ok(Json(json!({ "volume_sats": sats_volume, "volume_tokens": token_volume, "token_id": token, "period_start": start_timestamp, "period_end": end_timestamp }))) } #[cfg(test)] mod tests { use super::*; use crate::db::cauldron::pool::{ create_table, dummy_init_seq, insert_new_pool, insert_pool_history_entry, }; use crate::db::cauldron::tx::{create_table as create_tx_table, insert_block_tx}; use crate::db::cauldron::utxo_funding::create_table as create_utxo_funding_table; use crate::utiltest::mock_db_pool; use bitcoin_hashes::hex::ToHex; use bitcoin_hashes::Hash; use bitcoincash::{BlockHash, PubkeyHash, TokenID, Txid}; use riftenlabs_defi::cauldron::ParsedContract; use riftenlabs_defi::chainutil::OutPointHash; use rusqlite::Connection; fn setup_mock_db(conn: &Connection) { create_tx_table(conn); create_table(conn); create_utxo_funding_table(conn); dummy_init_seq(); } #[test] fn test_volume_all() { let mock_db = mock_db_pool(setup_mock_db); let write_conn = mock_db.cauldron_w.get().unwrap(); // Insert test data let owner_pkh = PubkeyHash::from_hash(bitcoin_hashes::hash160::Hash::from_slice(&[0xca; 20]).unwrap()); let block_zero = BlockHash::from_hash(bitcoin_hashes::sha256d::Hash::from_slice(&[0x00; 32]).unwrap()); let current_time = time_now() as u64; // Create test pool and history entries let utxo = OutPointHash::from_hash(bitcoin_hashes::sha256::Hash::from_slice(&[0xda; 32]).unwrap()); let token_id = TokenID::from_hash(bitcoin_hashes::sha256d::Hash::from_slice(&[0xdb; 32]).unwrap()); let txid = Txid::from_hash(bitcoin_hashes::sha256d::Hash::from_slice(&[0xdc; 32]).unwrap()); let cauldron = ParsedContract { pkh: owner_pkh, is_withdrawn: false, spent_utxo_hash: OutPointHash::from_hash( bitcoin_hashes::sha256::Hash::from_slice(&[0x00; 32]).unwrap(), ), new_utxo_hash: Some(utxo), new_utxo_txid: Some(txid), new_utxo_n: Some(0), token_id: Some(token_id), sats: Some(1000), token_amount: Some(100), }; insert_new_pool(&write_conn, &cauldron).unwrap(); insert_block_tx( &write_conn, &txid, &block_zero, current_time.try_into().unwrap(), ) .unwrap(); // Insert pool history entry with volume insert_pool_history_entry( &write_conn, &utxo, &cauldron, Some(current_time), Some(current_time), 500, // sats_delta for trading activity 50, // token_delta for trading activity ) .unwrap(); // Test volume calculation let result = get_total_volume_sats(&write_conn, current_time - 3600, current_time + 3600).unwrap(); assert_eq!(result, 500); } #[test] fn test_volume_token() { let mock_db = mock_db_pool(setup_mock_db); let write_conn = mock_db.cauldron_w.get().unwrap(); // Insert test data let owner_pkh = PubkeyHash::from_hash(bitcoin_hashes::hash160::Hash::from_slice(&[0xca; 20]).unwrap()); let block_zero = BlockHash::from_hash(bitcoin_hashes::sha256d::Hash::from_slice(&[0x00; 32]).unwrap()); let current_time = time_now() as u64; // Create test pool and history entries let utxo = OutPointHash::from_hash(bitcoin_hashes::sha256::Hash::from_slice(&[0xda; 32]).unwrap()); let token_id = TokenID::from_hash(bitcoin_hashes::sha256d::Hash::from_slice(&[0xdb; 32]).unwrap()); let txid = Txid::from_hash(bitcoin_hashes::sha256d::Hash::from_slice(&[0xdc; 32]).unwrap()); let cauldron = ParsedContract { pkh: owner_pkh, is_withdrawn: false, spent_utxo_hash: OutPointHash::from_hash( bitcoin_hashes::sha256::Hash::from_slice(&[0x00; 32]).unwrap(), ), new_utxo_hash: Some(utxo), new_utxo_txid: Some(txid), new_utxo_n: Some(0), token_id: Some(token_id), sats: Some(1000), token_amount: Some(100), }; insert_new_pool(&write_conn, &cauldron).unwrap(); insert_block_tx( &write_conn, &txid, &block_zero, current_time.try_into().unwrap(), ) .unwrap(); // Insert pool history entry with volume insert_pool_history_entry( &write_conn, &utxo, &cauldron, Some(current_time), Some(current_time), 500, // sats_delta for trading activity 50, // token_delta for trading activity ) .unwrap(); // Test token-specific volume calculation let (sats_volume, token_volume) = get_token_volume_sats( &write_conn, current_time - 3600, current_time + 3600, &token_id.to_hex(), ) .unwrap(); assert_eq!(sats_volume, 500); assert_eq!(token_volume, 50); } }