1use 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 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 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 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}