Module error_handling

Module error_handling 

Source
Expand description

§Error Handling Guide

This guide covers error handling patterns for the O2 Rust SDK.

See also: O2Error API reference.

§Error Type

All SDK errors are represented by the O2Error enum, which implements std::error::Error and Display via thiserror:

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)

CodeVariantDescriptionRecovery
1000InternalErrorUnexpected server errorRetry with backoff
1001InvalidRequestMalformed or invalid requestFix request
1002ParseErrorFailed to parse request bodyFix request format
1003RateLimitExceededToo many requestsWait 3-5s, retry
1004GeoRestrictedRegion not allowedUse different region

§Market Errors (2xxx)

CodeVariantDescriptionRecovery
2000MarketNotFoundMarket not foundCheck market_id
2001MarketPausedMarket is pausedWait for market to resume
2002MarketAlreadyExistsMarket already existsUse existing market

§Order Errors (3xxx)

CodeVariantDescriptionRecovery
3000OrderNotFoundOrder not foundOrder may be filled/cancelled
3001OrderNotActiveOrder is not activeOrder already closed
3002InvalidOrderParamsInvalid order parametersCheck price/quantity

§Account/Session Errors (4xxx)

CodeVariantDescriptionRecovery
4000InvalidSignatureSignature verification failedCheck signing logic
4001InvalidSessionSession invalid or expiredRecreate session
4002AccountNotFoundTrading account not foundCall setup_account()
4003WhitelistNotConfiguredWhitelist not configuredWhitelist the account

§Trade Errors (5xxx)

CodeVariantDescriptionRecovery
5000TradeNotFoundTrade not foundCheck trade_id
5001InvalidTradeCountInvalid trade countAdjust count parameter

§Subscription Errors (6xxx)

CodeVariantDescriptionRecovery
6000AlreadySubscribedAlready subscribedSkip duplicate subscription
6001TooManySubscriptionsSubscription limit reachedUnsubscribe from unused streams
6002SubscriptionErrorGeneral subscription errorReconnect WebSocket

§Validation Errors (7xxx)

CodeVariantDescriptionRecovery
7000InvalidAmountInvalid amountCheck amount value
7001InvalidTimeRangeInvalid time rangeFix from/to timestamps
7002InvalidPaginationInvalid pagination paramsFix count/offset
7003NoActionsProvidedNo actions in requestAdd at least one action
7004TooManyActionsToo many actions (max 5)Split into multiple batches

§Block/Events Errors (8xxx)

CodeVariantDescriptionRecovery
8000BlockNotFoundBlock not foundBlock may not be indexed yet
8001EventsNotFoundEvents not foundEvents may not be indexed yet

§Matching Specific Errors

Use Rust’s match expression to handle specific error variants:

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 with message and reason fields:

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:

ReasonDescription
NotEnoughBalanceInsufficient funds for the operation
TraderNotWhiteListedThe trading account is not whitelisted
InvalidPricePrice violates on-chain constraints
OrderNotFoundThe order to cancel does not exist
PricePrecisionPrice exceeds maximum precision
FractionalPricePrice * quantity is not evenly divisible
MinOrderNotReachedOrder value below minimum

§Response-Level Error Detection

The SessionActionsResponse provides helper methods to distinguish between success and different error types:

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 enum provides helper methods:

// 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 after any action failure.

If you manage nonces manually, always re-fetch after errors:

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:

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(())
}