2024-10-21 15:13:16 +02: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 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,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-22 11:11:39 +02:00
|
|
|
/// 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}
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
2024-10-21 15:13:16 +02:00
|
|
|
#[get("/pool/aggregated_apy?<token>&<pkh>&<start>&<end>")]
|
|
|
|
|
pub fn aggregate_apy(
|
|
|
|
|
token: Option<&str>,
|
|
|
|
|
pkh: Option<&str>,
|
|
|
|
|
start: Option<i64>, // default 30 days before end
|
|
|
|
|
end: Option<i64>, // default now
|
|
|
|
|
db: &State<DB>,
|
|
|
|
|
) -> Result<Json<Value>, Custom<String>> {
|
|
|
|
|
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()
|
2025-07-16 09:31:14 +02:00
|
|
|
.map_err(|e| Custom(Status::InternalServerError, format!("DB error: {e}")))?;
|
2024-10-21 15:13:16 +02:00
|
|
|
|
|
|
|
|
let pools: anyhow::Result<Vec<PoolPeriod>> =
|
|
|
|
|
get_pool_period_snapshot(&conn, token, pkh, start, end)
|
2025-07-16 09:31:14 +02:00
|
|
|
.map_err(|e| Custom(Status::InternalServerError, format!("Error: {e}")))?
|
2024-10-21 15:13:16 +02:00
|
|
|
.into_iter()
|
|
|
|
|
.map(|(start, end)| PoolPeriod::new(start, end))
|
|
|
|
|
.collect();
|
|
|
|
|
|
2025-07-16 09:31:14 +02:00
|
|
|
let pools = pools.map_err(|e| Custom(Status::InternalServerError, format!("Error: {e}")))?;
|
2024-10-21 15:13:16 +02:00
|
|
|
|
|
|
|
|
let pools_count = pools.len();
|
|
|
|
|
let apy = apyaggregator::APYAggregator::aggregate_apy(pools.into_iter(), Some(start as u64))
|
2025-07-16 09:31:14 +02:00
|
|
|
.map_err(|e| Custom(Status::InternalServerError, format!("Error: {e}")))?;
|
2024-10-21 15:13:16 +02:00
|
|
|
|
|
|
|
|
Ok(Json(json!({
|
|
|
|
|
"apy": apy.to_string(),
|
|
|
|
|
"pools": pools_count,
|
|
|
|
|
})))
|
|
|
|
|
}
|