riftenlabs-indexer/src/rpc/volume.rs

259 lines
8.4 KiB
Rust
Raw Normal View History

// 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::timeutil::time_now;
use rocket::get;
use rocket::http::Status;
use rocket::response::status::Custom;
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?<start>&<end>")]
pub fn volume_all(
start: Option<usize>,
end: Option<usize>,
conn: &State<DB>,
) -> Result<Json<Value>, Custom<String>> {
let db = conn.cauldron_r.get().map_err(|_| {
Custom(
Status::InternalServerError,
"Failed to get DB connection".into(),
)
})?;
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(|e| Custom(Status::InternalServerError, format!("Error: {e}")))?;
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/<token>?<start>&<end>")]
pub fn volume_token(
token: &str,
start: Option<usize>,
end: Option<usize>,
conn: &State<DB>,
) -> Result<Json<Value>, Custom<String>> {
let db = conn.cauldron_r.get().map_err(|_| {
Custom(
Status::InternalServerError,
"Failed to get DB connection".into(),
)
})?;
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(|e| Custom(Status::InternalServerError, format!("Error: {e}")))?;
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);
}
}