Skip to content

[Feature]: Expose TransportOptions (or httpx client injection) in AsyncSecureClient.create() #209

Description

@zixuanz313

What would you like the SDK to support?

Package: polymarket-client 0.2.0
Area: transport configuration / connection lifecycle

Summary

TransportOptions (in polymarket/clients/_transport.py) already supports configuring http2, limits, timeout, and event_hooks, and both SyncTransport / AsyncTransport accept an injected httpx client. However, none of this is reachable from the public constructors — AsyncSecureClient.create() builds its transports with hardcoded defaults. As a result there is no supported way to:

  • disable HTTP/2 or tune connection limits / keepalive,
  • attach event_hooks for request/response observability,
  • inject a pre-configured httpx.AsyncClient (custom transport, proxies, instrumentation).

Why this matters: HTTP/2 GOAWAY vs. non-idempotent requests

The CLOB edge recycles every HTTP/2 connection after ~10,000 requests with a graceful GOAWAY. A long-running trading service (SDK client kept alive across cycles, as recommended for connection reuse) hits this cap regularly. Requests racing with the GOAWAY surface as:

polymarket.errors.TransportError: <ConnectionTerminated error_code:0, last_stream_id:19999, additional_data:None>

(last_stream_id: 19999 = the 10,000th client-initiated stream, so this is deterministic, not network flakiness.)

For idempotent calls this is retryable. For post_orders it is not safely retryable from application code: httpcore raises the identical exception both for streams the server never processed (opened after GOAWAY was seen) and for in-flight streams at or below last_stream_id, which the server will still process. A blind retry can therefore double-place orders, and the information needed to distinguish the two cases is lost by the time the exception reaches SDK users.

With TransportOptions exposed, users could resolve this cleanly on their side, e.g.:

  • http2=False: HTTP/1.1 keep-alive connections are recycled via Connection: close, which httpx handles transparently — this failure mode disappears entirely; or
  • inject an httpx client with a custom connection pool that proactively expires connections before the ~10k cap (Envoy-style max_requests_per_connection).

Current workaround

We reach through private attributes (client._ctx.secure_clob._client._transport._pool) to close idle connections whose _request_count approaches the cap before each trading cycle. It works, but it is coupled to httpcore internals and will silently break on refactors.

Proposed API

Any of these would work (first is probably minimal):

client = await AsyncSecureClient.create(
    private_key=...,
    credentials=...,
    transport_options=TransportOptions(http2=False, limits=...),  # applied to all transports
)

or accept per-service overrides / a pre-built httpx.AsyncClient, mirroring what AsyncTransport.__init__ already supports.

Why do you need this?

We run a long-lived trading service that keeps one AsyncSecureClient alive across decision cycles for connection reuse. The CLOB edge (Cloudflare) recycles every HTTP/2 connection after ~10,000 requests with a graceful GOAWAY, so a few times a day in-flight requests fail with:

polymarket.errors.TransportError: <ConnectionTerminated error_code:0, last_stream_id:19999, additional_data:None>
For reads this is retryable, but for post_orders it is not safely retryable from application code: httpcore raises the identical exception both for streams the server never processed and for in-flight streams it will still process, so a blind retry risks double-placing orders. The clean fixes are all transport-level — disable HTTP/2 (HTTP/1.1 recycling via Connection: close is handled transparently by httpx), or inject an httpx client whose pool expires connections before the cap. TransportOptions and client injection already exist in _transport.py; they're just not reachable from AsyncSecureClient.create(). Today we work around it by reaching through private attributes (client._ctx.secure_clob._client._transport._pool) to proactively close near-cap connections, which is coupled to httpcore internals and will break silently on refactors.

Example API or additional context

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions