riftenlabs-indexer/src/rpc/moria.rs
2026-03-30 23:52:29 +02:00

118 lines
3.8 KiB
Rust

// Copyright (C) 2024-2026 Whiterun LLC
//
// 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 crate::db::moria::{get_active_loans, get_global_history, get_loan_history, get_stats};
use crate::db::DB;
use crate::rpc::err::{bad_request, db_error, ApiErrorCode, CachedApiResult};
use crate::rpc::response::{cached_ok, CACHE_AGGREGATE};
use rocket::{get, State};
use serde_json::Value;
fn parse_nfth(hex_str: &str) -> Result<Vec<u8>, (ApiErrorCode, String)> {
let bytes = hex::decode(hex_str).map_err(|e| {
(
ApiErrorCode::InvalidParameters,
format!("Invalid nfth hex: {e}"),
)
})?;
if bytes.len() != 32 {
return Err((
ApiErrorCode::InvalidParameters,
format!("nfth must be 32 bytes (64 hex chars), got {}", bytes.len()),
));
}
Ok(bytes)
}
/// Get full loan history for a given borrower NFT hash.
///
/// - borrower_hash: 64-character hex string (32-byte P2NFTH hash identifying the loan)
///
/// Returns an array of all actions (borrow, repay, redeem, refinance, add_collateral)
/// for this loan, sorted by timestamp.
#[get("/loan/<borrower_hash>/history")]
pub async fn loan_history(borrower_hash: &str, db: &State<DB>) -> CachedApiResult<Value> {
let hash_bytes = hex::decode(borrower_hash).map_err(|e| {
bad_request(
ApiErrorCode::InvalidParameters,
&format!("Invalid borrower hash: {e}"),
)
})?;
if hash_bytes.len() != 32 {
return Err(bad_request(
ApiErrorCode::InvalidParameters,
"Borrower hash must be 32 bytes (64 hex characters)",
));
}
let entries = get_loan_history(&db.moria_r, &hash_bytes)
.await
.map_err(db_error)?;
Ok(cached_ok(
serde_json::to_value(entries).unwrap(),
CACHE_AGGREGATE,
))
}
/// Get global moria action history with pagination and optional nfth filter.
///
/// - offset: Number of entries to skip (default: 0)
/// - limit: Maximum entries to return (default: 50, max: 200)
/// - nfth: Comma-separated list of borrower NFT hashes (64-char hex each) to filter by
#[get("/history?<offset>&<limit>&<nfth>")]
pub async fn global_history(
offset: Option<i64>,
limit: Option<i64>,
nfth: Option<&str>,
db: &State<DB>,
) -> CachedApiResult<Value> {
let offset = offset.unwrap_or(0).max(0);
let limit = limit.unwrap_or(50).clamp(1, 200);
let nfth_filter: Vec<Vec<u8>> = match nfth {
Some(s) if !s.is_empty() => {
let mut filters = Vec::new();
for hash_hex in s.split(',') {
let hash_hex = hash_hex.trim();
if hash_hex.is_empty() {
continue;
}
filters.push(parse_nfth(hash_hex).map_err(|(code, msg)| bad_request(code, &msg))?);
}
filters
}
_ => Vec::new(),
};
let entries = get_global_history(&db.moria_r, &nfth_filter, offset, limit)
.await
.map_err(db_error)?;
Ok(cached_ok(
serde_json::to_value(entries).unwrap(),
CACHE_AGGREGATE,
))
}
/// List all active (not yet repaid/redeemed) loans.
#[get("/loans/active")]
pub async fn active_loans(db: &State<DB>) -> CachedApiResult<Value> {
let entries = get_active_loans(&db.moria_r).await.map_err(db_error)?;
Ok(cached_ok(
serde_json::to_value(entries).unwrap(),
CACHE_AGGREGATE,
))
}
/// Get Moria protocol statistics.
#[get("/stats")]
pub async fn moria_stats(db: &State<DB>) -> CachedApiResult<Value> {
let stats = get_stats(&db.moria_r).await.map_err(db_error)?;
Ok(cached_ok(stats, CACHE_AGGREGATE))
}