@waterx/sdk 4.1.0 → 4.3.1

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 (80) hide show
  1. package/README.md +29 -19
  2. package/dist/cjs/src/account/config.d.ts +3 -2
  3. package/dist/cjs/src/generated/waterx_rule/waterx_rule.d.ts +7 -134
  4. package/dist/cjs/src/generated/waterx_rule/waterx_rule.js +8 -154
  5. package/dist/cjs/src/oracle/aggregate.d.ts +21 -21
  6. package/dist/cjs/src/oracle/aggregate.js +121 -71
  7. package/dist/cjs/src/oracle/config.d.ts +38 -72
  8. package/dist/cjs/src/oracle/config.js +0 -56
  9. package/dist/cjs/src/oracle/host.d.ts +24 -20
  10. package/dist/cjs/src/oracle/index.d.ts +7 -3
  11. package/dist/cjs/src/oracle/index.js +41 -8
  12. package/dist/cjs/src/oracle/price-update-rule.d.ts +32 -11
  13. package/dist/cjs/src/oracle/price-update-rule.js +23 -3
  14. package/dist/cjs/src/oracle/pyth.d.ts +44 -0
  15. package/dist/cjs/src/oracle/pyth.js +48 -6
  16. package/dist/cjs/src/oracle/read-plane.d.ts +70 -0
  17. package/dist/cjs/src/oracle/read-plane.js +85 -0
  18. package/dist/cjs/src/oracle/rule-registry.d.ts +6 -4
  19. package/dist/cjs/src/oracle/rule-registry.js +6 -4
  20. package/dist/cjs/src/oracle/rules/pyth-core-rule.js +6 -5
  21. package/dist/cjs/src/oracle/rules/pyth-lazer-rule.d.ts +21 -0
  22. package/dist/cjs/src/oracle/rules/pyth-lazer-rule.js +34 -6
  23. package/dist/cjs/src/oracle/rules/pyth-rule.js +5 -4
  24. package/dist/cjs/src/oracle/rules/waterx-rule.d.ts +42 -5
  25. package/dist/cjs/src/oracle/rules/waterx-rule.js +61 -18
  26. package/dist/cjs/src/oracle/source-list.d.ts +36 -0
  27. package/dist/cjs/src/oracle/source-list.js +57 -0
  28. package/dist/cjs/src/perp/client.d.ts +41 -28
  29. package/dist/cjs/src/perp/client.js +32 -20
  30. package/dist/cjs/src/perp/config-view.js +7 -6
  31. package/dist/cjs/src/perp/config.d.ts +6 -6
  32. package/dist/cjs/src/perp/config.js +12 -12
  33. package/dist/cjs/src/perp/index.d.ts +3 -3
  34. package/dist/cjs/src/perp/index.js +11 -4
  35. package/dist/cjs/src/perp/user/staking.js +2 -1
  36. package/dist/cjs/src/unified-client.d.ts +14 -9
  37. package/dist/cjs/src/unified-client.js +2 -2
  38. package/dist/cjs/src/utils/config.js +2 -1
  39. package/dist/cjs/src/utils/record.d.ts +12 -0
  40. package/dist/cjs/src/utils/record.js +22 -0
  41. package/dist/src/account/config.d.ts +3 -2
  42. package/dist/src/generated/waterx_rule/waterx_rule.d.ts +7 -134
  43. package/dist/src/generated/waterx_rule/waterx_rule.js +7 -143
  44. package/dist/src/oracle/aggregate.d.ts +21 -21
  45. package/dist/src/oracle/aggregate.js +121 -71
  46. package/dist/src/oracle/config.d.ts +38 -72
  47. package/dist/src/oracle/config.js +1 -55
  48. package/dist/src/oracle/host.d.ts +24 -20
  49. package/dist/src/oracle/index.d.ts +7 -3
  50. package/dist/src/oracle/index.js +32 -10
  51. package/dist/src/oracle/price-update-rule.d.ts +32 -11
  52. package/dist/src/oracle/price-update-rule.js +22 -3
  53. package/dist/src/oracle/pyth.d.ts +44 -0
  54. package/dist/src/oracle/pyth.js +45 -5
  55. package/dist/src/oracle/read-plane.d.ts +70 -0
  56. package/dist/src/oracle/read-plane.js +81 -0
  57. package/dist/src/oracle/rule-registry.d.ts +6 -4
  58. package/dist/src/oracle/rule-registry.js +6 -4
  59. package/dist/src/oracle/rules/pyth-core-rule.js +7 -6
  60. package/dist/src/oracle/rules/pyth-lazer-rule.d.ts +21 -0
  61. package/dist/src/oracle/rules/pyth-lazer-rule.js +33 -5
  62. package/dist/src/oracle/rules/pyth-rule.js +5 -4
  63. package/dist/src/oracle/rules/waterx-rule.d.ts +42 -5
  64. package/dist/src/oracle/rules/waterx-rule.js +57 -15
  65. package/dist/src/oracle/source-list.d.ts +36 -0
  66. package/dist/src/oracle/source-list.js +53 -0
  67. package/dist/src/perp/client.d.ts +41 -28
  68. package/dist/src/perp/client.js +33 -21
  69. package/dist/src/perp/config-view.js +7 -6
  70. package/dist/src/perp/config.d.ts +6 -6
  71. package/dist/src/perp/config.js +11 -9
  72. package/dist/src/perp/index.d.ts +3 -3
  73. package/dist/src/perp/index.js +2 -2
  74. package/dist/src/perp/user/staking.js +2 -1
  75. package/dist/src/unified-client.d.ts +14 -9
  76. package/dist/src/unified-client.js +2 -2
  77. package/dist/src/utils/config.js +2 -1
  78. package/dist/src/utils/record.d.ts +12 -0
  79. package/dist/src/utils/record.js +19 -0
  80. package/package.json +1 -1
