Expand description
§Odos SDK
A production-ready Rust SDK for the Odos protocol - a decentralized exchange aggregator that provides optimal routing for token swaps across multiple EVM chains.
§Features
- Multi-chain Support: 16+ EVM chains including Ethereum, Arbitrum, Optimism, Polygon, Base, etc.
- Type-safe: Leverages Rust’s type system with Alloy primitives for addresses, chain IDs, and amounts
- Production-ready: Built-in retry logic, circuit breakers, timeouts, and error handling
- Builder Pattern: Ergonomic API using the
boncrate for request building - Comprehensive Error Handling: Detailed error types for different failure scenarios
§Quick Start
§High-Level API with SwapBuilder
The easiest way to get started is with the SwapBuilder API:
use odos_sdk::prelude::*;
use std::str::FromStr;
// Create a client
let client = OdosClient::new()?;
// Define tokens and amount
let usdc = Address::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")?; // USDC on Ethereum
let weth = Address::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")?; // WETH on Ethereum
let my_address = Address::from_str("0x742d35Cc6634C0532925a3b8D35f3e7a5edD29c0")?;
// Build and execute swap in one go
let transaction = client.swap()
.chain(Chain::ethereum())
.from_token(usdc, U256::from(1_000_000)) // 1 USDC (6 decimals)
.to_token(weth)
.slippage(Slippage::percent(0.5).unwrap()) // 0.5% slippage
.signer(my_address)
.build_transaction()
.await?;
println!("Transaction ready: {:?}", transaction);§Low-Level API
For more control, use the low-level API with quote() and assemble():
use odos_sdk::prelude::*;
use alloy_primitives::address;
use std::str::FromStr;
let client = OdosClient::new()?;
let usdc = Address::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")?;
let weth = Address::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")?;
// Step 1: Get a quote
let quote_request = QuoteRequest::builder()
.chain_id(1)
.input_tokens(vec![(usdc, U256::from(1_000_000)).into()])
.output_tokens(vec![(weth, 1).into()])
.slippage_limit_percent(0.5)
.user_addr(address!("742d35Cc6634C0532925a3b8D35f3e7a5edD29c0"))
.compact(false)
.simple(false)
.referral_code(0)
.disable_rfqs(false)
.build();
let quote = client.quote("e_request).await?;
println!("Expected output: {} WETH", quote.out_amount().unwrap_or(&"0".to_string()));
// Step 2: Assemble transaction
let assembly_request = AssemblyRequest::builder()
.chain(alloy_chains::NamedChain::Mainnet)
.router_address(alloy_chains::NamedChain::Mainnet.v2_router_address()?)
.signer_address(Address::from_str("0x742d35Cc6634C0532925a3b8D35f3e7a5edD29c0")?)
.output_recipient(Address::from_str("0x742d35Cc6634C0532925a3b8D35f3e7a5edD29c0")?)
.token_address(usdc)
.token_amount(U256::from(1_000_000))
.path_id(quote.path_id().to_string())
.build();
let transaction = client.assemble(&assembly_request).await?;§Configuration
The SDK supports extensive configuration for production use:
use odos_sdk::*;
use std::time::Duration;
// Full configuration
let config = ClientConfig {
timeout: Duration::from_secs(30),
connect_timeout: Duration::from_secs(10),
retry_config: RetryConfig {
max_retries: 3,
initial_backoff_ms: 100,
retry_server_errors: true,
retry_predicate: None,
},
max_connections: 20,
pool_idle_timeout: Duration::from_secs(90),
api_key: None,
..Default::default()
};
let client = OdosClient::with_config(config)?;
// Or use convenience constructors
let client = OdosClient::with_retry_config(RetryConfig::conservative())?;§Error Handling
The SDK provides comprehensive error types with strongly-typed error codes:
use odos_sdk::*;
use alloy_primitives::Address;
match client.quote("e_request).await {
Ok(quote) => {
// Handle successful quote
println!("Got quote with path ID: {}", quote.path_id());
}
Err(err) => {
// Check for specific error codes
if let Some(code) = err.error_code() {
if code.is_invalid_chain_id() {
eprintln!("Invalid chain ID - check configuration");
} else if code.is_no_viable_path() {
eprintln!("No routing path found");
} else if code.is_timeout() {
eprintln!("Service timeout: {}", code);
}
}
// Log trace ID for support
if let Some(trace_id) = err.trace_id() {
eprintln!("Trace ID: {}", trace_id);
}
// Handle by error type
match err {
OdosError::Api { status, message, .. } => {
eprintln!("API error {}: {}", status, message);
}
OdosError::Timeout(msg) => {
eprintln!("Request timed out: {}", msg);
}
OdosError::RateLimit { message, retry_after, .. } => {
if let Some(duration) = retry_after {
eprintln!("Rate limited: {}. Retry after {} seconds", message, duration.as_secs());
} else {
eprintln!("Rate limited: {}", message);
}
}
_ => eprintln!("Error: {}", err),
}
}
}§Strongly-Typed Error Codes
The SDK provides error codes matching the Odos API documentation:
- General (1XXX):
ApiError - Algo/Quote (2XXX):
NoViablePath,AlgoTimeout,AlgoInternal - Internal Service (3XXX):
TxnAssemblyTimeout,GasUnavailable - Validation (4XXX):
InvalidChainId,BlockedUserAddr,InvalidTokenAmount - Internal (5XXX):
InternalError,SwapUnavailable
use odos_sdk::{OdosError, error_code::OdosErrorCode};
if let Some(code) = error.error_code() {
// Check categories
if code.is_validation_error() {
println!("Validation error - check request parameters");
}
// Check retryability
if code.is_retryable() {
println!("Error can be retried: {}", code);
}
}§Rate Limiting
The Odos API enforces rate limits to ensure fair usage. The SDK handles rate limits intelligently:
- HTTP 429 responses are detected and classified as
OdosError::RateLimit - Rate limit errors are NOT retried (return immediately with
Retry-Afterheader) - The SDK captures
Retry-Afterheaders for application-level handling - Applications should handle rate limits globally with proper backoff coordination
§Best Practices for Avoiding Rate Limits
- Share a single client across your application instead of creating new clients per request
- Implement application-level rate limiting if making many concurrent requests
- Handle rate limit errors gracefully and back off at the application level if needed
§Example: Handling Rate Limits
use odos_sdk::*;
use alloy_primitives::{Address, U256};
use std::time::Duration;
match client.quote("e_request).await {
Ok(quote) => {
println!("Got quote: {}", quote.path_id());
}
Err(e) if e.is_rate_limit() => {
// Rate limit exceeded even after SDK retries
// Consider backing off at application level
eprintln!("Rate limited - waiting before retry");
tokio::time::sleep(Duration::from_secs(5)).await;
// Retry or handle accordingly
}
Err(e) => {
eprintln!("Error: {}", e);
}
}§Configuring Retry Behavior
You can customize retry behavior for your use case:
use odos_sdk::*;
// Conservative: only retry network errors
let client = OdosClient::with_retry_config(RetryConfig::conservative())?;
// No retries: handle all errors at application level
let client = OdosClient::with_retry_config(RetryConfig::no_retries())?;
// Custom configuration
let retry_config = RetryConfig {
max_retries: 5,
initial_backoff_ms: 200,
retry_server_errors: false, // Don't retry 5xx errors
retry_predicate: None,
};
let client = OdosClient::with_retry_config(retry_config)?;Note: Rate limit errors (429) are never retried regardless of configuration. This prevents retry cascades that make rate limiting worse.
§Provider Construction (Alloy Best Practices)
§Dynamic Chain Support with AnyNetwork
For applications that need to work with multiple chains dynamically (without knowing
the chain at compile time), use AnyNetwork:
use alloy_network::AnyNetwork;
use alloy_provider::ProviderBuilder;
// Works with any EVM chain without network-specific types
let provider = ProviderBuilder::new()
.network::<AnyNetwork>()
.connect_http(rpc_url.parse()?);
// Routers work with AnyNetwork
let router: V3Router<AnyNetwork, _> = V3Router::new(router_address, provider);Trade-offs:
- ✅ Single code path for all chains
- ✅ Simpler multi-chain applications
- ⚠️ Loses network-specific receipt fields (e.g., OP-stack L1 gas info)
- ⚠️ Less compile-time type safety
When executing swaps on-chain, use Alloy’s ProviderBuilder
with recommended fillers for proper nonce management, gas estimation, and chain ID handling:
use alloy_provider::ProviderBuilder;
use alloy_signer_local::PrivateKeySigner;
use alloy_network::EthereumWallet;
// Create a signer from a private key
let signer: PrivateKeySigner = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
.parse()
.expect("valid private key");
// Create a provider with recommended fillers (nonce, gas, chain ID)
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.wallet(EthereumWallet::new(signer))
.connect_http("https://eth.llamarpc.com".parse()?);
// Use the provider with routers
let router = odos_sdk::V3Router::new(router_address, provider);§OP-Stack Chains (Base, Optimism, Fraxtal)
For OP-stack chains, use the Optimism network type to access L1 gas information:
#[cfg(feature = "op-stack")]
{
use odos_sdk::op_stack::Optimism;
use alloy_provider::ProviderBuilder;
// Create a provider for OP-stack chains
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.network::<Optimism>()
.connect_http("https://mainnet.base.org".parse()?);
// Transaction receipts will include L1 gas information
let receipt = provider.get_transaction_receipt(tx_hash).await?;
if let Some(l1_fee) = receipt.inner.l1_fee {
println!("L1 fee: {l1_fee}");
}
}§Sharing Providers
For concurrent swap operations, share a single provider instance:
use std::sync::Arc;
use alloy_provider::ProviderBuilder;
// Create a shared provider
let provider = Arc::new(
ProviderBuilder::new()
.with_recommended_fillers()
.connect_http("https://eth.llamarpc.com".parse()?)
);
// Clone the Arc for each concurrent operation
let provider_clone = Arc::clone(&provider);
tokio::spawn(async move {
// Use provider_clone for concurrent swap
});§WebSocket Providers for Real-Time Monitoring
Use WebSocket providers for real-time swap event monitoring:
use alloy_provider::ProviderBuilder;
use alloy_rpc_types::Filter;
use odos_sdk::events::SwapEventFilter;
use futures_util::StreamExt;
// Connect via WebSocket for subscriptions
let ws_provider = ProviderBuilder::new()
.connect_ws("wss://eth.llamarpc.com".parse()?)
.await?;
// Create a filter for swap events
let filter = SwapEventFilter::new(router_address)
.from_latest()
.build_v3_filter();
// Subscribe to real-time swap events
let mut stream = ws_provider.subscribe_logs(&filter).await?;
while let Some(log) = stream.next().await {
match log {
Ok(log) => println!("New swap detected: {:?}", log),
Err(e) => eprintln!("Subscription error: {e}"),
}
}Modules§
- IOdos
Router V3 - Module containing a contract’s types and functions.
- Odos
Router V2 - Module containing a contract’s types and functions.
- Odos
V2Router - Generated by the following Solidity interface…
- Odos
V3Router - Generated by the following Solidity interface…
- error_
code - Strongly-typed Odos API error codes
- events
- Event monitoring utilities for tracking Odos swaps.
- multicall
- Utilities for batching on-chain queries.
- prelude
- Prelude module for convenient imports
Structs§
- ApiKey
- API key for authenticating with the Odos API
- Assemble
Request - Request to the Odos Assemble API: https://docs.odos.xyz/build/api-docs
- Assembly
Request - Request for assembling a transaction from a quote
- Assembly
Response - Response from the Odos Assemble API: https://docs.odos.xyz/build/api-docs
- Chain
- Type-safe chain identifier with convenient constructors
- Client
Config - Configuration for the HTTP client
- Endpoint
- Complete API endpoint configuration combining host tier and API version
- Input
Token - Input token for the Odos quote API
- Odos
ApiError Response - Error response from the Odos API
- Odos
Client - The Odos API client
- Odos
Http Client - Enhanced HTTP client with retry logic and timeouts
- Output
Token - Output token for the Odos quote API
- Quote
Request - Request to the Odos quote API: https://docs.odos.xyz/build/api-docs
- Referral
Code - Type-safe referral code
- Retry
Config - Configuration for retry behavior
- Router
Availability - Represents which routers are available on a specific chain
- Simulation
- Simulation from the Odos Assemble API: https://docs.odos.xyz/build/api-docs
- Simulation
Error - Simulation error from the Odos Assemble API: https://docs.odos.xyz/build/api-docs
- Single
Quote Response - Single quote response from the Odos quote API: https://docs.odos.xyz/build/api-docs
- Slippage
- Type-safe slippage percentage with validation
- Swap
Builder - High-level swap builder for common use cases
- Swap
Inputs - Swap inputs for the Odos assemble API
- Transaction
Data - Transaction data from the Odos Assemble API: https://docs.odos.xyz/build/api-docs
- Transfer
Router Funds - A transfer of a token from one address to another.
- V2Router
- The V2 SOR Router contract.
- V3Router
- The V3 SOR Router contract.
Enums§
- ApiHost
- API host tier for the Odos API
- ApiVersion
- Version of the Odos API
- Odos
Chain Error - Errors that can occur when working with Odos chains
- Odos
Error - Comprehensive error types for the Odos SDK
- Router
Type - Represents the different types of Odos routers.
Constants§
- ODOS_
LO_ ARBITRUM_ ROUTER - Arbitrum One - Limit Order V2 Router contract address
- ODOS_
LO_ AVALANCHE_ ROUTER - Avalanche C-Chain - Limit Order V2 Router contract address
- ODOS_
LO_ BASE_ ROUTER - Base - Limit Order V2 Router contract address
- ODOS_
LO_ BSC_ ROUTER - BNB Smart Chain - Limit Order V2 Router contract address
- ODOS_
LO_ ETHEREUM_ ROUTER - Ethereum Mainnet - Limit Order V2 Router contract address
- ODOS_
LO_ FRAXTAL_ ROUTER - Fraxtal - Limit Order V2 Router contract address
- ODOS_
LO_ LINEA_ ROUTER - Linea - Limit Order V2 Router contract address
- ODOS_
LO_ MANTLE_ ROUTER - Mantle - Limit Order V2 Router contract address
- ODOS_
LO_ OP_ ROUTER - Optimism - Limit Order V2 Router contract address
- ODOS_
LO_ POLYGON_ ROUTER - Polygon - Limit Order V2 Router contract address
- ODOS_
LO_ SCROLL_ ROUTER - Scroll - Limit Order V2 Router contract address
- ODOS_
LO_ SONIC_ ROUTER - Sonic - Limit Order V2 Router contract address
- ODOS_
LO_ UNICHAIN_ ROUTER - Unichain - Limit Order V2 Router contract address
- ODOS_
LO_ ZKSYNC_ ROUTER - zkSync Era - Limit Order V2 Router contract address
- ODOS_V3
- Odos V3 Router - Next-generation router contract
- ODOS_
V2_ ARBITRUM_ ROUTER - Arbitrum One - V2 Router contract address
- ODOS_
V2_ AVALANCHE_ ROUTER - Avalanche C-Chain - V2 Router contract address
- ODOS_
V2_ BASE_ ROUTER - Base - V2 Router contract address
- ODOS_
V2_ BSC_ ROUTER - BNB Smart Chain - V2 Router contract address
- ODOS_
V2_ ETHEREUM_ ROUTER - Ethereum Mainnet - V2 Router contract address
- ODOS_
V2_ FRAXTAL_ ROUTER - Fraxtal - V2 Router contract address
- ODOS_
V2_ LINEA_ ROUTER - Linea - V2 Router contract address
- ODOS_
V2_ MANTLE_ ROUTER - Mantle - V2 Router contract address
- ODOS_
V2_ OP_ ROUTER - Optimism - V2 Router contract address
- ODOS_
V2_ POLYGON_ ROUTER - Polygon - V2 Router contract address
- ODOS_
V2_ SCROLL_ ROUTER - Scroll - V2 Router contract address
- ODOS_
V2_ SONIC_ ROUTER - Sonic - V2 Router contract address
- ODOS_
V2_ UNICHAIN_ ROUTER - Unichain - V2 Router contract address
- ODOS_
V2_ ZKSYNC_ ROUTER - zkSync Era - V2 Router contract address
Traits§
- Odos
Chain - Trait for chains that support Odos protocol
- Odos
Router Selection - Extension trait for easy router selection
Functions§
- get_
lo_ router_ by_ chain_ id - Get the Limit Order V2 router address for a specific chain ID
- get_
supported_ chains - Get all supported chains
- get_
supported_ lo_ chains - Get all chains that support Limit Order V2 routers
- get_
supported_ v2_ chains - Get all chains that support V2 routers
- get_
supported_ v3_ chains - Get all chains that support V3 routers
- get_
v2_ router_ by_ chain_ id - Get the V2 router address for a specific chain ID
- get_
v3_ router_ by_ chain_ id - Get the V3 router address for a specific chain ID
- parse_
value - Parse a value string as U256, supporting both decimal and hexadecimal formats
Type Aliases§
- Odos
Chain Result - Result type for Odos chain operations
- OdosSor
Deprecated - Deprecated alias for
OdosClient - Result
- Result type alias for Odos SDK operations
- Swap
Context Deprecated - Deprecated alias for
AssemblyRequest