@waterx/sdk 4.3.3 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/.claude/skills/waterx-sdk-integration/SKILL.md +41 -35
  2. package/README.md +81 -77
  3. package/dist/cjs/src/account/config.d.ts +3 -5
  4. package/dist/cjs/src/account/funding/wormhole.d.ts +1 -2
  5. package/dist/cjs/src/account/funding/wormhole.js +1 -2
  6. package/dist/cjs/src/oracle/aggregate.d.ts +45 -61
  7. package/dist/cjs/src/oracle/aggregate.js +138 -138
  8. package/dist/cjs/src/oracle/config.d.ts +35 -49
  9. package/dist/cjs/src/oracle/config.js +1 -1
  10. package/dist/cjs/src/oracle/host.d.ts +14 -19
  11. package/dist/cjs/src/oracle/host.js +3 -3
  12. package/dist/cjs/src/oracle/index.d.ts +25 -16
  13. package/dist/cjs/src/oracle/index.js +79 -66
  14. package/dist/cjs/src/oracle/price-update-rule.d.ts +104 -66
  15. package/dist/cjs/src/oracle/price-update-rule.js +24 -16
  16. package/dist/cjs/src/oracle/pyth-pro-history.d.ts +36 -0
  17. package/dist/cjs/src/oracle/pyth-pro-history.js +41 -0
  18. package/dist/cjs/src/oracle/read-plane.d.ts +35 -53
  19. package/dist/cjs/src/oracle/read-plane.js +26 -48
  20. package/dist/cjs/src/oracle/read-prices.d.ts +105 -0
  21. package/dist/cjs/src/oracle/read-prices.js +162 -0
  22. package/dist/cjs/src/oracle/rule-registry.d.ts +12 -12
  23. package/dist/cjs/src/oracle/rule-registry.js +12 -14
  24. package/dist/cjs/src/oracle/rules/pyth-lazer-rule.d.ts +24 -3
  25. package/dist/cjs/src/oracle/rules/pyth-lazer-rule.js +40 -20
  26. package/dist/cjs/src/oracle/rules/waterx-rule.d.ts +137 -12
  27. package/dist/cjs/src/oracle/rules/waterx-rule.js +318 -55
  28. package/dist/cjs/src/oracle/schedule.d.ts +109 -0
  29. package/dist/cjs/src/oracle/schedule.js +991 -0
  30. package/dist/cjs/src/oracle/source-list.d.ts +31 -27
  31. package/dist/cjs/src/oracle/source-list.js +43 -45
  32. package/dist/cjs/src/oracle/symbol-catalog.d.ts +54 -0
  33. package/dist/cjs/src/oracle/symbol-catalog.js +69 -0
  34. package/dist/cjs/src/oracle/update-fetch.d.ts +11 -15
  35. package/dist/cjs/src/oracle/update-fetch.js +12 -19
  36. package/dist/cjs/src/oracle/validate.d.ts +105 -0
  37. package/dist/cjs/src/oracle/validate.js +185 -0
  38. package/dist/cjs/src/oracle/weight-coverage.d.ts +79 -0
  39. package/dist/cjs/src/oracle/weight-coverage.js +173 -0
  40. package/dist/cjs/src/perp/client.d.ts +35 -52
  41. package/dist/cjs/src/perp/client.js +44 -17
  42. package/dist/cjs/src/perp/config-view.d.ts +5 -14
  43. package/dist/cjs/src/perp/config-view.js +5 -16
  44. package/dist/cjs/src/perp/config.d.ts +3 -3
  45. package/dist/cjs/src/perp/config.js +11 -13
  46. package/dist/cjs/src/perp/index.d.ts +4 -6
  47. package/dist/cjs/src/perp/index.js +35 -18
  48. package/dist/cjs/src/perp/tx-builders/common.d.ts +86 -48
  49. package/dist/cjs/src/perp/tx-builders/common.js +115 -47
  50. package/dist/cjs/src/perp/tx-builders/wlp.d.ts +3 -14
  51. package/dist/cjs/src/perp/tx-builders/wlp.js +6 -35
  52. package/dist/cjs/src/perp/tx-builders.d.ts +6 -5
  53. package/dist/cjs/src/perp/tx-builders.js +16 -10
  54. package/dist/cjs/src/unified-client.d.ts +8 -34
  55. package/dist/cjs/src/unified-client.js +3 -4
  56. package/dist/cjs/src/utils/config.d.ts +0 -8
  57. package/dist/cjs/src/utils/config.js +5 -13
  58. package/dist/src/account/config.d.ts +3 -5
  59. package/dist/src/account/funding/wormhole.d.ts +1 -2
  60. package/dist/src/account/funding/wormhole.js +1 -2
  61. package/dist/src/oracle/aggregate.d.ts +45 -61
  62. package/dist/src/oracle/aggregate.js +138 -137
  63. package/dist/src/oracle/config.d.ts +35 -49
  64. package/dist/src/oracle/config.js +1 -1
  65. package/dist/src/oracle/host.d.ts +14 -19
  66. package/dist/src/oracle/host.js +3 -3
  67. package/dist/src/oracle/index.d.ts +25 -16
  68. package/dist/src/oracle/index.js +55 -50
  69. package/dist/src/oracle/price-update-rule.d.ts +104 -66
  70. package/dist/src/oracle/price-update-rule.js +23 -16
  71. package/dist/src/oracle/pyth-pro-history.d.ts +36 -0
  72. package/dist/src/oracle/pyth-pro-history.js +38 -0
  73. package/dist/src/oracle/read-plane.d.ts +35 -53
  74. package/dist/src/oracle/read-plane.js +25 -47
  75. package/dist/src/oracle/read-prices.d.ts +105 -0
  76. package/dist/src/oracle/read-prices.js +156 -0
  77. package/dist/src/oracle/rule-registry.d.ts +12 -12
  78. package/dist/src/oracle/rule-registry.js +12 -14
  79. package/dist/src/oracle/rules/pyth-lazer-rule.d.ts +24 -3
  80. package/dist/src/oracle/rules/pyth-lazer-rule.js +39 -20
  81. package/dist/src/oracle/rules/waterx-rule.d.ts +137 -12
  82. package/dist/src/oracle/rules/waterx-rule.js +314 -56
  83. package/dist/src/oracle/schedule.d.ts +109 -0
  84. package/dist/src/oracle/schedule.js +985 -0
  85. package/dist/src/oracle/source-list.d.ts +31 -27
  86. package/dist/src/oracle/source-list.js +42 -43
  87. package/dist/src/oracle/symbol-catalog.d.ts +54 -0
  88. package/dist/src/oracle/symbol-catalog.js +65 -0
  89. package/dist/src/oracle/update-fetch.d.ts +11 -15
  90. package/dist/src/oracle/update-fetch.js +12 -18
  91. package/dist/src/oracle/validate.d.ts +105 -0
  92. package/dist/src/oracle/validate.js +177 -0
  93. package/dist/src/oracle/weight-coverage.d.ts +79 -0
  94. package/dist/src/oracle/weight-coverage.js +166 -0
  95. package/dist/src/perp/client.d.ts +35 -52
  96. package/dist/src/perp/client.js +45 -18
  97. package/dist/src/perp/config-view.d.ts +5 -14
  98. package/dist/src/perp/config-view.js +5 -16
  99. package/dist/src/perp/config.d.ts +3 -3
  100. package/dist/src/perp/config.js +11 -13
  101. package/dist/src/perp/index.d.ts +4 -6
  102. package/dist/src/perp/index.js +11 -5
  103. package/dist/src/perp/tx-builders/common.d.ts +86 -48
  104. package/dist/src/perp/tx-builders/common.js +114 -48
  105. package/dist/src/perp/tx-builders/wlp.d.ts +3 -14
  106. package/dist/src/perp/tx-builders/wlp.js +6 -35
  107. package/dist/src/perp/tx-builders.d.ts +6 -5
  108. package/dist/src/perp/tx-builders.js +15 -6
  109. package/dist/src/unified-client.d.ts +8 -34
  110. package/dist/src/unified-client.js +3 -4
  111. package/dist/src/utils/config.d.ts +0 -8
  112. package/dist/src/utils/config.js +5 -12
  113. package/package.json +1 -1
  114. package/dist/cjs/src/generated/pyth_sponsor_rule/deps/sui/balance.d.ts +0 -12
  115. package/dist/cjs/src/generated/pyth_sponsor_rule/deps/sui/balance.js +0 -17
  116. package/dist/cjs/src/generated/pyth_sponsor_rule/pyth_sponsor_rule.d.ts +0 -68
  117. package/dist/cjs/src/generated/pyth_sponsor_rule/pyth_sponsor_rule.js +0 -113
  118. package/dist/cjs/src/generated/waterx_pyth_rule/deps/sui/vec_map.d.ts +0 -36
  119. package/dist/cjs/src/generated/waterx_pyth_rule/deps/sui/vec_map.js +0 -31
  120. package/dist/cjs/src/generated/waterx_pyth_rule/pyth_rule.d.ts +0 -192
  121. package/dist/cjs/src/generated/waterx_pyth_rule/pyth_rule.js +0 -214
  122. package/dist/cjs/src/oracle/pyth.d.ts +0 -227
  123. package/dist/cjs/src/oracle/pyth.js +0 -652
  124. package/dist/cjs/src/oracle/rules/pyth-core-rule.d.ts +0 -15
  125. package/dist/cjs/src/oracle/rules/pyth-core-rule.js +0 -100
  126. package/dist/cjs/src/oracle/rules/pyth-rule.d.ts +0 -11
  127. package/dist/cjs/src/oracle/rules/pyth-rule.js +0 -29
  128. package/dist/cjs/src/oracle/rules/sponsor.d.ts +0 -32
  129. package/dist/cjs/src/oracle/rules/sponsor.js +0 -56
  130. package/dist/src/generated/pyth_sponsor_rule/deps/sui/balance.d.ts +0 -12
  131. package/dist/src/generated/pyth_sponsor_rule/deps/sui/balance.js +0 -14
  132. package/dist/src/generated/pyth_sponsor_rule/pyth_sponsor_rule.d.ts +0 -68
  133. package/dist/src/generated/pyth_sponsor_rule/pyth_sponsor_rule.js +0 -73
  134. package/dist/src/generated/waterx_pyth_rule/deps/sui/vec_map.d.ts +0 -36
  135. package/dist/src/generated/waterx_pyth_rule/deps/sui/vec_map.js +0 -27
  136. package/dist/src/generated/waterx_pyth_rule/pyth_rule.d.ts +0 -192
  137. package/dist/src/generated/waterx_pyth_rule/pyth_rule.js +0 -170
  138. package/dist/src/oracle/pyth.d.ts +0 -227
  139. package/dist/src/oracle/pyth.js +0 -638
  140. package/dist/src/oracle/rules/pyth-core-rule.d.ts +0 -15
  141. package/dist/src/oracle/rules/pyth-core-rule.js +0 -97
  142. package/dist/src/oracle/rules/pyth-rule.d.ts +0 -11
  143. package/dist/src/oracle/rules/pyth-rule.js +0 -26
  144. package/dist/src/oracle/rules/sponsor.d.ts +0 -32
  145. package/dist/src/oracle/rules/sponsor.js +0 -52
