@pear-protocol/market-sdk 0.0.1-preview.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.
Files changed (82) hide show
  1. package/README.md +319 -0
  2. package/dist/chart/cache/index.d.ts +21 -0
  3. package/dist/chart/cache/index.js +101 -0
  4. package/dist/chart/chart.d.ts +25 -0
  5. package/dist/chart/chart.js +141 -0
  6. package/dist/chart/collector/binance.d.ts +10 -0
  7. package/dist/chart/collector/binance.js +27 -0
  8. package/dist/chart/collector/bybit.d.ts +10 -0
  9. package/dist/chart/collector/bybit.js +39 -0
  10. package/dist/chart/collector/helpers.d.ts +10 -0
  11. package/dist/chart/collector/helpers.js +21 -0
  12. package/dist/chart/collector/hyperliquid.d.ts +10 -0
  13. package/dist/chart/collector/hyperliquid.js +15 -0
  14. package/dist/chart/collector/index.d.ts +41 -0
  15. package/dist/chart/collector/index.js +202 -0
  16. package/dist/chart/collector/okx.d.ts +10 -0
  17. package/dist/chart/collector/okx.js +38 -0
  18. package/dist/chart/compute/asset.d.ts +11 -0
  19. package/dist/chart/compute/asset.js +24 -0
  20. package/dist/chart/compute/index.d.ts +10 -0
  21. package/dist/chart/compute/index.js +4 -0
  22. package/dist/chart/compute/performance.d.ts +11 -0
  23. package/dist/chart/compute/performance.js +81 -0
  24. package/dist/chart/compute/price-ratio.d.ts +11 -0
  25. package/dist/chart/compute/price-ratio.js +107 -0
  26. package/dist/chart/compute/weighted-ratio.d.ts +11 -0
  27. package/dist/chart/compute/weighted-ratio.js +109 -0
  28. package/dist/chart/types.d.ts +55 -0
  29. package/dist/chart/types.js +1 -0
  30. package/dist/chart/utils.d.ts +14 -0
  31. package/dist/chart/utils.js +91 -0
  32. package/dist/chart/ws/base-candle.d.ts +29 -0
  33. package/dist/chart/ws/base-candle.js +71 -0
  34. package/dist/chart/ws/binance.d.ts +19 -0
  35. package/dist/chart/ws/binance.js +43 -0
  36. package/dist/chart/ws/bybit.d.ts +18 -0
  37. package/dist/chart/ws/bybit.js +63 -0
  38. package/dist/chart/ws/hyperliquid.d.ts +31 -0
  39. package/dist/chart/ws/hyperliquid.js +40 -0
  40. package/dist/chart/ws/index.d.ts +11 -0
  41. package/dist/chart/ws/index.js +24 -0
  42. package/dist/chart/ws/okx.d.ts +18 -0
  43. package/dist/chart/ws/okx.js +60 -0
  44. package/dist/index.d.ts +10 -0
  45. package/dist/index.js +4 -0
  46. package/dist/orderbook/book/aggregate.d.ts +26 -0
  47. package/dist/orderbook/book/aggregate.js +38 -0
  48. package/dist/orderbook/book/local-book.d.ts +37 -0
  49. package/dist/orderbook/book/local-book.js +90 -0
  50. package/dist/orderbook/orderbook.d.ts +48 -0
  51. package/dist/orderbook/orderbook.js +111 -0
  52. package/dist/orderbook/types.d.ts +67 -0
  53. package/dist/orderbook/types.js +4 -0
  54. package/dist/orderbook/utils.d.ts +12 -0
  55. package/dist/orderbook/utils.js +35 -0
  56. package/dist/orderbook/ws/base-depth.d.ts +41 -0
  57. package/dist/orderbook/ws/base-depth.js +89 -0
  58. package/dist/orderbook/ws/binance.d.ts +23 -0
  59. package/dist/orderbook/ws/binance.js +126 -0
  60. package/dist/orderbook/ws/bybit.d.ts +15 -0
  61. package/dist/orderbook/ws/bybit.js +40 -0
  62. package/dist/orderbook/ws/hyperliquid.d.ts +20 -0
  63. package/dist/orderbook/ws/hyperliquid.js +87 -0
  64. package/dist/orderbook/ws/index.d.ts +11 -0
  65. package/dist/orderbook/ws/index.js +24 -0
  66. package/dist/orderbook/ws/okx.d.ts +15 -0
  67. package/dist/orderbook/ws/okx.js +33 -0
  68. package/dist/shared/types.d.ts +6 -0
  69. package/dist/shared/types.js +1 -0
  70. package/dist/transport/base-transport.d.ts +28 -0
  71. package/dist/transport/base-transport.js +95 -0
  72. package/dist/transport/binance.d.ts +13 -0
  73. package/dist/transport/binance.js +16 -0
  74. package/dist/transport/bybit.d.ts +13 -0
  75. package/dist/transport/bybit.js +16 -0
  76. package/dist/transport/hyperliquid.d.ts +13 -0
  77. package/dist/transport/hyperliquid.js +16 -0
  78. package/dist/transport/index.d.ts +10 -0
  79. package/dist/transport/index.js +24 -0
  80. package/dist/transport/okx.d.ts +13 -0
  81. package/dist/transport/okx.js +16 -0
  82. package/package.json +37 -0
