feat(client): add perps support#133
Conversation
Port Perps support from the TypeScript SDK to the async clients: - Public reads on AsyncPublicClient and AsyncSecureClient: instruments, tickers (with statistics merge), book, fees, and paginated candles, funding history, and trades with SDK-owned opaque cursors. - Perps market data streaming through subscribe() with per-topic specs (trades, bbo, book, candles, tickers, statistics), channel dedupe, and reconnect with resubscribe. - Delegated credential lifecycle: open_perps_session creates or resumes credentials (CreateProxy typed-data signature, validation, default 7d expiry) and revoke_perps_credentials signs deleteProxy ops. - PerpsSession trading over WebSocket with EIP-712 signed msgpack ops: place_order (gtc/ioc/fok overloads, optional TP/SL triggers), post_orders, place_position_tp_sl, cancel_order(s), update_leverage, session account reads, and an async-iterable event stream with sequence-gap and reconnect resync events. - Funds: deposit_to_perps (EOA broadcast or gasless relayer call) and withdraw_from_perps (owner-signed Withdraw typed data). Signing is pinned byte-for-byte against golden vectors generated from the TypeScript implementation (msgpack encoding, Op data hash, and signatures). Verified with ruff, pyright, the unit suite, and the live integration suite including a 10 USDC deposit/withdraw roundtrip and GTC and TP/SL order place/cancel against production.
| if has_more and last_ts is not None: | ||
| next_cursor = encode_perps_cursor( | ||
| {**state, "start_timestamp": last_ts + interval_ms(state["interval"])} | ||
| ) |
There was a problem hiding this comment.
Candles pagination ignores end bound
Medium Severity
list_perps_candles sets has_more from the API more flag and a parsed last timestamp only, without comparing the next window start to end_timestamp. Account equity/PnL paginators in the same change stop when the last point reaches the end bound. Candle paging can advance start_timestamp past end, causing an invalid range, skipped buckets near the end, or extra empty pages.
Reviewed by Cursor Bugbot for commit c646fcd. Configure here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4073de5. Configure here.
| self._scheduler.reset() | ||
| if emit_resync: | ||
| self._sequences.clear() | ||
| self._push(PerpsResyncEvent(reason="reconnect")) |
There was a problem hiding this comment.
Stale order buffer after reconnect
Medium Severity
After a WebSocket reconnect, _recent_orders is left intact while sequence state is cleared. place_order can then resolve from a pre-reconnect buffered update for the same order_id, returning an outdated order snapshot instead of waiting for the new placement’s orders-channel event.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 4073de5. Configure here.


Ports Perps support from the TypeScript SDK (ts-sdk #133, #145, #147, #149, #154, #158–#162, #167, #172, and TP/SL from #153) to the Python SDK. Perps lands on the async clients only since the trading UX is WebSocket-centric.
Surface
Public reads (
AsyncPublicClientandAsyncSecureClient)fetch_perps_instruments/ticker/tickers/book/fees(tickers merge 24h statistics)list_perps_candles/funding_history/tradeswith SDK-owned opaque cursors (forward interval stepping for candles, backward windows with boundary trade-id dedupe for trades)Market data streaming
subscribe():PerpsTradesSpec,PerpsBboSpec,PerpsBookSpec,PerpsCandlesSpec,PerpsTickersSpec,PerpsStatisticsSpecDelegated credentials (
AsyncSecureClient)open_perps_session()creates credentials with a wallet-signed CreateProxy payload (default 7d expiry,expires_in=timedelta(...)override) or validates and resumes existingPerpsCredentialsrevoke_perps_credentials()signs a deleteProxy op with the owner accountPerpsSession(trading over WebSocket)Oppayloads committing to msgpack-encoded positional tuples, using the delegated session keyplace_order(gtc/ioc/fok overloads; gtc requirespriceand allowspost_only; optionaltake_profit/stop_losstriggers place a grouped bracket),post_orders,place_position_tp_sl,cancel_order(s),update_leveragetpsl) with sequence-gap detection and reconnectresynceventsFunds
deposit_to_perps(EOA broadcast or gasless relayer call for proxy/safe/deposit wallets)withdraw_from_perps(owner-signed Withdraw typed data; note the wire timestamp is seconds)Notable decisions
expires_intakes atimedelta; models exposeDecimalanddatetimeUserInputError(the TS session account reads raiseTransportError; flagged as an inconsistency there)place_orderreturns onePerpsOrderPlacement(order, tp_sl=None)result type instead of overloaded result shapesmsgpackadded as a core dependency for op signingVerification
Op.datahashes, and full signatures for createOrders (including grouped TP/SL rows), cancels, updateLeverage, deleteProxy, CreateProxy, and Withdraw; the deposit calldata is pinned against the TS encoder as welluv run ruff check .,uv run ruff format --check .,uv run pyright(0 errors),uv run pytest -m "not integration"(1782 passed)perps.test.tsand passed against production, including the metered flows: 10 USDC deposit/withdraw roundtrip with deposit confirmation tracking, GTC place/cancel, TP/SL bracket place/cancel, and credential create/resume/revoke/invalid-secretdeposit_wallet_clientfixture; the deposit test uses a relayer-enabled variant with the builder API key already provided by the integration workflowNote
High Risk
Large new trading, signing, and funds-moving surface (orders, withdrawals, deposits, delegated keys); mistakes affect live collateral and positions despite test coverage.
Overview
Introduces Perps (perpetuals) as a first-class async surface, ported from the TypeScript SDK.
msgpackis added for EIP-712 command signing;Environmentgainsperps_url,perps_ws_url, andperps_deposit_contract; trading approvals now include the Perps deposit contract spender.Public and secure async clients get REST helpers (
fetch_perps_*, paginatedlist_perps_candles/funding_history/tradeswith SDK-owned cursors) andsubscribe()support via newPerps*Spectypes, backed by a multiplexedPerpsMarketStreamManager.AsyncSecureClientadds delegated credential lifecycle (open_perps_session,revoke_perps_credentials),deposit_to_perps/withdraw_from_perps, andPerpsSession: one WebSocket for signed trading (place/cancel orders, leverage, TP/SL brackets), private account REST reads, and iterable session events with reconnect and sequence-gapresync.New
polymarket.models.perpstypes and package exports wire through__init__,streams, andpolymarket.perps.Reviewed by Cursor Bugbot for commit 4073de5. Bugbot is set up for automated code reviews on this repo. Configure here.