@@ -1,68 +1,47 @@
1
1
  "use strict";
2
2
  /**
3
3
  * `read-plane.ts` — per-source READ-plane resolution: which of a caller's
4
- * tickers a source can PRICE off-chain, and with which ids. The write plane
5
- * (`PriceUpdateRule` + each source's `feeds` block) and the read plane are
6
- * DIFFERENT namespaces for the pyth sources: Lazer WRITES with the integer
7
- * ids in `pyth_lazer_rule.feeds`, but every Hermes-compatible price READ is
8
- * keyed by the hex ids only `pyth_rule.feeds` carries. That cross-block
9
- * dependency is a config invariant ("a lazer-fed ticker must also carry a
10
- * Core hex entry to be read-visible"), and it lives HERE, once — consumers
11
- * (FE/BE price facades) resolve through this instead of hardcoding which
12
- * sources share which feeds namespace.
4
+ * tickers a source can PRICE off-chain, and with which ids. Every source
5
+ * reads through ITS OWN feeds namespace `pyth_lazer_rule` via its integer
6
+ * ids on the Lazer HTTP API, `waterx_rule` via its tickers on the
7
+ * quote-center so a source's write set and read set coincide by
8
+ * construction. (Until 5.0.0 Lazer reads borrowed `pyth_rule.feeds` hex ids
9
+ * on a Hermes-compatible endpoint; that cross-block dependency, the whole
10
+ * hermes plane, and the `unreadable` diagnostic are gone with the `pyth_rule`
11
+ * retirement.) Consumers (FE/BE price facades) resolve through this instead
12
+ * of hardcoding feed namespaces, and execute plans via the sibling
13
+ * `read-prices.ts` executors.
13
14
  */
