Skip to content

feat(clob): add wait_for_order_settlement for async order settlement - #177

Merged
cesarenaldi merged 7 commits into
mainfrom
feature/dev-418-order-settlement
Jul 24, 2026
Merged

feat(clob): add wait_for_order_settlement for async order settlement#177
cesarenaldi merged 7 commits into
mainfrom
feature/dev-418-order-settlement

Conversation

@cesarenaldi

@cesarenaldi cesarenaldi commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Context

CLOB order settlement is moving to an asynchronous pipeline (Polymarket/clob-v2#299): once it rolls out, matched order responses no longer include transactionsHashes and instead carry the tradeIDs of the fills, with hashes surfacing on the trades shortly after. The rollout is runtime-flag gated server-side, so both response shapes will coexist during the transition.

Python port of Polymarket/ts-sdk#210. Tracked in DEV-418. Related legacy client change: Polymarket/clob-client-v2#90.

Design

Placement stays fast; settlement is an explicit, awaitable step — same design as the TypeScript SDK and consistent with the existing relayer transaction wait pattern. Blocking resolution inside place_market_order was considered and rejected: it would silently re-add ~0.5–1s to the placement hot path and cannot represent timeout/failed-fill outcomes without new response surface anyway.

response = client.place_market_order(token_id="...", amount=10, side=OrderSide.BUY)

if response.ok:
    hashes = client.wait_for_order_settlement(response)
    # tuple[TransactionHash, ...]

Changes

  • SecureClient.wait_for_order_settlement(order, *, timeout_s=30.0) and the AsyncSecureClient equivalent — polls the order's trades (by the response's trade_ids) until every fill reaches a terminal state, then returns the settlement hashes as tuple[TransactionHash, ...].
    • Hashes already present on the order are returned without waiting, so behavior is identical before and after the pipeline rollout — code can migrate today as a no-op.
    • Orders without fills return () immediately. Settlement covers fills that occurred at placement; future fills of a resting order are followed via the user stream (documented).
    • Raises TimeoutError when fills are still settling at the deadline and TransactionFailedError when every fill failed execution. Partial failures return the settled hashes; failed fills contribute no hash.
  • Shared loop logic in _internal/actions/orders/settlement.py following the house sync/async pattern (thin duplicated loops over shared pure helpers, mirroring the relayer poll module), reusing the existing account-trades request builder/parser.
  • Model docstrings on AcceptedOrder / RejectedOrder documenting the best-effort semantics of transactions_hashes and the fills-at-placement scope of trade_ids.

Testing

  • 6 unit tests with httpx.MockTransport on the sync client: sync short-circuit, no-fills, polling, partial failure, all-failed → TransactionFailedError, timeout → TimeoutError. The async variant shares the same pure helpers and differs only in transport/sleep.
  • make check clean: ruff, format, pyright, 1842 tests.

Note

Medium Risk
New trading helper affects how integrators wait for settlement and interpret trade status; behavior is additive and placement is unchanged, but polling/timeouts and partial-failure semantics matter for production order flows.

Overview
Adds an optional, awaitable settlement step after order placement so callers can obtain on-chain transaction hashes when CLOB responses only include trade_ids (async settlement rollout).

SecureClient / AsyncSecureClient expose wait_for_order_fill_settlement(order, *, timeout_s=30) — polls account trades by the accepted order’s trade_ids until each fill is terminal (CONFIRMED or FAILED), then returns deduplicated settlement hashes. No trade_ids → returns existing transactions_hashes immediately (backward compatible). Raises TimeoutError or TransactionFailedError when all fills fail.

Internal _internal/actions/orders/settlement.py holds shared sync/async polling (reuses account-trades list builder/parser).

Models: TradeStatus + wire normalization (TRADE_STATUS_* vs plain) on ClobTrade and user stream trades; AcceptedOrder docs clarify trade_ids / best-effort transactions_hashes scope (fills at placement only).

Tests: unit coverage for polling, short-circuit, partial/total failure, timeout; integration order tests assert hashes after matched placement.

Reviewed by Cursor Bugbot for commit 980b1bf. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread src/polymarket/_internal/actions/orders/settlement.py Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 623b084. Configure here.

hashes = dict.fromkeys(
trade.transaction_hash for trade in trades if not _is_failed_trade(trade)
)
return tuple(cast(TransactionHash, tx_hash) for tx_hash in hashes)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty settlement hashes returned

Medium Severity

_collect_settlement_hashes treats any non-FAILED trade as settled and includes its transaction_hash verbatim. The CLOB API is known to return empty transaction_hash strings, and unlike the relayer poll path—which raises UnexpectedResponseError when a confirmed transaction has no hash—this can surface "" as a TransactionHash to callers after a successful wait.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 623b084. Configure here.

@cesarenaldi
cesarenaldi merged commit fe24c40 into main Jul 24, 2026
7 checks passed
@cesarenaldi
cesarenaldi deleted the feature/dev-418-order-settlement branch July 24, 2026 08:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant