riftenlabs-indexer/src/rpc/tvl.rs

174 lines
5 KiB
Rust
Raw Normal View History

2024-04-03 09:49:44 +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 anyhow::{Context, Result};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use rocket::{get, http::Status, response::status::Custom, serde::json::Json, State};
use rusqlite::{params, Connection};
use serde_json::{json, Value};
use crate::{db::DBPool, timeutil::time_now};
/// Fetch TVL for all tokens
pub fn get_all_token_tvl(
connection: &Connection,
max_timestamp: usize,
) -> Result<Vec<(String, u64, u64)>> {
let mut statement = connection.prepare(
"SELECT
uf.token_id,
SUM(uf.token_amount) AS total_unspent_token_amount,
SUM(uf.sats) AS total_unspent_sats
FROM
utxo_funding uf
LEFT JOIN
utxo_spending us ON uf.new_utxo_hash = us.spent_utxo_hash
JOIN
tx ON uf.txid = tx.txid
WHERE
us.spent_utxo_hash IS NULL AND
(tx.mtp_timestamp <= ? OR tx.first_seen_timestamp <= ?)
GROUP BY
uf.token_id
",
)?;
let tvl: Vec<(String, u64, u64)> = statement
.query_and_then([max_timestamp, max_timestamp], |row| {
let token_id: String = row.get(0)?;
let token_amount: u64 = row.get(1)?;
let sats: u64 = row.get(2)?;
Ok((token_id, token_amount, sats))
})
.unwrap()
.map(|row: Result<(String, u64, u64)>| row.unwrap())
.collect();
Ok(tvl)
}
/// Get TVL for a single token
pub fn get_token_tvl(
connection: &Connection,
max_timestamp: usize,
token_id: &str,
) -> Result<(u64, u64)> {
let mut statement = connection.prepare(
"SELECT
uf.token_amount,
uf.sats
FROM
utxo_funding uf
LEFT JOIN
utxo_spending us ON uf.new_utxo_hash = us.spent_utxo_hash
JOIN
tx ON uf.txid = tx.txid
WHERE
us.spent_utxo_hash IS NULL AND
(tx.mtp_timestamp <= ? OR tx.first_seen_timestamp <= ?) AND
uf.token_id = ?
",
)?;
// Execute the query with the specified parameters
let mut query = statement.query(params![max_timestamp, max_timestamp, token_id])?;
// Initialize accumulators
let mut sum_tokens: u64 = 0;
let mut sum_sats: u64 = 0;
// Iterate over the query result
while let Some(row) = query.next()? {
let tokens: u64 = row.get(0)?;
let sats: u64 = row.get(1)?;
sum_tokens = sum_tokens.checked_add(tokens).context("token overflow")?;
sum_sats = sum_sats.checked_add(sats).context("sats overflow")?;
}
Ok((sum_sats, sum_tokens))
}
// Deprecated; use valuelocked with optional parameters
#[get("/tvl/<time>")]
pub fn deprecated_tvl(
time: usize,
conn: &State<DBPool>,
) -> Result<Json<Vec<Value>>, Custom<String>> {
let db = conn
.get()
.map_err(|e| Custom(Status::InternalServerError, format!("Error: {}", e)))?;
let tvl: Vec<(String, u64, u64)> = get_all_token_tvl(&db, time)
.map_err(|e| Custom(Status::InternalServerError, format!("Error: {}", e)))?;
let result: Vec<Value> = tvl
.into_par_iter()
.map(|(token, token_amount, sats)| {
json!({
"token_id": token,
"token_amount": token_amount,
"satoshis": sats
})
})
.collect();
Ok(Json(result))
}
#[get("/valuelocked?<time>")]
pub fn valuelocked_all(
time: Option<usize>,
conn: &State<DBPool>,
) -> Result<Json<Vec<Value>>, Custom<String>> {
let db = conn.get().map_err(|_| {
Custom(
Status::InternalServerError,
"Failed to get DB connection".into(),
)
})?;
let time_filter = time.unwrap_or_else(|| time_now() as usize);
let tvl: Vec<(String, u64, u64)> = get_all_token_tvl(&db, time_filter)
.map_err(|e| Custom(Status::InternalServerError, format!("Error: {}", e)))?;
let result: Vec<Value> = tvl
.into_par_iter()
.map(|(token, token_amount, sats)| {
json!({
"token_id": token,
"token_amount": token_amount,
"satoshis": sats
})
})
.collect();
Ok(Json(result))
}
#[get("/valuelocked/<token>?<time>")]
pub fn valuelocked_token(
token: &str,
time: Option<usize>,
conn: &State<DBPool>,
) -> Result<Json<Value>, Custom<String>> {
let db = conn.get().map_err(|_| {
Custom(
Status::InternalServerError,
"Failed to get DB connection".into(),
)
})?;
let time_filter = time.unwrap_or_else(|| time_now() as usize);
let (sats, token_amount) = get_token_tvl(&db, time_filter, token)
.map_err(|e| Custom(Status::InternalServerError, format!("Error: {}", e)))?;
Ok(Json(json!({
"token_amount": token_amount,
"satoshis": sats
})))
}