14
15
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.resolveHermesReadEndpoint = resolveHermesReadEndpoint;
16
+ exports.readPlanTickers = readPlanTickers;
16
17
  exports.resolveOracleReadPlan = resolveOracleReadPlan;
17
18
  const record_ts_1 = require("../utils/record.js");
18
- const pyth_ts_1 = require("./pyth.js");
19
+ /** The tickers a resolved plan can actually serve, regardless of plane. */
20
+ function readPlanTickers(plan) {
21
+ return plan.plane === "quote_center" ? plan.tickers : [...plan.feedIdByTicker.keys()];
22
+ }
19
23
  /**
20
24
  * Resolve `source`'s read plan for `tickers`. Pure config lookup — no
21
- * network, no endpoint resolution (endpoints come from
22
- * `pythCoreHermesEndpoint` / `waterxQuoteCenterEndpoint` / the deployment's
23
- * own env). A ticker absent from the returned plan is simply not servable by
24
- * THIS source's read plane — callers decide how to degrade (typically: ask
25
- * the next source in their `ORACLE_SOURCE` list, then omit).
26
- */
27
- /**
28
- * The Hermes-compatible REST base a deployment's hermes-plane read plans
29
- * execute against — the endpoint half of the read contract
30
- * ({@link resolveOracleReadPlan} is the ids half):
31
- *
32
- * - `pyth_rule` in the fed set → the Core source's own keyless endpoint
33
- * (`pythCoreHermesEndpoint(network)`).
34
- * - otherwise → `override` when the deployment set one (a proxy or
35
- * self-hosted mirror), else the documented Pyth Pro base
36
- * (`pythProHermesEndpoint()` — identical for every subscriber; auth is the
37
- * caller's `pythApiKey` Bearer, not a per-deployment URL).
38
- *
39
- * Total — never throws, never falls back Core-ward: a fed set without
40
- * `pyth_rule` reads Pro (or the override), full stop.
25
+ * network, no endpoint resolution (endpoints come from `LAZER_INFRA` /
26
+ * `waterxQuoteCenterEndpoint` / the deployment's own env).
41
27
  */
