contrib: Tool for documenting API calls
This commit is contained in:
parent
450ae701a8
commit
007aeb06dd
11 changed files with 661 additions and 4 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
|||
/target
|
||||
*.db
|
||||
/apidoc
|
||||
|
|
|
|||
|
|
@ -37,3 +37,12 @@ linters:
|
|||
script:
|
||||
- ./linters/run_linters.py
|
||||
|
||||
build-api-docs:
|
||||
image: python:latest
|
||||
script:
|
||||
- ./contrib/api_doc_generator.py
|
||||
artifacts:
|
||||
paths:
|
||||
- apidoc
|
||||
expire_in: 30 days
|
||||
|
||||
|
|
|
|||
430
contrib/api_doc_generator.py
Executable file
430
contrib/api_doc_generator.py
Executable file
|
|
@ -0,0 +1,430 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2025 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
|
||||
|
||||
"""
|
||||
API Documentation Generator for Riften Labs Indexer
|
||||
|
||||
This tool parses routes from main.rs and generates API documentation
|
||||
in markdown format for each mount point.
|
||||
"""
|
||||
|
||||
import re
|
||||
import os
|
||||
import ast
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class RouteInfo:
|
||||
"""Information about a route"""
|
||||
path: str
|
||||
function_name: str
|
||||
method: str = "GET"
|
||||
parameters: List[str] = None
|
||||
query_params: List[str] = None
|
||||
path_params: List[str] = None
|
||||
description: str = ""
|
||||
status: str = "unstable"
|
||||
is_deprecated: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class MountInfo:
|
||||
"""Information about a mount point"""
|
||||
path: str
|
||||
routes: List[RouteInfo]
|
||||
|
||||
|
||||
class RustRouteParser:
|
||||
"""Parser for Rust route definitions"""
|
||||
|
||||
def __init__(self, main_rs_path: str):
|
||||
self.main_rs_path = main_rs_path
|
||||
self.mounts: List[MountInfo] = []
|
||||
|
||||
def parse_mounts(self) -> List[MountInfo]:
|
||||
"""Parse all mount points from main.rs"""
|
||||
with open(self.main_rs_path, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# Find all mount blocks - simpler approach
|
||||
# Look for .mount( followed by path and routes![
|
||||
mount_pattern = r'\.mount\(\s*"([^"]+)"\s*,\s*routes!\[(.*?)\]'
|
||||
matches = list(re.finditer(mount_pattern, content, re.DOTALL))
|
||||
|
||||
for match in matches:
|
||||
mount_path = match.group(1)
|
||||
routes_content = match.group(2)
|
||||
|
||||
routes = self._parse_routes(routes_content)
|
||||
mount_info = MountInfo(path=mount_path, routes=routes)
|
||||
self.mounts.append(mount_info)
|
||||
|
||||
return self.mounts
|
||||
|
||||
def _parse_routes(self, routes_content: str) -> List[RouteInfo]:
|
||||
"""Parse individual routes from a routes! macro"""
|
||||
routes = []
|
||||
|
||||
# Split by commas and clean up
|
||||
route_items = [item.strip() for item in routes_content.split(',') if item.strip()]
|
||||
|
||||
for item in route_items:
|
||||
# Remove any trailing commas and whitespace
|
||||
item = item.strip().rstrip(',')
|
||||
if item:
|
||||
route_info = self._parse_single_route(item)
|
||||
if route_info:
|
||||
routes.append(route_info)
|
||||
|
||||
return routes
|
||||
|
||||
def _parse_single_route(self, route_item: str) -> Optional[RouteInfo]:
|
||||
"""Parse a single route item"""
|
||||
# Extract function name (remove module prefix if present)
|
||||
if '::' in route_item:
|
||||
function_name = route_item.split('::')[-1]
|
||||
else:
|
||||
function_name = route_item
|
||||
|
||||
# For now, we'll get the actual route path and parameters from the function definition
|
||||
# This is a simplified approach - in a real implementation you'd want to parse the actual function signatures
|
||||
return RouteInfo(
|
||||
path="", # Will be filled in by function signature parsing
|
||||
function_name=function_name,
|
||||
parameters=[],
|
||||
query_params=[],
|
||||
path_params=[]
|
||||
)
|
||||
|
||||
|
||||
class FunctionSignatureParser:
|
||||
"""Parser for Rust function signatures to extract route information"""
|
||||
|
||||
def __init__(self, src_dir: str):
|
||||
self.src_dir = src_dir
|
||||
self.rpc_modules = {}
|
||||
self._load_rpc_modules()
|
||||
|
||||
def _load_rpc_modules(self):
|
||||
"""Load all RPC module files"""
|
||||
rpc_dir = os.path.join(self.src_dir, 'rpc')
|
||||
if not os.path.exists(rpc_dir):
|
||||
return
|
||||
|
||||
# Load files directly in rpc directory
|
||||
for file in os.listdir(rpc_dir):
|
||||
if file.endswith('.rs') and file != 'mod.rs':
|
||||
module_name = file[:-3] # Remove .rs extension
|
||||
file_path = os.path.join(rpc_dir, file)
|
||||
self.rpc_modules[module_name] = file_path
|
||||
|
||||
# Load files in subdirectories
|
||||
for subdir in os.listdir(rpc_dir):
|
||||
subdir_path = os.path.join(rpc_dir, subdir)
|
||||
if os.path.isdir(subdir_path):
|
||||
# Look for mod.rs files in subdirectories
|
||||
mod_rs_path = os.path.join(subdir_path, 'mod.rs')
|
||||
if os.path.exists(mod_rs_path):
|
||||
self.rpc_modules[subdir] = mod_rs_path
|
||||
|
||||
def get_function_signature(self, function_name: str) -> Optional[RouteInfo]:
|
||||
"""Get function signature and route information for a given function name"""
|
||||
for module_name, file_path in self.rpc_modules.items():
|
||||
route_info = self._parse_function_in_file(file_path, function_name)
|
||||
if route_info:
|
||||
return route_info
|
||||
return None
|
||||
|
||||
def _parse_function_in_file(self, file_path: str, function_name: str) -> Optional[RouteInfo]:
|
||||
"""Parse a specific function in a file"""
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# Look for the function definition
|
||||
func_pattern = rf'pub fn {function_name}\s*\([^)]*\)\s*->[^{{]*{{'
|
||||
func_match = re.search(func_pattern, content)
|
||||
|
||||
if not func_match:
|
||||
return None
|
||||
|
||||
# Extract description, status, and deprecated flag from comments above the function
|
||||
description, status, is_deprecated = self._extract_function_description(content, function_name)
|
||||
|
||||
# Skip deprecated functions
|
||||
if is_deprecated:
|
||||
return None
|
||||
|
||||
# Find the #[get("...")] attribute before the function
|
||||
get_attr_pattern = rf'#\[get\("([^"]+)"\)\]\s*pub fn {function_name}'
|
||||
get_match = re.search(get_attr_pattern, content)
|
||||
|
||||
if get_match:
|
||||
route_path = get_match.group(1)
|
||||
route_info = self._parse_route_path(route_path, function_name)
|
||||
route_info.description = description
|
||||
route_info.status = status
|
||||
route_info.is_deprecated = is_deprecated
|
||||
return route_info
|
||||
|
||||
# If no #[get] attribute found, return None - we can't determine the route
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error parsing {file_path}: {e}")
|
||||
return None
|
||||
|
||||
def _extract_function_description(self, content: str, function_name: str) -> tuple[str, str, bool]:
|
||||
"""Extract description, status, and deprecated flag from comments above the function"""
|
||||
# Look for the function definition - simpler pattern
|
||||
func_pattern = rf'pub fn {function_name}'
|
||||
func_match = re.search(func_pattern, content)
|
||||
|
||||
if not func_match:
|
||||
return "no description", "unstable", False
|
||||
|
||||
# Get the position of the function
|
||||
func_start = func_match.start()
|
||||
|
||||
# Look backwards from the function for comments
|
||||
# Find consecutive /// comment lines before the function
|
||||
lines = content[:func_start].split('\n')
|
||||
comment_lines = []
|
||||
|
||||
# Go backwards from the function to find comment lines
|
||||
for line in reversed(lines):
|
||||
line = line.strip()
|
||||
if line.startswith('///'):
|
||||
comment_lines.insert(0, line[3:].strip()) # Remove /// and strip
|
||||
elif line.startswith('pub fn'):
|
||||
break
|
||||
elif line and not line.startswith('///') and not line.startswith('#['):
|
||||
break
|
||||
|
||||
if comment_lines:
|
||||
# Filter out status lines and clean up the description
|
||||
filtered_lines = []
|
||||
for line in comment_lines:
|
||||
# Skip status lines (they're handled separately)
|
||||
if "Status:" in line:
|
||||
continue
|
||||
# Keep empty lines to preserve paragraph structure
|
||||
if not line.strip():
|
||||
filtered_lines.append("")
|
||||
else:
|
||||
# Preserve indentation by removing only the /// prefix
|
||||
cleaned_line = line[3:] if line.startswith('///') else line
|
||||
filtered_lines.append(cleaned_line.rstrip())
|
||||
|
||||
# Join filtered lines, preserving original structure
|
||||
description = '\n'.join(filtered_lines).strip()
|
||||
|
||||
# Check for deprecated functions
|
||||
is_deprecated = any('deprecated' in line.lower() for line in comment_lines)
|
||||
|
||||
# Check for status
|
||||
status = "unstable" # default
|
||||
for line in comment_lines:
|
||||
if "Status: Stable" in line:
|
||||
status = "stable"
|
||||
break
|
||||
|
||||
return description, status, is_deprecated
|
||||
|
||||
return "no description", "unstable", False
|
||||
|
||||
|
||||
def _parse_route_path(self, route_path: str, function_name: str) -> RouteInfo:
|
||||
"""Parse route path to extract parameters"""
|
||||
path_params = []
|
||||
query_params = []
|
||||
|
||||
# Split path and query parts
|
||||
if '?' in route_path:
|
||||
path_part, query_part = route_path.split('?', 1)
|
||||
else:
|
||||
path_part = route_path
|
||||
query_part = ""
|
||||
|
||||
# Extract path parameters (e.g., <token_id>)
|
||||
path_param_pattern = r'<([^>]+)>'
|
||||
path_params = re.findall(path_param_pattern, path_part)
|
||||
|
||||
# Extract query parameters (e.g., <token_id>&<timestamp>)
|
||||
if query_part:
|
||||
query_param_pattern = r'<([^>]+)>'
|
||||
query_params = re.findall(query_param_pattern, query_part)
|
||||
|
||||
# Clean up the route path for display
|
||||
display_path = path_part
|
||||
|
||||
return RouteInfo(
|
||||
path=display_path,
|
||||
function_name=function_name,
|
||||
method="GET",
|
||||
path_params=path_params,
|
||||
query_params=query_params,
|
||||
parameters=path_params + query_params
|
||||
)
|
||||
|
||||
|
||||
class APIDocGenerator:
|
||||
"""Generate API documentation in markdown format"""
|
||||
|
||||
def __init__(self, output_dir: str = "apidoc"):
|
||||
self.output_dir = output_dir
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
def generate_docs(self, mounts: List[MountInfo], function_parser: FunctionSignatureParser):
|
||||
"""Generate documentation for all mounts"""
|
||||
# Generate index page
|
||||
self._generate_index(mounts)
|
||||
|
||||
# Generate individual mount pages
|
||||
for mount in mounts:
|
||||
self._generate_mount_page(mount, function_parser)
|
||||
|
||||
def _generate_index(self, mounts: List[MountInfo]):
|
||||
"""Generate the main index page"""
|
||||
index_content = """# Riften Labs Indexer API Documentation
|
||||
|
||||
This document provides API documentation for the Riften Labs Indexer service.
|
||||
|
||||
Note that endpoints marked at unsable are subject to change or may be removed.
|
||||
If you are using unstable endpoints, let us know in the telegram channel so that we can prioritize them.
|
||||
|
||||
## Available Endpoints
|
||||
|
||||
"""
|
||||
|
||||
for mount in mounts:
|
||||
mount_name = mount.path.strip('/').replace('/', '_') or 'root'
|
||||
mount_display = mount.path if mount.path else '/'
|
||||
index_content += f"- [{mount_display}]({mount_name}.md) - {len(mount.routes)} endpoints\n"
|
||||
|
||||
index_content += """
|
||||
|
||||
## Getting Started
|
||||
|
||||
All endpoints return JSON responses. Most endpoints support GET requests only.
|
||||
|
||||
### Authentication
|
||||
|
||||
Currently, no authentication is required for API access.
|
||||
|
||||
"""
|
||||
|
||||
with open(os.path.join(self.output_dir, 'index.md'), 'w') as f:
|
||||
f.write(index_content)
|
||||
|
||||
def _generate_mount_page(self, mount: MountInfo, function_parser: FunctionSignatureParser):
|
||||
"""Generate documentation for a specific mount point"""
|
||||
mount_name = mount.path.strip('/').replace('/', '_') or 'root'
|
||||
mount_display = mount.path if mount.path else '/'
|
||||
|
||||
content = f"""# {mount_display} Endpoints
|
||||
|
||||
Base URL: `{mount_display}`
|
||||
|
||||
## Available Endpoints
|
||||
|
||||
"""
|
||||
|
||||
# Get detailed route information
|
||||
detailed_routes = []
|
||||
for route in mount.routes:
|
||||
detailed_route = function_parser.get_function_signature(route.function_name)
|
||||
if detailed_route:
|
||||
detailed_routes.append(detailed_route)
|
||||
# Skip routes without #[get] attributes - they can't be documented properly
|
||||
|
||||
# Sort routes alphabetically by their path
|
||||
detailed_routes.sort(key=lambda x: x.path)
|
||||
|
||||
for route in detailed_routes:
|
||||
content += self._generate_route_doc(route)
|
||||
|
||||
# Add navigation
|
||||
content += f"""
|
||||
|
||||
---
|
||||
|
||||
[← Back to Index](index.md)
|
||||
"""
|
||||
|
||||
with open(os.path.join(self.output_dir, f'{mount_name}.md'), 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
def _generate_route_doc(self, route: RouteInfo) -> str:
|
||||
"""Generate documentation for a single route"""
|
||||
full_path = route.path if route.path.startswith('/') else f'/{route.path}'
|
||||
|
||||
# Build the complete route including query parameters
|
||||
complete_route = full_path
|
||||
if route.query_params:
|
||||
complete_route += "?" + "&".join(f"<{param}>" for param in route.query_params)
|
||||
|
||||
doc = f"""### `{complete_route}`
|
||||
|
||||
**Endpoint:** `{route.method} {full_path}`
|
||||
|
||||
**Status:** {route.status.title()}
|
||||
|
||||
"""
|
||||
|
||||
if route.description and route.description != "no description":
|
||||
doc += f"**Description:** {route.description}\n\n"
|
||||
|
||||
doc += "---\n\n"
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to generate API documentation"""
|
||||
# Configuration
|
||||
main_rs_path = "src/main.rs"
|
||||
src_dir = "src"
|
||||
output_dir = "apidoc"
|
||||
|
||||
print("🔍 Parsing routes from main.rs...")
|
||||
|
||||
# Parse mounts from main.rs
|
||||
route_parser = RustRouteParser(main_rs_path)
|
||||
mounts = route_parser.parse_mounts()
|
||||
|
||||
print(f"📁 Found {len(mounts)} mount points:")
|
||||
for mount in mounts:
|
||||
print(f" - {mount.path}: {len(mount.routes)} routes")
|
||||
|
||||
# Initialize function signature parser
|
||||
function_parser = FunctionSignatureParser(src_dir)
|
||||
|
||||
print("📝 Generating API documentation...")
|
||||
|
||||
# Generate documentation
|
||||
doc_generator = APIDocGenerator(output_dir)
|
||||
doc_generator.generate_docs(mounts, function_parser)
|
||||
|
||||
print(f"✅ API documentation generated in '{output_dir}/' directory")
|
||||
print("📄 Generated files:")
|
||||
|
||||
# List generated files
|
||||
for file in os.listdir(output_dir):
|
||||
if file.endswith('.md'):
|
||||
print(f" - {output_dir}/{file}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -36,6 +36,22 @@ impl PoolSnapshot {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,33 @@ use serde_json::Value;
|
|||
use crate::db::bcmr::get_well_known_bcmr;
|
||||
use crate::db::{bcmr::get_token_bcmr, DB};
|
||||
|
||||
/// Fetches BCMR data from on-chain registry
|
||||
/// Fetches BCMR data for token from on-chain registry.
|
||||
/// Status: Stable
|
||||
///
|
||||
/// - category: Token ID or symbol
|
||||
///
|
||||
/// **Response Example:**
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "description" : "Cauldron socks",
|
||||
/// "filemeta": {
|
||||
/// "actual_hash":"c705cc90a56ac7ef9a15ef90ebbc8ba7e60e4c622e5464d52d8baf7887949fcc",
|
||||
/// "expected_hash":"c705cc90a56ac7ef9a15ef90ebbc8ba7e60e4c622e5464d52d8baf7887949fcc"
|
||||
/// "source":"onchain"
|
||||
/// },
|
||||
/// "name":"Cauldron Socks",
|
||||
/// "token":{
|
||||
/// "category":"b79bfc8246b5fc4707e7c7dedcb6619ef1ab91f494a790c20b0f4c422ed95b92",
|
||||
/// "decimals":0,
|
||||
/// "symbol":"SOCK"
|
||||
/// },
|
||||
/// "uris":{
|
||||
/// "icon":"https://sock.cauldron.quest/sock.png",
|
||||
/// "web":"https://cauldron.quest"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[get("/token/<category>")]
|
||||
pub fn token_bcmr(category: Option<&str>, db: &State<DB>) -> Result<Json<Value>, Custom<String>> {
|
||||
let token_id_hex = category
|
||||
|
|
@ -33,7 +59,9 @@ pub fn token_bcmr(category: Option<&str>, db: &State<DB>) -> Result<Json<Value>,
|
|||
Ok(Json(json!(bcmr)))
|
||||
}
|
||||
|
||||
/// Fetches BCMR data for token from all registries
|
||||
/// Fetches BCMR data for token from all registries (including OTR).
|
||||
///
|
||||
/// Return format is same as `/token/<category>` route; except it returns an array of BCMR entries.
|
||||
#[get("/token/<category>/all")]
|
||||
pub fn token_bcmr_all(
|
||||
category: Option<&str>,
|
||||
|
|
|
|||
|
|
@ -216,6 +216,33 @@ pub fn candlesticks(
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
/// Fetch candlesticks in BCH satoshis for a token.
|
||||
///
|
||||
/// If an interval has no trades, it will be omitted from the result.
|
||||
///
|
||||
///
|
||||
/// - start: unix timestamp for period start (default 30 days)
|
||||
/// - end: unix timestamp for period end (default NOW)
|
||||
/// - stepsize: seconds per interval (default: 3600 seconds)
|
||||
///
|
||||
/// **Response Example:**
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "candlesticks": [
|
||||
/// {"close":64654136.35714286,
|
||||
/// "high":87959043.0,
|
||||
/// "low":58755326.0,
|
||||
/// "open":87959043.0,
|
||||
/// "time":1752522150,
|
||||
/// "transaction_count":4,
|
||||
/// "volume_sats":3170247594,
|
||||
/// "volume_tokens":43},
|
||||
/// ]
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
|
||||
#[get("/price/<token>/candlesticks?<start>&<end>&<stepsize>")]
|
||||
pub fn price_candlesticks(
|
||||
token: &str,
|
||||
|
|
|
|||
|
|
@ -69,6 +69,14 @@ fn db_contract_count_by_token(db: &Connection, token_id: &str) -> Result<Contrac
|
|||
})
|
||||
}
|
||||
|
||||
/// Get number of active and ended cauldron contracts.
|
||||
/// Status: Stable
|
||||
///
|
||||
/// **Response Example:**
|
||||
///
|
||||
/// ```json
|
||||
/// {"active":100,"ended":10}
|
||||
/// ```
|
||||
#[get("/contract/count")]
|
||||
pub fn contract_count_all(conn: &State<DB>) -> Result<Json<Value>, Custom<String>> {
|
||||
let db = conn
|
||||
|
|
@ -80,6 +88,15 @@ pub fn contract_count_all(conn: &State<DB>) -> Result<Json<Value>, Custom<String
|
|||
Ok(Json(json!(count)))
|
||||
}
|
||||
|
||||
/// Get number of active and ended cauldron contracts for a given token.
|
||||
/// Status: Stable
|
||||
///
|
||||
/// - token: Token ID or symbol
|
||||
///
|
||||
/// **Response Example:**
|
||||
/// ```json
|
||||
/// {"active":100,"ended":10}
|
||||
/// ```
|
||||
#[get("/contract/count/<token>")]
|
||||
pub fn contract_count_token(token: &str, conn: &State<DB>) -> Result<Json<Value>, Custom<String>> {
|
||||
let db = conn
|
||||
|
|
@ -91,6 +108,9 @@ pub fn contract_count_token(token: &str, conn: &State<DB>) -> Result<Json<Value>
|
|||
Ok(Json(json!(count)))
|
||||
}
|
||||
|
||||
/// Get volume for given token.
|
||||
/// Status: Deprecated
|
||||
/// (Needs to be split into interval rather than producing 3 fixed ones)
|
||||
#[get("/contract/volume?<end>")]
|
||||
pub fn contract_volume(
|
||||
end: Option<i64>,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,12 @@ use rocket::serde::json::Json;
|
|||
use rocket::{get, State};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
/// Get the closest oracle price for a given token and timestamp.
|
||||
///
|
||||
/// Status: Stable
|
||||
///
|
||||
/// - token_id: The 32 byte token ID
|
||||
/// - timestamp: Unix timestamp
|
||||
#[get("/delphi/closest?<token_id>&<timestamp>")]
|
||||
pub fn oracle_get_closest(
|
||||
token_id: Option<String>,
|
||||
|
|
@ -38,6 +44,7 @@ pub fn oracle_get_closest(
|
|||
})))
|
||||
}
|
||||
|
||||
/// Status: Deprecated
|
||||
#[get("/delphi/range?<token_id>&<start>&<end>")]
|
||||
pub fn oracle_get_range(
|
||||
token_id: Option<String>,
|
||||
|
|
@ -67,6 +74,10 @@ pub fn oracle_get_range(
|
|||
))
|
||||
}
|
||||
|
||||
/// Get historical oracle prices for a given token.
|
||||
///
|
||||
/// - token: The 32 byte token ID
|
||||
/// - start: Unix timestamp for start of period
|
||||
#[get("/delphi/<token>/history?<start>&<end>&<stepsize>")]
|
||||
pub fn oracle_get_history(
|
||||
token: &str,
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ fn pools_by_apy(connection: &Connection) -> Result<Vec<PoolYield>> {
|
|||
Ok(pools)
|
||||
}
|
||||
|
||||
/// Status: Deprecated
|
||||
#[get("/pool/list_by_apy")]
|
||||
pub fn list_pools_by_apy(conn: &State<DB>) -> Result<Json<Value>, Custom<String>> {
|
||||
let db = conn
|
||||
|
|
@ -207,6 +208,33 @@ impl PoolVisitor for ActivePoolList {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get list of active pools for given token and/or user.
|
||||
/// Either user or token ID must be provided.
|
||||
///
|
||||
/// - user: 20 byte hash of a users pkh
|
||||
/// - token: byte token ID.
|
||||
///
|
||||
/// Status: Stable
|
||||
///
|
||||
/// - token: Token ID or symbol
|
||||
/// - pkh: Public key hash
|
||||
///
|
||||
/// **Response Example:**
|
||||
/// ```json
|
||||
/// {
|
||||
/// "active": [
|
||||
/// {
|
||||
/// "owner_p2pkh_addr": "bitcoincash:zqmvqqsd6w08e4nvy8er0hzn6wzxvxj40u7tlk8wl3",
|
||||
/// "owner_pkh": "36c0020dd39e7cd66c21f237dc53d384661a557f",
|
||||
/// "sats": 776661580,
|
||||
/// "token_id": "b79bfc8246b5fc4707e7c7dedcb6619ef1ab91f494a790c20b0f4c422ed95b92",
|
||||
/// "tokens": 16,
|
||||
/// "tx_pos": 0,
|
||||
/// "txid": "94a933a0fa55093a0965eb867f1b9cac2bb07488ced4825bc31f86c9371f76aa"
|
||||
/// }
|
||||
/// ]
|
||||
/// }
|
||||
/// ```
|
||||
#[get("/pool/active?<token>&<pkh>")]
|
||||
pub fn list_active_pools(
|
||||
token: Option<&str>,
|
||||
|
|
|
|||
|
|
@ -237,6 +237,18 @@ fn price_at_or_before(
|
|||
Ok((latest_timestamp, overall_price_f64))
|
||||
}
|
||||
|
||||
/// Get the price of a given token at a specific timestamp.
|
||||
///
|
||||
/// - token: The 32 byte token ID
|
||||
/// - timestamp: Unix timestamp
|
||||
///
|
||||
/// **Response Example:**
|
||||
/// ```json
|
||||
/// {
|
||||
/// "price":34493809.347826086,
|
||||
/// "timestamp":1709468902
|
||||
/// }
|
||||
/// ```
|
||||
#[get("/price/<token>/at/<timestamp>")]
|
||||
pub fn price_at(
|
||||
token: &str,
|
||||
|
|
@ -277,6 +289,18 @@ pub fn price_at(
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the current price of a given token in satoshis.
|
||||
///
|
||||
/// Status: Stable
|
||||
///
|
||||
/// - token: The 32 byte token ID
|
||||
///
|
||||
/// **Response Example:**
|
||||
///
|
||||
/// ```json
|
||||
/// {"price":1000.00}
|
||||
/// ```
|
||||
///
|
||||
#[get("/price/<token>/current")]
|
||||
pub fn price_current(token: &str, conn: &State<DB>) -> Result<Json<Value>, Custom<String>> {
|
||||
let db = conn
|
||||
|
|
@ -292,6 +316,41 @@ pub fn price_current(token: &str, conn: &State<DB>) -> Result<Json<Value>, Custo
|
|||
})))
|
||||
}
|
||||
|
||||
/// Fetch historical price in satoshis for a given token.
|
||||
///
|
||||
/// Note: If an interval has no trades, it will be omitted from the response.
|
||||
///
|
||||
/// **Response Example:**
|
||||
/// ```json
|
||||
/// {
|
||||
/// "history": [
|
||||
/// {
|
||||
/// "avg": 33829844.78947368,
|
||||
/// "max": 33829844.78947368,
|
||||
/// "min": 33829844.78947368,
|
||||
/// "time": 1709470824
|
||||
/// },
|
||||
/// {
|
||||
/// "avg": 37699099,
|
||||
/// "max": 37699099,
|
||||
/// "min": 37699099,
|
||||
/// "time": 1709751624
|
||||
/// },
|
||||
/// {
|
||||
/// "avg": 42271799.176470585,
|
||||
/// "max": 42271799.176470585,
|
||||
/// "min": 42271799.176470585,
|
||||
/// "time": 1709755224
|
||||
/// },
|
||||
/// {
|
||||
/// "avg": 63517742.53043478,
|
||||
/// "max": 101130081.9090909,
|
||||
/// "min": 47729344.9375,
|
||||
/// "time": 1710129624
|
||||
/// },
|
||||
/// ]
|
||||
/// }
|
||||
/// ```
|
||||
#[get("/price/<token>/history?<start>&<end>&<stepsize>")]
|
||||
pub fn price_history(
|
||||
token: &str,
|
||||
|
|
|
|||
|
|
@ -130,8 +130,9 @@ pub fn get_token_tvl(
|
|||
Ok((visitor.sats, visitor.tokens))
|
||||
}
|
||||
|
||||
// Deprecated; use valuelocked with optional parameters
|
||||
// used by defilama; fix adapter first
|
||||
/// Status: Deprecated
|
||||
/// use valuelocked with optional parameters
|
||||
/// used by defilama; fix adapter before removing
|
||||
#[get("/tvl/<time>")]
|
||||
pub fn deprecated_tvl(time: usize, conn: &State<DB>) -> Result<Json<Vec<Value>>, Custom<String>> {
|
||||
let db = conn
|
||||
|
|
@ -156,6 +157,18 @@ pub fn deprecated_tvl(time: usize, conn: &State<DB>) -> Result<Json<Vec<Value>>,
|
|||
Ok(Json(result))
|
||||
}
|
||||
|
||||
/// Gives total satoshis locked for all tokens.
|
||||
/// Status: Stable
|
||||
///
|
||||
/// - time: Unix timestamp (optional)
|
||||
///
|
||||
/// **Response Example:**
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "satoshis": 1459676788
|
||||
/// }
|
||||
/// ```
|
||||
#[get("/valuelocked?<time>")]
|
||||
pub fn valuelocked_all(
|
||||
time: Option<usize>,
|
||||
|
|
@ -176,6 +189,21 @@ pub fn valuelocked_all(
|
|||
})))
|
||||
}
|
||||
|
||||
/// Gives total value locked for a single token.
|
||||
/// Status: Stable
|
||||
///
|
||||
/// - token: Token identifier / category.
|
||||
/// - time: Unix timestamp (optional)
|
||||
///
|
||||
/// **Response Example:**
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "satoshis": 1459676788,
|
||||
/// "token_amount": 19,
|
||||
/// "token_id": "b79bfc8246b5fc4707e7c7dedcb6619ef1ab91f494a790c20b0f4c422ed95b92"
|
||||
/// }
|
||||
/// ```
|
||||
#[get("/valuelocked/<token>?<time>")]
|
||||
pub fn valuelocked_token(
|
||||
token: &str,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue