Skip to main content

noesis_api/
lib.rs

1//! Official Rust SDK for the [Noesis](https://noesisapi.dev) on-chain intelligence API.
2//!
3//! # Example
4//!
5//! ```no_run
6//! use noesis_api::Noesis;
7//!
8//! #[tokio::main]
9//! async fn main() -> Result<(), noesis::Error> {
10//!     let client = Noesis::new("se_...");
11//!     let preview = client.token_preview("<MINT>").await?;
12//!     println!("{:?}", preview);
13//!     Ok(())
14//! }
15//! ```
16
17use serde_json::Value;
18use thiserror::Error;
19
20const DEFAULT_BASE_URL: &str = "https://noesisapi.dev";
21
22#[derive(Debug, Error)]
23pub enum Error {
24    #[error("HTTP error: {0}")]
25    Http(#[from] reqwest::Error),
26    #[error("Noesis API error {status}: {message}")]
27    Api { status: u16, message: String, details: Option<Value> },
28    #[error("JSON error: {0}")]
29    Json(#[from] serde_json::Error),
30}
31
32pub type Result<T> = std::result::Result<T, Error>;
33
34#[derive(Clone)]
35pub struct Noesis {
36    http: reqwest::Client,
37    base_url: String,
38    api_key: String,
39}
40
41impl Noesis {
42    pub fn new(api_key: impl Into<String>) -> Self {
43        Self::with_base_url(api_key, DEFAULT_BASE_URL)
44    }
45
46    pub fn with_base_url(api_key: impl Into<String>, base_url: impl Into<String>) -> Self {
47        Self {
48            http: reqwest::Client::new(),
49            base_url: base_url.into().trim_end_matches('/').to_string(),
50            api_key: api_key.into(),
51        }
52    }
53
54    async fn get(&self, path: &str, query: &[(&str, String)]) -> Result<Value> {
55        let url = format!("{}/api/v1{}", self.base_url, path);
56        let res = self.http.get(&url)
57            .header("X-API-Key", &self.api_key)
58            .query(query)
59            .send()
60            .await?;
61        Self::handle(res).await
62    }
63
64    async fn post(&self, path: &str, body: &Value) -> Result<Value> {
65        let url = format!("{}/api/v1{}", self.base_url, path);
66        let res = self.http.post(&url)
67            .header("X-API-Key", &self.api_key)
68            .json(body)
69            .send()
70            .await?;
71        Self::handle(res).await
72    }
73
74    async fn handle(res: reqwest::Response) -> Result<Value> {
75        let status = res.status();
76        if status.is_success() {
77            Ok(res.json().await?)
78        } else {
79            let details = res.json::<Value>().await.ok();
80            Err(Error::Api {
81                status: status.as_u16(),
82                message: format!("Noesis API error {}", status.as_u16()),
83                details,
84            })
85        }
86    }
87
88    // --- Token ---
89
90    pub async fn token_preview(&self, mint: &str) -> Result<Value> {
91        self.get(&format!("/token/{mint}/preview"), &[("chain", "sol".into())]).await
92    }
93
94    pub async fn token_scan(&self, mint: &str) -> Result<Value> {
95        self.get(&format!("/token/{mint}/scan"), &[("chain", "sol".into())]).await
96    }
97
98    pub async fn token_top_holders(&self, mint: &str) -> Result<Value> {
99        self.get(&format!("/token/{mint}/top-holders"), &[]).await
100    }
101
102    pub async fn token_bundles(&self, mint: &str) -> Result<Value> {
103        self.get(&format!("/token/{mint}/bundles"), &[]).await
104    }
105
106    pub async fn token_fresh_wallets(&self, mint: &str) -> Result<Value> {
107        self.get(&format!("/token/{mint}/fresh-wallets"), &[]).await
108    }
109
110    pub async fn token_dev_profile(&self, mint: &str) -> Result<Value> {
111        self.get(&format!("/token/{mint}/dev-profile"), &[]).await
112    }
113
114    pub async fn token_best_traders(&self, mint: &str) -> Result<Value> {
115        self.get(&format!("/token/{mint}/best-traders"), &[]).await
116    }
117
118    pub async fn token_early_buyers(&self, mint: &str, hours: u32) -> Result<Value> {
119        self.get(&format!("/token/{mint}/early-buyers"), &[("hours", hours.to_string())]).await
120    }
121
122    // --- Wallet ---
123
124    pub async fn wallet_profile(&self, addr: &str) -> Result<Value> {
125        self.get(&format!("/wallet/{addr}"), &[]).await
126    }
127
128    pub async fn wallet_history(&self, addr: &str) -> Result<Value> {
129        self.get(&format!("/wallet/{addr}/history"), &[]).await
130    }
131
132    pub async fn wallet_connections(&self, addr: &str) -> Result<Value> {
133        self.get(&format!("/wallet/{addr}/connections"), &[]).await
134    }
135
136    pub async fn wallets_batch_identity(&self, addresses: &[String]) -> Result<Value> {
137        self.post("/wallets/batch-identity", &serde_json::json!({ "addresses": addresses })).await
138    }
139
140    pub async fn cross_holders(&self, tokens: &[String]) -> Result<Value> {
141        self.post("/tokens/cross-holders", &serde_json::json!({ "tokens": tokens })).await
142    }
143
144    pub async fn cross_traders(&self, tokens: &[String]) -> Result<Value> {
145        self.post("/tokens/cross-traders", &serde_json::json!({ "tokens": tokens })).await
146    }
147
148    // --- Chain ---
149
150    pub async fn chain_status(&self) -> Result<Value> {
151        self.get("/chain/status", &[]).await
152    }
153
154    pub async fn chain_fees(&self) -> Result<Value> {
155        self.get("/chain/fees", &[]).await
156    }
157
158    pub async fn account(&self, addr: &str) -> Result<Value> {
159        self.get(&format!("/account/{addr}"), &[]).await
160    }
161}