@structbuild/sdk 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -0
- package/dist/client.d.ts +25 -0
- package/dist/errors.d.ts +24 -0
- package/dist/generated/polymarket.d.ts +2924 -0
- package/dist/http.d.ts +18 -0
- package/dist/index.cjs +898 -0
- package/dist/index.cjs.map +25 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +866 -0
- package/dist/index.js.map +25 -0
- package/dist/namespaces/base.d.ts +9 -0
- package/dist/namespaces/bonds.d.ts +7 -0
- package/dist/namespaces/events.d.ts +10 -0
- package/dist/namespaces/holders.d.ts +12 -0
- package/dist/namespaces/index.d.ts +10 -0
- package/dist/namespaces/markets.d.ts +16 -0
- package/dist/namespaces/scoring.d.ts +10 -0
- package/dist/namespaces/search.d.ts +7 -0
- package/dist/namespaces/series.d.ts +9 -0
- package/dist/namespaces/tags.d.ts +8 -0
- package/dist/namespaces/trader.d.ts +18 -0
- package/dist/paginate.d.ts +3 -0
- package/dist/types/common.d.ts +10 -0
- package/dist/types/helpers.d.ts +21 -0
- package/dist/types/http.d.ts +42 -0
- package/dist/types/index.d.ts +492 -0
- package/dist/types/ws.d.ts +110 -0
- package/dist/ws-transport.d.ts +35 -0
- package/dist/ws.d.ts +46 -0
- package/package.json +35 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,866 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
class StructError extends Error {
|
|
3
|
+
constructor(message, options) {
|
|
4
|
+
super(message, options);
|
|
5
|
+
this.name = "StructError";
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class HttpError extends StructError {
|
|
10
|
+
status;
|
|
11
|
+
statusText;
|
|
12
|
+
body;
|
|
13
|
+
responseHeaders;
|
|
14
|
+
constructor(status, statusText, body, responseHeaders) {
|
|
15
|
+
super(`HTTP ${status}: ${statusText}`);
|
|
16
|
+
this.name = "HttpError";
|
|
17
|
+
this.status = status;
|
|
18
|
+
this.statusText = statusText;
|
|
19
|
+
this.body = body;
|
|
20
|
+
this.responseHeaders = responseHeaders;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class NetworkError extends StructError {
|
|
25
|
+
constructor(message, options) {
|
|
26
|
+
super(message, options);
|
|
27
|
+
this.name = "NetworkError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class TimeoutError extends StructError {
|
|
32
|
+
constructor(timeout) {
|
|
33
|
+
super(`Request timed out after ${timeout}ms`);
|
|
34
|
+
this.name = "TimeoutError";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class WebSocketError extends StructError {
|
|
39
|
+
constructor(message, options) {
|
|
40
|
+
super(message, options);
|
|
41
|
+
this.name = "WebSocketError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class WebSocketClosedError extends WebSocketError {
|
|
46
|
+
code;
|
|
47
|
+
reason;
|
|
48
|
+
constructor(code, reason) {
|
|
49
|
+
super(`WebSocket closed: ${code} ${reason}`);
|
|
50
|
+
this.name = "WebSocketClosedError";
|
|
51
|
+
this.code = code;
|
|
52
|
+
this.reason = reason;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/http.ts
|
|
57
|
+
var DEFAULT_TIMEOUT = 30000;
|
|
58
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
59
|
+
var DEFAULT_INITIAL_DELAY_MS = 1000;
|
|
60
|
+
var DEFAULT_MAX_DELAY_MS = 30000;
|
|
61
|
+
var RETRYABLE_STATUSES = new Set([429, 500, 502, 503, 504]);
|
|
62
|
+
|
|
63
|
+
class HttpClient {
|
|
64
|
+
baseUrl;
|
|
65
|
+
defaultHeaders;
|
|
66
|
+
timeout;
|
|
67
|
+
retry;
|
|
68
|
+
onRequest;
|
|
69
|
+
onResponse;
|
|
70
|
+
constructor(config) {
|
|
71
|
+
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
72
|
+
this.defaultHeaders = config.defaultHeaders ?? {};
|
|
73
|
+
this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
74
|
+
this.retry = config.retry;
|
|
75
|
+
this.onRequest = config.onRequest;
|
|
76
|
+
this.onResponse = config.onResponse;
|
|
77
|
+
}
|
|
78
|
+
async get(path, options) {
|
|
79
|
+
return this.request("GET", path, options);
|
|
80
|
+
}
|
|
81
|
+
async request(method, path, options) {
|
|
82
|
+
const maxRetries = this.retry ? this.retry.maxRetries ?? DEFAULT_MAX_RETRIES : 0;
|
|
83
|
+
const initialDelay = this.retry?.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
|
|
84
|
+
const maxDelay = this.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
|
|
85
|
+
let lastError;
|
|
86
|
+
for (let attempt = 0;attempt <= maxRetries; attempt++) {
|
|
87
|
+
if (attempt > 0 && lastError !== undefined) {
|
|
88
|
+
const retryAfter = lastError instanceof HttpError ? this.parseRetryAfter(lastError) : undefined;
|
|
89
|
+
const exponentialDelay = Math.min(initialDelay * 2 ** (attempt - 1), maxDelay);
|
|
90
|
+
const delay = retryAfter ?? exponentialDelay;
|
|
91
|
+
await this.sleep(delay, options?.signal);
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
return await this.executeRequest(method, path, options);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (attempt < maxRetries && this.shouldRetry(error)) {
|
|
97
|
+
lastError = error;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (lastError instanceof Error) {
|
|
104
|
+
throw lastError;
|
|
105
|
+
}
|
|
106
|
+
throw new NetworkError("Request failed after retries");
|
|
107
|
+
}
|
|
108
|
+
async executeRequest(method, path, options) {
|
|
109
|
+
const url = this.buildUrl(path, options?.params);
|
|
110
|
+
const headers = { ...this.defaultHeaders, ...options?.headers };
|
|
111
|
+
const controller = new AbortController;
|
|
112
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
113
|
+
const signal = options?.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
|
|
114
|
+
try {
|
|
115
|
+
if (this.onRequest) {
|
|
116
|
+
try {
|
|
117
|
+
await this.onRequest({ method, url, headers });
|
|
118
|
+
} catch {}
|
|
119
|
+
}
|
|
120
|
+
const startTime = performance.now();
|
|
121
|
+
const response = await fetch(url, { method, headers, signal });
|
|
122
|
+
if (this.onResponse) {
|
|
123
|
+
try {
|
|
124
|
+
await this.onResponse({
|
|
125
|
+
method,
|
|
126
|
+
url,
|
|
127
|
+
status: response.status,
|
|
128
|
+
headers: response.headers,
|
|
129
|
+
durationMs: performance.now() - startTime
|
|
130
|
+
});
|
|
131
|
+
} catch {}
|
|
132
|
+
}
|
|
133
|
+
if (!response.ok) {
|
|
134
|
+
let body;
|
|
135
|
+
try {
|
|
136
|
+
body = await response.json();
|
|
137
|
+
} catch {
|
|
138
|
+
body = await response.text();
|
|
139
|
+
}
|
|
140
|
+
throw new HttpError(response.status, response.statusText, body, response.headers);
|
|
141
|
+
}
|
|
142
|
+
return await this.parseApiResponse(response, method);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (error instanceof HttpError)
|
|
145
|
+
throw error;
|
|
146
|
+
if (error instanceof SyntaxError || error instanceof TypeError) {
|
|
147
|
+
throw new StructError("Failed to parse HTTP response body", { cause: error });
|
|
148
|
+
}
|
|
149
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
150
|
+
if (options?.signal?.aborted)
|
|
151
|
+
throw error;
|
|
152
|
+
throw new TimeoutError(this.timeout);
|
|
153
|
+
}
|
|
154
|
+
throw new NetworkError("Network request failed", { cause: error });
|
|
155
|
+
} finally {
|
|
156
|
+
clearTimeout(timeoutId);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
shouldRetry(error) {
|
|
160
|
+
if (error instanceof HttpError) {
|
|
161
|
+
return RETRYABLE_STATUSES.has(error.status);
|
|
162
|
+
}
|
|
163
|
+
return error instanceof NetworkError || error instanceof TimeoutError;
|
|
164
|
+
}
|
|
165
|
+
async parseApiResponse(response, method) {
|
|
166
|
+
if (method === "HEAD" || response.status === 204 || response.status === 205) {
|
|
167
|
+
return { data: undefined, message: null, success: true };
|
|
168
|
+
}
|
|
169
|
+
const text = await response.text();
|
|
170
|
+
if (text.length === 0) {
|
|
171
|
+
return { data: undefined, message: null, success: true };
|
|
172
|
+
}
|
|
173
|
+
const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
|
|
174
|
+
if (!contentType.includes("json")) {
|
|
175
|
+
return { data: text, message: null, success: true };
|
|
176
|
+
}
|
|
177
|
+
const body = JSON.parse(text);
|
|
178
|
+
if (body && typeof body === "object" && "success" in body && "data" in body) {
|
|
179
|
+
return {
|
|
180
|
+
data: body.data,
|
|
181
|
+
message: body.message ?? null,
|
|
182
|
+
success: body.success,
|
|
183
|
+
info: body.info
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
return { data: body, message: null, success: true };
|
|
187
|
+
}
|
|
188
|
+
parseRetryAfter(error) {
|
|
189
|
+
const header = error.responseHeaders?.get("retry-after");
|
|
190
|
+
if (!header)
|
|
191
|
+
return;
|
|
192
|
+
const seconds = Number(header);
|
|
193
|
+
if (!Number.isNaN(seconds) && seconds > 0)
|
|
194
|
+
return seconds * 1000;
|
|
195
|
+
const date = Date.parse(header);
|
|
196
|
+
if (!Number.isNaN(date)) {
|
|
197
|
+
const delay = date - Date.now();
|
|
198
|
+
return delay > 0 ? delay : undefined;
|
|
199
|
+
}
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
sleep(ms, signal) {
|
|
203
|
+
return new Promise((resolve, reject) => {
|
|
204
|
+
if (signal?.aborted) {
|
|
205
|
+
reject(signal.reason);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const timer = setTimeout(resolve, ms);
|
|
209
|
+
signal?.addEventListener("abort", () => {
|
|
210
|
+
clearTimeout(timer);
|
|
211
|
+
reject(signal.reason);
|
|
212
|
+
}, { once: true });
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
buildUrl(path, params) {
|
|
216
|
+
const url = new URL(path, this.baseUrl);
|
|
217
|
+
if (params) {
|
|
218
|
+
for (const [key, value] of Object.entries(params)) {
|
|
219
|
+
if (value !== undefined) {
|
|
220
|
+
url.searchParams.set(key, String(value));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return url.toString();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/namespaces/base.ts
|
|
229
|
+
class Namespace {
|
|
230
|
+
http;
|
|
231
|
+
defaultVenue;
|
|
232
|
+
constructor(http, defaultVenue) {
|
|
233
|
+
this.http = http;
|
|
234
|
+
this.defaultVenue = defaultVenue;
|
|
235
|
+
}
|
|
236
|
+
get(venue, path, options) {
|
|
237
|
+
return this.http.get(`/${venue ?? this.defaultVenue}${path}`, options);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// src/namespaces/holders.ts
|
|
241
|
+
class HoldersNamespace extends Namespace {
|
|
242
|
+
async getMarketHolders(params, venue) {
|
|
243
|
+
const { conditionId, ...query } = params;
|
|
244
|
+
return this.get(venue, `/holders/markets/${encodeURIComponent(conditionId)}`, { params: query });
|
|
245
|
+
}
|
|
246
|
+
async getEventHolders(params, venue) {
|
|
247
|
+
const { eventSlug, ...query } = params;
|
|
248
|
+
return this.get(venue, `/holders/events/${encodeURIComponent(eventSlug)}`, { params: query });
|
|
249
|
+
}
|
|
250
|
+
async getPositionHolders(params, venue) {
|
|
251
|
+
const { positionId, ...query } = params;
|
|
252
|
+
return this.get(venue, `/holders/positions/${encodeURIComponent(positionId)}`, { params: query });
|
|
253
|
+
}
|
|
254
|
+
async getMarketHoldersHistory(params, venue) {
|
|
255
|
+
const { conditionId, ...query } = params;
|
|
256
|
+
return this.get(venue, `/holders/markets/${encodeURIComponent(conditionId)}/history`, { params: query });
|
|
257
|
+
}
|
|
258
|
+
async getEventHoldersHistory(params, venue) {
|
|
259
|
+
const { eventSlug, ...query } = params;
|
|
260
|
+
return this.get(venue, `/holders/events/${encodeURIComponent(eventSlug)}/history`, { params: query });
|
|
261
|
+
}
|
|
262
|
+
async getPositionHoldersHistory(params, venue) {
|
|
263
|
+
const { positionId, ...query } = params;
|
|
264
|
+
return this.get(venue, `/holders/positions/${encodeURIComponent(positionId)}/history`, { params: query });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// src/namespaces/scoring.ts
|
|
268
|
+
class ScoringNamespace extends Namespace {
|
|
269
|
+
async getTraderScore(params, venue) {
|
|
270
|
+
return this.get(venue, `/scoring/trader/${encodeURIComponent(params.address)}`);
|
|
271
|
+
}
|
|
272
|
+
async getSmartMoneyLeaderboard(params, venue) {
|
|
273
|
+
return this.get(venue, "/scoring/leaderboard/smart-money", { params: { ...params } });
|
|
274
|
+
}
|
|
275
|
+
async getInsiderLeaderboard(params, venue) {
|
|
276
|
+
return this.get(venue, "/scoring/leaderboard/insiders", { params: { ...params } });
|
|
277
|
+
}
|
|
278
|
+
async getBots(params, venue) {
|
|
279
|
+
return this.get(venue, "/scoring/bots", { params: { ...params } });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// src/namespaces/tags.ts
|
|
283
|
+
class TagsNamespace extends Namespace {
|
|
284
|
+
async getTags(params, venue) {
|
|
285
|
+
return this.get(venue, "/tags", { params: { ...params } });
|
|
286
|
+
}
|
|
287
|
+
async getTag(params, venue) {
|
|
288
|
+
return this.get(venue, `/tags/${encodeURIComponent(params.identifier)}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// src/namespaces/events.ts
|
|
292
|
+
class EventsNamespace extends Namespace {
|
|
293
|
+
async getEvent(params, venue) {
|
|
294
|
+
const { id, ...query } = params;
|
|
295
|
+
return this.get(venue, `/events/${encodeURIComponent(id)}`, { params: query });
|
|
296
|
+
}
|
|
297
|
+
async getEventBySlug(params, venue) {
|
|
298
|
+
const { slug, ...query } = params;
|
|
299
|
+
return this.get(venue, `/events/slug/${encodeURIComponent(slug)}`, { params: query });
|
|
300
|
+
}
|
|
301
|
+
async getEvents(params, venue) {
|
|
302
|
+
return this.get(venue, "/events", { params: { ...params } });
|
|
303
|
+
}
|
|
304
|
+
async getEventMetrics(params, venue) {
|
|
305
|
+
return this.get(venue, "/events/metrics", { params: { ...params } });
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// src/namespaces/markets.ts
|
|
309
|
+
class MarketsNamespace extends Namespace {
|
|
310
|
+
async getMarkets(params, venue) {
|
|
311
|
+
return this.get(venue, "/market", { params: { ...params } });
|
|
312
|
+
}
|
|
313
|
+
async getMarket(params, venue) {
|
|
314
|
+
return this.get(venue, `/market/${encodeURIComponent(params.conditionId)}`);
|
|
315
|
+
}
|
|
316
|
+
async getMarketBySlug(params, venue) {
|
|
317
|
+
return this.get(venue, `/market/slug/${encodeURIComponent(params.slug)}`);
|
|
318
|
+
}
|
|
319
|
+
async getMarketMetrics(params, venue) {
|
|
320
|
+
return this.get(venue, "/market/metrics", { params: { ...params } });
|
|
321
|
+
}
|
|
322
|
+
async getTrades(params, venue) {
|
|
323
|
+
return this.get(venue, "/market/trades", { params: { ...params } });
|
|
324
|
+
}
|
|
325
|
+
async getCandlestick(params, venue) {
|
|
326
|
+
return this.get(venue, "/market/candlestick", { params: { ...params } });
|
|
327
|
+
}
|
|
328
|
+
async getPositionCandlestick(params, venue) {
|
|
329
|
+
return this.get(venue, "/market/position/candlestick", { params: { ...params } });
|
|
330
|
+
}
|
|
331
|
+
async getPositionMetrics(params, venue) {
|
|
332
|
+
return this.get(venue, "/market/position/metrics", { params: { ...params } });
|
|
333
|
+
}
|
|
334
|
+
async getPositionVolumeChart(params, venue) {
|
|
335
|
+
return this.get(venue, "/market/position/volume-chart", { params: { ...params } });
|
|
336
|
+
}
|
|
337
|
+
async getMarketVolumeChart(params, venue) {
|
|
338
|
+
return this.get(venue, "/market/volume-chart", { params: { ...params } });
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
// src/namespaces/series.ts
|
|
342
|
+
class SeriesNamespace extends Namespace {
|
|
343
|
+
async getSeriesList(params, venue) {
|
|
344
|
+
return this.get(venue, "/series", { params: { ...params } });
|
|
345
|
+
}
|
|
346
|
+
async getSeriesDetail(params, venue) {
|
|
347
|
+
const { identifier, ...query } = params;
|
|
348
|
+
return this.get(venue, `/series/${encodeURIComponent(identifier)}`, { params: query });
|
|
349
|
+
}
|
|
350
|
+
async getSeriesEvents(params, venue) {
|
|
351
|
+
const { identifier, ...query } = params;
|
|
352
|
+
return this.get(venue, `/series/${encodeURIComponent(identifier)}/events`, { params: query });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// src/namespaces/trader.ts
|
|
356
|
+
class TraderNamespace extends Namespace {
|
|
357
|
+
async getPortfolio(params, venue) {
|
|
358
|
+
const { address, ...query } = params;
|
|
359
|
+
return this.get(venue, `/trader/portfolio/${encodeURIComponent(address)}`, { params: query });
|
|
360
|
+
}
|
|
361
|
+
async getPortfolioPositions(params, venue) {
|
|
362
|
+
const { address, ...query } = params;
|
|
363
|
+
return this.get(venue, `/trader/positions/${encodeURIComponent(address)}`, { params: query });
|
|
364
|
+
}
|
|
365
|
+
async getTraderTrades(params, venue) {
|
|
366
|
+
const { address, ...query } = params;
|
|
367
|
+
return this.get(venue, `/trader/trades/${encodeURIComponent(address)}`, { params: query });
|
|
368
|
+
}
|
|
369
|
+
async getTraderProfile(params, venue) {
|
|
370
|
+
return this.get(venue, `/trader/profile/${encodeURIComponent(params.address)}`);
|
|
371
|
+
}
|
|
372
|
+
async getTraderProfilesBatch(params, venue) {
|
|
373
|
+
return this.get(venue, "/trader/profiles/batch", { params: { ...params } });
|
|
374
|
+
}
|
|
375
|
+
async getTraderVolumeChart(params, venue) {
|
|
376
|
+
const { address, ...query } = params;
|
|
377
|
+
return this.get(venue, `/trader/volume-chart/${encodeURIComponent(address)}`, { params: query });
|
|
378
|
+
}
|
|
379
|
+
async getTraderPnl(params, venue) {
|
|
380
|
+
const { address, ...query } = params;
|
|
381
|
+
return this.get(venue, `/trader/pnl/${encodeURIComponent(address)}`, { params: query });
|
|
382
|
+
}
|
|
383
|
+
async getTraderPositionPnl(params, venue) {
|
|
384
|
+
const { address, ...query } = params;
|
|
385
|
+
return this.get(venue, `/trader/pnl/${encodeURIComponent(address)}/positions`, { params: query });
|
|
386
|
+
}
|
|
387
|
+
async getTraderMarketPnl(params, venue) {
|
|
388
|
+
const { address, ...query } = params;
|
|
389
|
+
return this.get(venue, `/trader/pnl/${encodeURIComponent(address)}/markets`, { params: query });
|
|
390
|
+
}
|
|
391
|
+
async getTraderEventPnl(params, venue) {
|
|
392
|
+
const { address, ...query } = params;
|
|
393
|
+
return this.get(venue, `/trader/pnl/${encodeURIComponent(address)}/events`, { params: query });
|
|
394
|
+
}
|
|
395
|
+
async getTraderPnlCandles(params, venue) {
|
|
396
|
+
const { address, ...query } = params;
|
|
397
|
+
return this.get(venue, `/trader/pnl/${encodeURIComponent(address)}/candles`, { params: query });
|
|
398
|
+
}
|
|
399
|
+
async getGlobalPnl(params, venue) {
|
|
400
|
+
return this.get(venue, "/trader/global_pnl", { params: { ...params } });
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
// src/namespaces/bonds.ts
|
|
404
|
+
class BondsNamespace extends Namespace {
|
|
405
|
+
async getBonds(params, venue) {
|
|
406
|
+
return this.get(venue, "/bonds", { params: { ...params } });
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
// src/namespaces/search.ts
|
|
410
|
+
class SearchNamespace extends Namespace {
|
|
411
|
+
async search(params, venue) {
|
|
412
|
+
return this.get(venue, "/search", { params: { ...params } });
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
// src/client.ts
|
|
416
|
+
var DEFAULT_BASE_URL = "https://api.struct.to/v1";
|
|
417
|
+
|
|
418
|
+
class StructClient {
|
|
419
|
+
holders;
|
|
420
|
+
scoring;
|
|
421
|
+
tags;
|
|
422
|
+
events;
|
|
423
|
+
markets;
|
|
424
|
+
series;
|
|
425
|
+
trader;
|
|
426
|
+
bonds;
|
|
427
|
+
search;
|
|
428
|
+
constructor(config) {
|
|
429
|
+
const http = new HttpClient({
|
|
430
|
+
baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
|
|
431
|
+
defaultHeaders: {
|
|
432
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
433
|
+
...config.headers
|
|
434
|
+
},
|
|
435
|
+
timeout: config.timeout,
|
|
436
|
+
retry: config.retry,
|
|
437
|
+
onRequest: config.onRequest,
|
|
438
|
+
onResponse: config.onResponse
|
|
439
|
+
});
|
|
440
|
+
const venue = config.venue ?? "polymarket";
|
|
441
|
+
this.holders = new HoldersNamespace(http, venue);
|
|
442
|
+
this.scoring = new ScoringNamespace(http, venue);
|
|
443
|
+
this.tags = new TagsNamespace(http, venue);
|
|
444
|
+
this.events = new EventsNamespace(http, venue);
|
|
445
|
+
this.markets = new MarketsNamespace(http, venue);
|
|
446
|
+
this.series = new SeriesNamespace(http, venue);
|
|
447
|
+
this.trader = new TraderNamespace(http, venue);
|
|
448
|
+
this.bonds = new BondsNamespace(http, venue);
|
|
449
|
+
this.search = new SearchNamespace(http, venue);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
// src/ws-transport.ts
|
|
453
|
+
var DEFAULT_INITIAL_DELAY_MS2 = 1000;
|
|
454
|
+
var DEFAULT_MAX_DELAY_MS2 = 30000;
|
|
455
|
+
|
|
456
|
+
class WebSocketTransport {
|
|
457
|
+
ws = null;
|
|
458
|
+
_state = "disconnected";
|
|
459
|
+
reconnectTimer = null;
|
|
460
|
+
reconnectAttempt = 0;
|
|
461
|
+
intentionalClose = false;
|
|
462
|
+
pendingMessages = [];
|
|
463
|
+
replayMessages = [];
|
|
464
|
+
url;
|
|
465
|
+
retry;
|
|
466
|
+
callbacks;
|
|
467
|
+
constructor(url, retry, callbacks) {
|
|
468
|
+
this.url = url;
|
|
469
|
+
this.retry = retry;
|
|
470
|
+
this.callbacks = callbacks;
|
|
471
|
+
}
|
|
472
|
+
get state() {
|
|
473
|
+
return this._state;
|
|
474
|
+
}
|
|
475
|
+
connect() {
|
|
476
|
+
if (this._state === "connected" || this._state === "connecting" || this._state === "reconnecting") {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
this.intentionalClose = false;
|
|
483
|
+
this.clearReconnectTimer();
|
|
484
|
+
this.setState("connecting");
|
|
485
|
+
this.createSocket();
|
|
486
|
+
}
|
|
487
|
+
disconnect() {
|
|
488
|
+
this.intentionalClose = true;
|
|
489
|
+
this.clearReconnectTimer();
|
|
490
|
+
this.pendingMessages.length = 0;
|
|
491
|
+
this.replayMessages.length = 0;
|
|
492
|
+
if (this.ws) {
|
|
493
|
+
this.ws.close(1000, "client disconnect");
|
|
494
|
+
this.ws = null;
|
|
495
|
+
}
|
|
496
|
+
this.setState("disconnected");
|
|
497
|
+
}
|
|
498
|
+
send(message) {
|
|
499
|
+
if (this._state === "connected" && this.ws?.readyState === WebSocket.OPEN) {
|
|
500
|
+
this.ws.send(JSON.stringify(message));
|
|
501
|
+
} else {
|
|
502
|
+
this.pendingMessages.push(message);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
addReplayMessage(message) {
|
|
506
|
+
this.replayMessages.push(message);
|
|
507
|
+
}
|
|
508
|
+
removeReplayMessages(predicate) {
|
|
509
|
+
for (let i = this.replayMessages.length - 1;i >= 0; i--) {
|
|
510
|
+
if (predicate(this.replayMessages[i])) {
|
|
511
|
+
this.replayMessages.splice(i, 1);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
clearReplayMessages() {
|
|
516
|
+
this.replayMessages.length = 0;
|
|
517
|
+
}
|
|
518
|
+
createSocket() {
|
|
519
|
+
try {
|
|
520
|
+
this.ws = new WebSocket(this.url);
|
|
521
|
+
} catch (err) {
|
|
522
|
+
this.callbacks.onError(new WebSocketError("Failed to create WebSocket", { cause: err }));
|
|
523
|
+
this.scheduleReconnect();
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
this.ws.onopen = () => {
|
|
527
|
+
this._state = "connected";
|
|
528
|
+
this.reconnectAttempt = 0;
|
|
529
|
+
this.replaySubscriptions();
|
|
530
|
+
this.flushPendingMessages();
|
|
531
|
+
this.callbacks.onOpen();
|
|
532
|
+
};
|
|
533
|
+
this.ws.onclose = (event) => {
|
|
534
|
+
this.ws = null;
|
|
535
|
+
if (this.intentionalClose) {
|
|
536
|
+
this.setState("disconnected");
|
|
537
|
+
this.callbacks.onClose(event.code, event.reason);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
this.callbacks.onClose(event.code, event.reason);
|
|
541
|
+
this.scheduleReconnect();
|
|
542
|
+
};
|
|
543
|
+
this.ws.onerror = () => {
|
|
544
|
+
this.callbacks.onError(new WebSocketError("WebSocket connection error"));
|
|
545
|
+
};
|
|
546
|
+
this.ws.onmessage = (event) => {
|
|
547
|
+
try {
|
|
548
|
+
const data = JSON.parse(String(event.data));
|
|
549
|
+
this.callbacks.onMessage(data);
|
|
550
|
+
} catch {
|
|
551
|
+
this.callbacks.onError(new WebSocketError("Failed to parse WebSocket message"));
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
replaySubscriptions() {
|
|
556
|
+
for (const msg of this.replayMessages) {
|
|
557
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
558
|
+
this.ws.send(JSON.stringify(msg));
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
flushPendingMessages() {
|
|
563
|
+
while (this.pendingMessages.length > 0) {
|
|
564
|
+
const msg = this.pendingMessages.shift();
|
|
565
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
566
|
+
this.ws.send(JSON.stringify(msg));
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
scheduleReconnect() {
|
|
571
|
+
const maxRetries = this.retry.maxRetries ?? Infinity;
|
|
572
|
+
if (this.reconnectAttempt >= maxRetries) {
|
|
573
|
+
this.setState("disconnected");
|
|
574
|
+
this.callbacks.onError(new WebSocketClosedError(1006, "Max reconnection attempts reached"));
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
this.setState("reconnecting");
|
|
578
|
+
this.reconnectAttempt++;
|
|
579
|
+
this.callbacks.onReconnecting(this.reconnectAttempt);
|
|
580
|
+
const initialDelay = this.retry.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS2;
|
|
581
|
+
const maxDelay = this.retry.maxDelayMs ?? DEFAULT_MAX_DELAY_MS2;
|
|
582
|
+
const exponentialDelay = Math.min(initialDelay * 2 ** (this.reconnectAttempt - 1), maxDelay);
|
|
583
|
+
const jitter = exponentialDelay * 0.2 * Math.random();
|
|
584
|
+
const delay = exponentialDelay + jitter;
|
|
585
|
+
this.reconnectTimer = setTimeout(() => {
|
|
586
|
+
this.reconnectTimer = null;
|
|
587
|
+
this.createSocket();
|
|
588
|
+
}, delay);
|
|
589
|
+
}
|
|
590
|
+
clearReconnectTimer() {
|
|
591
|
+
if (this.reconnectTimer !== null) {
|
|
592
|
+
clearTimeout(this.reconnectTimer);
|
|
593
|
+
this.reconnectTimer = null;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
setState(state) {
|
|
597
|
+
this._state = state;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// src/ws.ts
|
|
602
|
+
var DEFAULT_BASE_URL2 = "https://api.struct.to/v1";
|
|
603
|
+
|
|
604
|
+
class StructWebSocket {
|
|
605
|
+
transport;
|
|
606
|
+
listeners = new Map;
|
|
607
|
+
activeMarkets = new Set;
|
|
608
|
+
activeMarketPositions = new Set;
|
|
609
|
+
activeWallets = new Set;
|
|
610
|
+
activeConditions = new Set;
|
|
611
|
+
activeSpecialRooms = new Set;
|
|
612
|
+
constructor(config) {
|
|
613
|
+
const httpBase = (config.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
|
|
614
|
+
const wsBase = httpBase.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://");
|
|
615
|
+
const url = `${wsBase}?token=${encodeURIComponent(config.apiKey)}`;
|
|
616
|
+
this.transport = new WebSocketTransport(url, config.reconnect ?? {}, {
|
|
617
|
+
onOpen: () => this.handleOpen(),
|
|
618
|
+
onClose: (code, reason) => this.handleClose(code, reason),
|
|
619
|
+
onError: (error) => this.emit("error", error),
|
|
620
|
+
onMessage: (data) => this.handleMessage(data),
|
|
621
|
+
onReconnecting: (attempt) => this.emit("reconnecting", { attempt })
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
get state() {
|
|
625
|
+
return this.transport.state;
|
|
626
|
+
}
|
|
627
|
+
connect() {
|
|
628
|
+
this.transport.connect();
|
|
629
|
+
}
|
|
630
|
+
disconnect() {
|
|
631
|
+
this.activeMarkets.clear();
|
|
632
|
+
this.activeMarketPositions.clear();
|
|
633
|
+
this.activeWallets.clear();
|
|
634
|
+
this.activeConditions.clear();
|
|
635
|
+
this.activeSpecialRooms.clear();
|
|
636
|
+
this.transport.disconnect();
|
|
637
|
+
}
|
|
638
|
+
on(event, listener) {
|
|
639
|
+
let set = this.listeners.get(event);
|
|
640
|
+
if (!set) {
|
|
641
|
+
set = new Set;
|
|
642
|
+
this.listeners.set(event, set);
|
|
643
|
+
}
|
|
644
|
+
set.add(listener);
|
|
645
|
+
return this;
|
|
646
|
+
}
|
|
647
|
+
off(event, listener) {
|
|
648
|
+
this.listeners.get(event)?.delete(listener);
|
|
649
|
+
return this;
|
|
650
|
+
}
|
|
651
|
+
once(event, listener) {
|
|
652
|
+
const wrapper = (payload) => {
|
|
653
|
+
this.off(event, wrapper);
|
|
654
|
+
listener(payload);
|
|
655
|
+
};
|
|
656
|
+
return this.on(event, wrapper);
|
|
657
|
+
}
|
|
658
|
+
subscribeMarket(conditionId) {
|
|
659
|
+
if (this.activeMarkets.has(conditionId))
|
|
660
|
+
return;
|
|
661
|
+
this.activeMarkets.add(conditionId);
|
|
662
|
+
const msg = this.marketSubscribeMessage(conditionId);
|
|
663
|
+
this.transport.addReplayMessage(msg);
|
|
664
|
+
this.transport.send(msg);
|
|
665
|
+
}
|
|
666
|
+
unsubscribeMarket(conditionId) {
|
|
667
|
+
if (!this.activeMarkets.has(conditionId))
|
|
668
|
+
return;
|
|
669
|
+
this.activeMarkets.delete(conditionId);
|
|
670
|
+
const msg = {
|
|
671
|
+
type: "unsubscribe_market",
|
|
672
|
+
room_id: `market_${conditionId}`,
|
|
673
|
+
message: {}
|
|
674
|
+
};
|
|
675
|
+
this.transport.removeReplayMessages((m) => m.type === "subscribe_market" && m.room_id === `market_${conditionId}`);
|
|
676
|
+
this.transport.send(msg);
|
|
677
|
+
}
|
|
678
|
+
subscribeMarketByPosition(positionId) {
|
|
679
|
+
if (this.activeMarketPositions.has(positionId))
|
|
680
|
+
return;
|
|
681
|
+
this.activeMarketPositions.add(positionId);
|
|
682
|
+
const msg = this.marketPositionSubscribeMessage(positionId);
|
|
683
|
+
this.transport.addReplayMessage(msg);
|
|
684
|
+
this.transport.send(msg);
|
|
685
|
+
}
|
|
686
|
+
unsubscribeMarketByPosition(positionId) {
|
|
687
|
+
if (!this.activeMarketPositions.has(positionId))
|
|
688
|
+
return;
|
|
689
|
+
this.activeMarketPositions.delete(positionId);
|
|
690
|
+
const msg = {
|
|
691
|
+
type: "unsubscribe_market",
|
|
692
|
+
room_id: `market_position_${positionId}`,
|
|
693
|
+
message: {}
|
|
694
|
+
};
|
|
695
|
+
this.transport.removeReplayMessages((m) => m.type === "subscribe_market" && m.room_id === `market_position_${positionId}`);
|
|
696
|
+
this.transport.send(msg);
|
|
697
|
+
}
|
|
698
|
+
trackWallets(addresses) {
|
|
699
|
+
const uniqueAddresses = [...new Set(addresses)];
|
|
700
|
+
const added = uniqueAddresses.filter((addr) => !this.activeWallets.has(addr));
|
|
701
|
+
if (added.length === 0)
|
|
702
|
+
return;
|
|
703
|
+
for (const addr of added)
|
|
704
|
+
this.activeWallets.add(addr);
|
|
705
|
+
const msg = this.walletTrackMessage("subscribe", added);
|
|
706
|
+
this.rebuildWalletReplay();
|
|
707
|
+
this.transport.send(msg);
|
|
708
|
+
}
|
|
709
|
+
untrackWallets(addresses) {
|
|
710
|
+
const uniqueAddresses = [...new Set(addresses)];
|
|
711
|
+
const removed = uniqueAddresses.filter((addr) => this.activeWallets.has(addr));
|
|
712
|
+
if (removed.length === 0)
|
|
713
|
+
return;
|
|
714
|
+
for (const addr of removed)
|
|
715
|
+
this.activeWallets.delete(addr);
|
|
716
|
+
const msg = this.walletTrackMessage("unsubscribe", removed);
|
|
717
|
+
this.rebuildWalletReplay();
|
|
718
|
+
this.transport.send(msg);
|
|
719
|
+
}
|
|
720
|
+
subscribeWhaleTrades() {
|
|
721
|
+
this.subscribeSpecialRoom("whale_trades");
|
|
722
|
+
}
|
|
723
|
+
unsubscribeWhaleTrades() {
|
|
724
|
+
this.unsubscribeSpecialRoom("whale_trades");
|
|
725
|
+
}
|
|
726
|
+
subscribeSmartMoneyTrades() {
|
|
727
|
+
this.subscribeSpecialRoom("smart_money_trades");
|
|
728
|
+
}
|
|
729
|
+
unsubscribeSmartMoneyTrades() {
|
|
730
|
+
this.unsubscribeSpecialRoom("smart_money_trades");
|
|
731
|
+
}
|
|
732
|
+
subscribeInsiderTrades() {
|
|
733
|
+
this.subscribeSpecialRoom("insider_trades");
|
|
734
|
+
}
|
|
735
|
+
unsubscribeInsiderTrades() {
|
|
736
|
+
this.unsubscribeSpecialRoom("insider_trades");
|
|
737
|
+
}
|
|
738
|
+
trackConditions(conditionIds) {
|
|
739
|
+
const uniqueConditionIds = [...new Set(conditionIds)];
|
|
740
|
+
const added = uniqueConditionIds.filter((id) => !this.activeConditions.has(id));
|
|
741
|
+
if (added.length === 0)
|
|
742
|
+
return;
|
|
743
|
+
for (const id of added)
|
|
744
|
+
this.activeConditions.add(id);
|
|
745
|
+
const msg = this.conditionsTrackMessage("subscribe", added);
|
|
746
|
+
this.rebuildConditionsReplay();
|
|
747
|
+
this.transport.send(msg);
|
|
748
|
+
}
|
|
749
|
+
untrackConditions(conditionIds) {
|
|
750
|
+
const uniqueConditionIds = [...new Set(conditionIds)];
|
|
751
|
+
const removed = uniqueConditionIds.filter((id) => this.activeConditions.has(id));
|
|
752
|
+
if (removed.length === 0)
|
|
753
|
+
return;
|
|
754
|
+
for (const id of removed)
|
|
755
|
+
this.activeConditions.delete(id);
|
|
756
|
+
const msg = this.conditionsTrackMessage("unsubscribe", removed);
|
|
757
|
+
this.rebuildConditionsReplay();
|
|
758
|
+
this.transport.send(msg);
|
|
759
|
+
}
|
|
760
|
+
handleOpen() {
|
|
761
|
+
this.emit("connected", undefined);
|
|
762
|
+
}
|
|
763
|
+
handleClose(code, reason) {
|
|
764
|
+
this.emit("disconnected", { code, reason });
|
|
765
|
+
}
|
|
766
|
+
handleMessage(raw) {
|
|
767
|
+
const msg = raw;
|
|
768
|
+
if (!msg || typeof msg !== "object")
|
|
769
|
+
return;
|
|
770
|
+
const roomId = msg.room_id ?? "";
|
|
771
|
+
const type = msg.type;
|
|
772
|
+
if (type === "market_trade" || roomId.startsWith("market_")) {
|
|
773
|
+
this.emit("market_trade", msg.data);
|
|
774
|
+
} else if (type === "whale_trade" || roomId === "whale_trades") {
|
|
775
|
+
this.emit("whale_trade", msg.data);
|
|
776
|
+
} else if (type === "smart_money_trade" || roomId === "smart_money_trades") {
|
|
777
|
+
this.emit("smart_money_trade", msg.data);
|
|
778
|
+
} else if (type === "insider_trade" || roomId === "insider_trades") {
|
|
779
|
+
this.emit("insider_trade", msg.data);
|
|
780
|
+
} else if (type === "wallet_tracking_alert") {
|
|
781
|
+
this.emit("wallet_tracking_alert", msg.data);
|
|
782
|
+
} else if (type === "conditions_tracking_alert") {
|
|
783
|
+
this.emit("conditions_tracking_alert", msg.data);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
emit(event, payload) {
|
|
787
|
+
const set = this.listeners.get(event);
|
|
788
|
+
if (!set)
|
|
789
|
+
return;
|
|
790
|
+
for (const fn of set) {
|
|
791
|
+
try {
|
|
792
|
+
fn(payload);
|
|
793
|
+
} catch {}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
marketSubscribeMessage(conditionId) {
|
|
797
|
+
return { type: "subscribe_market", room_id: `market_${conditionId}`, message: {} };
|
|
798
|
+
}
|
|
799
|
+
marketPositionSubscribeMessage(positionId) {
|
|
800
|
+
return { type: "subscribe_market", room_id: `market_position_${positionId}`, message: {} };
|
|
801
|
+
}
|
|
802
|
+
walletTrackMessage(action, addresses) {
|
|
803
|
+
return { type: "wallet_tracking", action, wallet_addresses: addresses };
|
|
804
|
+
}
|
|
805
|
+
conditionsTrackMessage(action, conditionIds) {
|
|
806
|
+
return { type: "conditions_tracking", action, condition_ids: conditionIds };
|
|
807
|
+
}
|
|
808
|
+
subscribeSpecialRoom(roomId) {
|
|
809
|
+
if (this.activeSpecialRooms.has(roomId))
|
|
810
|
+
return;
|
|
811
|
+
this.activeSpecialRooms.add(roomId);
|
|
812
|
+
const msg = { type: "subscribe_market", room_id: roomId, message: {} };
|
|
813
|
+
this.transport.addReplayMessage(msg);
|
|
814
|
+
this.transport.send(msg);
|
|
815
|
+
}
|
|
816
|
+
unsubscribeSpecialRoom(roomId) {
|
|
817
|
+
if (!this.activeSpecialRooms.has(roomId))
|
|
818
|
+
return;
|
|
819
|
+
this.activeSpecialRooms.delete(roomId);
|
|
820
|
+
const msg = { type: "unsubscribe_market", room_id: roomId, message: {} };
|
|
821
|
+
this.transport.removeReplayMessages((m) => m.type === "subscribe_market" && m.room_id === roomId);
|
|
822
|
+
this.transport.send(msg);
|
|
823
|
+
}
|
|
824
|
+
rebuildWalletReplay() {
|
|
825
|
+
this.transport.removeReplayMessages((m) => m.type === "wallet_tracking");
|
|
826
|
+
if (this.activeWallets.size > 0) {
|
|
827
|
+
this.transport.addReplayMessage(this.walletTrackMessage("subscribe", [...this.activeWallets]));
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
rebuildConditionsReplay() {
|
|
831
|
+
this.transport.removeReplayMessages((m) => m.type === "conditions_tracking");
|
|
832
|
+
if (this.activeConditions.size > 0) {
|
|
833
|
+
this.transport.addReplayMessage(this.conditionsTrackMessage("subscribe", [...this.activeConditions]));
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
// src/paginate.ts
|
|
838
|
+
var DEFAULT_PAGE_SIZE = 100;
|
|
839
|
+
async function* paginate(fetcher, params, pageSize = DEFAULT_PAGE_SIZE) {
|
|
840
|
+
let offset = 0;
|
|
841
|
+
while (true) {
|
|
842
|
+
const response = await fetcher({ ...params, limit: pageSize, offset });
|
|
843
|
+
for (const item of response.data) {
|
|
844
|
+
yield item;
|
|
845
|
+
}
|
|
846
|
+
if (response.data.length < pageSize) {
|
|
847
|
+
break;
|
|
848
|
+
}
|
|
849
|
+
offset += pageSize;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
export {
|
|
853
|
+
paginate,
|
|
854
|
+
WebSocketError,
|
|
855
|
+
WebSocketClosedError,
|
|
856
|
+
TimeoutError,
|
|
857
|
+
StructWebSocket,
|
|
858
|
+
StructError,
|
|
859
|
+
StructClient,
|
|
860
|
+
NetworkError,
|
|
861
|
+
HttpError,
|
|
862
|
+
HttpClient
|
|
863
|
+
};
|
|
864
|
+
|
|
865
|
+
//# debugId=F77A56DA0E15D59664756E2164756E21
|
|
866
|
+
//# sourceMappingURL=index.js.map
|