@@ -0,0 +1,23 @@
1
+ import { WsMessage } from '../../shared/types.js';
2
+ import { DepthUpdate } from '../types.js';
3
+ import { BaseDepthWs } from './base-depth.js';
4
+ import '../../transport/index.js';
5
+ import '@pear-protocol/types';
6
+ import '../../transport/base-transport.js';
7
+ import 'partysocket';
8
+
9
+ declare class BinanceDepthWs extends BaseDepthWs {
10
+ private idCounter;
11
+ private symbolStates;
12
+ protected buildSubscribeMessage(symbol: string): WsMessage;
13
+ protected buildUnsubscribeMessage(symbol: string): WsMessage;
14
+ subscribe(symbol: string): void;
15
+ private bootstrap;
16
+ private applySnapshot;
17
+ private processEvent;
18
+ private emitDelta;
19
+ private rebootstrap;
20
+ protected parseMessage(data: string): DepthUpdate | null;
21
+ }
22
+
23
+ export { BinanceDepthWs };
@@ -0,0 +1,126 @@
1
+ import { BaseDepthWs, parseStringLevels } from './base-depth';
2
+
3
+ const BINANCE_DEPTH_REST = "https://fapi.binance.com/fapi/v1/depth";
4
+ const toStreamName = (symbol) => `${symbol.toLowerCase()}@depth@100ms`;
5
+ class BinanceDepthWs extends BaseDepthWs {
6
+ idCounter = 1;
7
+ symbolStates = /* @__PURE__ */ new Map();
8
+ buildSubscribeMessage(symbol) {
9
+ return {
10
+ method: "SUBSCRIBE",
11
+ params: [toStreamName(symbol)],
12
+ id: this.idCounter++
13
+ };
14
+ }
15
+ buildUnsubscribeMessage(symbol) {
16
+ this.symbolStates.delete(symbol);
17
+ return {
18
+ method: "UNSUBSCRIBE",
19
+ params: [toStreamName(symbol)],
20
+ id: this.idCounter++
21
+ };
22
+ }
23
+ subscribe(symbol) {
24
+ this.symbolStates.set(symbol, { phase: "buffering", buffer: [] });
25
+ super.subscribe(symbol);
26
+ this.bootstrap(symbol);
27
+ }
28
+ async bootstrap(symbol) {
29
+ try {
30
+ const url = `${BINANCE_DEPTH_REST}?symbol=${symbol}&limit=1000`;
31
+ const res = await fetch(url);
32
+ if (!res.ok) {
33
+ console.warn(`Binance depth snapshot failed for ${symbol}: ${res.status}`);
34
+ this.rebootstrap(symbol);
35
+ return;
36
+ }
37
+ const snapshot = await res.json();
38
+ this.applySnapshot(symbol, snapshot);
39
+ } catch (err) {
40
+ console.warn("Binance depth snapshot fetch error:", err);
41
+ this.rebootstrap(symbol);
42
+ }
43
+ }
44
+ applySnapshot(symbol, snapshot) {
45
+ const state = this.symbolStates.get(symbol);
46
+ if (!state || state.phase !== "buffering") return;
47
+ const { lastUpdateId } = snapshot;
48
+ const buffered = state.buffer;
49
+ this.onUpdate({
50
+ symbol,
51
+ type: "snapshot",
52
+ bids: parseStringLevels(snapshot.bids),
53
+ asks: parseStringLevels(snapshot.asks),
54
+ ts: snapshot.E ?? Date.now()
55
+ });
56
+ this.symbolStates.set(symbol, { phase: "syncing", lastUpdateId });
57
+ for (const event of buffered) {
58
+ if (!this.processEvent(symbol, event)) {
59
+ this.rebootstrap(symbol);
60
+ return;
61
+ }
62
+ }
63
+ }
64
+ processEvent(symbol, payload) {
65
+ const state = this.symbolStates.get(symbol);
66
+ if (!state) return false;
67
+ if (state.phase === "syncing") {
68
+ if (payload.u < state.lastUpdateId) return true;
69
+ if (!(payload.U <= state.lastUpdateId && payload.u >= state.lastUpdateId)) {
70
+ return false;
71
+ }
72
+ this.symbolStates.set(symbol, { phase: "ready", lastFinalUpdateId: payload.u });
73
+ this.emitDelta(symbol, payload);
74
+ return true;
75
+ }
76
+ if (state.phase === "ready") {
77
+ if (payload.pu !== state.lastFinalUpdateId) {
78
+ return false;
79
+ }
80
+ state.lastFinalUpdateId = payload.u;
81
+ this.emitDelta(symbol, payload);
82
+ return true;
83
+ }
84
+ return true;
85
+ }
86
+ emitDelta(symbol, payload) {
87
+ this.onUpdate({
88
+ symbol,
89
+ type: "delta",
90
+ bids: parseStringLevels(payload.b),
91
+ asks: parseStringLevels(payload.a),
92
+ ts: payload.E ?? Date.now(),
93
+ firstUpdateId: payload.U,
94
+ finalUpdateId: payload.u,
95
+ prevFinalUpdateId: payload.pu
96
+ });
97
+ }
98
+ rebootstrap(symbol) {
99
+ if (!this.symbolStates.has(symbol)) return;
100
+ this.symbolStates.set(symbol, { phase: "buffering", buffer: [] });
101
+ setTimeout(() => this.bootstrap(symbol), 1e3);
102
+ }
103
+ parseMessage(data) {
104
+ let msg;
105
+ try {
106
+ msg = JSON.parse(data);
107
+ } catch {
108
+ return null;
109
+ }
110
+ if (msg.e !== "depthUpdate") return null;
111
+ const payload = msg;
112
+ if (!payload.s || !Array.isArray(payload.b) || !Array.isArray(payload.a)) return null;
113
+ const state = this.symbolStates.get(payload.s);
114
+ if (!state) return null;
115
+ if (state.phase === "buffering") {
116
+ state.buffer.push(payload);
117
+ return null;
118
+ }
119
+ if (!this.processEvent(payload.s, payload)) {
120
+ this.rebootstrap(payload.s);
121
+ }
122
+ return null;
123
+ }
124
+ }
125
+
126
+ export { BinanceDepthWs };
@@ -0,0 +1,15 @@
1
+ import { WsMessage } from '../../shared/types.js';
2
+ import { DepthUpdate } from '../types.js';
3
+ import { BaseDepthWs } from './base-depth.js';
4
+ import '../../transport/index.js';
5
+ import '@pear-protocol/types';
6
+ import '../../transport/base-transport.js';
7
+ import 'partysocket';
8
+
9
+ declare class BybitDepthWs extends BaseDepthWs {
10
+ protected buildSubscribeMessage(symbol: string): WsMessage;
11
+ protected buildUnsubscribeMessage(symbol: string): WsMessage;
12
+ protected parseMessage(data: string): DepthUpdate | null;
13
+ }
14
+
15
+ export { BybitDepthWs };
@@ -0,0 +1,40 @@
1
+ import { BaseDepthWs, parseStringLevels } from './base-depth';
2
+
3
+ const toTopic = (symbol) => `orderbook.1000.${symbol}`;
4
+ const fromTopic = (topic) => {
5
+ const parts = topic.split(".");
6
+ return parts[2] ?? "";
7
+ };
8
+ class BybitDepthWs extends BaseDepthWs {
9
+ buildSubscribeMessage(symbol) {
10
+ return { op: "subscribe", args: [toTopic(symbol)] };
11
+ }
12
+ buildUnsubscribeMessage(symbol) {
13
+ return { op: "unsubscribe", args: [toTopic(symbol)] };
14
+ }
15
+ parseMessage(data) {
16
+ let msg;
17
+ try {
18
+ msg = JSON.parse(data);
19
+ } catch {
20
+ return null;
21
+ }
22
+ const topic = msg.topic;
23
+ const type = msg.type;
24
+ const payload = msg.data;
25
+ const ts = msg.ts ?? Date.now();
26
+ if (!topic || !topic.startsWith("orderbook.")) return null;
27
+ if (!payload) return null;
28
+ if (type !== "snapshot" && type !== "delta") return null;
29
+ const symbol = payload.s ?? fromTopic(topic);
30
+ return {
31
+ symbol,
32
+ type,
33
+ bids: parseStringLevels(payload.b),
34
+ asks: parseStringLevels(payload.a),
35
+ ts
36
+ };
37
+ }
38
+ }
39
+
40
+ export { BybitDepthWs };
@@ -0,0 +1,20 @@
1
+ import { WsMessage } from '../../shared/types.js';
2
+ import { DepthUpdate } from '../types.js';
3
+ import { BaseDepthWs } from './base-depth.js';
4
+ import '../../transport/index.js';
5
+ import '@pear-protocol/types';
6
+ import '../../transport/base-transport.js';
7
+ import 'partysocket';
8
+
9
+ declare class HyperliquidDepthWs extends BaseDepthWs {
10
+ private aggregation;
11
+ private lastNSigFigs;
12
+ private lastMantissa;
13
+ get serverAggregated(): boolean;
14
+ onAggregationChange(aggregation: number): void;
15
+ protected buildSubscribeMessage(symbol: string): WsMessage;
16
+ protected buildUnsubscribeMessage(symbol: string): WsMessage;
17
+ protected parseMessage(data: string): DepthUpdate | null;
18
+ }
19
+
20
+ export { HyperliquidDepthWs };
@@ -0,0 +1,87 @@
1
+ import { BaseDepthWs } from './base-depth';
2
+
3
+ const parseLevels = (levels) => {
4
+ if (!levels) return [];
5
+ return levels.map((l) => [l.px, Number(l.sz)]);
6
+ };
7
+ function computeSigFigs(bucketSize, currentPrice) {
8
+ if (bucketSize <= 0 || currentPrice <= 0) return {};
9
+ const priceMag = Math.floor(Math.log10(currentPrice));
10
+ const bucketMag = Math.floor(Math.log10(bucketSize));
11
+ const rawSigFigs = priceMag - bucketMag + 1;
12
+ if (rawSigFigs < 2) return {};
13
+ if (rawSigFigs > 5) return { nSigFigs: null };
14
+ const nSigFigs = rawSigFigs;
15
+ if (nSigFigs < 5) return { nSigFigs };
16
+ const ratio = bucketSize / 10 ** bucketMag;
17
+ const mantissaCandidates = [1, 2, 5];
18
+ const mantissa = mantissaCandidates.reduce(
19
+ (closest, candidate) => Math.abs(candidate - ratio) < Math.abs(closest - ratio) ? candidate : closest
20
+ );
21
+ if (mantissa === 1) return { nSigFigs: 5 };
22
+ return { nSigFigs: 5, mantissa };
23
+ }
24
+ class HyperliquidDepthWs extends BaseDepthWs {
25
+ aggregation = 0;
26
+ lastNSigFigs;
27
+ lastMantissa;
28
+ get serverAggregated() {
29
+ return true;
30
+ }
31
+ onAggregationChange(aggregation) {
32
+ this.aggregation = aggregation;
33
+ if (this.transport.connected) {
34
+ for (const symbol of this.symbols) {
35
+ this.transport.send(this.buildUnsubscribeMessage(symbol));
36
+ this.transport.send(this.buildSubscribeMessage(symbol));
37
+ }
38
+ }
39
+ }
40
+ buildSubscribeMessage(symbol) {
41
+ const { nSigFigs, mantissa } = this.aggregation > 0 && this.snapshottedPrice > 0 ? computeSigFigs(this.aggregation, this.snapshottedPrice) : {};
42
+ this.lastNSigFigs = nSigFigs;
43
+ this.lastMantissa = mantissa;
44
+ const subscription = {
45
+ type: "l2Book",
46
+ coin: symbol
47
+ };
48
+ if (nSigFigs !== void 0) subscription.nSigFigs = nSigFigs;
49
+ if (mantissa !== void 0) subscription.mantissa = mantissa;
50
+ return {
51
+ method: "subscribe",
52
+ subscription
53
+ };
54
+ }
55
+ buildUnsubscribeMessage(symbol) {
56
+ const subscription = {
57
+ type: "l2Book",
58
+ coin: symbol
59
+ };
60
+ if (this.lastNSigFigs !== void 0) subscription.nSigFigs = this.lastNSigFigs;
61
+ if (this.lastMantissa !== void 0) subscription.mantissa = this.lastMantissa;
62
+ return {
63
+ method: "unsubscribe",
64
+ subscription
65
+ };
66
+ }
67
+ parseMessage(data) {
68
+ let msg;
69
+ try {
70
+ msg = JSON.parse(data);
71
+ } catch {
72
+ return null;
73
+ }
74
+ if (msg.channel !== "l2Book" || !msg.data) return null;
75
+ const payload = msg.data;
76
+ if (!payload.coin || !Array.isArray(payload.levels) || payload.levels.length < 2) return null;
77
+ return {
78
+ symbol: payload.coin,
79
+ type: "snapshot",
80
+ bids: parseLevels(payload.levels[0]),
81
+ asks: parseLevels(payload.levels[1]),
82
+ ts: payload.time ?? Date.now()
83
+ };
84
+ }
85
+ }
86
+
87
+ export { HyperliquidDepthWs };
@@ -0,0 +1,11 @@
1
+ import { Connector } from '@pear-protocol/types';
2
+ import { BaseTransport } from '../../transport/base-transport.js';
3
+ import { DepthHandler, BaseDepthWs } from './base-depth.js';
4
+ import 'partysocket';
5
+ import '../../shared/types.js';
6
+ import '../types.js';
7
+ import '../../transport/index.js';
8
+
9
+ declare function createDepthWs(transport: BaseTransport, connector: Connector, onUpdate: DepthHandler): BaseDepthWs;
10
+
11
+ export { BaseDepthWs, DepthHandler, createDepthWs };
@@ -0,0 +1,24 @@
1
+ export { BaseDepthWs } from './base-depth';
2
+ import { BinanceDepthWs } from './binance';
3
+ import { BybitDepthWs } from './bybit';
4
+ import { HyperliquidDepthWs } from './hyperliquid';
5
+ import { OkxDepthWs } from './okx';
6
+
7
+ function createDepthWs(transport, connector, onUpdate) {
8
+ switch (connector) {
9
+ case "hyperliquid":
10
+ return new HyperliquidDepthWs(transport, onUpdate);
11
+ case "binance":
12
+ return new BinanceDepthWs(transport, onUpdate);
13
+ case "bybit":
14
+ return new BybitDepthWs(transport, onUpdate);
15
+ case "okx":
16
+ return new OkxDepthWs(transport, onUpdate);
17
+ default: {
18
+ const _exhaustive = connector;
19
+ throw new Error(`Unsupported exchange: ${String(_exhaustive)}`);
20
+ }
21
+ }
22
+ }
23
+
24
+ export { createDepthWs };
@@ -0,0 +1,15 @@
1
+ import { WsMessage } from '../../shared/types.js';
2
+ import { DepthUpdate } from '../types.js';
3
+ import { BaseDepthWs } from './base-depth.js';
4
+ import '../../transport/index.js';
5
+ import '@pear-protocol/types';
6
+ import '../../transport/base-transport.js';
7
+ import 'partysocket';
8
+
9
+ declare class OkxDepthWs extends BaseDepthWs {
10
+ protected buildSubscribeMessage(symbol: string): WsMessage;
11
+ protected buildUnsubscribeMessage(symbol: string): WsMessage;
12
+ protected parseMessage(data: string): DepthUpdate | null;
13
+ }
14
+
15
+ export { OkxDepthWs };
@@ -0,0 +1,33 @@
1
+ import { BaseDepthWs, parseStringLevels } from './base-depth';
2
+
3
+ class OkxDepthWs extends BaseDepthWs {
4
+ buildSubscribeMessage(symbol) {
5
+ return { op: "subscribe", args: [{ channel: "books", instId: symbol }] };
6
+ }
7
+ buildUnsubscribeMessage(symbol) {
8
+ return { op: "unsubscribe", args: [{ channel: "books", instId: symbol }] };
9
+ }
10
+ parseMessage(data) {
11
+ if (data === "pong") return null;
12
+ let msg;
13
+ try {
14
+ msg = JSON.parse(data);
15
+ } catch {
16
+ return null;
17
+ }
18
+ if (!msg.arg || msg.arg.channel !== "books") return null;
19
+ if (!msg.data || !Array.isArray(msg.data) || msg.data.length === 0) return null;
20
+ const payload = msg.data[0];
21
+ if (!payload) return null;
22
+ const type = msg.action === "snapshot" ? "snapshot" : "delta";
23
+ return {
24
+ symbol: msg.arg.instId,
25
+ type,
26
+ bids: parseStringLevels(payload.bids),
27
+ asks: parseStringLevels(payload.asks),
28
+ ts: Number(payload.ts) || Date.now()
29
+ };
30
+ }
31
+ }
32
+
33
+ export { OkxDepthWs };
@@ -0,0 +1,6 @@
1
+ interface WsMessage {
2
+ [key: string]: unknown;
3
+ }
4
+ type MessageListener = (data: string) => void;
5
+
6
+ export type { MessageListener, WsMessage };
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,28 @@
1
+ import { Connector } from '@pear-protocol/types';
2
+ import { WebSocket } from 'partysocket';
3
+ import { WsMessage, MessageListener } from '../shared/types.js';
4
+
5
+ declare abstract class BaseTransport {
6
+ abstract readonly connector: Connector;
7
+ protected ws: WebSocket | null;
8
+ protected pingTimer: ReturnType<typeof setInterval> | null;
9
+ private messageListeners;
10
+ private openListeners;
11
+ private nextListenerId;
12
+ protected abstract getWsUrl(): string;
13
+ protected abstract getPingIntervalMs(): number;
14
+ protected abstract buildPingMessage(): WsMessage | string | null;
15
+ connect(): void;
16
+ disconnect(): void;
17
+ get connected(): boolean;
18
+ send(msg: WsMessage | string): void;
19
+ addMessageListener(listener: MessageListener): string;
20
+ removeMessageListener(id: string): void;
21
+ addOpenListener(listener: () => void): string;
22
+ removeOpenListener(id: string): void;
23
+ destroy(): void;
24
+ private startPing;
25
+ private stopPing;
26
+ }
27
+
28
+ export { BaseTransport };
@@ -0,0 +1,95 @@
1
+ import { WebSocket } from 'partysocket';
2
+
3
+ class BaseTransport {
4
+ ws = null;
5
+ pingTimer = null;
6
+ messageListeners = /* @__PURE__ */ new Map();
7
+ openListeners = /* @__PURE__ */ new Map();
8
+ nextListenerId = 0;
9
+ connect() {
10
+ if (this.ws) return;
11
+ const ws = new WebSocket(this.getWsUrl(), [], {
12
+ maxReconnectionDelay: 3e4,
13
+ minReconnectionDelay: 1e3,
14
+ reconnectionDelayGrowFactor: 2,
15
+ connectionTimeout: 1e4
16
+ });
17
+ ws.addEventListener("open", () => {
18
+ this.startPing();
19
+ for (const listener of this.openListeners.values()) {
20
+ try {
21
+ listener();
22
+ } catch (error) {
23
+ console.warn("Error in transport open listener", error);
24
+ }
25
+ }
26
+ });
27
+ ws.addEventListener("message", (event) => {
28
+ for (const listener of this.messageListeners.values()) {
29
+ try {
30
+ listener(event.data);
31
+ } catch (error) {
32
+ console.warn("Error in transport message listener", error);
33
+ }
34
+ }
35
+ });
36
+ ws.addEventListener("close", () => {
37
+ this.stopPing();
38
+ });
39
+ ws.addEventListener("error", () => {
40
+ });
41
+ this.ws = ws;
42
+ }
43
+ disconnect() {
44
+ this.stopPing();
45
+ if (this.ws) {
46
+ this.ws.close();
47
+ this.ws = null;
48
+ }
49
+ }
50
+ get connected() {
51
+ return this.ws?.readyState === WebSocket.OPEN;
52
+ }
53
+ send(msg) {
54
+ if (this.ws?.readyState === WebSocket.OPEN) {
55
+ this.ws.send(typeof msg === "string" ? msg : JSON.stringify(msg));
56
+ }
57
+ }
58
+ addMessageListener(listener) {
59
+ const id = String(++this.nextListenerId);
60
+ this.messageListeners.set(id, listener);
61
+ return id;
62
+ }
63
+ removeMessageListener(id) {
64
+ this.messageListeners.delete(id);
65
+ }
66
+ addOpenListener(listener) {
67
+ const id = String(++this.nextListenerId);
68
+ this.openListeners.set(id, listener);
69
+ return id;
70
+ }
71
+ removeOpenListener(id) {
72
+ this.openListeners.delete(id);
73
+ }
74
+ destroy() {
75
+ this.disconnect();
76
+ this.messageListeners.clear();
77
+ this.openListeners.clear();
78
+ }
79
+ startPing() {
80
+ this.stopPing();
81
+ const intervalMs = this.getPingIntervalMs();
82
+ if (intervalMs <= 0) return;
83
+ const pingMsg = this.buildPingMessage();
84
+ if (!pingMsg) return;
85
+ this.pingTimer = setInterval(() => this.send(pingMsg), intervalMs);
86
+ }
87
+ stopPing() {
88
+ if (this.pingTimer) {
89
+ clearInterval(this.pingTimer);
90
+ this.pingTimer = null;
91
+ }
92
+ }
93
+ }
94
+
95
+ export { BaseTransport };
@@ -0,0 +1,13 @@
1
+ import { Connector } from '@pear-protocol/types';
2
+ import { BaseTransport } from './base-transport.js';
3
+ import 'partysocket';
4
+ import '../shared/types.js';
5
+
6
+ declare class BinanceTransport extends BaseTransport {
7
+ readonly connector: Connector;
8
+ protected getWsUrl(): string;
9
+ protected getPingIntervalMs(): number;
10
+ protected buildPingMessage(): null;
11
+ }
12
+
13
+ export { BinanceTransport };
@@ -0,0 +1,16 @@
1
+ import { BaseTransport } from './base-transport';
2
+
3
+ class BinanceTransport extends BaseTransport {
4
+ connector = "binance";
5
+ getWsUrl() {
6
+ return "wss://fstream.binance.com/ws";
7
+ }
8
+ getPingIntervalMs() {
9
+ return 0;
10
+ }
11
+ buildPingMessage() {
12
+ return null;
13
+ }
14
+ }
15
+
16
+ export { BinanceTransport };
@@ -0,0 +1,13 @@
1
+ import { Connector } from '@pear-protocol/types';
2
+ import { WsMessage } from '../shared/types.js';
3
+ import { BaseTransport } from './base-transport.js';
4
+ import 'partysocket';
5
+
6
+ declare class BybitTransport extends BaseTransport {
7
+ readonly connector: Connector;
8
+ protected getWsUrl(): string;
9
+ protected getPingIntervalMs(): number;
10
+ protected buildPingMessage(): WsMessage;
11
+ }
12
+
13
+ export { BybitTransport };
@@ -0,0 +1,16 @@
1
+ import { BaseTransport } from './base-transport';
2
+
3
+ class BybitTransport extends BaseTransport {
4
+ connector = "bybit";
5
+ getWsUrl() {
6
+ return "wss://stream.bybit.com/v5/public/linear";
7
+ }
8
+ getPingIntervalMs() {
9
+ return 2e4;
10
+ }
11
+ buildPingMessage() {
12
+ return { op: "ping" };
13
+ }
14
+ }
15
+
16
+ export { BybitTransport };
@@ -0,0 +1,13 @@
1
+ import { Connector } from '@pear-protocol/types';
2
+ import { WsMessage } from '../shared/types.js';
3
+ import { BaseTransport } from './base-transport.js';
4
+ import 'partysocket';
5
+
6
+ declare class HyperliquidTransport extends BaseTransport {
7
+ readonly connector: Connector;
8
+ protected getWsUrl(): string;
9
+ protected getPingIntervalMs(): number;
10
+ protected buildPingMessage(): WsMessage;
11
+ }
12
+
13
+ export { HyperliquidTransport };
@@ -0,0 +1,16 @@
1
+ import { BaseTransport } from './base-transport';
2
+
3
+ class HyperliquidTransport extends BaseTransport {
4
+ connector = "hyperliquid";
5
+ getWsUrl() {
6
+ return "wss://api.hyperliquid.xyz/ws";
7
+ }
8
+ getPingIntervalMs() {
9
+ return 3e4;
10
+ }
11
+ buildPingMessage() {
12
+ return { method: "ping" };
13
+ }
14
+ }
15
+
16
+ export { HyperliquidTransport };
@@ -0,0 +1,10 @@
1
+ import { Connector } from '@pear-protocol/types';
2
+ import { BaseTransport } from './base-transport.js';
3
+ import 'partysocket';
4
+ import '../shared/types.js';
5
+
6
+ type Transport = BaseTransport;
7
+
8
+ declare function CreateTransport(connector: Connector): Transport;
9
+
10
+ export { BaseTransport, CreateTransport, type Transport };
@@ -0,0 +1,24 @@
1
+ export { BaseTransport } from './base-transport';
2
+ import { BinanceTransport } from './binance';
3
+ import { BybitTransport } from './bybit';
4
+ import { HyperliquidTransport } from './hyperliquid';
5
+ import { OkxTransport } from './okx';
6
+
7
+ function CreateTransport(connector) {
8
+ switch (connector) {
9
+ case "binance":
10
+ return new BinanceTransport();
11
+ case "bybit":
12
+ return new BybitTransport();
13
+ case "okx":
14
+ return new OkxTransport();
15
+ case "hyperliquid":
16
+ return new HyperliquidTransport();
17
+ default: {
18
+ const _exhaustive = connector;
19
+ throw new Error(`Unsupported connector: ${String(_exhaustive)}`);
20
+ }
21
+ }
22
+ }
23
+
24
+ export { CreateTransport };