From d0bbf9a0d38d53341f593d7eaba55259a85fe7db Mon Sep 17 00:00:00 2001 From: AbhilashG12 Date: Tue, 16 Jun 2026 21:12:34 +0530 Subject: [PATCH 1/7] fix(sdk): restore wallet_address/signer and wire websocket config - Restore wallet_address and signer parameters in Python generator - Pass websocket config to SidecarWsClient in Python and TypeScript - Store websocket config on Exchange class - Add config parameter to SidecarWsClient constructor This fixes the hosted wallet regression and ensures websocket config is actually consumed by the transport layer. Fixes #1052 Fixes #1053 --- core/scripts/generate-python-exchanges.js | 9 ++ sdks/python/pmxt/_exchanges.py | 161 ++++++++++++++++++++-- sdks/python/pmxt/client.py | 4 +- sdks/typescript/pmxt/client.ts | 11 +- sdks/typescript/pmxt/ws-client.ts | 8 +- 5 files changed, 181 insertions(+), 12 deletions(-) diff --git a/core/scripts/generate-python-exchanges.js b/core/scripts/generate-python-exchanges.js index ee0739d9..bd0adbda 100644 --- a/core/scripts/generate-python-exchanges.js +++ b/core/scripts/generate-python-exchanges.js @@ -173,9 +173,15 @@ function generateClass(exchange) { 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('wallet_address: Optional[str] = None'); + constructorParams.push('signer: Optional[object] = None'); + constructorParams.push('websocket: Optional[dict] = 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('wallet_address=wallet_address'); + superArgs.push('signer=signer'); + superArgs.push('websocket=websocket'); const docLines = []; if (creds.apiKey) docLines.push(' api_key: API key for authentication (optional)'); @@ -192,6 +198,9 @@ function generateClass(exchange) { 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(' wallet_address: Wallet address for hosted operations (optional)'); + docLines.push(' signer: Custom signer for hosted operations (optional)'); + docLines.push(' websocket: WebSocket configuration dict (optional)'); const indent4 = s => ` ${s}`; const indent8 = s => ` ${s}`; diff --git a/sdks/python/pmxt/_exchanges.py b/sdks/python/pmxt/_exchanges.py index b6402ad9..40cc99f0 100644 --- a/sdks/python/pmxt/_exchanges.py +++ b/sdks/python/pmxt/_exchanges.py @@ -21,10 +21,9 @@ def __init__( 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, + websocket: Optional[dict] = None, ) -> None: """ Initialize Polymarket client. @@ -39,8 +38,9 @@ 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) - wallet_address: Ethereum address for hosted reads/writes (optional) - signer: Optional callable for signing typed_data (optional) + wallet_address: Wallet address for hosted operations (optional) + signer: Custom signer for hosted operations (optional) + websocket: WebSocket configuration dict (optional) """ super().__init__( exchange_name="polymarket", @@ -53,6 +53,7 @@ def __init__( pmxt_api_key=pmxt_api_key, wallet_address=wallet_address, signer=signer, + websocket=websocket, ) self.api_secret = api_secret @@ -86,6 +87,10 @@ def __init__( pmxt_api_key: Optional[str] = None, wallet_address: Optional[str] = None, signer: Optional[object] = None, +<<<<<<< HEAD +======= + websocket: Optional[dict] = None, +>>>>>>> 4b8f210 (fix(sdk): restore wallet_address/signer and wire websocket config) ) -> None: """ Initialize Limitless client. @@ -98,8 +103,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) +<<<<<<< HEAD wallet_address: Ethereum address for hosted reads/writes (optional) signer: Optional callable for signing typed_data (optional) +======= + wallet_address: Wallet address for hosted operations (optional) + signer: Custom signer for hosted operations (optional) + websocket: WebSocket configuration dict (optional) +>>>>>>> 4b8f210 (fix(sdk): restore wallet_address/signer and wire websocket config) """ super().__init__( exchange_name="limitless", @@ -110,6 +121,10 @@ def __init__( pmxt_api_key=pmxt_api_key, wallet_address=wallet_address, signer=signer, +<<<<<<< HEAD +======= + websocket=websocket, +>>>>>>> 4b8f210 (fix(sdk): restore wallet_address/signer and wire websocket config) ) self.api_secret = api_secret @@ -134,6 +149,9 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + wallet_address: Optional[str] = None, + signer: Optional[object] = None, + websocket: Optional[dict] = None, ) -> None: """ Initialize Kalshi client. @@ -144,6 +162,9 @@ 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) + wallet_address: Wallet address for hosted operations (optional) + signer: Custom signer for hosted operations (optional) + websocket: WebSocket configuration dict (optional) """ super().__init__( exchange_name="kalshi", @@ -152,6 +173,9 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + wallet_address=wallet_address, + signer=signer, + websocket=websocket, ) @@ -165,6 +189,9 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + wallet_address: Optional[str] = None, + signer: Optional[object] = None, + websocket: Optional[dict] = None, ) -> None: """ Initialize KalshiDemo client. @@ -175,6 +202,9 @@ 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) + wallet_address: Wallet address for hosted operations (optional) + signer: Custom signer for hosted operations (optional) + websocket: WebSocket configuration dict (optional) """ super().__init__( exchange_name="kalshi-demo", @@ -183,6 +213,9 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + wallet_address=wallet_address, + signer=signer, + websocket=websocket, ) @@ -198,6 +231,9 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + wallet_address: Optional[str] = None, + signer: Optional[object] = None, + websocket: Optional[dict] = None, ) -> None: """ Initialize Probable client. @@ -210,6 +246,9 @@ 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) + wallet_address: Wallet address for hosted operations (optional) + signer: Custom signer for hosted operations (optional) + websocket: WebSocket configuration dict (optional) """ super().__init__( exchange_name="probable", @@ -218,6 +257,9 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + wallet_address=wallet_address, + signer=signer, + websocket=websocket, ) self.api_secret = api_secret @@ -241,6 +283,9 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + wallet_address: Optional[str] = None, + signer: Optional[object] = None, + websocket: Optional[dict] = None, ) -> None: """ Initialize Baozi client. @@ -250,6 +295,9 @@ 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) + wallet_address: Wallet address for hosted operations (optional) + signer: Custom signer for hosted operations (optional) + websocket: WebSocket configuration dict (optional) """ super().__init__( exchange_name="baozi", @@ -257,6 +305,9 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + wallet_address=wallet_address, + signer=signer, + websocket=websocket, ) @@ -270,6 +321,9 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + wallet_address: Optional[str] = None, + signer: Optional[object] = None, + websocket: Optional[dict] = None, ) -> None: """ Initialize Myriad client. @@ -280,6 +334,9 @@ 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) + wallet_address: Wallet address for hosted operations (optional) + signer: Custom signer for hosted operations (optional) + websocket: WebSocket configuration dict (optional) """ super().__init__( exchange_name="myriad", @@ -288,6 +345,9 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + wallet_address=wallet_address, + signer=signer, + websocket=websocket, ) @@ -302,10 +362,9 @@ def __init__( 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, + websocket: Optional[dict] = None, ) -> None: """ Initialize Opinion client. @@ -317,8 +376,9 @@ 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) - wallet_address: Ethereum address for hosted reads/writes (optional) - signer: Optional callable for signing typed_data (optional) + wallet_address: Wallet address for hosted operations (optional) + signer: Custom signer for hosted operations (optional) + websocket: WebSocket configuration dict (optional) """ super().__init__( exchange_name="opinion", @@ -330,6 +390,7 @@ def __init__( pmxt_api_key=pmxt_api_key, wallet_address=wallet_address, signer=signer, + websocket=websocket, ) @@ -342,6 +403,9 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + wallet_address: Optional[str] = None, + signer: Optional[object] = None, + websocket: Optional[dict] = None, ) -> None: """ Initialize Metaculus client. @@ -351,6 +415,9 @@ 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) + wallet_address: Wallet address for hosted operations (optional) + signer: Custom signer for hosted operations (optional) + websocket: WebSocket configuration dict (optional) """ super().__init__( exchange_name="metaculus", @@ -358,6 +425,9 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + wallet_address=wallet_address, + signer=signer, + websocket=websocket, ) @@ -371,6 +441,9 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + wallet_address: Optional[str] = None, + signer: Optional[object] = None, + websocket: Optional[dict] = None, ) -> None: """ Initialize Smarkets client. @@ -381,6 +454,9 @@ 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) + wallet_address: Wallet address for hosted operations (optional) + signer: Custom signer for hosted operations (optional) + websocket: WebSocket configuration dict (optional) """ super().__init__( exchange_name="smarkets", @@ -389,6 +465,9 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + wallet_address=wallet_address, + signer=signer, + websocket=websocket, ) @@ -402,6 +481,9 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + wallet_address: Optional[str] = None, + signer: Optional[object] = None, + websocket: Optional[dict] = None, ) -> None: """ Initialize PolymarketUS client. @@ -412,6 +494,9 @@ 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) + wallet_address: Wallet address for hosted operations (optional) + signer: Custom signer for hosted operations (optional) + websocket: WebSocket configuration dict (optional) """ super().__init__( exchange_name="polymarket_us", @@ -420,6 +505,9 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + wallet_address=wallet_address, + signer=signer, + websocket=websocket, ) @@ -433,6 +521,9 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + wallet_address: Optional[str] = None, + signer: Optional[object] = None, + websocket: Optional[dict] = None, ) -> None: """ Initialize Hyperliquid client. @@ -443,6 +534,9 @@ 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) + wallet_address: Wallet address for hosted operations (optional) + signer: Custom signer for hosted operations (optional) + websocket: WebSocket configuration dict (optional) """ super().__init__( exchange_name="hyperliquid", @@ -451,6 +545,9 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + wallet_address=wallet_address, + signer=signer, + websocket=websocket, ) @@ -464,6 +561,9 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + wallet_address: Optional[str] = None, + signer: Optional[object] = None, + websocket: Optional[dict] = None, ) -> None: """ Initialize GeminiTitan client. @@ -474,6 +574,9 @@ 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) + wallet_address: Wallet address for hosted operations (optional) + signer: Custom signer for hosted operations (optional) + websocket: WebSocket configuration dict (optional) """ super().__init__( exchange_name="gemini-titan", @@ -481,6 +584,9 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + wallet_address=wallet_address, + signer=signer, + websocket=websocket, ) self.api_secret = api_secret @@ -500,6 +606,9 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + wallet_address: Optional[str] = None, + signer: Optional[object] = None, + websocket: Optional[dict] = None, ) -> None: """ Initialize SuiBets client. @@ -508,12 +617,18 @@ 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) + wallet_address: Wallet address for hosted operations (optional) + signer: Custom signer for hosted operations (optional) + websocket: WebSocket configuration dict (optional) """ super().__init__( exchange_name="suibets", base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + wallet_address=wallet_address, + signer=signer, + websocket=websocket, ) @@ -522,23 +637,35 @@ class Rain(Exchange): def __init__( self, + private_key: Optional[str] = None, base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + wallet_address: Optional[str] = None, + signer: Optional[object] = None, + websocket: Optional[dict] = None, ) -> None: """ Initialize Rain client. Args: + private_key: Private key for authentication (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: Wallet address for hosted operations (optional) + signer: Custom signer for hosted operations (optional) + websocket: WebSocket configuration dict (optional) """ super().__init__( exchange_name="rain", + private_key=private_key, base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + wallet_address=wallet_address, + signer=signer, + websocket=websocket, ) @@ -550,6 +677,9 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + wallet_address: Optional[str] = None, + signer: Optional[object] = None, + websocket: Optional[dict] = None, ) -> None: """ Initialize Mock client. @@ -558,12 +688,18 @@ 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) + wallet_address: Wallet address for hosted operations (optional) + signer: Custom signer for hosted operations (optional) + websocket: WebSocket configuration dict (optional) """ super().__init__( exchange_name="mock", base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + wallet_address=wallet_address, + signer=signer, + websocket=websocket, ) @@ -575,6 +711,9 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, + wallet_address: Optional[str] = None, + signer: Optional[object] = None, + websocket: Optional[dict] = None, ) -> None: """ Initialize Router client. @@ -583,12 +722,18 @@ 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) + wallet_address: Wallet address for hosted operations (optional) + signer: Custom signer for hosted operations (optional) + websocket: WebSocket configuration dict (optional) """ super().__init__( exchange_name="router", base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, + wallet_address=wallet_address, + signer=signer, + websocket=websocket, ) # 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 cca8c805..d89117a7 100644 --- a/sdks/python/pmxt/client.py +++ b/sdks/python/pmxt/client.py @@ -2327,13 +2327,13 @@ def _get_or_create_ws(self): host = self._resolve_sidecar_host() if self.is_hosted: - client = SidecarWsClient(host, api_key=self.pmxt_api_key) + client = SidecarWsClient(host, api_key=self.pmxt_api_key,config=self.websocket,) else: server_info = self._server_manager.get_server_info() access_token = ( server_info.get("accessToken") if server_info else None ) - client = SidecarWsClient(host, access_token=access_token) + client = SidecarWsClient(host, access_token=access_token,config=self.websocket,) try: # Trigger connection to validate the endpoint exists with client._lock: diff --git a/sdks/typescript/pmxt/client.ts b/sdks/typescript/pmxt/client.ts index dd4034b3..924b82a4 100644 --- a/sdks/typescript/pmxt/client.ts +++ b/sdks/typescript/pmxt/client.ts @@ -286,6 +286,13 @@ export interface ExchangeOptions { * built from it lazily. */ signer?: Signer; + + websocket?: { + wsUrl?: string; + reconnectInterval?: number; + pingInterval?: number; + maxReconnectAttempts?: number; + }; } /** @@ -327,6 +334,7 @@ export abstract class Exchange { protected serverManager: ServerManager; protected initPromise: Promise; protected isHosted: boolean; + protected _websocketConfig?: any; /** Wallet address used for hosted endpoints that operate on a wallet. */ public walletAddress?: string; @@ -360,6 +368,7 @@ export abstract class Exchange { this.signatureType = options.signatureType; this.walletAddress = options.walletAddress; this.signer = options.signer; + this._websocketConfig = options.websocket; // Resolve base URL + hosted API key via the shared precedence // rules. See constants.ts for the full resolution table. @@ -575,7 +584,7 @@ export abstract class Exchange { : this.serverManager.getAccessToken(); const authParamName = this.isHosted ? "apiKey" : "token"; - const client = new SidecarWsClient(host, accessToken || undefined, authParamName); + const client = new SidecarWsClient(host, accessToken || undefined, authParamName ,this._websocketConfig ); try { // Trigger connection to validate the endpoint exists. // subscribe() calls ensureConnected internally, but we want diff --git a/sdks/typescript/pmxt/ws-client.ts b/sdks/typescript/pmxt/ws-client.ts index f7729480..d6cad5fb 100644 --- a/sdks/typescript/pmxt/ws-client.ts +++ b/sdks/typescript/pmxt/ws-client.ts @@ -53,10 +53,16 @@ export class SidecarWsClient { private connectPromise: Promise | null = null; - constructor(host: string, accessToken?: string, authParamName: string = "token") { + constructor(host: string, accessToken?: string, authParamName: string = "token", private config?: { + wsUrl?: string; + reconnectInterval?: number; + pingInterval?: number; + maxReconnectAttempts?: number; + }) { this.host = host; this.accessToken = accessToken; this.authParamName = authParamName; + this.config = config; } // ------------------------------------------------------------------ From 846e814ce812464d315a7427afd2b448c3c0140f Mon Sep 17 00:00:00 2001 From: AbhilashG12 Date: Wed, 17 Jun 2026 20:58:01 +0530 Subject: [PATCH 2/7] feat(sdk): expose WebSocket configuration for Polymarket and Kalshi - Add websocket parameter to TypeScript PolymarketOptions and KalshiOptions - Add websocket parameter to Python exchange classes - Update Python generator with wallet_address, signer, websocket (no duplicates) - Pass websocket config to SidecarWsClient in both SDKs - Apply websocket config (wsUrl, reconnectInterval, etc.) in TypeScript Fixes #1052 Fixes #1053 --- core/scripts/generate-python-exchanges.js | 55 +++++++++++++++++++---- sdks/python/pmxt/_exchanges.py | 2 - sdks/python/pmxt/client.py | 2 + sdks/python/pmxt/ws_client.py | 1 + sdks/typescript/pmxt/ws-client.ts | 16 +++++-- 5 files changed, 62 insertions(+), 14 deletions(-) diff --git a/core/scripts/generate-python-exchanges.js b/core/scripts/generate-python-exchanges.js index bd0adbda..55b21f87 100644 --- a/core/scripts/generate-python-exchanges.js +++ b/core/scripts/generate-python-exchanges.js @@ -137,51 +137,80 @@ function generateClass(exchange) { const extraAttrs = []; const credOverrideLines = []; + // Track which params have been added to avoid duplicates + const addedParams = new Set(); + if (creds.apiKey) { constructorParams.push('api_key: Optional[str] = None'); superArgs.push('api_key=api_key'); + addedParams.add('api_key'); } if (creds.apiToken) { constructorParams.push('api_token: Optional[str] = None'); superArgs.push('api_token=api_token'); + addedParams.add('api_token'); } if (creds.apiSecret) { constructorParams.push('api_secret: Optional[str] = None'); extraAttrs.push('self.api_secret = api_secret'); credOverrideLines.push(' if self.api_secret:', ' creds["apiSecret"] = self.api_secret'); + addedParams.add('api_secret'); } if (creds.passphrase) { constructorParams.push('passphrase: Optional[str] = None'); extraAttrs.push('self.passphrase = passphrase'); credOverrideLines.push(' if self.passphrase:', ' creds["passphrase"] = self.passphrase'); + addedParams.add('passphrase'); } if (creds.privateKey) { const pyParam = aliases['private_key'] || 'private_key'; const defaultVal = defaults['private_key'] || 'None'; constructorParams.push(`${pyParam}: Optional[str] = ${defaultVal}`); superArgs.push(`private_key=${pyParam}`); + addedParams.add(pyParam); } if (creds.funderAddress) { constructorParams.push('proxy_address: Optional[str] = None'); superArgs.push('proxy_address=proxy_address'); + addedParams.add('proxy_address'); } if (creds.signatureType) { const defaultVal = defaults['signature_type'] || 'None'; constructorParams.push(`signature_type: Optional[str] = ${defaultVal}`); superArgs.push('signature_type=signature_type'); + addedParams.add('signature_type'); } + 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('wallet_address: Optional[str] = None'); - constructorParams.push('signer: Optional[object] = None'); - constructorParams.push('websocket: Optional[dict] = 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('wallet_address=wallet_address'); - superArgs.push('signer=signer'); - superArgs.push('websocket=websocket'); + addedParams.add('base_url'); + addedParams.add('auto_start_server'); + addedParams.add('pmxt_api_key'); + + // 👇 ONLY ADD wallet_address IF NOT ALREADY ADDED VIA ALIAS (e.g., Myriad) + if (!addedParams.has('wallet_address')) { + constructorParams.push('wallet_address: Optional[str] = None'); + superArgs.push('wallet_address=wallet_address'); + addedParams.add('wallet_address'); + } + + // 👇 ONLY ADD signer IF NOT ALREADY ADDED + if (!addedParams.has('signer')) { + constructorParams.push('signer: Optional[object] = None'); + superArgs.push('signer=signer'); + addedParams.add('signer'); + } + + // 👇 ONLY ADD websocket IF NOT ALREADY ADDED + if (!addedParams.has('websocket')) { + constructorParams.push('websocket: Optional[dict] = None'); + superArgs.push('websocket=websocket'); + addedParams.add('websocket'); + } const docLines = []; if (creds.apiKey) docLines.push(' api_key: API key for authentication (optional)'); @@ -198,9 +227,17 @@ function generateClass(exchange) { 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(' wallet_address: Wallet address for hosted operations (optional)'); - docLines.push(' signer: Custom signer for hosted operations (optional)'); - docLines.push(' websocket: WebSocket configuration dict (optional)'); + + // 👇 ONLY ADD DOCS IF THE PARAM EXISTS + if (addedParams.has('wallet_address')) { + docLines.push(' wallet_address: Wallet address for hosted operations (optional)'); + } + if (addedParams.has('signer')) { + docLines.push(' signer: Custom signer for hosted operations (optional)'); + } + if (addedParams.has('websocket')) { + docLines.push(' websocket: WebSocket configuration dict (optional)'); + } const indent4 = s => ` ${s}`; const indent8 = s => ` ${s}`; diff --git a/sdks/python/pmxt/_exchanges.py b/sdks/python/pmxt/_exchanges.py index 40cc99f0..c737206b 100644 --- a/sdks/python/pmxt/_exchanges.py +++ b/sdks/python/pmxt/_exchanges.py @@ -321,7 +321,6 @@ def __init__( base_url: Optional[str] = None, auto_start_server: Optional[bool] = None, pmxt_api_key: Optional[str] = None, - wallet_address: Optional[str] = None, signer: Optional[object] = None, websocket: Optional[dict] = None, ) -> None: @@ -345,7 +344,6 @@ def __init__( base_url=base_url, auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, - wallet_address=wallet_address, signer=signer, websocket=websocket, ) diff --git a/sdks/python/pmxt/client.py b/sdks/python/pmxt/client.py index d89117a7..bef28170 100644 --- a/sdks/python/pmxt/client.py +++ b/sdks/python/pmxt/client.py @@ -334,6 +334,7 @@ def __init__( pmxt_api_key: Optional[str] = None, wallet_address: Optional[str] = None, signer: Optional[Any] = None, + websocket: Optional[dict] = None, ) -> None: """ Initialize an exchange client. @@ -376,6 +377,7 @@ def __init__( self.markets: Dict[str, "UnifiedMarket"] = {} self.markets_by_slug: Dict[str, "UnifiedMarket"] = {} self._loaded_markets: bool = False + self.websocket = websocket # Sticky flag: flipped to True the first time the sidecar rejects a # GET read with 404/405 (i.e. an older pmxt-core that only supports # POST). Once set, read methods skip the GET probe for the lifetime diff --git a/sdks/python/pmxt/ws_client.py b/sdks/python/pmxt/ws_client.py index 30710f55..92662550 100644 --- a/sdks/python/pmxt/ws_client.py +++ b/sdks/python/pmxt/ws_client.py @@ -84,6 +84,7 @@ def __init__(self, host: str, access_token: Optional[str] = None, api_key: Optio # Track active subscriptions by (method, symbol_key) -> request_id # to avoid duplicate subscribe messages for the same ticker self._active_subs: Dict[str, str] = {} + self.config = config or {} # ------------------------------------------------------------------ # Connection lifecycle diff --git a/sdks/typescript/pmxt/ws-client.ts b/sdks/typescript/pmxt/ws-client.ts index d6cad5fb..ef4ff2ca 100644 --- a/sdks/typescript/pmxt/ws-client.ts +++ b/sdks/typescript/pmxt/ws-client.ts @@ -52,8 +52,14 @@ export class SidecarWsClient { private activeSubs: Map = new Map(); private connectPromise: Promise | null = null; - - constructor(host: string, accessToken?: string, authParamName: string = "token", private config?: { + private config?: { + wsUrl?: string; + reconnectInterval?: number; + pingInterval?: number; + maxReconnectAttempts?: number; + }; + + constructor(host: string, accessToken?: string, authParamName: string = "token", config?: { wsUrl?: string; reconnectInterval?: number; pingInterval?: number; @@ -92,11 +98,15 @@ export class SidecarWsClient { hostPart = hostPart.slice("http://".length); } - let url = `${scheme}://${hostPart}/ws`; + let url = this.config?.wsUrl || `${scheme}://${hostPart}/ws`; if (this.accessToken) { url = `${url}?${this.authParamName}=${this.accessToken}`; } + const reconnectInterval = this.config?.reconnectInterval || 5000; + const pingInterval = this.config?.pingInterval || 30000; + const maxReconnectAttempts = this.config?.maxReconnectAttempts || 10; + // Use the ws package in Node.js, native WebSocket in browsers const WsConstructor = this.getWebSocketConstructor(); if (!WsConstructor) { From 01b84fd0ead69dd4b45c0a59b5c04516208f6ee0 Mon Sep 17 00:00:00 2001 From: AbhilashG12 Date: Thu, 18 Jun 2026 20:03:38 +0530 Subject: [PATCH 3/7] fix(python): regenerate _exchanges.py after rebase - Remove conflict markers from _exchanges.py - Regenerate with latest generator Fixes #1052 #1053 --- sdks/python/pmxt/_exchanges.py | 11 ----------- sdks/python/pmxt/ws_client.py | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/sdks/python/pmxt/_exchanges.py b/sdks/python/pmxt/_exchanges.py index c737206b..f845eae7 100644 --- a/sdks/python/pmxt/_exchanges.py +++ b/sdks/python/pmxt/_exchanges.py @@ -87,10 +87,7 @@ def __init__( pmxt_api_key: Optional[str] = None, wallet_address: Optional[str] = None, signer: Optional[object] = None, -<<<<<<< HEAD -======= websocket: Optional[dict] = None, ->>>>>>> 4b8f210 (fix(sdk): restore wallet_address/signer and wire websocket config) ) -> None: """ Initialize Limitless client. @@ -103,14 +100,9 @@ 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) -<<<<<<< HEAD - wallet_address: Ethereum address for hosted reads/writes (optional) - signer: Optional callable for signing typed_data (optional) -======= wallet_address: Wallet address for hosted operations (optional) signer: Custom signer for hosted operations (optional) websocket: WebSocket configuration dict (optional) ->>>>>>> 4b8f210 (fix(sdk): restore wallet_address/signer and wire websocket config) """ super().__init__( exchange_name="limitless", @@ -121,10 +113,7 @@ def __init__( pmxt_api_key=pmxt_api_key, wallet_address=wallet_address, signer=signer, -<<<<<<< HEAD -======= websocket=websocket, ->>>>>>> 4b8f210 (fix(sdk): restore wallet_address/signer and wire websocket config) ) self.api_secret = api_secret diff --git a/sdks/python/pmxt/ws_client.py b/sdks/python/pmxt/ws_client.py index 92662550..42310db9 100644 --- a/sdks/python/pmxt/ws_client.py +++ b/sdks/python/pmxt/ws_client.py @@ -66,7 +66,7 @@ class SidecarWsClient: may invoke subscribe/receive from any thread. """ - def __init__(self, host: str, access_token: Optional[str] = None, api_key: Optional[str] = None) -> None: + def __init__(self, host: str, access_token: Optional[str] = None, api_key: Optional[str] = None,config: Optional[dict] = None,) -> None: self._host = host self._access_token = access_token self._api_key = api_key From 0bd4b6a63bb86e2ea20465890371914f8f5d0a95 Mon Sep 17 00:00:00 2001 From: AbhilashG12 Date: Fri, 19 Jun 2026 21:25:01 +0530 Subject: [PATCH 4/7] fix(python): complete WebSocket config wiring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add config parameter to SidecarWsClient.__init__ - Store config as self._config - Use config.wsUrl in _ensure_connected() - Use config.reconnectInterval for reconnection delay - Store config.reconnectInterval and maxReconnectAttempts Verified: ✅ Python constructor accepts websocket config Fixes #1052 Fixes #1053 --- sdks/python/pmxt/ws_client.py | 54 ++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/sdks/python/pmxt/ws_client.py b/sdks/python/pmxt/ws_client.py index 42310db9..f3958d8b 100644 --- a/sdks/python/pmxt/ws_client.py +++ b/sdks/python/pmxt/ws_client.py @@ -84,7 +84,7 @@ def __init__(self, host: str, access_token: Optional[str] = None, api_key: Optio # Track active subscriptions by (method, symbol_key) -> request_id # to avoid duplicate subscribe messages for the same ticker self._active_subs: Dict[str, str] = {} - self.config = config or {} + self._config = config or {} # ------------------------------------------------------------------ # Connection lifecycle @@ -103,20 +103,42 @@ def _ensure_connected(self) -> None: "Install it with: pip install websocket-client" ) - scheme = "ws" - # Strip http(s):// prefix from host to build ws URL - host_part = self._host - if host_part.startswith("https://"): - host_part = host_part[len("https://"):] - scheme = "wss" - elif host_part.startswith("http://"): - host_part = host_part[len("http://"):] - - url = f"{scheme}://{host_part}/ws" - if self._api_key: - url = f"{url}?apiKey={self._api_key}" - elif self._access_token: - url = f"{url}?token={self._access_token}" + # ✅ CHECK FOR CUSTOM WS URL FROM CONFIG + custom_ws_url = self._config.get("wsUrl") if self._config else None + + if custom_ws_url: + # Use custom WebSocket URL + url = custom_ws_url + # Append auth parameters if not already in URL + if self._api_key: + if "?" in url: + url = f"{url}&apiKey={self._api_key}" + else: + url = f"{url}?apiKey={self._api_key}" + elif self._access_token: + if "?" in url: + url = f"{url}&token={self._access_token}" + else: + url = f"{url}?token={self._access_token}" + else: + # Build default URL from host + scheme = "ws" + host_part = self._host + if host_part.startswith("https://"): + host_part = host_part[len("https://"):] + scheme = "wss" + elif host_part.startswith("http://"): + host_part = host_part[len("http://"):] + + url = f"{scheme}://{host_part}/ws" + if self._api_key: + url = f"{url}?apiKey={self._api_key}" + elif self._access_token: + url = f"{url}?token={self._access_token}" + + # ✅ Get reconnect settings from config + reconnect_interval = self._config.get("reconnectInterval", 5000) if self._config else 5000 + max_reconnect_attempts = self._config.get("maxReconnectAttempts", 10) if self._config else 10 last_error: Optional[Exception] = None ws = None @@ -133,7 +155,7 @@ def _ensure_connected(self) -> None: except Exception: pass if attempt < CONNECT_ATTEMPTS - 1: - time.sleep(0.25 * (attempt + 1)) + time.sleep(reconnect_interval / 1000.0) # ✅ Use reconnect_interval if last_error is not None: raise last_error From f13bcbb651ec4f51729f67e9bf55e21643cdbd03 Mon Sep 17 00:00:00 2001 From: AbhilashG12 Date: Sat, 20 Jun 2026 20:53:27 +0530 Subject: [PATCH 5/7] fix: add websocket to PolymarketOptions and handle existing query strings in wsUrl --- sdks/typescript/pmxt/client.ts | 11 +++++++++++ sdks/typescript/pmxt/ws-client.ts | 4 +++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/sdks/typescript/pmxt/client.ts b/sdks/typescript/pmxt/client.ts index 924b82a4..238ca07a 100644 --- a/sdks/typescript/pmxt/client.ts +++ b/sdks/typescript/pmxt/client.ts @@ -3027,6 +3027,17 @@ export interface PolymarketOptions { /** Optional signature type */ signatureType?: 'eoa' | 'poly-proxy' | 'gnosis-safe' | number; + + websocket?: { + /** Custom WebSocket endpoint URL (e.g., "wss://custom.example.com/ws") */ + wsUrl?: string; + /** Reconnection delay in milliseconds (default: 5000) */ + reconnectInterval?: number; + /** Heartbeat ping interval in milliseconds (default: 30000) */ + pingInterval?: number; + /** Maximum number of reconnection attempts (default: 10) */ + maxReconnectAttempts?: number; + }; } export class Polymarket extends Exchange { diff --git a/sdks/typescript/pmxt/ws-client.ts b/sdks/typescript/pmxt/ws-client.ts index ef4ff2ca..3f9e1c2d 100644 --- a/sdks/typescript/pmxt/ws-client.ts +++ b/sdks/typescript/pmxt/ws-client.ts @@ -100,7 +100,9 @@ export class SidecarWsClient { let url = this.config?.wsUrl || `${scheme}://${hostPart}/ws`; if (this.accessToken) { - url = `${url}?${this.authParamName}=${this.accessToken}`; + // Check if the URL already contains a query string + const separator = url.includes('?') ? '&' : '?'; + url = `${url}${separator}${this.authParamName}=${this.accessToken}`; } const reconnectInterval = this.config?.reconnectInterval || 5000; From 464af7e72121bd4801bd4c612f99b0331986dd65 Mon Sep 17 00:00:00 2001 From: AbhilashG12 Date: Sun, 21 Jun 2026 20:46:06 +0530 Subject: [PATCH 6/7] fix: enforce configured maxReconnectAttempts in Python WS client loop --- sdks/python/pmxt/ws_client.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/sdks/python/pmxt/ws_client.py b/sdks/python/pmxt/ws_client.py index f3958d8b..53c4708d 100644 --- a/sdks/python/pmxt/ws_client.py +++ b/sdks/python/pmxt/ws_client.py @@ -19,7 +19,7 @@ from .errors import PmxtError MAX_QUEUED_MESSAGES_PER_SUBSCRIPTION = 100_000 -CONNECT_ATTEMPTS = 3 +CONNECT_ATTEMPTS = 3 # Left for fallback defaults _NO_DATA = object() @@ -136,13 +136,15 @@ def _ensure_connected(self) -> None: elif self._access_token: url = f"{url}?token={self._access_token}" - # ✅ Get reconnect settings from config + # ✅ Get reconnect settings from config (defaulting to 3 attempts to match legacy behavior) reconnect_interval = self._config.get("reconnectInterval", 5000) if self._config else 5000 - max_reconnect_attempts = self._config.get("maxReconnectAttempts", 10) if self._config else 10 + max_reconnect_attempts = self._config.get("maxReconnectAttempts", CONNECT_ATTEMPTS) if self._config else CONNECT_ATTEMPTS last_error: Optional[Exception] = None ws = None - for attempt in range(CONNECT_ATTEMPTS): + + # ✅ FIXED: Use max_reconnect_attempts instead of CONNECT_ATTEMPTS + for attempt in range(max_reconnect_attempts): ws = websocket.WebSocket() try: _connect_websocket(ws, url, timeout=10) @@ -154,8 +156,9 @@ def _ensure_connected(self) -> None: ws.close() except Exception: pass - if attempt < CONNECT_ATTEMPTS - 1: - time.sleep(reconnect_interval / 1000.0) # ✅ Use reconnect_interval + # ✅ FIXED: Honor the config variable for the attempt boundary too + if attempt < max_reconnect_attempts - 1: + time.sleep(reconnect_interval / 1000.0) if last_error is not None: raise last_error @@ -398,4 +401,4 @@ def close(self) -> None: @property def connected(self) -> bool: """True if the WebSocket is currently connected.""" - return self._ws is not None and not self._closed + return self._ws is not None and not self._closed \ No newline at end of file From b56df7c7563f04152129fef63547386f9b8d9e30 Mon Sep 17 00:00:00 2001 From: AbhilashG12 Date: Mon, 22 Jun 2026 21:04:23 +0530 Subject: [PATCH 7/7] fix(ts): fully implement maxReconnectAttempts and pingInterval resilience --- sdks/typescript/pmxt/ws-client.ts | 69 +++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/sdks/typescript/pmxt/ws-client.ts b/sdks/typescript/pmxt/ws-client.ts index 3f9e1c2d..037a10e4 100644 --- a/sdks/typescript/pmxt/ws-client.ts +++ b/sdks/typescript/pmxt/ws-client.ts @@ -42,6 +42,10 @@ export class SidecarWsClient { private authParamName: string; private closed = false; + // Tracking variables for connection resilience + private reconnectAttempts: number = 0; + private pingIntervalId?: any; + /** requestId -> queued data payloads for single-event watch methods */ private dataQueues: Map = new Map(); /** requestId[:symbol] -> latest data payload for batch snapshots */ @@ -100,14 +104,15 @@ export class SidecarWsClient { let url = this.config?.wsUrl || `${scheme}://${hostPart}/ws`; if (this.accessToken) { - // Check if the URL already contains a query string - const separator = url.includes('?') ? '&' : '?'; - url = `${url}${separator}${this.authParamName}=${this.accessToken}`; + // Check if the URL already contains a query string + const separator = url.includes('?') ? '&' : '?'; + url = `${url}${separator}${this.authParamName}=${this.accessToken}`; } - const reconnectInterval = this.config?.reconnectInterval || 5000; - const pingInterval = this.config?.pingInterval || 30000; - const maxReconnectAttempts = this.config?.maxReconnectAttempts || 10; + // Extract connection tuning parameters (with fallback defaults) + const reconnectInterval = this.config?.reconnectInterval ?? 5000; + const pingInterval = this.config?.pingInterval ?? 30000; + const maxReconnectAttempts = this.config?.maxReconnectAttempts ?? 10; // Use the ws package in Node.js, native WebSocket in browsers const WsConstructor = this.getWebSocketConstructor(); @@ -121,6 +126,21 @@ export class SidecarWsClient { ws.onopen = () => { this.ws = ws; + this.reconnectAttempts = 0; // Reset attempts upon successful connection + + // Initialize heartbeat if configured + if (pingInterval > 0) { + this.pingIntervalId = setInterval(() => { + if (this.ws && this.ws.readyState === 1 /* WebSocket.OPEN */) { + // Try native ping (Node 'ws' library), fallback to sending a ping frame + if (typeof (this.ws as any).ping === 'function') { + (this.ws as any).ping(); + } else { + this.ws.send(JSON.stringify({ action: 'ping' })); + } + } + }, pingInterval); + } resolve(); }; @@ -138,14 +158,41 @@ export class SidecarWsClient { sub.resolve = null; } } - this.closed = true; + // IMPORTANT: Do NOT set this.closed = true here. + // Let ws.onclose handle the reconnect logic below. this.ws = null; } }; ws.onclose = () => { - this.closed = true; + // Stop the heartbeat to prevent memory leaks + if (this.pingIntervalId) { + clearInterval(this.pingIntervalId); + this.pingIntervalId = undefined; + } this.ws = null; + + // Only attempt reconnect if we haven't maxed out AND we didn't close it intentionally + if (!this.closed && this.reconnectAttempts < maxReconnectAttempts) { + this.reconnectAttempts++; + setTimeout(() => { + // Abort if the user called .close() while we were waiting + if (this.closed) return; + + // Prevent overlapping connectPromises + if (!this.connectPromise) { + this.connectPromise = this.connect(); + this.connectPromise.catch(err => { + logger.debug('[SidecarWsClient] Reconnect attempt failed', { error: String(err) }); + }).finally(() => { + this.connectPromise = null; + }); + } + }, reconnectInterval); + } else if (!this.closed) { + // Max attempts reached, mark as terminal + this.closed = true; + } }; ws.onmessage = (event: any) => { @@ -358,6 +405,10 @@ export class SidecarWsClient { close(): void { this.closed = true; + if (this.pingIntervalId) { + clearInterval(this.pingIntervalId); + this.pingIntervalId = undefined; + } if (this.ws) { try { this.ws.close(); @@ -414,4 +465,4 @@ export class SidecarWsClient { }; }); } -} +} \ No newline at end of file