@dexalot/dexalot-sdk
v0.6.0
Published
Here is our first public release of Dexalot SDK for TypeScript. 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.
Downloads
177
Readme
Dexalot TypeScript SDK
Disclaimer
Here is our first public release of Dexalot SDK for TypeScript. 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.
Pleaase Note: The public interface may undergo breaking changes.
Overview
dexalot-sdk is a TypeScript/JavaScript 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: Functional mixins for CLOB, Swap, and Transfer operations
- Multi-Chain Support: Works with Dexalot L1 subnet and connected mainnet networks
- Type Safety: Full TypeScript support with comprehensive type definitions
- Caching: TTL-based memory cache utilities for performance optimization
Architecture
Core Components
core/client.ts: UnifiedDexalotClientcomposed from modular mixinscore/base.ts: Environment setup, Web3 connections, error handlingcore/clob.ts: Central Limit Order Book trading operationscore/swap.ts: SimpleSwap RFQ (Request for Quote) functionalitycore/transfer.ts: Cross-chain deposits/withdrawals, portfolio management
Utilities
utils/input_validators.ts: Validate SDK method input parameters (amounts, addresses, pairs, etc.)utils/cache.ts: TTL-based caching utilities (MemoryCache,withCache,withInstanceCache)utils/observability.ts: Structured logging and operation trackingutils/result.ts: StandardizedResult<T>type for consistent error handlingutils/retry.ts: Async retry decorator with exponential backoffutils/rateLimit.ts: Token bucket rate limiter for API and RPC callsutils/nonceManager.ts: Thread-safe nonce management to prevent transaction race conditionsutils/providerManager.ts: RPC provider failover with health trackingutils/errorSanitizer.ts: Error message sanitization to prevent information leakageutils/websocketManager.ts: Persistent WebSocket connection manager with reconnection and heartbeat
Installation
Install the SDK using pnpm (recommended):
pnpm add @dexalot/dexalot-sdkOr use alternative package managers:
# npm
npm install @dexalot/dexalot-sdk
# yarn
yarn add @dexalot/dexalot-sdkOr install directly from the repository (the prepare script builds
dist/ automatically, so no separate build step is needed):
pnpm add github:Dexalot/dexalot-sdk-typescriptPackage exports
dexalot-sdk: Default exportDexalotClient, plusDexalotConfig(type),createConfig,loadConfigFromEnv,MemoryCache,Result,getLogger/Logger,version, andgetVersion().dexalot-sdk/secrets-vault:generateSecretsVaultKey,secretsVaultGet/Set/List/Remove.dexalot-sdk/internal:BaseClient,CLOBClient,SwapClient,TransferClient,Utils, types, constants, and the rest of the implementation surface for advanced use (excludes the secrets vault; use the subpath above).
Call await client.initializeClient() before trading RPC/API usage, optionally await client.connect(), and await client.close() when tearing down. Successful on-chain Result payloads use camelCase fields such as txHash, operation, and batch id lists where applicable.
Secrets Vault
The Node-only secrets vault is exported from dexalot-sdk/secrets-vault. It stores encrypted values in a local Fernet-encrypted JSON file using the shared Dexalot vault format, so Python and TypeScript tooling can read the same vault file.
Default path:
~/.dexalot/secrets_vault.jsonEnvironment variables:
DEXALOT_SECRETS_VAULT_KEY=<your-fernet-key>
DEXALOT_SECRETS_VAULT_PATH=~/.dexalot/secrets_vault.jsonExample:
import {
generateSecretsVaultKey,
secretsVaultSet,
secretsVaultGet,
} from "dexalot-sdk/secrets-vault";
const key = generateSecretsVaultKey();
secretsVaultSet("~/.dexalot/secrets_vault.json", "PRIVATE_KEY", "0x...", key);
const result = secretsVaultGet("~/.dexalot/secrets_vault.json", "PRIVATE_KEY", key);A CLI helper is also available:
npm run secrets-vault -- keygen
npm run secrets-vault -- add PRIVATE_KEY 0xabc123...Quick Start
import DexalotClient from 'dexalot-sdk';
async function main() {
let client: DexalotClient | null = null;
try {
// Initialize client
client = new DexalotClient();
const result = await client.initializeClient();
if (!result.success) {
console.error(`Initialization failed: ${result.error}`);
return;
}
// Fetch trading pairs (stores pairs in client.pairs)
const pairsResult = await client.getClobPairs();
if (pairsResult.success) {
console.log(`Available pairs: ${Object.keys(client.pairs)}`);
} else {
console.error(`Error: ${pairsResult.error}`);
}
} finally {
// Always close the client to clean up resources
if (client !== null) {
await client.close();
}
}
}
// Run the async function
main().catch(console.error);Key Points:
- The SDK is fully async - all methods must be awaited
- All methods return
Result<T>for consistent error handling - Use
async/awaitfor async contexts - Always call
await client.close()when done to clean up resources
Usage
Basic Async Usage
import DexalotClient from 'dexalot-sdk';
async function main() {
let client: DexalotClient | null = null;
try {
client = new DexalotClient();
// Initialize client (required before other operations)
const initResult = await client.initializeClient();
if (!initResult.success) {
console.error(`Failed to initialize: ${initResult.error}`);
return;
}
// Get available trading pairs (stores pairs in client.pairs)
const pairsResult = await client.getClobPairs();
if (pairsResult.success) {
console.log(`Found ${Object.keys(client.pairs).length} trading pairs`);
} else {
console.error(`Error fetching pairs: ${pairsResult.error}`);
}
} finally {
// Always close the client to clean up resources
if (client !== null) {
await client.close();
}
}
}
main().catch(console.error);Error Handling with Result Pattern
All SDK methods return Result<T> which provides consistent error handling:
const result = await client.getOrderBook("AVAX/USDC");
if (result.success) {
const orderbook = result.data;
console.log(`Bids: ${orderbook.bids}`);
console.log(`Asks: ${orderbook.asks}`);
} else {
console.error(`Error: ${result.error}`);
// Handle error appropriately
}Order history
const result = await client.getOrderHistory(undefined, {
pair: "ALOT/USDC",
status: "FILLED",
limit: 50,
});
if (result.success) {
for (const order of result.data) {
console.log(`${order.pair} ${order.side} ${order.quantity} @ ${order.price}`);
}
}Order Types & Time-in-Force
CLOB orders are described by three on-chain fields, all optional on OrderRequest
and defaulting to today's behavior:
type(type1) —'LIMIT'(default) or'MARKET'.timeInForce(type2) —'GTC'(default),'FOK','IOC', or'PO'(Post-Only). Aliases like'POST_ONLY'/'FILL_OR_KILL'are accepted.stp(self-trade prevention) —'CANCEL_TAKER'(default),'CANCEL_MAKER','CANCEL_BOTH', or'CANCEL_NONE'(contract spellings'CANCELTAKER'etc. also accepted).
Stop / stop-limit orders cannot be placed: although the contract
Type1enum reservesSTOP/STOPLIMIT, they are unused on-chain, sotypeaccepts onlyMARKET/LIMIT. Order reads still labeltype12/3 asSTOP/STOPLIMITfor fidelity with the contract enum.
The SDK only pre-validates the rule the contract relies on — a LIMIT order
requires a price — and defers the rest to on-chain reverts (MARKET ignores
timeInForce/price; Post-Only, per-pair allowed types, FOK and self-trade are
enforced on-chain: T-IVOT-01, T-POOA-01, T-T2PO-01, T-FOKF-01,
T-STPR-01).
// Limit IOC
await client.addOrder({ pair: 'AVAX/USDC', side: 'BUY', amount: 1, price: 25, timeInForce: 'IOC' });
// Post-Only (maker-only)
await client.addOrder({ pair: 'AVAX/USDC', side: 'SELL', amount: 1, price: 25, timeInForce: 'PO' });
// Market BUY (no price)
await client.addOrder({ pair: 'AVAX/USDC', side: 'BUY', amount: 1, type: 'MARKET' });
// Self-trade prevention
await client.addOrder({ pair: 'AVAX/USDC', side: 'BUY', amount: 1, price: 25, stp: 'CANCEL_MAKER' });
// Batch with mixed types (addOrderList is an alias for addLimitOrderList)
await client.addOrderList([
{ pair: 'AVAX/USDC', side: 'BUY', amount: 1, price: 24, timeInForce: 'PO' },
{ pair: 'AVAX/USDC', side: 'SELL', amount: 1, type: 'MARKET', timeInForce: 'IOC' },
]);replaceOrder uses the contract's cancelReplaceOrder, which carries only a
new price and quantity — the replacement inherits the original order's
type1/timeInForce/stp. To change those, cancel and place a new order
(e.g. via cancelAddList).
Dependencies
ethers>=6.0.0: Multi-chain blockchain interactionsaxios: HTTP client for Dexalot API communicationdotenv: Environment variable management
Testing
Run tests from the package directory:
pnpm test # Unit tests
pnpm test:unit # Unit tests only
pnpm test:int # Integration testsRelease
Releases are tag-driven. Pushing a v* tag to main triggers
.github/workflows/npm.yml, which builds the package and publishes
to NPM via trusted publishing (OIDC — no long-lived NPM token is
stored anywhere) with npm publish --provenance for supply-chain
attestation.
Release gates (all enforced by the workflow):
github.ref_typemust betag(notbranch).- The tagged commit must be reachable from
origin/main(git merge-base --is-ancestor). Tags on throwaway branches will not publish. - The tag name must equal
v{package.json version}.
Steps:
Sync the version across all version-bearing files:
pnpm run version:bump:patch # touches package.json, VERSION, src/version.tsReview the diff, commit on a feature branch, and merge a PR into
main.From
main, tag and push:git checkout main && git pull git tag -a v<new-version> -m "Release v<new-version>" git push origin v<new-version>Watch the Publish to NPM workflow in GitHub Actions. On green, verify the new version at https://npmjs.com/package/@dexalot/dexalot-sdk.
⚠️ Once a version is published to NPM it cannot be re-uploaded under the same number;
npm deprecateis the available remediation. Always bump the version before tagging.Version numbers
v0.5.18andv0.5.19previously existed as local-only orphan tags and were never published. Reusing them would confuse operators — the next release afterv0.5.17should start atv0.5.20or later.
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, mainnets | | 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 DexalotClient from 'dexalot-sdk';
// Caching is enabled by default
const client = new DexalotClient();
await client.initializeClient();
// First call fetches from API
const balances = await client.getAllPortfolioBalances();
console.log(balances);
// { ALOT: { available: 95.5, locked: 4.5, total: 100.0 }, AVAX: ... }
// Second call within 10 seconds returns cached result
const cachedBalances = await client.getAllPortfolioBalances(); // Cached!Configuration
Customize cache behavior during client initialization:
import DexalotClient, { createConfig } from 'dexalot-sdk';
// Disable caching entirely
const clientNoCache = new DexalotClient(createConfig({ cacheEnabled: false }));
// Custom TTL values (in seconds)
const clientCustomCache = new DexalotClient(createConfig({
cacheEnabled: true,
cacheTtlStatic: 7200, // 2 hours for static data
cacheTtlSemiStatic: 1800, // 30 minutes for semi-static
cacheTtlBalance: 5, // 5 seconds for balances
cacheTtlOrderbook: 0.5 // 500ms for orderbook
}));Cache Invalidation
Manually clear cached data when needed:
// Clear all cache levels
client.invalidateCache();
// Clear specific cache level
client.invalidateCache('balance'); // Options: 'static', 'semi_static', 'balance', 'orderbook', 'all'Cached Methods
Static Data (1 hour):
getEnvironments()getChains()getDeployment()(also caches per{env, contractType, returnAbi}filter combination)getTokenPriceHistory(token, opts)getTokenHourlyPriceHistory(token, opts)
Semi-Static Data (15 minutes):
getTokens()getClobPairs()getSwapPairs(chainId)getTokenUsdPrices(env?)
Balance Data (10 seconds):
getPortfolioBalance(token, address?)getAllPortfolioBalances(address?)getChainWalletBalance(chain, token, address?)getChainWalletBalances(chain, address?)getAllChainWalletBalances(address?)getOrderHistory(account?, opts?)getCombinedTransfers(opts?)—opts:{ symbol?, fromTs?, toTs?, limit?, offset? }| Param | Type | Description | |---|---|---| |
symbol|string| Token symbol filter; canonicalized vianormalizeToken(casing + aliases collapse). | |fromTs|number| Inclusive window start, unix seconds. Sent to the backend asperiodfrom. | |toTs|number| Inclusive window end, unix seconds. Sent to the backend asperiodto. | |limit|number| Page size (default100). Translates toitemsperpage = max(1, limit). | |offset|number| Rows to skip (default0). Translates topageno = floor(offset / itemsperpage) + 1. |limit/offsettranslate to the backend'sitemsperpage/pagenopaging andfromTs/toTstoperiodfrom/periodtointernally; the cache key is built from the translated values, so equivalentlimit/offsetcombos that land on the same page share a cache slot.
Orderbook Data (1 second):
getOrderBook(pair)
Note: Write operations (e.g., addOrder(), cancelOrder(), deposit(), withdraw()) are never cached to ensure data integrity.
Per-User Caching
Balance data is cached per user address. When address is not provided, the SDK uses the connected wallet's address:
// Each user gets their own cached balance data
const balance1 = await client.getPortfolioBalance("USDC"); // Uses connected wallet
const balance2 = await client.getPortfolioBalance("USDC", "0xOtherUser"); // Different cache entryPerformance 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
Configuration
The SDK uses a centralized configuration system (DexalotConfig) that supports multiple initialization methods.
Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| parentEnv | string | "fuji-multi" | Environment configuration (e.g., production-multi-avax, fuji-multi) |
| apiBaseUrl | string | Auto-detected | Base URL for Dexalot API (derived from parentEnv) |
| privateKey | string | undefined | Wallet private key for signing transactions |
| cacheEnabled | boolean | true | Enable/disable all caching behavior |
| timeoutConnect | number | 5 | Connect timeout in seconds (env parity with Python; axios uses read timeout as the request cap) |
| timeoutRead | number | 30 | Read timeout in seconds (axios request timeout = this value × 1000 ms) |
| logLevel | string | "info" | Logging verbosity (debug, info, warn, error) |
| logFormat | string | "console" | Log output format (console, json) |
| connectionPoolLimit | number | 100 | Total connection pool size across all hosts |
| connectionPoolLimitPerHost | number | 30 | Maximum connections per individual host |
Retry Settings
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| retryEnabled | boolean | true | Enable/disable automatic retry |
| retryMaxAttempts | number | 3 | Maximum number of retry attempts |
| retryInitialDelay | number | 1 | Initial delay in seconds before first retry |
| retryMaxDelay | number | 10 | Maximum delay in seconds between retries |
| retryExponentialBase | number | 2.0 | Exponential backoff multiplier |
| retryOnStatus | number[] | [429, 500, 502, 503, 504] | HTTP status codes that trigger retry |
Rate Limiting Settings
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| rateLimitEnabled | boolean | true | Enable/disable rate limiting |
| rateLimitRequestsPerSecond | number | 5.0 | Maximum API requests per second |
| rateLimitRpcPerSecond | number | 10.0 | Maximum RPC calls per second |
Nonce Manager Settings
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| nonceManagerEnabled | boolean | true | Enable/disable nonce manager (prevents race conditions) |
WebSocket Settings
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| wsManagerEnabled | boolean | false | Enable/disable WebSocket Manager (persistent connections) |
| wsPingInterval | number | 30 | Seconds between ping messages |
| wsPingTimeout | number | 10 | Seconds to wait for pong before reconnecting |
| wsReconnectInitialDelay | number | 1 | Initial reconnect delay in seconds |
| wsReconnectMaxDelay | number | 60 | Maximum reconnect delay in seconds |
| wsReconnectExponentialBase | number | 2.0 | Exponential backoff multiplier |
| wsReconnectMaxAttempts | number | 10 | Maximum reconnection attempts (0 = infinite) |
Precedence
Configuration values are resolved in the following order (highest to lowest priority):
Constructor Arguments: Passed directly to
DexalotClient// 1. Highest Priority const client = new DexalotClient(createConfig({ parentEnv: "custom-env" }));Environment Variables: System-level variables
# 2. High Priority export DEXALOT_PARENT_ENV="production-multi-avax".envFile: Variables loaded from local.envfile# 3. Medium Priority DEXALOT_PARENT_ENV=fuji-multiDefaults: Hardcoded SDK defaults (
fuji-multi)
Advanced Configuration
For complex setups, you can pass a DexalotConfig object directly:
import DexalotClient, { createConfig } from 'dexalot-sdk';
const config = createConfig({
parentEnv: "production-multi-subnet",
timeoutConnect: 10,
timeoutRead: 60,
cacheEnabled: false
});
const client = new DexalotClient(config);See env.example for all available configuration options.
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
- Thread-Safe: Concurrent operations are handled safely with async locks
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/rpcHow 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
import DexalotClient, { createConfig } from 'dexalot-sdk';
// Configure failover
const config = createConfig({
providerFailoverEnabled: true,
providerFailoverCooldown: 60, // 60 seconds cooldown
providerFailoverMaxFailures: 3, // Mark unhealthy after 3 failures
});
const client = new DexalotClient(config);
await client.initializeClient();
// RPC calls use failover automatically when the primary provider fails (if enabled)Provider failover behavior
- With
providerFailoverEnabled: 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.
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, WARN, 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 (openOrders). - Swap: RFQ operation lifecycle including Firm/Soft Quotes and Swap Execution.
- Transfer: Cross-chain Bridge operations (
deposit/withdraw), Portfolio Management (transferPortfolio), 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": "addOrder",
"duration": 0.123,
"status": "success"
}
}Resource Cleanup
The SDK manages several resources that need proper cleanup:
- HTTP sessions (
axiosinstances) - Web3 provider sessions (internal HTTP sessions)
- 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 function main() {
let client: DexalotClient | null = null;
try {
client = new DexalotClient();
await client.initializeClient();
// Your operations here
const result = await client.getTokens();
if (result.success) {
console.log(`Tokens: ${result.data}`);
}
} finally {
// Always close the client in a finally block
if (client !== null) {
await client.close();
}
}
}Note: The async close() method:
- Closes all HTTP sessions
- Closes WebSocket connections (if enabled)
- Resets rate limiters and nonce managers
- Is safe to call multiple times (idempotent)
Async Usage
The SDK is fully async - all methods are async and must be awaited. This enables concurrent operations and better performance.
Script Usage
For standalone scripts, use async/await:
import DexalotClient from 'dexalot-sdk';
async function main() {
let client: DexalotClient | null = null;
try {
client = new DexalotClient();
await client.initializeClient();
// Your async operations here
const result = await client.getTokens();
if (result.success) {
console.log(`Tokens: ${result.data}`);
}
} finally {
if (client !== null) {
await client.close();
}
}
}
main().catch(console.error);Application Usage
In async applications (e.g., Node.js servers, async web frameworks), use await directly:
import DexalotClient from 'dexalot-sdk';
// In Express.js or similar
const client = new DexalotClient();
// Initialize on startup
await client.initializeClient();
// Use in routes
app.get('/tokens', async (req, res) => {
const result = await client.getTokens();
if (result.success) {
res.json(result.data);
} else {
res.status(500).json({ error: result.error });
}
});
// Close on shutdown
process.on('SIGTERM', () => {
void client.close();
});Parallel Operations
The async architecture enables parallel operations for better performance:
import DexalotClient from 'dexalot-sdk';
async function main() {
let client: DexalotClient | null = null;
try {
client = new DexalotClient();
await client.initializeClient();
// Fetch multiple orderbooks in parallel
const pairs = ["AVAX/USDC", "ALOT/USDC", "ETH/USDC"];
const results = await Promise.all(
pairs.map(pair => client.getOrderBook(pair))
);
results.forEach((result, index) => {
if (result.success) {
console.log(`${pairs[index]}: ${result.data.bids.length} bids`);
}
});
} finally {
if (client !== null) {
await client.close();
}
}
}
main().catch(console.error);Error Handling
The SDK uses a Result<T> pattern for consistent error handling across all methods.
Result Pattern
All SDK methods return Result<T> with three fields:
success: boolean- True if operation succeededdata: T | null- Result data on success, null on errorerror: string | null- Error message on failure, null on success
Basic Error Handling
const result = await client.addOrder({
pair: "AVAX/USDC",
side: "BUY",
amount: 1.0,
price: 25.0
});
if (result.success) {
const txHash = result.data.txHash;
console.log(`Order placed: ${txHash}`);
} else {
console.error(`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)
const result = await client.addOrder({
pair: "AVAX/USDC",
side: "BUY",
amount: -1.0, // Invalid!
price: 25.0
});
if (!result.success) {
// result.error will be: "Invalid amount: must be positive (> 0), got -1.0"
console.error(`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
Backend reason codes
Errors from the Dexalot REST API now include structured reasonCode (e.g. FQ-015, P-AFNE-02, T-TMDQ-01, RF-IMV-01) and human reason fields. These are preserved verbatim in thrown errors and Result.fail() messages — you'll see "FQ-015: insufficient liquidity" rather than the generic "Request failed with status code 400". Catch and pattern-match on the code prefix to react programmatically:
const result = await client.getSwapFirmQuote('USDC', 'AVAX', 100);
if (!result.success && result.error.startsWith('FQ-')) {
// RFQ backend rejected the quote — see the reasonCode for why
}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 function placeOrderSafely(
client: DexalotClient,
pair: string,
side: string,
amount: number,
price: number
) {
const result = await client.addOrder({ pair, side, amount, price });
if (result.success) {
return { status: "success", txHash: result.data.txHash };
} else {
// Log error for debugging
console.error(`Order failed: ${result.error}`);
// Return user-friendly message
return { status: "error", message: "Failed to place order. Please try again." };
}
}Transaction Receipt Handling
All state-changing operations (placing orders, deposits, withdrawals, etc.) now support a waitForReceipt 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 (waitForReceipt=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)
const result = await client.addOrder({
pair: "AVAX/USDC",
side: "BUY",
amount: 1.0,
price: 25.0
});
// Method returns only after transaction is confirmed
// Explicitly wait for receipt
const result = await client.addOrder({
pair: "AVAX/USDC",
side: "BUY",
amount: 1.0,
price: 25.0,
waitForReceipt: true
});
// Don't wait for receipt (returns immediately after sending)
const result = await client.addOrder({
pair: "AVAX/USDC",
side: "BUY",
amount: 1.0,
price: 25.0,
waitForReceipt: false
});
// Method returns immediately with transaction hash
// Transaction may still be pendingWhen to Use waitForReceipt=false
Use waitForReceipt=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 waitForReceipt=false, the SDK returns immediately after broadcasting the transaction. You should:
- Check transaction status yourself using the returned
txHash - 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 waitForReceipt:
CLOB Operations:
addOrder({ pair, side, amount, price, type?, waitForReceipt? })addOrderList(orders, waitForReceipt?)cancelOrder(orderId, waitForReceipt?)cancelListOrders(orderIds, waitForReceipt?)cancelListOrdersByClientId(clientOrderIds, waitForReceipt?)replaceOrder(orderId, newPrice, newAmount, waitForReceipt?)cancelAddList(replacements, waitForReceipt?)
Transfer Operations:
deposit(token, amount, sourceChain, useLayerZero?, waitForReceipt?)withdraw(token, amount, destinationChain, useLayerZero?, waitForReceipt?)addGas(amount, waitForReceipt?)removeGas(amount, waitForReceipt?)transferPortfolio(token, amount, toAddress, waitForReceipt?)
Swap Operations:
executeRFQSwap(quote, waitForReceipt?)
Example: Batch Order Placement
// Place multiple orders without waiting for each receipt
const 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
const result = await client.addOrderList(orders, false);
if (result.success) {
const txHash = result.data.txHash;
// Check status later
// await checkTransactionStatus(txHash);
}Example: Fire-and-Forget Deposit
// Submit deposit and continue with other operations
const result = await client.deposit("AVAX", 1.0, "Avalanche", false, false);
if (result.success) {
const txHash = result.data; // Just the transaction hash
// Continue with other operations
// Monitor deposit status separately
}Canonical Order Model
Order reads (getOpenOrders, getOrder, getOrderByClientId) return one canonical order object regardless of whether the source was the REST API or the contract:
internalOrderId,clientOrderId,tradePairId,pairprice,totalAmount,quantity,quantityFilled,totalFeetraderAddress,side,type1,type2,statusupdateBlock,createBlock,createTs,updateTs,tx
Enum-style fields are normalized to human-readable strings such as BUY, SELL, LIMIT, GTC, and FILLED. Contract-backed reads return createBlock and updateBlock as JavaScript numbers, not hex strings. API-backed open orders may return those block fields as null when the Dexalot API omits them, while still preserving createTs, updateTs, and tx.
API Field Name Standardization
The SDK automatically standardizes API response field names to match TypeScript naming conventions (camelCase). This ensures consistent field names regardless of API response format variations.
Standardized Fields
Orders API:
internalOrderId(fromid)clientOrderId(fromclientordid,client_order_id)tradePairId(fromtradePairId,tradepairid,trade_pair_id, or derived frompair)pair,price,quantity,quantityFilled,totalAmount,totalFeetraderAddress,side,type1,type2,statuscreateBlock,updateBlock,createTs,updateTs,tx
Orders are normalized into one canonical SDK shape across REST and contract order reads.
Environments API:
chainId(fromchainid,chain_id)envType(fromtype,env_type)rpc(fromchain_instance)network(fromchain_display_name)
Tokens API:
evmDecimals(fromevmdecimals,evm_decimals,decimals)chainId(fromchainid,chain_id)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)
Price History API (getTokenPriceHistory, getTokenHourlyPriceHistory):
timestamp(fromdateISO-8601,ts,timestamp,time— ms auto-detected by magnitude and divided to seconds)price(from string decimal, coerced to number; scientific notation supported)
Combined Transfers API (getCombinedTransfers):
- The canonical
Transfershape exposes camelCase fields normalized from the backend's snake_caseDBTransfer:actionType,status,symbol,quantity,fee,traderAddress,bridge,bridgeUrl,nonce,sourceEnv,sourceChainId,sourceTx,sourceTs,targetEnv,targetChainId,targetTx,targetTs. - Numeric enums are mapped to string labels:
status(COMPLETED/INFLIGHT/DELAYED),actionType(10 labels includingWITHDRAWN/DEPOSITED/SENT/RECEIVED/RECOVERED/ADD_GAS/REMOVE_GAS/AUTO_FILL/WITHDRAW_PENDING/DEPOSIT_PENDING),bridge(NATIVE/LAYER0/CELER/ICM). - Quantity and fee arrive as display-decimal Big-strings — no wei→human decode needed (parsed via
Number()). - Timestamps are normalized to unix seconds (UTC) numbers (the backend emits ISO-8601 strings, which are coerced via
Date.parse):sourceTsis always present (0sentinel when missing), andtargetTsisnullwhen there is no target leg. The other target fields (targetEnv,targetChainId,targetTx) are likewisenullfor transfers that never cross.
Order History API (getOrderHistory):
- Same canonical
Ordershape and aliases asgetOpenOrders— see the "Canonical Order Model" section above.
Benefits
- Consistent interface: Field names are exposed in camelCase in TypeScript.
- Alias handling: Common snake_case 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
import DexalotClient, { createConfig } from 'dexalot-sdk';
// Custom retry configuration
const config = createConfig({
retryEnabled: true,
retryMaxAttempts: 5,
retryInitialDelay: 2000, // Start with 2s delay
retryMaxDelay: 30000, // Max 30s between retries
retryExponentialBase: 2.0
});
const client = new DexalotClient(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
const config = createConfig({
rateLimitEnabled: true,
rateLimitRequestsPerSecond: 10.0, // 10 API calls/second
rateLimitRpcPerSecond: 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
const tasks = [
client.addOrder({ pair: "AVAX/USDC", side: "BUY", amount: 1.0, price: 25.0 }),
client.addOrder({ pair: "ALOT/USDC", side: "BUY", amount: 10.0, price: 0.5 }),
client.deposit("AVAX", 1.0)
];
const results = await Promise.all(tasks);
// Nonce manager ensures correct nonce orderingProvider 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
import DexalotClient, { createConfig } from 'dexalot-sdk';
async function main() {
let client: DexalotClient | null = null;
try {
const config = createConfig({
wsManagerEnabled: true
});
client = new DexalotClient(config);
await client.initializeClient();
// Subscribe to orderbook updates
const onOrderbookUpdate = (message: any) => {
console.log(`Orderbook update: ${JSON.stringify(message)}`);
};
await client.subscribeToEvents(
"orderbook.AVAX/USDC",
onOrderbookUpdate,
false
);
// Subscribe to private order updates
const onOrderUpdate = (message: any) => {
console.log(`Order update: ${JSON.stringify(message)}`);
};
await client.subscribeToEvents(
"orders",
onOrderUpdate,
true
);
// Keep connection alive
await new Promise(resolve => setTimeout(resolve, 60000));
// Unsubscribe when done
await client.unsubscribeFromEvents("orderbook.AVAX/USDC");
} finally {
// Always close the client to clean up WebSocket and HTTP sessions
if (client !== null) {
await client.close();
}
}
}
main().catch(console.error);Configuration
import DexalotClient, { createConfig } from 'dexalot-sdk';
const config = createConfig({
wsManagerEnabled: true,
wsPingInterval: 30, // Ping every 30 seconds
wsPingTimeout: 10, // Wait 10s for pong before reconnecting
wsReconnectInitialDelay: 1,
wsReconnectMaxDelay: 60,
wsReconnectExponentialBase: 2.0,
wsReconnectMaxAttempts: 10 // 0 = infinite retries
});
const client = new DexalotClient(config);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.ts and returns Result<null> for consistent error handling.
Automatic Validation
Input validation is applied to all critical methods:
- CLOB Operations:
addOrder(),cancelOrder(),getOrderBook(), etc. - Swap Operations:
executeRfqSwap(),getSwapFirmQuote(), etc. - Transfer Operations:
deposit(),withdraw(),transferPortfolio(), 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 hex strings or bytes32 format
- Token Symbols: Must be non-empty, alphanumeric strings
Handling Validation Errors
Validation errors are returned as Result.fail() with descriptive messages:
// Invalid amount
const result = await client.addOrder({
pair: "AVAX/USDC",
side: "BUY",
amount: -1.0, // Invalid: negative amount
price: 25.0
});
if (!result.success) {
// result.error: "Invalid amount: must be positive (> 0), got -1.0"
console.error(result.error);
}
// Invalid address
const balanceResult = await client.getPortfolioBalance(
"USDC",
"invalid" // Not a valid Ethereum address
);
if (!balanceResult.success) {
// result.error: "Invalid address: must be a valid Ethereum address (0x prefix, 42 chars, hex)"
console.error(balanceResult.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.
