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}