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 = OdosClient::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//! api_key: None,
85//! ..Default::default()
86//! };
87//! let client = OdosClient::with_config(config)?;
88//!
89//! // Or use convenience constructors
90//! let client = OdosClient::with_retry_config(RetryConfig::conservative())?;
91//! # Ok(())
92//! # }
93//! ```
94//!
95//! ## Error Handling
96//!
97//! The SDK provides comprehensive error types with strongly-typed error codes:
98//!
99//! ```rust,no_run
100//! use odos_sdk::*;
101//!
102//! # async fn example() {
103//! # let client = OdosClient::new().unwrap();
104//! # 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();
105//! match client.get_swap_quote("e_request).await {
106//! Ok(quote) => {
107//! // Handle successful quote
108//! println!("Got quote with path ID: {}", quote.path_id());
109//! }
110//! Err(err) => {
111//! // Check for specific error codes
112//! if let Some(code) = err.error_code() {
113//! if code.is_invalid_chain_id() {
114//! eprintln!("Invalid chain ID - check configuration");
115//! } else if code.is_no_viable_path() {
116//! eprintln!("No routing path found");
117//! } else if code.is_timeout() {
118//! eprintln!("Service timeout: {}", code);
119//! }
120//! }
121//!
122//! // Log trace ID for support
123//! if let Some(trace_id) = err.trace_id() {
124//! eprintln!("Trace ID: {}", trace_id);
125//! }
126//!
127//! // Handle by error type
128//! match err {
129//! OdosError::Api { status, message, .. } => {
130//! eprintln!("API error {}: {}", status, message);
131//! }
132//! OdosError::Timeout(msg) => {
133//! eprintln!("Request timed out: {}", msg);
134//! }
135//! OdosError::RateLimit { message, retry_after, .. } => {
136//! if let Some(duration) = retry_after {
137//! eprintln!("Rate limited: {}. Retry after {} seconds", message, duration.as_secs());
138//! } else {
139//! eprintln!("Rate limited: {}", message);
140//! }
141//! }
142//! _ => eprintln!("Error: {}", err),
143//! }
144//! }
145//! }
146//! # }
147//! ```
148//!
149//! ### Strongly-Typed Error Codes
150//!
151//! The SDK provides error codes matching the [Odos API documentation](https://docs.odos.xyz/build/api_errors):
152//!
153//! - **General (1XXX)**: `ApiError`
154//! - **Algo/Quote (2XXX)**: `NoViablePath`, `AlgoTimeout`, `AlgoInternal`
155//! - **Internal Service (3XXX)**: `TxnAssemblyTimeout`, `GasUnavailable`
156//! - **Validation (4XXX)**: `InvalidChainId`, `BlockedUserAddr`, `InvalidTokenAmount`
157//! - **Internal (5XXX)**: `InternalError`, `SwapUnavailable`
158//!
159//! ```rust,no_run
160//! use odos_sdk::{OdosError, error_code::OdosErrorCode};
161//!
162//! # fn handle_error(error: OdosError) {
163//! if let Some(code) = error.error_code() {
164//! // Check categories
165//! if code.is_validation_error() {
166//! println!("Validation error - check request parameters");
167//! }
168//!
169//! // Check retryability
170//! if code.is_retryable() {
171//! println!("Error can be retried: {}", code);
172//! }
173//! }
174//! # }
175//! ```
176//!
177//! ## Rate Limiting
178//!
179//! The Odos API enforces rate limits to ensure fair usage. The SDK handles rate limits intelligently:
180//!
181//! - **HTTP 429 responses** are detected and classified as [`OdosError::RateLimit`]
182//! - Rate limit errors are **NOT retried** (return immediately with `Retry-After` header)
183//! - The SDK **captures `Retry-After` headers** for application-level handling
184//! - Applications should handle rate limits globally with proper backoff coordination
185//!
186//! ### Best Practices for Avoiding Rate Limits
187//!
188//! 1. **Share a single client** across your application instead of creating new clients per request
189//! 2. **Implement application-level rate limiting** if making many concurrent requests
190//! 3. **Handle rate limit errors gracefully** and back off at the application level if needed
191//!
192//! ### Example: Handling Rate Limits
193//!
194//! ```rust,no_run
195//! use odos_sdk::*;
196//! use alloy_primitives::{Address, U256};
197//! use std::time::Duration;
198//!
199//! # async fn example() -> Result<()> {
200//! # let client = OdosClient::new()?;
201//! # let quote_request = QuoteRequest::builder()
202//! # .chain_id(1)
203//! # .input_tokens(vec![])
204//! # .output_tokens(vec![])
205//! # .slippage_limit_percent(1.0)
206//! # .user_addr("test".to_string())
207//! # .compact(false)
208//! # .simple(false)
209//! # .referral_code(0)
210//! # .disable_rfqs(false)
211//! # .build();
212//! match client.get_swap_quote("e_request).await {
213//! Ok(quote) => {
214//! println!("Got quote: {}", quote.path_id());
215//! }
216//! Err(e) if e.is_rate_limit() => {
217//! // Rate limit exceeded even after SDK retries
218//! // Consider backing off at application level
219//! eprintln!("Rate limited - waiting before retry");
220//! tokio::time::sleep(Duration::from_secs(5)).await;
221//! // Retry or handle accordingly
222//! }
223//! Err(e) => {
224//! eprintln!("Error: {}", e);
225//! }
226//! }
227//! # Ok(())
228//! # }
229//! ```
230//!
231//! ### Configuring Retry Behavior
232//!
233//! You can customize retry behavior for your use case:
234//!
235//! ```rust,no_run
236//! use odos_sdk::*;
237//!
238//! # fn example() -> Result<()> {
239//! // Conservative: only retry network errors
240//! let client = OdosClient::with_retry_config(RetryConfig::conservative())?;
241//!
242//! // No retries: handle all errors at application level
243//! let client = OdosClient::with_retry_config(RetryConfig::no_retries())?;
244//!
245//! // Custom configuration
246//! let retry_config = RetryConfig {
247//! max_retries: 5,
248//! initial_backoff_ms: 200,
249//! retry_server_errors: false, // Don't retry 5xx errors
250//! retry_predicate: None,
251//! };
252//! let client = OdosClient::with_retry_config(retry_config)?;
253//! # Ok(())
254//! # }
255//! ```
256//!
257//! **Note:** Rate limit errors (429) are never retried regardless of configuration.
258//! This prevents retry cascades that make rate limiting worse.
259
260mod api;
261mod api_key;
262mod assemble;
263mod chain;
264mod client;
265mod contract;
266mod error;
267pub mod error_code;
268#[cfg(test)]
269mod integration_tests;
270#[cfg(feature = "limit-orders")]
271mod limit_order_v2;
272mod router_type;
273mod sor;
274mod swap;
275mod swap_builder;
276mod transfer;
277mod types;
278#[cfg(feature = "v2")]
279mod v2_router;
280#[cfg(feature = "v3")]
281mod v3_router;
282
283// API types
284pub use api::{
285 ApiHost, ApiVersion, Endpoint, InputToken, OdosApiErrorResponse, OutputToken, QuoteRequest,
286 SingleQuoteResponse,
287};
288
289// SwapInputs is only available with v2 feature (contains V2 router types)
290#[cfg(feature = "v2")]
291pub use api::SwapInputs;
292
293// API key management
294pub use api_key::ApiKey;
295
296// Transaction assembly
297pub use assemble::{
298 parse_value, AssembleRequest, AssemblyResponse, Simulation, SimulationError, TransactionData,
299};
300
301// Chain support
302pub use chain::{OdosChain, OdosChainError, OdosChainResult, OdosRouterSelection};
303
304// HTTP client configuration
305pub use client::{ClientConfig, OdosHttpClient, RetryConfig};
306
307// Contract addresses and chain helpers
308pub use contract::{
309 get_lo_router_by_chain_id, get_supported_chains, get_supported_lo_chains,
310 get_supported_v2_chains, get_supported_v3_chains, get_v2_router_by_chain_id,
311 get_v3_router_by_chain_id, ODOS_LO_ARBITRUM_ROUTER, ODOS_LO_AVALANCHE_ROUTER,
312 ODOS_LO_BASE_ROUTER, ODOS_LO_BERACHAIN_ROUTER, ODOS_LO_BSC_ROUTER, ODOS_LO_ETHEREUM_ROUTER,
313 ODOS_LO_FRAXTAL_ROUTER, ODOS_LO_LINEA_ROUTER, ODOS_LO_MANTLE_ROUTER, ODOS_LO_MODE_ROUTER,
314 ODOS_LO_OP_ROUTER, ODOS_LO_POLYGON_ROUTER, ODOS_LO_SCROLL_ROUTER, ODOS_LO_SONIC_ROUTER,
315 ODOS_LO_UNICHAIN_ROUTER, ODOS_LO_ZKSYNC_ROUTER, ODOS_V2_ARBITRUM_ROUTER,
316 ODOS_V2_AVALANCHE_ROUTER, ODOS_V2_BASE_ROUTER, ODOS_V2_BSC_ROUTER, ODOS_V2_ETHEREUM_ROUTER,
317 ODOS_V2_FRAXTAL_ROUTER, ODOS_V2_LINEA_ROUTER, ODOS_V2_MANTLE_ROUTER, ODOS_V2_MODE_ROUTER,
318 ODOS_V2_OP_ROUTER, ODOS_V2_POLYGON_ROUTER, ODOS_V2_SCROLL_ROUTER, ODOS_V2_SONIC_ROUTER,
319 ODOS_V2_UNICHAIN_ROUTER, ODOS_V2_ZKSYNC_ROUTER, ODOS_V3,
320};
321
322// Error handling
323pub use error::{OdosError, Result};
324
325// Limit order contract bindings
326#[cfg(feature = "limit-orders")]
327pub use limit_order_v2::LimitOrderV2;
328
329// Router type selection
330pub use router_type::{RouterAvailability, RouterType};
331
332// Smart Order Router client
333#[allow(deprecated)]
334pub use sor::{OdosClient, OdosSor};
335
336// Swap execution context
337#[allow(deprecated)]
338pub use swap::{AssemblyRequest, SwapContext};
339
340// High-level swap builder
341pub use swap_builder::SwapBuilder;
342
343// Transfer types
344pub use transfer::TransferRouterFunds;
345
346// Type-safe domain types
347pub use types::{Chain, ReferralCode, Slippage};
348
349// V2 router contract bindings
350#[cfg(feature = "v2")]
351pub use v2_router::{OdosRouterV2, OdosV2Router, V2Router};
352
353// V3 router contract bindings
354#[cfg(feature = "v3")]
355pub use v3_router::{IOdosRouterV3, OdosV3Router, V3Router};