@n1xyz/nord-ts 0.0.12 → 0.0.14

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/types.d.ts CHANGED
@@ -15,6 +15,16 @@ export declare enum PeakTpsPeriodUnit {
15
15
  Month = "m",
16
16
  Year = "y"
17
17
  }
18
+ /**
19
+ * Nord subscription type for trades or deltas
20
+ */
21
+ export type SubscriptionType = 'trades' | 'deltas' | 'account';
22
+ /**
23
+ * Pattern for a valid Nord subscription
24
+ * Format should be: "<type>@<parameter>"
25
+ * Examples: "trades@BTCUSDC", "deltas@ETHUSDC", "account@42"
26
+ */
27
+ export type SubscriptionPattern = `${SubscriptionType}@${string}` | string;
18
28
  /**
19
29
  * Configuration options for the Nord client
20
30
  */
@@ -27,11 +37,24 @@ export interface NordConfig {
27
37
  solanaUrl: string;
28
38
  /** Whether to initialize WebSockets on creation, defaults to true */
29
39
  initWebSockets?: boolean;
30
- /** Initial subscriptions for the trades WebSocket (e.g., ["trades@BTCUSDC"]) */
31
- tradesSubscriptions?: string[];
32
- /** Initial subscriptions for the deltas WebSocket (e.g., ["deltas@BTCUSDC"]) */
33
- deltasSubscriptions?: string[];
40
+ /**
41
+ * Initial subscriptions for the trades WebSocket
42
+ * Supports both formats:
43
+ * - Legacy format: ["BTCUSDC", "ETHUSDC"]
44
+ * - New format: ["trades@BTCUSDC", "trades@ETHUSDC"]
45
+ */
46
+ tradesSubscriptions?: SubscriptionPattern[];
47
+ /**
48
+ * Initial subscriptions for the deltas WebSocket
49
+ * Supports both formats:
50
+ * - Legacy format: ["BTCUSDC", "ETHUSDC"]
51
+ * - New format: ["deltas@BTCUSDC", "deltas@ETHUSDC"]
52
+ */
53
+ deltasSubscriptions?: SubscriptionPattern[];
34
54
  }
