Return proper 4XX status codes for client errors to prevent load balancers from misinterpreting input validation failures as server errors. Add centralized ApiResult type and ApiErrorCode enum for consistent handling.
99 lines
2.8 KiB
Rust
99 lines
2.8 KiB
Rust
// 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, serde::json::Json, State};
|
|
use serde_json::Value;
|
|
|
|
use crate::{
|
|
db::{cauldron::pool::get_pool_period_snapshot, DB},
|
|
rpc::err::{bad_request, db_error, ApiErrorCode, ApiResult},
|
|
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?<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>,
|
|
) -> ApiResult<Value> {
|
|
let end = end.unwrap_or(time_now());
|
|
let start = start.unwrap_or(end - (3600 * 24 * 30)); // 30 days
|
|
|
|
if end < start {
|
|
return Err(bad_request(
|
|
ApiErrorCode::InvalidTimeRange,
|
|
"end time cannot be less than start time",
|
|
));
|
|
}
|
|
if start < 0 {
|
|
return Err(bad_request(
|
|
ApiErrorCode::InvalidTimeRange,
|
|
"start time cannot be negative",
|
|
));
|
|
}
|
|
|
|
let conn = db.cauldron_r.get().map_err(db_error)?;
|
|
|
|
let pools: anyhow::Result<Vec<PoolPeriod>> =
|
|
get_pool_period_snapshot(&conn, token, pkh, start, end)
|
|
.map_err(db_error)?
|
|
.into_iter()
|
|
.map(|(start, end)| PoolPeriod::new(start, end))
|
|
.collect();
|
|
|
|
let pools = pools.map_err(db_error)?;
|
|
|
|
let pools_count = pools.len();
|
|
let apy = apyaggregator::APYAggregator::aggregate_apy(pools.into_iter(), Some(start as u64))
|
|
.map_err(db_error)?;
|
|
|
|
Ok(Json(json!({
|
|
"apy": apy.to_string(),
|
|
"pools": pools_count,
|
|
})))
|
|
}
|