enso_api/
lib.rs

1//! Rust client for the Enso Finance DeFi aggregator API
2//!
3//! Enso Finance is a DeFi infrastructure platform that provides routing and
4//! execution for complex DeFi operations. It supports bundling multiple DeFi
5//! actions into single transactions and provides smart routing across protocols.
6//!
7//! # Features
8//!
9//! - Multi-action bundling (swap + deposit + stake in one tx)
10//! - Cross-protocol routing
11//! - Position management (enter/exit strategies)
12//! - Support for lending, DEXs, yield farming
13//! - Gas-efficient batched transactions
14//!
15//! # Quick Start
16//!
17//! ```no_run
18//! use enso_api::{Client, Chain, RouteRequest};
19//!
20//! #[tokio::main]
21//! async fn main() -> Result<(), enso_api::Error> {
22//!     let client = Client::with_api_key("your-api-key")?;
23//!
24//!     // Get route for swapping tokens
25//!     let request = RouteRequest::new(
26//!         Chain::Ethereum.chain_id(),
27//!         "0xYourAddress",
28//!         "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", // ETH
29//!         "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
30//!         "1000000000000000000", // 1 ETH
31//!         100, // 1% slippage
32//!     );
33//!
34//!     let response = client.get_route(&request).await?;
35//!     println!("Output: {}", response.amount_out);
36//!
37//!     Ok(())
38//! }
39//! ```
40
41pub mod error;
42pub mod types;
43
44pub use error::{Error, Result};
45pub use types::{
46    ApiErrorResponse, BundleAction, BundleRequest, BundleResponse, Chain, RouteRequest,
47    RouteResponse, RouteStep, RoutingStrategy, TokenBalance, TokenPrice, TransactionData,
48};
49
50// Re-export common utilities
51pub use yldfi_common::api::{ApiConfig, BaseClient};
52pub use yldfi_common::{with_retry, with_simple_retry, RetryConfig, RetryError, RetryableError};
53
54/// Default base URL for the Enso Finance API
55pub const DEFAULT_BASE_URL: &str = "https://api.enso.finance";
56
57/// Native token address (used for ETH and other native tokens)
58pub const NATIVE_TOKEN_ADDRESS: &str = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
59
60/// Configuration for the Enso Finance API client
61///
62/// This is a type alias for `ApiConfig` with Enso-specific defaults.
63pub type Config = ApiConfig;
64
65/// Create a default Enso config
66#[must_use]
67pub fn default_config() -> Config {
68    ApiConfig::new(DEFAULT_BASE_URL)
69}
70
71/// Create a config with an API key
72#[must_use]
73pub fn config_with_api_key(api_key: impl Into<String>) -> Config {
74    ApiConfig::with_api_key(DEFAULT_BASE_URL, api_key)
75}
76
77/// Client for interacting with the Enso Finance API
78#[derive(Debug, Clone)]
79pub struct Client {
80    base: BaseClient,
81}
82
83impl Client {
84    /// Create a new client with default configuration
85    pub fn new() -> Result<Self> {
86        Self::with_config(default_config())
87    }
88
89    /// Create a new client with an API key
90    pub fn with_api_key(api_key: impl Into<String>) -> Result<Self> {
91        Self::with_config(config_with_api_key(api_key))
92    }
93
94    /// Create a new client with custom configuration
95    pub fn with_config(config: Config) -> Result<Self> {
96        let base = BaseClient::new(config)?;
97        Ok(Self { base })
98    }
99
100    /// Get the underlying base client
101    #[must_use]
102    pub fn base(&self) -> &BaseClient {
103        &self.base
104    }
105
106    /// Get the configuration
107    #[must_use]
108    pub fn config(&self) -> &Config {
109        self.base.config()
110    }
111
112    /// Get optimal route for token swap
113    ///
114    /// Returns the best route for swapping tokens with transaction data.
115    ///
116    /// # Arguments
117    ///
118    /// * `request` - Route request parameters
119    ///
120    /// # Example
121    ///
122    /// ```no_run
123    /// use enso_api::{Client, Chain, RouteRequest};
124    ///
125    /// #[tokio::main]
126    /// async fn main() -> Result<(), enso_api::Error> {
127    ///     let client = Client::with_api_key("your-key")?;
128    ///
129    ///     let request = RouteRequest::new(
130    ///         1, // Ethereum
131    ///         "0xYourAddress",
132    ///         "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
133    ///         "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
134    ///         "1000000000000000000",
135    ///         100,
136    ///     );
137    ///
138    ///     let route = client.get_route(&request).await?;
139    ///     println!("Output: {}", route.amount_out);
140    ///
141    ///     Ok(())
142    /// }
143    /// ```
144    pub async fn get_route(&self, request: &RouteRequest) -> Result<RouteResponse> {
145        self.base
146            .post_json("/api/v1/shortcuts/route", request)
147            .await
148    }
149
150    /// Bundle multiple DeFi actions into one transaction
151    ///
152    /// Allows combining multiple actions (swap, deposit, stake) into a single transaction.
153    ///
154    /// # Arguments
155    ///
156    /// * `request` - Bundle request with actions
157    ///
158    /// # Example
159    ///
160    /// ```no_run
161    /// use enso_api::{Client, BundleRequest, BundleAction};
162    ///
163    /// #[tokio::main]
164    /// async fn main() -> Result<(), enso_api::Error> {
165    ///     let client = Client::with_api_key("your-key")?;
166    ///
167    ///     let actions = vec![
168    ///         BundleAction::swap("0xTokenA", "0xTokenB", "1000000"),
169    ///     ];
170    ///
171    ///     let request = BundleRequest::new(1, "0xYourAddress", actions);
172    ///     let bundle = client.bundle(&request).await?;
173    ///
174    ///     println!("Send to: {}", bundle.tx.to);
175    ///
176    ///     Ok(())
177    /// }
178    /// ```
179    pub async fn bundle(&self, request: &BundleRequest) -> Result<BundleResponse> {
180        self.base
181            .post_json("/api/v1/shortcuts/bundle", request)
182            .await
183    }
184
185    /// Get token price
186    ///
187    /// # Arguments
188    ///
189    /// * `chain_id` - Chain ID
190    /// * `token` - Token address
191    pub async fn get_token_price(&self, chain_id: u64, token: &str) -> Result<TokenPrice> {
192        let path = format!("/api/v1/prices/{}/{}", chain_id, token);
193        self.base
194            .get::<TokenPrice, _>(&path, &[] as &[(&str, &str)])
195            .await
196    }
197
198    /// Get token balances for address
199    ///
200    /// # Arguments
201    ///
202    /// * `chain_id` - Chain ID
203    /// * `address` - Wallet address
204    pub async fn get_balances(&self, chain_id: u64, address: &str) -> Result<Vec<TokenBalance>> {
205        let path = format!("/api/v1/balances/{}/{}", chain_id, address);
206        self.base
207            .get::<Vec<TokenBalance>, _>(&path, &[] as &[(&str, &str)])
208            .await
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    #[test]
217    fn test_client_creation() {
218        let client = Client::new();
219        assert!(client.is_ok());
220    }
221
222    #[test]
223    fn test_client_with_api_key() {
224        let client = Client::with_api_key("test-key");
225        assert!(client.is_ok());
226        let client = client.unwrap();
227        assert_eq!(client.config().get_api_key(), Some("test-key"));
228    }
229
230    #[test]
231    fn test_default_config() {
232        let config = default_config();
233        assert_eq!(config.base_url, DEFAULT_BASE_URL);
234    }
235
236    #[test]
237    fn test_config_with_api_key() {
238        let config = config_with_api_key("my-key");
239        assert_eq!(config.get_api_key(), Some("my-key"));
240        assert_eq!(config.base_url, DEFAULT_BASE_URL);
241    }
242}