42
- function resolveHermesReadEndpoint(network, sources, override) {
43
- if (sources.includes("pyth_rule"))
44
- return (0, pyth_ts_1.pythCoreHermesEndpoint)(network);
45
- return override ?? (0, pyth_ts_1.pythProHermesEndpoint)();
46
- }
47
28
  function resolveOracleReadPlan(host, source, tickers) {
48
29
  switch (source) {
49
- case "pyth_rule":
50
30
  case "pyth_lazer_rule": {
51
- // All ticker lookups go through `ownEntry` (own-keys-only): a ticker
52
- // named like an Object.prototype key ("toString", "constructor", …)
53
- // must read as not-listed, not as an inherited Function.
54
- const hexFeeds = host.config.packages.pyth_rule?.feeds;
31
+ // Lazer reads through its OWN integer ids the same
32
+ // `pyth_lazer_rule.feeds` entries its write leg uses so the read set
33
+ // is exactly the write set. All ticker lookups go through `ownEntry`
34
+ // (own-keys-only): a ticker named like an Object.prototype key
35
+ // ("toString", "constructor", …) must read as not-listed, not as an
36
+ // inherited Function.
37
+ const feeds = host.config.packages.pyth_lazer_rule?.feeds;
55
38
  const feedIdByTicker = new Map();
56
39
  for (const ticker of tickers) {
57
- const feedId = (0, record_ts_1.ownEntry)(hexFeeds, ticker)?.feed_id;
40
+ const feedId = (0, record_ts_1.ownEntry)(feeds, ticker);
58
41
  if (feedId !== undefined)
59
42
  feedIdByTicker.set(ticker, feedId);
60
43
  }
61
- // For pyth_rule the write and read namespaces coincide, so `unreadable`
62
- // is always empty; for lazer it is exactly the hex-entry gap.
63
- const writeFeeds = source === "pyth_lazer_rule" ? host.config.packages.pyth_lazer_rule?.feeds : hexFeeds;
64
- const unreadable = tickers.filter((ticker) => (0, record_ts_1.ownEntry)(writeFeeds, ticker) !== undefined && !feedIdByTicker.has(ticker));
65
- return { plane: "hermes", feedIdByTicker, unreadable };
44
+ return { plane: "lazer", feedIdByTicker };
66
45
  }
67
46
  case "waterx_rule": {
68
47
  // Absent feeds block ⇒ serves nothing (see the OracleReadPlan doc) —
@@ -74,7 +53,6 @@ function resolveOracleReadPlan(host, source, tickers) {
74
53
  return {
75
54
  plane: "quote_center",
76
55
  tickers: tickers.filter((ticker) => (0, record_ts_1.ownEntry)(feeds, ticker) !== undefined),
77
- unreadable: [],
78
56
  };
79
57
  }
80
58
  default: {
@@ -0,0 +1,105 @@
1
+ /**
2
+ * `read-prices.ts` — the READ-plane executors for the two live sources: parsed
3
+ * (display-grade) prices from the Lazer HTTP API and the WaterX quote-center.
4
+ * The sibling `read-plane.ts` resolves WHICH tickers a source can price and
5
+ * with which ids; these functions execute that plan. Consumers (FE/BE price
6
+ * facades) fold onto these instead of hand-rolling per-source fetch + decode —
7
+ * the price DECODING here is the one place each source's wire scaling is
8
+ * interpreted (the retired hermes decode was deleted, not re-homed).
9
+ *
10
+ * Both executors return a Map keyed the way the plan is keyed (integer Lazer
11
+ * feed id / ticker) of {@link OraclePriceEntry} — plain numbers, for display
12
+ * and freshness policy, NEVER for rebuilding signed payloads (tx-builds carry
13
+ * the exact signed bytes through `refreshOraclePrices`).
14
+ */
15
+ import type { Network } from "../constants.ts";
16
+ import type { PythFetchPolicy } from "./config.ts";
17
+ import type { FetchPolicy } from "./update-fetch.ts";
18
+ /**
19
+ * One decoded read-plane price. `price` / `conf` are display-grade floats in
20
+ * quote units (USD); `publishTimeMs` is the feed's own publish timestamp in
21
+ * ms — the input to freshness policy (`isFreshWaterxEntry`, consumers'
22
+ * max-age gates).
23
+ */
24
+ export type OraclePriceEntry = {
25
+ price: number;
26
+ publishTimeMs: number;
27
+ conf: number;
28
+ };
29
+ /**
30
+ * Thrown by {@link readLazerPrices} on a Lazer `403` — the key's grant does
31
+ * not cover one of the requested feeds (Lazer rejects the WHOLE batch). The
32
+ * message carries the endpoint's own PLAIN-TEXT body verbatim (e.g.
33
+ * `Not entitled: feed 327 (no grant accepts this feed (asset type 'fx',
34
+ * instrument type 'spot'))`) — it names the offending feed and grant reason
35
+ * better than anything the SDK could synthesize. `instanceof`-able so a
36
+ * consumer can drop unentitled feeds and retry rather than string-matching.
37
+ */
38
+ export declare class LazerNotEntitledError extends Error {
39
+ /** The integer Lazer feed ids the rejected request asked for. */
40
+ readonly feedIds: number[];
41
+ constructor(body: string, feedIds: number[]);
42
+ }
43
+ /**
44
+ * Read parsed prices for `feedIds` (integer Lazer ids — the `"lazer"` arm of
45
+ * `resolveOracleReadPlan`) via `POST /v1/latest_price`.
46
+ *
47
+ * - `network` is REQUIRED: it selects the Lazer endpoint AND the channel, and
48
+ * the integer feed ids in a plan are network-specific, so defaulting it
49
+ * would silently read mainnet infra with testnet ids. Every caller already
50
+ * holds one (`resolveOracleReadPlan` takes the same host).
51
+ * - `channel` defaults to `LAZER_INFRA[network].channel` (the same channel
52
+ * the write leg uses — bounded by the feeds' `min_channel` AND the key's
53
+ * grant). A `400` naming an unsupported/rate-limited channel propagates as
54
+ * a plain error with the body attached — that is an operator
55
+ * misconfiguration, not a retry case.
56
+ * - Decoding: `price = Number(price) * 10 ** exponent` (the wire `price` is a
57
+ * decimal STRING), `conf = Number(confidence) * 10 ** exponent`,
58
+ * `publishTimeMs = feedUpdateTimestamp / 1000` (the wire value is µs). An
59
+ * entry with NO `feedUpdateTimestamp` is SKIPPED — never timestamped off
60
+ * the response's batch `timestampUs`, which says when Lazer answered, not
61
+ * when that feed last printed (a closed-market feed would masquerade as
62
+ * fresh).
63
+ * - `403` → {@link LazerNotEntitledError} (whole batch rejected; body text
64
+ * verbatim).
65
+ */
66
+ export declare function readLazerPrices(opts: {
67
+ apiKey: string;
68
+ feedIds: number[];
69
+ network: Network;
70
+ channel?: string;
71
+ fetch?: PythFetchPolicy;
72
+ }): Promise<Map<number, OraclePriceEntry>>;
73
+ /**
74
+ * Read prices for `tickers` (the `"quote_center"` arm of
75
+ * `resolveOracleReadPlan`) through {@link pullWaterxQuotes}, the rule-owned
76
+ * route ladder the write path uses: per-symbol Merkle leaves by default, the
77
+ * batch envelope only against a quote-center with no leaf route. Public read,
78
+ * no auth, same retry/timeout policy as every oracle fetch.
79
+ *
80
+ * Going through the shared ladder is what keeps the read plane from drifting
81
+ * off the write plane, and it is why reads do not pull a whole-registry
82
+ * envelope: that is one signature over every symbol, so a plane polling a
83
+ * handful of them would transfer and bigint-revive the lot on every tick.
84
+ *
85
+ * Decoding per item: `price = Number(price_n) / Number(price_scale)` and
86
+ * `conf = Number(confidence_n) / Number(confidence_scale)`, each `0` when its
87
+ * scale is `0` (a zero divisor is "no value", not `Infinity`);
88
+ * `publishTimeMs = Number(price_timestamp_ms)`. Reads share the write path's
89
+ * fetch — a mispointed endpoint fails the same intent/shape checks tx-builds
90
+ * fail — but this NEVER hands its signed data to a tx-build: the on-chain
91
+ * per-symbol replay guard (F-014) burns one submission per signed timestamp,
92
+ * and reads must not race trades for it.
93
+ */
94
+ export declare function readQuoteCenterPrices(opts: {
95
+ endpoint: string;
96
+ /**
97
+ * Oracle TICKERS, per the repo convention — this is the consumer boundary,
98
+ * fed directly by `readPlanTickers(plan)`, and naming the same values
99
+ * `symbols` here made every call site cross a rename. Below this point the
100
+ * wire helpers keep `symbols`: that mirrors the literal `?symbols=` query
101
+ * parameter and the `symbol` field the quote-center returns.
102
+ */
103
+ tickers: string[];
104
+ fetch?: FetchPolicy;
105
+ }): Promise<Map<string, OraclePriceEntry>>;
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ /**
3
+ * `read-prices.ts` — the READ-plane executors for the two live sources: parsed
4
+ * (display-grade) prices from the Lazer HTTP API and the WaterX quote-center.
5
+ * The sibling `read-plane.ts` resolves WHICH tickers a source can price and
6
+ * with which ids; these functions execute that plan. Consumers (FE/BE price
7
+ * facades) fold onto these instead of hand-rolling per-source fetch + decode —
8
+ * the price DECODING here is the one place each source's wire scaling is
9
+ * interpreted (the retired hermes decode was deleted, not re-homed).
10
+ *
11
+ * Both executors return a Map keyed the way the plan is keyed (integer Lazer
12
+ * feed id / ticker) of {@link OraclePriceEntry} — plain numbers, for display
13
+ * and freshness policy, NEVER for rebuilding signed payloads (tx-builds carry
14
+ * the exact signed bytes through `refreshOraclePrices`).
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.LazerNotEntitledError = void 0;
18
+ exports.readLazerPrices = readLazerPrices;
19
+ exports.readQuoteCenterPrices = readQuoteCenterPrices;
20
+ const pyth_lazer_rule_ts_1 = require("./rules/pyth-lazer-rule.js");
21
+ const waterx_rule_ts_1 = require("./rules/waterx-rule.js");
22
+ /**
23
+ * Thrown by {@link readLazerPrices} on a Lazer `403` — the key's grant does
24
+ * not cover one of the requested feeds (Lazer rejects the WHOLE batch). The
25
+ * message carries the endpoint's own PLAIN-TEXT body verbatim (e.g.
26
+ * `Not entitled: feed 327 (no grant accepts this feed (asset type 'fx',
27
+ * instrument type 'spot'))`) — it names the offending feed and grant reason
28
+ * better than anything the SDK could synthesize. `instanceof`-able so a
29
+ * consumer can drop unentitled feeds and retry rather than string-matching.
30
+ */
31
+ class LazerNotEntitledError extends Error {
32
+ /** The integer Lazer feed ids the rejected request asked for. */
33
+ feedIds;
34
+ constructor(body, feedIds) {
35
+ super(`Lazer price read not entitled: ${body}`);
36
+ this.name = "LazerNotEntitledError";
37
+ this.feedIds = [...feedIds];
38
+ }
39
+ }
40
+ exports.LazerNotEntitledError = LazerNotEntitledError;
41
+ /**
42
+ * The parsed-read request pins, mirroring the live-probed Lazer contract:
43
+ * `formats: []` is REQUIRED — an ABSENT `formats` is rejected while an empty
44
+ * array means "parsed only, no signed blob", which is exactly what a read
45
+ * plane wants (no signature bytes to pay bandwidth for). `properties` pins
46
+ * the fields the decoder below consumes; `marketSession` rides along for
47
+ * consumers that surface venue state.
48
+ */
49
+ const LAZER_PARSED_READ_REQUEST = {
50
+ properties: ["price", "exponent", "confidence", "feedUpdateTimestamp", "marketSession"],
51
+ formats: [],
52
+ parsed: true,
53
+ };
54
+ /**
55
+ * Read parsed prices for `feedIds` (integer Lazer ids — the `"lazer"` arm of
56
+ * `resolveOracleReadPlan`) via `POST /v1/latest_price`.
57
+ *
58
+ * - `network` is REQUIRED: it selects the Lazer endpoint AND the channel, and
59
+ * the integer feed ids in a plan are network-specific, so defaulting it
60
+ * would silently read mainnet infra with testnet ids. Every caller already
61
+ * holds one (`resolveOracleReadPlan` takes the same host).
62
+ * - `channel` defaults to `LAZER_INFRA[network].channel` (the same channel
63
+ * the write leg uses — bounded by the feeds' `min_channel` AND the key's
64
+ * grant). A `400` naming an unsupported/rate-limited channel propagates as
65
+ * a plain error with the body attached — that is an operator
66
+ * misconfiguration, not a retry case.
67
+ * - Decoding: `price = Number(price) * 10 ** exponent` (the wire `price` is a
68
+ * decimal STRING), `conf = Number(confidence) * 10 ** exponent`,
69
+ * `publishTimeMs = feedUpdateTimestamp / 1000` (the wire value is µs). An
70
+ * entry with NO `feedUpdateTimestamp` is SKIPPED — never timestamped off
71
+ * the response's batch `timestampUs`, which says when Lazer answered, not
72
+ * when that feed last printed (a closed-market feed would masquerade as
73
+ * fresh).
74
+ * - `403` → {@link LazerNotEntitledError} (whole batch rejected; body text
75
+ * verbatim).
76
+ */
77
+ async function readLazerPrices(opts) {
78
+ const out = new Map();
79
+ if (opts.feedIds.length === 0)
80
+ return out;
81
+ const infra = pyth_lazer_rule_ts_1.LAZER_INFRA[opts.network];
82
+ // Same POST transport the rule's write leg uses (URL join, method, headers,
83
+ // Bearer + retry policy) — only the request pins and the decoding below are
84
+ // read-specific.
85
+ const res = await (0, pyth_lazer_rule_ts_1.postLazerLatestPrice)(infra.endpoint, opts.channel ?? infra.channel, opts.apiKey, opts.feedIds, LAZER_PARSED_READ_REQUEST, opts.fetch);
86
+ if (res.status === 403) {
87
+ // Plain-text body naming the unentitled feed + grant reason — verbatim.
88
+ throw new LazerNotEntitledError((await res.text()).trim(), opts.feedIds);
89
+ }
90
+ if (!res.ok) {
91
+ // Includes the 400 channel rejections ("Feeds do not support channel …" /
92
+ // rate-limit minimum-channel) — deterministic misconfigurations that must
93
+ // reach the operator with the endpoint's own wording.
94
+ throw new Error(`Lazer price read failed: ${res.status} ${await res.text()}`);
95
+ }
96
+ const json = (await res.json());
97
+ for (const feed of json.parsed?.priceFeeds ?? []) {
98
+ if (typeof feed.priceFeedId !== "number" ||
99
+ feed.price === undefined ||
100
+ typeof feed.exponent !== "number" ||
101
+ // No per-feed timestamp ⇒ no honest freshness claim ⇒ skip (see doc).
102
+ typeof feed.feedUpdateTimestamp !== "number") {
103
+ continue;
104
+ }
105
+ const scale = 10 ** feed.exponent;
106
+ const price = Number(feed.price) * scale;
107
+ const conf = typeof feed.confidence === "number" ? feed.confidence * scale : 0;
108
+ // A non-finite decode is a MISSING price, not a price. `Number("abc")` is
109
+ // NaN and an absurd exponent overflows to Infinity; either would flow
110
+ // straight into a consumer's staleness/deviation maths, where NaN silently
111
+ // fails every comparison it appears in. Skipping matches how a feed with no
112
+ // timestamp is handled above — omitted, so the caller sees it as unserved.
113
+ if (!Number.isFinite(price) || !Number.isFinite(conf))
114
+ continue;
115
+ out.set(feed.priceFeedId, {
116
+ price,
117
+ conf,
118
+ publishTimeMs: feed.feedUpdateTimestamp / 1000,
119
+ });
120
+ }
121
+ return out;
122
+ }
123
+ /**
124
+ * Read prices for `tickers` (the `"quote_center"` arm of
125
+ * `resolveOracleReadPlan`) through {@link pullWaterxQuotes}, the rule-owned
126
+ * route ladder the write path uses: per-symbol Merkle leaves by default, the
127
+ * batch envelope only against a quote-center with no leaf route. Public read,
128
+ * no auth, same retry/timeout policy as every oracle fetch.
129
+ *
130
+ * Going through the shared ladder is what keeps the read plane from drifting
131
+ * off the write plane, and it is why reads do not pull a whole-registry
132
+ * envelope: that is one signature over every symbol, so a plane polling a
133
+ * handful of them would transfer and bigint-revive the lot on every tick.
134
+ *
135
+ * Decoding per item: `price = Number(price_n) / Number(price_scale)` and
136
+ * `conf = Number(confidence_n) / Number(confidence_scale)`, each `0` when its
137
+ * scale is `0` (a zero divisor is "no value", not `Infinity`);
138
+ * `publishTimeMs = Number(price_timestamp_ms)`. Reads share the write path's
139
+ * fetch — a mispointed endpoint fails the same intent/shape checks tx-builds
140
+ * fail — but this NEVER hands its signed data to a tx-build: the on-chain
141
+ * per-symbol replay guard (F-014) burns one submission per signed timestamp,
142
+ * and reads must not race trades for it.
143
+ */
144
+ async function readQuoteCenterPrices(opts) {
145
+ const out = new Map();
146
+ if (opts.tickers.length === 0)
147
+ return out;
148
+ const { items } = await (0, waterx_rule_ts_1.pullWaterxQuotes)(opts.endpoint, opts.tickers, opts.fetch);
149
+ const requested = new Set(opts.tickers);
150
+ for (const item of items) {
151
+ if (!requested.has(item.symbol))
152
+ continue;
153
+ out.set(item.symbol, {
154
+ price: item.price_scale === 0n ? 0 : Number(item.price_n) / Number(item.price_scale),
155
+ conf: item.confidence_scale === 0n
156
+ ? 0
157
+ : Number(item.confidence_n) / Number(item.confidence_scale),
158
+ publishTimeMs: Number(item.price_timestamp_ms),
159
+ });
160
+ }
161
+ return out;
162
+ }
@@ -5,26 +5,26 @@
5
5
  * every `host.oracleSources` entry through it, and consumers (e.g. a BE
6
6
  * prefetch cache keying per source) import `resolveOracleRule` rather than
7
7
  * hand-mirroring this map. Selection is driven purely by the value passed in
8
- * (ultimately `OracleHost.oracleSources`, the `oracleSource` client create
8
+ * (ultimately `OracleHost.oracleSources`, derived from the deployment config
9
9
  * option) — never by a config JSON `enabled` flag and never by `process.env`.
10
10
  *
11
11
  * Each source is self-contained: it owns its own infra + config and does NOT
12
- * back-stop any other source. Three are registered: `pyth_rule`
13
- * (`PythCoreRule`, Hermes VAA), `pyth_lazer_rule` (`PythLazerRule`, Lazer
14
- * signed updates), and `waterx_rule` (`WaterxRule`, first-party quote-center
15
- * ed25519 signed batches). Resolving a source with no registered rule throws a
16
- * clear `OracleSourceNotImplemented` error. There is deliberately no
17
- * cross-source fallback and no client-creation config guard: selecting a source
18
- * whose feeds are absent is not an error at init it surfaces at tx-build time
19
- * for the specific tickers that source can't serve (see `refreshOraclePrices`).
12
+ * back-stop any other source. Two are registered: `pyth_lazer_rule`
13
+ * (`PythLazerRule`, Lazer signed updates) and `waterx_rule` (`WaterxRule`,
14
+ * first-party quote-center ed25519 signed batches). Resolving a source with
15
+ * no registered rule throws a clear `OracleSourceNotImplemented` error. There
16
+ * is deliberately no cross-source fallback and no client-creation config
17
+ * guard: selecting a source whose feeds are absent is not an error at init —
18
+ * it surfaces at tx-build time for the specific tickers that source can't
19
+ * serve (see `refreshOraclePrices`).
20
20
  */
21
21
  import type { OracleSource, PriceUpdateRule } from "./price-update-rule.ts";
22
22
  /**
23
23
  * Thrown by {@link resolveOracleRule} when `source` has no `PriceUpdateRule`
24
24
  * registered in either the production registry or a test's `overrides` map.
25
- * `instanceof`-able (mirrors `OracleFeeSourceUnavailableError` in `pyth.ts`)
26
- * so a consumer can branch on the failure type directly instead of
27
- * string-matching `error.message`.
25
+ * `instanceof`-able (mirrors `LazerApiKeyMissingError` in
26
+ * `rules/pyth-lazer-rule.ts`) so a consumer can branch on the failure type
27
+ * directly instead of string-matching `error.message`.
28
28
  */
29
29
  export declare class OracleSourceNotImplementedError extends Error {
30
30
  /** The unregistered `OracleSource` that was requested. */
@@ -6,23 +6,22 @@
6
6
  * every `host.oracleSources` entry through it, and consumers (e.g. a BE
7
7
  * prefetch cache keying per source) import `resolveOracleRule` rather than
8
8
  * hand-mirroring this map. Selection is driven purely by the value passed in
9
- * (ultimately `OracleHost.oracleSources`, the `oracleSource` client create
9
+ * (ultimately `OracleHost.oracleSources`, derived from the deployment config
10
10
  * option) — never by a config JSON `enabled` flag and never by `process.env`.
11
11
  *
12
12
  * Each source is self-contained: it owns its own infra + config and does NOT
13
- * back-stop any other source. Three are registered: `pyth_rule`
14
- * (`PythCoreRule`, Hermes VAA), `pyth_lazer_rule` (`PythLazerRule`, Lazer
15
- * signed updates), and `waterx_rule` (`WaterxRule`, first-party quote-center
16
- * ed25519 signed batches). Resolving a source with no registered rule throws a
17
- * clear `OracleSourceNotImplemented` error. There is deliberately no
18
- * cross-source fallback and no client-creation config guard: selecting a source
19
- * whose feeds are absent is not an error at init it surfaces at tx-build time
20
- * for the specific tickers that source can't serve (see `refreshOraclePrices`).
13
+ * back-stop any other source. Two are registered: `pyth_lazer_rule`
14
+ * (`PythLazerRule`, Lazer signed updates) and `waterx_rule` (`WaterxRule`,
15
+ * first-party quote-center ed25519 signed batches). Resolving a source with
16
+ * no registered rule throws a clear `OracleSourceNotImplemented` error. There
17
+ * is deliberately no cross-source fallback and no client-creation config
18
+ * guard: selecting a source whose feeds are absent is not an error at init —
19
+ * it surfaces at tx-build time for the specific tickers that source can't
20
+ * serve (see `refreshOraclePrices`).
21
21
  */
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
23
  exports.OracleSourceNotImplementedError = void 0;
24
24
  exports.resolveOracleRule = resolveOracleRule;
25
- const pyth_core_rule_ts_1 = require("./rules/pyth-core-rule.js");
26
25
  const pyth_lazer_rule_ts_1 = require("./rules/pyth-lazer-rule.js");
27
26
  const waterx_rule_ts_1 = require("./rules/waterx-rule.js");
28
27
  /**
@@ -30,16 +29,15 @@ const waterx_rule_ts_1 = require("./rules/waterx-rule.js");
30
29
  * `resolveOracleRule`'s `overrides` param instead of mutating this.
31
30
  */
32
31
  const DEFAULT_RULES = Object.freeze({
33
- pyth_rule: pyth_core_rule_ts_1.PythCoreRule,
34
32
  pyth_lazer_rule: pyth_lazer_rule_ts_1.PythLazerRule,
35
33
  waterx_rule: waterx_rule_ts_1.WaterxRule,
36
34
  });
37
35
  /**
38
36
  * Thrown by {@link resolveOracleRule} when `source` has no `PriceUpdateRule`
39
37
  * registered in either the production registry or a test's `overrides` map.
40
- * `instanceof`-able (mirrors `OracleFeeSourceUnavailableError` in `pyth.ts`)
41
- * so a consumer can branch on the failure type directly instead of
42
- * string-matching `error.message`.
38
+ * `instanceof`-able (mirrors `LazerApiKeyMissingError` in
39
+ * `rules/pyth-lazer-rule.ts`) so a consumer can branch on the failure type
40
+ * directly instead of string-matching `error.message`.
43
41
  */
44
42
  class OracleSourceNotImplementedError extends Error {
45
43
  /** The unregistered `OracleSource` that was requested. */
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import type { Transaction, TransactionArgument } from "@mysten/sui/transactions";
12
12
  import type { Network } from "../../constants.ts";
13
+ import type { PythFetchPolicy } from "../config.ts";
13
14
  import type { OracleHost } from "../host.ts";
14
15
  import { type PriceUpdateRule } from "../price-update-rule.ts";
15
16
  /**
@@ -60,13 +61,33 @@ export interface PythLazerUpdatePayload {
60
61
  * Thrown by {@link PythLazerRule.fetchUpdateData} when `pyth_lazer_rule` is
61
62
  * deployed in config but no `pythApiKey` was supplied at client init — the
62
63
  * Lazer HTTP API requires a Bearer token and the SDK never reads
63
- * `process.env` to find one. `instanceof`-able (mirrors
64
- * `OracleFeeSourceUnavailableError` in `pyth.ts`) so a consumer can branch on
65
- * the failure type directly instead of string-matching `error.message`.
64
+ * `process.env` to find one. Also thrown by `refreshOraclePrices`'s hoisted
65
+ * credential pre-check (`aggregate.ts`) BEFORE any fetch, keyed off this
66
+ * rule's `credential` declaration. `instanceof`-able (mirrors `FetchPolicyError`
67
+ * in `../update-fetch.ts`) so a consumer can branch on the failure type
68
+ * directly instead of string-matching `error.message`.
66
69
  */
67
70
  export declare class LazerApiKeyMissingError extends Error {
68
71
  constructor();
69
72
  }
73
+ /**
74
+ * THE `POST {endpoint}/v1/latest_price` transport — shared by BOTH legs of
75
+ * this endpoint: the signed-update WRITE fetch below and the parsed-price READ
76
+ * executor (`readLazerPrices` in `../read-prices.ts`). They differ only in the
77
+ * request pins they send and how they decode/blame the response, so keeping
78
+ * one transport stops the URL, method, headers, and auth/retry policy drifting
79
+ * between them.
80
+ *
81
+ * `requestPins` is spread into the body alongside `priceFeedIds` + `channel`;
82
+ * the raw `Response` comes back undecoded so each caller owns its own error
83
+ * framing (the write leg reframes an exhausted retry, the read leg maps 403 to
84
+ * an entitlement error).
85
+ *
86
+ * Goes through the shared `fetchWithPolicy` (`../update-fetch.ts`) — the one
87
+ * retry/timeout/Bearer policy every oracle fetch shares, so all sources fail
88
+ * the same way under upstream degradation.
89
+ */
90
+ export declare function postLazerLatestPrice(endpoint: string, channel: string, apiKey: string, feedIds: number[], requestPins: Record<string, unknown>, fetchOpts?: PythFetchPolicy): Promise<Response>;
70
91
  /**
71
92
  * `pyth_lazer_rule::feed(collector, config, clock, &update)` — contribute the
72
93
  * verified Lazer price for `collector.symbol()` to the collector. `update` is
@@ -11,6 +11,7 @@
11
11
  */
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.PythLazerRule = exports.LazerApiKeyMissingError = exports.LAZER_INFRA = void 0;
14
+ exports.postLazerLatestPrice = postLazerLatestPrice;
14
15
  exports.feedLazerRule = feedLazerRule;
15
16
  const bcs_1 = require("@mysten/bcs");
16
17
  const record_ts_1 = require("../../utils/record.js");
@@ -89,7 +90,7 @@ const LAZER_LATEST_PRICE_REQUEST = {
89
90
  };
90
91
  /**
91
92
  * Shape check ONLY — the `kind` discriminant is checked separately by the
92
- * caller before this runs (mirrors `PythCoreRule`'s guard split), so a
93
+ * caller before this runs (mirrors `WaterxRule`'s guard split), so a
93
94
  * same-shaped payload from a different rule can never silently pass.
94
95
  */
95
96
  function isPythLazerUpdatePayloadShape(payload) {
@@ -102,9 +103,11 @@ function isPythLazerUpdatePayloadShape(payload) {
102
103
  * Thrown by {@link PythLazerRule.fetchUpdateData} when `pyth_lazer_rule` is
103
104
  * deployed in config but no `pythApiKey` was supplied at client init — the
104
105
  * Lazer HTTP API requires a Bearer token and the SDK never reads
105
- * `process.env` to find one. `instanceof`-able (mirrors
106
- * `OracleFeeSourceUnavailableError` in `pyth.ts`) so a consumer can branch on
107
- * the failure type directly instead of string-matching `error.message`.
106
+ * `process.env` to find one. Also thrown by `refreshOraclePrices`'s hoisted
107
+ * credential pre-check (`aggregate.ts`) BEFORE any fetch, keyed off this
108
+ * rule's `credential` declaration. `instanceof`-able (mirrors `FetchPolicyError`
109
+ * in `../update-fetch.ts`) so a consumer can branch on the failure type
110
+ * directly instead of string-matching `error.message`.
108
111
  */
109
112
  class LazerApiKeyMissingError extends Error {
110
113
  constructor() {
@@ -123,24 +126,39 @@ function requireLazerPackage(host) {
123
126
  return entry;
124
127
  }
125
128
  /**
126
- * Fetch one signed `leEcdsa` update for `feedIds` from the Lazer HTTP API.
127
- * Goes through the shared `fetchWithPolicy` (`../update-fetch.ts`) same
128
- * retry/timeout/Bearer policy as `fetchPriceFeedsUpdateData`, unified so
129
- * both oracle sources fail the same way under upstream degradation.
129
+ * THE `POST {endpoint}/v1/latest_price` transport shared by BOTH legs of
130
+ * this endpoint: the signed-update WRITE fetch below and the parsed-price READ
131
+ * executor (`readLazerPrices` in `../read-prices.ts`). They differ only in the
132
+ * request pins they send and how they decode/blame the response, so keeping
133
+ * one transport stops the URL, method, headers, and auth/retry policy drifting
134
+ * between them.
135
+ *
136
+ * `requestPins` is spread into the body alongside `priceFeedIds` + `channel`;
137
+ * the raw `Response` comes back undecoded so each caller owns its own error
138
+ * framing (the write leg reframes an exhausted retry, the read leg maps 403 to
139
+ * an entitlement error).
140
+ *
141
+ * Goes through the shared `fetchWithPolicy` (`../update-fetch.ts`) — the one
142
+ * retry/timeout/Bearer policy every oracle fetch shares, so all sources fail
143
+ * the same way under upstream degradation.
130
144
  */
131
- async function fetchLazerSignedUpdate(endpoint, channel, apiKey, feedIds, fetchOpts) {
145
+ function postLazerLatestPrice(endpoint, channel, apiKey, feedIds, requestPins, fetchOpts) {
132
146
  // joinEndpointPath preserves any base path on the endpoint — the same
133
147
  // leading-slash `new URL` footgun that 404'd every feed on the Pyth Pro
134
148
  // Hermes endpoint (see update-fetch.ts). Defensive here: the default
135
149
  // Lazer endpoint has no base path, but a config override may.
136
150
  const url = (0, update_fetch_ts_1.joinEndpointPath)(endpoint, "v1/latest_price");
151
+ return (0, update_fetch_ts_1.fetchWithPolicy)(url.toString(), {
152
+ method: "POST",
153
+ headers: { "Content-Type": "application/json" },
154
+ body: JSON.stringify({ priceFeedIds: feedIds, ...requestPins, channel }),
155
+ }, { apiKey, ...fetchOpts });
156
+ }
157
+ /** Fetch one signed `leEcdsa` update for `feedIds` from the Lazer HTTP API. */
158
+ async function fetchLazerSignedUpdate(endpoint, channel, apiKey, feedIds, fetchOpts) {
137
159
  let res;
138
160
  try {
139
- res = await (0, update_fetch_ts_1.fetchWithPolicy)(url.toString(), {
140
- method: "POST",
141
- headers: { "Content-Type": "application/json" },
142
- body: JSON.stringify({ priceFeedIds: feedIds, ...LAZER_LATEST_PRICE_REQUEST, channel }),
143
- }, { apiKey, ...fetchOpts });
161
+ res = await postLazerLatestPrice(endpoint, channel, apiKey, feedIds, LAZER_LATEST_PRICE_REQUEST, fetchOpts);
144
162
  }
145
163
  catch (err) {
146
164
  (0, update_fetch_ts_1.rethrowExhaustedFetch)(err, (e) => `Lazer price fetch failed: ${e.status}${e.bodySnippet ? ` ${e.bodySnippet}` : ""}`);
@@ -172,9 +190,12 @@ function feedLazerRule(tx, host, collector, update) {
172
190
  }
173
191
  exports.PythLazerRule = {
174
192
  kind: "pyth_lazer_rule",
175
- // Verification is a flat signature check with no Coin argument — no
176
- // update feesee `PriceUpdateRule.requiresFeeSource`.
177
- requiresFeeSource: false,
193
+ // Lazer is auth-first: the signed-update fetch cannot run without the
194
+ // caller's `pythApiKey` Bearer. Declared on the port kind AND error
195
+ // together — so `refreshOraclePrices`'s hoisted pre-check and consumers'
196
+ // `missingOracleCredentials` boot asserts both key off the rule itself, and
197
+ // the orchestrator never constructs this error on the rule's behalf.
198
+ credential: { kind: "pyth_api_key", missing: () => new LazerApiKeyMissingError() },
178
199
  /** Tickers with a `pyth_lazer_rule.feeds` entry (integer Lazer feed ids). */
179
200
  supportedTickers(host) {
180
201
  return Object.keys(host.config.packages.pyth_lazer_rule?.feeds ?? {});
@@ -227,14 +248,13 @@ exports.PythLazerRule = {
227
248
  * Appends the single `parse_and_verify_le_ecdsa_update*(state, clock, bytes)`
228
249
  * call — one secp256k1 signature check covering every feed in the payload —
229
250
  * and returns its `Update` result as the handle the per-ticker feed leg
230
- * consumes. `opts.cache` / `opts.feeSource` are Pyth-Core-specific and
231
- * ignored (Lazer verification charges no update fee).
251
+ * consumes.
232
252
  *
233
253
  * The entry name comes from `LAZER_INFRA[network].verify_entry`: mainnet's
234
254
  * rule binds `update_v2`, testnet's is still the v1 publish. Both take
235
255
  * `(state, clock, bytes)` and accept the same `leEcdsa` payload.
236
256
  */
237
- buildUpdateCalls(tx, host, data, _opts) {
257
+ buildUpdateCalls(tx, host, data) {
238
258
  const payload = (0, price_update_rule_ts_1.assertRuleUpdateData)(data, "pyth_lazer_rule", isPythLazerUpdatePayloadShape, "{ update: Uint8Array; feedIds: number[] }");
239
259
  if (!payload)
240
260
  return undefined;