odos_sdk/
lib.rs

1//! # Odos SDK
2//!
3//! A production-ready Rust SDK for the Odos protocol - a decentralized exchange aggregator
4//! that provides optimal routing for token swaps across multiple EVM chains.
5//!
6//! ## Features
7//!
8//! - **Multi-chain Support**: 16+ EVM chains including Ethereum, Arbitrum, Optimism, Polygon, Base, etc.
9//! - **Type-safe**: Leverages Rust's type system with Alloy primitives for addresses, chain IDs, and amounts
10//! - **Production-ready**: Built-in retry logic, circuit breakers, timeouts, and error handling
11//! - **Builder Pattern**: Ergonomic API using the `bon` crate for request building
12//! - **Comprehensive Error Handling**: Detailed error types for different failure scenarios
13//!
14//! ## Quick Start
15//!
16//! ```rust,no_run
17//! use odos_sdk::*;
18//! use alloy_primitives::{Address, U256};
19//! use std::str::FromStr;
20//!
21//! # async fn example() -> Result<()> {
22//! // Create a client
23//! let client = OdosSorV2::new()?;
24//!
25//! // Build a quote request
26//! let quote_request = QuoteRequest::builder()
27//!     .chain_id(1) // Ethereum mainnet
28//!     .input_tokens(vec![(
29//!         Address::from_str("0xA0b86a33E6441d35a6b083d5b02a8e3F6CE21a2E")?, // WETH
30//!         U256::from(1000000000000000000u64) // 1 ETH
31//!     ).into()])
32//!     .output_tokens(vec![(
33//!         Address::from_str("0xA0b86a33E6441d35a6b083d5b02a8e3F6CE21a2E")?, // USDC
34//!         1
35//!     ).into()])
36//!     .slippage_limit_percent(1.0)
37//!     .user_addr("0x742d35Cc6634C0532925a3b8D35f3e7a5edD29c0".to_string())
38//!     .compact(false)
39//!     .simple(false)
40//!     .referral_code(0)
41//!     .disable_rfqs(false)
42//!     .build();
43//!
44//! // Get a quote
45//! let quote = client.get_swap_quote(&quote_request).await?;
46//!
47//! // Build transaction data
48//! let swap_context = SwapContext::builder()
49//!     .chain(alloy_chains::NamedChain::Mainnet)
50//!     .router_address(alloy_chains::NamedChain::Mainnet.v2_router_address()?)
51//!     .signer_address(Address::from_str("0x742d35Cc6634C0532925a3b8D35f3e7a5edD29c0")?)
52//!     .output_recipient(Address::from_str("0x742d35Cc6634C0532925a3b8D35f3e7a5edD29c0")?)
53//!     .token_address(Address::from_str("0xA0b86a33E6441d35a6b083d5b02a8e3F6CE21a2E")?)
54//!     .token_amount(U256::from(1000000000000000000u64))
55//!     .path_id(quote.path_id().to_string())
56//!     .build();
57//!
58//! let transaction = client.build_base_transaction(&swap_context).await?;
59//! # Ok(())
60//! # }
61//! ```
62//!
63//! ## Configuration
64//!
65//! The SDK supports extensive configuration for production use:
66//!
67//! ```rust,no_run
68//! use odos_sdk::*;
69//! use std::time::Duration;
70//!
71//! # fn example() -> Result<()> {
72//! // Full configuration
73//! let config = ClientConfig {
74//!     timeout: Duration::from_secs(30),
75//!     connect_timeout: Duration::from_secs(10),
76//!     retry_config: RetryConfig {
77//!         max_retries: 3,
78//!         initial_backoff_ms: 100,
79//!         retry_server_errors: true,
80//!         retry_predicate: None,
81//!     },
82//!     max_connections: 20,
83//!     pool_idle_timeout: Duration::from_secs(90),
84//! };
85//! let client = OdosSorV2::with_config(config)?;
86//!
87//! // Or use convenience constructors
88//! let client = OdosSorV2::with_retry_config(RetryConfig::conservative())?;
89//! # Ok(())
90//! # }
91//! ```
92//!
93//! ## Error Handling
94//!
95//! The SDK provides comprehensive error types with strongly-typed error codes:
96//!
97//! ```rust,no_run
98//! use odos_sdk::*;
99//!
100//! # async fn example() {
101//! # let client = OdosSorV2::new().unwrap();
102//! # let quote_request = QuoteRequest::builder().chain_id(1).input_tokens(vec![]).output_tokens(vec![]).slippage_limit_percent(1.0).user_addr("test".to_string()).compact(false).simple(false).referral_code(0).disable_rfqs(false).build();
103//! match client.get_swap_quote(&quote_request).await {
104//!     Ok(quote) => {
105//!         // Handle successful quote
106//!         println!("Got quote with path ID: {}", quote.path_id());
107//!     }
108//!     Err(err) => {
109//!         // Check for specific error codes
110//!         if let Some(code) = err.error_code() {
111//!             if code.is_invalid_chain_id() {
112//!                 eprintln!("Invalid chain ID - check configuration");
113//!             } else if code.is_no_viable_path() {
114//!                 eprintln!("No routing path found");
115//!             } else if code.is_timeout() {
116//!                 eprintln!("Service timeout: {}", code);
117//!             }
118//!         }
119//!
120//!         // Log trace ID for support
121//!         if let Some(trace_id) = err.trace_id() {
122//!             eprintln!("Trace ID: {}", trace_id);
123//!         }
124//!
125//!         // Handle by error type
126//!         match err {
127//!             OdosError::Api { status, message, .. } => {
128//!                 eprintln!("API error {}: {}", status, message);
129//!             }
130//!             OdosError::Timeout(msg) => {
131//!                 eprintln!("Request timed out: {}", msg);
132//!             }
133//!             OdosError::RateLimit { message, retry_after } => {
134//!                 if let Some(duration) = retry_after {
135//!                     eprintln!("Rate limited: {}. Retry after {} seconds", message, duration.as_secs());
136//!                 } else {
137//!                     eprintln!("Rate limited: {}", message);
138//!                 }
139//!             }
140//!             _ => eprintln!("Error: {}", err),
141//!         }
142//!     }
143//! }
144//! # }
145//! ```
146//!
147//! ### Strongly-Typed Error Codes
148//!
149//! The SDK provides error codes matching the [Odos API documentation](https://docs.odos.xyz/build/api_errors):
150//!
151//! - **General (1XXX)**: `ApiError`
152//! - **Algo/Quote (2XXX)**: `NoViablePath`, `AlgoTimeout`, `AlgoInternal`
153//! - **Internal Service (3XXX)**: `TxnAssemblyTimeout`, `GasUnavailable`
154//! - **Validation (4XXX)**: `InvalidChainId`, `BlockedUserAddr`, `InvalidTokenAmount`
155//! - **Internal (5XXX)**: `InternalError`, `SwapUnavailable`
156//!
157//! ```rust,no_run
158//! use odos_sdk::{OdosError, error_code::OdosErrorCode};
159//!
160//! # fn handle_error(error: OdosError) {
161//! if let Some(code) = error.error_code() {
162//!     // Check categories
163//!     if code.is_validation_error() {
164//!         println!("Validation error - check request parameters");
165//!     }
166//!
167//!     // Check retryability
168//!     if code.is_retryable() {
169//!         println!("Error can be retried: {}", code);
170//!     }
171//! }
172//! # }
173//! ```
174//!
175//! ## Rate Limiting
176//!
177//! The Odos API enforces rate limits to ensure fair usage. The SDK handles rate limits intelligently:
178//!
179//! - **HTTP 429 responses** are detected and classified as [`OdosError::RateLimit`]
180//! - Rate limit errors are **NOT retried** (return immediately with `Retry-After` header)
181//! - The SDK **captures `Retry-After` headers** for application-level handling
182//! - Applications should handle rate limits globally with proper backoff coordination
183//!
184//! ### Best Practices for Avoiding Rate Limits
185//!
186//! 1. **Share a single client** across your application instead of creating new clients per request
187//! 2. **Implement application-level rate limiting** if making many concurrent requests
188//! 3. **Handle rate limit errors gracefully** and back off at the application level if needed
189//!
190//! ### Example: Handling Rate Limits
191//!
192//! ```rust,no_run
193//! use odos_sdk::*;
194//! use alloy_primitives::{Address, U256};
195//! use std::time::Duration;
196//!
197//! # async fn example() -> Result<()> {
198//! # let client = OdosSorV2::new()?;
199//! # let quote_request = QuoteRequest::builder()
200//! #     .chain_id(1)
201//! #     .input_tokens(vec![])
202//! #     .output_tokens(vec![])
203//! #     .slippage_limit_percent(1.0)
204//! #     .user_addr("test".to_string())
205//! #     .compact(false)
206//! #     .simple(false)
207//! #     .referral_code(0)
208//! #     .disable_rfqs(false)
209//! #     .build();
210//! match client.get_swap_quote(&quote_request).await {
211//!     Ok(quote) => {
212//!         println!("Got quote: {}", quote.path_id());
213//!     }
214//!     Err(e) if e.is_rate_limit() => {
215//!         // Rate limit exceeded even after SDK retries
216//!         // Consider backing off at application level
217//!         eprintln!("Rate limited - waiting before retry");
218//!         tokio::time::sleep(Duration::from_secs(5)).await;
219//!         // Retry or handle accordingly
220//!     }
221//!     Err(e) => {
222//!         eprintln!("Error: {}", e);
223//!     }
224//! }
225//! # Ok(())
226//! # }
227//! ```
228//!
229//! ### Configuring Retry Behavior
230//!
231//! You can customize retry behavior for your use case:
232//!
233//! ```rust,no_run
234//! use odos_sdk::*;
235//!
236//! # fn example() -> Result<()> {
237//! // Conservative: only retry network errors
238//! let client = OdosSorV2::with_retry_config(RetryConfig::conservative())?;
239//!
240//! // No retries: handle all errors at application level
241//! let client = OdosSorV2::with_retry_config(RetryConfig::no_retries())?;
242//!
243//! // Custom configuration
244//! let retry_config = RetryConfig {
245//!     max_retries: 5,
246//!     initial_backoff_ms: 200,
247//!     retry_server_errors: false,  // Don't retry 5xx errors
248//!     retry_predicate: None,
249//! };
250//! let client = OdosSorV2::with_retry_config(retry_config)?;
251//! # Ok(())
252//! # }
253//! ```
254//!
255//! **Note:** Rate limit errors (429) are never retried regardless of configuration.
256//! This prevents retry cascades that make rate limiting worse.
257
258mod api;
259mod assemble;
260mod chain;
261mod client;
262mod contract;
263mod error;
264pub mod error_code;
265#[cfg(test)]
266mod integration_tests;
267mod limit_order_v2;
268mod sor;
269mod swap;
270mod transfer;
271mod v2_router;
272mod v3_router;
273
274pub use api::*;
275pub use assemble::*;
276pub use chain::*;
277pub use client::*;
278pub use contract::*;
279pub use error::*;
280pub use limit_order_v2::*;
281pub use sor::*;
282pub use swap::*;
283pub use transfer::*;
284pub use v2_router::*;
285pub use v3_router::*;