@n1xyz/nord-ts 0.0.1 → 0.0.4

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 (237) hide show
  1. package/.eslintrc.js +11 -0
  2. package/README.md +148 -65
  3. package/dist/bridge/NordUser.d.ts +78 -0
  4. package/dist/bridge/NordUser.js +196 -0
  5. package/dist/bridge/client.d.ts +150 -0
  6. package/dist/bridge/client.js +394 -0
  7. package/dist/bridge/const.d.ts +23 -0
  8. package/dist/bridge/const.js +47 -0
  9. package/dist/bridge/index.d.ts +5 -0
  10. package/dist/bridge/index.js +23 -0
  11. package/dist/bridge/types.d.ts +118 -0
  12. package/dist/bridge/types.js +16 -0
  13. package/dist/bridge/utils.d.ts +64 -0
  14. package/dist/bridge/utils.js +131 -0
  15. package/dist/client.d.ts +70 -0
  16. package/dist/client.js +129 -0
  17. package/dist/const.d.ts +2 -5
  18. package/dist/const.js +18 -22
  19. package/dist/constants/endpoints.d.ts +65 -0
  20. package/dist/constants/endpoints.js +68 -0
  21. package/dist/gen/common.d.ts +6 -1
  22. package/dist/gen/common.js +19 -9
  23. package/dist/gen/nord.d.ts +75 -17
  24. package/dist/gen/nord.js +987 -423
  25. package/dist/idl/bridge.d.ts +2 -0
  26. package/dist/idl/bridge.js +703 -0
  27. package/dist/index.d.ts +8 -5
  28. package/dist/index.js +18 -2
  29. package/dist/models/account.d.ts +58 -0
  30. package/dist/models/account.js +6 -0
  31. package/dist/models/index.d.ts +8 -0
  32. package/dist/models/index.js +28 -0
  33. package/dist/models/market.d.ts +137 -0
  34. package/dist/models/market.js +6 -0
  35. package/dist/models/order.d.ts +211 -0
  36. package/dist/models/order.js +6 -0
  37. package/dist/models/token.d.ts +50 -0
  38. package/dist/models/token.js +6 -0
  39. package/dist/nord/Nord.d.ts +222 -49
  40. package/dist/nord/Nord.js +290 -278
  41. package/dist/nord/NordError.d.ts +23 -0
  42. package/dist/nord/NordError.js +48 -0
  43. package/dist/nord/NordImpl.d.ts +6 -2
  44. package/dist/nord/NordImpl.js +21 -1
  45. package/dist/nord/NordUser.d.ts +208 -42
  46. package/dist/nord/NordUser.js +389 -157
  47. package/dist/nord/Subscriber.d.ts +37 -0
  48. package/dist/nord/Subscriber.js +29 -0
  49. package/dist/nord/api/actions.d.ts +101 -0
  50. package/dist/nord/api/actions.js +250 -0
  51. package/dist/nord/api/core.d.ts +49 -0
  52. package/dist/nord/api/core.js +121 -0
  53. package/dist/nord/api/index.d.ts +1 -0
  54. package/dist/nord/api/index.js +17 -0
  55. package/dist/nord/api/market.d.ts +36 -0
  56. package/dist/nord/api/market.js +98 -0
  57. package/dist/nord/api/metrics.d.ts +67 -0
  58. package/dist/nord/api/metrics.js +132 -0
  59. package/dist/nord/api/orderFunctions.d.ts +168 -0
  60. package/dist/nord/api/orderFunctions.js +133 -0
  61. package/dist/nord/api/queries.d.ts +81 -0
  62. package/dist/nord/api/queries.js +187 -0
  63. package/dist/nord/client/Nord.d.ts +335 -0
  64. package/dist/nord/client/Nord.js +532 -0
  65. package/dist/nord/client/NordUser.d.ts +320 -0
  66. package/dist/nord/client/NordUser.js +701 -0
  67. package/dist/nord/core.d.ts +48 -0
  68. package/dist/nord/core.js +97 -0
  69. package/dist/nord/index.d.ts +9 -2
  70. package/dist/nord/index.js +30 -6
  71. package/dist/nord/market.d.ts +36 -0
  72. package/dist/nord/market.js +90 -0
  73. package/dist/nord/metrics.d.ts +67 -0
  74. package/dist/nord/metrics.js +124 -0
  75. package/dist/nord/models/Subscriber.d.ts +37 -0
  76. package/dist/nord/models/Subscriber.js +29 -0
  77. package/dist/nord/queries.d.ts +81 -0
  78. package/dist/nord/queries.js +181 -0
  79. package/dist/nord/types.d.ts +88 -0
  80. package/dist/nord/types.js +2 -0
  81. package/dist/nord/utils/NordError.d.ts +35 -0
  82. package/dist/nord/utils/NordError.js +46 -0
  83. package/dist/nord/websocket.d.ts +49 -0
  84. package/dist/nord/websocket.js +107 -0
  85. package/dist/operations/account.d.ts +58 -0
  86. package/dist/operations/account.js +112 -0
  87. package/dist/operations/market.d.ts +65 -0
  88. package/dist/operations/market.js +131 -0
  89. package/dist/operations/orders.d.ts +57 -0
  90. package/dist/operations/orders.js +129 -0
  91. package/dist/solana/NordUser.d.ts +78 -0
  92. package/dist/solana/NordUser.js +196 -0
  93. package/dist/solana/client.d.ts +139 -0
  94. package/dist/solana/client.js +360 -0
  95. package/dist/solana/const.d.ts +23 -0
  96. package/dist/solana/const.js +47 -0
  97. package/dist/solana/index.d.ts +5 -0
  98. package/dist/solana/index.js +23 -0
  99. package/dist/solana/types.d.ts +118 -0
  100. package/dist/solana/types.js +16 -0
  101. package/dist/solana/utils.d.ts +64 -0
  102. package/dist/solana/utils.js +131 -0
  103. package/dist/types/api.d.ts +152 -0
  104. package/dist/types/api.js +6 -0
  105. package/dist/types/config.d.ts +34 -0
  106. package/dist/types/config.js +6 -0
  107. package/dist/types.d.ts +144 -87
  108. package/dist/types.js +13 -2
  109. package/dist/utils/errors.d.ts +96 -0
  110. package/dist/utils/errors.js +132 -0
  111. package/dist/utils/http.d.ts +35 -0
  112. package/dist/utils/http.js +105 -0
  113. package/dist/utils.d.ts +14 -5
  114. package/dist/utils.js +26 -7
  115. package/dist/websocket/NordWebSocketClient.d.ts +71 -0
  116. package/dist/websocket/NordWebSocketClient.js +343 -0
  117. package/dist/websocket/client.d.ts +93 -0
  118. package/dist/websocket/client.js +222 -0
  119. package/dist/websocket/events.d.ts +19 -0
  120. package/dist/websocket/events.js +2 -0
  121. package/dist/websocket/index.d.ts +2 -0
  122. package/dist/websocket/index.js +5 -0
  123. package/dist/websocket.d.ts +55 -0
  124. package/dist/websocket.js +211 -0
  125. package/docs/assets/navigation.js +1 -1
  126. package/docs/assets/search.js +1 -1
  127. package/docs/classes/Nord.html +2 -15
  128. package/docs/classes/NordUser.html +4 -4
  129. package/docs/enums/FillMode.html +2 -2
  130. package/docs/enums/KeyType.html +2 -2
  131. package/docs/enums/PeakTpsPeriodUnit.html +2 -2
  132. package/docs/enums/Side.html +2 -2
  133. package/docs/functions/assert.html +1 -1
  134. package/docs/functions/bigIntToProtoU128.html +1 -1
  135. package/docs/functions/checkPubKeyLength.html +1 -1
  136. package/docs/functions/checkedFetch.html +1 -1
  137. package/docs/functions/decodeLengthDelimited.html +1 -1
  138. package/docs/functions/encodeLengthDelimited.html +1 -1
  139. package/docs/functions/fillModeToProtoFillMode.html +1 -1
  140. package/docs/functions/findMarket.html +1 -1
  141. package/docs/functions/findToken.html +1 -1
  142. package/docs/functions/makeWalletSignFn.html +1 -1
  143. package/docs/functions/optExpect.html +1 -1
  144. package/docs/functions/optMap.html +1 -1
  145. package/docs/functions/optUnwrap.html +1 -1
  146. package/docs/functions/panic.html +1 -1
  147. package/docs/functions/signAction.html +1 -1
  148. package/docs/functions/toScaledU128.html +1 -1
  149. package/docs/functions/toScaledU64.html +1 -1
  150. package/docs/interfaces/Account.html +2 -2
  151. package/docs/interfaces/ActionInfo.html +2 -2
  152. package/docs/interfaces/ActionQuery.html +2 -2
  153. package/docs/interfaces/ActionResponse.html +2 -2
  154. package/docs/interfaces/ActionsExtendedInfo.html +2 -2
  155. package/docs/interfaces/ActionsQuery.html +2 -2
  156. package/docs/interfaces/ActionsResponse.html +2 -2
  157. package/docs/interfaces/AggregateMetrics.html +2 -2
  158. package/docs/interfaces/BlockQuery.html +2 -2
  159. package/docs/interfaces/BlockResponse.html +2 -2
  160. package/docs/interfaces/BlockSummary.html +2 -2
  161. package/docs/interfaces/BlockSummaryResponse.html +2 -2
  162. package/docs/interfaces/DeltaEvent.html +2 -2
  163. package/docs/interfaces/ERC20TokenInfo.html +2 -2
  164. package/docs/interfaces/Info.html +2 -2
  165. package/docs/interfaces/Market.html +2 -2
  166. package/docs/interfaces/MarketStats.html +2 -2
  167. package/docs/interfaces/MarketsStatsResponse.html +2 -2
  168. package/docs/interfaces/NordConfig.html +2 -2
  169. package/docs/interfaces/Order.html +2 -2
  170. package/docs/interfaces/OrderInfo.html +2 -2
  171. package/docs/interfaces/PerpMarketStats.html +2 -2
  172. package/docs/interfaces/RollmanActionExtendedInfo.html +2 -2
  173. package/docs/interfaces/RollmanActionInfo.html +2 -2
  174. package/docs/interfaces/RollmanActionResponse.html +2 -2
  175. package/docs/interfaces/RollmanActionsResponse.html +2 -2
  176. package/docs/interfaces/RollmanBlockResponse.html +2 -2
  177. package/docs/interfaces/SubscriberConfig.html +2 -2
  178. package/docs/interfaces/Token.html +2 -2
  179. package/docs/interfaces/Trade.html +2 -2
  180. package/docs/interfaces/Trades.html +2 -2
  181. package/docs/modules.html +0 -7
  182. package/docs/types/BigIntValue.html +1 -1
  183. package/docs/variables/DEBUG_KEYS.html +1 -1
  184. package/docs/variables/DEFAULT_FUNDING_AMOUNTS.html +1 -1
  185. package/docs/variables/DEV_CONTRACT_ADDRESS.html +1 -1
  186. package/docs/variables/DEV_TOKEN_INFOS.html +1 -1
  187. package/docs/variables/DEV_URL.html +1 -1
  188. package/docs/variables/ERC20_ABI.html +1 -1
  189. package/docs/variables/EVM_DEV_URL.html +1 -1
  190. package/docs/variables/FAUCET_PRIVATE_ADDRESS.html +1 -1
  191. package/docs/variables/MAX_BUFFER_LEN.html +1 -1
  192. package/docs/variables/NORD_GETTERS_FACET_ABI.html +1 -1
  193. package/docs/variables/NORD_RAMP_FACET_ABI.html +1 -1
  194. package/docs/variables/SESSION_TTL.html +1 -1
  195. package/docs/variables/WEBSERVER_DEV_URL.html +1 -1
  196. package/docs/variables/ZERO_DECIMAL.html +1 -1
  197. package/package.json +10 -12
  198. package/src/bridge/client.ts +487 -0
  199. package/src/bridge/const.ts +53 -0
  200. package/src/bridge/index.ts +7 -0
  201. package/src/bridge/types.ts +127 -0
  202. package/src/bridge/utils.ts +140 -0
  203. package/src/const.ts +20 -25
  204. package/src/gen/common.ts +27 -10
  205. package/src/gen/nord.ts +1044 -483
  206. package/src/idl/bridge.ts +702 -0
  207. package/src/index.ts +24 -5
  208. package/src/nord/{actions.ts → api/actions.ts} +33 -37
  209. package/src/nord/api/core.ts +130 -0
  210. package/src/nord/api/market.ts +125 -0
  211. package/src/nord/api/metrics.ts +154 -0
  212. package/src/nord/api/queries.ts +236 -0
  213. package/src/nord/client/Nord.ts +652 -0
  214. package/src/nord/client/NordUser.ts +1105 -0
  215. package/src/nord/index.ts +16 -2
  216. package/src/nord/models/Subscriber.ts +57 -0
  217. package/src/nord/utils/NordError.ts +72 -0
  218. package/src/types.ts +170 -99
  219. package/src/utils.ts +40 -19
  220. package/src/websocket/NordWebSocketClient.ts +432 -0
  221. package/src/websocket/events.ts +31 -0
  222. package/src/websocket/index.ts +2 -0
  223. package/tests/utils.spec.ts +24 -24
  224. package/docs/classes/Subscriber.html +0 -6
  225. package/docs/functions/createWebSocketSubscription.html +0 -12
  226. package/docs/interfaces/OrderbookOrder.html +0 -6
  227. package/docs/interfaces/OrderbookResponse.html +0 -10
  228. package/docs/interfaces/TradeInfo.html +0 -20
  229. package/docs/interfaces/TradesQueryParams.html +0 -10
  230. package/docs/interfaces/TradesResponse.html +0 -12
  231. package/src/abis/ERC20_ABI.ts +0 -310
  232. package/src/abis/NORD_GETTERS_FACET_ABI.ts +0 -192
  233. package/src/abis/NORD_RAMP_FACET_ABI.ts +0 -141
  234. package/src/abis/index.ts +0 -3
  235. package/src/nord/Nord.ts +0 -504
  236. package/src/nord/NordImpl.ts +0 -8
  237. package/src/nord/NordUser.ts +0 -469
