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("e_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("e_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("e_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::*;