@@ -17,7 +17,7 @@
17
17
  * rules that consume it are separate concerns.
18
18
  */
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.reimbursePythSponsor = exports.openPythSponsorFund = exports.refreshOraclePrices = exports.aggregateTickerWithConstant = exports.aggregateTickerWithPyth = exports.aggregateTicker = exports.OracleSourceNotImplementedError = exports.parseSignedEnvelope = exports.WaterxRule = exports.LazerApiKeyMissingError = exports.PythLazerRule = exports.PythCoreRule = exports.OracleFeeSourceUnavailableError = exports.MISSING_FEED_MEMO_TTL_MS = exports.HermesEndpointRejectedAllFeedsError = exports.updatePythPrices = exports.buildPythPriceUpdateCalls = exports.probeMissingFeeds = exports.endpointSupportedFeedIds = exports.fetchPriceFeedsUpdateData = exports.PythCache = exports.joinEndpointPath = exports.fetchWithPolicy = exports.FetchPolicyError = void 0;
20
+ exports.reimbursePythSponsor = exports.openPythSponsorFund = exports.refreshOraclePrices = exports.aggregateTickerWithConstant = exports.aggregateTickerWithPyth = exports.aggregateTicker = exports.resolveOracleRule = exports.OracleSourceNotImplementedError = exports.waterxEnvelopeOf = exports.waterxQuoteCenterEndpoint = exports.WATERX_INFRA = exports.BATCH_PRICE_INTENT = exports.parseSignedEnvelope = exports.WaterxRule = exports.LazerApiKeyMissingError = exports.PythLazerRule = exports.PythCoreRule = exports.resolveHermesReadEndpoint = exports.resolveOracleReadPlan = exports.parseOracleSourceList = exports.isOracleSource = exports.ORACLE_SOURCES = exports.OracleFeeSourceUnavailableError = exports.MISSING_FEED_MEMO_TTL_MS = exports.HermesEndpointRejectedAllFeedsError = exports.updatePythPrices = exports.PYTH_PRO_HERMES_ENDPOINT = exports.pythProHermesEndpoint = exports.pythCoreHermesEndpoint = exports.buildPythPriceUpdateCalls = exports.probeMissingFeeds = exports.endpointSupportedFeedIds = exports.fetchPriceFeedsUpdateData = exports.PythCache = exports.joinEndpointPath = exports.fetchWithPolicy = exports.FetchPolicyError = void 0;
21
21
  // Shared fetch resilience wrapper — `FetchPolicyError` is re-exported (not
22
22
  // just the type) so a consumer (e.g. a BE prefetch cache) can `instanceof`
23
23
  // it off the failure `fetchPriceFeedsUpdateData` / `PythLazerRule` /
