I'd like to add an AsyncClobClient alongside the existing sync ClobClient. HTTP is already on httpx, so httpx.Client → httpx.AsyncClient is mechanical. Motivation: CLOB workflows are concurrent by nature (multi-market watching, batched cancels via asyncio.gather, mixing REST with the async-native WS feed), and most production Python stacks today are asyncio-based, so users currently wrap every call in asyncio.to_thread — a thread per call defeats the point. Sync client stays, no breaking change for existing users.
The non-obvious part is that Signer.sign(...) must also become async (async def sign(self, message_hash: bytes) -> str). Local eth_account signing is CPU-bound and fine sync, but the moment you want remote signing — KMS, Vault, Fireblocks, Web3Signer, MPC services — signing is a network round-trip, and a sync signer blocks the event loop on the hot path of every order. This propagates async/await through L1 auth header generation, EIP-712 signing, create_or_derive_api_key, and RFQ accept/approve — same surface area [#27](#27) already touched before it was closed. Happy to open a draft PR if direction is acceptable.
- Add
AsyncClobClient mirroring ClobClient method-for-method, all async def.
- Add
http_helpers/async_helpers.py using httpx.AsyncClient.
- Add
AsyncSigner protocol; current Signer becomes LocalSigner implementing it.
- Split each "build-and-sign" function into
build_unsigned_* + apply_signature so both clients reuse them.
- Async client body:
build_unsigned_* → await signer.sign(hash) → apply_signature → await apost(...).
- Tests on pytest-asyncio + equivalence test (sync and async produce identical signed payload).
I'd like to add an
AsyncClobClientalongside the existing syncClobClient. HTTP is already onhttpx, sohttpx.Client→httpx.AsyncClientis mechanical. Motivation: CLOB workflows are concurrent by nature (multi-market watching, batched cancels viaasyncio.gather, mixing REST with the async-native WS feed), and most production Python stacks today are asyncio-based, so users currently wrap every call inasyncio.to_thread— a thread per call defeats the point. Sync client stays, no breaking change for existing users.The non-obvious part is that
Signer.sign(...)must also become async (async def sign(self, message_hash: bytes) -> str). Localeth_accountsigning is CPU-bound and fine sync, but the moment you want remote signing — KMS, Vault, Fireblocks, Web3Signer, MPC services — signing is a network round-trip, and a sync signer blocks the event loop on the hot path of every order. This propagatesasync/awaitthrough L1 auth header generation, EIP-712 signing,create_or_derive_api_key, and RFQ accept/approve — same surface area [#27](#27) already touched before it was closed. Happy to open a draft PR if direction is acceptable.AsyncClobClientmirroringClobClientmethod-for-method, allasync def.http_helpers/async_helpers.pyusinghttpx.AsyncClient.AsyncSignerprotocol; currentSignerbecomesLocalSignerimplementing it.build_unsigned_*+apply_signatureso both clients reuse them.build_unsigned_*→await signer.sign(hash)→apply_signature→await apost(...).