2024-03-04 16:40:50 +01: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
|
|
|
|
|
|
2024-05-29 08:45:50 +02:00
|
|
|
use std::{
|
|
|
|
|
collections::HashMap,
|
|
|
|
|
sync::{Arc, Mutex},
|
|
|
|
|
};
|
2024-04-03 11:39:49 +02:00
|
|
|
|
|
|
|
|
use anyhow::{Context, Result};
|
2024-03-04 16:40:50 +01:00
|
|
|
use rusqlite::{params, Connection};
|
|
|
|
|
|
2024-09-10 22:37:44 +02:00
|
|
|
use crate::{
|
|
|
|
|
bcmr::parsedbcmr::ParsedBCMR,
|
|
|
|
|
db::bcmr::{get_token_bcmr, get_well_known_bcmr},
|
|
|
|
|
};
|
2024-05-09 11:25:27 +02:00
|
|
|
|
|
|
|
|
pub mod bcmr;
|
2024-04-03 11:39:49 +02:00
|
|
|
pub mod contract;
|
2024-04-03 21:40:33 +02:00
|
|
|
pub mod pool;
|
2024-04-03 11:39:49 +02:00
|
|
|
pub mod price;
|
2024-05-09 11:25:27 +02:00
|
|
|
pub mod tokens;
|
2024-04-03 09:49:44 +02:00
|
|
|
pub mod tvl;
|
2024-09-04 11:11:43 +02:00
|
|
|
pub mod user;
|
2024-03-04 16:40:50 +01:00
|
|
|
|
2024-05-29 08:45:50 +02:00
|
|
|
pub type ResponseCache = Arc<Mutex<HashMap<String, (i64 /* timestamp */, Vec<serde_json::Value>)>>>;
|
|
|
|
|
|
2024-03-04 16:40:50 +01:00
|
|
|
#[allow(clippy::type_complexity)]
|
|
|
|
|
pub fn list_tokens_by_volume(
|
2024-05-09 11:25:27 +02:00
|
|
|
cauldron_conn: &Connection,
|
|
|
|
|
bcmr_conn: &Connection,
|
2024-03-04 16:40:50 +01:00
|
|
|
seconds: usize,
|
|
|
|
|
limit: usize,
|
2024-09-10 22:37:44 +02:00
|
|
|
) -> Result<
|
|
|
|
|
Vec<(
|
|
|
|
|
String,
|
|
|
|
|
u64,
|
|
|
|
|
u64,
|
|
|
|
|
u64,
|
|
|
|
|
u64,
|
|
|
|
|
u64,
|
|
|
|
|
u64,
|
|
|
|
|
Option<ParsedBCMR>,
|
|
|
|
|
Vec<ParsedBCMR>,
|
|
|
|
|
)>,
|
|
|
|
|
> {
|
2024-03-04 16:40:50 +01:00
|
|
|
// List token ID's by volume last n seconds
|
2024-05-09 11:25:27 +02:00
|
|
|
let mut statement = cauldron_conn
|
2024-03-04 16:40:50 +01:00
|
|
|
.prepare(
|
|
|
|
|
"
|
|
|
|
|
WITH TradeData AS (
|
|
|
|
|
SELECT
|
|
|
|
|
uf1.token_id,
|
|
|
|
|
ABS(uf1.sats - COALESCE(uf2.sats, 0)) as trade_volume
|
|
|
|
|
FROM utxo_funding uf1
|
|
|
|
|
INNER JOIN utxo_funding uf2 ON uf1.spent_utxo_hash = uf2.new_utxo_hash
|
|
|
|
|
JOIN tx ON uf1.txid = tx.txid
|
|
|
|
|
WHERE COALESCE(tx.first_seen_timestamp, tx.mtp_timestamp) >= (strftime('%s', 'now') - ?)
|
|
|
|
|
),
|
|
|
|
|
TVLData AS (
|
|
|
|
|
SELECT
|
|
|
|
|
token_id,
|
|
|
|
|
SUM(sats) as tvl_sats,
|
|
|
|
|
SUM(token_amount) as tvl_tokens
|
|
|
|
|
FROM utxo_funding
|
|
|
|
|
WHERE new_utxo_hash NOT IN (SELECT spent_utxo_hash FROM utxo_spending)
|
|
|
|
|
GROUP BY token_id
|
|
|
|
|
),
|
|
|
|
|
HighestUnspentUTXO AS (
|
|
|
|
|
SELECT
|
|
|
|
|
token_id,
|
|
|
|
|
MAX(sats) as highest_sats,
|
|
|
|
|
MAX(token_amount) as token_amount
|
|
|
|
|
FROM utxo_funding
|
|
|
|
|
WHERE new_utxo_hash NOT IN (SELECT spent_utxo_hash FROM utxo_spending)
|
|
|
|
|
GROUP BY token_id
|
|
|
|
|
),
|
|
|
|
|
AggregateTradeData AS (
|
|
|
|
|
SELECT
|
|
|
|
|
COALESCE(td.token_id, tvl.token_id) as token_id,
|
|
|
|
|
COALESCE(SUM(td.trade_volume), 0) as total_trade_volume,
|
|
|
|
|
COALESCE(COUNT(td.token_id), 0) as number_of_trades,
|
|
|
|
|
tvl.tvl_sats,
|
|
|
|
|
tvl.tvl_tokens,
|
|
|
|
|
hu.highest_sats,
|
|
|
|
|
hu.token_amount
|
|
|
|
|
FROM TVLData tvl
|
|
|
|
|
LEFT JOIN TradeData td ON tvl.token_id = td.token_id
|
|
|
|
|
LEFT JOIN HighestUnspentUTXO hu ON tvl.token_id = hu.token_id
|
|
|
|
|
GROUP BY tvl.token_id
|
|
|
|
|
)
|
|
|
|
|
SELECT
|
|
|
|
|
token_id,
|
|
|
|
|
total_trade_volume,
|
|
|
|
|
number_of_trades,
|
|
|
|
|
tvl_sats,
|
|
|
|
|
tvl_tokens,
|
|
|
|
|
highest_sats,
|
|
|
|
|
token_amount
|
|
|
|
|
FROM AggregateTradeData
|
|
|
|
|
ORDER BY total_trade_volume DESC, tvl_sats DESC
|
|
|
|
|
LIMIT ?;
|
|
|
|
|
",
|
2024-04-15 23:05:06 +02:00
|
|
|
)?;
|
2024-03-04 16:40:50 +01:00
|
|
|
|
2024-09-10 22:37:44 +02:00
|
|
|
let tokens: Vec<(
|
|
|
|
|
String,
|
|
|
|
|
u64,
|
|
|
|
|
u64,
|
|
|
|
|
u64,
|
|
|
|
|
u64,
|
|
|
|
|
u64,
|
|
|
|
|
u64,
|
|
|
|
|
Option<ParsedBCMR>,
|
|
|
|
|
Vec<ParsedBCMR>,
|
|
|
|
|
)> = statement
|
2024-03-04 16:40:50 +01:00
|
|
|
.query_and_then([seconds, limit], |row| {
|
|
|
|
|
let token_id: String = row.get(0)?;
|
|
|
|
|
let trade_volume: u64 = row.get(1)?;
|
|
|
|
|
let trade_count: u64 = row.get(2)?;
|
|
|
|
|
let tvl_sats: u64 = row.get(3)?;
|
|
|
|
|
let tvl_token: u64 = row.get(4)?;
|
|
|
|
|
let best_contract_sats: u64 = row.get(5)?;
|
|
|
|
|
let best_contracts_token: u64 = row.get(6)?;
|
2024-05-09 11:25:27 +02:00
|
|
|
let bcmr = get_token_bcmr(bcmr_conn, &token_id)?;
|
2024-09-10 22:37:44 +02:00
|
|
|
let bcmr_well_known = get_well_known_bcmr(bcmr_conn, &token_id)?;
|
2024-03-04 16:40:50 +01:00
|
|
|
Ok((
|
|
|
|
|
token_id,
|
|
|
|
|
trade_volume,
|
|
|
|
|
trade_count,
|
|
|
|
|
tvl_sats,
|
|
|
|
|
tvl_token,
|
|
|
|
|
best_contract_sats,
|
|
|
|
|
best_contracts_token,
|
2024-05-09 11:25:27 +02:00
|
|
|
bcmr,
|
2024-09-10 22:37:44 +02:00
|
|
|
bcmr_well_known,
|
2024-03-04 16:40:50 +01:00
|
|
|
))
|
|
|
|
|
})
|
|
|
|
|
.unwrap()
|
2024-09-10 22:37:44 +02:00
|
|
|
.map(
|
|
|
|
|
|row: Result<(
|
|
|
|
|
String,
|
|
|
|
|
u64,
|
|
|
|
|
u64,
|
|
|
|
|
u64,
|
|
|
|
|
u64,
|
|
|
|
|
u64,
|
|
|
|
|
u64,
|
|
|
|
|
Option<ParsedBCMR>,
|
|
|
|
|
Vec<ParsedBCMR>,
|
|
|
|
|
)>| row.unwrap(),
|
|
|
|
|
)
|
2024-03-04 16:40:50 +01:00
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
Ok(tokens)
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-21 11:15:44 +01:00
|
|
|
fn all_time_volume(db: &Connection, end_timestamp: u64) -> Result<Vec<(String, i64)>> {
|
|
|
|
|
let sql = "
|
|
|
|
|
SELECT
|
|
|
|
|
uf1.token_id,
|
|
|
|
|
SUM(ABS(uf1.sats - COALESCE(uf2.sats, 0))) AS total_volume_sats
|
|
|
|
|
FROM utxo_funding uf1
|
|
|
|
|
INNER JOIN utxo_funding uf2 ON uf1.spent_utxo_hash = uf2.new_utxo_hash
|
|
|
|
|
JOIN tx ON uf1.txid = tx.txid
|
|
|
|
|
WHERE COALESCE(tx.first_seen_timestamp, tx.mtp_timestamp) <= ?
|
|
|
|
|
GROUP BY uf1.token_id";
|
|
|
|
|
|
|
|
|
|
let mut stmt = db.prepare(sql)?;
|
|
|
|
|
|
|
|
|
|
let volume_iter = stmt.query_map(params![end_timestamp], |row| {
|
|
|
|
|
Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?))
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
let mut volumes = Vec::new();
|
|
|
|
|
for volume in volume_iter {
|
|
|
|
|
volumes.push(volume?);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(volumes)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn period_volume(
|
|
|
|
|
db: &Connection,
|
|
|
|
|
begin_timestamp: u64,
|
|
|
|
|
end_timestamp: u64,
|
|
|
|
|
) -> Result<Vec<(String, i64)>> {
|
|
|
|
|
let sql = "
|
|
|
|
|
SELECT
|
|
|
|
|
uf1.token_id,
|
|
|
|
|
SUM(ABS(uf1.sats - COALESCE(uf2.sats, 0))) AS total_volume_sats
|
|
|
|
|
FROM utxo_funding uf1
|
|
|
|
|
INNER JOIN utxo_funding uf2 ON uf1.spent_utxo_hash = uf2.new_utxo_hash
|
|
|
|
|
JOIN tx ON uf1.txid = tx.txid
|
|
|
|
|
WHERE COALESCE(tx.first_seen_timestamp, tx.mtp_timestamp) BETWEEN ? AND ?
|
|
|
|
|
GROUP BY uf1.token_id
|
|
|
|
|
";
|
|
|
|
|
|
|
|
|
|
let mut stmt = db.prepare(sql)?;
|
|
|
|
|
let volume_iter = stmt.query_map(params![begin_timestamp, end_timestamp], |row| {
|
|
|
|
|
Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?))
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
let mut volumes = Vec::new();
|
|
|
|
|
for volume in volume_iter {
|
|
|
|
|
volumes.push(volume?);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(volumes)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn contract_volume(
|
|
|
|
|
db: &Connection,
|
|
|
|
|
end_timestamp: u64,
|
|
|
|
|
) -> Result<HashMap<String, (i64, i64, i64)>> {
|
|
|
|
|
let one_day_seconds = 86400_u64;
|
|
|
|
|
let thirty_days_seconds = 30 * 86400_u64;
|
|
|
|
|
|
|
|
|
|
let one_day_begin_timestamp = end_timestamp
|
|
|
|
|
.checked_sub(one_day_seconds)
|
|
|
|
|
.context("timestamp underflow")?;
|
|
|
|
|
let thirty_days_begin_timestamp = end_timestamp
|
|
|
|
|
.checked_sub(thirty_days_seconds)
|
|
|
|
|
.context("timestamp underflow")?;
|
|
|
|
|
|
|
|
|
|
let all_time = all_time_volume(db, end_timestamp)?;
|
|
|
|
|
let one_day = period_volume(db, one_day_begin_timestamp, end_timestamp)?;
|
|
|
|
|
let thirty_days = period_volume(db, thirty_days_begin_timestamp, end_timestamp)?;
|
|
|
|
|
|
|
|
|
|
let mut result = HashMap::new();
|
|
|
|
|
|
|
|
|
|
for (token_id, volume) in all_time.into_iter() {
|
|
|
|
|
result.insert(token_id, (volume, 0, 0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (token_id, day_volume) in one_day.into_iter() {
|
|
|
|
|
if let Some((_, _, one_day_volume)) = result.get_mut(&token_id) {
|
|
|
|
|
*one_day_volume = day_volume;
|
|
|
|
|
} else {
|
|
|
|
|
result.insert(token_id.clone(), (0, 0, day_volume));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (token_id, month_volume) in thirty_days.into_iter() {
|
|
|
|
|
if let Some((_, thirty_day_volume, _)) = result.get_mut(&token_id) {
|
|
|
|
|
*thirty_day_volume = month_volume;
|
|
|
|
|
} else {
|
|
|
|
|
result.insert(token_id.clone(), (0, month_volume, 0));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(result)
|
|
|
|
|
}
|