Error Handling Guide¶
This guide covers error handling patterns for the O2 Python SDK.
See also
For the complete error reference, see errors — Error types.
Error hierarchy¶
All SDK errors inherit from O2Error:
O2Error
├── InternalError (1000)
├── InvalidRequest (1001)
├── ParseError (1002)
├── RateLimitExceeded (1003)
├── GeoRestricted (1004)
├── MarketNotFound (2000)
├── MarketPaused (2001)
├── MarketAlreadyExists (2002)
├── OrderNotFound (3000)
├── OrderNotActive (3001)
├── InvalidOrderParams (3002)
├── InvalidSignature (4000)
├── InvalidSession (4001)
├── AccountNotFound (4002)
├── WhitelistNotConfigured (4003)
├── TradeNotFound (5000)
├── InvalidTradeCount (5001)
├── AlreadySubscribed (6000)
├── TooManySubscriptions (6001)
├── SubscriptionError (6002)
├── InvalidAmount (7000)
├── InvalidTimeRange (7001)
├── InvalidPagination (7002)
├── NoActionsProvided (7003)
├── TooManyActions (7004)
├── BlockNotFound (8000)
├── EventsNotFound (8001)
├── SessionExpired (client-side)
└── OnChainRevert (on-chain)
Basic error handling¶
from o2_sdk import O2Error, OrderSide
try:
result = await client.create_order(
"fFUEL/fUSDC", OrderSide.BUY, 0.02, 100.0
)
except O2Error as e:
print(f"Error: {e.message} (code={e.code})")
Catching specific errors¶
from o2_sdk import (
InvalidSignature,
RateLimitExceeded,
SessionExpired,
OnChainRevert,
AccountNotFound,
O2Error,
)
try:
result = await client.create_order(
"fFUEL/fUSDC", OrderSide.BUY, 0.02, 100.0
)
except SessionExpired:
# The session has expired — create a new one
session = await client.create_session(
owner=owner, markets=["fFUEL/fUSDC"]
)
result = await client.create_order(
"fFUEL/fUSDC", OrderSide.BUY, 0.02, 100.0
)
except InvalidSignature:
# Signing verification failed — check your key/signer setup
raise
except AccountNotFound:
# Account doesn't exist — set it up first
account = await client.setup_account(owner)
raise
except OnChainRevert as e:
# On-chain transaction reverted
print(f"Revert: {e.message}")
print(f"Reason: {e.reason}") # e.g. "NotEnoughBalance"
except O2Error as e:
# Catch-all for any other O2 error
print(f"Error {e.code}: {e.message}")
Rate limiting¶
The SDK automatically retries rate-limited requests (error code 1003)
with exponential backoff, up to 3 attempts. If all retries are exhausted,
RateLimitExceeded is raised:
import asyncio
from o2_sdk import RateLimitExceeded
try:
result = await client.create_order(...)
except RateLimitExceeded:
# SDK already retried 3 times with backoff
# Add additional delay if needed
await asyncio.sleep(10)
On-chain reverts¶
When a transaction is submitted successfully but reverts on-chain, the
SDK raises OnChainRevert. These errors have no
code field but include a message and reason:
from o2_sdk import OnChainRevert
try:
result = await client.create_order(...)
except OnChainRevert as e:
if e.reason == "NotEnoughBalance":
# Need more funds
pass
elif e.reason == "TraderNotWhiteListed":
# Re-whitelist
await client.api.whitelist_account(session.trade_account_id)
else:
print(f"Revert: {e.reason}")
Common revert reasons:
Reason |
Description |
|---|---|
|
Insufficient funds for the operation. |
|
The trading account is not whitelisted. |
|
Price violates on-chain constraints. |
|
The order to cancel does not exist. |
Nonce errors¶
The on-chain nonce increments even on reverted transactions. The SDK
handles this automatically by calling
refresh_nonce() after any action failure.
If you manage nonces manually, always re-fetch after errors:
try:
result = await client.batch_actions(actions)
except O2Error:
# Nonce was already refreshed by the SDK
# The next call will use the correct nonce
pass
Robust trading loop¶
A production-grade pattern with error recovery:
import asyncio
import os
from o2_sdk import (
O2Client, Network, O2Error, OrderSide, OrderType,
SessionExpired, OnChainRevert, RateLimitExceeded,
)
async def trading_loop():
client = O2Client(network=Network.MAINNET)
owner = client.load_wallet(os.environ["O2_PRIVATE_KEY"])
account = await client.setup_account(owner)
session = await client.create_session(
owner=owner, markets=["FUEL/USDC"]
)
while True:
try:
result = await client.create_order(
"FUEL/USDC", OrderSide.BUY, 0.02, 100.0,
order_type=OrderType.POST_ONLY,
)
print(f"Order placed: {result.tx_id}")
except SessionExpired:
session = await client.create_session(
owner=owner, markets=["FUEL/USDC"]
)
continue
except RateLimitExceeded:
await asyncio.sleep(10)
continue
except OnChainRevert as e:
print(f"Revert: {e.reason}")
if e.reason == "NotEnoughBalance":
break # Stop trading
await asyncio.sleep(5)
continue
except O2Error as e:
print(f"Error: {e}")
await asyncio.sleep(5)
continue
await asyncio.sleep(15)
await client.close()