@0xarchive/sdk 0.1.0

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/dist/index.mjs ADDED
@@ -0,0 +1,734 @@
1
+ // src/types.ts
2
+ var OxArchiveError = class extends Error {
3
+ code;
4
+ requestId;
5
+ constructor(message, code, requestId) {
6
+ super(message);
7
+ this.name = "OxArchiveError";
8
+ this.code = code;
9
+ this.requestId = requestId;
10
+ }
11
+ };
12
+
13
+ // src/http.ts
14
+ var HttpClient = class {
15
+ baseUrl;
16
+ apiKey;
17
+ timeout;
18
+ constructor(options) {
19
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
20
+ this.apiKey = options.apiKey;
21
+ this.timeout = options.timeout;
22
+ }
23
+ /**
24
+ * Make a GET request to the API
25
+ */
26
+ async get(path, params) {
27
+ const url = new URL(`${this.baseUrl}${path}`);
28
+ if (params) {
29
+ for (const [key, value] of Object.entries(params)) {
30
+ if (value !== void 0 && value !== null) {
31
+ url.searchParams.set(key, String(value));
32
+ }
33
+ }
34
+ }
35
+ const controller = new AbortController();
36
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
37
+ try {
38
+ const response = await fetch(url.toString(), {
39
+ method: "GET",
40
+ headers: {
41
+ "X-API-Key": this.apiKey,
42
+ "Content-Type": "application/json"
43
+ },
44
+ signal: controller.signal
45
+ });
46
+ clearTimeout(timeoutId);
47
+ const data = await response.json();
48
+ if (!response.ok) {
49
+ const error = data;
50
+ throw new OxArchiveError(
51
+ error.error || `Request failed with status ${response.status}`,
52
+ response.status,
53
+ data.request_id
54
+ );
55
+ }
56
+ return data;
57
+ } catch (error) {
58
+ clearTimeout(timeoutId);
59
+ if (error instanceof OxArchiveError) {
60
+ throw error;
61
+ }
62
+ if (error instanceof Error && error.name === "AbortError") {
63
+ throw new OxArchiveError(`Request timeout after ${this.timeout}ms`, 408);
64
+ }
65
+ throw new OxArchiveError(
66
+ error instanceof Error ? error.message : "Unknown error",
67
+ 500
68
+ );
69
+ }
70
+ }
71
+ };
72
+
73
+ // src/resources/orderbook.ts
74
+ var OrderBookResource = class {
75
+ constructor(http) {
76
+ this.http = http;
77
+ }
78
+ /**
79
+ * Get order book snapshot for a coin
80
+ *
81
+ * @param coin - The coin symbol (e.g., 'BTC', 'ETH')
82
+ * @param params - Optional parameters
83
+ * @returns Order book snapshot
84
+ */
85
+ async get(coin, params) {
86
+ const response = await this.http.get(
87
+ `/v1/orderbook/${coin.toUpperCase()}`,
88
+ params
89
+ );
90
+ return response.data;
91
+ }
92
+ /**
93
+ * Get historical order book snapshots
94
+ *
95
+ * @param coin - The coin symbol (e.g., 'BTC', 'ETH')
96
+ * @param params - Time range and pagination parameters
97
+ * @returns Array of order book snapshots
98
+ */
99
+ async history(coin, params) {
100
+ const response = await this.http.get(
101
+ `/v1/orderbook/${coin.toUpperCase()}/history`,
102
+ params
103
+ );
104
+ return response.data;
105
+ }
106
+ };
107
+
108
+ // src/resources/trades.ts
109
+ var TradesResource = class {
110
+ constructor(http) {
111
+ this.http = http;
112
+ }
113
+ /**
114
+ * Get trade history for a coin
115
+ *
116
+ * @param coin - The coin symbol (e.g., 'BTC', 'ETH')
117
+ * @param params - Time range and pagination parameters
118
+ * @returns Array of trades
119
+ */
120
+ async list(coin, params) {
121
+ const response = await this.http.get(
122
+ `/v1/trades/${coin.toUpperCase()}`,
123
+ params
124
+ );
125
+ return response.data;
126
+ }
127
+ /**
128
+ * Get most recent trades for a coin
129
+ *
130
+ * @param coin - The coin symbol (e.g., 'BTC', 'ETH')
131
+ * @param limit - Number of trades to return (default: 100)
132
+ * @returns Array of recent trades
133
+ */
134
+ async recent(coin, limit) {
135
+ const response = await this.http.get(
136
+ `/v1/trades/${coin.toUpperCase()}/recent`,
137
+ { limit }
138
+ );
139
+ return response.data;
140
+ }
141
+ };
142
+
143
+ // src/resources/candles.ts
144
+ var CandlesResource = class {
145
+ constructor(http) {
146
+ this.http = http;
147
+ }
148
+ /**
149
+ * Get OHLCV candles for a coin
150
+ *
151
+ * @param coin - The coin symbol (e.g., 'BTC', 'ETH')
152
+ * @param params - Interval, time range, and pagination parameters
153
+ * @returns Array of candles
154
+ */
155
+ async list(coin, params) {
156
+ const response = await this.http.get(
157
+ `/v1/candles/${coin.toUpperCase()}`,
158
+ params
159
+ );
160
+ return response.data;
161
+ }
162
+ };
163
+
164
+ // src/resources/instruments.ts
165
+ var InstrumentsResource = class {
166
+ constructor(http) {
167
+ this.http = http;
168
+ }
169
+ /**
170
+ * List all available trading instruments
171
+ *
172
+ * @returns Array of instruments
173
+ */
174
+ async list() {
175
+ const response = await this.http.get(
176
+ "/v1/instruments"
177
+ );
178
+ return response.data;
179
+ }
180
+ /**
181
+ * Get a specific instrument by coin symbol
182
+ *
183
+ * @param coin - The coin symbol (e.g., 'BTC', 'ETH')
184
+ * @returns Instrument details
185
+ */
186
+ async get(coin) {
187
+ const response = await this.http.get(
188
+ `/v1/instruments/${coin.toUpperCase()}`
189
+ );
190
+ return response.data;
191
+ }
192
+ };
193
+
194
+ // src/resources/funding.ts
195
+ var FundingResource = class {
196
+ constructor(http) {
197
+ this.http = http;
198
+ }
199
+ /**
200
+ * Get funding rate history for a coin
201
+ *
202
+ * @param coin - The coin symbol (e.g., 'BTC', 'ETH')
203
+ * @param params - Time range and pagination parameters
204
+ * @returns Array of funding rate records
205
+ */
206
+ async history(coin, params) {
207
+ const response = await this.http.get(
208
+ `/v1/funding/${coin.toUpperCase()}`,
209
+ params
210
+ );
211
+ return response.data;
212
+ }
213
+ /**
214
+ * Get current funding rate for a coin
215
+ *
216
+ * @param coin - The coin symbol (e.g., 'BTC', 'ETH')
217
+ * @returns Current funding rate
218
+ */
219
+ async current(coin) {
220
+ const response = await this.http.get(
221
+ `/v1/funding/${coin.toUpperCase()}/current`
222
+ );
223
+ return response.data;
224
+ }
225
+ };
226
+
227
+ // src/resources/openinterest.ts
228
+ var OpenInterestResource = class {
229
+ constructor(http) {
230
+ this.http = http;
231
+ }
232
+ /**
233
+ * Get open interest history for a coin
234
+ *
235
+ * @param coin - The coin symbol (e.g., 'BTC', 'ETH')
236
+ * @param params - Time range and pagination parameters
237
+ * @returns Array of open interest records
238
+ */
239
+ async history(coin, params) {
240
+ const response = await this.http.get(
241
+ `/v1/openinterest/${coin.toUpperCase()}`,
242
+ params
243
+ );
244
+ return response.data;
245
+ }
246
+ /**
247
+ * Get current open interest for a coin
248
+ *
249
+ * @param coin - The coin symbol (e.g., 'BTC', 'ETH')
250
+ * @returns Current open interest
251
+ */
252
+ async current(coin) {
253
+ const response = await this.http.get(
254
+ `/v1/openinterest/${coin.toUpperCase()}/current`
255
+ );
256
+ return response.data;
257
+ }
258
+ };
259
+
260
+ // src/client.ts
261
+ var DEFAULT_BASE_URL = "https://api.0xarchive.io";
262
+ var DEFAULT_TIMEOUT = 3e4;
263
+ var OxArchive = class {
264
+ http;
265
+ /**
266
+ * Order book data (L2 snapshots from April 2023)
267
+ */
268
+ orderbook;
269
+ /**
270
+ * Trade/fill history
271
+ */
272
+ trades;
273
+ /**
274
+ * OHLCV candles
275
+ */
276
+ candles;
277
+ /**
278
+ * Trading instruments metadata
279
+ */
280
+ instruments;
281
+ /**
282
+ * Funding rates
283
+ */
284
+ funding;
285
+ /**
286
+ * Open interest
287
+ */
288
+ openInterest;
289
+ /**
290
+ * Create a new 0xarchive client
291
+ *
292
+ * @param options - Client configuration options
293
+ */
294
+ constructor(options) {
295
+ if (!options.apiKey) {
296
+ throw new Error("API key is required. Get one at https://0xarchive.io/signup");
297
+ }
298
+ this.http = new HttpClient({
299
+ baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
300
+ apiKey: options.apiKey,
301
+ timeout: options.timeout ?? DEFAULT_TIMEOUT
302
+ });
303
+ this.orderbook = new OrderBookResource(this.http);
304
+ this.trades = new TradesResource(this.http);
305
+ this.candles = new CandlesResource(this.http);
306
+ this.instruments = new InstrumentsResource(this.http);
307
+ this.funding = new FundingResource(this.http);
308
+ this.openInterest = new OpenInterestResource(this.http);
309
+ }
310
+ };
311
+
312
+ // src/websocket.ts
313
+ var DEFAULT_WS_URL = "wss://ws.0xarchive.io";
314
+ var DEFAULT_PING_INTERVAL = 3e4;
315
+ var DEFAULT_RECONNECT_DELAY = 1e3;
316
+ var DEFAULT_MAX_RECONNECT_ATTEMPTS = 10;
317
+ var OxArchiveWs = class {
318
+ ws = null;
319
+ options;
320
+ handlers = {};
321
+ subscriptions = /* @__PURE__ */ new Set();
322
+ state = "disconnected";
323
+ reconnectAttempts = 0;
324
+ pingTimer = null;
325
+ reconnectTimer = null;
326
+ constructor(options) {
327
+ this.options = {
328
+ apiKey: options.apiKey,
329
+ wsUrl: options.wsUrl ?? DEFAULT_WS_URL,
330
+ autoReconnect: options.autoReconnect ?? true,
331
+ reconnectDelay: options.reconnectDelay ?? DEFAULT_RECONNECT_DELAY,
332
+ maxReconnectAttempts: options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS,
333
+ pingInterval: options.pingInterval ?? DEFAULT_PING_INTERVAL
334
+ };
335
+ }
336
+ /**
337
+ * Connect to the WebSocket server
338
+ */
339
+ connect(handlers) {
340
+ if (handlers) {
341
+ this.handlers = handlers;
342
+ }
343
+ this.setState("connecting");
344
+ const url = `${this.options.wsUrl}?apiKey=${encodeURIComponent(this.options.apiKey)}`;
345
+ this.ws = new WebSocket(url);
346
+ this.ws.onopen = () => {
347
+ this.reconnectAttempts = 0;
348
+ this.setState("connected");
349
+ this.startPing();
350
+ this.resubscribe();
351
+ this.handlers.onOpen?.();
352
+ };
353
+ this.ws.onclose = (event) => {
354
+ this.stopPing();
355
+ this.handlers.onClose?.(event.code, event.reason);
356
+ if (this.options.autoReconnect && this.state !== "disconnected") {
357
+ this.scheduleReconnect();
358
+ } else {
359
+ this.setState("disconnected");
360
+ }
361
+ };
362
+ this.ws.onerror = () => {
363
+ const error = new Error("WebSocket connection error");
364
+ this.handlers.onError?.(error);
365
+ };
366
+ this.ws.onmessage = (event) => {
367
+ try {
368
+ const message = JSON.parse(event.data);
369
+ this.handlers.onMessage?.(message);
370
+ } catch {
371
+ }
372
+ };
373
+ }
374
+ /**
375
+ * Disconnect from the WebSocket server
376
+ */
377
+ disconnect() {
378
+ this.setState("disconnected");
379
+ this.stopPing();
380
+ this.clearReconnectTimer();
381
+ if (this.ws) {
382
+ this.ws.close(1e3, "Client disconnect");
383
+ this.ws = null;
384
+ }
385
+ }
386
+ /**
387
+ * Subscribe to a channel
388
+ */
389
+ subscribe(channel, coin) {
390
+ const key = this.subscriptionKey(channel, coin);
391
+ this.subscriptions.add(key);
392
+ if (this.isConnected()) {
393
+ this.send({ op: "subscribe", channel, coin });
394
+ }
395
+ }
396
+ /**
397
+ * Subscribe to order book updates for a coin
398
+ */
399
+ subscribeOrderbook(coin) {
400
+ this.subscribe("orderbook", coin);
401
+ }
402
+ /**
403
+ * Subscribe to trades for a coin
404
+ */
405
+ subscribeTrades(coin) {
406
+ this.subscribe("trades", coin);
407
+ }
408
+ /**
409
+ * Subscribe to ticker updates for a coin
410
+ */
411
+ subscribeTicker(coin) {
412
+ this.subscribe("ticker", coin);
413
+ }
414
+ /**
415
+ * Subscribe to all tickers
416
+ */
417
+ subscribeAllTickers() {
418
+ this.subscribe("all_tickers");
419
+ }
420
+ /**
421
+ * Unsubscribe from a channel
422
+ */
423
+ unsubscribe(channel, coin) {
424
+ const key = this.subscriptionKey(channel, coin);
425
+ this.subscriptions.delete(key);
426
+ if (this.isConnected()) {
427
+ this.send({ op: "unsubscribe", channel, coin });
428
+ }
429
+ }
430
+ /**
431
+ * Unsubscribe from order book updates for a coin
432
+ */
433
+ unsubscribeOrderbook(coin) {
434
+ this.unsubscribe("orderbook", coin);
435
+ }
436
+ /**
437
+ * Unsubscribe from trades for a coin
438
+ */
439
+ unsubscribeTrades(coin) {
440
+ this.unsubscribe("trades", coin);
441
+ }
442
+ /**
443
+ * Unsubscribe from ticker updates for a coin
444
+ */
445
+ unsubscribeTicker(coin) {
446
+ this.unsubscribe("ticker", coin);
447
+ }
448
+ /**
449
+ * Unsubscribe from all tickers
450
+ */
451
+ unsubscribeAllTickers() {
452
+ this.unsubscribe("all_tickers");
453
+ }
454
+ // ==========================================================================
455
+ // Historical Replay (Option B) - Like Tardis.dev
456
+ // ==========================================================================
457
+ /**
458
+ * Start historical replay with timing preserved
459
+ *
460
+ * @param channel - Data channel to replay
461
+ * @param coin - Trading pair (e.g., 'BTC', 'ETH')
462
+ * @param options - Replay options
463
+ *
464
+ * @example
465
+ * ```typescript
466
+ * ws.replay('orderbook', 'BTC', {
467
+ * start: Date.now() - 86400000, // 24 hours ago
468
+ * speed: 10 // 10x faster than real-time
469
+ * });
470
+ * ```
471
+ */
472
+ replay(channel, coin, options) {
473
+ this.send({
474
+ op: "replay",
475
+ channel,
476
+ coin,
477
+ start: options.start,
478
+ end: options.end,
479
+ speed: options.speed ?? 1
480
+ });
481
+ }
482
+ /**
483
+ * Pause the current replay
484
+ */
485
+ replayPause() {
486
+ this.send({ op: "replay.pause" });
487
+ }
488
+ /**
489
+ * Resume a paused replay
490
+ */
491
+ replayResume() {
492
+ this.send({ op: "replay.resume" });
493
+ }
494
+ /**
495
+ * Seek to a specific timestamp in the replay
496
+ * @param timestamp - Unix timestamp in milliseconds
497
+ */
498
+ replaySeek(timestamp) {
499
+ this.send({ op: "replay.seek", timestamp });
500
+ }
501
+ /**
502
+ * Stop the current replay
503
+ */
504
+ replayStop() {
505
+ this.send({ op: "replay.stop" });
506
+ }
507
+ // ==========================================================================
508
+ // Bulk Streaming (Option D) - Like Databento
509
+ // ==========================================================================
510
+ /**
511
+ * Start bulk streaming for fast data download
512
+ *
513
+ * @param channel - Data channel to stream
514
+ * @param coin - Trading pair (e.g., 'BTC', 'ETH')
515
+ * @param options - Stream options
516
+ *
517
+ * @example
518
+ * ```typescript
519
+ * ws.stream('orderbook', 'ETH', {
520
+ * start: Date.now() - 3600000, // 1 hour ago
521
+ * end: Date.now(),
522
+ * batchSize: 1000
523
+ * });
524
+ * ```
525
+ */
526
+ stream(channel, coin, options) {
527
+ this.send({
528
+ op: "stream",
529
+ channel,
530
+ coin,
531
+ start: options.start,
532
+ end: options.end,
533
+ batch_size: options.batchSize ?? 1e3
534
+ });
535
+ }
536
+ /**
537
+ * Stop the current bulk stream
538
+ */
539
+ streamStop() {
540
+ this.send({ op: "stream.stop" });
541
+ }
542
+ // ==========================================================================
543
+ // Event Handlers for Replay/Stream
544
+ // ==========================================================================
545
+ /**
546
+ * Handle historical data points (replay mode)
547
+ */
548
+ onHistoricalData(handler) {
549
+ const originalHandler = this.handlers.onMessage;
550
+ this.handlers.onMessage = (message) => {
551
+ if (message.type === "historical_data") {
552
+ const msg = message;
553
+ handler(msg.coin, msg.timestamp, msg.data);
554
+ }
555
+ originalHandler?.(message);
556
+ };
557
+ }
558
+ /**
559
+ * Handle batched data (bulk stream mode)
560
+ */
561
+ onBatch(handler) {
562
+ const originalHandler = this.handlers.onMessage;
563
+ this.handlers.onMessage = (message) => {
564
+ if (message.type === "historical_batch") {
565
+ const msg = message;
566
+ handler(msg.coin, msg.records);
567
+ }
568
+ originalHandler?.(message);
569
+ };
570
+ }
571
+ /**
572
+ * Handle replay started event
573
+ */
574
+ onReplayStart(handler) {
575
+ const originalHandler = this.handlers.onMessage;
576
+ this.handlers.onMessage = (message) => {
577
+ if (message.type === "replay_started") {
578
+ const msg = message;
579
+ handler(msg.channel, msg.coin, msg.total_records, msg.speed);
580
+ }
581
+ originalHandler?.(message);
582
+ };
583
+ }
584
+ /**
585
+ * Handle replay completed event
586
+ */
587
+ onReplayComplete(handler) {
588
+ const originalHandler = this.handlers.onMessage;
589
+ this.handlers.onMessage = (message) => {
590
+ if (message.type === "replay_completed") {
591
+ const msg = message;
592
+ handler(msg.channel, msg.coin, msg.records_sent);
593
+ }
594
+ originalHandler?.(message);
595
+ };
596
+ }
597
+ /**
598
+ * Handle stream started event
599
+ */
600
+ onStreamStart(handler) {
601
+ const originalHandler = this.handlers.onMessage;
602
+ this.handlers.onMessage = (message) => {
603
+ if (message.type === "stream_started") {
604
+ const msg = message;
605
+ handler(msg.channel, msg.coin, msg.total_records);
606
+ }
607
+ originalHandler?.(message);
608
+ };
609
+ }
610
+ /**
611
+ * Handle stream progress event
612
+ */
613
+ onStreamProgress(handler) {
614
+ const originalHandler = this.handlers.onMessage;
615
+ this.handlers.onMessage = (message) => {
616
+ if (message.type === "stream_progress") {
617
+ const msg = message;
618
+ handler(msg.records_sent, msg.total_records, msg.progress_pct);
619
+ }
620
+ originalHandler?.(message);
621
+ };
622
+ }
623
+ /**
624
+ * Handle stream completed event
625
+ */
626
+ onStreamComplete(handler) {
627
+ const originalHandler = this.handlers.onMessage;
628
+ this.handlers.onMessage = (message) => {
629
+ if (message.type === "stream_completed") {
630
+ const msg = message;
631
+ handler(msg.channel, msg.coin, msg.records_sent);
632
+ }
633
+ originalHandler?.(message);
634
+ };
635
+ }
636
+ /**
637
+ * Get current connection state
638
+ */
639
+ getState() {
640
+ return this.state;
641
+ }
642
+ /**
643
+ * Check if connected
644
+ */
645
+ isConnected() {
646
+ return this.ws?.readyState === WebSocket.OPEN;
647
+ }
648
+ /**
649
+ * Set event handlers after construction
650
+ */
651
+ on(event, handler) {
652
+ this.handlers[event] = handler;
653
+ }
654
+ /**
655
+ * Helper to handle typed orderbook data
656
+ */
657
+ onOrderbook(handler) {
658
+ const originalHandler = this.handlers.onMessage;
659
+ this.handlers.onMessage = (message) => {
660
+ if (message.type === "data" && message.channel === "orderbook") {
661
+ handler(message.coin, message.data);
662
+ }
663
+ originalHandler?.(message);
664
+ };
665
+ }
666
+ /**
667
+ * Helper to handle typed trade data
668
+ */
669
+ onTrades(handler) {
670
+ const originalHandler = this.handlers.onMessage;
671
+ this.handlers.onMessage = (message) => {
672
+ if (message.type === "data" && message.channel === "trades") {
673
+ handler(message.coin, message.data);
674
+ }
675
+ originalHandler?.(message);
676
+ };
677
+ }
678
+ // Private methods
679
+ send(message) {
680
+ if (this.ws?.readyState === WebSocket.OPEN) {
681
+ this.ws.send(JSON.stringify(message));
682
+ }
683
+ }
684
+ setState(state) {
685
+ this.state = state;
686
+ this.handlers.onStateChange?.(state);
687
+ }
688
+ startPing() {
689
+ this.stopPing();
690
+ this.pingTimer = setInterval(() => {
691
+ this.send({ op: "ping" });
692
+ }, this.options.pingInterval);
693
+ }
694
+ stopPing() {
695
+ if (this.pingTimer) {
696
+ clearInterval(this.pingTimer);
697
+ this.pingTimer = null;
698
+ }
699
+ }
700
+ subscriptionKey(channel, coin) {
701
+ return coin ? `${channel}:${coin}` : channel;
702
+ }
703
+ resubscribe() {
704
+ for (const key of this.subscriptions) {
705
+ const [channel, coin] = key.split(":");
706
+ this.send({ op: "subscribe", channel, coin });
707
+ }
708
+ }
709
+ scheduleReconnect() {
710
+ if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
711
+ this.setState("disconnected");
712
+ return;
713
+ }
714
+ this.setState("reconnecting");
715
+ this.reconnectAttempts++;
716
+ const delay = this.options.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
717
+ this.reconnectTimer = setTimeout(() => {
718
+ this.connect();
719
+ }, delay);
720
+ }
721
+ clearReconnectTimer() {
722
+ if (this.reconnectTimer) {
723
+ clearTimeout(this.reconnectTimer);
724
+ this.reconnectTimer = null;
725
+ }
726
+ }
727
+ };
728
+ export {
729
+ OxArchive,
730
+ OxArchiveError,
731
+ OxArchiveWs,
732
+ OxArchive as default
733
+ };
734
+ //# sourceMappingURL=index.mjs.map