Typed dYdX
A fully typed, validated async client for the dYdX v4 APIs.
Use autocomplete instead of documentation.
from dydx import Indexer
async with Indexer.new() as indexer:
btc = await 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 three entry points because dYdX itself is split across the indexer and node APIs:
Indexerfor HTTP market/account data and WebSocket streamsPublicNodefor public node readsPrivateNodefor signed trading actions
from dydx import Indexer, PublicNode
async with Indexer.new() as indexer:
market = await indexer.data.get_market('BTC-USD')
public_node = await PublicNode.connect()
price = await public_node.get_price(int(market['clobPairId']))
Features
No Unnecessary Imports
Notice something? You never imported Literal types. Just use strings:
# ❌ Other libraries
from some_sdk import Market
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:
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:
# 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
async with Indexer.new() as indexer:
async for chunk in indexer.data.get_fills_paged(
address='dydx...',
market='BTC-USD',
market_type='PERPETUAL',
limit=100,
):
print(len(chunk))
WebSocket Streams
Real-time user data comes from the indexer WebSocket API:
from dydx import Indexer
async with Indexer.new() as indexer:
initial, updates = await indexer.streams.subaccounts('dydx...', subaccount=0)
async for update in updates:
print(update)
API Coverage
Current coverage is split across:
Indexer.datafor markets, order books, candles, trades, fills, transfers, subaccounts, funding, orders, and positionsIndexer.streamsfor subaccount 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
- Reference - Error handling, env vars, and endpoint overview
- Examples - Practical usage patterns
Design Philosophy
Typed dYdX follows the principles outlined in this blog post.
Details matter. Developer experience matters.