madeonsol/lib.rs
1//! # MadeOnSol — official Rust SDK
2//!
3//! Solana KOL wallet tracking, Pump.fun deployer intelligence, alpha-wallet scoring,
4//! and an all-DEX trade firehose.
5//!
6//! ## Get an API key
7//!
8//! Free tier: **200 requests/day, no credit card** at <https://madeonsol.com/pricing>.
9//! Paid tiers (PRO $49/mo, ULTRA $149/mo) unlock higher rate limits, sub-hour windows,
10//! WebSocket streaming, webhooks, and the all-DEX firehose.
11//!
12//! All keys start with `msk_`.
13//!
14//! ## Quick start
15//!
16//! ```no_run
17//! use madeonsol::{MadeOnSol, types::KolFeedParams};
18//!
19//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
20//! let api_key = std::env::var("MADEONSOL_API_KEY")?;
21//! let client = MadeOnSol::new(api_key)?;
22//!
23//! let feed = client
24//! .kol
25//! .feed(&KolFeedParams { limit: Some(10), ..Default::default() })
26//! .await?;
27//!
28//! for trade in feed.trades {
29//! println!("{:?} bought {:?}", trade.kol_name, trade.token_symbol);
30//! }
31//! # Ok(())
32//! # }
33//! ```
34//!
35//! ## Namespaces
36//!
37//! - [`MadeOnSol::kol`] — KOL feed, leaderboard, coordination, PnL, trending tokens, alerts
38//! - [`MadeOnSol::deployer`] — Pump.fun deployer leaderboard, alerts, trajectory
39//! - [`MadeOnSol::alpha`] — alpha-wallet leaderboard, profiles, cap tables, buyer quality
40//! - [`MadeOnSol::wallet_tracker`] — track arbitrary Solana wallets
41//! - [`MadeOnSol::coordination_alerts`] — push alerts on coordinated buying (PRO/ULTRA)
42//! - [`MadeOnSol::tools`] — Solana tool directory search
43//! - [`MadeOnSol::stream`] — WebSocket streaming token issuance
44//! - [`MadeOnSol::webhooks`] — webhook CRUD (PRO/ULTRA)
45//!
46//! Full API reference: <https://madeonsol.com/api-docs>
47
48#![warn(missing_debug_implementations)]
49#![warn(rust_2018_idioms)]
50
51mod client;
52pub mod api;
53pub mod error;
54pub mod types;
55
56use std::sync::Arc;
57
58use crate::api::{
59 alpha::Alpha, coordination_alerts::CoordinationAlerts, deployer::Deployer,
60 first_touch_subscriptions::FirstTouchSubscriptions, kol::Kol, me::Me, stream::Stream, token::Token,
61 tools::Tools, wallet_tracker::WalletTracker, webhooks::Webhooks,
62};
63use crate::client::HttpCore;
64use crate::error::{MadeOnSolError, Result};
65
66pub use crate::error::MadeOnSolError as Error;
67
68/// MadeOnSol API client.
69///
70/// Construct with [`MadeOnSol::new`] and a `msk_…` API key, then access the
71/// namespaced sub-clients ([`kol`](Self::kol), [`deployer`](Self::deployer), etc.).
72///
73/// Cheap to clone — internal HTTP state is reference-counted.
74///
75/// # Example
76///
77/// ```no_run
78/// use madeonsol::MadeOnSol;
79///
80/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
81/// let client = MadeOnSol::new(std::env::var("MADEONSOL_API_KEY")?)?;
82/// let stats = client.deployer.stats().await?;
83/// println!("{} elite deployers tracked", stats.elite_count);
84/// # Ok(())
85/// # }
86/// ```
87#[derive(Debug, Clone)]
88pub struct MadeOnSol {
89 /// KOL wallet tracking endpoints.
90 pub kol: Kol,
91 /// Pump.fun deployer intelligence endpoints.
92 pub deployer: Deployer,
93 /// Alpha wallet intelligence: leaderboard, profiles, cap tables, buyer quality.
94 pub alpha: Alpha,
95 /// Token intelligence — comprehensive per-mint snapshot + batch lookups.
96 pub token: Token,
97 /// Account self-inspection — tier, quota, feature usage (v0.8).
98 pub me: Me,
99 /// Wallet tracker: watchlist CRUD, trades, summary.
100 pub wallet_tracker: WalletTracker,
101 /// Coordination alert rules CRUD (v1.1) — PRO/ULTRA.
102 pub coordination_alerts: CoordinationAlerts,
103 /// First-touch webhook subscriptions CRUD — ULTRA only. Use `kol.first_touches()` for read-only queries.
104 pub first_touch_subscriptions: FirstTouchSubscriptions,
105 /// Solana tool directory search.
106 pub tools: Tools,
107 /// WebSocket streaming token issuance.
108 pub stream: Stream,
109 /// Webhook management (PRO/ULTRA).
110 pub webhooks: Webhooks,
111}
112
113impl MadeOnSol {
114 /// Construct a new client.
115 ///
116 /// `api_key` must start with `msk_`. Get a free key (200 req/day, no card)
117 /// at <https://madeonsol.com/pricing>.
118 ///
119 /// # Errors
120 ///
121 /// Returns [`MadeOnSolError::MissingApiKey`] if the key is empty or missing the
122 /// `msk_` prefix. The error message includes the signup URL so end users know
123 /// where to go.
124 pub fn new(api_key: impl Into<String>) -> Result<Self> {
125 let api_key = api_key.into();
126 if !api_key.starts_with("msk_") {
127 // Print to stderr too — a bare Err can be swallowed and the user
128 // never sees the link to /pricing.
129 eprintln!(
130 "\n[madeonsol] Missing or invalid API key.\n\
131 → Get a free key (200 req/day, no card) at https://madeonsol.com/pricing\n\
132 → Then: madeonsol::MadeOnSol::new(std::env::var(\"MADEONSOL_API_KEY\")?)?\n"
133 );
134 return Err(MadeOnSolError::MissingApiKey);
135 }
136
137 let core = Arc::new(HttpCore::new(api_key));
138 Ok(Self {
139 kol: Kol { core: Arc::clone(&core) },
140 deployer: Deployer { core: Arc::clone(&core) },
141 alpha: Alpha { core: Arc::clone(&core) },
142 token: Token { core: Arc::clone(&core) },
143 me: Me { core: Arc::clone(&core) },
144 wallet_tracker: WalletTracker { core: Arc::clone(&core) },
145 coordination_alerts: CoordinationAlerts { core: Arc::clone(&core) },
146 first_touch_subscriptions: FirstTouchSubscriptions { core: Arc::clone(&core) },
147 tools: Tools { core: Arc::clone(&core) },
148 stream: Stream { core: Arc::clone(&core) },
149 webhooks: Webhooks { core },
150 })
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn rejects_missing_api_key() {
160 let err = MadeOnSol::new("").unwrap_err();
161 assert!(matches!(err, MadeOnSolError::MissingApiKey));
162 }
163
164 #[test]
165 fn rejects_wrong_prefix() {
166 let err = MadeOnSol::new("sk_live_abc").unwrap_err();
167 assert!(matches!(err, MadeOnSolError::MissingApiKey));
168 }
169
170 #[test]
171 fn accepts_valid_prefix() {
172 let client = MadeOnSol::new("msk_test_abcdef").unwrap();
173 // Smoke test — namespaces exist and the client clones cheaply.
174 let _cloned = client.clone();
175 }
176}