@@ -45,10 +45,35 @@ Object.defineProperty(exports, "fetchPriceFeedsUpdateData", { enumerable: true,
45
45
  Object.defineProperty(exports, "endpointSupportedFeedIds", { enumerable: true, get: function () { return pyth_ts_1.endpointSupportedFeedIds; } });
46
46
  Object.defineProperty(exports, "probeMissingFeeds", { enumerable: true, get: function () { return pyth_ts_1.probeMissingFeeds; } });
47
47
  Object.defineProperty(exports, "buildPythPriceUpdateCalls", { enumerable: true, get: function () { return pyth_ts_1.buildPythPriceUpdateCalls; } });
48
+ // The pyth read-plane endpoint accessors — Core (keyless, per network) and
49
+ // Pro (the documented fixed base; auth via the caller's Bearer key). There
50
+ // is no client-level endpoint field: consumers pick via
51
+ // `resolveHermesReadEndpoint` (pyth_rule listed → Core, else override ??
52
+ // Pro) — never a hand-rolled branch, never a cross-source fallback.
53
+ Object.defineProperty(exports, "pythCoreHermesEndpoint", { enumerable: true, get: function () { return pyth_ts_1.pythCoreHermesEndpoint; } });
54
+ Object.defineProperty(exports, "pythProHermesEndpoint", { enumerable: true, get: function () { return pyth_ts_1.pythProHermesEndpoint; } });
55
+ Object.defineProperty(exports, "PYTH_PRO_HERMES_ENDPOINT", { enumerable: true, get: function () { return pyth_ts_1.PYTH_PRO_HERMES_ENDPOINT; } });
48
56
  Object.defineProperty(exports, "updatePythPrices", { enumerable: true, get: function () { return pyth_ts_1.updatePythPrices; } });
49
57
  Object.defineProperty(exports, "HermesEndpointRejectedAllFeedsError", { enumerable: true, get: function () { return pyth_ts_1.HermesEndpointRejectedAllFeedsError; } });
50
58
  Object.defineProperty(exports, "MISSING_FEED_MEMO_TTL_MS", { enumerable: true, get: function () { return pyth_ts_1.MISSING_FEED_MEMO_TTL_MS; } });
51
59
  Object.defineProperty(exports, "OracleFeeSourceUnavailableError", { enumerable: true, get: function () { return pyth_ts_1.OracleFeeSourceUnavailableError; } });
60
+ // Canonical OracleSource value list + THE env-string parser consumers fold
61
+ // onto — semantics and rationale in `source-list.ts`'s header.
62
+ var price_update_rule_ts_1 = require("./price-update-rule.js");
63
+ Object.defineProperty(exports, "ORACLE_SOURCES", { enumerable: true, get: function () { return price_update_rule_ts_1.ORACLE_SOURCES; } });
64
+ var source_list_ts_1 = require("./source-list.js");
65
+ Object.defineProperty(exports, "isOracleSource", { enumerable: true, get: function () { return source_list_ts_1.isOracleSource; } });
66
+ Object.defineProperty(exports, "parseOracleSourceList", { enumerable: true, get: function () { return source_list_ts_1.parseOracleSourceList; } });
67
+ // Per-source READ-plane resolution — which tickers a source can price
68
+ // off-chain and with which ids (`resolveOracleReadPlan`), and which
69
+ // Hermes-compatible base the hermes plans execute against
70
+ // (`resolveHermesReadEndpoint`: pyth_rule listed → Core, else override ??
71
+ // the documented Pyth Pro base). The one place the "lazer reads through
72
+ // `pyth_rule.feeds` hex ids" invariant lives; consumers resolve through
73
+ // this instead of hardcoding namespace sharing or endpoint branching.
74
+ var read_plane_ts_1 = require("./read-plane.js");
75
+ Object.defineProperty(exports, "resolveOracleReadPlan", { enumerable: true, get: function () { return read_plane_ts_1.resolveOracleReadPlan; } });
76
+ Object.defineProperty(exports, "resolveHermesReadEndpoint", { enumerable: true, get: function () { return read_plane_ts_1.resolveHermesReadEndpoint; } });
52
77
  // Pyth Core rule (PriceUpdateRule wrapper over the Pyth source above)
53
78
  var pyth_core_rule_ts_1 = require("./rules/pyth-core-rule.js");
54
79
  Object.defineProperty(exports, "PythCoreRule", { enumerable: true, get: function () { return pyth_core_rule_ts_1.PythCoreRule; } });
@@ -58,20 +83,28 @@ Object.defineProperty(exports, "PythCoreRule", { enumerable: true, get: function
58
83
  var pyth_lazer_rule_ts_1 = require("./rules/pyth-lazer-rule.js");
59
84
  Object.defineProperty(exports, "PythLazerRule", { enumerable: true, get: function () { return pyth_lazer_rule_ts_1.PythLazerRule; } });
60
85
  Object.defineProperty(exports, "LazerApiKeyMissingError", { enumerable: true, get: function () { return pyth_lazer_rule_ts_1.LazerApiKeyMissingError; } });
86
+ // `WATERX_INFRA` / `waterxQuoteCenterEndpoint` are the source's own infra table +
87
+ // read-plane accessor (mirrors `pythCoreHermesEndpoint`).
61
88
  // WaterX quote-center rule (first-party ed25519 signed batches; `feedWaterxRule`
62
89
  // stays internal to `aggregate.ts`).
63
90
  var waterx_rule_ts_1 = require("./rules/waterx-rule.js");
64
91
  Object.defineProperty(exports, "WaterxRule", { enumerable: true, get: function () { return waterx_rule_ts_1.WaterxRule; } });
65
92
  Object.defineProperty(exports, "parseSignedEnvelope", { enumerable: true, get: function () { return waterx_rule_ts_1.parseSignedEnvelope; } });
66
- // `resolveOracleRule` (rule-registry.ts) is NOT re-exported here it has one
67
- // production consumer (`refreshOraclePrices` below) and no external caller;
68
- // per repo convention ("no unused exports") it stays module-internal. Tests
69
- // import it directly from `./rule-registry.ts`. `OracleSourceNotImplementedError`
70
- // IS re-exported (the same `instanceof` reason as `OracleFeeSourceUnavailableError`
71
- // above) a consumer of `refreshOraclePrices` can catch it without importing
72
- // `resolveOracleRule` itself.
93
+ Object.defineProperty(exports, "BATCH_PRICE_INTENT", { enumerable: true, get: function () { return waterx_rule_ts_1.BATCH_PRICE_INTENT; } });
94
+ Object.defineProperty(exports, "WATERX_INFRA", { enumerable: true, get: function () { return waterx_rule_ts_1.WATERX_INFRA; } });
95
+ Object.defineProperty(exports, "waterxQuoteCenterEndpoint", { enumerable: true, get: function () { return waterx_rule_ts_1.waterxQuoteCenterEndpoint; } });
96
+ // Rule-owned payload accessor (kind-check + unwrap in one place) — never
97
+ // hand-cast the payload shape.
98
+ Object.defineProperty(exports, "waterxEnvelopeOf", { enumerable: true, get: function () { return waterx_rule_ts_1.waterxEnvelopeOf; } });
99
+ // `resolveOracleRule` is the ONE source→rule registry — exported so external
100
+ // consumers (e.g. a BE prefetch cache that keys per source and needs each
101
+ // source's `supportedTickers`/`fetchUpdateData`) resolve through it instead of
102
+ // hand-mirroring the map and drifting. `OracleSourceNotImplementedError` is
103
+ // its `instanceof`-able failure (same reason as `OracleFeeSourceUnavailableError`
104
+ // above).
73
105
  var rule_registry_ts_1 = require("./rule-registry.js");
74
106
  Object.defineProperty(exports, "OracleSourceNotImplementedError", { enumerable: true, get: function () { return rule_registry_ts_1.OracleSourceNotImplementedError; } });
107
+ Object.defineProperty(exports, "resolveOracleRule", { enumerable: true, get: function () { return rule_registry_ts_1.resolveOracleRule; } });
75
108
  // Aggregation orchestrator
76
109
  var aggregate_ts_1 = require("./aggregate.js");
77
110
  Object.defineProperty(exports, "aggregateTicker", { enumerable: true, get: function () { return aggregate_ts_1.aggregateTicker; } });
@@ -11,23 +11,37 @@
11
11
  * remain plain collector-feed helpers wired directly into `aggregate.ts`.
12
12
  *
13
13
  * This file defines the port only — routing IS wired: `aggregate.ts`'s
14
- * `refreshOraclePrices` selects the concrete rule per `host.oracleSource` via
15
- * `rule-registry.ts`, then drives fetch + `buildUpdateCalls` through this
16
- * port; `aggregate.ts` stays the sole orchestrator.
14
+ * `refreshOraclePrices` resolves a concrete rule per `host.oracleSources`
15
+ * entry via `rule-registry.ts`, then drives fetch + `buildUpdateCalls`
16
+ * through this port; `aggregate.ts` stays the sole orchestrator.
17
17
  */
18
18
  import type { Transaction, TransactionArgument } from "@mysten/sui/transactions";
19
19
  import type { OracleHost } from "./host.ts";
20
20
  import type { OracleFeeSource, PythCache } from "./pyth.ts";
21
21
  export type PriceUpdateRuleKind = "pyth_rule" | "pyth_lazer_rule" | "supra_rule" | "constant_rule" | "waterx_rule";
22
22
  /**
23
- * The subset of `PriceUpdateRuleKind`s selectable via a client's `oracleSource`
24
- * create option (see `OracleHost.oracleSource`) i.e. rules that can serve as
25
- * the on-chain price *update* leg `refreshOraclePrices` runs before aggregating.
26
- * `supra_rule` and `constant_rule` are auxiliary rules fed alongside whichever
27
- * source is selected (see `aggregateTicker`), not sources themselves. The SDK
28
- * never reads `process.env` consumers resolve their own env var to this type.
23
+ * The canonical list of selectable oracle sources the SINGLE authority the
24
+ * {@link OracleSource} union derives from (the value-list→union derive
25
+ * idiom of `unified-client.ts`'s `NON_CLIENT_FIRST`, plus `Object.freeze`
26
+ * so the immutability is RUNTIME truth: `as const` alone would let a JS
27
+ * consumer push into the array and desync the membership Set built from it
28
+ * in `source-list.ts`). Runtime membership checks and the `ORACLE_SOURCE`
29
+ * env parser live there, on `isOracleSource` / `parseOracleSourceList`.
30
+ * Only sources belong here: `supra_rule` and `constant_rule` are auxiliary
31
+ * rules fed alongside whichever sources are selected (see
32
+ * `aggregateTicker`), not sources themselves — the `satisfies` keeps
33
+ * entries inside `PriceUpdateRuleKind` but adding an auxiliary rule to this
34
+ * list is an (incorrect) editorial decision this comment exists to prevent.
29
35
  */
30
- export type OracleSource = "pyth_rule" | "pyth_lazer_rule" | "waterx_rule";
36
+ export declare const ORACLE_SOURCES: readonly ["pyth_rule", "pyth_lazer_rule", "waterx_rule"];
37
+ /**
38
+ * The kinds listable in a client's `oracleSource` create option (see
39
+ * `OracleHost.oracleSources`) — i.e. rules that can serve as the on-chain
40
+ * price *update* leg `refreshOraclePrices` runs before aggregating. Derived
41
+ * from {@link ORACLE_SOURCES}. The SDK never reads `process.env` — consumers
42
+ * resolve their own env var to this type.
43
+ */
44
+ export type OracleSource = (typeof ORACLE_SOURCES)[number];
31
45
  /**
32
46
  * Off-chain payload fetched by a rule, tagged by `kind` so a caller holding
33
47
  * several rules' results can tell them apart. `payload` is `unknown` here —
@@ -116,7 +130,14 @@ export interface UpdateDataProvider {
116
130
  get(source: OracleSource, tickers: string[]): Promise<RuleUpdateData | null>;
117
131
  }
118
132
  export interface PriceUpdateRule {
119
- readonly kind: PriceUpdateRuleKind;
133
+ /**
134
+ * `OracleSource`, not the wider `PriceUpdateRuleKind`: only selectable
135
+ * sources implement this port (`supra_rule`/`constant_rule` are plain
136
+ * collector-feed helpers), and the narrower type is what lets
137
+ * `refreshOraclePrices`'s per-source carry step switch exhaustively —
138
+ * adding a source without deciding its carry becomes a compile error.
139
+ */
140
+ readonly kind: OracleSource;
120
141
  /**
121
142
  * `true` when this rule's on-chain update leg charges a per-update fee
122
143
  * that must be paid from either a sponsor fund or `tx.gas` (Pyth Core:
@@ -12,12 +12,32 @@
12
12
  * remain plain collector-feed helpers wired directly into `aggregate.ts`.
13
13
  *
14
14
  * This file defines the port only — routing IS wired: `aggregate.ts`'s
15
- * `refreshOraclePrices` selects the concrete rule per `host.oracleSource` via
16
- * `rule-registry.ts`, then drives fetch + `buildUpdateCalls` through this
17
- * port; `aggregate.ts` stays the sole orchestrator.
15
+ * `refreshOraclePrices` resolves a concrete rule per `host.oracleSources`
16
+ * entry via `rule-registry.ts`, then drives fetch + `buildUpdateCalls`
17
+ * through this port; `aggregate.ts` stays the sole orchestrator.
18
18
  */
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.ORACLE_SOURCES = void 0;
20
21
  exports.assertRuleUpdateData = assertRuleUpdateData;
22
+ /**
23
+ * The canonical list of selectable oracle sources — the SINGLE authority the
24
+ * {@link OracleSource} union derives from (the value-list→union derive
25
+ * idiom of `unified-client.ts`'s `NON_CLIENT_FIRST`, plus `Object.freeze`
26
+ * so the immutability is RUNTIME truth: `as const` alone would let a JS
27
+ * consumer push into the array and desync the membership Set built from it
28
+ * in `source-list.ts`). Runtime membership checks and the `ORACLE_SOURCE`
29
+ * env parser live there, on `isOracleSource` / `parseOracleSourceList`.
30
+ * Only sources belong here: `supra_rule` and `constant_rule` are auxiliary
31
+ * rules fed alongside whichever sources are selected (see
32
+ * `aggregateTicker`), not sources themselves — the `satisfies` keeps
33
+ * entries inside `PriceUpdateRuleKind` but adding an auxiliary rule to this
34
+ * list is an (incorrect) editorial decision this comment exists to prevent.
35
+ */
36
+ exports.ORACLE_SOURCES = Object.freeze([
37
+ "pyth_rule",
38
+ "pyth_lazer_rule",
39
+ "waterx_rule",
40
+ ]);
21
41
  /**
22
42
  * Shared null → kind → shape guard ladder for a `PriceUpdateRule.buildUpdateCalls`
23
43
  * payload — every rule's `buildUpdateCalls` needs the exact same three checks,
@@ -14,8 +14,52 @@
14
14
  * 4. hot_potato_vector::destroy
15
15
  */
16
16
  import type { Transaction, TransactionArgument } from "@mysten/sui/transactions";
17
+ import type { Network } from "../constants.ts";
17
18
  import type { PythFetchPolicy } from "./config.ts";
18
19
  import type { OracleHost } from "./host.ts";
20
+ /**
21
+ * Everything the Core source needs that is not in the canonical
22
+ * `waterx-config` JSON lives HERE, in the source that consumes it —
23
+ * `client.pyth` carries only the caller's credential/fetch policy
24
+ * (see `PythAccessConfig`). No other oracle source reads this table:
25
+ * selecting `pyth_lazer_rule` (or any future source) must never touch a Core
26
+ * endpoint or Core state object, so nothing Core-shaped stays on the shared
27
+ * client to leak across sources.
28
+ *
29
+ * - `state_id` / `wormhole_state_id` — the Core Pyth + Wormhole state objects
30
+ * the on-chain update PTB reads (below).
31
+ * - `hermes_endpoint` — the Core Hermes REST base for
32
+ * `fetchPriceFeedsUpdateData`. Keyless today; auth-first after the Pyth Pro
33
+ * migration (post-2026-08-18) via `client.pyth.api_key`.
34
+ */
35
+ export interface PythCoreInfra {
36
+ state_id: string;
37
+ wormhole_state_id: string;
38
+ hermes_endpoint: string;
39
+ }
40
+ export declare const PYTH_CORE_INFRA: Record<Network, PythCoreInfra>;
41
+ /**
42
+ * The Core source's Hermes REST base for `network` — for consumers (BE/FE
43
+ * read planes) whose `ORACLE_SOURCE` fed set includes `'pyth_rule'`. A fed
44
+ * set WITHOUT `pyth_rule` reads the keyed Pyth Pro base instead — resolve
45
+ * through `resolveHermesReadEndpoint` (`oracle/read-plane.ts`) rather than
46
+ * branching by hand.
47
+ */
48
+ export declare function pythCoreHermesEndpoint(network: Network): string;
49
+ /**
50
+ * The Pyth Pro Hermes-compatible REST base — the documented drop-in
51
+ * replacement for the Core endpoint (`https://pyth.dourolabs.app/hermes/…`),
52
+ * IDENTICAL for every subscriber: only the `Authorization: Bearer` key
53
+ * (`client.pyth.api_key` / the `pythApiKey` create option) is
54
+ * account-specific, so the URL is SDK infra, not deployment config. One base
55
+ * for both networks — Pro has no testnet variant (Pyth feed ids are
56
+ * chain-agnostic; `hermes-beta` is a Core-testnet concern). Consumers with a
57
+ * proxy/self-hosted mirror override it per deployment via
58
+ * `resolveHermesReadEndpoint`'s `override` param.
59
+ */
60
+ export declare const PYTH_PRO_HERMES_ENDPOINT = "https://pyth.dourolabs.app/hermes";
61
+ /** Accessor mirror of {@link pythCoreHermesEndpoint} for the Pro base. */
62
+ export declare function pythProHermesEndpoint(): string;
19
63
  type PriceTableInfo = {
20
64
  id: string;
21
65
  fieldType: string;
@@ -15,7 +15,9 @@
15
15
  * 4. hot_potato_vector::destroy
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.OracleFeeSourceUnavailableError = exports.HermesEndpointRejectedAllFeedsError = exports.MISSING_FEED_MEMO_TTL_MS = exports.PythCache = void 0;
18
+ exports.OracleFeeSourceUnavailableError = exports.HermesEndpointRejectedAllFeedsError = exports.MISSING_FEED_MEMO_TTL_MS = exports.PythCache = exports.PYTH_PRO_HERMES_ENDPOINT = exports.PYTH_CORE_INFRA = void 0;
19
+ exports.pythCoreHermesEndpoint = pythCoreHermesEndpoint;
20
+ exports.pythProHermesEndpoint = pythProHermesEndpoint;
19
21
  exports.endpointSupportedFeedIds = endpointSupportedFeedIds;
20
22
  exports.__resetMissingFeedCacheForTest = __resetMissingFeedCacheForTest;
21
23
  exports.probeMissingFeeds = probeMissingFeeds;
@@ -25,6 +27,44 @@ exports.updatePythPrices = updatePythPrices;
25
27
  const bcs_1 = require("@mysten/bcs");
26
28
  const bcs_2 = require("@mysten/sui/bcs");
27
29
  const update_fetch_ts_1 = require("./update-fetch.js");
30
+ exports.PYTH_CORE_INFRA = {
31
+ MAINNET: {
32
+ state_id: "0x1f9310238ee9298fb703c3419030b35b22bb1cc37113e3bb5007c99aec79e5b8",
33
+ wormhole_state_id: "0xaeab97f96cf9877fee2883315d459552b2b921edc16d7ceac6eab944dd88919c",
34
+ hermes_endpoint: "https://hermes.pyth.network",
35
+ },
36
+ TESTNET: {
37
+ state_id: "0x243759059f4c3111179da5878c12f68d612c21a8d54d85edc86164bb18be1c7c",
38
+ wormhole_state_id: "0x31358d198147da50db32eda2562951d53973a0c0ad5ed738e9b17d88b213d790",
39
+ hermes_endpoint: "https://hermes-beta.pyth.network",
40
+ },
41
+ };
42
+ /**
43
+ * The Core source's Hermes REST base for `network` — for consumers (BE/FE
44
+ * read planes) whose `ORACLE_SOURCE` fed set includes `'pyth_rule'`. A fed
45
+ * set WITHOUT `pyth_rule` reads the keyed Pyth Pro base instead — resolve
46
+ * through `resolveHermesReadEndpoint` (`oracle/read-plane.ts`) rather than
47
+ * branching by hand.
48
+ */
49
+ function pythCoreHermesEndpoint(network) {
50
+ return exports.PYTH_CORE_INFRA[network].hermes_endpoint;
51
+ }
52
+ /**
53
+ * The Pyth Pro Hermes-compatible REST base — the documented drop-in
54
+ * replacement for the Core endpoint (`https://pyth.dourolabs.app/hermes/…`),
55
+ * IDENTICAL for every subscriber: only the `Authorization: Bearer` key
56
+ * (`client.pyth.api_key` / the `pythApiKey` create option) is
57
+ * account-specific, so the URL is SDK infra, not deployment config. One base
58
+ * for both networks — Pro has no testnet variant (Pyth feed ids are
59
+ * chain-agnostic; `hermes-beta` is a Core-testnet concern). Consumers with a
60
+ * proxy/self-hosted mirror override it per deployment via
61
+ * `resolveHermesReadEndpoint`'s `override` param.
62
+ */
63
+ exports.PYTH_PRO_HERMES_ENDPOINT = "https://pyth.dourolabs.app/hermes";
64
+ /** Accessor mirror of {@link pythCoreHermesEndpoint} for the Pro base. */
65
+ function pythProHermesEndpoint() {
66
+ return exports.PYTH_PRO_HERMES_ENDPOINT;
67
+ }
28
68
  class PythCache {
29
69
  pythStateInfo;
30
70
  wormholePackageId;
@@ -439,7 +479,7 @@ async function getPriceTableInfo(client, pythStateId, cache) {
439
479
  }
440
480
  async function getPriceFeedObjectId(client, table, feedId, cache, pythStateId) {
441
481
  const normalized = feedId.replace(/^0x/, "");
442
- const cacheKey = pythStateId ? `${pythStateId}:${normalized}` : normalized;
482
+ const cacheKey = `${pythStateId}:${normalized}`;
443
483
  if (cache?.priceFeedObjectIdCache.has(cacheKey)) {
444
484
  return cache.priceFeedObjectIdCache.get(cacheKey);
445
485
  }
@@ -535,7 +575,9 @@ async function buildPythPriceUpdateCalls(tx, host, updates, feedIds, opts) {
535
575
  throw new OracleFeeSourceUnavailableError();
536
576
  }
537
577
  const cache = opts?.cache;
538
- const pyth = host.pyth;
578
+ // Core on-chain infra is the `pyth_rule` source's own table, keyed by the
579
+ // host's network — `client.pyth` carries only the caller's credential/policy.
580
+ const pyth = exports.PYTH_CORE_INFRA[host.network];
539
581
  const [stateInfo, wormholePackageId, table] = await Promise.all([
540
582
  getPythStateInfo(host.grpcClient, pyth.state_id, cache),
541
583
  getWormholePackageId(host.grpcClient, pyth.wormhole_state_id, cache),
@@ -593,9 +635,9 @@ async function buildPythPriceUpdateCalls(tx, host, updates, feedIds, opts) {
593
635
  }
594
636
  /** All-in-one: fetch from Hermes, append update calls. Returns PriceInfoObject IDs. */
595
637
  async function updatePythPrices(tx, host, feedIds, opts) {
596
- // `host.pyth` is the Pyth Core infra (fixed per network) plus the caller's
597
- // api_key/fetch endpoint, credential and policy all come from it.
598
- const endpoint = host.pyth.hermes_endpoint;
638
+ // The Core source's own Hermes endpoint (per-network, rule-owned table);
639
+ // credential + fetch policy are the caller-supplied `client.pyth` slice.
640
+ const endpoint = exports.PYTH_CORE_INFRA[host.network].hermes_endpoint;
599
641
  const updates = await fetchPriceFeedsUpdateData(endpoint, feedIds, {
600
642
  apiKey: host.pyth.api_key,
601
643
  fetch: host.pyth.fetch,
@@ -0,0 +1,70 @@
1
+ /**
2
+ * `read-plane.ts` — per-source READ-plane resolution: which of a caller's
3
+ * tickers a source can PRICE off-chain, and with which ids. The write plane
4
+ * (`PriceUpdateRule` + each source's `feeds` block) and the read plane are
5
+ * DIFFERENT namespaces for the pyth sources: Lazer WRITES with the integer
6
+ * ids in `pyth_lazer_rule.feeds`, but every Hermes-compatible price READ is
7
+ * keyed by the hex ids only `pyth_rule.feeds` carries. That cross-block
8
+ * dependency is a config invariant ("a lazer-fed ticker must also carry a
9
+ * Core hex entry to be read-visible"), and it lives HERE, once — consumers
10
+ * (FE/BE price facades) resolve through this instead of hardcoding which
11
+ * sources share which feeds namespace.
12
+ */
13
+ import type { Network } from "../constants.ts";
14
+ import type { OracleHost } from "./host.ts";
15
+ import type { OracleSource } from "./price-update-rule.ts";
16
+ /**
17
+ * One source's read plan for a requested ticker set.
18
+ *
19
+ * - `plane: "hermes"` (pyth sources) — price via a Hermes-compatible REST
20
+ * endpoint, one entry per servable ticker mapped to its HEX feed id. The
21
+ * endpoint to execute against is {@link resolveHermesReadEndpoint} — the
22
+ * plan (ids) plus that resolver (host) is the complete read contract.
23
+ * - `plane: "quote_center"` (waterx) — price via the quote-center symbols
24
+ * api, keyed by ticker; served set = the `waterx_rule.feeds` block. An
25
+ * ABSENT block (source listed, package missing from the loaded config)
26
+ * serves NOTHING: claiming tickers would silently reroute reads to the
27
+ * quote-center — it happily serves symbols regardless of on-chain config —
28
+ * and swallow tickers a later-listed source could price. The
29
+ * misconfiguration is caught loudly by the consumer's feeds assert at
30
+ * client creation instead.
31
+ * - `unreadable` — requested tickers this source WRITES on-chain (its update
32
+ * leg serves them) but its read plane cannot price: the silent-invisibility
33
+ * trap (e.g. a lazer-fed ticker with no `pyth_rule.feeds` hex entry).
34
+ * Callers should surface these loudly; empty for sources whose write and
35
+ * read namespaces coincide.
36
+ */
37
+ export type OracleReadPlan = {
38
+ unreadable: string[];
39
+ } & ({
40
+ plane: "hermes";
41
+ feedIdByTicker: Map<string, string>;
42
+ } | {
43
+ plane: "quote_center";
44
+ tickers: string[];
45
+ });
46
+ /**
47
+ * Resolve `source`'s read plan for `tickers`. Pure config lookup — no
48
+ * network, no endpoint resolution (endpoints come from
49
+ * `pythCoreHermesEndpoint` / `waterxQuoteCenterEndpoint` / the deployment's
50
+ * own env). A ticker absent from the returned plan is simply not servable by
51
+ * THIS source's read plane — callers decide how to degrade (typically: ask
52
+ * the next source in their `ORACLE_SOURCE` list, then omit).
53
+ */
54
+ /**
55
+ * The Hermes-compatible REST base a deployment's hermes-plane read plans
56
+ * execute against — the endpoint half of the read contract
57
+ * ({@link resolveOracleReadPlan} is the ids half):
58
+ *
59
+ * - `pyth_rule` in the fed set → the Core source's own keyless endpoint
60
+ * (`pythCoreHermesEndpoint(network)`).
61
+ * - otherwise → `override` when the deployment set one (a proxy or
62
+ * self-hosted mirror), else the documented Pyth Pro base
63
+ * (`pythProHermesEndpoint()` — identical for every subscriber; auth is the
64
+ * caller's `pythApiKey` Bearer, not a per-deployment URL).
65
+ *
66
+ * Total — never throws, never falls back Core-ward: a fed set without
67
+ * `pyth_rule` reads Pro (or the override), full stop.
68
+ */
69
+ export declare function resolveHermesReadEndpoint(network: Network, sources: readonly OracleSource[], override?: string): string;
70
+ export declare function resolveOracleReadPlan(host: OracleHost, source: OracleSource, tickers: string[]): OracleReadPlan;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ /**
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.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.resolveHermesReadEndpoint = resolveHermesReadEndpoint;
16
+ exports.resolveOracleReadPlan = resolveOracleReadPlan;
17
+ const record_ts_1 = require("../utils/record.js");
18
+ const pyth_ts_1 = require("./pyth.js");
19
+ /**
20
+ * 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.
41
+ */
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
+ function resolveOracleReadPlan(host, source, tickers) {
48
+ switch (source) {
49
+ case "pyth_rule":
50
+ 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;
55
+ const feedIdByTicker = new Map();
56
+ for (const ticker of tickers) {
57
+ const feedId = (0, record_ts_1.ownEntry)(hexFeeds, ticker)?.feed_id;
58
+ if (feedId !== undefined)
59
+ feedIdByTicker.set(ticker, feedId);
60
+ }
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 };
66
+ }
67
+ case "waterx_rule": {
68
+ // Absent feeds block ⇒ serves nothing (see the OracleReadPlan doc) —
69
+ // never claim tickers the config doesn't name. `ownEntry` (own-keys-
70
+ // only, never the `in` operator or a bare bracket read) so a
71
+ // prototype-key ticker can't count as feeds-listed and poison the
72
+ // quote-center batch (which 404s whole batches on unknown symbols).
73
+ const feeds = host.config.packages.waterx_rule?.feeds;
74
+ return {
75
+ plane: "quote_center",
76
+ tickers: tickers.filter((ticker) => (0, record_ts_1.ownEntry)(feeds, ticker) !== undefined),
77
+ unreadable: [],
78
+ };
79
+ }
80
+ default: {
81
+ const exhausted = source;
82
+ throw new Error(`resolveOracleReadPlan: unhandled OracleSource '${String(exhausted)}'`);
83
+ }
84
+ }
85
+ }
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * `rule-registry.ts` — maps a client-selected `OracleSource` to its concrete
3
- * `PriceUpdateRule` implementation. `refreshOraclePrices` (`aggregate.ts`) is
4
- * the only production caller; this is the one place `OracleSource` values are
5
- * wired to a rule instance. Selection is driven purely by the value passed in
6
- * (ultimately `OracleHost.oracleSource`, the `oracleSource` client create
3
+ * `PriceUpdateRule` implementation; the ONE place `OracleSource` values are
4
+ * wired to a rule instance. `refreshOraclePrices` (`aggregate.ts`) resolves
5
+ * every `host.oracleSources` entry through it, and consumers (e.g. a BE
6
+ * prefetch cache keying per source) import `resolveOracleRule` rather than
7
+ * hand-mirroring this map. Selection is driven purely by the value passed in
8
+ * (ultimately `OracleHost.oracleSources`, the `oracleSource` client create
7
9
  * option) — never by a config JSON `enabled` flag and never by `process.env`.
8
10
  *
9
11
  * Each source is self-contained: it owns its own infra + config and does NOT
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  /**
3
3
  * `rule-registry.ts` — maps a client-selected `OracleSource` to its concrete
4
- * `PriceUpdateRule` implementation. `refreshOraclePrices` (`aggregate.ts`) is
5
- * the only production caller; this is the one place `OracleSource` values are
6
- * wired to a rule instance. Selection is driven purely by the value passed in
7
- * (ultimately `OracleHost.oracleSource`, the `oracleSource` client create
4
+ * `PriceUpdateRule` implementation; the ONE place `OracleSource` values are
5
+ * wired to a rule instance. `refreshOraclePrices` (`aggregate.ts`) resolves
6
+ * every `host.oracleSources` entry through it, and consumers (e.g. a BE
7
+ * prefetch cache keying per source) import `resolveOracleRule` rather than
8
+ * hand-mirroring this map. Selection is driven purely by the value passed in
9
+ * (ultimately `OracleHost.oracleSources`, the `oracleSource` client create
8
10
  * option) — never by a config JSON `enabled` flag and never by `process.env`.
9
11
  *
10
12
  * Each source is self-contained: it owns its own infra + config and does NOT
@@ -9,6 +9,7 @@
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.PythCoreRule = void 0;
12
+ const record_ts_1 = require("../../utils/record.js");
12
13
  const price_update_rule_ts_1 = require("../price-update-rule.js");
13
14
  const pyth_ts_1 = require("../pyth.js");
14
15
  /**
@@ -36,10 +37,10 @@ exports.PythCoreRule = {
36
37
  async fetchUpdateData(host, tickers) {
37
38
  if (tickers.length === 0)
38
39
  return null;
39
- // `host.pyth` is this source's own infra — the fixed per-network Core
40
- // Pyth block plus the caller's api_key/fetch. Endpoint, credential and
41
- // retry/timeout policy all come from it.
42
- const endpoint = host.pyth.hermes_endpoint;
40
+ // Endpoint is this source's own per-network infra (`PYTH_CORE_INFRA`);
41
+ // credential + retry/timeout policy are the caller-supplied `client.pyth`
42
+ // access slice.
43
+ const endpoint = (0, pyth_ts_1.pythCoreHermesEndpoint)(host.network);
43
44
  const feedIds = tickers.map((ticker) => host.getPythFeed(ticker).feed_id);
44
45
  const updates = await (0, pyth_ts_1.fetchPriceFeedsUpdateData)(endpoint, feedIds, {
45
46
  apiKey: host.pyth.api_key,
@@ -79,7 +80,7 @@ exports.PythCoreRule = {
79
80
  for (const ticker of tickers) {
80
81
  // Same lookup as `host.getPythFeed(ticker)` minus its throw — an
81
82
  // unlisted ticker is a miss here, not an error.
82
- const feedId = host.config.packages.pyth_rule?.feeds?.[ticker]?.feed_id;
83
+ const feedId = (0, record_ts_1.ownEntry)(host.config.packages.pyth_rule?.feeds, ticker)?.feed_id;
83
84
  if (feedId === undefined || !packedFeedIds.has(feedId))
84
85
  return null;
85
86
  feedIds.push(feedId);
@@ -8,8 +8,29 @@
8
8
  * `Update` PTB value back through a `RuleUpdateHandle` for the feed calls.
9
9
  */
10
10
  import type { Transaction, TransactionArgument } from "@mysten/sui/transactions";
11
+ import type { Network } from "../../constants.ts";
11
12
  import type { OracleHost } from "../host.ts";
12
13
  import { type PriceUpdateRule } from "../price-update-rule.ts";
14
+ /**
15
+ * Pyth Lazer (Pyth Pro) external infra — owned by THIS source, by network.
16
+ * Per-network constants for infrastructure Pyth operates (not part of the
17
+ * `waterx-config` JSON), co-located with the only rule that reads them —
18
+ * no other oracle source ever touches a Lazer endpoint or verifier.
19
+ *
20
+ * - `endpoint` — Lazer HTTP API base; signed updates come from
21
+ * `POST /v1/latest_price` (Bearer-authenticated). The service is
22
+ * network-agnostic (one signed payload verifies on any chain that trusts the
23
+ * 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.
29
+ */
30
+ export declare const LAZER_INFRA: Record<Network, {
31
+ endpoint: string;
32
+ verifier_package: string;
33
+ }>;
13
34
  /** `pyth_lazer_rule`'s narrowed `RuleUpdateData.payload` shape. */
14
35
  export interface PythLazerUpdatePayload {
15
36
  /** One signed `leEcdsa` message carrying every requested feed. */