@pythnetwork/pyth-lazer-sdk 7.0.0 → 7.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/README.md CHANGED
@@ -25,8 +25,8 @@ const client = await PythLazerClient.create({
25
25
 
26
26
  // Optional.
27
27
  // Override the stream endpoint(s). Round-robined across `numConnections`.
28
- // Defaults to the api service instances (pyth-0/1/2.dourolabs.app).
29
- // urls: ["wss://pyth-0.dourolabs.app/v1/prices/stream"],
28
+ // Defaults to the redundant router stream instances (pyth-lazer-0/1/2).
29
+ // urls: ["wss://pyth-lazer-0.dourolabs.app/v1/stream"],
30
30
  onWebSocketError: (error) => {
31
31
  console.error("⛔️ WebSocket error:", error.message);
32
32
  },
@@ -74,15 +74,14 @@ browser and in Node. It is never placed in the URL as a query parameter, so the
74
74
  token stays out of URLs, browser history, and proxy/server access logs — worth
75
75
  knowing if you care about credential hygiene.
76
76
 
77
- Connections default to the redundant Lazer api service stream instances
78
- (`wss://pyth-{0,1,2}.dourolabs.app/v1/prices/stream`), round-robined across
77
+ Connections default to the redundant Lazer router stream instances
78
+ (`wss://pyth-lazer-{0,1,2}.dourolabs.app/v1/stream`), round-robined across
79
79
  `numConnections` (override via `webSocketPoolConfig.urls`). They serve parsed /
80
- unsigned JSON updates; signed `evm` / `solana` update payloads are not served on
81
- this stream.
80
+ unsigned JSON updates; signed `evm` / `solana` update payloads require the
81
+ router stream with signature support.
82
82
 
83
83
  > **v7 breaking change:** the token moved from `?ACCESS_TOKEN=` to
84
- > `Sec-WebSocket-Protocol`, and the default stream moved from the router to the
85
- > api service. See [CHANGELOG.md](CHANGELOG.md).
84
+ > `Sec-WebSocket-Protocol`. See [CHANGELOG.md](CHANGELOG.md).
86
85
 
87
86
  ## Error callbacks and outage detection
88
87
 
@@ -113,7 +113,17 @@ class PythLazerClient {
113
113
  pos += UINT16_NUM_BYTES;
114
114
  const magic = buffData.subarray(pos, pos + UINT32_NUM_BYTES).readUint32LE();
115
115
  if (magic === _protocol.FORMAT_MAGICS_LE.EVM) {
116
- value.evm = buffData.subarray(pos, pos + len);
116
+ // `evm` and `packedEvmEcdsa` messages share the EVM envelope and its
117
+ // magic. Only the magic at the start of the payload tells them apart.
118
+ const message = buffData.subarray(pos, pos + len);
119
+ const payloadMagic = readPayloadMagicBE(message.subarray(71));
120
+ if (payloadMagic === _protocol.PACKED_EVM_PAYLOAD_FORMAT_MAGIC_BE) {
121
+ value.packedEvmEcdsa = message;
122
+ } else if (payloadMagic === _protocol.PAYLOAD_FORMAT_MAGIC_BE) {
123
+ value.evm = message;
124
+ } else {
125
+ throw new Error(`unknown payload magic: ${payloadMagic.toString()}`);
126
+ }
117
127
  } else if (magic === _protocol.FORMAT_MAGICS_LE.SOLANA) {
118
128
  value.solana = buffData.subarray(pos, pos + len);
119
129
  } else if (magic === _protocol.FORMAT_MAGICS_LE.LE_ECDSA) {
@@ -264,3 +274,9 @@ class PythLazerClient {
264
274
  }
265
275
  }
266
276
  }
277
+ function readPayloadMagicBE(payload) {
278
+ if (payload.length < UINT32_NUM_BYTES) {
279
+ throw new Error(`payload too short to hold a magic: ${payload.length.toString()} bytes`);
280
+ }
281
+ return payload.readUint32BE(0);
282
+ }
@@ -8,6 +8,7 @@ export type BinaryResponse = {
8
8
  parsed?: ParsedPayload | undefined;
9
9
  leEcdsa?: Buffer | undefined;
10
10
  leUnsigned?: Buffer | undefined;
11
+ packedEvmEcdsa?: Buffer | undefined;
11
12
  };
12
13
  export type JsonOrBinaryResponse = {
13
14
  type: "json";
@@ -33,8 +33,8 @@ const SOLANA_LAZER_STORAGE_ID = "3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL";
33
33
  const DEFAULT_METADATA_SERVICE_URL = "https://pyth.dourolabs.app";
34
34
  const DEFAULT_PRICE_SERVICE_URL = "https://pyth-lazer-0.dourolabs.app";
35
35
  const DEFAULT_STREAM_SERVICE_URLS = [
36
- "wss://pyth-0.dourolabs.app/v1/prices/stream",
37
- "wss://pyth-1.dourolabs.app/v1/prices/stream",
38
- "wss://pyth-2.dourolabs.app/v1/prices/stream"
36
+ "wss://pyth-lazer-0.dourolabs.app/v1/stream",
37
+ "wss://pyth-lazer-1.dourolabs.app/v1/stream",
38
+ "wss://pyth-lazer-2.dourolabs.app/v1/stream"
39
39
  ];
40
40
  const AUTH_SUBPROTOCOL = "pyth-lazer-auth";
@@ -4,8 +4,8 @@ export declare const DEFAULT_METADATA_SERVICE_URL = "https://pyth.dourolabs.app"
4
4
  export declare const DEFAULT_PRICE_SERVICE_URL = "https://pyth-lazer-0.dourolabs.app";
5
5
  /**
6
6
  * Default stream endpoints, round-robined across the pool's connections when
7
- * the caller does not supply their own `urls`. These are redundant instances
8
- * of the Lazer api-service stream.
7
+ * the caller does not supply their own `urls`. These are redundant router
8
+ * instances.
9
9
  */
10
10
  export declare const DEFAULT_STREAM_SERVICE_URLS: string[];
11
11
  /**
@@ -17,6 +17,12 @@ _export(exports, {
17
17
  },
18
18
  get FORMAT_MAGICS_LE () {
19
19
  return FORMAT_MAGICS_LE;
20
+ },
21
+ get PACKED_EVM_PAYLOAD_FORMAT_MAGIC_BE () {
22
+ return PACKED_EVM_PAYLOAD_FORMAT_MAGIC_BE;
23
+ },
24
+ get PAYLOAD_FORMAT_MAGIC_BE () {
25
+ return PAYLOAD_FORMAT_MAGIC_BE;
20
26
  }
21
27
  });
22
28
  const BINARY_UPDATE_FORMAT_MAGIC_LE = 461_928_307;
@@ -27,6 +33,8 @@ const FORMAT_MAGICS_LE = {
27
33
  LE_UNSIGNED: 1_499_680_012,
28
34
  SOLANA: 2_182_742_457
29
35
  };
36
+ const PAYLOAD_FORMAT_MAGIC_BE = 2_479_346_549;
37
+ const PACKED_EVM_PAYLOAD_FORMAT_MAGIC_BE = 1_128_617_549;
30
38
  var CustomSocketClosureCodes = /*#__PURE__*/ function(CustomSocketClosureCodes) {
31
39
  CustomSocketClosureCodes[CustomSocketClosureCodes["CLIENT_TIMEOUT_BUT_RECONNECTING"] = 4000] = "CLIENT_TIMEOUT_BUT_RECONNECTING";
32
40
  return CustomSocketClosureCodes;
@@ -1,4 +1,4 @@
1
- export type Format = "evm" | "solana" | "leEcdsa" | "leUnsigned";
1
+ export type Format = "evm" | "solana" | "leEcdsa" | "leUnsigned" | "packedEvmEcdsa";
2
2
  export type DeliveryFormat = "json" | "binary";
3
3
  export type JsonBinaryEncoding = "base64" | "hex";
4
4
  export type PriceFeedProperty = "price" | "bestBidPrice" | "bestAskPrice" | "exponent" | "publisherCount" | "confidence" | "fundingRate" | "fundingTimestamp" | "fundingRateInterval" | "marketSession" | "emaPrice" | "emaConfidence" | "feedUpdateTimestamp";
@@ -8,7 +8,8 @@ export type Request = {
8
8
  subscriptionId: number;
9
9
  priceFeedIds?: number[] | undefined;
10
10
  symbols?: string[] | undefined;
11
- properties: PriceFeedProperty[];
11
+ /** Required unless `formats` is `["packedEvmEcdsa"]`, which fixes the properties itself and accepts none. */
12
+ properties?: PriceFeedProperty[] | undefined;
12
13
  formats: Format[];
13
14
  deliveryFormat?: DeliveryFormat | undefined;
14
15
  jsonBinaryEncoding?: JsonBinaryEncoding | undefined;
@@ -75,6 +76,7 @@ export type Response = {
75
76
  solana?: JsonBinaryData | undefined;
76
77
  leEcdsa?: JsonBinaryData | undefined;
77
78
  leUnsigned?: JsonBinaryData | undefined;
79
+ packedEvmEcdsa?: JsonBinaryData | undefined;
78
80
  };
79
81
  export declare const BINARY_UPDATE_FORMAT_MAGIC_LE = 461928307;
80
82
  export declare const FORMAT_MAGICS_LE: {
@@ -84,6 +86,10 @@ export declare const FORMAT_MAGICS_LE: {
84
86
  LE_UNSIGNED: number;
85
87
  SOLANA: number;
86
88
  };
89
+ /** The magic at the start of a standard payload serialized as big-endian, read as a big-endian u32. */
90
+ export declare const PAYLOAD_FORMAT_MAGIC_BE = 2479346549;
91
+ /** The magic at the start of a `packedEvmEcdsa` payload: `CEVM`, read as a big-endian u32. */
92
+ export declare const PACKED_EVM_PAYLOAD_FORMAT_MAGIC_BE = 1128617549;
87
93
  export type AssetType = "crypto" | "fx" | "equity" | "metal" | "rates" | "nav" | "commodity" | "funding-rate" | "eco" | "kalshi";
88
94
  export type SymbolsQueryParams = {
89
95
  query?: string;
@@ -107,7 +113,8 @@ export type SymbolResponse = {
107
113
  export type LatestPriceRequest = {
108
114
  priceFeedIds?: number[];
109
115
  symbols?: string[];
110
- properties: PriceFeedProperty[];
116
+ /** Required unless `formats` is `["packedEvmEcdsa"]`, which fixes the properties itself and accepts none. */
117
+ properties?: PriceFeedProperty[];
111
118
  formats: Format[];
112
119
  jsonBinaryEncoding?: JsonBinaryEncoding;
113
120
  parsed?: boolean;
@@ -117,7 +124,8 @@ export type PriceRequest = {
117
124
  timestamp: number;
118
125
  priceFeedIds?: number[];
119
126
  symbols?: string[];
120
- properties: PriceFeedProperty[];
127
+ /** Required unless `formats` is `["packedEvmEcdsa"]`, which fixes the properties itself and accepts none. */
128
+ properties?: PriceFeedProperty[];
121
129
  formats: Format[];
122
130
  jsonBinaryEncoding?: JsonBinaryEncoding;
123
131
  parsed?: boolean;
@@ -129,6 +137,7 @@ export type JsonUpdate = {
129
137
  solana?: JsonBinaryData;
130
138
  leEcdsa?: JsonBinaryData;
131
139
  leUnsigned?: JsonBinaryData;
140
+ packedEvmEcdsa?: JsonBinaryData;
132
141
  };
133
142
  export declare enum CustomSocketClosureCodes {
134
143
  CLIENT_TIMEOUT_BUT_RECONNECTING = 4000
@@ -8,6 +8,7 @@ export type BinaryResponse = {
8
8
  parsed?: ParsedPayload | undefined;
9
9
  leEcdsa?: Buffer | undefined;
10
10
  leUnsigned?: Buffer | undefined;
11
+ packedEvmEcdsa?: Buffer | undefined;
11
12
  };
12
13
  export type JsonOrBinaryResponse = {
13
14
  type: "json";
@@ -1,6 +1,6 @@
1
1
  import { dummyLogger } from "ts-log";
2
2
  import { DEFAULT_METADATA_SERVICE_URL, DEFAULT_PRICE_SERVICE_URL } from "./constants.mjs";
3
- import { BINARY_UPDATE_FORMAT_MAGIC_LE, FORMAT_MAGICS_LE } from "./protocol.mjs";
3
+ import { BINARY_UPDATE_FORMAT_MAGIC_LE, FORMAT_MAGICS_LE, PACKED_EVM_PAYLOAD_FORMAT_MAGIC_BE, PAYLOAD_FORMAT_MAGIC_BE } from "./protocol.mjs";
4
4
  import { WebSocketPool } from "./socket/websocket-pool.mjs";
5
5
  import { bufferFromWebsocketData } from "./util/buffer-util.mjs";
6
6
  const UINT16_NUM_BYTES = 2;
@@ -103,7 +103,17 @@ export class PythLazerClient {
103
103
  pos += UINT16_NUM_BYTES;
104
104
  const magic = buffData.subarray(pos, pos + UINT32_NUM_BYTES).readUint32LE();
105
105
  if (magic === FORMAT_MAGICS_LE.EVM) {
106
- value.evm = buffData.subarray(pos, pos + len);
106
+ // `evm` and `packedEvmEcdsa` messages share the EVM envelope and its
107
+ // magic. Only the magic at the start of the payload tells them apart.
108
+ const message = buffData.subarray(pos, pos + len);
109
+ const payloadMagic = readPayloadMagicBE(message.subarray(71));
110
+ if (payloadMagic === PACKED_EVM_PAYLOAD_FORMAT_MAGIC_BE) {
111
+ value.packedEvmEcdsa = message;
112
+ } else if (payloadMagic === PAYLOAD_FORMAT_MAGIC_BE) {
113
+ value.evm = message;
114
+ } else {
115
+ throw new Error(`unknown payload magic: ${payloadMagic.toString()}`);
116
+ }
107
117
  } else if (magic === FORMAT_MAGICS_LE.SOLANA) {
108
118
  value.solana = buffData.subarray(pos, pos + len);
109
119
  } else if (magic === FORMAT_MAGICS_LE.LE_ECDSA) {
@@ -254,3 +264,9 @@ export class PythLazerClient {
254
264
  }
255
265
  }
256
266
  }
267
+ function readPayloadMagicBE(payload) {
268
+ if (payload.length < UINT32_NUM_BYTES) {
269
+ throw new Error(`payload too short to hold a magic: ${payload.length.toString()} bytes`);
270
+ }
271
+ return payload.readUint32BE(0);
272
+ }
@@ -4,8 +4,8 @@ export declare const DEFAULT_METADATA_SERVICE_URL = "https://pyth.dourolabs.app"
4
4
  export declare const DEFAULT_PRICE_SERVICE_URL = "https://pyth-lazer-0.dourolabs.app";
5
5
  /**
6
6
  * Default stream endpoints, round-robined across the pool's connections when
7
- * the caller does not supply their own `urls`. These are redundant instances
8
- * of the Lazer api-service stream.
7
+ * the caller does not supply their own `urls`. These are redundant router
8
+ * instances.
9
9
  */
10
10
  export declare const DEFAULT_STREAM_SERVICE_URLS: string[];
11
11
  /**
@@ -4,12 +4,12 @@ export const DEFAULT_METADATA_SERVICE_URL = "https://pyth.dourolabs.app";
4
4
  export const DEFAULT_PRICE_SERVICE_URL = "https://pyth-lazer-0.dourolabs.app";
5
5
  /**
6
6
  * Default stream endpoints, round-robined across the pool's connections when
7
- * the caller does not supply their own `urls`. These are redundant instances
8
- * of the Lazer api-service stream.
7
+ * the caller does not supply their own `urls`. These are redundant router
8
+ * instances.
9
9
  */ export const DEFAULT_STREAM_SERVICE_URLS = [
10
- "wss://pyth-0.dourolabs.app/v1/prices/stream",
11
- "wss://pyth-1.dourolabs.app/v1/prices/stream",
12
- "wss://pyth-2.dourolabs.app/v1/prices/stream"
10
+ "wss://pyth-lazer-0.dourolabs.app/v1/stream",
11
+ "wss://pyth-lazer-1.dourolabs.app/v1/stream",
12
+ "wss://pyth-lazer-2.dourolabs.app/v1/stream"
13
13
  ];
14
14
  /**
15
15
  * WebSocket subprotocol marker that carries the auth token during the handshake
@@ -1,4 +1,4 @@
1
- export type Format = "evm" | "solana" | "leEcdsa" | "leUnsigned";
1
+ export type Format = "evm" | "solana" | "leEcdsa" | "leUnsigned" | "packedEvmEcdsa";
2
2
  export type DeliveryFormat = "json" | "binary";
3
3
  export type JsonBinaryEncoding = "base64" | "hex";
4
4
  export type PriceFeedProperty = "price" | "bestBidPrice" | "bestAskPrice" | "exponent" | "publisherCount" | "confidence" | "fundingRate" | "fundingTimestamp" | "fundingRateInterval" | "marketSession" | "emaPrice" | "emaConfidence" | "feedUpdateTimestamp";
@@ -8,7 +8,8 @@ export type Request = {
8
8
  subscriptionId: number;
9
9
  priceFeedIds?: number[] | undefined;
10
10
  symbols?: string[] | undefined;
11
- properties: PriceFeedProperty[];
11
+ /** Required unless `formats` is `["packedEvmEcdsa"]`, which fixes the properties itself and accepts none. */
12
+ properties?: PriceFeedProperty[] | undefined;
12
13
  formats: Format[];
13
14
  deliveryFormat?: DeliveryFormat | undefined;
14
15
  jsonBinaryEncoding?: JsonBinaryEncoding | undefined;
@@ -75,6 +76,7 @@ export type Response = {
75
76
  solana?: JsonBinaryData | undefined;
76
77
  leEcdsa?: JsonBinaryData | undefined;
77
78
  leUnsigned?: JsonBinaryData | undefined;
79
+ packedEvmEcdsa?: JsonBinaryData | undefined;
78
80
  };
79
81
  export declare const BINARY_UPDATE_FORMAT_MAGIC_LE = 461928307;
80
82
  export declare const FORMAT_MAGICS_LE: {
@@ -84,6 +86,10 @@ export declare const FORMAT_MAGICS_LE: {
84
86
  LE_UNSIGNED: number;
85
87
  SOLANA: number;
86
88
  };
89
+ /** The magic at the start of a standard payload serialized as big-endian, read as a big-endian u32. */
90
+ export declare const PAYLOAD_FORMAT_MAGIC_BE = 2479346549;
91
+ /** The magic at the start of a `packedEvmEcdsa` payload: `CEVM`, read as a big-endian u32. */
92
+ export declare const PACKED_EVM_PAYLOAD_FORMAT_MAGIC_BE = 1128617549;
87
93
  export type AssetType = "crypto" | "fx" | "equity" | "metal" | "rates" | "nav" | "commodity" | "funding-rate" | "eco" | "kalshi";
88
94
  export type SymbolsQueryParams = {
89
95
  query?: string;
@@ -107,7 +113,8 @@ export type SymbolResponse = {
107
113
  export type LatestPriceRequest = {
108
114
  priceFeedIds?: number[];
109
115
  symbols?: string[];
110
- properties: PriceFeedProperty[];
116
+ /** Required unless `formats` is `["packedEvmEcdsa"]`, which fixes the properties itself and accepts none. */
117
+ properties?: PriceFeedProperty[];
111
118
  formats: Format[];
112
119
  jsonBinaryEncoding?: JsonBinaryEncoding;
113
120
  parsed?: boolean;
@@ -117,7 +124,8 @@ export type PriceRequest = {
117
124
  timestamp: number;
118
125
  priceFeedIds?: number[];
119
126
  symbols?: string[];
120
- properties: PriceFeedProperty[];
127
+ /** Required unless `formats` is `["packedEvmEcdsa"]`, which fixes the properties itself and accepts none. */
128
+ properties?: PriceFeedProperty[];
121
129
  formats: Format[];
122
130
  jsonBinaryEncoding?: JsonBinaryEncoding;
123
131
  parsed?: boolean;
@@ -129,6 +137,7 @@ export type JsonUpdate = {
129
137
  solana?: JsonBinaryData;
130
138
  leEcdsa?: JsonBinaryData;
131
139
  leUnsigned?: JsonBinaryData;
140
+ packedEvmEcdsa?: JsonBinaryData;
132
141
  };
133
142
  export declare enum CustomSocketClosureCodes {
134
143
  CLIENT_TIMEOUT_BUT_RECONNECTING = 4000
@@ -6,6 +6,8 @@ export const FORMAT_MAGICS_LE = {
6
6
  LE_UNSIGNED: 1_499_680_012,
7
7
  SOLANA: 2_182_742_457
8
8
  };
9
+ /** The magic at the start of a standard payload serialized as big-endian, read as a big-endian u32. */ export const PAYLOAD_FORMAT_MAGIC_BE = 2_479_346_549;
10
+ /** The magic at the start of a `packedEvmEcdsa` payload: `CEVM`, read as a big-endian u32. */ export const PACKED_EVM_PAYLOAD_FORMAT_MAGIC_BE = 1_128_617_549;
9
11
  export var CustomSocketClosureCodes = /*#__PURE__*/ function(CustomSocketClosureCodes) {
10
12
  CustomSocketClosureCodes[CustomSocketClosureCodes["CLIENT_TIMEOUT_BUT_RECONNECTING"] = 4000] = "CLIENT_TIMEOUT_BUT_RECONNECTING";
11
13
  return CustomSocketClosureCodes;
package/package.json CHANGED
@@ -6,15 +6,15 @@
6
6
  "@isaacs/ttlcache": "^2.1.5",
7
7
  "buffer": "^6.0.3",
8
8
  "isomorphic-ws": "^5.0.0",
9
- "ts-log": "^3.0.2",
10
- "ws": "^8.21.0"
9
+ "ts-log": "^3.0.3",
10
+ "ws": "^8.22.0"
11
11
  },
12
12
  "description": "Pyth Lazer SDK",
13
13
  "devDependencies": {
14
14
  "@cprussin/tsconfig": "^5.0.0",
15
- "@types/node": "^26.0.0",
16
- "@types/ws": "^8.18.1",
17
- "typedoc": "^0.28.19"
15
+ "@types/node": "^26.6.6",
16
+ "@types/ws": "^8.18.2",
17
+ "typedoc": "^0.28.20"
18
18
  },
19
19
  "engines": {
20
20
  "node": "^24.0.0"
@@ -154,5 +154,5 @@
154
154
  },
155
155
  "type": "module",
156
156
  "types": "./dist/cjs/index.d.ts",
157
- "version": "7.0.0"
157
+ "version": "7.1.0"
158
158
  }