// 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 poolperiod::PoolPeriod; use rocket::{get, http::Status, response::status::Custom, serde::json::Json, State}; use serde_json::Value; use crate::{ db::{cauldron::pool::get_pool_period_snapshot, DB}, timeutil::time_now, }; use serde_json::json; pub mod apyaggregator; pub mod poolperiod; #[derive(Clone, Debug)] pub struct PoolSnapshot { pub pool_id: String, pub timestamp: u64, pub sats: u64, pub token_amount: u64, } impl PoolSnapshot { #[allow(dead_code)] // used in unit tests pub fn dummy(timestamp: u64, sats: u64, token_amount: u64) -> Self { Self { pool_id: "dummy".to_string(), timestamp, sats, token_amount, } } } /// Fetch apy for a token and/or an account within a given time interval. All variables are optional. /// A query with no variables will return the AAPY based on all users and all tokens aggregated. /// /// Status: Stable /// /// - token: The 32 byte token ID /// - pkh: Public key hash for wallet account /// - start: Unix timestamp for period start (default 30 days) /// - end: Unix timestamp for period end (default NOW) /// /// **Response Example:** /// /// ```json /// {"apy":"10.00","pools":100} /// ``` /// #[get("/pool/aggregated_apy?&&&")] pub fn aggregate_apy( token: Option<&str>, pkh: Option<&str>, start: Option, // default 30 days before end end: Option, // default now db: &State, ) -> Result, Custom> { let end = end.unwrap_or(time_now()); let start = start.unwrap_or(end - (3600 * 24 * 30)); // 30 days if end < start { return Err(Custom( Status::BadRequest, "end time cannot be less than start time".to_string(), )); } if start < 0 { return Err(Custom( Status::BadRequest, "start time cannot be negative".to_string(), )); } let conn = db .cauldron_r .get() .map_err(|e| Custom(Status::InternalServerError, format!("DB error: {e}")))?; let pools: anyhow::Result> = get_pool_period_snapshot(&conn, token, pkh, start, end) .map_err(|e| Custom(Status::InternalServerError, format!("Error: {e}")))? .into_iter() .map(|(start, end)| PoolPeriod::new(start, end)) .collect(); let pools = pools.map_err(|e| Custom(Status::InternalServerError, format!("Error: {e}")))?; let pools_count = pools.len(); let apy = apyaggregator::APYAggregator::aggregate_apy(pools.into_iter(), Some(start as u64)) .map_err(|e| Custom(Status::InternalServerError, format!("Error: {e}")))?; Ok(Json(json!({ "apy": apy.to_string(), "pools": pools_count, }))) }