diff --git a/core/scripts/generate-python-exchanges.js b/core/scripts/generate-python-exchanges.js index ee0739d9..e77c0629 100644 --- a/core/scripts/generate-python-exchanges.js +++ b/core/scripts/generate-python-exchanges.js @@ -18,6 +18,18 @@ const OVERRIDES = { polymarket: { // The Python SDK defaults to gnosis-safe to match historical behaviour. defaults: { signature_type: '"gnosis-safe"' }, + extraParams: [ + 'wallet_address: Optional[str] = None', + 'signer: Optional[object] = None', + ], + extraSuperArgs: [ + 'wallet_address=wallet_address', + 'signer=signer', + ], + extraDocs: [ + ' wallet_address: Ethereum address for hosted reads/writes (optional)', + ' signer: Optional callable for signing typed_data (optional)', + ], methods: [ [ ' def init_auth(self) -> None:', @@ -27,6 +39,20 @@ const OVERRIDES = { ].join('\n'), ], }, + opinion: { + extraParams: [ + 'wallet_address: Optional[str] = None', + 'signer: Optional[object] = None', + ], + extraSuperArgs: [ + 'wallet_address=wallet_address', + 'signer=signer', + ], + extraDocs: [ + ' wallet_address: Ethereum address for hosted reads/writes (optional)', + ' signer: Optional callable for signing typed_data (optional)', + ], + }, myriad: { // Myriad uses privateKey as the wallet address, not a signing key. paramAliases: { private_key: 'wallet_address' }, @@ -131,6 +157,9 @@ function generateClass(exchange) { const defaults = ov.defaults || {}; const paramDocs = ov.paramDocs || {}; const methods = ov.methods || []; + const extraParams = ov.extraParams || []; + const extraSuperArgs = ov.extraSuperArgs || []; + const extraDocs = ov.extraDocs || []; const constructorParams = []; const superArgs = [`exchange_name="${name}"`]; @@ -170,12 +199,16 @@ function generateClass(exchange) { constructorParams.push(`signature_type: Optional[str] = ${defaultVal}`); superArgs.push('signature_type=signature_type'); } + constructorParams.push(...extraParams); + superArgs.push(...extraSuperArgs); constructorParams.push('base_url: Optional[str] = None'); constructorParams.push('auto_start_server: Optional[bool] = None'); constructorParams.push('pmxt_api_key: Optional[str] = None'); + constructorParams.push('rate_limit: Optional[float] = None'); superArgs.push('base_url=base_url'); superArgs.push('auto_start_server=auto_start_server'); superArgs.push('pmxt_api_key=pmxt_api_key'); + superArgs.push('rate_limit=rate_limit'); const docLines = []; if (creds.apiKey) docLines.push(' api_key: API key for authentication (optional)'); @@ -189,9 +222,11 @@ function generateClass(exchange) { } if (creds.funderAddress) docLines.push(' proxy_address: Proxy/smart wallet address (optional)'); if (creds.signatureType) docLines.push(' signature_type: Signature type (optional)'); + docLines.push(...extraDocs); docLines.push(' base_url: Base URL of the PMXT sidecar server'); docLines.push(' auto_start_server: Automatically start server if not running (default: True)'); docLines.push(' pmxt_api_key: Hosted PMXT API key (optional; enables hosted mode)'); + docLines.push(' rate_limit: Minimum delay in milliseconds between SDK HTTP requests (default: 1000)'); const indent4 = s => ` ${s}`; const indent8 = s => ` ${s}`; diff --git a/sdks/python/pmxt/_exchanges.py b/sdks/python/pmxt/_exchanges.py index 98265ac9..67145b3a 100644 --- a/sdks/python/pmxt/_exchanges.py +++ b/sdks/python/pmxt/_exchanges.py @@ -18,13 +18,12 @@ def __init__( private_key: Optional[str] = None, proxy_address: Optional[str] = None, signature_type: Optional[str] = "gnosis-safe", + wallet_address: Optional[str] = None, + signer: Optional[object] = None, base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, - # NOTE: Generated wrapper; update the generator template in - # core/scripts/generate-python-exchanges.js in a follow-up. - wallet_address: Optional[str] = None, - signer: Optional[object] = None, + rate_limit: Optional[float] = None, ) -> None: """ Initialize Polymarket client. @@ -36,11 +35,12 @@ def __init__( private_key: Private key for authentication (optional) proxy_address: Proxy/smart wallet address (optional) signature_type: Signature type (optional) + wallet_address: Ethereum address for hosted reads/writes (optional) + signer: Optional callable for signing typed_data (optional) base_url: Base URL of the PMXT sidecar server auto_start_server: Automatically start server if not running (default: True) pmxt_api_key: Hosted PMXT API key (optional; enables hosted mode) - wallet_address: Ethereum address for hosted reads/writes (optional) - signer: Optional callable for signing typed_data (optional) + rate_limit: Minimum delay in milliseconds between SDK HTTP requests (default: 1000) """ super().__init__( exchange_name="polymarket", @@ -48,11 +48,12 @@ def __init__( private_key=private_key, proxy_address=proxy_address, signature_type=signature_type, + wallet_address=wallet_address, + signer=signer, base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, - wallet_address=wallet_address, - signer=signer, + rate_limit=rate_limit, ) self.api_secret = api_secret @@ -84,6 +85,7 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + rate_limit: Optional[float] = None, ) -> None: """ Initialize Limitless client. @@ -96,6 +98,7 @@ def __init__( base_url: Base URL of the PMXT sidecar server auto_start_server: Automatically start server if not running (default: True) pmxt_api_key: Hosted PMXT API key (optional; enables hosted mode) + rate_limit: Minimum delay in milliseconds between SDK HTTP requests (default: 1000) """ super().__init__( exchange_name="limitless", @@ -104,6 +107,7 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + rate_limit=rate_limit, ) self.api_secret = api_secret @@ -128,6 +132,7 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + rate_limit: Optional[float] = None, ) -> None: """ Initialize Kalshi client. @@ -138,6 +143,7 @@ def __init__( base_url: Base URL of the PMXT sidecar server auto_start_server: Automatically start server if not running (default: True) pmxt_api_key: Hosted PMXT API key (optional; enables hosted mode) + rate_limit: Minimum delay in milliseconds between SDK HTTP requests (default: 1000) """ super().__init__( exchange_name="kalshi", @@ -146,6 +152,7 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + rate_limit=rate_limit, ) @@ -159,6 +166,7 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + rate_limit: Optional[float] = None, ) -> None: """ Initialize KalshiDemo client. @@ -169,6 +177,7 @@ def __init__( base_url: Base URL of the PMXT sidecar server auto_start_server: Automatically start server if not running (default: True) pmxt_api_key: Hosted PMXT API key (optional; enables hosted mode) + rate_limit: Minimum delay in milliseconds between SDK HTTP requests (default: 1000) """ super().__init__( exchange_name="kalshi-demo", @@ -177,6 +186,7 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + rate_limit=rate_limit, ) @@ -192,6 +202,7 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + rate_limit: Optional[float] = None, ) -> None: """ Initialize Probable client. @@ -204,6 +215,7 @@ def __init__( base_url: Base URL of the PMXT sidecar server auto_start_server: Automatically start server if not running (default: True) pmxt_api_key: Hosted PMXT API key (optional; enables hosted mode) + rate_limit: Minimum delay in milliseconds between SDK HTTP requests (default: 1000) """ super().__init__( exchange_name="probable", @@ -212,6 +224,7 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + rate_limit=rate_limit, ) self.api_secret = api_secret @@ -235,6 +248,7 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + rate_limit: Optional[float] = None, ) -> None: """ Initialize Baozi client. @@ -244,6 +258,7 @@ def __init__( base_url: Base URL of the PMXT sidecar server auto_start_server: Automatically start server if not running (default: True) pmxt_api_key: Hosted PMXT API key (optional; enables hosted mode) + rate_limit: Minimum delay in milliseconds between SDK HTTP requests (default: 1000) """ super().__init__( exchange_name="baozi", @@ -251,6 +266,7 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + rate_limit=rate_limit, ) @@ -264,6 +280,7 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + rate_limit: Optional[float] = None, ) -> None: """ Initialize Myriad client. @@ -274,6 +291,7 @@ def __init__( base_url: Base URL of the PMXT sidecar server auto_start_server: Automatically start server if not running (default: True) pmxt_api_key: Hosted PMXT API key (optional; enables hosted mode) + rate_limit: Minimum delay in milliseconds between SDK HTTP requests (default: 1000) """ super().__init__( exchange_name="myriad", @@ -282,6 +300,7 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + rate_limit=rate_limit, ) @@ -293,13 +312,12 @@ def __init__( api_key: Optional[str] = None, private_key: Optional[str] = None, proxy_address: Optional[str] = None, + wallet_address: Optional[str] = None, + signer: Optional[object] = None, base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, - # NOTE: Generated wrapper; update the generator template in - # core/scripts/generate-python-exchanges.js in a follow-up. - wallet_address: Optional[str] = None, - signer: Optional[object] = None, + rate_limit: Optional[float] = None, ) -> None: """ Initialize Opinion client. @@ -308,22 +326,24 @@ def __init__( api_key: API key for authentication (optional) private_key: Private key for authentication (optional) proxy_address: Proxy/smart wallet address (optional) + wallet_address: Ethereum address for hosted reads/writes (optional) + signer: Optional callable for signing typed_data (optional) base_url: Base URL of the PMXT sidecar server auto_start_server: Automatically start server if not running (default: True) pmxt_api_key: Hosted PMXT API key (optional; enables hosted mode) - wallet_address: Ethereum address for hosted reads/writes (optional) - signer: Optional callable for signing typed_data (optional) + rate_limit: Minimum delay in milliseconds between SDK HTTP requests (default: 1000) """ super().__init__( exchange_name="opinion", api_key=api_key, private_key=private_key, proxy_address=proxy_address, + wallet_address=wallet_address, + signer=signer, base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, - wallet_address=wallet_address, - signer=signer, + rate_limit=rate_limit, ) @@ -336,6 +356,7 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + rate_limit: Optional[float] = None, ) -> None: """ Initialize Metaculus client. @@ -345,6 +366,7 @@ def __init__( base_url: Base URL of the PMXT sidecar server auto_start_server: Automatically start server if not running (default: True) pmxt_api_key: Hosted PMXT API key (optional; enables hosted mode) + rate_limit: Minimum delay in milliseconds between SDK HTTP requests (default: 1000) """ super().__init__( exchange_name="metaculus", @@ -352,6 +374,7 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + rate_limit=rate_limit, ) @@ -365,6 +388,7 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + rate_limit: Optional[float] = None, ) -> None: """ Initialize Smarkets client. @@ -375,6 +399,7 @@ def __init__( base_url: Base URL of the PMXT sidecar server auto_start_server: Automatically start server if not running (default: True) pmxt_api_key: Hosted PMXT API key (optional; enables hosted mode) + rate_limit: Minimum delay in milliseconds between SDK HTTP requests (default: 1000) """ super().__init__( exchange_name="smarkets", @@ -383,6 +408,7 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + rate_limit=rate_limit, ) @@ -396,6 +422,7 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + rate_limit: Optional[float] = None, ) -> None: """ Initialize PolymarketUS client. @@ -406,6 +433,7 @@ def __init__( base_url: Base URL of the PMXT sidecar server auto_start_server: Automatically start server if not running (default: True) pmxt_api_key: Hosted PMXT API key (optional; enables hosted mode) + rate_limit: Minimum delay in milliseconds between SDK HTTP requests (default: 1000) """ super().__init__( exchange_name="polymarket_us", @@ -414,6 +442,7 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + rate_limit=rate_limit, ) @@ -427,6 +456,7 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + rate_limit: Optional[float] = None, ) -> None: """ Initialize Hyperliquid client. @@ -437,6 +467,7 @@ def __init__( base_url: Base URL of the PMXT sidecar server auto_start_server: Automatically start server if not running (default: True) pmxt_api_key: Hosted PMXT API key (optional; enables hosted mode) + rate_limit: Minimum delay in milliseconds between SDK HTTP requests (default: 1000) """ super().__init__( exchange_name="hyperliquid", @@ -445,6 +476,7 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + rate_limit=rate_limit, ) @@ -458,6 +490,7 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + rate_limit: Optional[float] = None, ) -> None: """ Initialize GeminiTitan client. @@ -468,6 +501,7 @@ def __init__( base_url: Base URL of the PMXT sidecar server auto_start_server: Automatically start server if not running (default: True) pmxt_api_key: Hosted PMXT API key (optional; enables hosted mode) + rate_limit: Minimum delay in milliseconds between SDK HTTP requests (default: 1000) """ super().__init__( exchange_name="gemini-titan", @@ -475,6 +509,7 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + rate_limit=rate_limit, ) self.api_secret = api_secret @@ -494,6 +529,7 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + rate_limit: Optional[float] = None, ) -> None: """ Initialize SuiBets client. @@ -502,12 +538,14 @@ def __init__( base_url: Base URL of the PMXT sidecar server auto_start_server: Automatically start server if not running (default: True) pmxt_api_key: Hosted PMXT API key (optional; enables hosted mode) + rate_limit: Minimum delay in milliseconds between SDK HTTP requests (default: 1000) """ super().__init__( exchange_name="suibets", base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + rate_limit=rate_limit, ) @@ -519,6 +557,7 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + rate_limit: Optional[float] = None, ) -> None: """ Initialize Mock client. @@ -527,12 +566,14 @@ def __init__( base_url: Base URL of the PMXT sidecar server auto_start_server: Automatically start server if not running (default: True) pmxt_api_key: Hosted PMXT API key (optional; enables hosted mode) + rate_limit: Minimum delay in milliseconds between SDK HTTP requests (default: 1000) """ super().__init__( exchange_name="mock", base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + rate_limit=rate_limit, ) @@ -544,6 +585,7 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + rate_limit: Optional[float] = None, ) -> None: """ Initialize Router client. @@ -552,12 +594,14 @@ def __init__( base_url: Base URL of the PMXT sidecar server auto_start_server: Automatically start server if not running (default: True) pmxt_api_key: Hosted PMXT API key (optional; enables hosted mode) + rate_limit: Minimum delay in milliseconds between SDK HTTP requests (default: 1000) """ super().__init__( exchange_name="router", base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + rate_limit=rate_limit, ) # Backwards-compatible aliases for exchange classes generated before underscore handling. diff --git a/sdks/python/pmxt/client.py b/sdks/python/pmxt/client.py index 95cc3f47..9a37af3f 100644 --- a/sdks/python/pmxt/client.py +++ b/sdks/python/pmxt/client.py @@ -9,6 +9,7 @@ import os import re import sys +import threading import time import urllib.error import uuid @@ -333,6 +334,7 @@ def __init__( pmxt_api_key: Optional[str] = None, wallet_address: Optional[str] = None, signer: Optional[Any] = None, + rate_limit: Optional[float] = None, ) -> None: """ Initialize an exchange client. @@ -363,6 +365,8 @@ def __init__( ``sign_typed_data(typed_data) -> 0x-prefixed hex``. When ``private_key`` is supplied without ``signer`` in hosted mode, an :class:`EthAccountSigner` is created automatically. + rate_limit: Minimum delay in milliseconds between SDK HTTP + requests. Defaults to 1000, matching core BaseExchange. """ self.exchange_name = exchange_name.lower() self.api_key = api_key @@ -372,6 +376,11 @@ def __init__( self.signature_type = signature_type self.wallet_address = wallet_address self.signer = signer + self._rate_limit: float = 1000.0 + self._last_http_request_at: float = 0.0 + self._rate_limit_lock = threading.Lock() + if rate_limit is not None: + self.rate_limit = rate_limit self.markets: Dict[str, "UnifiedMarket"] = {} self.markets_by_slug: Dict[str, "UnifiedMarket"] = {} self._loaded_markets: bool = False @@ -442,6 +451,30 @@ def __init__( self._api = DefaultApi(api_client=self._api_client) + @property + def rate_limit(self) -> float: + """Minimum delay, in milliseconds, between SDK HTTP requests.""" + return self._rate_limit + + @rate_limit.setter + def rate_limit(self, value: float) -> None: + value = float(value) + if value < 0: + raise ValueError("rate_limit must be a non-negative number of milliseconds") + self._rate_limit = value + + def _throttle_http_request(self) -> None: + """Apply the SDK-side request delay configured by ``rate_limit``.""" + with self._rate_limit_lock: + if self._rate_limit <= 0: + self._last_http_request_at = time.monotonic() + return + now = time.monotonic() + elapsed_ms = (now - self._last_http_request_at) * 1000.0 + if self._last_http_request_at > 0 and elapsed_ms < self._rate_limit: + time.sleep((self._rate_limit - elapsed_ms) / 1000.0) + self._last_http_request_at = time.monotonic() + def _handle_response(self, response: Dict[str, Any]) -> Any: """Handle API response and extract data.""" if not response.get("success"): @@ -498,6 +531,7 @@ def _fetch_with_retry(self, fn): for attempt in range(len(delays) + 1): try: + self._throttle_http_request() return fn() except (ConnectionError, OSError, urllib.error.URLError) as e: last_error = e diff --git a/sdks/python/pmxt/models.py b/sdks/python/pmxt/models.py index 084af961..cb142fbe 100644 --- a/sdks/python/pmxt/models.py +++ b/sdks/python/pmxt/models.py @@ -629,6 +629,7 @@ class ExchangeOptions(TypedDict, total=False): api_token: str proxy_address: str signature_type: Union[str, int] + rate_limit: Union[int, float] class PolymarketOptions(ExchangeOptions, total=False): diff --git a/sdks/python/tests/test_hosted_dispatch.py b/sdks/python/tests/test_hosted_dispatch.py index e0b4aba4..3069b968 100644 --- a/sdks/python/tests/test_hosted_dispatch.py +++ b/sdks/python/tests/test_hosted_dispatch.py @@ -129,6 +129,22 @@ def _make_polymarket( return Polymarket(**kwargs) +def test_rate_limit_property_defaults_and_updates() -> None: + default_api = Polymarket(pmxt_api_key=PMXT_API_KEY, auto_start_server=False) + assert default_api.rate_limit == 1000.0 + + api = Polymarket( + pmxt_api_key=PMXT_API_KEY, + auto_start_server=False, + rate_limit=25, + ) + assert api.rate_limit == 25.0 + api.rate_limit = 0 + assert api.rate_limit == 0.0 + with pytest.raises(ValueError): + api.rate_limit = -1 + + # --------------------------------------------------------------------------- # # Read-method dispatch tests # --------------------------------------------------------------------------- # diff --git a/sdks/typescript/pmxt/client.ts b/sdks/typescript/pmxt/client.ts index df11e70f..7374acbd 100644 --- a/sdks/typescript/pmxt/client.ts +++ b/sdks/typescript/pmxt/client.ts @@ -284,6 +284,14 @@ export interface ExchangeOptions { * built from it lazily. */ signer?: Signer; + + /** + * Minimum delay in milliseconds between SDK HTTP requests. + * + * Defaults to 1000ms to mirror core BaseExchange.rateLimit. The value can + * also be inspected or changed at runtime via the `rateLimit` property. + */ + rateLimit?: number; } /** @@ -336,6 +344,8 @@ export abstract class Exchange { public escrow?: Escrow; private _hostedAccount?: { depositWallet?: string; signatureType?: number }; private _accountDiscoveryPromise?: Promise; + private _rateLimit: number = 1000; + private _lastHttpRequestAt: number = 0; /** * Sticky flag: set to `true` the first time a GET read is rejected by @@ -358,6 +368,9 @@ export abstract class Exchange { this.signatureType = options.signatureType; this.walletAddress = options.walletAddress; this.signer = options.signer; + if (options.rateLimit !== undefined) { + this.rateLimit = options.rateLimit; + } // Resolve base URL + hosted API key via the shared precedence // rules. See constants.ts for the full resolution table. @@ -410,6 +423,31 @@ export abstract class Exchange { this.initPromise = this.initializeServer(autoStartServer); } + /** Minimum delay, in milliseconds, between SDK HTTP requests. */ + get rateLimit(): number { + return this._rateLimit; + } + + set rateLimit(value: number) { + if (!Number.isFinite(value) || value < 0) { + throw new RangeError("rateLimit must be a non-negative finite number of milliseconds"); + } + this._rateLimit = value; + } + + private async throttleHttpRequest(): Promise { + if (this._rateLimit <= 0) { + this._lastHttpRequestAt = Date.now(); + return; + } + const now = Date.now(); + const elapsed = now - this._lastHttpRequestAt; + if (this._lastHttpRequestAt > 0 && elapsed < this._rateLimit) { + await new Promise(resolve => setTimeout(resolve, this._rateLimit - elapsed)); + } + this._lastHttpRequestAt = Date.now(); + } + private async initializeServer(autoStartServer: boolean): Promise { if (autoStartServer) { try { @@ -535,6 +573,7 @@ export abstract class Exchange { for (let attempt = 0; attempt <= delays.length; attempt++) { try { + await this.throttleHttpRequest(); return await fetch(input, { ...init, signal: AbortSignal.timeout(30_000), @@ -3033,6 +3072,9 @@ export interface PolymarketOptions { /** Optional signature type */ signatureType?: 'eoa' | 'poly-proxy' | 'gnosis-safe' | number; + + /** Minimum delay in milliseconds between SDK HTTP requests. */ + rateLimit?: number; } export class Polymarket extends Exchange { diff --git a/sdks/typescript/tests/hosted-dispatch.test.ts b/sdks/typescript/tests/hosted-dispatch.test.ts index 88223938..261a9df2 100644 --- a/sdks/typescript/tests/hosted-dispatch.test.ts +++ b/sdks/typescript/tests/hosted-dispatch.test.ts @@ -80,6 +80,19 @@ afterEach(() => { jest.restoreAllMocks(); }); +describe("SDK rateLimit property", () => { + it("defaults to core-compatible 1000ms and can be changed at runtime", () => { + const defaultApi = new Polymarket({ pmxtApiKey: PMXT_API_KEY, autoStartServer: false }); + expect(defaultApi.rateLimit).toBe(1000); + + const api = new Polymarket({ pmxtApiKey: PMXT_API_KEY, autoStartServer: false, rateLimit: 25 }); + expect(api.rateLimit).toBe(25); + api.rateLimit = 0; + expect(api.rateLimit).toBe(0); + expect(() => { api.rateLimit = -1; }).toThrow(RangeError); + }); +}); + // -------------------------------------------------------------------------- // Read-method dispatch // --------------------------------------------------------------------------