Async Usage
dYdX clients are async-first and support two usage styles:
- construct a client and call methods directly for quick one-off requests
- use
async withwhen you want explicit lifecycle management
Quick Usage
For short request-response flows, plain construction is fine.
from typed_dydx import Dydx
client = Dydx.testnet(public=True)
market = await client.indexer.data.get_market('BTC-USD')
print(market)The indexer's HTTP and WebSocket transports open lazily on first use, the same as every other Typed client.
Context Manager Usage
Use async with when you want the client to open up front and close cleanly at the end of
the block.
from typed_dydx import Dydx
async with Dydx.testnet(public=True) as client:
market = await client.indexer.data.get_market('BTC-USD')
block = await client.chain.comet.block()Dydx.__aenter__ enters client.indexer and client.chain together -- you never
separately enter a sub-client. This is the recommended style for multiple requests,
long-lived sessions, any streaming workflow, or code where explicit cleanup matters.
The Chain gRPC Exception
Every other transport in this catalog opens lazily: entering it via async with does not,
by itself, open a live connection -- only the first actual request does. dYdX's chain
surface is the one confirmed exception.
Chain.__aenter__ enters both the gRPC modules client and the Comet HTTP client, and
GrpcClient.__aenter__ explicitly forces construction of the underlying
grpclib.client.Channel immediately on entry:
# GrpcClient.__aenter__ (chain/core.py)
async def __aenter__(self) -> Self:
"""Open the channel for an async client context."""
self.channel
return selfSo async with Dydx.mainnet(public=True) as client: (or async with Chain.mainnet() as chain: on its own) opens the gRPC channel right away, not on the first client.chain.*
call. This only matters when you use async with -- plain construction without a context
manager still opens the channel lazily, on first use, the same as everything else.
Streams
client.indexer.streams groups every indexer WebSocket subscription --
block_height, candles, markets, orders, parent_subaccounts, subaccounts,
trades -- built on typed_core.ws.streams.Streams. Each call returns a StreamManager.
Use async with on the manager so the subscription is unsubscribed automatically when the
block exits:
from typed_dydx import Dydx
async with Dydx.testnet(public=True) as client:
async with client.indexer.streams.candles('ETH-USD', resolution='1MIN') as candles:
async for candle in candles:
print(candle)awaiting the manager directly also works, but leaves the subscription open until you call
unsubscribe() yourself:
from typed_dydx import Dydx
async with Dydx.testnet(public=True) as client:
candles = await client.indexer.streams.candles('ETH-USD', resolution='1MIN')
async for candle in candles:
print(candle)
break
await candles.unsubscribe()Composite/Multi-Surface Client
Dydx composes three surfaces:
indexer-- HTTP + WebSocket, market data, account history, and streamschain-- gRPC + Comet HTTP, shared read-only chain state, entered eagerly on the gRPC side (see above)node-- a wallet/signing wrapper aroundchainfor order placement and transactions.node.publicis a plain lazy@propertyreturningPublic(self.chain), not a separate transport -- it just proxies read-only queries onto the already-openchain
Dydx.new(...) builds this from raw options. Every named provider constructor --
.oegs(), .mainnet(), .polkachu(), .kingnodes(), .enigma(), plus their _archive
and testnet_* counterparts -- funnels through .new() with a different Chain provider
preset.
Guidance
Use direct construction for quick reads.
Use async with by default when:
- you are doing more than one call
- you are opening streams
- you want predictable cleanup -- for
chain, this also means the gRPC channel opens immediately rather than on first use