package/dist/utils.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- import { Decimal } from "decimal.js";
2
- import { KeyType, type Market, type Token } from "./types";
3
- import * as proto from "./gen/nord";
4
- import { ethers } from "ethers";
5
- import { RequestInfo, RequestInit, Response } from "node-fetch";
1
+ import { Decimal } from 'decimal.js';
2
+ import { KeyType, type Market, type Token } from './types';
3
+ import * as proto from './gen/nord';
4
+ import { ethers } from 'ethers';
5
+ import { RequestInfo, RequestInit, Response } from 'node-fetch';
6
+ import { BN } from '@coral-xyz/anchor';
6
7
  export declare const SESSION_TTL: bigint;
7
8
  export declare const ZERO_DECIMAL: Decimal;
8
9
  export declare const MAX_BUFFER_LEN = 10000;
@@ -112,3 +113,11 @@ export declare function decodeLengthDelimited<T, M extends proto.MessageFns<T>>(
112
113
  export declare function checkPubKeyLength(keyType: KeyType, len: number): void;
113
114
  export declare function findMarket(markets: Market[], marketId: number): Market;
114
115
  export declare function findToken(tokens: Token[], tokenId: number): Token;
116
+ /**
117
+ * Convert a number to a BN with the specified number of decimals
118
+ *
119
+ * @param amount Amount as a number
120
+ * @param decimals Number of decimal places
121
+ * @returns Amount as a BN
122
+ */
123
+ export declare function toBN(amount: number, decimals: number): BN;
package/dist/utils.js CHANGED
@@ -18,6 +18,7 @@ exports.decodeLengthDelimited = decodeLengthDelimited;
18
18
  exports.checkPubKeyLength = checkPubKeyLength;
19
19
  exports.findMarket = findMarket;
20
20
  exports.findToken = findToken;
21
+ exports.toBN = toBN;
21
22
  const decimal_js_1 = require("decimal.js");
22
23
  const ed25519_1 = require("@noble/curves/ed25519");
23
24
  const bls12_381_1 = require("@noble/curves/bls12-381");
@@ -27,7 +28,8 @@ const types_1 = require("./types");
27
28
  const wire_1 = require("@bufbuild/protobuf/wire");
28
29
  const ethers_1 = require("ethers");
29
30
  const node_fetch_1 = __importDefault(require("node-fetch"));
30
- exports.SESSION_TTL = 10n * 60n * 1000n * 10000n;
31
+ const anchor_1 = require("@coral-xyz/anchor");
32
+ exports.SESSION_TTL = 60n * 60n * 24n * 30n;
31
33
  exports.ZERO_DECIMAL = new decimal_js_1.Decimal(0);
32
34
  exports.MAX_BUFFER_LEN = 10000;
33
35
  const MAX_PAYLOAD_SIZE = 100 * 1024; // 100 kB
@@ -36,7 +38,7 @@ function panic(message) {
36
38
  }
37
39
  function assert(predicate, message) {
38
40
  if (!predicate)
39
- panic(message ?? "Assertion violated");
41
+ panic(message ?? 'Assertion violated');
40
42
  }
41
43
  /**
42
44
  * Extracts value out of optional if it's defined, or throws error if it's not
@@ -55,7 +57,7 @@ function optExpect(value, message) {
55
57
  * @returns
56
58
  */
57
59
  function optUnwrap(value) {
58
- return optExpect(value, "Optional contains no value");
60
+ return optExpect(value, 'Optional contains no value');
59
61
  }
60
62
  /**
61
63
  * Applies function to value if it's defined, or passes `undefined` through
@@ -97,7 +99,7 @@ function signAction(action, sk, keyType) {
97
99
  sig = secp256k1_1.secp256k1.sign((0, sha256_1.sha256)(action), sk).toCompactRawBytes();
98
100
  }
99
101
  else {
100
- throw new Error("Invalid key type");
102
+ throw new Error('Invalid key type');
101
103
  }
102
104
  return new Uint8Array([...action, ...sig]);
103
105
  }
@@ -234,13 +236,13 @@ function decodeLengthDelimited(bytes, coder) {
234
236
  }
235
237
  function checkPubKeyLength(keyType, len) {
236
238
  if (keyType === types_1.KeyType.Bls12_381) {
237
- throw new Error("Cannot create a user using Bls12_381, use Ed25119 or Secp256k1 instead.");
239
+ throw new Error('Cannot create a user using Bls12_381, use Ed25119 or Secp256k1 instead.');
238
240
  }
239
241
  if (len !== 32 && keyType === types_1.KeyType.Ed25519) {
240
- throw new Error("Ed25519 pubkeys must be 32 length.");
242
+ throw new Error('Ed25519 pubkeys must be 32 length.');
241
243
  }
242
244
  if (len !== 33 && keyType === types_1.KeyType.Secp256k1) {
243
- throw new Error("Secp256k1 pubkeys must be 33 length.");
245
+ throw new Error('Secp256k1 pubkeys must be 33 length.');
244
246
  }
245
247
  }
246
248
  function findMarket(markets, marketId) {
@@ -255,3 +257,20 @@ function findToken(tokens, tokenId) {
255
257
  }
256
258
  return tokens[tokenId];
257
259
  }
260
+ /**
261
+ * Convert a number to a BN with the specified number of decimals
262
+ *
263
+ * @param amount Amount as a number
264
+ * @param decimals Number of decimal places
265
+ * @returns Amount as a BN
266
+ */
267
+ function toBN(amount, decimals) {
268
+ const amountString = amount.toFixed(decimals);
269
+ const [whole, fraction] = amountString.split('.');
270
+ // Convert to smallest units (no decimals)
271
+ const wholeBN = new anchor_1.BN(whole).mul(new anchor_1.BN(10).pow(new anchor_1.BN(decimals)));
272
+ const fractionBN = fraction
273
+ ? new anchor_1.BN(fraction.padEnd(decimals, '0').slice(0, decimals))
274
+ : new anchor_1.BN(0);
275
+ return wholeBN.add(fractionBN);
276
+ }
@@ -0,0 +1,71 @@
1
+ import { EventEmitter } from 'events';
2
+ import { NordWebSocketClientEvents } from './events';
3
+ /**
4
+ * WebSocket client for Nord exchange
5
+ *
6
+ * This client connects to one of the specific Nord WebSocket endpoints:
7
+ * - /ws/trades - For trade updates
8
+ * - /ws/deltas - For orderbook delta updates
9
+ * - /ws/user - For user-specific updates
10
+ *
11
+ * Each endpoint handles a specific type of data and subscriptions must match
12
+ * the endpoint type (e.g., only 'trades@BTCUSDC' subscriptions are valid on
13
+ * the /ws/trades endpoint).
14
+ */
15
+ export declare class NordWebSocketClient extends EventEmitter implements NordWebSocketClientEvents {
16
+ private ws;
17
+ private url;
18
+ private subscriptions;
19
+ private reconnectAttempts;
20
+ private maxReconnectAttempts;
21
+ private reconnectDelay;
22
+ private pingInterval;
23
+ private pingTimeout;
24
+ private isBrowser;
25
+ /**
26
+ * Create a new NordWebSocketClient
27
+ * @param url WebSocket server URL
28
+ */
29
+ constructor(url: string);
30
+ /**
31
+ * Validate stream format
32
+ * @param stream Stream identifier to validate
33
+ * @throws Error if stream format is invalid
34
+ */
35
+ private validateStream;
36
+ /**
37
+ * Setup WebSocket ping/pong heartbeat
38
+ */
39
+ private setupHeartbeat;
40
+ /**
41
+ * Get the appropriate WebSocket class based on environment
42
+ */
43
+ private getWebSocketClass;
44
+ /**
45
+ * Connect to the Nord WebSocket server
46
+ */
47
+ connect(): void;
48
+ /**
49
+ * Subscribe to one or more streams
50
+ * @param streams Array of streams to subscribe to (e.g. ["trades@BTCUSDC", "deltas@BTCUSDC"])
51
+ */
52
+ subscribe(streams: string[]): void;
53
+ /**
54
+ * Unsubscribe from one or more streams
55
+ * @param streams Array of streams to unsubscribe from
56
+ */
57
+ unsubscribe(streams: string[]): void;
58
+ /**
59
+ * Close the WebSocket connection
60
+ */
61
+ close(): void;
62
+ /**
63
+ * Handle incoming WebSocket messages
64
+ * @param message WebSocket message
65
+ */
66
+ private handleMessage;
67
+ /**
68
+ * Attempt to reconnect to the WebSocket server
69
+ */
70
+ private reconnect;
71
+ }
@@ -0,0 +1,343 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.NordWebSocketClient = void 0;
7
+ const ws_1 = __importDefault(require("ws"));
8
+ const events_1 = require("events");
9
+ const types_1 = require("../types");
10
+ const VALID_STREAM_TYPES = ['trades', 'deltas', 'user'];
11
+ // Constants for WebSocket readyState
12
+ const WS_OPEN = 1;
13
+ /**
14
+ * WebSocket client for Nord exchange
15
+ *
16
+ * This client connects to one of the specific Nord WebSocket endpoints:
17
+ * - /ws/trades - For trade updates
18
+ * - /ws/deltas - For orderbook delta updates
19
+ * - /ws/user - For user-specific updates
20
+ *
21
+ * Each endpoint handles a specific type of data and subscriptions must match
22
+ * the endpoint type (e.g., only 'trades@BTCUSDC' subscriptions are valid on
23
+ * the /ws/trades endpoint).
24
+ */
25
+ class NordWebSocketClient extends events_1.EventEmitter {
26
+ /**
27
+ * Create a new NordWebSocketClient
28
+ * @param url WebSocket server URL
29
+ */
30
+ constructor(url) {
31
+ super();
32
+ this.ws = null;
33
+ this.subscriptions = new Set();
34
+ this.reconnectAttempts = 0;
35
+ this.maxReconnectAttempts = 5;
36
+ this.reconnectDelay = 1000;
37
+ this.pingInterval = null;
38
+ this.pingTimeout = null;
39
+ this.url = url;
40
+ // Check if we're in a browser environment
41
+ // The most reliable way is to check for Node.js process
42
+ this.isBrowser =
43
+ typeof process === 'undefined' ||
44
+ !process.versions ||
45
+ !process.versions.node;
46
+ }
47
+ /**
48
+ * Validate stream format
49
+ * @param stream Stream identifier to validate
50
+ * @throws Error if stream format is invalid
51
+ */
52
+ validateStream(stream) {
53
+ const [type, params] = stream.split('@');
54
+ if (!type || !params) {
55
+ throw new Error(`Invalid stream format: ${stream}. Expected format: <type>@<params>`);
56
+ }
57
+ // Extract the endpoint from the URL
58
+ const urlPath = new URL(this.url).pathname;
59
+ const endpoint = urlPath.split('/').pop();
60
+ // Ensure the stream type matches the endpoint we're connected to
61
+ if (endpoint && type !== endpoint) {
62
+ throw new Error(`Stream type '${type}' doesn't match the connected endpoint '${endpoint}'`);
63
+ }
64
+ if (!VALID_STREAM_TYPES.includes(type)) {
65
+ throw new Error(`Invalid stream type: ${type}. Valid types are: ${VALID_STREAM_TYPES.join(', ')}`);
66
+ }
67
+ if (type === 'user' && !/^\d+$/.test(params)) {
68
+ throw new Error(`Invalid user ID in stream: ${params}. Expected numeric ID`);
69
+ }
70
+ }
71
+ /**
72
+ * Setup WebSocket ping/pong heartbeat
73
+ */
74
+ setupHeartbeat() {
75
+ if (this.pingInterval) {
76
+ clearInterval(this.pingInterval);
77
+ }
78
+ if (this.pingTimeout) {
79
+ clearTimeout(this.pingTimeout);
80
+ }
81
+ // In browser, we rely on the browser's WebSocket implementation to handle ping/pong
82
+ if (this.isBrowser) {
83
+ return;
84
+ }
85
+ this.pingInterval = setInterval(() => {
86
+ if (this.ws && !this.isBrowser) {
87
+ // Only use ping() method in Node.js environment
88
+ this.ws.ping();
89
+ // Set timeout for pong response
90
+ this.pingTimeout = setTimeout(() => {
91
+ this.emit('error', new Error('WebSocket ping timeout'));
92
+ this.close();
93
+ this.reconnect();
94
+ }, 5000); // 5 second timeout
95
+ }
96
+ }, 30000); // Send ping every 30 seconds
97
+ }
98
+ /**
99
+ * Get the appropriate WebSocket class based on environment
100
+ */
101
+ getWebSocketClass() {
102
+ if (this.isBrowser) {
103
+ // In browser environments
104
+ // @ts-ignore - Check for WebSocket in globalThis
105
+ if (typeof globalThis !== 'undefined' && globalThis.WebSocket) {
106
+ // @ts-ignore - Return WebSocket from globalThis
107
+ return globalThis.WebSocket;
108
+ }
109
+ throw new Error('WebSocket is not available in this environment');
110
+ }
111
+ else {
112
+ // In Node.js
113
+ return ws_1.default;
114
+ }
115
+ }
116
+ /**
117
+ * Connect to the Nord WebSocket server
118
+ */
119
+ connect() {
120
+ if (this.ws) {
121
+ return;
122
+ }
123
+ try {
124
+ const WebSocketClass = this.getWebSocketClass();
125
+ if (this.isBrowser) {
126
+ // Browser WebSocket setup
127
+ this.ws = new WebSocketClass(this.url);
128
+ this.ws.onopen = () => {
129
+ this.emit('connected');
130
+ this.reconnectAttempts = 0;
131
+ this.reconnectDelay = 1000;
132
+ // Resubscribe to previous subscriptions
133
+ if (this.subscriptions.size > 0) {
134
+ this.subscribe([...this.subscriptions]);
135
+ }
136
+ };
137
+ this.ws.onmessage = (event) => {
138
+ try {
139
+ const message = JSON.parse(event.data);
140
+ this.handleMessage(message);
141
+ }
142
+ catch (error) {
143
+ this.emit('error', new Error(`Failed to parse message: ${error instanceof Error ? error.message : String(error)}`));
144
+ }
145
+ };
146
+ this.ws.onclose = (event) => {
147
+ const reason = event && event.reason ? ` Reason: ${event.reason}` : '';
148
+ const code = event && event.code ? ` Code: ${event.code}` : '';
149
+ this.emit('disconnected');
150
+ console.log(`WebSocket closed.${code}${reason}`);
151
+ this.reconnect();
152
+ };
153
+ this.ws.onerror = (event) => {
154
+ const errorMsg = `WebSocket error: ${event && event.type ? event.type : 'unknown'}`;
155
+ console.error(errorMsg, event);
156
+ this.emit('error', new Error(errorMsg));
157
+ };
158
+ }
159
+ else {
160
+ // Node.js WebSocket setup
161
+ const nodeWs = new WebSocketClass(this.url);
162
+ this.ws = nodeWs;
163
+ nodeWs.on('open', () => {
164
+ this.emit('connected');
165
+ this.reconnectAttempts = 0;
166
+ this.reconnectDelay = 1000;
167
+ this.setupHeartbeat();
168
+ // Resubscribe to previous subscriptions
169
+ if (this.subscriptions.size > 0) {
170
+ this.subscribe([...this.subscriptions]);
171
+ }
172
+ });
173
+ nodeWs.on('message', (data) => {
174
+ try {
175
+ const message = JSON.parse(data.toString());
176
+ this.handleMessage(message);
177
+ }
178
+ catch (error) {
179
+ this.emit('error', new Error(`Failed to parse message: ${error instanceof Error ? error.message : String(error)}`));
180
+ }
181
+ });
182
+ nodeWs.on('close', (code, reason) => {
183
+ this.emit('disconnected');
184
+ console.log(`WebSocket closed. Code: ${code} Reason: ${reason}`);
185
+ if (this.pingInterval) {
186
+ clearInterval(this.pingInterval);
187
+ }
188
+ if (this.pingTimeout) {
189
+ clearTimeout(this.pingTimeout);
190
+ }
191
+ this.reconnect();
192
+ });
193
+ nodeWs.on('error', (error) => {
194
+ console.error('WebSocket error:', error);
195
+ this.emit('error', error);
196
+ });
197
+ nodeWs.on('pong', () => {
198
+ if (this.pingTimeout) {
199
+ clearTimeout(this.pingTimeout);
200
+ }
201
+ });
202
+ }
203
+ }
204
+ catch (error) {
205
+ const errorMsg = `Failed to initialize WebSocket: ${error instanceof Error ? error.message : String(error)}`;
206
+ console.error(errorMsg);
207
+ this.emit('error', new Error(errorMsg));
208
+ }
209
+ }
210
+ /**
211
+ * Subscribe to one or more streams
212
+ * @param streams Array of streams to subscribe to (e.g. ["trades@BTCUSDC", "deltas@BTCUSDC"])
213
+ */
214
+ subscribe(streams) {
215
+ // Validate all streams first
216
+ try {
217
+ streams.forEach((stream) => this.validateStream(stream));
218
+ }
219
+ catch (error) {
220
+ this.emit('error', error instanceof Error ? error : new Error(String(error)));
221
+ return;
222
+ }
223
+ if (!this.ws ||
224
+ (this.isBrowser
225
+ ? this.ws.readyState !== WS_OPEN
226
+ : this.ws.readyState !== ws_1.default.OPEN)) {
227
+ streams.forEach((stream) => this.subscriptions.add(stream));
228
+ return;
229
+ }
230
+ const message = {
231
+ type: types_1.WebSocketMessageType.Subscribe,
232
+ streams,
233
+ };
234
+ try {
235
+ const messageStr = JSON.stringify(message);
236
+ if (this.isBrowser) {
237
+ this.ws.send(messageStr);
238
+ }
239
+ else {
240
+ this.ws.send(messageStr);
241
+ }
242
+ streams.forEach((stream) => this.subscriptions.add(stream));
243
+ }
244
+ catch (error) {
245
+ this.emit('error', error instanceof Error ? error : new Error(String(error)));
246
+ }
247
+ }
248
+ /**
249
+ * Unsubscribe from one or more streams
250
+ * @param streams Array of streams to unsubscribe from
251
+ */
252
+ unsubscribe(streams) {
253
+ // Validate all streams first
254
+ try {
255
+ streams.forEach((stream) => this.validateStream(stream));
256
+ }
257
+ catch (error) {
258
+ this.emit('error', error instanceof Error ? error : new Error(String(error)));
259
+ return;
260
+ }
261
+ if (!this.ws ||
262
+ (this.isBrowser
263
+ ? this.ws.readyState !== WS_OPEN
264
+ : this.ws.readyState !== ws_1.default.OPEN)) {
265
+ streams.forEach((stream) => this.subscriptions.delete(stream));
266
+ return;
267
+ }
268
+ const message = {
269
+ type: types_1.WebSocketMessageType.Unsubscribe,
270
+ streams,
271
+ };
272
+ try {
273
+ const messageStr = JSON.stringify(message);
274
+ if (this.isBrowser) {
275
+ this.ws.send(messageStr);
276
+ }
277
+ else {
278
+ this.ws.send(messageStr);
279
+ }
280
+ streams.forEach((stream) => this.subscriptions.delete(stream));
281
+ }
282
+ catch (error) {
283
+ this.emit('error', error instanceof Error ? error : new Error(String(error)));
284
+ }
285
+ }
286
+ /**
287
+ * Close the WebSocket connection
288
+ */
289
+ close() {
290
+ if (this.ws) {
291
+ if (this.isBrowser) {
292
+ this.ws.close();
293
+ }
294
+ else {
295
+ this.ws.close();
296
+ }
297
+ this.ws = null;
298
+ }
299
+ if (this.pingInterval) {
300
+ clearInterval(this.pingInterval);
301
+ this.pingInterval = null;
302
+ }
303
+ if (this.pingTimeout) {
304
+ clearTimeout(this.pingTimeout);
305
+ this.pingTimeout = null;
306
+ }
307
+ this.subscriptions.clear();
308
+ }
309
+ /**
310
+ * Handle incoming WebSocket messages
311
+ * @param message WebSocket message
312
+ */
313
+ handleMessage(message) {
314
+ switch (message.type) {
315
+ case types_1.WebSocketMessageType.TradeUpdate:
316
+ this.emit('trade', message);
317
+ break;
318
+ case types_1.WebSocketMessageType.DeltaUpdate:
319
+ this.emit('delta', message);
320
+ break;
321
+ case types_1.WebSocketMessageType.UserUpdate:
322
+ this.emit('user', message);
323
+ break;
324
+ default:
325
+ this.emit('error', new Error(`Unknown message type: ${message.type}`));
326
+ }
327
+ }
328
+ /**
329
+ * Attempt to reconnect to the WebSocket server
330
+ */
331
+ reconnect() {
332
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
333
+ this.emit('error', new Error('Max reconnection attempts reached'));
334
+ return;
335
+ }
336
+ setTimeout(() => {
337
+ this.reconnectAttempts++;
338
+ this.reconnectDelay *= 2; // Exponential backoff
339
+ this.connect();
340
+ }, this.reconnectDelay);
341
+ }
342
+ }
343
+ exports.NordWebSocketClient = NordWebSocketClient;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * WebSocket client for real-time data
3
+ * @module websocket/client
4
+ */
5
+ import { OrderbookData, Trade } from '../models';
6
+ /**
7
+ * WebSocket client for real-time data
8
+ */
9
+ export declare class WebSocketClient {
10
+ /**
11
+ * WebSocket connection
12
+ */
13
+ private ws;
14
+ /**
15
+ * Base URL for the WebSocket server
16
+ */
17
+ private readonly baseUrl;
18
+ /**
19
+ * Map of subscription IDs to callbacks
20
+ */
21
+ private subscriptions;
22
+ /**
23
+ * Whether the WebSocket is connected
24
+ */
25
+ private connected;
26
+ /**
27
+ * Whether the WebSocket is connecting
28
+ */
29
+ private connecting;
30
+ /**
31
+ * Queue of messages to send when connected
32
+ */
33
+ private messageQueue;
34
+ /**
35
+ * Create a new WebSocket client
36
+ *
37
+ * @param baseUrl - Base URL for the WebSocket server
38
+ */
39
+ constructor(baseUrl: string);
40
+ /**
41
+ * Connect to the WebSocket server
42
+ *
43
+ * @returns Promise that resolves when connected
44
+ */
45
+ connect(): Promise<void>;
46
+ /**
47
+ * Disconnect from the WebSocket server
48
+ */
49
+ disconnect(): void;
50
+ /**
51
+ * Send a message to the WebSocket server
52
+ *
53
+ * @param message - Message to send
54
+ */
55
+ private sendMessage;
56
+ /**
57
+ * Generate a unique subscription ID
58
+ *
59
+ * @returns Unique subscription ID
60
+ */
61
+ private generateSubscriptionId;
62
+ /**
63
+ * Subscribe to orderbook updates for a specific market
64
+ *
65
+ * @param market - Market symbol (e.g., "BTCUSDC")
66
+ * @param callback - Callback function to handle updates
67
+ * @returns Subscription ID
68
+ */
69
+ subscribeToOrderbook(market: string, callback: (data: OrderbookData) => void): string;
70
+ /**
71
+ * Subscribe to trade updates for a specific market
72
+ *
73
+ * @param market - Market symbol (e.g., "BTCUSDC")
74
+ * @param callback - Callback function to handle updates
75
+ * @returns Subscription ID
76
+ */
77
+ subscribeToTrades(market: string, callback: (data: Trade[]) => void): string;
78
+ /**
79
+ * Subscribe to user updates for a specific account
80
+ *
81
+ * @param accountId - Account ID
82
+ * @param callback - Callback function to handle updates
83
+ * @returns Subscription ID
84
+ */
85
+ subscribeToUserUpdates(accountId: number, callback: (data: any) => void): string;
86
+ /**
87
+ * Unsubscribe from a subscription
88
+ *
89
+ * @param subscriptionId - Subscription ID to unsubscribe from
90
+ * @returns Whether the unsubscription was successful
91
+ */
92
+ unsubscribe(subscriptionId: string): boolean;
93
+ }