# Error Handling Guide
This guide covers error handling patterns for the O2 Rust SDK.
> See also: [`O2Error`](crate::O2Error) API reference.
## Error Type
All SDK errors are represented by the [`O2Error`](crate::O2Error) enum, which implements
`std::error::Error` and `Display` via `thiserror`:
```rust,ignore
use o2_sdk::O2Error;
let market = "fFUEL/fUSDC";
match client.create_order(&mut session, market, Side::Buy, "0.02", "100", OrderType::Spot, true, true).await {
Ok(resp) => println!("Success: {:?}", resp.tx_id),
Err(e) => println!("Error: {}", e),
}
```
## Error Variant Reference
### General Errors (1xxx)
| 1000 | `InternalError` | Unexpected server error | Retry with backoff |
| 1001 | `InvalidRequest` | Malformed or invalid request | Fix request |
| 1002 | `ParseError` | Failed to parse request body | Fix request format |
| 1003 | `RateLimitExceeded` | Too many requests | Wait 3-5s, retry |
| 1004 | `GeoRestricted` | Region not allowed | Use different region |
### Market Errors (2xxx)
| 2000 | `MarketNotFound` | Market not found | Check market_id |
| 2001 | `MarketPaused` | Market is paused | Wait for market to resume |
| 2002 | `MarketAlreadyExists` | Market already exists | Use existing market |
### Order Errors (3xxx)
| 3000 | `OrderNotFound` | Order not found | Order may be filled/cancelled |
| 3001 | `OrderNotActive` | Order is not active | Order already closed |
| 3002 | `InvalidOrderParams` | Invalid order parameters | Check price/quantity |
### Account/Session Errors (4xxx)
| 4000 | `InvalidSignature` | Signature verification failed | Check signing logic |
| 4001 | `InvalidSession` | Session invalid or expired | Recreate session |
| 4002 | `AccountNotFound` | Trading account not found | Call `setup_account()` |
| 4003 | `WhitelistNotConfigured` | Whitelist not configured | Whitelist the account |
### Trade Errors (5xxx)
| 5000 | `TradeNotFound` | Trade not found | Check trade_id |
| 5001 | `InvalidTradeCount` | Invalid trade count | Adjust count parameter |
### Subscription Errors (6xxx)
| 6000 | `AlreadySubscribed` | Already subscribed | Skip duplicate subscription |
| 6001 | `TooManySubscriptions` | Subscription limit reached | Unsubscribe from unused streams |
| 6002 | `SubscriptionError` | General subscription error | Reconnect WebSocket |
### Validation Errors (7xxx)
| 7000 | `InvalidAmount` | Invalid amount | Check amount value |
| 7001 | `InvalidTimeRange` | Invalid time range | Fix from/to timestamps |
| 7002 | `InvalidPagination` | Invalid pagination params | Fix count/offset |
| 7003 | `NoActionsProvided` | No actions in request | Add at least one action |
| 7004 | `TooManyActions` | Too many actions (max 5) | Split into multiple batches |
### Block/Events Errors (8xxx)
| 8000 | `BlockNotFound` | Block not found | Block may not be indexed yet |
| 8001 | `EventsNotFound` | Events not found | Events may not be indexed yet |
## Matching Specific Errors
Use Rust's `match` expression to handle specific error variants:
```rust,ignore
use o2_sdk::{O2Error, O2Client, Side, OrderType};
match client.create_order(
&mut session, market, Side::Buy, "0.02", "100",
OrderType::Spot, true, true,
).await {
Ok(resp) if resp.is_success() => {
println!("Order placed: {}", resp.tx_id.unwrap_or_default());
}
Ok(resp) if resp.is_onchain_error() => {
// On-chain revert (has message but no code)
println!("Revert: {}", resp.reason.as_deref().unwrap_or("unknown"));
}
Ok(resp) => {
// Pre-flight error (has code)
println!("Error {}: {}", resp.code.unwrap_or(0), resp.message.as_deref().unwrap_or("?"));
}
Err(O2Error::SessionExpired(_)) => {
// Create a new session
session = client.create_session(&wallet, &[market], std::time::Duration::from_secs(30 * 24 * 3600)).await?;
}
Err(O2Error::RateLimitExceeded(_)) => {
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
}
Err(O2Error::InvalidSignature(_)) => {
// Check your key/signer setup
panic!("Signing verification failed");
}
Err(O2Error::AccountNotFound(_)) => {
// Set up the account first
client.setup_account(&wallet).await?;
}
Err(e) => {
println!("Unexpected error: {}", e);
}
}
```
## On-Chain Reverts
On-chain reverts occur when a transaction is submitted but fails during
execution. These are returned as [`O2Error::OnChainRevert`](crate::O2Error::OnChainRevert) with `message`
and `reason` fields:
```rust,ignore
match client.create_order(&mut session, market, Side::Buy, "0.02", "100", OrderType::Spot, true, true).await {
Err(O2Error::OnChainRevert { reason, message, .. }) => {
match reason.as_str() {
"NotEnoughBalance" => {
println!("Insufficient funds");
}
"TraderNotWhiteListed" => {
// Re-whitelist the account
client.api.whitelist_account(&session.trade_account_id).await?;
}
_ => {
println!("Revert: {} ({})", message, reason);
}
}
}
_ => {}
}
```
Common revert reasons:
| `NotEnoughBalance` | Insufficient funds for the operation |
| `TraderNotWhiteListed` | The trading account is not whitelisted |
| `InvalidPrice` | Price violates on-chain constraints |
| `OrderNotFound` | The order to cancel does not exist |
| `PricePrecision` | Price exceeds maximum precision |
| `FractionalPrice` | Price * quantity is not evenly divisible |
| `MinOrderNotReached` | Order value below minimum |
## Response-Level Error Detection
The [`SessionActionsResponse`](crate::SessionActionsResponse) provides helper methods to distinguish
between success and different error types:
```rust,ignore
let resp = client.create_order(&mut session, market, Side::Buy, "0.02", "100", OrderType::Spot, true, true).await?;
if resp.is_success() {
// Transaction succeeded — tx_id is present
println!("TX: {}", resp.tx_id.unwrap_or_default());
} else if resp.is_preflight_error() {
// Pre-flight validation error — code field is present
println!("Pre-flight error {}: {}", resp.code.unwrap_or(0), resp.message.as_deref().unwrap_or("?"));
} else if resp.is_onchain_error() {
// On-chain revert — message present but no code
println!("On-chain revert: {}", resp.reason.as_deref().unwrap_or("?"));
}
```
## Error Utility Methods
The [`O2Error`](crate::O2Error) enum provides helper methods:
```rust,ignore
// Get the numeric error code (if applicable)
if let Some(code) = error.error_code() {
println!("Error code: {}", code);
}
// Check if the error is retryable
if error.is_retryable() {
// Retry with backoff
}
```
## Nonce Errors
The on-chain nonce increments **even on reverted transactions**. The SDK
handles this automatically by calling [`O2Client::refresh_nonce`](crate::client::O2Client::refresh_nonce) after any action
failure.
If you manage nonces manually, always re-fetch after errors:
```rust,ignore
match client.batch_actions(&mut session, market, actions, true).await {
Err(_) => {
// Nonce was already refreshed by the SDK
// The next call will use the correct nonce
}
Ok(resp) => { /* ... */ }
}
```
## Robust Trading Loop
A production-grade pattern with error recovery:
```rust,ignore
use o2_sdk::{O2Client, Network, O2Error, Side, OrderType};
use std::time::Duration;
async fn trading_loop() -> Result<(), O2Error> {
let mut client = O2Client::new(Network::Mainnet);
let wallet = client.load_wallet(&std::env::var("O2_PRIVATE_KEY").unwrap())?;
let _account = client.setup_account(&wallet).await?;
let market = "FUEL/USDC";
let mut session = client.create_session(&wallet, &[market], std::time::Duration::from_secs(30 * 24 * 3600)).await?;
loop {
match client.create_order(
&mut session, market, Side::Buy, "0.02", "100",
OrderType::PostOnly, true, true,
).await {
Ok(resp) if resp.is_success() => {
println!("Order placed: {}", resp.tx_id.unwrap_or_default());
}
Ok(resp) => {
if let Some(reason) = &resp.reason {
println!("Revert: {}", reason);
if reason == "NotEnoughBalance" {
break;
}
}
tokio::time::sleep(Duration::from_secs(5)).await;
continue;
}
Err(O2Error::SessionExpired(_)) => {
session = client.create_session(&wallet, &[market], std::time::Duration::from_secs(30 * 24 * 3600)).await?;
continue;
}
Err(O2Error::RateLimitExceeded(_)) => {
tokio::time::sleep(Duration::from_secs(10)).await;
continue;
}
Err(O2Error::OnChainRevert { reason, .. }) => {
println!("Revert: {}", reason);
if reason == "NotEnoughBalance" {
break;
}
tokio::time::sleep(Duration::from_secs(5)).await;
continue;
}
Err(e) => {
println!("Error: {}", e);
tokio::time::sleep(Duration::from_secs(5)).await;
continue;
}
}
tokio::time::sleep(Duration::from_secs(15)).await;
}
client.disconnect_ws().await?;
Ok(())
}
```