55
+ /**
56
+ * Configuration options for the Nord client
57
+ */
35
58
  export interface TokenInfo {
36
59
  address: string;
37
60
  precision: number;
@@ -355,22 +378,22 @@ export interface ActionNonceResponse {
355
378
  export declare enum WebSocketMessageType {
356
379
  Subscribe = "subscribe",
357
380
  Unsubscribe = "unsubscribe",
358
- TradeUpdate = "trade",
381
+ TradeUpdate = "trades",
359
382
  DeltaUpdate = "delta",
360
- UserUpdate = "user"
383
+ AccountUpdate = "account"
361
384
  }
362
385
  /**
363
386
  * WebSocket subscription request
364
387
  */
365
388
  export interface WebSocketSubscription {
366
- type: WebSocketMessageType;
389
+ e: WebSocketMessageType;
367
390
  streams: string[];
368
391
  }
369
392
  /**
370
393
  * WebSocket trade update message
371
394
  */
372
395
  export interface WebSocketTradeUpdate {
373
- type: WebSocketMessageType.TradeUpdate;
396
+ e: WebSocketMessageType.TradeUpdate;
374
397
  symbol: string;
375
398
  trades: Trade[];
376
399
  timestamp: number;
@@ -379,7 +402,7 @@ export interface WebSocketTradeUpdate {
379
402
  * WebSocket delta update message
380
403
  */
381
404
  export interface WebSocketDeltaUpdate {
382
- type: WebSocketMessageType.DeltaUpdate;
405
+ e: WebSocketMessageType.DeltaUpdate;
383
406
  last_update_id: number;
384
407
  update_id: number;
385
408
  market_symbol: string;
@@ -390,11 +413,11 @@ export interface WebSocketDeltaUpdate {
390
413
  /**
391
414
  * WebSocket user update message
392
415
  */
393
- export interface WebSocketUserUpdate {
394
- type: WebSocketMessageType.UserUpdate;
395
- userId: number;
416
+ export interface WebSocketAccountUpdate {
417
+ e: WebSocketMessageType.AccountUpdate;
418
+ accountId: number;
396
419
  account: Account;
397
420
  timestamp: number;
398
421
  }
399
- export type WebSocketMessage = WebSocketSubscription | WebSocketTradeUpdate | WebSocketDeltaUpdate | WebSocketUserUpdate;
422
+ export type WebSocketMessage = WebSocketSubscription | WebSocketTradeUpdate | WebSocketDeltaUpdate | WebSocketAccountUpdate;
400
423
  export {};
package/dist/types.js CHANGED
@@ -97,7 +97,7 @@ var WebSocketMessageType;
97
97
  (function (WebSocketMessageType) {
98
98
  WebSocketMessageType["Subscribe"] = "subscribe";
99
99
  WebSocketMessageType["Unsubscribe"] = "unsubscribe";
100
- WebSocketMessageType["TradeUpdate"] = "trade";
100
+ WebSocketMessageType["TradeUpdate"] = "trades";
101
101
  WebSocketMessageType["DeltaUpdate"] = "delta";
102
- WebSocketMessageType["UserUpdate"] = "user";
102
+ WebSocketMessageType["AccountUpdate"] = "account";
103
103
  })(WebSocketMessageType || (exports.WebSocketMessageType = WebSocketMessageType = {}));
@@ -4,9 +4,6 @@ import { NordWebSocketClientEvents } from "./events";
4
4
  * WebSocket client for Nord exchange
5
5
  *
6
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
7
  *
11
8
  * Each endpoint handles a specific type of data and subscriptions must match
12
9
  * the endpoint type (e.g., only 'trades@BTCUSDC' subscriptions are valid on
@@ -7,16 +7,13 @@ exports.NordWebSocketClient = void 0;
7
7
  const ws_1 = __importDefault(require("ws"));
8
8
  const events_1 = require("events");
9
9
  const types_1 = require("../types");
10
- const VALID_STREAM_TYPES = ["trades", "deltas", "user"];
10
+ const VALID_STREAM_TYPES = ["trades", "delta", "account"];
11
11
  // Constants for WebSocket readyState
12
12
  const WS_OPEN = 1;
13
13
  /**
14
14
  * WebSocket client for Nord exchange
15
15
  *
16
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
17
  *
21
18
  * Each endpoint handles a specific type of data and subscriptions must match
22
19
  * the endpoint type (e.g., only 'trades@BTCUSDC' subscriptions are valid on
@@ -64,8 +61,8 @@ class NordWebSocketClient extends events_1.EventEmitter {
64
61
  if (!VALID_STREAM_TYPES.includes(type)) {
65
62
  throw new Error(`Invalid stream type: ${type}. Valid types are: ${VALID_STREAM_TYPES.join(", ")}`);
66
63
  }
67
- if (type === "user" && !/^\d+$/.test(params)) {
68
- throw new Error(`Invalid user ID in stream: ${params}. Expected numeric ID`);
64
+ if (type === "account" && !/^\d+$/.test(params)) {
65
+ throw new Error(`Invalid account ID in stream: ${params}. Expected numeric ID`);
69
66
  }
70
67
  }
71
68
  /**
@@ -228,7 +225,7 @@ class NordWebSocketClient extends events_1.EventEmitter {
228
225
  return;
229
226
  }
230
227
  const message = {
231
- type: types_1.WebSocketMessageType.Subscribe,
228
+ e: types_1.WebSocketMessageType.Subscribe,
232
229
  streams,
233
230
  };
234
231
  try {
@@ -266,7 +263,7 @@ class NordWebSocketClient extends events_1.EventEmitter {
266
263
  return;
267
264
  }
268
265
  const message = {
269
- type: types_1.WebSocketMessageType.Unsubscribe,
266
+ e: types_1.WebSocketMessageType.Unsubscribe,
270
267
  streams,
271
268
  };
272
269
  try {
@@ -311,18 +308,18 @@ class NordWebSocketClient extends events_1.EventEmitter {
311
308
  * @param message WebSocket message
312
309
  */
313
310
  handleMessage(message) {
314
- switch (message.type) {
311
+ switch (message.e) {
315
312
  case types_1.WebSocketMessageType.TradeUpdate:
316
- this.emit("trade", message);
313
+ this.emit("trades", message);
317
314
  break;
318
315
  case types_1.WebSocketMessageType.DeltaUpdate:
319
316
  this.emit("delta", message);
320
317
  break;
321
- case types_1.WebSocketMessageType.UserUpdate:
322
- this.emit("user", message);
318
+ case types_1.WebSocketMessageType.AccountUpdate:
319
+ this.emit("account", message);
323
320
  break;
324
321
  default:
325
- this.emit("error", new Error(`Unknown message type: ${message.type}`));
322
+ this.emit("error", new Error(`Unknown message type: ${message.e}`));
326
323
  }
327
324
  }
328
325
  /**
@@ -1,4 +1,4 @@
1
- import { WebSocketTradeUpdate, WebSocketDeltaUpdate, WebSocketUserUpdate } from "../types";
1
+ import { WebSocketTradeUpdate, WebSocketDeltaUpdate, WebSocketAccountUpdate } from "../types";
2
2
  /**
3
3
  * Event type definitions for the NordWebSocketClient
4
4
  */
@@ -8,7 +8,7 @@ export interface NordWebSocketEvents {
8
8
  error: (error: Error) => void;
9
9
  trade: (update: WebSocketTradeUpdate) => void;
10
10
  delta: (update: WebSocketDeltaUpdate) => void;
11
- user: (update: WebSocketUserUpdate) => void;
11
+ account: (update: WebSocketAccountUpdate) => void;
12
12
  }
13
13
  /**
14
14
  * Type declaration for NordWebSocketClient event methods
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@n1xyz/nord-ts",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "description": "Typescript for Nord",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/idl/bridge.ts CHANGED
@@ -927,4 +927,3 @@ export const BRIDGE_IDL: Idl = {
927
927
  },
928
928
  ],
929
929
  };
930
-
@@ -1,4 +1,4 @@
1
- import { Account, Info } from "../../types";
1
+ import { Account, Info, SubscriptionPattern } from "../../types";
2
2
  import { checkedFetch } from "../../utils";
3
3
  import { NordWebSocketClient } from "../../websocket/index";
4
4
  import { NordError } from "../utils/NordError";
@@ -77,27 +77,55 @@ export async function getAccount(
77
77
  /**
78
78
  * Initialize a WebSocket client for Nord
79
79
  *
80
- * Connects to one of the specific Nord WebSocket endpoints:
81
- * - /ws/trades - For trade updates (default)
82
- * - /ws/deltas - For orderbook delta updates
83
- * - /ws/user - For user-specific updates
80
+ * Connects to the Nord WebSocket endpoint with support for multiple subscription types:
81
+ * - trades@SYMBOL - For trade updates
82
+ * - deltas@SYMBOL - For orderbook delta updates
83
+ * - account@ACCOUNT_ID - For user-specific updates
84
84
  *
85
85
  * @param webServerUrl - Base URL for the Nord web server
86
- * @param endpoint - Specific WebSocket endpoint to connect to (trades, deltas, or user)
87
- * @param initialSubscriptions - Optional array of initial subscriptions (e.g., ["trades@BTCUSDC"])
86
+ * @param subscriptions - Array of subscriptions (e.g., ["trades@BTCUSDC", "deltas@BTCUSDC", "account@42"])
88
87
  * @returns WebSocket client
88
+ * @throws {NordError} If initialization fails or invalid subscription is provided
89
89
  */
90
90
  export function initWebSocketClient(
91
91
  webServerUrl: string,
92
- endpoint?: "trades" | "deltas" | "user",
93
- initialSubscriptions?: string[],
92
+ subscriptions?: SubscriptionPattern[] | "trades" | "delta" | "account",
93
+ initialSubscriptions?: SubscriptionPattern[],
94
94
  ): NordWebSocketClient {
95
95
  try {
96
- // Convert HTTP URL to WebSocket URL with specific endpoint
97
- // If no specific endpoint is provided, we'll connect to trades by default
98
- const specificEndpoint = endpoint || "trades";
99
- const wsUrl =
100
- webServerUrl.replace(/^http/, "ws") + `/ws/${specificEndpoint}`;
96
+ // Determine URL and subscriptions based on parameters
97
+ let wsUrl = webServerUrl.replace(/^http/, "ws") + `/ws`;
98
+ let wsSubscriptions: SubscriptionPattern[] = [];
99
+
100
+ // Validate subscriptions parameter
101
+ if (typeof subscriptions === "string") {
102
+ // Legacy mode - handle endpoint string
103
+ if (
104
+ subscriptions === "trades" ||
105
+ subscriptions === "delta" ||
106
+ subscriptions === "account"
107
+ ) {
108
+ wsUrl += `/${subscriptions}`;
109
+ // If initialSubscriptions provided, use them
110
+ if (initialSubscriptions && initialSubscriptions.length > 0) {
111
+ // Validate initialSubscriptions
112
+ initialSubscriptions.forEach(validateSubscription);
113
+ wsSubscriptions = initialSubscriptions;
114
+ }
115
+ } else {
116
+ throw new NordError(
117
+ `Invalid endpoint: ${subscriptions}. Must be "trades", "deltas", or "account".`,
118
+ );
119
+ }
120
+ } else if (Array.isArray(subscriptions) && subscriptions.length > 0) {
121
+ // New mode - validate and combine subscriptions in URL
122
+ subscriptions.forEach(validateSubscription);
123
+ wsUrl += `/${subscriptions.join("&")}`;
124
+ } else {
125
+ // Default to trades endpoint if no subscriptions specified
126
+ wsUrl += `/trades`;
127
+ }
128
+
101
129
  console.log(`Initializing WebSocket client with URL: ${wsUrl}`);
102
130
 
103
131
  // Create and connect the WebSocket client
@@ -112,9 +140,10 @@ export function initWebSocketClient(
112
140
  ws.on("connected", () => {
113
141
  console.log("Nord WebSocket connected successfully");
114
142
 
115
- // Subscribe to initial subscriptions if provided
116
- if (initialSubscriptions && initialSubscriptions.length > 0) {
117
- ws.subscribe(initialSubscriptions);
143
+ // Subscribe to additional subscriptions if provided
144
+ // For new format, these are already part of the URL
145
+ if (wsSubscriptions.length > 0) {
146
+ ws.subscribe(wsSubscriptions);
118
147
  }
119
148
  });
120
149
 
@@ -128,3 +157,26 @@ export function initWebSocketClient(
128
157
  });
129
158
  }
130
159
  }
160
+
161
+ /**
162
+ * Validates a subscription string follows the correct format
163
+ *
164
+ * @param subscription - The subscription to validate
165
+ * @throws {NordError} If the subscription format is invalid
166
+ */
167
+ function validateSubscription(subscription: string): void {
168
+ const [type, param] = subscription.split("@");
169
+
170
+ if (!type || !param || !["trades", "deltas", "account"].includes(type)) {
171
+ throw new NordError(
172
+ `Invalid subscription format: ${subscription}. Expected format: "trades@SYMBOL", "deltas@SYMBOL", or "account@ID"`,
173
+ );
174
+ }
175
+
176
+ // Additional validation for account subscriptions
177
+ if (type === "account" && isNaN(Number(param))) {
178
+ throw new NordError(
179
+ `Invalid account ID in subscription: ${subscription}. Account ID must be a number.`,
180
+ );
181
+ }
182
+ }
@@ -33,19 +33,19 @@ export async function aggregateMetrics(
33
33
  const response = await checkedFetch(
34
34
  `${webServerUrl}/metrics?tx_peak_tps_period=${txPeakTpsPeriod}&tx_peak_tps_period_unit=${txPeakTpsPeriodUnit}`,
35
35
  );
36
-
36
+
37
37
  // Get the raw text response (Prometheus format)
38
38
  const text = await response.text();
39
-
39
+
40
40
  // Parse the Prometheus-formatted metrics text into an AggregateMetrics object
41
41
  const metrics: AggregateMetrics = {
42
42
  blocks_total: 0,
43
43
  tx_total: extractMetricValue(text, "nord_requests_ok_count"),
44
44
  tx_tps: calculateTps(text),
45
45
  tx_tps_peak: calculatePeakTps(text),
46
- request_latency_average: extractLatency(text)
46
+ request_latency_average: extractLatency(text),
47
47
  };
48
-
48
+
49
49
  return metrics;
50
50
  } catch (error) {
51
51
  throw new NordError("Failed to fetch aggregate metrics", { cause: error });
@@ -54,20 +54,20 @@ export async function aggregateMetrics(
54
54
 
55
55
  /**
56
56
  * Extract a metric value from Prometheus-formatted text
57
- *
57
+ *
58
58
  * @param text - Prometheus-formatted metrics text
59
59
  * @param metricName - Name of the metric to extract
60
60
  * @returns The metric value as a number, or 0 if not found
61
61
  */
62
62
  function extractMetricValue(text: string, metricName: string): number {
63
- const regex = new RegExp(`^${metricName}\\s+([\\d.]+)`, 'm');
63
+ const regex = new RegExp(`^${metricName}\\s+([\\d.]+)`, "m");
64
64
  const match = text.match(regex);
65
65
  return match ? parseFloat(match[1]) : 0;
66
66
  }
67
67
 
68
68
  /**
69
69
  * Calculate TPS from Prometheus metrics
70
- *
70
+ *
71
71
  * @param text - Prometheus-formatted metrics text
72
72
  * @returns Calculated TPS value
73
73
  */
@@ -75,22 +75,25 @@ function calculateTps(text: string): number {
75
75
  // Use the request count and latency to estimate TPS
76
76
  const requestCount = extractMetricValue(text, "nord_requests_ok_count");
77
77
  const latencySum = extractSummaryValue(text, "nord_requests_ok_latency_sum");
78
- const latencyCount = extractSummaryValue(text, "nord_requests_ok_latency_count");
79
-
78
+ const latencyCount = extractSummaryValue(
79
+ text,
80
+ "nord_requests_ok_latency_count",
81
+ );
82
+
80
83
  if (latencySum > 0 && latencyCount > 0) {
81
84
  // Average latency in seconds
82
85
  const avgLatency = latencySum / latencyCount;
83
86
  // If we have valid latency data, estimate TPS as requests per second
84
87
  return avgLatency > 0 ? requestCount / (latencyCount * avgLatency) : 0;
85
88
  }
86
-
89
+
87
90
  // Fallback: just return a small fraction of the total request count
88
91
  return requestCount > 0 ? requestCount / 100 : 0;
89
92
  }
90
93
 
91
94
  /**
92
95
  * Calculate peak TPS from Prometheus metrics
93
- *
96
+ *
94
97
  * @param text - Prometheus-formatted metrics text
95
98
  * @returns Calculated peak TPS value
96
99
  */
@@ -101,26 +104,29 @@ function calculatePeakTps(text: string): number {
101
104
 
102
105
  /**
103
106
  * Extract latency from Prometheus metrics
104
- *
107
+ *
105
108
  * @param text - Prometheus-formatted metrics text
106
109
  * @returns Average latency in seconds
107
110
  */
108
111
  function extractLatency(text: string): number {
109
112
  const latencySum = extractSummaryValue(text, "nord_requests_ok_latency_sum");
110
- const latencyCount = extractSummaryValue(text, "nord_requests_ok_latency_count");
111
-
113
+ const latencyCount = extractSummaryValue(
114
+ text,
115
+ "nord_requests_ok_latency_count",
116
+ );
117
+
112
118
  return latencyCount > 0 ? latencySum / latencyCount : 0;
113
119
  }
114
120
 
115
121
  /**
116
122
  * Extract a summary value from Prometheus-formatted text
117
- *
123
+ *
118
124
  * @param text - Prometheus-formatted metrics text
119
125
  * @param metricName - Name of the metric to extract
120
126
  * @returns The metric value as a number, or 0 if not found
121
127
  */
122
128
  function extractSummaryValue(text: string, metricName: string): number {
123
- const regex = new RegExp(`^${metricName}\\s+([\\d.]+)`, 'm');
129
+ const regex = new RegExp(`^${metricName}\\s+([\\d.]+)`, "m");
124
130
  const match = text.match(regex);
125
131
  return match ? parseFloat(match[1]) : 0;
126
132
  }
@@ -235,21 +241,21 @@ export async function queryPrometheus(
235
241
  const response = await checkedFetch(
236
242
  `${webServerUrl}/prometheus?query=${encodeURIComponent(params)}`,
237
243
  );
238
-
244
+
239
245
  // Handle raw text response
240
246
  const text = await response.text();
241
247
  try {
242
248
  // Try to parse as JSON first
243
249
  const data = JSON.parse(text);
244
250
  return data.data.result[0]?.value[1] || 0;
245
- } catch (e) {
246
-
251
+ } catch (error) {
252
+ console.log("Prometheus query failed:", error);
247
253
  // Try to find a number in the response
248
254
  const numberMatch = text.match(/[\d.]+/);
249
255
  if (numberMatch) {
250
256
  return parseFloat(numberMatch[0]);
251
257
  }
252
-
258
+
253
259
  // Return 0 if no number is found
254
260
  return 0;
255
261
  }
@@ -2,17 +2,12 @@ import {
2
2
  ActionQuery,
3
3
  ActionResponse,
4
4
  ActionsResponse,
5
- BlockQuery,
6
- BlockResponse,
7
- BlockSummaryResponse,
8
5
  RollmanActionResponse,
9
6
  RollmanActionsResponse,
10
- RollmanBlockResponse,
11
7
  } from "../../types";
12
8
  import { checkedFetch } from "../../utils";
13
9
  import { NordError } from "../utils/NordError";
14
10
 
15
-
16
11
  /**
17
12
  * Query a specific action
18
13
  *
@@ -60,9 +55,12 @@ export async function queryRecentActions(
60
55
  );
61
56
  return await response.json();
62
57
  } catch (error) {
63
- throw new NordError(`Failed to query recent actions (from ${from} to ${to})`, {
64
- cause: error,
65
- });
58
+ throw new NordError(
59
+ `Failed to query recent actions (from ${from} to ${to})`,
60
+ {
61
+ cause: error,
62
+ },
63
+ );
66
64
  }
67
65
  }
68
66
 
@@ -73,9 +71,7 @@ export async function queryRecentActions(
73
71
  * @returns Last action ID
74
72
  * @throws {NordError} If the request fails
75
73
  */
76
- export async function getLastActionId(
77
- webServerUrl: string,
78
- ): Promise<number> {
74
+ export async function getLastActionId(webServerUrl: string): Promise<number> {
79
75
  try {
80
76
  const response = await checkedFetch(`${webServerUrl}/last_actionid`);
81
77
  const data = await response.json();