Skip to content

feat(clob): retry rate-limited reads and report rate-limit updates - #31

Merged
SebastianBoehler merged 5 commits into
SebastianBoehler:mainfrom
yluoc:rate-limit-handling
Oct 7, 2026
Merged

SebastianBoehler merged 5 commits into
SebastianBoehler:mainfrom
yluoc:rate-limit-handling

Conversation

@yluoc

@yluoc yluoc commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Summary

The SDK classified HTTP 429 as RateLimit but never retried, ignored Retry-After, and dropped the Poly-RateLimit-* headers.

  • Retries: reads that get a 429 are retried up to twice, each after exactly the server's Retry-After delay (1 s when absent). A requested delay above 5 s returns the error instead. Configure with set_rate_limit_retry(), PositionClientConfig::rate_limit_retry or Config::rate_limit_retry; std::nullopt disables.
  • Coverage: all 37 ClobClient reads (batch POST reads included), the Data API, the PositionClient Gamma lookup, relayer nonce and status reads, and MarketFetcher. Signed reads rebuild their L2 headers on every attempt.
  • Never retried: orders, cancellations, relayer /submit and other writes.
  • Errors: SdkError gains retry_after_seconds (seconds, HTTP date, or the retry_after_seconds body field) and rate_limit.
  • Listener: ClobClient::set_rate_limit_listener() receives the Poly-RateLimit-* state from order and cancel responses, tagged with an order or cancel bucket.

Verification

Commands run and their actual results:

  • ctest --test-dir build -LE live (Linux Release): 56 of 56 passed. New tests: test_rate_limit, test_clob_rate_limit and test_position_rate_limit.
  • test_clob_rate_limit run 25 times in a row: 25 of 25 passed.
  • scripts/quality.py: C++ checks passed.
  • Mutation checks, each made the matching test fail: dropping the /order listener hook, disabling the default policy, reusing prebuilt L2 headers on a retry, and ignoring the MarketFetcher policy.

Checks not run and the reason (or none):

  • Prettier on README.md: no Node runtime was available locally. CI runs it.
  • macOS and Debug builds: covered by the CI matrix.
  • Live 429 responses: not reproduced against Polymarket. Behavior is covered by local fixtures.

Compatibility and evidence

Public API, binary compatibility, or release implications (or none):

  • Behavior change: CLOB reads now wait up to 5 s and retry on a 429 instead of failing on the first one. The official SDKs retry only Data API and Gamma reads. Latency-sensitive callers can call set_rate_limit_retry(std::nullopt).
  • Rebuild required: SdkError, ClobClient, Config and PositionClientConfig grew. New fields go at the end, so existing positional brace initializers of SdkError still compile.
  • Additive: the new header polymarket/rate_limit.hpp (RateLimitUpdate, RateLimitRetry) and the two ClobClient setters.
  • Test fixture: the shared local test server can now send response headers.

For protocol changes: the retry policy and header parsing follow py-sdk _internal/retry.py at ac69106 and rate_limit.py at 9fec251, and ts-sdk retry.ts at d02b337 and rate-limit.ts at c9106d4.

For live checks: no live calls were made. All behavior is covered by local fixture servers.

@yluoc
yluoc marked this pull request as draft October 7, 2026 16:01
@yluoc
yluoc marked this pull request as ready for review October 7, 2026 17:05
@yluoc

yluoc commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

@SebastianBoehler this PR is a minor change, no need to be released or be tagged. Thanks.

@SebastianBoehler

Copy link
Copy Markdown
Owner

Thanks again for the contribution! LGTM

@SebastianBoehler
SebastianBoehler merged commit 8e674d8 into SebastianBoehler:main Oct 7, 2026
4 checks passed
@yluoc
yluoc deleted the rate-limit-handling branch October 7, 2026 21:35
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.

2 participants