@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
@@ -11,7 +11,6 @@
11
11
  */
12
12
  import type { BasePackageEntry } from "../account/config.ts";
13
13
  import type { BaseLineConfig } from "../base-client.ts";
14
- import type { Network } from "../constants.ts";
15
14
  import type { FetchPolicy } from "./update-fetch.ts";
16
15
  export interface PythRulePackage extends BasePackageEntry {
17
16
  config: string;
@@ -31,7 +30,7 @@ export interface PythSponsorRulePackage extends BasePackageEntry {
31
30
  *
32
31
  * `enabled` mirrors the JSON field verbatim but MUST NOT be read for routing —
33
32
  * which rule prices a ticker is decided solely by the client's `oracleSource`
34
- * create option (see `OracleHost.oracleSource`), never by this flag or any
33
+ * create option (see `OracleHost.oracleSources`), never by this flag or any
35
34
  * other config value.
36
35
  */
37
36
  export interface PythLazerRulePackage extends BasePackageEntry {
@@ -99,11 +98,12 @@ export interface WaterxRuleFeedEntry {
99
98
  * Read by `WaterxRule` (`rules/waterx-rule.ts`): `feeds` for ticker support,
100
99
  * `config`/`enclave_config`/`enclave` for the `collect_batch_latest` call,
101
100
  * `published_at` for the package address. The off-chain signed price is pulled
102
- * from the quote-center (endpoint from {@link WATERX_DEFAULTS}), not this JSON.
101
+ * from the quote-center (endpoint from the rule-owned `WATERX_INFRA` table in
102
+ * `rules/waterx-rule.ts`), not this JSON.
103
103
  *
104
104
  * `enabled` mirrors the JSON field verbatim but MUST NOT be read for routing —
105
105
  * which rule prices a ticker is decided solely by the client's `oracleSource`
106
- * create option (see `OracleHost.oracleSource`), mirroring `pyth_lazer_rule`.
106
+ * create option (see `OracleHost.oracleSources`), mirroring `pyth_lazer_rule`.
107
107
  */
108
108
  export interface WaterxRulePackage extends BasePackageEntry {
109
109
  /** Shared `waterx_rule::Config` (per-symbol on-chain feed_config). */
@@ -137,17 +137,6 @@ export interface OraclePackages {
137
137
  waterx_rule?: WaterxRulePackage;
138
138
  waterx_oracle: WaterxOraclePackage;
139
139
  }
140
- /**
141
- * Resolved Pyth Core infra as it lives on `client.pyth` — NOT a config-JSON
142
- * shape. `state_id` / `wormhole_state_id` / `hermes_endpoint` come verbatim
143
- * from the fixed per-network constant ({@link PYTH_DEFAULTS}); `api_key` /
144
- * `fetch` are layered on from the caller's `pythApiKey` / `pythFetch` create
145
- * options. None of it is sourced from the canonical `waterx-config` JSON — the
146
- * SDK never reads a `pyth` block there (a Bearer secret has no place in a
147
- * public CDN document). The infra is the same for every `oracleSource`; the
148
- * `pyth_lazer_rule` source reads only the `api_key` / `fetch` from here and
149
- * gets its on-chain infra from {@link LAZER_DEFAULTS} + config instead.
150
- */
151
140
  /**
152
141
  * The caller-tunable subset of `fetchWithPolicy`'s policy exposed on the
153
142
  * `pythFetch` create option and `client.pyth.fetch` — the retry/timeout budget
@@ -160,18 +149,24 @@ export type PythFetchPolicy = {
160
149
  timeoutMs?: number;
161
150
  retries?: number;
162
151
  };
163
- export interface PythInfraConfig {
164
- state_id: string;
165
- wormhole_state_id: string;
166
- hermes_endpoint: string;
152
+ /**
153
+ * `client.pyth` — ONLY the caller-supplied Pyth credential + fetch policy,
154
+ * shared by the Pyth-family rules (`pyth_rule`, `pyth_lazer_rule`). It carries
155
+ * NO endpoints and NO on-chain object ids: every oracle source owns its own
156
+ * infra, co-located with its rule (`PYTH_CORE_INFRA` in `oracle/pyth.ts`;
157
+ * the Lazer constants inside `rules/pyth-lazer-rule.ts`). A non-Pyth source
158
+ * never reads this slice.
159
+ * Nothing here is sourced from the canonical `waterx-config` JSON — a Bearer
160
+ * secret has no place in a public CDN document.
161
+ */
162
+ export interface PythAccessConfig {
167
163
  /**
168
- * Pyth Pro / Lazer access token (`Authorization: Bearer …`) for
164
+ * Pyth access token (`Authorization: Bearer …`). Required by
169
165
  * `PythLazerRule`'s signed-update fetch — Lazer is auth-first, so there is
170
- * no keyless default. Optional: Pyth-Core-only deployments never need it.
171
- * Supplied via the `pythApiKey` create option (the SDK never reads
172
- * `process.env` or the config JSON). Absent when a lazer-routed fetch runs →
173
- * `LazerApiKeyMissing` is thrown at fetch time. As of the Pyth Pro
174
- * migration (post-2026-08-18, per
166
+ * no keyless default; absent when a lazer-routed fetch runs
167
+ * `LazerApiKeyMissing` is thrown at fetch time. Supplied via the
168
+ * `pythApiKey` create option (the SDK never reads `process.env` or the
169
+ * config JSON). As of the Pyth Pro migration (post-2026-08-18, per
175
170
  * https://docs.pyth.network/price-feeds/core/upgrade) this is ALSO required
176
171
  * for `pyth_rule`'s Hermes fetch (`fetchPriceFeedsUpdateData`) — see
177
172
  * `fetch` below.
@@ -187,66 +182,37 @@ export interface PythInfraConfig {
187
182
  */
188
183
  fetch?: PythFetchPolicy;
189
184
  }
190
- export declare const PYTH_DEFAULTS: Record<Network, PythInfraConfig>;
191
- /**
192
- * Pyth Lazer (Pyth Pro) external infra the `PythLazerRule` needs, by network.
193
- * Mirrors {@link PYTH_DEFAULTS}: per-network constants for infrastructure Pyth
194
- * operates (not part of the `waterx-config` JSON). A fuller `PYTH_INFRA`
195
- * restructure is deferred — this stays a minimal map until then.
196
- *
197
- * - `endpoint` — Lazer HTTP API base; signed updates come from
198
- * `POST /v1/latest_price` (Bearer-authenticated). The service is
199
- * network-agnostic (one signed payload verifies on any chain that trusts the
200
- * Lazer signers), so both networks share the production host.
201
- * - `verifier_package` — the Sui package carrying
202
- * `pyth_lazer::parse_and_verify_le_ecdsa_update`. Per-network: testnet is
203
- * still the original v1 publish; mainnet is the v2-upgraded package (which
204
- * still exposes the v1 entry `pyth_lazer_rule` binds). Values mirror the
205
- * contract repo's `pyth_lazer_rule/Move.toml` published-at pins.
206
- */
207
- export declare const LAZER_DEFAULTS: Record<Network, {
208
- endpoint: string;
209
- verifier_package: string;
210
- }>;
211
185
  /**
212
- * Resolved WaterX quote-center infra for `WaterxRule` — the network default
213
- * from {@link WATERX_DEFAULTS}, with both fields overridable at client init
214
- * (`waterxEndpoint` / `waterxFetch`).
186
+ * `client.waterx` ONLY the caller-supplied quote-center overrides for
187
+ * `WaterxRule`, mirroring {@link PythAccessConfig}: no resolved infra lives on
188
+ * the client. When a field is unset the rule resolves it against its OWN
189
+ * per-network table (`WATERX_INFRA` in `rules/waterx-rule.ts`) — no other
190
+ * source's endpoint or policy is ever consulted.
215
191
  *
216
- * The override exists because this is the one oracle source a BROWSER fetches
217
- * itself: the rule pulls the signed envelope from the page, so it is subject to
218
- * the quote-center deployment's CORS allowlist. A front end whose origin is not
219
- * on that list — or one that must route egress through its own backend — points
220
- * `endpoint` at a same-origin proxy (or supplies `fetch.fetchImpl`) instead of
221
- * being locked to the hardcoded host.
192
+ * The endpoint override exists because this is the one oracle source a BROWSER
193
+ * fetches itself: the rule pulls the signed envelope from the page, so it is
194
+ * subject to the quote-center deployment's CORS allowlist. A front end whose
195
+ * origin is not on that list — or one that must route egress through its own
196
+ * backend — points `endpoint` at a same-origin proxy (or supplies
197
+ * `fetch.fetchImpl`) instead of being locked to the default host.
222
198
  */
223
- export interface WaterxInfraConfig {
199
+ export interface WaterxAccessConfig {
224
200
  /**
225
- * Quote-center base URL. A base PATH is preserved — the rule appends via
226
- * `joinEndpointPath`, so `https://app.example/api/quote-center` resolves to
201
+ * Quote-center base URL override (`waterxEndpoint` create option). A base
202
+ * PATH is preserved — the rule appends via `joinEndpointPath`, so
203
+ * `https://app.example/api/quote-center` resolves to
227
204
  * `…/api/quote-center/v1/quotes/update` and a proxy route is not rewritten
228
205
  * away. A trailing slash is trimmed.
229
206
  */
230
- endpoint: string;
207
+ endpoint?: string;
231
208
  /**
232
209
  * Retry/timeout policy (and `fetchImpl`) for the quote-center fetch — see
233
210
  * `fetchWithPolicy` (`./update-fetch.ts`). Supplied via the `waterxFetch`
234
- * create option. When unset the rule falls back to the shared `pyth.fetch`
235
- * policy, then to `fetchWithPolicy`'s defaults (15s timeout, 2 retries).
211
+ * create option. Falls back to `fetchWithPolicy`'s built-in defaults (15s
212
+ * timeout, 2 retries) when unset never to another source's policy.
236
213
  */
237
214
  fetch?: FetchPolicy;
238
215
  }
239
- /**
240
- * WaterX quote-center base URL by network — the first-party TEE-signed price
241
- * hub `WaterxRule` pulls from (`GET /v1/quotes/update?symbols=…`). Mirrors
242
- * {@link LAZER_DEFAULTS}: infra WaterX operates, not part of the `waterx-config`
243
- * JSON. Public read (no auth), so there is no api_key. `endpoint` has no
244
- * trailing slash — the rule appends the path.
245
- *
246
- * These are DEFAULTS, not a hard pin: a consumer overrides them per client via
247
- * `waterxEndpoint` / `waterxFetch` (resolved onto `client.waterx`).
248
- */
249
- export declare const WATERX_DEFAULTS: Record<Network, WaterxInfraConfig>;
250
216
  /**
251
217
  * The narrow config shape the oracle/refresh code needs. `WaterXConfig`
252
218
  * (the perp line's full config) is assignable to this, so `PerpClient` satisfies
@@ -9,58 +9,4 @@
9
9
  * depends on this shared schema instead of the perp line's full config —
10
10
  * mirrors the earlier account-config hoist.
11
11
  */
12
- export const PYTH_DEFAULTS = {
13
- MAINNET: {
14
- state_id: "0x1f9310238ee9298fb703c3419030b35b22bb1cc37113e3bb5007c99aec79e5b8",
15
- wormhole_state_id: "0xaeab97f96cf9877fee2883315d459552b2b921edc16d7ceac6eab944dd88919c",
16
- hermes_endpoint: "https://hermes.pyth.network",
17
- },
18
- TESTNET: {
19
- state_id: "0x243759059f4c3111179da5878c12f68d612c21a8d54d85edc86164bb18be1c7c",
20
- wormhole_state_id: "0x31358d198147da50db32eda2562951d53973a0c0ad5ed738e9b17d88b213d790",
21
- hermes_endpoint: "https://hermes-beta.pyth.network",
22
- },
23
- };
24
- // ============================================================================
25
- // Pyth Lazer — external infra, defaults by network
26
- // ============================================================================
27
- /**
28
- * Pyth Lazer (Pyth Pro) external infra the `PythLazerRule` needs, by network.
29
- * Mirrors {@link PYTH_DEFAULTS}: per-network constants for infrastructure Pyth
30
- * operates (not part of the `waterx-config` JSON). A fuller `PYTH_INFRA`
31
- * restructure is deferred — this stays a minimal map until then.
32
- *
33
- * - `endpoint` — Lazer HTTP API base; signed updates come from
34
- * `POST /v1/latest_price` (Bearer-authenticated). The service is
35
- * network-agnostic (one signed payload verifies on any chain that trusts the
36
- * Lazer signers), so both networks share the production host.
37
- * - `verifier_package` — the Sui package carrying
38
- * `pyth_lazer::parse_and_verify_le_ecdsa_update`. Per-network: testnet is
39
- * still the original v1 publish; mainnet is the v2-upgraded package (which
40
- * still exposes the v1 entry `pyth_lazer_rule` binds). Values mirror the
41
- * contract repo's `pyth_lazer_rule/Move.toml` published-at pins.
42
- */
43
- export const LAZER_DEFAULTS = {
44
- MAINNET: {
45
- endpoint: "https://pyth-lazer.dourolabs.app",
46
- verifier_package: "0xefbfd064480777699fd9c557a5804d72ace7bc82661fdc8d1f1a44ea6d92ee10",
47
- },
48
- TESTNET: {
49
- endpoint: "https://pyth-lazer.dourolabs.app",
50
- verifier_package: "0xf5bd2141967507050a91b58de3d95e77c432cd90d1799ee46effc27430a68c21",
51
- },
52
- };
53
- /**
54
- * WaterX quote-center base URL by network — the first-party TEE-signed price
55
- * hub `WaterxRule` pulls from (`GET /v1/quotes/update?symbols=…`). Mirrors
56
- * {@link LAZER_DEFAULTS}: infra WaterX operates, not part of the `waterx-config`
57
- * JSON. Public read (no auth), so there is no api_key. `endpoint` has no
58
- * trailing slash — the rule appends the path.
59
- *
60
- * These are DEFAULTS, not a hard pin: a consumer overrides them per client via
61
- * `waterxEndpoint` / `waterxFetch` (resolved onto `client.waterx`).
62
- */
63
- export const WATERX_DEFAULTS = {
64
- MAINNET: { endpoint: "https://quote-center.waterx.app" },
65
- TESTNET: { endpoint: "https://quote-center-staging.waterx.app" },
66
- };
12
+ export {};
@@ -9,37 +9,41 @@
9
9
  */
10
10
  import type { SuiGrpcClient } from "@mysten/sui/grpc";
11
11
  import type { Network } from "../constants.ts";
12
- import type { OracleConfig, PythInfraConfig, WaterxInfraConfig } from "./config.ts";
12
+ import type { OracleConfig, PythAccessConfig, WaterxAccessConfig } from "./config.ts";
13
13
  import type { OracleSource } from "./price-update-rule.ts";
14
14
  export interface OracleHost {
15
- /** Sui network this client targets — selects per-network external-infra defaults (e.g. `LAZER_DEFAULTS`). */
15
+ /** Sui network this client targets — each rule keys its OWN infra table by it (`PYTH_CORE_INFRA`, `LAZER_INFRA`). */
16
16
  readonly network: Network;
17
17
  /** Oracle slice of the canonical `waterx-config` JSON (rule packages + per-ticker feeds). */
18
18
  readonly config: OracleConfig;
19
- /** External Pyth/Wormhole/Hermes infra fixed per `(network, generation)`; api_key/fetch layered from create options. */
20
- readonly pyth: PythInfraConfig;
19
+ /** Caller-supplied Pyth credential + fetch policy (create options) NO endpoints, NO object ids. */
20
+ readonly pyth: PythAccessConfig;
21
21
  /**
22
- * WaterX quote-center infra for `WaterxRule` — endpoint + fetch policy,
23
- * resolved from the `waterxEndpoint` / `waterxFetch` create options.
24
- *
25
- * OPTIONAL so an existing host stays a valid `OracleHost`: when absent the
26
- * rule falls back to `WATERX_DEFAULTS[network]`. This is the hook a browser
27
- * consumer uses to route the quote-center fetch through a same-origin proxy
28
- * (`endpoint`) or its own transport (`fetch.fetchImpl`) — that request is
29
- * made from the page, so it is bound by the quote-center's CORS allowlist.
22
+ * Caller-supplied WaterX quote-center overrides for `WaterxRule`
23
+ * (`waterxEndpoint` / `waterxFetch` create options) — access-only, mirroring
24
+ * `pyth` above. OPTIONAL so an existing host stays a valid `OracleHost`;
25
+ * unset fields resolve against the rule's own `WATERX_INFRA[network]` table.
26
+ * This is the hook a browser consumer uses to route the quote-center fetch
27
+ * through a same-origin proxy (`endpoint`) or its own transport
28
+ * (`fetch.fetchImpl`) — that request is made from the page, so it is bound
29
+ * by the quote-center's CORS allowlist.
30
30
  */
31
- readonly waterx?: WaterxInfraConfig;
31
+ readonly waterx?: WaterxAccessConfig;
32
32
  /** gRPC client for the on-chain reads the Pyth update path needs. */
33
33
  readonly grpcClient: SuiGrpcClient;
34
34
  /**
35
- * Client-selected oracle rule source for `refreshOraclePrices`'s on-chain
36
- * update leg resolved at client creation from the `oracleSource` create
37
- * option (default `'pyth_rule'`). Routing is driven by this value ALONE:
38
- * never by a config JSON `enabled` flag (e.g. a future `pyth_lazer_rule.enabled`)
39
- * and never by `process.env` the SDK never reads it; consumers (BE/FE) wire
40
- * this option from their own env var.
35
+ * The FED SET for `refreshOraclePrices`'s update legs — the REQUIRED
36
+ * `oracleSource` create option normalized to a non-empty, deduped list.
37
+ * Every listed source's data is fetched and fed in one build; the chain's
38
+ * per-ticker weight tables decide which contributions count (feeding an
39
+ * unweighted rule is dropped on-chain; starving a weighted one aborts), so
40
+ * during weight migrations the list stays a SUPERSET of every ticker's
41
+ * weighted set. Routing is driven by this value ALONE: never by a config
42
+ * JSON `enabled` flag and never by `process.env` — the SDK never reads it;
43
+ * consumers (BE/FE) wire this option from their own env var
44
+ * (`ORACLE_SOURCE`, comma-separated).
41
45
  */
42
- readonly oracleSource: OracleSource;
46
+ readonly oracleSources: readonly OracleSource[];
43
47
  /** True when `ticker` is priced by `constant_rule`. */
44
48
  isConstantTicker(ticker: string): boolean;
45
49
  /** The `supra_rule` config when deployed, enabled, and fully wired; else `undefined`. */
@@ -18,15 +18,19 @@
18
18
  export type { OracleHost } from "./host.ts";
19
19
  export { FetchPolicyError, fetchWithPolicy, joinEndpointPath } from "./update-fetch.ts";
20
20
  export type { FetchPolicy } from "./update-fetch.ts";
21
- export { PythCache, fetchPriceFeedsUpdateData, endpointSupportedFeedIds, probeMissingFeeds, buildPythPriceUpdateCalls, updatePythPrices, HermesEndpointRejectedAllFeedsError, MISSING_FEED_MEMO_TTL_MS, OracleFeeSourceUnavailableError, } from "./pyth.ts";
21
+ export { PythCache, fetchPriceFeedsUpdateData, endpointSupportedFeedIds, probeMissingFeeds, buildPythPriceUpdateCalls, pythCoreHermesEndpoint, pythProHermesEndpoint, PYTH_PRO_HERMES_ENDPOINT, updatePythPrices, HermesEndpointRejectedAllFeedsError, MISSING_FEED_MEMO_TTL_MS, OracleFeeSourceUnavailableError, } from "./pyth.ts";
22
22
  export type { OracleFeeSource } from "./pyth.ts";
23
23
  export type { PriceUpdateRule, PriceUpdateRuleKind, RuleUpdateData, RuleUpdateHandle, BuildUpdateOpts, OracleSource, UpdateDataProvider, } from "./price-update-rule.ts";
24
+ export { ORACLE_SOURCES } from "./price-update-rule.ts";
25
+ export { isOracleSource, parseOracleSourceList } from "./source-list.ts";
26
+ export { resolveOracleReadPlan, resolveHermesReadEndpoint } from "./read-plane.ts";
27
+ export type { OracleReadPlan } from "./read-plane.ts";
24
28
  export { PythCoreRule } from "./rules/pyth-core-rule.ts";
25
29
  export type { PythCoreUpdatePayload } from "./rules/pyth-core-rule.ts";
26
30
  export { PythLazerRule, LazerApiKeyMissingError } from "./rules/pyth-lazer-rule.ts";
27
31
  export type { PythLazerUpdatePayload } from "./rules/pyth-lazer-rule.ts";
28
- export { WaterxRule, parseSignedEnvelope } from "./rules/waterx-rule.ts";
32
+ export { WaterxRule, parseSignedEnvelope, BATCH_PRICE_INTENT, WATERX_INFRA, waterxQuoteCenterEndpoint, waterxEnvelopeOf, } from "./rules/waterx-rule.ts";
29
33
  export type { WaterxUpdatePayload, WaterxSignedEnvelope, WaterxBatchItem, } from "./rules/waterx-rule.ts";
30
- export { OracleSourceNotImplementedError } from "./rule-registry.ts";
34
+ export { OracleSourceNotImplementedError, resolveOracleRule } from "./rule-registry.ts";
31
35
  export { aggregateTicker, aggregateTickerWithPyth, aggregateTickerWithConstant, refreshOraclePrices, } from "./aggregate.ts";
32
36
  export { openPythSponsorFund, reimbursePythSponsor } from "./rules/sponsor.ts";
@@ -33,24 +33,46 @@ export { FetchPolicyError, fetchWithPolicy, joinEndpointPath } from "./update-fe
33
33
  // branch on the fee-source failure directly, and a consumer of
34
34
  // `fetchPriceFeedsUpdateData` / `probeMissingFeeds` can tell a misconfigured
35
35
  // or unentitled endpoint apart from feeds that endpoint genuinely lacks.
36
- export { PythCache, fetchPriceFeedsUpdateData, endpointSupportedFeedIds, probeMissingFeeds, buildPythPriceUpdateCalls, updatePythPrices, HermesEndpointRejectedAllFeedsError, MISSING_FEED_MEMO_TTL_MS, OracleFeeSourceUnavailableError, } from "./pyth.js";
36
+ export { PythCache, fetchPriceFeedsUpdateData, endpointSupportedFeedIds, probeMissingFeeds, buildPythPriceUpdateCalls,
37
+ // The pyth read-plane endpoint accessors — Core (keyless, per network) and
38
+ // Pro (the documented fixed base; auth via the caller's Bearer key). There
39
+ // is no client-level endpoint field: consumers pick via
40
+ // `resolveHermesReadEndpoint` (pyth_rule listed → Core, else override ??
41
+ // Pro) — never a hand-rolled branch, never a cross-source fallback.
42
+ pythCoreHermesEndpoint, pythProHermesEndpoint, PYTH_PRO_HERMES_ENDPOINT, updatePythPrices, HermesEndpointRejectedAllFeedsError, MISSING_FEED_MEMO_TTL_MS, OracleFeeSourceUnavailableError, } from "./pyth.js";
43
+ // Canonical OracleSource value list + THE env-string parser consumers fold
44
+ // onto — semantics and rationale in `source-list.ts`'s header.
45
+ export { ORACLE_SOURCES } from "./price-update-rule.js";
46
+ export { isOracleSource, parseOracleSourceList } from "./source-list.js";
47
+ // Per-source READ-plane resolution — which tickers a source can price
48
+ // off-chain and with which ids (`resolveOracleReadPlan`), and which
49
+ // Hermes-compatible base the hermes plans execute against
50
+ // (`resolveHermesReadEndpoint`: pyth_rule listed → Core, else override ??
51
+ // the documented Pyth Pro base). The one place the "lazer reads through
52
+ // `pyth_rule.feeds` hex ids" invariant lives; consumers resolve through
53
+ // this instead of hardcoding namespace sharing or endpoint branching.
54
+ export { resolveOracleReadPlan, resolveHermesReadEndpoint } from "./read-plane.js";
37
55
  // Pyth Core rule (PriceUpdateRule wrapper over the Pyth source above)
38
56
  export { PythCoreRule } from "./rules/pyth-core-rule.js";
39
57
  // Pyth Lazer rule (signed-update generation; `feedLazerRule` stays internal to `aggregate.ts`)
40
58
  // `LazerApiKeyMissingError` is re-exported (not just the type) for the same
41
59
  // `instanceof` reason as `OracleFeeSourceUnavailableError` above.
42
60
  export { PythLazerRule, LazerApiKeyMissingError } from "./rules/pyth-lazer-rule.js";
61
+ // `WATERX_INFRA` / `waterxQuoteCenterEndpoint` are the source's own infra table +
62
+ // read-plane accessor (mirrors `pythCoreHermesEndpoint`).
43
63
  // WaterX quote-center rule (first-party ed25519 signed batches; `feedWaterxRule`
44
64
  // stays internal to `aggregate.ts`).
45
- export { WaterxRule, parseSignedEnvelope } from "./rules/waterx-rule.js";
46
- // `resolveOracleRule` (rule-registry.ts) is NOT re-exported hereit has one
47
- // production consumer (`refreshOraclePrices` below) and no external caller;
48
- // per repo convention ("no unused exports") it stays module-internal. Tests
49
- // import it directly from `./rule-registry.ts`. `OracleSourceNotImplementedError`
50
- // IS re-exported (the same `instanceof` reason as `OracleFeeSourceUnavailableError`
51
- // above) — a consumer of `refreshOraclePrices` can catch it without importing
52
- // `resolveOracleRule` itself.
53
- export { OracleSourceNotImplementedError } from "./rule-registry.js";
65
+ export { WaterxRule, parseSignedEnvelope, BATCH_PRICE_INTENT, WATERX_INFRA, waterxQuoteCenterEndpoint,
66
+ // Rule-owned payload accessor (kind-check + unwrap in one place) never
67
+ // hand-cast the payload shape.
68
+ waterxEnvelopeOf, } from "./rules/waterx-rule.js";
69
+ // `resolveOracleRule` is the ONE source→rule registry — exported so external
70
+ // consumers (e.g. a BE prefetch cache that keys per source and needs each
71
+ // source's `supportedTickers`/`fetchUpdateData`) resolve through it instead of
72
+ // hand-mirroring the map and drifting. `OracleSourceNotImplementedError` is
73
+ // its `instanceof`-able failure (same reason as `OracleFeeSourceUnavailableError`
74
+ // above).
75
+ export { OracleSourceNotImplementedError, resolveOracleRule } from "./rule-registry.js";
54
76
  // Aggregation orchestrator
55
77
  export { aggregateTicker, aggregateTickerWithPyth, aggregateTickerWithConstant, refreshOraclePrices, } from "./aggregate.js";
56
78
  // Sponsor rule (fund open / reimburse + witness attach)
@@ -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:
@@ -11,10 +11,29 @@
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
+ /**
19
+ * The canonical list of selectable oracle sources — the SINGLE authority the
20
+ * {@link OracleSource} union derives from (the value-list→union derive
21
+ * idiom of `unified-client.ts`'s `NON_CLIENT_FIRST`, plus `Object.freeze`
22
+ * so the immutability is RUNTIME truth: `as const` alone would let a JS
23
+ * consumer push into the array and desync the membership Set built from it
24
+ * in `source-list.ts`). Runtime membership checks and the `ORACLE_SOURCE`
25
+ * env parser live there, on `isOracleSource` / `parseOracleSourceList`.
26
+ * Only sources belong here: `supra_rule` and `constant_rule` are auxiliary
27
+ * rules fed alongside whichever sources are selected (see
28
+ * `aggregateTicker`), not sources themselves — the `satisfies` keeps
29
+ * entries inside `PriceUpdateRuleKind` but adding an auxiliary rule to this
30
+ * list is an (incorrect) editorial decision this comment exists to prevent.
31
+ */
32
+ export const ORACLE_SOURCES = Object.freeze([
33
+ "pyth_rule",
34
+ "pyth_lazer_rule",
35
+ "waterx_rule",
36
+ ]);
18
37
  /**
19
38
  * Shared null → kind → shape guard ladder for a `PriceUpdateRule.buildUpdateCalls`
20
39
  * 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;
@@ -16,6 +16,44 @@
16
16
  import { fromHex, toHex } from "@mysten/bcs";
17
17
  import { bcs } from "@mysten/sui/bcs";
18
18
  import { fetchWithPolicy, joinEndpointPath, rethrowExhaustedFetch, trimTrailingSlashes, } from "./update-fetch.js";
19
+ export const PYTH_CORE_INFRA = {
20
+ MAINNET: {
21
+ state_id: "0x1f9310238ee9298fb703c3419030b35b22bb1cc37113e3bb5007c99aec79e5b8",
22
+ wormhole_state_id: "0xaeab97f96cf9877fee2883315d459552b2b921edc16d7ceac6eab944dd88919c",
23
+ hermes_endpoint: "https://hermes.pyth.network",
24
+ },
25
+ TESTNET: {
26
+ state_id: "0x243759059f4c3111179da5878c12f68d612c21a8d54d85edc86164bb18be1c7c",
27
+ wormhole_state_id: "0x31358d198147da50db32eda2562951d53973a0c0ad5ed738e9b17d88b213d790",
28
+ hermes_endpoint: "https://hermes-beta.pyth.network",
29
+ },
30
+ };
31
+ /**
32
+ * The Core source's Hermes REST base for `network` — for consumers (BE/FE
33
+ * read planes) whose `ORACLE_SOURCE` fed set includes `'pyth_rule'`. A fed
34
+ * set WITHOUT `pyth_rule` reads the keyed Pyth Pro base instead — resolve
35
+ * through `resolveHermesReadEndpoint` (`oracle/read-plane.ts`) rather than
36
+ * branching by hand.
37
+ */
38
+ export function pythCoreHermesEndpoint(network) {
39
+ return PYTH_CORE_INFRA[network].hermes_endpoint;
40
+ }
41
+ /**
42
+ * The Pyth Pro Hermes-compatible REST base — the documented drop-in
43
+ * replacement for the Core endpoint (`https://pyth.dourolabs.app/hermes/…`),
44
+ * IDENTICAL for every subscriber: only the `Authorization: Bearer` key
45
+ * (`client.pyth.api_key` / the `pythApiKey` create option) is
46
+ * account-specific, so the URL is SDK infra, not deployment config. One base
47
+ * for both networks — Pro has no testnet variant (Pyth feed ids are
48
+ * chain-agnostic; `hermes-beta` is a Core-testnet concern). Consumers with a
49
+ * proxy/self-hosted mirror override it per deployment via
50
+ * `resolveHermesReadEndpoint`'s `override` param.
51
+ */
52
+ export const PYTH_PRO_HERMES_ENDPOINT = "https://pyth.dourolabs.app/hermes";
53
+ /** Accessor mirror of {@link pythCoreHermesEndpoint} for the Pro base. */
54
+ export function pythProHermesEndpoint() {
55
+ return PYTH_PRO_HERMES_ENDPOINT;
56
+ }
19
57
  export class PythCache {
20
58
  pythStateInfo;
21
59
  wormholePackageId;
@@ -428,7 +466,7 @@ async function getPriceTableInfo(client, pythStateId, cache) {
428
466
  }
429
467
  async function getPriceFeedObjectId(client, table, feedId, cache, pythStateId) {
430
468
  const normalized = feedId.replace(/^0x/, "");
431
- const cacheKey = pythStateId ? `${pythStateId}:${normalized}` : normalized;
469
+ const cacheKey = `${pythStateId}:${normalized}`;
432
470
  if (cache?.priceFeedObjectIdCache.has(cacheKey)) {
433
471
  return cache.priceFeedObjectIdCache.get(cacheKey);
434
472
  }
@@ -523,7 +561,9 @@ export async function buildPythPriceUpdateCalls(tx, host, updates, feedIds, opts
523
561
  throw new OracleFeeSourceUnavailableError();
524
562
  }
525
563
  const cache = opts?.cache;
526
- const pyth = host.pyth;
564
+ // Core on-chain infra is the `pyth_rule` source's own table, keyed by the
565
+ // host's network — `client.pyth` carries only the caller's credential/policy.
566
+ const pyth = PYTH_CORE_INFRA[host.network];
527
567
  const [stateInfo, wormholePackageId, table] = await Promise.all([
528
568
  getPythStateInfo(host.grpcClient, pyth.state_id, cache),
529
569
  getWormholePackageId(host.grpcClient, pyth.wormhole_state_id, cache),
@@ -581,9 +621,9 @@ export async function buildPythPriceUpdateCalls(tx, host, updates, feedIds, opts
581
621
  }
582
622
  /** All-in-one: fetch from Hermes, append update calls. Returns PriceInfoObject IDs. */
583
623
  export async function updatePythPrices(tx, host, feedIds, opts) {
584
- // `host.pyth` is the Pyth Core infra (fixed per network) plus the caller's
585
- // api_key/fetch endpoint, credential and policy all come from it.
586
- const endpoint = host.pyth.hermes_endpoint;
624
+ // The Core source's own Hermes endpoint (per-network, rule-owned table);
625
+ // credential + fetch policy are the caller-supplied `client.pyth` slice.
626
+ const endpoint = PYTH_CORE_INFRA[host.network].hermes_endpoint;
587
627
  const updates = await fetchPriceFeedsUpdateData(endpoint, feedIds, {
588
628
  apiKey: host.pyth.api_key,
589
629
  fetch: host.pyth.fetch,