@waterx/sdk 4.3.1 → 4.3.2

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.
@@ -38,9 +38,10 @@ import { type WaterxSignedEnvelope } from "./rules/waterx-rule.ts";
38
38
  * if it is stale — it never aborts — so the call stays mandatory while
39
39
  * `pyth_rule` remains in the ticker's on-chain weighted set
40
40
  * (`EMissingPriceSource` requires every weighted rule to appear).
41
- * - **Lazer** — fed when `lazerUpdate` is supplied: the verified
42
- * `pyth_lazer::update::Update` produced by this PTB's lazer update leg
43
- * (see `PythLazerRule.buildUpdateCalls`). If the ticker's aggregator does
41
+ * - **Lazer** — fed when `lazerUpdate` is supplied: the verified update this
42
+ * PTB's lazer update leg produced with the network's verify entry
43
+ * (`update_v2::Update` on mainnet, `update::Update` on testnet see
44
+ * `PythLazerRule.buildUpdateCalls`). If the ticker's aggregator does
44
45
  * not (yet) weight `PythLazerRule`, the contribution is silently dropped
45
46
  * on-chain — feeding ahead of the weight migration is harmless.
46
47
  * - **Supra** — fed alongside Pyth/Lazer when supra is enabled + wired
@@ -90,9 +90,10 @@ async function resolveGroupUpdateData(host, group, provider) {
90
90
  * if it is stale — it never aborts — so the call stays mandatory while
91
91
  * `pyth_rule` remains in the ticker's on-chain weighted set
92
92
  * (`EMissingPriceSource` requires every weighted rule to appear).
93
- * - **Lazer** — fed when `lazerUpdate` is supplied: the verified
94
- * `pyth_lazer::update::Update` produced by this PTB's lazer update leg
95
- * (see `PythLazerRule.buildUpdateCalls`). If the ticker's aggregator does
93
+ * - **Lazer** — fed when `lazerUpdate` is supplied: the verified update this
94
+ * PTB's lazer update leg produced with the network's verify entry
95
+ * (`update_v2::Update` on mainnet, `update::Update` on testnet see
96
+ * `PythLazerRule.buildUpdateCalls`). If the ticker's aggregator does
96
97
  * not (yet) weight `PythLazerRule`, the contribution is silently dropped
97
98
  * on-chain — feeding ahead of the weight migration is harmless.
98
99
  * - **Supra** — fed alongside Pyth/Lazer when supra is enabled + wired
@@ -83,14 +83,22 @@ export declare function assertRuleUpdateData<T>(data: RuleUpdateData, kind: Pric
83
83
  * return when its collector-feed leg needs a value produced by the update leg
84
84
  * *within the same PTB*. Pyth Core needs none (its feed leg reads the shared
85
85
  * `PriceInfoObject` the update leg refreshed), so it returns `void`. The Lazer
86
- * rule returns the verified `pyth_lazer::update::Update` result — one
86
+ * rule returns the verified-update result of its network's verify entry — one
87
87
  * signature verification covers every feed in the payload, and
88
88
  * `pyth_lazer_rule::feed` takes it by reference per ticker (see
89
89
  * `aggregateTicker`'s `lazerUpdate` arg).
90
90
  */
91
91
  export type RuleUpdateHandle = {
92
92
  readonly kind: "pyth_lazer_rule";
93
- /** Result of `pyth_lazer::parse_and_verify_le_ecdsa_update` in this PTB. */
93
+ /**
94
+ * Opaque result of this network's `LAZER_INFRA.verify_entry` in this PTB,
95
+ * passed straight to `pyth_lazer_rule::feed`. The Move type is
96
+ * network-dependent and never named here: mainnet's
97
+ * `pyth_lazer::parse_and_verify_le_ecdsa_update_v2` yields
98
+ * `pyth_lazer::update_v2::Update`, testnet's v1
99
+ * `…_le_ecdsa_update` yields `pyth_lazer::update::Update`, and each
100
+ * network's `pyth_lazer_rule` is published bound to the matching one.
101
+ */
94
102
  readonly update: TransactionArgument;
95
103
  };
96
104
  /**
@@ -4,8 +4,9 @@
4
4
  * appends per lazer-routed ticker. Fetches one `leEcdsa` payload for all
5
5
  * requested integer feed ids from the Lazer HTTP API (Bearer-authenticated
6
6
  * via the `pythApiKey` create option), verifies it ONCE on-chain via
7
- * `pyth_lazer::parse_and_verify_le_ecdsa_update`, and hands the resulting
8
- * `Update` PTB value back through a `RuleUpdateHandle` for the feed calls.
7
+ * `pyth_lazer`'s verify entry for that network (see `LAZER_INFRA`), and hands the
8
+ * resulting `Update` PTB value back through a `RuleUpdateHandle` for the feed
9
+ * calls.
9
10
  */
10
11
  import type { Transaction, TransactionArgument } from "@mysten/sui/transactions";
11
12
  import type { Network } from "../../constants.ts";
@@ -21,16 +22,33 @@ import { type PriceUpdateRule } from "../price-update-rule.ts";
21
22
  * `POST /v1/latest_price` (Bearer-authenticated). The service is
22
23
  * network-agnostic (one signed payload verifies on any chain that trusts the
23
24
  * Lazer signers), so both networks share the production host.
24
- * - `verifier_package` — the Sui package carrying
25
- * `pyth_lazer::parse_and_verify_le_ecdsa_update`. Per-network: testnet is
26
- * still the original v1 publish; mainnet is the v2-upgraded package (which
27
- * still exposes the v1 entry `pyth_lazer_rule` binds). Values mirror the
28
- * contract repo's `pyth_lazer_rule/Move.toml` published-at pins.
25
+ * - `verifier_package` / `verify_entry` — the Sui package carrying the verify
26
+ * call, and which entry to call. These track what `pyth_lazer_rule` binds on
27
+ * that network, so they move together:
28
+ * - **mainnet** the v2 package `0xefbfd064…` and the **v2** entry. The rule
29
+ * was republished v2-bound after a 2026-08-05 mainnet probe: the ORIGINAL
30
+ * package `0x7b502c…` now aborts `EDifferentVersion` (`state::current_cap`)
31
+ * for any payload — the shared `State` has been migrated past that code —
32
+ * and its v1 entry aborts `EInvalidChannel` on `fixed_rate@1000ms`, the only
33
+ * channel WaterX's Pyth Pro grant permits.
34
+ * - **testnet** — still the original v1 publish, which has no `update_v2`
35
+ * module at all, so the v1 entry is the only one that exists there.
36
+ * Both entries take `(state, clock, bytes)` and accept the same `leEcdsa`
37
+ * payload. Values mirror the contract repo's `pyth_lazer_rule/Move.toml`
38
+ * published-at pins.
29
39
  */
30
40
  export declare const LAZER_INFRA: Record<Network, {
31
41
  endpoint: string;
32
42
  verifier_package: string;
43
+ verify_entry: LazerVerifyEntry;
44
+ channel: string;
33
45
  }>;
46
+ /**
47
+ * The `pyth_lazer` verify entry a network's deployed rule consumes. `_v2`
48
+ * returns `update_v2::Update`; the v1 entry returns `update::Update`, and the
49
+ * two are NOT interchangeable — the rule's `feed` takes one concrete type.
50
+ */
51
+ export type LazerVerifyEntry = "parse_and_verify_le_ecdsa_update" | "parse_and_verify_le_ecdsa_update_v2";
34
52
  /** `pyth_lazer_rule`'s narrowed `RuleUpdateData.payload` shape. */
35
53
  export interface PythLazerUpdatePayload {
36
54
  /** One signed `leEcdsa` message carrying every requested feed. */
@@ -5,8 +5,9 @@
5
5
  * appends per lazer-routed ticker. Fetches one `leEcdsa` payload for all
6
6
  * requested integer feed ids from the Lazer HTTP API (Bearer-authenticated
7
7
  * via the `pythApiKey` create option), verifies it ONCE on-chain via
8
- * `pyth_lazer::parse_and_verify_le_ecdsa_update`, and hands the resulting
9
- * `Update` PTB value back through a `RuleUpdateHandle` for the feed calls.
8
+ * `pyth_lazer`'s verify entry for that network (see `LAZER_INFRA`), and hands the
9
+ * resulting `Update` PTB value back through a `RuleUpdateHandle` for the feed
10
+ * calls.
10
11
  */
11
12
  Object.defineProperty(exports, "__esModule", { value: true });
12
13
  exports.PythLazerRule = exports.LazerApiKeyMissingError = exports.LAZER_INFRA = void 0;
@@ -25,20 +26,33 @@ const update_fetch_ts_1 = require("../update-fetch.js");
25
26
  * `POST /v1/latest_price` (Bearer-authenticated). The service is
26
27
  * network-agnostic (one signed payload verifies on any chain that trusts the
27
28
  * Lazer signers), so both networks share the production host.
28
- * - `verifier_package` — the Sui package carrying
29
- * `pyth_lazer::parse_and_verify_le_ecdsa_update`. Per-network: testnet is
30
- * still the original v1 publish; mainnet is the v2-upgraded package (which
31
- * still exposes the v1 entry `pyth_lazer_rule` binds). Values mirror the
32
- * contract repo's `pyth_lazer_rule/Move.toml` published-at pins.
29
+ * - `verifier_package` / `verify_entry` — the Sui package carrying the verify
30
+ * call, and which entry to call. These track what `pyth_lazer_rule` binds on
31
+ * that network, so they move together:
32
+ * - **mainnet** the v2 package `0xefbfd064…` and the **v2** entry. The rule
33
+ * was republished v2-bound after a 2026-08-05 mainnet probe: the ORIGINAL
34
+ * package `0x7b502c…` now aborts `EDifferentVersion` (`state::current_cap`)
35
+ * for any payload — the shared `State` has been migrated past that code —
36
+ * and its v1 entry aborts `EInvalidChannel` on `fixed_rate@1000ms`, the only
37
+ * channel WaterX's Pyth Pro grant permits.
38
+ * - **testnet** — still the original v1 publish, which has no `update_v2`
39
+ * module at all, so the v1 entry is the only one that exists there.
40
+ * Both entries take `(state, clock, bytes)` and accept the same `leEcdsa`
41
+ * payload. Values mirror the contract repo's `pyth_lazer_rule/Move.toml`
42
+ * published-at pins.
33
43
  */
34
44
  exports.LAZER_INFRA = {
35
45
  MAINNET: {
36
46
  endpoint: "https://pyth-lazer.dourolabs.app",
37
47
  verifier_package: "0xefbfd064480777699fd9c557a5804d72ace7bc82661fdc8d1f1a44ea6d92ee10",
48
+ verify_entry: "parse_and_verify_le_ecdsa_update_v2",
49
+ channel: "fixed_rate@1000ms",
38
50
  },
39
51
  TESTNET: {
40
52
  endpoint: "https://pyth-lazer.dourolabs.app",
41
53
  verifier_package: "0xf5bd2141967507050a91b58de3d95e77c432cd90d1799ee46effc27430a68c21",
54
+ verify_entry: "parse_and_verify_le_ecdsa_update",
55
+ channel: "fixed_rate@200ms",
42
56
  },
43
57
  };
44
58
  /**
@@ -56,9 +70,15 @@ exports.LAZER_INFRA = {
56
70
  * and every xStock — are `min_channel: fixed_rate@200ms` (Lazer symbol
57
71
  * registry, verified 2026-07-22: the same 29-feed batch 400s at
58
72
  * `real_time`/`50ms` and serves 200 with the leEcdsa blob at `200ms`).
59
- * 200ms is the fastest channel every configured feed supports, and the
60
- * deployed rule accepts it: the v1 on-chain `channel::from_u8` aborts only
61
- * on the 1000ms fixed-rate channel (real_time / 50ms / 200ms are safe).
73
+ * The channel is therefore per-network (`LAZER_INFRA[network].channel`), and
74
+ * it is bounded from BOTH sides — by what the feeds publish and by what the
75
+ * grant allows:
76
+ * - **mainnet: `fixed_rate@1000ms`.** WaterX's Pyth Pro grant no longer
77
+ * permits anything faster ("Channel fixed_rate@200ms violates rate limit.
78
+ * Minimum allowed channel is 1000ms", measured 2026-08-05), and the
79
+ * mainnet rule is v2-bound, so it accepts that channel.
80
+ * - **testnet: `fixed_rate@200ms`.** Its rule is still v1-bound, and the v1
81
+ * on-chain `channel::from_u8` aborts on the 1000ms channel.
62
82
  * - `formats: leEcdsa` + `jsonBinaryEncoding: hex` — the Sui verifier takes
63
83
  * the `leEcdsa` framing; hex matches `fromHex` below.
64
84
  */
@@ -66,7 +86,6 @@ const LAZER_LATEST_PRICE_REQUEST = {
66
86
  properties: ["price", "exponent", "confidence"],
67
87
  formats: ["leEcdsa"],
68
88
  jsonBinaryEncoding: "hex",
69
- channel: "fixed_rate@200ms",
70
89
  };
71
90
  /**
72
91
  * Shape check ONLY — the `kind` discriminant is checked separately by the
@@ -109,7 +128,7 @@ function requireLazerPackage(host) {
109
128
  * retry/timeout/Bearer policy as `fetchPriceFeedsUpdateData`, unified so
110
129
  * both oracle sources fail the same way under upstream degradation.
111
130
  */
112
- async function fetchLazerSignedUpdate(endpoint, apiKey, feedIds, fetchOpts) {
131
+ async function fetchLazerSignedUpdate(endpoint, channel, apiKey, feedIds, fetchOpts) {
113
132
  // joinEndpointPath preserves any base path on the endpoint — the same
114
133
  // leading-slash `new URL` footgun that 404'd every feed on the Pyth Pro
115
134
  // Hermes endpoint (see update-fetch.ts). Defensive here: the default
@@ -120,7 +139,7 @@ async function fetchLazerSignedUpdate(endpoint, apiKey, feedIds, fetchOpts) {
120
139
  res = await (0, update_fetch_ts_1.fetchWithPolicy)(url.toString(), {
121
140
  method: "POST",
122
141
  headers: { "Content-Type": "application/json" },
123
- body: JSON.stringify({ priceFeedIds: feedIds, ...LAZER_LATEST_PRICE_REQUEST }),
142
+ body: JSON.stringify({ priceFeedIds: feedIds, ...LAZER_LATEST_PRICE_REQUEST, channel }),
124
143
  }, { apiKey, ...fetchOpts });
125
144
  }
126
145
  catch (err) {
@@ -180,13 +199,13 @@ exports.PythLazerRule = {
180
199
  if (!apiKey) {
181
200
  throw new LazerApiKeyMissingError();
182
201
  }
183
- const update = await fetchLazerSignedUpdate(exports.LAZER_INFRA[host.network].endpoint, apiKey, feedIds, host.pyth.fetch);
202
+ const update = await fetchLazerSignedUpdate(exports.LAZER_INFRA[host.network].endpoint, exports.LAZER_INFRA[host.network].channel, apiKey, feedIds, host.pyth.fetch);
184
203
  return { kind: "pyth_lazer_rule", payload: { update, feedIds } };
185
204
  },
186
205
  /**
187
206
  * A Lazer payload is ONE signed `leEcdsa` message covering every feed it was
188
207
  * fetched for — verification is a single flat signature check over the whole
189
- * message (`parse_and_verify_le_ecdsa_update`, no per-feed cost), so the
208
+ * message (one `parse_and_verify_le_ecdsa_update*` call, no per-feed cost), so the
190
209
  * payload is indivisible: it can only be served whole. Returns the whole
191
210
  * payload iff every requested ticker's integer feed id is packed in THIS
192
211
  * payload's `feedIds`; any coverage gap (unlisted ticker, or a feed this
@@ -205,19 +224,24 @@ exports.PythLazerRule = {
205
224
  return { kind: "pyth_lazer_rule", payload };
206
225
  },
207
226
  /**
208
- * Appends the single `parse_and_verify_le_ecdsa_update(state, clock, bytes)`
227
+ * Appends the single `parse_and_verify_le_ecdsa_update*(state, clock, bytes)`
209
228
  * call — one secp256k1 signature check covering every feed in the payload —
210
229
  * and returns its `Update` result as the handle the per-ticker feed leg
211
230
  * consumes. `opts.cache` / `opts.feeSource` are Pyth-Core-specific and
212
231
  * ignored (Lazer verification charges no update fee).
232
+ *
233
+ * The entry name comes from `LAZER_INFRA[network].verify_entry`: mainnet's
234
+ * rule binds `update_v2`, testnet's is still the v1 publish. Both take
235
+ * `(state, clock, bytes)` and accept the same `leEcdsa` payload.
213
236
  */
214
237
  buildUpdateCalls(tx, host, data, _opts) {
215
238
  const payload = (0, price_update_rule_ts_1.assertRuleUpdateData)(data, "pyth_lazer_rule", isPythLazerUpdatePayloadShape, "{ update: Uint8Array; feedIds: number[] }");
216
239
  if (!payload)
217
240
  return undefined;
218
241
  const lazer = requireLazerPackage(host);
242
+ const infra = exports.LAZER_INFRA[host.network];
219
243
  const [update] = tx.moveCall({
220
- target: `${exports.LAZER_INFRA[host.network].verifier_package}::pyth_lazer::parse_and_verify_le_ecdsa_update`,
244
+ target: `${infra.verifier_package}::pyth_lazer::${infra.verify_entry}`,
221
245
  arguments: [tx.object(lazer.state), tx.object.clock(), tx.pure.vector("u8", payload.update)],
222
246
  });
223
247
  return { kind: "pyth_lazer_rule", update };
@@ -38,9 +38,10 @@ import { type WaterxSignedEnvelope } from "./rules/waterx-rule.ts";
38
38
  * if it is stale — it never aborts — so the call stays mandatory while
39
39
  * `pyth_rule` remains in the ticker's on-chain weighted set
40
40
  * (`EMissingPriceSource` requires every weighted rule to appear).
41
- * - **Lazer** — fed when `lazerUpdate` is supplied: the verified
42
- * `pyth_lazer::update::Update` produced by this PTB's lazer update leg
43
- * (see `PythLazerRule.buildUpdateCalls`). If the ticker's aggregator does
41
+ * - **Lazer** — fed when `lazerUpdate` is supplied: the verified update this
42
+ * PTB's lazer update leg produced with the network's verify entry
43
+ * (`update_v2::Update` on mainnet, `update::Update` on testnet see
44
+ * `PythLazerRule.buildUpdateCalls`). If the ticker's aggregator does
44
45
  * not (yet) weight `PythLazerRule`, the contribution is silently dropped
45
46
  * on-chain — feeding ahead of the weight migration is harmless.
46
47
  * - **Supra** — fed alongside Pyth/Lazer when supra is enabled + wired
@@ -84,9 +84,10 @@ async function resolveGroupUpdateData(host, group, provider) {
84
84
  * if it is stale — it never aborts — so the call stays mandatory while
85
85
  * `pyth_rule` remains in the ticker's on-chain weighted set
86
86
  * (`EMissingPriceSource` requires every weighted rule to appear).
87
- * - **Lazer** — fed when `lazerUpdate` is supplied: the verified
88
- * `pyth_lazer::update::Update` produced by this PTB's lazer update leg
89
- * (see `PythLazerRule.buildUpdateCalls`). If the ticker's aggregator does
87
+ * - **Lazer** — fed when `lazerUpdate` is supplied: the verified update this
88
+ * PTB's lazer update leg produced with the network's verify entry
89
+ * (`update_v2::Update` on mainnet, `update::Update` on testnet see
90
+ * `PythLazerRule.buildUpdateCalls`). If the ticker's aggregator does
90
91
  * not (yet) weight `PythLazerRule`, the contribution is silently dropped
91
92
  * on-chain — feeding ahead of the weight migration is harmless.
92
93
  * - **Supra** — fed alongside Pyth/Lazer when supra is enabled + wired
@@ -83,14 +83,22 @@ export declare function assertRuleUpdateData<T>(data: RuleUpdateData, kind: Pric
83
83
  * return when its collector-feed leg needs a value produced by the update leg
84
84
  * *within the same PTB*. Pyth Core needs none (its feed leg reads the shared
85
85
  * `PriceInfoObject` the update leg refreshed), so it returns `void`. The Lazer
86
- * rule returns the verified `pyth_lazer::update::Update` result — one
86
+ * rule returns the verified-update result of its network's verify entry — one
87
87
  * signature verification covers every feed in the payload, and
88
88
  * `pyth_lazer_rule::feed` takes it by reference per ticker (see
89
89
  * `aggregateTicker`'s `lazerUpdate` arg).
90
90
  */
91
91
  export type RuleUpdateHandle = {
92
92
  readonly kind: "pyth_lazer_rule";
93
- /** Result of `pyth_lazer::parse_and_verify_le_ecdsa_update` in this PTB. */
93
+ /**
94
+ * Opaque result of this network's `LAZER_INFRA.verify_entry` in this PTB,
95
+ * passed straight to `pyth_lazer_rule::feed`. The Move type is
96
+ * network-dependent and never named here: mainnet's
97
+ * `pyth_lazer::parse_and_verify_le_ecdsa_update_v2` yields
98
+ * `pyth_lazer::update_v2::Update`, testnet's v1
99
+ * `…_le_ecdsa_update` yields `pyth_lazer::update::Update`, and each
100
+ * network's `pyth_lazer_rule` is published bound to the matching one.
101
+ */
94
102
  readonly update: TransactionArgument;
95
103
  };
96
104
  /**
@@ -4,8 +4,9 @@
4
4
  * appends per lazer-routed ticker. Fetches one `leEcdsa` payload for all
5
5
  * requested integer feed ids from the Lazer HTTP API (Bearer-authenticated
6
6
  * via the `pythApiKey` create option), verifies it ONCE on-chain via
7
- * `pyth_lazer::parse_and_verify_le_ecdsa_update`, and hands the resulting
8
- * `Update` PTB value back through a `RuleUpdateHandle` for the feed calls.
7
+ * `pyth_lazer`'s verify entry for that network (see `LAZER_INFRA`), and hands the
8
+ * resulting `Update` PTB value back through a `RuleUpdateHandle` for the feed
9
+ * calls.
9
10
  */
10
11
  import type { Transaction, TransactionArgument } from "@mysten/sui/transactions";
11
12
  import type { Network } from "../../constants.ts";
@@ -21,16 +22,33 @@ import { type PriceUpdateRule } from "../price-update-rule.ts";
21
22
  * `POST /v1/latest_price` (Bearer-authenticated). The service is
22
23
  * network-agnostic (one signed payload verifies on any chain that trusts the
23
24
  * Lazer signers), so both networks share the production host.
24
- * - `verifier_package` — the Sui package carrying
25
- * `pyth_lazer::parse_and_verify_le_ecdsa_update`. Per-network: testnet is
26
- * still the original v1 publish; mainnet is the v2-upgraded package (which
27
- * still exposes the v1 entry `pyth_lazer_rule` binds). Values mirror the
28
- * contract repo's `pyth_lazer_rule/Move.toml` published-at pins.
25
+ * - `verifier_package` / `verify_entry` — the Sui package carrying the verify
26
+ * call, and which entry to call. These track what `pyth_lazer_rule` binds on
27
+ * that network, so they move together:
28
+ * - **mainnet** the v2 package `0xefbfd064…` and the **v2** entry. The rule
29
+ * was republished v2-bound after a 2026-08-05 mainnet probe: the ORIGINAL
30
+ * package `0x7b502c…` now aborts `EDifferentVersion` (`state::current_cap`)
31
+ * for any payload — the shared `State` has been migrated past that code —
32
+ * and its v1 entry aborts `EInvalidChannel` on `fixed_rate@1000ms`, the only
33
+ * channel WaterX's Pyth Pro grant permits.
34
+ * - **testnet** — still the original v1 publish, which has no `update_v2`
35
+ * module at all, so the v1 entry is the only one that exists there.
36
+ * Both entries take `(state, clock, bytes)` and accept the same `leEcdsa`
37
+ * payload. Values mirror the contract repo's `pyth_lazer_rule/Move.toml`
38
+ * published-at pins.
29
39
  */
30
40
  export declare const LAZER_INFRA: Record<Network, {
31
41
  endpoint: string;
32
42
  verifier_package: string;
43
+ verify_entry: LazerVerifyEntry;
44
+ channel: string;
33
45
  }>;
46
+ /**
47
+ * The `pyth_lazer` verify entry a network's deployed rule consumes. `_v2`
48
+ * returns `update_v2::Update`; the v1 entry returns `update::Update`, and the
49
+ * two are NOT interchangeable — the rule's `feed` takes one concrete type.
50
+ */
51
+ export type LazerVerifyEntry = "parse_and_verify_le_ecdsa_update" | "parse_and_verify_le_ecdsa_update_v2";
34
52
  /** `pyth_lazer_rule`'s narrowed `RuleUpdateData.payload` shape. */
35
53
  export interface PythLazerUpdatePayload {
36
54
  /** One signed `leEcdsa` message carrying every requested feed. */
@@ -4,8 +4,9 @@
4
4
  * appends per lazer-routed ticker. Fetches one `leEcdsa` payload for all
5
5
  * requested integer feed ids from the Lazer HTTP API (Bearer-authenticated
6
6
  * via the `pythApiKey` create option), verifies it ONCE on-chain via
7
- * `pyth_lazer::parse_and_verify_le_ecdsa_update`, and hands the resulting
8
- * `Update` PTB value back through a `RuleUpdateHandle` for the feed calls.
7
+ * `pyth_lazer`'s verify entry for that network (see `LAZER_INFRA`), and hands the
8
+ * resulting `Update` PTB value back through a `RuleUpdateHandle` for the feed
9
+ * calls.
9
10
  */
10
11
  import { fromHex } from "@mysten/bcs";
11
12
  import { ownEntry } from "../../utils/record.js";
@@ -21,20 +22,33 @@ import { fetchWithPolicy, joinEndpointPath, rethrowExhaustedFetch } from "../upd
21
22
  * `POST /v1/latest_price` (Bearer-authenticated). The service is
22
23
  * network-agnostic (one signed payload verifies on any chain that trusts the
23
24
  * Lazer signers), so both networks share the production host.
24
- * - `verifier_package` — the Sui package carrying
25
- * `pyth_lazer::parse_and_verify_le_ecdsa_update`. Per-network: testnet is
26
- * still the original v1 publish; mainnet is the v2-upgraded package (which
27
- * still exposes the v1 entry `pyth_lazer_rule` binds). Values mirror the
28
- * contract repo's `pyth_lazer_rule/Move.toml` published-at pins.
25
+ * - `verifier_package` / `verify_entry` — the Sui package carrying the verify
26
+ * call, and which entry to call. These track what `pyth_lazer_rule` binds on
27
+ * that network, so they move together:
28
+ * - **mainnet** the v2 package `0xefbfd064…` and the **v2** entry. The rule
29
+ * was republished v2-bound after a 2026-08-05 mainnet probe: the ORIGINAL
30
+ * package `0x7b502c…` now aborts `EDifferentVersion` (`state::current_cap`)
31
+ * for any payload — the shared `State` has been migrated past that code —
32
+ * and its v1 entry aborts `EInvalidChannel` on `fixed_rate@1000ms`, the only
33
+ * channel WaterX's Pyth Pro grant permits.
34
+ * - **testnet** — still the original v1 publish, which has no `update_v2`
35
+ * module at all, so the v1 entry is the only one that exists there.
36
+ * Both entries take `(state, clock, bytes)` and accept the same `leEcdsa`
37
+ * payload. Values mirror the contract repo's `pyth_lazer_rule/Move.toml`
38
+ * published-at pins.
29
39
  */
30
40
  export const LAZER_INFRA = {
31
41
  MAINNET: {
32
42
  endpoint: "https://pyth-lazer.dourolabs.app",
33
43
  verifier_package: "0xefbfd064480777699fd9c557a5804d72ace7bc82661fdc8d1f1a44ea6d92ee10",
44
+ verify_entry: "parse_and_verify_le_ecdsa_update_v2",
45
+ channel: "fixed_rate@1000ms",
34
46
  },
35
47
  TESTNET: {
36
48
  endpoint: "https://pyth-lazer.dourolabs.app",
37
49
  verifier_package: "0xf5bd2141967507050a91b58de3d95e77c432cd90d1799ee46effc27430a68c21",
50
+ verify_entry: "parse_and_verify_le_ecdsa_update",
51
+ channel: "fixed_rate@200ms",
38
52
  },
39
53
  };
40
54
  /**
@@ -52,9 +66,15 @@ export const LAZER_INFRA = {
52
66
  * and every xStock — are `min_channel: fixed_rate@200ms` (Lazer symbol
53
67
  * registry, verified 2026-07-22: the same 29-feed batch 400s at
54
68
  * `real_time`/`50ms` and serves 200 with the leEcdsa blob at `200ms`).
55
- * 200ms is the fastest channel every configured feed supports, and the
56
- * deployed rule accepts it: the v1 on-chain `channel::from_u8` aborts only
57
- * on the 1000ms fixed-rate channel (real_time / 50ms / 200ms are safe).
69
+ * The channel is therefore per-network (`LAZER_INFRA[network].channel`), and
70
+ * it is bounded from BOTH sides — by what the feeds publish and by what the
71
+ * grant allows:
72
+ * - **mainnet: `fixed_rate@1000ms`.** WaterX's Pyth Pro grant no longer
73
+ * permits anything faster ("Channel fixed_rate@200ms violates rate limit.
74
+ * Minimum allowed channel is 1000ms", measured 2026-08-05), and the
75
+ * mainnet rule is v2-bound, so it accepts that channel.
76
+ * - **testnet: `fixed_rate@200ms`.** Its rule is still v1-bound, and the v1
77
+ * on-chain `channel::from_u8` aborts on the 1000ms channel.
58
78
  * - `formats: leEcdsa` + `jsonBinaryEncoding: hex` — the Sui verifier takes
59
79
  * the `leEcdsa` framing; hex matches `fromHex` below.
60
80
  */
@@ -62,7 +82,6 @@ const LAZER_LATEST_PRICE_REQUEST = {
62
82
  properties: ["price", "exponent", "confidence"],
63
83
  formats: ["leEcdsa"],
64
84
  jsonBinaryEncoding: "hex",
65
- channel: "fixed_rate@200ms",
66
85
  };
67
86
  /**
68
87
  * Shape check ONLY — the `kind` discriminant is checked separately by the
@@ -104,7 +123,7 @@ function requireLazerPackage(host) {
104
123
  * retry/timeout/Bearer policy as `fetchPriceFeedsUpdateData`, unified so
105
124
  * both oracle sources fail the same way under upstream degradation.
106
125
  */
107
- async function fetchLazerSignedUpdate(endpoint, apiKey, feedIds, fetchOpts) {
126
+ async function fetchLazerSignedUpdate(endpoint, channel, apiKey, feedIds, fetchOpts) {
108
127
  // joinEndpointPath preserves any base path on the endpoint — the same
109
128
  // leading-slash `new URL` footgun that 404'd every feed on the Pyth Pro
110
129
  // Hermes endpoint (see update-fetch.ts). Defensive here: the default
@@ -115,7 +134,7 @@ async function fetchLazerSignedUpdate(endpoint, apiKey, feedIds, fetchOpts) {
115
134
  res = await fetchWithPolicy(url.toString(), {
116
135
  method: "POST",
117
136
  headers: { "Content-Type": "application/json" },
118
- body: JSON.stringify({ priceFeedIds: feedIds, ...LAZER_LATEST_PRICE_REQUEST }),
137
+ body: JSON.stringify({ priceFeedIds: feedIds, ...LAZER_LATEST_PRICE_REQUEST, channel }),
119
138
  }, { apiKey, ...fetchOpts });
120
139
  }
121
140
  catch (err) {
@@ -175,13 +194,13 @@ export const PythLazerRule = {
175
194
  if (!apiKey) {
176
195
  throw new LazerApiKeyMissingError();
177
196
  }
178
- const update = await fetchLazerSignedUpdate(LAZER_INFRA[host.network].endpoint, apiKey, feedIds, host.pyth.fetch);
197
+ const update = await fetchLazerSignedUpdate(LAZER_INFRA[host.network].endpoint, LAZER_INFRA[host.network].channel, apiKey, feedIds, host.pyth.fetch);
179
198
  return { kind: "pyth_lazer_rule", payload: { update, feedIds } };
180
199
  },
181
200
  /**
182
201
  * A Lazer payload is ONE signed `leEcdsa` message covering every feed it was
183
202
  * fetched for — verification is a single flat signature check over the whole
184
- * message (`parse_and_verify_le_ecdsa_update`, no per-feed cost), so the
203
+ * message (one `parse_and_verify_le_ecdsa_update*` call, no per-feed cost), so the
185
204
  * payload is indivisible: it can only be served whole. Returns the whole
186
205
  * payload iff every requested ticker's integer feed id is packed in THIS
187
206
  * payload's `feedIds`; any coverage gap (unlisted ticker, or a feed this
@@ -200,19 +219,24 @@ export const PythLazerRule = {
200
219
  return { kind: "pyth_lazer_rule", payload };
201
220
  },
202
221
  /**
203
- * Appends the single `parse_and_verify_le_ecdsa_update(state, clock, bytes)`
222
+ * Appends the single `parse_and_verify_le_ecdsa_update*(state, clock, bytes)`
204
223
  * call — one secp256k1 signature check covering every feed in the payload —
205
224
  * and returns its `Update` result as the handle the per-ticker feed leg
206
225
  * consumes. `opts.cache` / `opts.feeSource` are Pyth-Core-specific and
207
226
  * ignored (Lazer verification charges no update fee).
227
+ *
228
+ * The entry name comes from `LAZER_INFRA[network].verify_entry`: mainnet's
229
+ * rule binds `update_v2`, testnet's is still the v1 publish. Both take
230
+ * `(state, clock, bytes)` and accept the same `leEcdsa` payload.
208
231
  */
209
232
  buildUpdateCalls(tx, host, data, _opts) {
210
233
  const payload = assertRuleUpdateData(data, "pyth_lazer_rule", isPythLazerUpdatePayloadShape, "{ update: Uint8Array; feedIds: number[] }");
211
234
  if (!payload)
212
235
  return undefined;
213
236
  const lazer = requireLazerPackage(host);
237
+ const infra = LAZER_INFRA[host.network];
214
238
  const [update] = tx.moveCall({
215
- target: `${LAZER_INFRA[host.network].verifier_package}::pyth_lazer::parse_and_verify_le_ecdsa_update`,
239
+ target: `${infra.verifier_package}::pyth_lazer::${infra.verify_entry}`,
216
240
  arguments: [tx.object(lazer.state), tx.object.clock(), tx.pure.vector("u8", payload.update)],
217
241
  });
218
242
  return { kind: "pyth_lazer_rule", update };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waterx/sdk",
3
- "version": "4.3.1",
3
+ "version": "4.3.2",
4
4
  "description": "WaterX SDK — perpetuals and prediction markets on Sui",
5
5
  "license": "MIT",
6
6
  "author": "WaterX",