Skip to content

Commit bcab4d4

Browse files
committed
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
1 parent 72dc168 commit bcab4d4

5 files changed

Lines changed: 62 additions & 14 deletions

File tree

core/scripts/generate-python-exchanges.js

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,51 +137,80 @@ function generateClass(exchange) {
137137
const extraAttrs = [];
138138
const credOverrideLines = [];
139139

140+
// Track which params have been added to avoid duplicates
141+
const addedParams = new Set();
142+
140143
if (creds.apiKey) {
141144
constructorParams.push('api_key: Optional[str] = None');
142145
superArgs.push('api_key=api_key');
146+
addedParams.add('api_key');
143147
}
144148
if (creds.apiToken) {
145149
constructorParams.push('api_token: Optional[str] = None');
146150
superArgs.push('api_token=api_token');
151+
addedParams.add('api_token');
147152
}
148153
if (creds.apiSecret) {
149154
constructorParams.push('api_secret: Optional[str] = None');
150155
extraAttrs.push('self.api_secret = api_secret');
151156
credOverrideLines.push(' if self.api_secret:', ' creds["apiSecret"] = self.api_secret');
157+
addedParams.add('api_secret');
152158
}
153159
if (creds.passphrase) {
154160
constructorParams.push('passphrase: Optional[str] = None');
155161
extraAttrs.push('self.passphrase = passphrase');
156162
credOverrideLines.push(' if self.passphrase:', ' creds["passphrase"] = self.passphrase');
163+
addedParams.add('passphrase');
157164
}
158165
if (creds.privateKey) {
159166
const pyParam = aliases['private_key'] || 'private_key';
160167
const defaultVal = defaults['private_key'] || 'None';
161168
constructorParams.push(`${pyParam}: Optional[str] = ${defaultVal}`);
162169
superArgs.push(`private_key=${pyParam}`);
170+
addedParams.add(pyParam);
163171
}
164172
if (creds.funderAddress) {
165173
constructorParams.push('proxy_address: Optional[str] = None');
166174
superArgs.push('proxy_address=proxy_address');
175+
addedParams.add('proxy_address');
167176
}
168177
if (creds.signatureType) {
169178
const defaultVal = defaults['signature_type'] || 'None';
170179
constructorParams.push(`signature_type: Optional[str] = ${defaultVal}`);
171180
superArgs.push('signature_type=signature_type');
181+
addedParams.add('signature_type');
172182
}
183+
173184
constructorParams.push('base_url: Optional[str] = None');
174185
constructorParams.push('auto_start_server: Optional[bool] = None');
175186
constructorParams.push('pmxt_api_key: Optional[str] = None');
176-
constructorParams.push('wallet_address: Optional[str] = None');
177-
constructorParams.push('signer: Optional[object] = None');
178-
constructorParams.push('websocket: Optional[dict] = None');
179187
superArgs.push('base_url=base_url');
180188
superArgs.push('auto_start_server=auto_start_server');
181189
superArgs.push('pmxt_api_key=pmxt_api_key');
182-
superArgs.push('wallet_address=wallet_address');
183-
superArgs.push('signer=signer');
184-
superArgs.push('websocket=websocket');
190+
addedParams.add('base_url');
191+
addedParams.add('auto_start_server');
192+
addedParams.add('pmxt_api_key');
193+
194+
// 👇 ONLY ADD wallet_address IF NOT ALREADY ADDED VIA ALIAS (e.g., Myriad)
195+
if (!addedParams.has('wallet_address')) {
196+
constructorParams.push('wallet_address: Optional[str] = None');
197+
superArgs.push('wallet_address=wallet_address');
198+
addedParams.add('wallet_address');
199+
}
200+
201+
// 👇 ONLY ADD signer IF NOT ALREADY ADDED
202+
if (!addedParams.has('signer')) {
203+
constructorParams.push('signer: Optional[object] = None');
204+
superArgs.push('signer=signer');
205+
addedParams.add('signer');
206+
}
207+
208+
// 👇 ONLY ADD websocket IF NOT ALREADY ADDED
209+
if (!addedParams.has('websocket')) {
210+
constructorParams.push('websocket: Optional[dict] = None');
211+
superArgs.push('websocket=websocket');
212+
addedParams.add('websocket');
213+
}
185214

186215
const docLines = [];
187216
if (creds.apiKey) docLines.push(' api_key: API key for authentication (optional)');
@@ -198,9 +227,17 @@ function generateClass(exchange) {
198227
docLines.push(' base_url: Base URL of the PMXT sidecar server');
199228
docLines.push(' auto_start_server: Automatically start server if not running (default: True)');
200229
docLines.push(' pmxt_api_key: Hosted PMXT API key (optional; enables hosted mode)');
201-
docLines.push(' wallet_address: Wallet address for hosted operations (optional)');
202-
docLines.push(' signer: Custom signer for hosted operations (optional)');
203-
docLines.push(' websocket: WebSocket configuration dict (optional)');
230+
231+
// 👇 ONLY ADD DOCS IF THE PARAM EXISTS
232+
if (addedParams.has('wallet_address')) {
233+
docLines.push(' wallet_address: Wallet address for hosted operations (optional)');
234+
}
235+
if (addedParams.has('signer')) {
236+
docLines.push(' signer: Custom signer for hosted operations (optional)');
237+
}
238+
if (addedParams.has('websocket')) {
239+
docLines.push(' websocket: WebSocket configuration dict (optional)');
240+
}
204241

205242
const indent4 = s => ` ${s}`;
206243
const indent8 = s => ` ${s}`;

sdks/python/pmxt/_exchanges.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,6 @@ def __init__(
321321
base_url: Optional[str] = None,
322322
auto_start_server: Optional[bool] = None,
323323
pmxt_api_key: Optional[str] = None,
324-
wallet_address: Optional[str] = None,
325324
signer: Optional[object] = None,
326325
websocket: Optional[dict] = None,
327326
) -> None:
@@ -345,7 +344,6 @@ def __init__(
345344
base_url=base_url,
346345
auto_start_server=auto_start_server,
347346
pmxt_api_key=pmxt_api_key,
348-
wallet_address=wallet_address,
349347
signer=signer,
350348
websocket=websocket,
351349
)

sdks/python/pmxt/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ def __init__(
334334
pmxt_api_key: Optional[str] = None,
335335
wallet_address: Optional[str] = None,
336336
signer: Optional[Any] = None,
337+
websocket: Optional[dict] = None,
337338
) -> None:
338339
"""
339340
Initialize an exchange client.
@@ -376,6 +377,7 @@ def __init__(
376377
self.markets: Dict[str, "UnifiedMarket"] = {}
377378
self.markets_by_slug: Dict[str, "UnifiedMarket"] = {}
378379
self._loaded_markets: bool = False
380+
self.websocket = websocket
379381
# Sticky flag: flipped to True the first time the sidecar rejects a
380382
# GET read with 404/405 (i.e. an older pmxt-core that only supports
381383
# POST). Once set, read methods skip the GET probe for the lifetime

sdks/python/pmxt/ws_client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ def __init__(self, host: str, access_token: Optional[str] = None, api_key: Optio
8484
# Track active subscriptions by (method, symbol_key) -> request_id
8585
# to avoid duplicate subscribe messages for the same ticker
8686
self._active_subs: Dict[str, str] = {}
87+
self.config = config or {}
8788

8889
# ------------------------------------------------------------------
8990
# Connection lifecycle

sdks/typescript/pmxt/ws-client.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,14 @@ export class SidecarWsClient {
5252
private activeSubs: Map<string, string> = new Map();
5353

5454
private connectPromise: Promise<void> | null = null;
55-
56-
constructor(host: string, accessToken?: string, authParamName: string = "token", private config?: {
55+
private config?: {
56+
wsUrl?: string;
57+
reconnectInterval?: number;
58+
pingInterval?: number;
59+
maxReconnectAttempts?: number;
60+
};
61+
62+
constructor(host: string, accessToken?: string, authParamName: string = "token", config?: {
5763
wsUrl?: string;
5864
reconnectInterval?: number;
5965
pingInterval?: number;
@@ -92,11 +98,15 @@ export class SidecarWsClient {
9298
hostPart = hostPart.slice("http://".length);
9399
}
94100

95-
let url = `${scheme}://${hostPart}/ws`;
101+
let url = this.config?.wsUrl || `${scheme}://${hostPart}/ws`;
96102
if (this.accessToken) {
97103
url = `${url}?${this.authParamName}=${this.accessToken}`;
98104
}
99105

106+
const reconnectInterval = this.config?.reconnectInterval || 5000;
107+
const pingInterval = this.config?.pingInterval || 30000;
108+
const maxReconnectAttempts = this.config?.maxReconnectAttempts || 10;
109+
100110
// Use the ws package in Node.js, native WebSocket in browsers
101111
const WsConstructor = this.getWebSocketConstructor();
102112
if (!WsConstructor) {

0 commit comments

Comments
 (0)