Typed dYdX
A fully typed, validated async client for the dYdX v4 APIs.
Use autocomplete instead of documentation.
from dydx import DYDX
async with DYDX.new() as dydx:
btc = await dydx.indexer.data.get_market('BTC-USD')
print(btc['oraclePrice'])
Why Typed dYdX?
- 🎯 Precise Types: Literal types where they help, so your IDE knows what is valid.
- ✅ Automatic Validation: Catch upstream API changes earlier.
- ⚡ Async First: Built for concurrent, network-heavy workflows.
- 🔒 Type Safety: Full type hints throughout.
- 🎨 Better DX: Clear routing, sensible defaults, optional complexity.
- 📦 Batteries Included: Pagination, streams, and helpers when they earn their place.
Installation
Quick Start
The package exposes four entry points because dYdX itself is split across the indexer and node APIs:
DYDXfromdydxas the default authenticated entry point for combined read/trade workflowsIndexerfromdydxfor HTTP market/account data and WebSocket streamsPublicNodefromdydx.nodefor public node readsPrivateNodefromdydx.nodefor signed trading actions
from dydx import DYDX
async with DYDX.new() as dydx:
market = await dydx.indexer.data.get_market('BTC-USD')
price = await dydx.node.get_price(int(market['clobPairId']))
Features
No Unnecessary Imports
Notice something? You never imported Literal types. Just use strings:
from dydx import Indexer
# ❌ Other libraries
# trades = await client.get_trades(Market.BTC_USD)
# ✅ Typed dYdX
async with Indexer.new() as indexer:
trades = await indexer.data.get_trades('BTC-USD', limit=50)
Precise Type Annotations
Every field is precisely typed. Market metadata is strongly shaped enough to use directly:
from decimal import Decimal
from dydx import Indexer
async with Indexer.new() as indexer:
btc = await indexer.data.get_market('BTC-USD')
clob_pair_id: str = btc['clobPairId']
oracle_price: Decimal = btc['oraclePrice']
Automatic Validation
Response validation is on by default but can be disabled:
from dydx import Indexer
# Validated (default) - throws ValidationError if API response changes
async with Indexer.new() as indexer:
markets = await indexer.data.get_markets(limit=5)
# Skip validation for maximum performance
async with Indexer.new(validate=False) as indexer:
markets = await indexer.data.get_markets(limit=5)
Built-in Pagination
import os
from dydx import Indexer
async with Indexer.new() as indexer:
async for chunk in indexer.data.get_fills_paged(
os.environ['DYDX_ADDRESS'],
subaccount=0,
market='BTC-USD',
market_type='PERPETUAL',
limit=100,
):
print(len(chunk))
WebSocket Streams
Real-time user data comes from the indexer WebSocket API:
import os
from dydx import Indexer
async with Indexer.new() as indexer:
stream = await indexer.streams.subaccounts(os.environ['DYDX_ADDRESS'], subaccount=0)
print(stream.reply['subaccount']['equity'])
API Coverage
Current coverage is split across:
DYDXas the default private/authenticated surfaceIndexer.datafor markets, order books, candles, trades, fills, transfers, subaccounts, funding, orders, and positionsIndexer.streamsfor subaccounts, parent subaccounts, trades, candles, markets, order book, and block-height updatesPublicNodefor price, CLOB pair, and fee-tier readsPrivateNodefor placing and cancelling orders
📋 See API Overview for the current structure and coverage.
Documentation
- Getting Started - Install the package and make your first requests
- Trading Access - Configure mnemonic-based trading access
- API Overview - Understand the client structure and coverage
- How To - Task-focused guides for market data, account data, trading, and streams
- Reference - Error handling, env vars, and endpoint overview
Design Philosophy
Typed dYdX follows the principles outlined in this blog post.
Details matter. Developer experience matters.