Dexalot Python SDK - Core library for Dexalot interaction
Project description
Dexalot Python SDK
Disclaimer
Here is our first public release of Dexalot SDK for Python. It is in alpha testing right now. Fork it, contribute to it and use it to integrate with Dexalot and let us know how we can improve it.
Please Note: The public interface may undergo breaking changes.
Overview
dexalot-sdk is a Python library that provides core functionality for interacting with the Dexalot decentralized exchange. It offers a unified client interface for trading operations, cross-chain transfers, and portfolio management across multiple blockchain networks.
Features
- Unified Client: Single
DexalotClientinterface for all Dexalot operations - Modular Architecture: Separate clients for CLOB, Swap, and Transfer operations
- Multi-Chain Support: Works with Dexalot L1 subnet and connected chains
- Caching: TTL-based memory cache utilities for performance optimization
Architecture
Core Components
core/client.py: UnifiedDexalotClientinheriting from modular componentscore/base.py: Environment setup, Web3 connections, error handlingcore/clob.py: Central Limit Order Book trading operationscore/swap.py: SimpleSwap RFQ (Request for Quote) functionalitycore/transfer.py: Cross-chain deposits/withdrawals, portfolio management
Utilities
utils/input_validators.py: Validate SDK method input parameters (amounts, addresses, pairs, etc.)utils/token_normalization.py: Normalize user token symbols andBASE/QUOTEpairs (strip, ASCII uppercase, optional aliases fromdata/token_aliases.json)utils/cache.py: TTL-based caching utilities (MemoryCache,ttl_cached,async_ttl_cached)utils/observability.py: Structured logging and operation trackingutils/result.py: StandardizedResult[T]type for consistent error handlingutils/retry.py: Async retry decorator with exponential backoffutils/rate_limit.py: Token bucket rate limiter for API and RPC callsutils/nonce_manager.py: Async-safe nonce management to prevent transaction race conditionsutils/provider_manager.py: RPC provider failover with health trackingutils/error_sanitizer.py: Error message sanitization to prevent information leakageutils/websocket_manager.py: Persistent WebSocket connection manager with reconnection and heartbeat
Token and pair inputs
User-facing methods accept common symbol variants: surrounding whitespace is ignored, symbols are folded to ASCII uppercase, and a small alias map (for example ETHER → ETH, BITCOIN → BTC) is applied after format validation. Trading pairs are normalized per leg (eth/usdc → ETH/USDC). Examples in this repo use canonical symbols; callers may pass mixed case or aliases interchangeably.
Installation
Install from PyPI:
pip install dexalot-sdk
For local development with uv:
uv venv
uv sync --group dev
Or with pip in editable mode from the repository root:
pip install -e .
Quick Start
import asyncio
from dexalot_sdk import DexalotClient
async def main():
client = None
try:
# Initialize client
client = DexalotClient()
result = await client.initialize_client()
if not result.success:
print(f"Initialization failed: {result.error}")
return
# Fetch trading pairs (stores pairs in client.pairs)
pairs_result = await client.get_clob_pairs()
if pairs_result.success:
print(f"Available pairs: {list(client.pairs.keys())}")
else:
print(f"Error: {pairs_result.error}")
finally:
# Always close the client to clean up resources
if client is not None:
await client.close()
# Run the async function
asyncio.run(main())
Key Points:
- The SDK is fully async - all methods must be awaited
- Async operational methods return
Result[T]for consistent error handling - Use
asyncio.run()for scripts orawaitin async contexts - Always call
await client.close()when done to clean up resources
See examples/async_basic.py for more examples.
Usage
Basic Async Usage
import asyncio
from dexalot_sdk import DexalotClient
async def main():
client = None
try:
client = DexalotClient()
# Initialize client (required before other operations)
init_result = await client.initialize_client()
if not init_result.success:
print(f"Failed to initialize: {init_result.error}")
return
# Get available trading pairs (stores pairs in client.pairs)
pairs_result = await client.get_clob_pairs()
if pairs_result.success:
print(f"Found {len(client.pairs)} trading pairs")
else:
print(f"Error fetching pairs: {pairs_result.error}")
finally:
# Always close the client to clean up resources
if client is not None:
await client.close()
asyncio.run(main())
Error Handling with Result Pattern
Async operational methods return Result[T] which provides consistent error handling:
result = await client.get_orderbook("AVAX/USDC")
if result.success:
orderbook = result.data
print(f"Bids: {orderbook['bids']}")
print(f"Asks: {orderbook['asks']}")
else:
print(f"Error: {result.error}")
# Handle error appropriately
See examples/error_handling.py for comprehensive error handling patterns.
Dependencies
web3>=6.0.0: Multi-chain blockchain interactions (AsyncWeb3 for async operations)aiohttp: Async HTTP client for Dexalot API communicationpython-dotenv: Environment variable managementeth-account: Ethereum account management and transaction signingwebsockets: Async WebSocket client for real-time event subscriptions
Scripts
The published package includes the secrets-vault console command:
secrets-vault keygen
Repository maintenance utilities such as scripts/version_manager.py are kept in the repo and are not part of the installed package.
Testing
Run tests from the repository root:
make test # Unit tests
make cov # Coverage report
Caching
The SDK includes a built-in 4-level caching system to optimize performance by reducing redundant API calls. Caching is enabled by default with sensible TTL (Time-To-Live) values.
📖 Detailed Documentation: See SDK Caching Guide for comprehensive caching documentation, including advanced usage patterns, use cases, troubleshooting, and performance considerations.
Cache Levels
| Level | Data Type | Default TTL | Examples |
|---|---|---|---|
| Static | Rarely changes | 1 hour | Environments, deployments, connected chains |
| Semi-Static | Changes occasionally | 15 minutes | Tokens, trading pairs |
| Balance | User-specific, updates frequently | 10 seconds | Portfolio balances, wallet balances |
| Orderbook | Real-time data | 1 second | Order book snapshots |
Basic Usage
import asyncio
from dexalot_sdk import DexalotClient
from pprint import pprint
async def main():
async with DexalotClient() as client:
await client.initialize_client()
# First call fetches from API
result = await client.get_all_portfolio_balances()
if result.success:
pprint(result.data)
# {'ALOT': {'available': 95.5, 'locked': 4.5, 'total': 100.0}, 'AVAX': ...}
# Second call within 10 seconds returns cached result
result = await client.get_all_portfolio_balances() # Cached!
asyncio.run(main())
Configuration
Customize cache behavior during client initialization:
# Disable caching entirely
client = DexalotClient(enable_cache=False)
# Custom TTL values (in seconds)
client = DexalotClient(
enable_cache=True,
cache_ttl_static=7200, # 2 hours for static data
cache_ttl_semi_static=1800, # 30 minutes for semi-static
cache_ttl_balance=5, # 5 seconds for balances
cache_ttl_orderbook=0.5 # 500ms for orderbook
)
Cache Invalidation
Manually clear cached data when needed:
# Clear all cache levels
client.invalidate_cache()
# Clear specific cache level
client.invalidate_cache(level="balance") # Options: static, semi_static, balance, orderbook
Cached Methods
Static Data (1 hour):
get_environments()get_chains()get_deployment()
Semi-Static Data (15 minutes):
get_tokens()get_clob_pairs()get_swap_pairs(chain_identifier)
Balance Data (10 seconds):
get_portfolio_balance(token, address=None)get_all_portfolio_balances(address=None)get_chain_wallet_balance(chain, token, address=None)get_chain_wallet_balances(chain, address=None)get_all_chain_wallet_balances(address=None)
Orderbook Data (1 second):
get_orderbook(pair)
Note: Write operations (e.g., add_order(), cancel_order(), deposit(), withdraw()) are never cached to ensure data integrity.
Per-User Caching
Balance data is cached per user address. When address=None, the SDK uses the connected wallet's address:
# Each user gets their own cached balance data
balance1 = await client.get_portfolio_balance("USDC") # Uses connected wallet
balance2 = await client.get_portfolio_balance("USDC", address="0xOtherUser") # Different cache entry
Performance Impact
Expected reduction in API calls:
- Static data: ~99.9% fewer calls (1 call per hour vs. every request)
- Semi-static data: ~95% fewer calls (1 call per 15 min vs. frequent polling)
- Balance data: Significant reduction for applications polling balances
- Orderbook data: Useful for multi-component applications
See examples/caching_demo.py for complete examples.
Configuration
The SDK uses a centralized configuration system (DexalotConfig) that supports multiple initialization methods.
Options
| Option | Type | Default | Description |
|---|---|---|---|
parent_env |
str |
"fuji-multi" |
Environment configuration (e.g., production-multi-avax, fuji-multi) |
api_base_url |
str |
Auto-detected | Base URL for Dexalot API (derived from parent_env) |
private_key |
str |
None |
Wallet private key for signing transactions |
enable_cache |
bool |
True |
Enable/disable all caching behavior |
timeouts |
tuple |
(5, 30) |
Connect/Read timeouts for HTTP requests |
log_level |
str |
"INFO" |
Logging verbosity (DEBUG, INFO, WARNING, ERROR) |
log_format |
str |
"console" |
Log output format (console, json) |
connection_pool_limit |
int |
100 |
Total connection pool size across all hosts |
connection_pool_limit_per_host |
int |
30 |
Maximum connections per individual host |
Retry Settings
| Option | Type | Default | Description |
|---|---|---|---|
retry_enabled |
bool |
True |
Enable/disable automatic retry |
retry_max_attempts |
int |
3 |
Maximum number of retry attempts |
retry_initial_delay |
float |
1.0 |
Initial delay in seconds before first retry |
retry_max_delay |
float |
10.0 |
Maximum delay in seconds between retries |
retry_exponential_base |
float |
2.0 |
Exponential backoff multiplier |
retry_on_status |
tuple |
(429, 500, 502, 503, 504) |
HTTP status codes that trigger retry |
retry_on_exceptions |
tuple |
(aiohttp.ClientError, asyncio.TimeoutError) |
Exception types that trigger retry |
Rate Limiting Settings
| Option | Type | Default | Description |
|---|---|---|---|
rate_limit_enabled |
bool |
True |
Enable/disable rate limiting |
rate_limit_requests_per_second |
float |
5.0 |
Maximum API requests per second |
rate_limit_rpc_per_second |
float |
10.0 |
Maximum RPC calls per second |
Nonce Manager Settings
| Option | Type | Default | Description |
|---|---|---|---|
nonce_manager_enabled |
bool |
True |
Enable/disable nonce manager (prevents race conditions) |
WebSocket Settings
| Option | Type | Default | Description |
|---|---|---|---|
ws_manager_enabled |
bool |
False |
Enable/disable WebSocket Manager (persistent connections) |
ws_ping_interval |
int |
30 |
Seconds between ping messages |
ws_ping_timeout |
int |
10 |
Seconds to wait for pong before reconnecting |
ws_reconnect_initial_delay |
float |
1.0 |
Initial reconnect delay in seconds |
ws_reconnect_max_delay |
float |
60.0 |
Maximum reconnect delay in seconds |
ws_reconnect_exponential_base |
float |
2.0 |
Exponential backoff multiplier |
ws_reconnect_max_attempts |
int |
10 |
Maximum reconnection attempts (0 = infinite) |
ws_time_offset_ms |
int |
0 |
Clock skew compensation added to timestamps in WebSocket auth messages |
Precedence
Configuration values are resolved in the following order (highest to lowest priority):
- Constructor Arguments: Passed directly to
DexalotClient# 1. Highest Priority client = DexalotClient(parent_env="custom-env")
- Environment Variables: System-level variables
# 2. High Priority export PARENTENV="production-multi-avax"
.envFile: Variables loaded from local.envfile# 3. Medium Priority PARENTENV=fuji-multi
- Defaults: Hardcoded SDK defaults (
fuji-multi)
Advanced Configuration
For complex setups, you can pass a DexalotConfig object directly:
from dexalot_sdk import DexalotClient
from dexalot_sdk.core.config import DexalotConfig
config = DexalotConfig(
parent_env="production-multi-subnet",
timeouts=(10, 60),
enable_cache=False
)
client = DexalotClient(config=config)
Provider Failover
The SDK includes automatic RPC provider failover to improve reliability when a single RPC endpoint fails. This feature allows you to configure multiple RPC endpoints per chain, with automatic failover to backup providers when the primary provider fails.
Features
- Multiple Providers: Configure multiple RPC endpoints per chain (primary + fallbacks)
- Fail-Fast Strategy: Automatically switches to the next provider when the current one fails
- Health Tracking: Tracks provider health (failure counts, last failure time)
- Automatic Recovery: Failed providers are retried after a cooldown period
- Async-Safe: Concurrent operations are handled safely with asyncio locks; lock-free fast path when the primary provider is healthy
Configuration
Provider failover is enabled by default. You can configure it via environment variables or DexalotConfig:
| Variable | Description | Default |
|---|---|---|
DEXALOT_PROVIDER_FAILOVER_ENABLED |
Enable/disable failover | true |
DEXALOT_PROVIDER_FAILOVER_COOLDOWN |
Seconds before retrying failed provider | 60 |
DEXALOT_PROVIDER_FAILOVER_MAX_FAILURES |
Max failures before marking provider unhealthy | 3 |
RPC Provider Override
You can override RPC endpoints for specific chains using environment variables. This is useful for:
- Adding backup providers for redundancy
- Using custom RPC endpoints
- Testing with different providers
Two formats are supported:
- Chain ID format (preferred):
DEXALOT_RPC_<CHAIN_ID>=url1,url2,url3 - Native token symbol format:
DEXALOT_RPC_<NATIVE_TOKEN_SYMBOL>=url1,url2,url3
Chain ID takes precedence over native token symbol if both are set. Examples:
# Chain ID format (preferred)
DEXALOT_RPC_43114=https://api.avax.network/ext/bc/C/rpc,https://avalanche.public-rpc.com
DEXALOT_RPC_1=https://eth.llamarpc.com,https://ethereum.public-rpc.com
DEXALOT_RPC_42161=https://arb1.arbitrum.io/rpc
DEXALOT_RPC_432204=https://subnets.avax.network/dexalot/mainnet/rpc
# Native token symbol format (alternative)
DEXALOT_RPC_AVAX=https://api.avax.network/ext/bc/C/rpc,https://avalanche.public-rpc.com
DEXALOT_RPC_ETH=https://eth.llamarpc.com,https://ethereum.public-rpc.com
DEXALOT_RPC_ALOT=https://subnets.avax.network/dexalot/mainnet/rpc
How It Works
-
Provider Initialization: When the client initializes, it loads RPC endpoints from:
- Environment variable overrides (if set)
- API response (from Dexalot API)
- Multiple URLs can be provided (comma-separated)
-
Failover Strategy: When an RPC call fails:
- The failed provider is marked with a failure count
- The SDK automatically tries the next available provider
- If all providers fail, an error is raised
-
Health Tracking: Each provider tracks:
- Failure count (incremented on each failure)
- Last failure time (for cooldown calculation)
- Health status (healthy/unhealthy)
-
Recovery: After the cooldown period, failed providers can be retried. Providers are marked as unhealthy only after exceeding the max failure threshold.
Example
from dexalot_sdk import DexalotClient
from dexalot_sdk.core.config import DexalotConfig
# Configure failover
config = DexalotConfig(
provider_failover_enabled=True,
provider_failover_cooldown=60, # 60 seconds cooldown
provider_failover_max_failures=3, # Mark unhealthy after 3 failures
)
client = DexalotClient(config=config)
await client.connect()
await client.initialize_client()
# RPC calls use failover automatically when the primary provider fails (if enabled)
Provider failover behavior
- With
provider_failover_enabled=False, only the primary RPC URL is used (no rotation). - When the API returns a single provider entry, the client uses that URL directly.
- Environment variables can override failover settings as documented above.
RPC Security Settings
By default, plain http:// RPC URLs are rejected at provider setup time with a ValueError. This prevents accidental use of unencrypted connections in production.
| Option | Env Variable | Default | Description |
|---|---|---|---|
allow_insecure_rpc |
DEXALOT_ALLOW_INSECURE_RPC |
false |
Allow plain http:// RPC endpoints |
Security note: Plain
http://RPC connections transmit JSON-RPC calls (including signed transactions) without encryption. In production, always usehttps://endpoints. Only setallow_insecure_rpc=Truefor local development or trusted private networks.
# Allow http:// for local development only
config = DexalotConfig(allow_insecure_rpc=True)
client = DexalotClient(config=config)
Observability
The SDK includes a comprehensive instrumentation layer to track API operations, performance metrics, and WebSocket events.
Features
- Structured Logging: Logs are output in JSON format (or plain text) with metadata.
- Performance Tracking: Automatically tracks the duration of all core operations (
clob,swap,transfer). - Security: Designed with privacy by default:
- No Arguments: Function arguments and return values are never logged.
- No Payloads: Transaction payloads and private keys are never logged.
- Safe Defaults: Minimal logging in production (
INFO), detailed tracing only inDEBUG.
Configuration
Control logging behavior using environment variables:
| Variable | Description | Default | Values |
|---|---|---|---|
DEXALOT_LOG_LEVEL |
Logging verbosity | INFO |
DEBUG, INFO, WARNING, ERROR |
DEXALOT_LOG_FORMAT |
Log output format | console |
json, console |
Instrumented Components
- CLOB: Full coverage of Order Management (
add/cancel/replace), Market Data (orderbook,pairs), and Account Data (open_orders). - Swap: RFQ operation lifecycle including Firm/Soft Quotes and Swap Execution.
- Transfer: Cross-chain Bridge operations (
deposit/withdraw), Portfolio Management (transfer_portfolio), and comprehensive Balance queries. - WebSocket: Connection lifecycle events (
Open/Close/Error) and message traffic (atDEBUGlevel).
Example Output
{
"timestamp": "2023-10-27T10:00:00Z",
"level": "INFO",
"logger": "dexalot_sdk.core.clob",
"message": "clob completed in 0.123s",
"extra_fields": {
"operation": "clob",
"function": "add_order",
"duration": 0.123,
"status": "success"
}
}
See examples/logging_console.py for a demonstration of logging capabilities.
Resource Cleanup
The SDK manages several resources that need proper cleanup:
- HTTP sessions (
aiohttp.ClientSession) - Web3 provider sessions (internal
aiohttpsessions) - WebSocket connections (if WebSocket manager is enabled)
Always Close the Client
Always call await client.close() when you're done with the client to ensure proper resource cleanup:
async def main():
client = None
try:
client = DexalotClient()
await client.initialize_client()
# Your operations here
result = await client.get_tokens()
if result.success:
print(f"Tokens: {result.data}")
finally:
# Always close the client in a finally block
if client is not None:
await client.close()
Context Manager (Alternative)
You can also use the client as an async context manager:
async def main():
async with DexalotClient() as client:
await client.initialize_client()
# Your operations here
result = await client.get_tokens()
if result.success:
print(f"Tokens: {result.data}")
# Client is automatically closed when exiting the context
Note: The close() method:
- Closes all HTTP sessions (SDK's main session and web3 provider sessions)
- Closes WebSocket connections (if enabled)
- Waits for graceful SSL connection shutdown
- Is safe to call multiple times (idempotent)
Async Usage
The SDK is fully async - all methods are async def and must be awaited. This enables concurrent operations and better performance.
Script Usage (asyncio.run)
For standalone scripts, use asyncio.run():
import asyncio
from dexalot_sdk import DexalotClient
async def main():
client = None
try:
client = DexalotClient()
await client.initialize_client()
# Your async operations here
result = await client.get_tokens()
if result.success:
print(f"Tokens: {result.data}")
finally:
if client is not None:
await client.close()
if __name__ == "__main__":
asyncio.run(main())
Application Usage (async context)
In async applications (e.g., FastAPI, async web frameworks), use await directly:
from fastapi import FastAPI
from dexalot_sdk import DexalotClient
app = FastAPI()
client = DexalotClient()
@app.on_event("startup")
async def startup():
await client.initialize_client()
@app.on_event("shutdown")
async def shutdown():
await client.close()
@app.get("/tokens")
async def get_tokens():
result = await client.get_tokens()
if result.success:
return result.data
return {"error": result.error}
Parallel Operations
The async architecture enables parallel operations for better performance:
import asyncio
from dexalot_sdk import DexalotClient
async def main():
client = None
try:
client = DexalotClient()
await client.initialize_client()
# Fetch multiple orderbooks in parallel
pairs = ["AVAX/USDC", "ALOT/USDC", "ETH/USDC"]
results = await asyncio.gather(
*[client.get_orderbook(pair) for pair in pairs]
)
for pair, result in zip(pairs, results, strict=True):
if result.success:
print(f"{pair}: {len(result.data['bids'])} bids")
finally:
if client is not None:
await client.close()
See examples/async_parallel.py for more parallel operation examples.
Error Handling
The SDK uses a Result[T] pattern for consistent error handling across async operational methods.
Result Pattern
Async operational methods return Result[T] with three fields:
success: bool- True if operation succeededdata: T | None- Result data on success, None on errorerror: str | None- Error message on failure, None on success
Basic Error Handling
result = await client.add_order(
pair="AVAX/USDC",
side="BUY",
amount=1.0,
price=25.0,
)
if result.success:
print(f"Order placed: {result.data['tx_hash']}")
print(f"Client order ID: {result.data['client_order_id']}") # save for cancel/replace
else:
print(f"Order failed: {result.error}")
# Handle error (retry, log, notify user, etc.)
Validation Errors
Input validation errors are returned as Result.fail() with descriptive messages:
# Invalid amount (negative)
result = await client.add_order(
pair="AVAX/USDC",
side="BUY",
amount=-1.0, # Invalid!
price=25.0
)
if not result.success:
# result.error will be: "Invalid amount: must be positive (> 0), got -1.0"
print(f"Validation error: {result.error}")
Error Sanitization
Error messages are automatically sanitized to prevent information leakage:
- File paths are removed
- URLs are removed
- Stack traces are removed
- User-friendly messages are provided
Best Practices
- Always check
result.successbefore accessingresult.data - Handle errors appropriately - log, retry, or notify users
- Use descriptive error messages - the SDK provides clear error messages
- Don't expose internal errors - error sanitization is automatic
async def place_order_safely(client, pair, side, amount, price):
result = await client.add_order(pair, side, amount, price)
if result.success:
return {"status": "success", "tx_hash": result.data["tx_hash"]}
else:
# Log error for debugging
logger.error(f"Order failed: {result.error}")
# Return user-friendly message
return {"status": "error", "message": "Failed to place order. Please try again."}
See examples/error_handling.py for comprehensive error handling patterns.
Transaction Receipt Handling
All state-changing operations (placing orders, deposits, withdrawals, etc.) now support a wait_for_receipt parameter that controls whether the SDK waits for blockchain transaction confirmation before returning.
Default Behavior
By default, all state-changing operations wait for transaction receipts (wait_for_receipt=True). This ensures:
- Transactions are confirmed on-chain before the method returns
- Transaction failures are detected immediately
- More reliable operation results
Usage
# Default behavior: waits for receipt (recommended)
result = await client.add_order("AVAX/USDC", "BUY", 1.0, 25.0)
# Method returns only after transaction is confirmed
# Explicitly wait for receipt
result = await client.add_order(
"AVAX/USDC", "BUY", 1.0, 25.0,
wait_for_receipt=True
)
# Don't wait for receipt (returns immediately after sending)
result = await client.add_order(
"AVAX/USDC", "BUY", 1.0, 25.0,
wait_for_receipt=False
)
# Method returns immediately with transaction hash
# Transaction may still be pending
When to Use wait_for_receipt=False
Use wait_for_receipt=False when:
- Batch operations: Sending many transactions and want to submit them quickly
- Fire-and-forget: You don't need immediate confirmation
- Custom polling: You'll check transaction status yourself
Important: When wait_for_receipt=False, the SDK returns immediately after broadcasting the transaction. You should:
- Check transaction status yourself using the returned
tx_hash - Handle potential transaction failures in your application logic
- Be aware that the transaction may still be pending when the method returns
Affected Methods
All state-changing methods support wait_for_receipt:
CLOB Operations:
add_order(pair, side, amount, price, order_type="LIMIT", client_order_id=None, wait_for_receipt=True)add_limit_order_list(orders, wait_for_receipt=True)cancel_order(order_id, wait_for_receipt=True)cancel_order_by_client_id(client_order_id, wait_for_receipt=True)cancel_list_orders(order_ids, wait_for_receipt=True)cancel_list_orders_by_client_id(client_order_ids, wait_for_receipt=True)replace_order(order_id, new_price, new_amount, client_order_id=None, wait_for_receipt=True)cancel_add_list(replacements, wait_for_receipt=True)
Order ID Semantics
Every canonical SDK order has two identifiers, one transaction hash for state-changing actions, and a normalized order shape for reads:
| Field | Source | Description |
|---|---|---|
internal_order_id |
Contract-assigned | Assigned by the TradePairs contract at placement; present in all order data returned by get_open_orders, get_order, and get_order_by_client_id |
client_order_id |
Caller-specified | Provided by the caller at placement (or generated by the SDK when omitted); echoed back in every placement and cancel/replace result |
tx_hash |
Blockchain | Transaction hash for the on-chain action; not an order identifier |
Order reads (get_open_orders, get_order, get_order_by_client_id) return a full canonical order dict with these fields:
internal_order_id,client_order_id,trade_pair_id,pairprice,total_amount,quantity,quantity_filled,total_feetrader_address,side,type1,type2,statusupdate_block,create_block,create_ts,update_ts
Enum-style fields are normalized to human-readable strings such as BUY, SELL, LIMIT, GTC, and FILLED. Block fields are returned as Python integers, not hex strings.
Placement methods — all four placement functions (add_order, add_limit_order_list, replace_order, cancel_add_list) accept an optional client_order_id. When omitted, the SDK generates a random 32-byte value. The result always contains client_order_id (or client_order_ids for batch calls) so you can record the ID for later operations.
Cancel/replace results — cancel and replace methods return typed ID fields so you always know exactly what was cancelled and what was created:
| Method | Returns |
|---|---|
cancel_order |
cancelled_client_order_id, cancelled_internal_order_id |
cancel_order_by_client_id |
cancelled_client_order_id |
cancel_list_orders |
cancelled_internal_order_ids |
cancel_list_orders_by_client_id |
cancelled_client_order_ids |
replace_order |
cancelled_client_order_id, cancelled_internal_order_id, client_order_id (new) |
cancel_add_list |
cancelled_client_order_ids, cancelled_internal_order_ids, client_order_ids (new) |
Routing by identifier type:
get_order()andcancel_order()andreplace_order()accept either type (order_idparameter).get_order_by_client_id()andcancel_order_by_client_id()take aclient_order_idspecifically.cancel_add_list()acceptsorder_idper replacement (either type); also inferspairfrom the existing order when possible.- Never pass
tx_hashto order lookup, cancel, or replace methods.
Transfer Operations:
deposit(token, amount, source_chain, use_layerzero=False, wait_for_receipt=True)withdraw(token, amount, destination_chain, use_layerzero=False, wait_for_receipt=True)add_gas(amount, wait_for_receipt=True)remove_gas(amount, wait_for_receipt=True)transfer_portfolio(token, amount, to_address, wait_for_receipt=True)
Swap Operations:
execute_rfq_swap(quote, wait_for_receipt=True)
Example: Batch Order Placement
# Place multiple orders without waiting for each receipt
orders = [
{"pair": "AVAX/USDC", "side": "BUY", "amount": 1.0, "price": 25.0},
{"pair": "AVAX/USDC", "side": "BUY", "amount": 2.0, "price": 24.0},
{"pair": "AVAX/USDC", "side": "SELL", "amount": 1.0, "price": 26.0},
]
# Submit all orders quickly without waiting
result = await client.add_limit_order_list(orders, wait_for_receipt=False)
if result.success:
tx_hash = result.data["tx_hash"]
# Check status later
# await check_transaction_status(tx_hash)
Example: Fire-and-Forget Deposit
# Submit deposit and continue with other operations
result = await client.deposit("AVAX", 1.0, "Avalanche", wait_for_receipt=False)
if result.success:
tx_hash = result.data # Just the transaction hash
# Continue with other operations
# Monitor deposit status separately
API Field Name Standardization
The SDK automatically standardizes API response field names to match Python naming conventions (snake_case). This ensures consistent field names regardless of API response format variations.
Standardized Fields
Orders API:
internal_order_id(fromid)client_order_id(fromclientordid,clientOrderId)trade_pair_id(fromtradePairId, or derived frompairwhen needed)pair,price,quantity,total_amount,quantity_filled,total_feetrader_address,side,type1,type2,statuscreate_block,update_block,create_ts,update_ts
Orders are normalized into one canonical SDK shape regardless of whether the source was the REST API or the contract.
Environments API:
chain_id(fromchainid,chainId)env_type(fromtype,envType)rpc(fromchain_instance)network(fromchain_display_name)
Tokens API:
evm_decimals(fromevmdecimals,evmDecimals,decimals)chain_id(fromchainid,chainId)network(fromchain_display_name)
Pairs API:
base_decimals,quote_decimalsbase_display_decimals,quote_display_decimalsmin_trade_amount,max_trade_amount
RFQ Quotes API:
chainId(fromchainid,chain_id)secureQuote(fromsecurequote,secure_quote)quoteId(fromquoteid,quote_id)- Nested order data:
nonceAndMeta,makerAsset,takerAsset,makerAmount,takerAmount
Deployment API:
env,address,abi(handles variations likeEnv,Address,Abi)
Benefits
- Consistent interface: Field names are exposed in snake_case in Python consistently.
- Alias handling: Common camelCase and alternate keys from the API are normalized automatically.
All API responses are automatically transformed before being returned, so you can always rely on standardized field names.
Reliability Features
The SDK includes several reliability features that work automatically to improve stability and performance.
Retry Mechanism
Automatic retry with exponential backoff for transient failures:
- Default: 3 attempts with exponential backoff (1s, 2s, 4s)
- Retries on: HTTP 429, 500, 502, 503, 504 and network errors
- Configurable: Via
DexalotConfigor environment variables
from dexalot_sdk import DexalotClient
from dexalot_sdk.core.config import DexalotConfig
# Custom retry configuration
config = DexalotConfig(
retry_enabled=True,
retry_max_attempts=5,
retry_initial_delay=2.0, # Start with 2s delay
retry_max_delay=30.0, # Max 30s between retries
retry_exponential_base=2.0
)
client = DexalotClient(config=config)
Rate Limiting
Token bucket rate limiter prevents API throttling:
- Default: 5 requests/second for API, 10 requests/second for RPC
- Automatic: Applied to all HTTP and RPC calls
- Configurable: Via
DexalotConfigor environment variables
config = DexalotConfig(
rate_limit_enabled=True,
rate_limit_requests_per_second=10.0, # 10 API calls/second
rate_limit_rpc_per_second=20.0 # 20 RPC calls/second
)
Nonce Manager
Automatic nonce management prevents transaction race conditions:
- Automatic: Tracks nonces per (chain_id, address) combination
- Thread-safe: Uses async locks for concurrent transactions
- Default-on: No manual nonce bookkeeping for normal use
The nonce manager is enabled by default and works automatically. It:
- Fetches the current nonce from the chain on first use
- Tracks nonces locally for subsequent transactions
- Automatically increments nonces for each transaction
- Prevents race conditions in concurrent scenarios
# Nonce manager works automatically - no configuration needed
# For high-concurrency scenarios, it's already handling nonces correctly
# Multiple transactions can be sent concurrently
tasks = [
client.add_order("AVAX/USDC", "BUY", 1.0, 25.0),
client.add_order("ALOT/USDC", "BUY", 10.0, 0.5),
client.deposit("AVAX", 1.0)
]
results = await asyncio.gather(*tasks)
# Nonce manager ensures correct nonce ordering
Provider Failover
Automatic RPC provider failover (see Provider Failover section above).
WebSocket Manager
The SDK includes a persistent WebSocket manager for long-running subscriptions with automatic reconnection and heartbeat.
Features
- Persistent Connections: Single connection for multiple subscriptions
- Multiple Subscriptions: Subscribe to multiple topics with individual callbacks
- Automatic Reconnection: Exponential backoff reconnection on failures
- Heartbeat Monitoring: Ping/pong mechanism to detect dead connections
- Thread-Safe: Safe for concurrent use
Basic Usage
from dexalot_sdk import DexalotClient
async def main():
client = None
try:
client = DexalotClient()
await client.initialize_client()
# Enable WebSocket manager
config = client.config
config.ws_manager_enabled = True
# Subscribe to orderbook updates
def on_orderbook_update(message):
print(f"Orderbook update: {message}")
await client.subscribe_to_events(
topic="OrderBook/AVAX/USDC",
callback=on_orderbook_update,
is_private=False
)
# Subscribe to private order updates
def on_order_update(message):
print(f"Order update: {message}")
await client.subscribe_to_events(
topic="Orders",
callback=on_order_update,
is_private=True
)
# Keep connection alive
await asyncio.sleep(60)
# Unsubscribe when done
client.unsubscribe_from_events("OrderBook/AVAX/USDC")
finally:
# Always close the client to clean up WebSocket and HTTP sessions
if client is not None:
await client.close()
asyncio.run(main())
Configuration
from dexalot_sdk.core.config import DexalotConfig
config = DexalotConfig(
ws_manager_enabled=True,
ws_ping_interval=30, # Ping every 30 seconds
ws_ping_timeout=10, # Wait 10s for pong before reconnecting
ws_reconnect_initial_delay=1.0,
ws_reconnect_max_delay=60.0,
ws_reconnect_exponential_base=2.0,
ws_reconnect_max_attempts=10 # 0 = infinite retries
)
client = DexalotClient(config=config)
One-Off Connections
Use subscribe_to_events() with the manager enabled:
async def on_message(message):
print(f"Received: {message}")
# Start the manager on first subscription
await client.subscribe_to_events(
topic="OrderBook/AVAX/USDC",
callback=on_message,
is_private=False
)
# Later, unsubscribe and close the client when done
client.unsubscribe_from_events("OrderBook/AVAX/USDC")
See examples/websocket_manager.py for complete examples.
Input Validation
The SDK automatically validates all input parameters before processing operations. This prevents invalid data from reaching the blockchain or API. Validation is implemented in utils/input_validators.py and returns Result[None] for consistent error handling.
Automatic Validation
Input validation is applied to all critical methods:
- CLOB Operations:
add_order(),cancel_order(),get_orderbook(), etc. - Swap Operations:
execute_rfq_swap(),get_swap_firm_quote(), etc. - Transfer Operations:
deposit(),withdraw(),transfer_portfolio(), etc.
Validation Rules
- Amounts: Must be positive, finite numbers (not NaN or infinite)
- Prices: Must be positive, finite numbers
- Addresses: Must be valid Ethereum addresses (0x prefix, 42 chars, hex)
- Pairs: Must be in
TOKEN/TOKENformat - Order IDs: Must be valid prefixed hex, decimal-string internal IDs, bytes32 values, or plain client IDs that fit in bytes32
- Token Symbols: Must be non-empty, alphanumeric strings
Handling Validation Errors
Validation errors are returned as Result.fail() with descriptive messages:
# Invalid amount
result = await client.add_order(
pair="AVAX/USDC",
side="BUY",
amount=-1.0, # Invalid: negative amount
price=25.0
)
if not result.success:
# result.error: "Invalid amount: must be positive (> 0), got -1.0"
print(result.error)
# Invalid address
result = await client.get_portfolio_balance(
token="USDC",
address="invalid" # Not a valid Ethereum address
)
if not result.success:
# result.error: "Invalid address: must be a valid Ethereum address (0x prefix, 42 chars, hex)"
print(result.error)
Common Validation Errors
| Error | Cause | Solution |
|---|---|---|
| "Invalid amount: must be positive" | Negative or zero amount | Use positive values |
| "Invalid address: must be a valid Ethereum address" | Invalid address format | Use 0x-prefixed hex addresses |
| "Invalid pair: must be in TOKEN/TOKEN format" | Invalid pair format | Use format like "AVAX/USDC" |
| "Invalid order_id: must be hex string or bytes32" | Invalid order ID | Use valid hex string |
Validation happens before any network calls, so invalid inputs fail fast with clear error messages.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dexalot_sdk-0.5.12.tar.gz.
File metadata
- Download URL: dexalot_sdk-0.5.12.tar.gz
- Upload date:
- Size: 119.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6122c7d0455038d536e27c4345856fcf536be10d2efae067672107943ad1ad09
|
|
| MD5 |
0fbb0d8b3fcdbb88d709ffa2c09b9419
|
|
| BLAKE2b-256 |
9ae5b8a4fb601186aaaac13b172107f18d05590f686c112d2630fdc60ad3bc66
|
Provenance
The following attestation bundles were made for dexalot_sdk-0.5.12.tar.gz:
Publisher:
pypi.yml on Dexalot/dexalot-sdk-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dexalot_sdk-0.5.12.tar.gz -
Subject digest:
6122c7d0455038d536e27c4345856fcf536be10d2efae067672107943ad1ad09 - Sigstore transparency entry: 1244553873
- Sigstore integration time:
-
Permalink:
Dexalot/dexalot-sdk-python@bfa3ef3ca30eb6fc639544472a96e4a767900de6 -
Branch / Tag:
refs/tags/v0.5.12 - Owner: https://github.com/Dexalot
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yml@bfa3ef3ca30eb6fc639544472a96e4a767900de6 -
Trigger Event:
push
-
Statement type:
File details
Details for the file dexalot_sdk-0.5.12-py3-none-any.whl.
File metadata
- Download URL: dexalot_sdk-0.5.12-py3-none-any.whl
- Upload date:
- Size: 104.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f8a457a6253139546caeb6530ad5d9acfa442c3c18d057a8b3441d2cb8aa3fa5
|
|
| MD5 |
5e7d8f0e1f8742ca4a2b24e701789780
|
|
| BLAKE2b-256 |
b922933e9b82d3dfcf53339de775a7c6afebac7991d10863137c423473d2d05a
|
Provenance
The following attestation bundles were made for dexalot_sdk-0.5.12-py3-none-any.whl:
Publisher:
pypi.yml on Dexalot/dexalot-sdk-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dexalot_sdk-0.5.12-py3-none-any.whl -
Subject digest:
f8a457a6253139546caeb6530ad5d9acfa442c3c18d057a8b3441d2cb8aa3fa5 - Sigstore transparency entry: 1244553888
- Sigstore integration time:
-
Permalink:
Dexalot/dexalot-sdk-python@bfa3ef3ca30eb6fc639544472a96e4a767900de6 -
Branch / Tag:
refs/tags/v0.5.12 - Owner: https://github.com/Dexalot
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yml@bfa3ef3ca30eb6fc639544472a96e4a767900de6 -
Trigger Event:
push
-
Statement type: