@waterx/sdk 4.3.2 → 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 (150) hide show
  1. package/.claude/skills/waterx-sdk-integration/SKILL.md +225 -0
  2. package/README.md +216 -45
  3. package/SKILLS.md +34 -0
  4. package/dist/cjs/src/account/config.d.ts +3 -5
  5. package/dist/cjs/src/account/funding/wormhole.d.ts +1 -2
  6. package/dist/cjs/src/account/funding/wormhole.js +1 -2
  7. package/dist/cjs/src/generated/waterx_rule/waterx_rule.d.ts +140 -7
  8. package/dist/cjs/src/generated/waterx_rule/waterx_rule.js +163 -8
  9. package/dist/cjs/src/oracle/aggregate.d.ts +49 -62
  10. package/dist/cjs/src/oracle/aggregate.js +208 -164
  11. package/dist/cjs/src/oracle/config.d.ts +38 -51
  12. package/dist/cjs/src/oracle/config.js +1 -1
  13. package/dist/cjs/src/oracle/host.d.ts +14 -19
  14. package/dist/cjs/src/oracle/host.js +3 -3
  15. package/dist/cjs/src/oracle/index.d.ts +25 -16
  16. package/dist/cjs/src/oracle/index.js +87 -69
  17. package/dist/cjs/src/oracle/price-update-rule.d.ts +104 -66
  18. package/dist/cjs/src/oracle/price-update-rule.js +24 -16
  19. package/dist/cjs/src/oracle/pyth-pro-history.d.ts +36 -0
  20. package/dist/cjs/src/oracle/pyth-pro-history.js +41 -0
  21. package/dist/cjs/src/oracle/read-plane.d.ts +35 -53
  22. package/dist/cjs/src/oracle/read-plane.js +26 -48
  23. package/dist/cjs/src/oracle/read-prices.d.ts +105 -0
  24. package/dist/cjs/src/oracle/read-prices.js +162 -0
  25. package/dist/cjs/src/oracle/rule-registry.d.ts +12 -12
  26. package/dist/cjs/src/oracle/rule-registry.js +12 -14
  27. package/dist/cjs/src/oracle/rules/pyth-lazer-rule.d.ts +24 -3
  28. package/dist/cjs/src/oracle/rules/pyth-lazer-rule.js +40 -20
  29. package/dist/cjs/src/oracle/rules/waterx-rule.d.ts +271 -57
  30. package/dist/cjs/src/oracle/rules/waterx-rule.js +743 -144
  31. package/dist/cjs/src/oracle/schedule.d.ts +109 -0
  32. package/dist/cjs/src/oracle/schedule.js +991 -0
  33. package/dist/cjs/src/oracle/source-list.d.ts +31 -27
  34. package/dist/cjs/src/oracle/source-list.js +43 -45
  35. package/dist/cjs/src/oracle/symbol-catalog.d.ts +54 -0
  36. package/dist/cjs/src/oracle/symbol-catalog.js +69 -0
  37. package/dist/cjs/src/oracle/update-fetch.d.ts +11 -15
  38. package/dist/cjs/src/oracle/update-fetch.js +12 -19
  39. package/dist/cjs/src/oracle/validate.d.ts +105 -0
  40. package/dist/cjs/src/oracle/validate.js +185 -0
  41. package/dist/cjs/src/oracle/weight-coverage.d.ts +79 -0
  42. package/dist/cjs/src/oracle/weight-coverage.js +173 -0
  43. package/dist/cjs/src/perp/client.d.ts +40 -55
  44. package/dist/cjs/src/perp/client.js +44 -17
  45. package/dist/cjs/src/perp/config-view.d.ts +5 -14
  46. package/dist/cjs/src/perp/config-view.js +5 -16
  47. package/dist/cjs/src/perp/config.d.ts +3 -3
  48. package/dist/cjs/src/perp/config.js +11 -13
  49. package/dist/cjs/src/perp/index.d.ts +4 -6
  50. package/dist/cjs/src/perp/index.js +43 -18
  51. package/dist/cjs/src/perp/tx-builders/common.d.ts +86 -48
  52. package/dist/cjs/src/perp/tx-builders/common.js +115 -47
  53. package/dist/cjs/src/perp/tx-builders/wlp.d.ts +3 -14
  54. package/dist/cjs/src/perp/tx-builders/wlp.js +6 -35
  55. package/dist/cjs/src/perp/tx-builders.d.ts +6 -5
  56. package/dist/cjs/src/perp/tx-builders.js +16 -10
  57. package/dist/cjs/src/unified-client.d.ts +9 -35
  58. package/dist/cjs/src/unified-client.js +3 -4
  59. package/dist/cjs/src/utils/config.d.ts +0 -8
  60. package/dist/cjs/src/utils/config.js +5 -13
  61. package/dist/src/account/config.d.ts +3 -5
  62. package/dist/src/account/funding/wormhole.d.ts +1 -2
  63. package/dist/src/account/funding/wormhole.js +1 -2
  64. package/dist/src/generated/waterx_rule/waterx_rule.d.ts +140 -7
  65. package/dist/src/generated/waterx_rule/waterx_rule.js +151 -7
  66. package/dist/src/oracle/aggregate.d.ts +49 -62
  67. package/dist/src/oracle/aggregate.js +209 -164
  68. package/dist/src/oracle/config.d.ts +38 -51
  69. package/dist/src/oracle/config.js +1 -1
  70. package/dist/src/oracle/host.d.ts +14 -19
  71. package/dist/src/oracle/host.js +3 -3
  72. package/dist/src/oracle/index.d.ts +25 -16
  73. package/dist/src/oracle/index.js +61 -54
  74. package/dist/src/oracle/price-update-rule.d.ts +104 -66
  75. package/dist/src/oracle/price-update-rule.js +23 -16
  76. package/dist/src/oracle/pyth-pro-history.d.ts +36 -0
  77. package/dist/src/oracle/pyth-pro-history.js +38 -0
  78. package/dist/src/oracle/read-plane.d.ts +35 -53
  79. package/dist/src/oracle/read-plane.js +25 -47
  80. package/dist/src/oracle/read-prices.d.ts +105 -0
  81. package/dist/src/oracle/read-prices.js +156 -0
  82. package/dist/src/oracle/rule-registry.d.ts +12 -12
  83. package/dist/src/oracle/rule-registry.js +12 -14
  84. package/dist/src/oracle/rules/pyth-lazer-rule.d.ts +24 -3
  85. package/dist/src/oracle/rules/pyth-lazer-rule.js +39 -20
  86. package/dist/src/oracle/rules/waterx-rule.d.ts +271 -57
  87. package/dist/src/oracle/rules/waterx-rule.js +735 -144
  88. package/dist/src/oracle/schedule.d.ts +109 -0
  89. package/dist/src/oracle/schedule.js +985 -0
  90. package/dist/src/oracle/source-list.d.ts +31 -27
  91. package/dist/src/oracle/source-list.js +42 -43
  92. package/dist/src/oracle/symbol-catalog.d.ts +54 -0
  93. package/dist/src/oracle/symbol-catalog.js +65 -0
  94. package/dist/src/oracle/update-fetch.d.ts +11 -15
  95. package/dist/src/oracle/update-fetch.js +12 -18
  96. package/dist/src/oracle/validate.d.ts +105 -0
  97. package/dist/src/oracle/validate.js +177 -0
  98. package/dist/src/oracle/weight-coverage.d.ts +79 -0
  99. package/dist/src/oracle/weight-coverage.js +166 -0
  100. package/dist/src/perp/client.d.ts +40 -55
  101. package/dist/src/perp/client.js +45 -18
  102. package/dist/src/perp/config-view.d.ts +5 -14
  103. package/dist/src/perp/config-view.js +5 -16
  104. package/dist/src/perp/config.d.ts +3 -3
  105. package/dist/src/perp/config.js +11 -13
  106. package/dist/src/perp/index.d.ts +4 -6
  107. package/dist/src/perp/index.js +17 -5
  108. package/dist/src/perp/tx-builders/common.d.ts +86 -48
  109. package/dist/src/perp/tx-builders/common.js +114 -48
  110. package/dist/src/perp/tx-builders/wlp.d.ts +3 -14
  111. package/dist/src/perp/tx-builders/wlp.js +6 -35
  112. package/dist/src/perp/tx-builders.d.ts +6 -5
  113. package/dist/src/perp/tx-builders.js +15 -6
  114. package/dist/src/unified-client.d.ts +9 -35
  115. package/dist/src/unified-client.js +3 -4
  116. package/dist/src/utils/config.d.ts +0 -8
  117. package/dist/src/utils/config.js +5 -12
  118. package/package.json +8 -2
  119. package/dist/cjs/src/generated/pyth_sponsor_rule/deps/sui/balance.d.ts +0 -12
  120. package/dist/cjs/src/generated/pyth_sponsor_rule/deps/sui/balance.js +0 -17
  121. package/dist/cjs/src/generated/pyth_sponsor_rule/pyth_sponsor_rule.d.ts +0 -68
  122. package/dist/cjs/src/generated/pyth_sponsor_rule/pyth_sponsor_rule.js +0 -113
  123. package/dist/cjs/src/generated/waterx_pyth_rule/deps/sui/vec_map.d.ts +0 -36
  124. package/dist/cjs/src/generated/waterx_pyth_rule/deps/sui/vec_map.js +0 -31
  125. package/dist/cjs/src/generated/waterx_pyth_rule/pyth_rule.d.ts +0 -192
  126. package/dist/cjs/src/generated/waterx_pyth_rule/pyth_rule.js +0 -214
  127. package/dist/cjs/src/oracle/pyth.d.ts +0 -227
  128. package/dist/cjs/src/oracle/pyth.js +0 -652
  129. package/dist/cjs/src/oracle/rules/pyth-core-rule.d.ts +0 -15
  130. package/dist/cjs/src/oracle/rules/pyth-core-rule.js +0 -100
  131. package/dist/cjs/src/oracle/rules/pyth-rule.d.ts +0 -11
  132. package/dist/cjs/src/oracle/rules/pyth-rule.js +0 -29
  133. package/dist/cjs/src/oracle/rules/sponsor.d.ts +0 -32
  134. package/dist/cjs/src/oracle/rules/sponsor.js +0 -56
  135. package/dist/src/generated/pyth_sponsor_rule/deps/sui/balance.d.ts +0 -12
  136. package/dist/src/generated/pyth_sponsor_rule/deps/sui/balance.js +0 -14
  137. package/dist/src/generated/pyth_sponsor_rule/pyth_sponsor_rule.d.ts +0 -68
  138. package/dist/src/generated/pyth_sponsor_rule/pyth_sponsor_rule.js +0 -73
  139. package/dist/src/generated/waterx_pyth_rule/deps/sui/vec_map.d.ts +0 -36
  140. package/dist/src/generated/waterx_pyth_rule/deps/sui/vec_map.js +0 -27
  141. package/dist/src/generated/waterx_pyth_rule/pyth_rule.d.ts +0 -192
  142. package/dist/src/generated/waterx_pyth_rule/pyth_rule.js +0 -170
  143. package/dist/src/oracle/pyth.d.ts +0 -227
  144. package/dist/src/oracle/pyth.js +0 -638
  145. package/dist/src/oracle/rules/pyth-core-rule.d.ts +0 -15
  146. package/dist/src/oracle/rules/pyth-core-rule.js +0 -97
  147. package/dist/src/oracle/rules/pyth-rule.d.ts +0 -11
  148. package/dist/src/oracle/rules/pyth-rule.js +0 -26
  149. package/dist/src/oracle/rules/sponsor.d.ts +0 -32
  150. package/dist/src/oracle/rules/sponsor.js +0 -52
@@ -1,36 +1,40 @@
1
1
  /**
2
- * `source-list.ts` — THE parser for a consumer's `ORACLE_SOURCE` env string
3
- * (comma list of `OracleSource` values → the fed set). The FE and BE
4
- * previously carried twin hand-written parsers whose semantics drifted once
5
- * in review (a trailing comma booted one deployment green and 500'd the
6
- * other); this canonical behavior is what both fold onto:
2
+ * `source-list.ts` — THE fed set, derived from the deployment config.
7
3
  *
8
- * - split on `,`, trim entries, DROP empties (trailing/doubled commas are
9
- * the most common env typo, never a boot failure)
10
- * - validate every entry against {@link ORACLE_SOURCES}
11
- * - dedupe, order-preserving (list order is consumer read-plane policy
12
- * the SDK's own fed-set build treats the list as a set)
13
- * - throw an operator-actionable error on empty/unset/invalid input —
14
- * there is NO default oracle source
4
+ * There is no `oracleSource` option and no `ORACLE_SOURCE` env var. Which
5
+ * sources a build feeds is a property of the DEPLOYMENT, so it is read from
6
+ * the same canonical JSON that carries their packages and feeds: a source is
7
+ * in the fed set when its block is published AND carries at least one feed.
15
8
  *
16
- * The SDK still never reads `process.env` callers pass the raw string.
9
+ * Why derived rather than declared. The chain arbitrates per-ticker weights
10
+ * decide which contributions count, feeding an UNWEIGHTED rule is dropped
11
+ * on-chain, and starving a WEIGHTED one aborts `EMissingPriceSource`. The
12
+ * failure is therefore one-sided: over-feeding is free, under-feeding is fatal.
13
+ * A hand-typed list errs in the fatal direction (the classic being one copied
14
+ * between networks, naming a source that deployment does not carry); the
15
+ * config cannot, because it IS what wires the rules. Mainnet derives
16
+ * `[pyth_lazer_rule, waterx_rule]` and testnet `[waterx_rule]` with no
17
+ * per-deployment configuration at all.
17
18
  *
18
- * STRICTER than the consumers' previous `in`-operator checks: a value named
19
- * like an `Object.prototype` key (`toString`, `constructor`, …) passed those
20
- * and died deep in the stack; `Set.has` rejects it here at parse.
19
+ * Retired rules are inert here by construction: `pyth_rule` and
20
+ * `pyth_sponsor_rule` still sit in the live configs, but neither is an
21
+ * {@link ORACLE_SOURCES} member there is no rule module that could feed one
22
+ * — so their blocks are never consulted.
21
23
  *
22
- * Zod adopters: this THROWS a plain Error. Inside a zod `.transform()` a
23
- * throw escapes `schema.parse()` un-aggregated and masks sibling issues
24
- * wrap it: `try { return parseOracleSourceList(raw); } catch (e) {
25
- * ctx.addIssue({ code: "custom", message: (e as Error).message }); return
26
- * z.NEVER; }`.
24
+ * Deliberately NOT filtered by which credentials the caller holds. A keyless
25
+ * client whose config wires Lazer fails loudly at build
26
+ * (`LazerApiKeyMissing`); silently dropping the source instead would starve a
27
+ * rule the chain may weight and turn a clear build error into an opaque
28
+ * on-chain abort.
27
29
  */
30
+ import type { OracleConfig } from "./config.ts";
28
31
  import { type OracleSource } from "./price-update-rule.ts";
29
32
  /**
30
- * THE runtime membership check for {@link ORACLE_SOURCES} the parser below
31
- * and `PerpClient`'s ctor validation both use this one predicate, so the env
32
- * parser and the create-option front door can never disagree. `Set.has`,
33
- * never `in`/bracket reads (prototype-chain safe by construction).
33
+ * The fed set this deployment wires: every implementable source with a
34
+ * published package and a non-empty feeds map, in {@link ORACLE_SOURCES}
35
+ * order.
36
+ *
37
+ * Pure and config-only, so consumers can call it before a client exists (e.g.
38
+ * to pair with {@link missingOracleCredentials} in a boot assert).
34
39
  */
35
- export declare function isOracleSource(value: string): value is OracleSource;
36
- export declare function parseOracleSourceList(raw: string | null | undefined): OracleSource[];
40
+ export declare function deriveOracleSources(config: OracleConfig): OracleSource[];
@@ -1,57 +1,55 @@
1
1
  "use strict";
2
2
  /**
3
- * `source-list.ts` — THE parser for a consumer's `ORACLE_SOURCE` env string
4
- * (comma list of `OracleSource` values → the fed set). The FE and BE
5
- * previously carried twin hand-written parsers whose semantics drifted once
6
- * in review (a trailing comma booted one deployment green and 500'd the
7
- * other); this canonical behavior is what both fold onto:
3
+ * `source-list.ts` — THE fed set, derived from the deployment config.
8
4
  *
9
- * - split on `,`, trim entries, DROP empties (trailing/doubled commas are
10
- * the most common env typo, never a boot failure)
11
- * - validate every entry against {@link ORACLE_SOURCES}
12
- * - dedupe, order-preserving (list order is consumer read-plane policy
13
- * the SDK's own fed-set build treats the list as a set)
14
- * - throw an operator-actionable error on empty/unset/invalid input —
15
- * there is NO default oracle source
5
+ * There is no `oracleSource` option and no `ORACLE_SOURCE` env var. Which
6
+ * sources a build feeds is a property of the DEPLOYMENT, so it is read from
7
+ * the same canonical JSON that carries their packages and feeds: a source is
8
+ * in the fed set when its block is published AND carries at least one feed.
16
9
  *
17
- * The SDK still never reads `process.env` callers pass the raw string.
10
+ * Why derived rather than declared. The chain arbitrates per-ticker weights
11
+ * decide which contributions count, feeding an UNWEIGHTED rule is dropped
12
+ * on-chain, and starving a WEIGHTED one aborts `EMissingPriceSource`. The
13
+ * failure is therefore one-sided: over-feeding is free, under-feeding is fatal.
14
+ * A hand-typed list errs in the fatal direction (the classic being one copied
15
+ * between networks, naming a source that deployment does not carry); the
16
+ * config cannot, because it IS what wires the rules. Mainnet derives
17
+ * `[pyth_lazer_rule, waterx_rule]` and testnet `[waterx_rule]` with no
18
+ * per-deployment configuration at all.
18
19
  *
19
- * STRICTER than the consumers' previous `in`-operator checks: a value named
20
- * like an `Object.prototype` key (`toString`, `constructor`, …) passed those
21
- * and died deep in the stack; `Set.has` rejects it here at parse.
20
+ * Retired rules are inert here by construction: `pyth_rule` and
21
+ * `pyth_sponsor_rule` still sit in the live configs, but neither is an
22
+ * {@link ORACLE_SOURCES} member there is no rule module that could feed one
23
+ * — so their blocks are never consulted.
22
24
  *
23
- * Zod adopters: this THROWS a plain Error. Inside a zod `.transform()` a
24
- * throw escapes `schema.parse()` un-aggregated and masks sibling issues
25
- * wrap it: `try { return parseOracleSourceList(raw); } catch (e) {
26
- * ctx.addIssue({ code: "custom", message: (e as Error).message }); return
27
- * z.NEVER; }`.
25
+ * Deliberately NOT filtered by which credentials the caller holds. A keyless
26
+ * client whose config wires Lazer fails loudly at build
27
+ * (`LazerApiKeyMissing`); silently dropping the source instead would starve a
28
+ * rule the chain may weight and turn a clear build error into an opaque
29
+ * on-chain abort.
28
30
  */
29
31
  Object.defineProperty(exports, "__esModule", { value: true });
30
- exports.isOracleSource = isOracleSource;
31
- exports.parseOracleSourceList = parseOracleSourceList;
32
+ exports.deriveOracleSources = deriveOracleSources;
32
33
  const price_update_rule_ts_1 = require("./price-update-rule.js");
33
- // Widened-annotation Set (not an assertion) so the type predicate below
34
- // narrows by CONSTRUCTION rather than by cast.
35
- const ORACLE_SOURCE_SET = new Set(price_update_rule_ts_1.ORACLE_SOURCES);
36
34
  /**
37
- * THE runtime membership check for {@link ORACLE_SOURCES} the parser below
38
- * and `PerpClient`'s ctor validation both use this one predicate, so the env
39
- * parser and the create-option front door can never disagree. `Set.has`,
40
- * never `in`/bracket reads (prototype-chain safe by construction).
35
+ * The fed set this deployment wires: every implementable source with a
36
+ * published package and a non-empty feeds map, in {@link ORACLE_SOURCES}
37
+ * order.
38
+ *
39
+ * Pure and config-only, so consumers can call it before a client exists (e.g.
40
+ * to pair with {@link missingOracleCredentials} in a boot assert).
41
41
  */
42
- function isOracleSource(value) {
43
- return ORACLE_SOURCE_SET.has(value);
44
- }
45
- function parseOracleSourceList(raw) {
46
- const parts = (raw ?? "")
47
- .split(",")
48
- .map((part) => part.trim())
49
- .filter((part) => part !== "");
50
- const sources = parts.filter(isOracleSource);
51
- if (parts.length === 0 || sources.length !== parts.length) {
52
- const got = raw == null || raw.trim() === "" ? "unset" : `'${raw}'`;
53
- throw new Error(`ORACLE_SOURCE must be a comma-separated list of ${price_update_rule_ts_1.ORACLE_SOURCES.join(" | ")} ` +
54
- `(got ${got}) — there is NO default oracle source; set it in the deployment's env.`);
55
- }
56
- return [...new Set(sources)];
42
+ function deriveOracleSources(config) {
43
+ return price_update_rule_ts_1.ORACLE_SOURCES.filter((source) => {
44
+ const block = config.packages[source];
45
+ if (!block?.published_at || Object.keys(block.feeds ?? {}).length === 0)
46
+ return false;
47
+ // `enabled` is the one lever the schema offers for switching a source off,
48
+ // and with routing derived from config it is the ONLY lever left — so it
49
+ // is honoured here. Absent means ON: every live config omits it, and a
50
+ // published block with feeds is a wired source. (`supra_rule` is
51
+ // default-OFF and requires an explicit `true` it is an auxiliary leg,
52
+ // not a source, so the asymmetry is deliberate.)
53
+ return block.enabled !== false;
54
+ });
57
55
  }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * `symbol-catalog.ts` — the Pyth Pro symbol catalog (`GET /v1/symbols`):
3
+ * every Lazer feed's integer id, its legacy Hermes hex id, its
4
+ * fully-qualified reference symbol (`Crypto.BTC/USD`, `Equity.US.AAPL/USD`),
5
+ * its `schedule` string (same grammar `parsePythSchedule` implements), asset
6
+ * type, state, and fastest channel. This replaces the retired Hermes
7
+ * `/v2/price_feeds` catalog for BOTH of its jobs: the schedule catalog for
8
+ * market hours AND the hex↔integer feed-id map.
9
+ *
10
+ * KEYLESS — the catalog read requires no Bearer (probed 2026-08-19), so
11
+ * schedule consumers need no credential. It is also BIG (~4.6MB, ~3.6k
12
+ * records), hence the generous default timeout; consumers cache the result
13
+ * (BE service interval-refreshes; FE route caches) rather than fetch per
14
+ * request.
15
+ */
16
+ import { type FetchPolicy } from "./update-fetch.ts";
17
+ /**
18
+ * The Pyth Pro API base every Pro read surface hangs off — the catalog here
19
+ * and the chart history in `pyth-pro-history.ts`, which imports this. ONE
20
+ * declaration, mirroring the rule-owned `LAZER_INFRA` / `WATERX_INFRA`
21
+ * pattern: a Pro host change is a single edit.
22
+ */
23
+ export declare const PYTH_PRO_API_ENDPOINT = "https://pyth.dourolabs.app";
24
+ /**
25
+ * One `/v1/symbols` record (the fields consumers key off; extra wire fields
26
+ * are dropped, not preserved).
27
+ */
28
+ export type PythSymbolRecord = {
29
+ /** Integer Lazer feed id — the `pyth_lazer_rule.feeds` id scheme. */
30
+ pyth_lazer_id: number;
31
+ /** Legacy Hermes hex feed id, or null for Lazer-only feeds. */
32
+ hermes_id: string | null;
33
+ /** Fully-qualified reference symbol, e.g. `Crypto.BTC/USD` — the join key consumers map tickers onto. */
34
+ symbol: string;
35
+ /** Pyth market-hours grammar string (absent for some 24/7 feeds). */
36
+ schedule?: string;
37
+ /** e.g. `"crypto"`, `"equity"`, `"fx"`, `"metal"`. */
38
+ asset_type: string;
39
+ /** Feed lifecycle state (e.g. `"stable"`). */
40
+ state: string;
41
+ /** Fastest channel the feed publishes (e.g. `"real_time"`, `"fixed_rate@200ms"`). */
42
+ min_channel?: string;
43
+ };
44
+ /**
45
+ * Fetch the full symbol catalog. Returns records with a numeric
46
+ * `pyth_lazer_id` and a string `symbol`; other fields pass through as-is
47
+ * (missing → undefined/null per the type). Throws on non-2xx or a non-array
48
+ * body. `opts.fetch` overrides the retry/timeout policy (and `fetchImpl` for
49
+ * tests); the default budget is deliberately generous — see the module
50
+ * header.
51
+ */
52
+ export declare function fetchPythSymbolCatalog(opts?: {
53
+ fetch?: FetchPolicy;
54
+ }): Promise<PythSymbolRecord[]>;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ /**
3
+ * `symbol-catalog.ts` — the Pyth Pro symbol catalog (`GET /v1/symbols`):
4
+ * every Lazer feed's integer id, its legacy Hermes hex id, its
5
+ * fully-qualified reference symbol (`Crypto.BTC/USD`, `Equity.US.AAPL/USD`),
6
+ * its `schedule` string (same grammar `parsePythSchedule` implements), asset
7
+ * type, state, and fastest channel. This replaces the retired Hermes
8
+ * `/v2/price_feeds` catalog for BOTH of its jobs: the schedule catalog for
9
+ * market hours AND the hex↔integer feed-id map.
10
+ *
11
+ * KEYLESS — the catalog read requires no Bearer (probed 2026-08-19), so
12
+ * schedule consumers need no credential. It is also BIG (~4.6MB, ~3.6k
13
+ * records), hence the generous default timeout; consumers cache the result
14
+ * (BE service interval-refreshes; FE route caches) rather than fetch per
15
+ * request.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.PYTH_PRO_API_ENDPOINT = void 0;
19
+ exports.fetchPythSymbolCatalog = fetchPythSymbolCatalog;
20
+ const update_fetch_ts_1 = require("./update-fetch.js");
21
+ /**
22
+ * The Pyth Pro API base every Pro read surface hangs off — the catalog here
23
+ * and the chart history in `pyth-pro-history.ts`, which imports this. ONE
24
+ * declaration, mirroring the rule-owned `LAZER_INFRA` / `WATERX_INFRA`
25
+ * pattern: a Pro host change is a single edit.
26
+ */
27
+ exports.PYTH_PRO_API_ENDPOINT = "https://pyth.dourolabs.app";
28
+ /** ~4.6MB body — a money-path 15s budget is too tight on slow links. */
29
+ const CATALOG_TIMEOUT_MS = 60_000;
30
+ /**
31
+ * Fetch the full symbol catalog. Returns records with a numeric
32
+ * `pyth_lazer_id` and a string `symbol`; other fields pass through as-is
33
+ * (missing → undefined/null per the type). Throws on non-2xx or a non-array
34
+ * body. `opts.fetch` overrides the retry/timeout policy (and `fetchImpl` for
35
+ * tests); the default budget is deliberately generous — see the module
36
+ * header.
37
+ */
38
+ async function fetchPythSymbolCatalog(opts) {
39
+ const url = (0, update_fetch_ts_1.joinEndpointPath)(exports.PYTH_PRO_API_ENDPOINT, "v1/symbols");
40
+ // Same ordering hazard as the history reader: spreading the caller's policy
41
+ // over the default let `{ timeoutMs: undefined }` revert the deliberately
42
+ // generous catalog budget to the 15s money-path default, which this ~4.6MB
43
+ // body does not fit. An explicit value still wins; absence does not.
44
+ const res = await (0, update_fetch_ts_1.fetchWithPolicy)(url.toString(), {}, { ...opts?.fetch, timeoutMs: opts?.fetch?.timeoutMs ?? CATALOG_TIMEOUT_MS });
45
+ if (!res.ok) {
46
+ throw new Error(`Pyth symbol catalog fetch failed: ${res.status} ${await res.text()}`);
47
+ }
48
+ const json = (await res.json());
49
+ if (!Array.isArray(json)) {
50
+ throw new Error("Pyth symbol catalog returned a non-array body");
51
+ }
52
+ const records = [];
53
+ for (const raw of json) {
54
+ // Tolerant per-record gate: one malformed row must not sink the ~3.6k
55
+ // others (mirrors the schedule parser's skip-a-bad-feed posture).
56
+ if (typeof raw?.pyth_lazer_id !== "number" || typeof raw.symbol !== "string")
57
+ continue;
58
+ records.push({
59
+ pyth_lazer_id: raw.pyth_lazer_id,
60
+ hermes_id: typeof raw.hermes_id === "string" ? raw.hermes_id : null,
61
+ symbol: raw.symbol,
62
+ ...(typeof raw.schedule === "string" ? { schedule: raw.schedule } : {}),
63
+ asset_type: typeof raw.asset_type === "string" ? raw.asset_type : "",
64
+ state: typeof raw.state === "string" ? raw.state : "",
65
+ ...(typeof raw.min_channel === "string" ? { min_channel: raw.min_channel } : {}),
66
+ });
67
+ }
68
+ return records;
69
+ }
@@ -1,14 +1,13 @@
1
1
  /**
2
2
  * `fetchWithPolicy` — resilience wrapper around `fetch` for the oracle money
3
3
  * path's off-chain update-data fetches. Every order/position/collateral
4
- * tx-build depends on one of these REST calls landing (Hermes VAA for
5
- * `pyth_rule`, Lazer signed updates for `pyth_lazer_rule`); a bare `fetch`
6
- * with a single attempt and no retry means one Hermes 429/5xx or timeout
7
- * fails every trade. `fetchPriceFeedsUpdateData` (`./pyth.ts`),
8
- * `PythLazerRule.fetchUpdateData`'s Lazer POST (`./rules/pyth-lazer-rule.ts`),
9
- * and `loadConfig` (`../perp/config.ts`) all delegate here instead of calling
10
- * `fetch` directly — this is the ONE place a retry/timeout/auth policy is
11
- * implemented for these fetches.
4
+ * tx-build depends on one of these REST calls landing (the Lazer signed
5
+ * update for `pyth_lazer_rule`, the quote-center pull for `waterx_rule`); a
6
+ * bare `fetch` with a single attempt and no retry means one upstream 429/5xx
7
+ * or timeout fails every trade. Those two rule fetches, the read executors
8
+ * (`./read-prices.ts`), the Pyth Pro catalog/history readers, and `loadConfig`
9
+ * (`../perp/config.ts`) all delegate here instead of calling `fetch` directly
10
+ * — this is the ONE place a retry/timeout/auth policy is implemented.
12
11
  *
13
12
  * Policy semantics:
14
13
  * - Bearer auth is attached iff `policy.apiKey` is a non-empty string —
@@ -96,14 +95,11 @@ export declare function rethrowExhaustedFetch(err: unknown, describe: (err: Fetc
96
95
  * Join an API `path` onto an `endpoint` PRESERVING the endpoint's own base
97
96
  * path. `new URL(path, endpoint)` is the footgun this replaces: a
98
97
  * leading-slash path is *absolute* and silently discards the endpoint's path
99
- * — harmless for a bare-origin endpoint (`https://hermes.pyth.network`) but
100
- * it dropped the `/hermes` prefix of the Pyth Pro compat endpoint and 404'd
101
- * every feed (see `fetchPriceFeedsUpdateData`). Every oracle fetch that
102
- * targets `<endpoint><fixed path>` must build its URL here.
98
+ * — harmless for a bare-origin endpoint, but it is exactly what a consumer's
99
+ * `waterxEndpoint` proxy route (`https://app.example/api/quote-center`) is
100
+ * made of, and dropping it silently bypasses the proxy. Every oracle fetch
101
+ * that targets `<endpoint><fixed path>` must build its URL here.
103
102
  */
104
- /** One canonical trailing-slash trim — `joinEndpointPath` (URL building) and
105
- * `pyth.ts`'s `memoKey` (endpoint identity) must never drift apart on it. */
106
- export declare function trimTrailingSlashes(endpoint: string): string;
107
103
  export declare function joinEndpointPath(endpoint: string, path: string): URL;
108
104
  /**
109
105
  * `fetch` with per-attempt timeout, bounded retry + backoff, and optional
@@ -2,14 +2,13 @@
2
2
  /**
3
3
  * `fetchWithPolicy` — resilience wrapper around `fetch` for the oracle money
4
4
  * path's off-chain update-data fetches. Every order/position/collateral
5
- * tx-build depends on one of these REST calls landing (Hermes VAA for
6
- * `pyth_rule`, Lazer signed updates for `pyth_lazer_rule`); a bare `fetch`
7
- * with a single attempt and no retry means one Hermes 429/5xx or timeout
8
- * fails every trade. `fetchPriceFeedsUpdateData` (`./pyth.ts`),
9
- * `PythLazerRule.fetchUpdateData`'s Lazer POST (`./rules/pyth-lazer-rule.ts`),
10
- * and `loadConfig` (`../perp/config.ts`) all delegate here instead of calling
11
- * `fetch` directly — this is the ONE place a retry/timeout/auth policy is
12
- * implemented for these fetches.
5
+ * tx-build depends on one of these REST calls landing (the Lazer signed
6
+ * update for `pyth_lazer_rule`, the quote-center pull for `waterx_rule`); a
7
+ * bare `fetch` with a single attempt and no retry means one upstream 429/5xx
8
+ * or timeout fails every trade. Those two rule fetches, the read executors
9
+ * (`./read-prices.ts`), the Pyth Pro catalog/history readers, and `loadConfig`
10
+ * (`../perp/config.ts`) all delegate here instead of calling `fetch` directly
11
+ * — this is the ONE place a retry/timeout/auth policy is implemented.
13
12
  *
14
13
  * Policy semantics:
15
14
  * - Bearer auth is attached iff `policy.apiKey` is a non-empty string —
@@ -56,7 +55,6 @@
56
55
  Object.defineProperty(exports, "__esModule", { value: true });
57
56
  exports.FetchPolicyError = void 0;
58
57
  exports.rethrowExhaustedFetch = rethrowExhaustedFetch;
59
- exports.trimTrailingSlashes = trimTrailingSlashes;
60
58
  exports.joinEndpointPath = joinEndpointPath;
61
59
  exports.fetchWithPolicy = fetchWithPolicy;
62
60
  const DEFAULT_TIMEOUT_MS = 15_000;
@@ -106,18 +104,13 @@ function rethrowExhaustedFetch(err, describe) {
106
104
  * Join an API `path` onto an `endpoint` PRESERVING the endpoint's own base
107
105
  * path. `new URL(path, endpoint)` is the footgun this replaces: a
108
106
  * leading-slash path is *absolute* and silently discards the endpoint's path
109
- * — harmless for a bare-origin endpoint (`https://hermes.pyth.network`) but
110
- * it dropped the `/hermes` prefix of the Pyth Pro compat endpoint and 404'd
111
- * every feed (see `fetchPriceFeedsUpdateData`). Every oracle fetch that
112
- * targets `<endpoint><fixed path>` must build its URL here.
107
+ * — harmless for a bare-origin endpoint, but it is exactly what a consumer's
108
+ * `waterxEndpoint` proxy route (`https://app.example/api/quote-center`) is
109
+ * made of, and dropping it silently bypasses the proxy. Every oracle fetch
110
+ * that targets `<endpoint><fixed path>` must build its URL here.
113
111
  */
114
- /** One canonical trailing-slash trim — `joinEndpointPath` (URL building) and
115
- * `pyth.ts`'s `memoKey` (endpoint identity) must never drift apart on it. */
116
- function trimTrailingSlashes(endpoint) {
117
- return endpoint.replace(/\/+$/, "");
118
- }
119
112
  function joinEndpointPath(endpoint, path) {
120
- return new URL(`${trimTrailingSlashes(endpoint)}/${path.replace(/^\/+/, "")}`);
113
+ return new URL(`${endpoint.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`);
121
114
  }
122
115
  function isRetryableStatus(status) {
123
116
  return status === 429 || status >= 500;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * `validate.ts` — boot-time oracle-deployment asserts consumers (FE/BE) fold
3
+ * onto instead of each hand-rolling them:
4
+ *
5
+ * - {@link assertOracleWriteCoverage} — the "this fed set can actually price
6
+ * the tickers I care about" guard. Because every source reads through its
7
+ * own feeds namespace, write set == read set — passing this ALSO validates
8
+ * the read plane (there is deliberately no separate read-coverage assert).
9
+ * - {@link missingOracleCredentials} — the env-shaped credential audit: which
10
+ * listed sources cannot run with the credentials this deployment supplied.
11
+ * Boot-time mirror of `refreshOraclePrices`'s own per-build credential
12
+ * pre-check (`aggregate.ts`), keyed off the same rule-owned
13
+ * `credential` declaration so the two can never disagree.
14
+ *
15
+ * Deliberately NOT called by `PerpClient` itself: client creation stays
16
+ * guard-free (a source with absent feeds surfaces at tx-build for exactly the
17
+ * tickers it can't serve). These are for consumers whose deployment policy is
18
+ * "fail the BOOT, not the first trade".
19
+ */
20
+ import type { OracleHost } from "./host.ts";
21
+ import { type OracleCredentialKind, type OracleSource } from "./price-update-rule.ts";
22
+ export type { OracleCredentialKind };
23
+ /**
24
+ * A build depends on a ticker this client's fed set cannot price.
25
+ *
26
+ * Raised from two places, deliberately the same type: the per-BUILD composers
27
+ * (`refreshOraclePrices` skipped the ticker by design — see
28
+ * `OracleRefreshSummary` — and the composer decided it was load-bearing for
29
+ * the action) and the boot-time {@link assertOracleWriteCoverage}. One error
30
+ * for one question: "this fed set cannot price these tickers".
31
+ */
32
+ export declare class OracleTickerUnservedError extends Error {
33
+ readonly tickers: string[];
34
+ readonly sources: readonly string[];
35
+ constructor(tickers: string[], sources: readonly string[], why?: string);
36
+ }
37
+ /**
38
+ * Assert this deployment's fed set can price every ticker in `tickers`.
39
+ *
40
+ * Throws {@link OracleTickerUnservedError} naming ALL the unservable ones (not
41
+ * just the first — an operator fixing a config wants the whole list).
42
+ *
43
+ * This is the boot-time twin of the per-build behaviour, and it guards the gap
44
+ * that per-build handling deliberately leaves open: `refreshOraclePrices`
45
+ * SKIPS a ticker no source serves, and only a composer that happens to depend
46
+ * on that ticker turns the skip into an error. A market nobody trades today
47
+ * would therefore stay silently unpriceable until someone did. Pass the ticker
48
+ * set your deployment cares about (typically every market) and find out at
49
+ * boot instead.
50
+ *
51
+ * It does NOT check "every listed source has feeds" any more: the fed set is
52
+ * derived from exactly that condition (see `deriveOracleSources`), so that
53
+ * assert became unreachable for any real client — it could only fire for a
54
+ * config mutated after construction, which is a test fixture, not a
55
+ * deployment. Write set == read set by construction (each source reads its own
56
+ * feeds), so this single assert still covers both planes.
57
+ */
58
+ export declare function assertOracleWriteCoverage(host: OracleHost, tickers: readonly string[]): void;
59
+ /**
60
+ * THE acceptance predicate, in partition form — the single definition of
61
+ * "will `refreshOraclePrices` put a price on chain for this ticker".
62
+ *
63
+ * A ticker is servable when some LISTED source's feeds carry it, or when it is
64
+ * CONSTANT-ONLY: `constant_rule` pins it and no other rule in the config feeds
65
+ * it. The stricter constant test matters — a constant-pinned ticker that some
66
+ * other rule also feeds gets aggregated from a constant-only collector, and if
67
+ * the chain weights that other rule the aggregate aborts `EMissingPriceSource`.
68
+ * (`aggregateTicker` only appends a supra leg when a price-update source
69
+ * already fed the collector, so a constant-only collector can never carry one.)
70
+ *
71
+ * `covered` lets a caller that has ALREADY resolved which of its tickers its
72
+ * fed set serves — `refreshOraclePrices`, off its rule groups — pass that in
73
+ * rather than have it recomputed. Both callers therefore share one rule, which
74
+ * is the point: a consumer pre-filtering with {@link servableTickers} cannot
75
+ * hand the build a ticker it will silently skip.
76
+ *
77
+ * Order-preserving. The `anyFeed` set is built at most once per call, and only
78
+ * when a constant-pinned ticker actually needs it.
79
+ */
80
+ export declare function partitionServableTickers(host: OracleHost, tickers: readonly string[], covered?: ReadonlySet<string>): {
81
+ servable: string[];
82
+ unservable: string[];
83
+ };
84
+ /**
85
+ * The subset of `tickers` this deployment's fed set can actually price — the
86
+ * servable half of {@link partitionServableTickers}, which is literally the
87
+ * rule `refreshOraclePrices` applies.
88
+ */
89
+ export declare function servableTickers(host: OracleHost, tickers: readonly string[]): string[];
90
+ /**
91
+ * Which of `sources` cannot run with the supplied credentials — one row per
92
+ * (source, missing credential). Empty array ⇒ the fed set is fully
93
+ * credentialed. Pure and env-shaped on purpose: consumers call it from their
94
+ * boot-time env asserts (zod superRefine, config validators) BEFORE any
95
+ * client exists, passing the raw values their env resolved. The per-build
96
+ * enforcement twin — `refreshOraclePrices`'s credential pre-check — reads the
97
+ * same rule-owned `credential` declaration, so a deployment this function passes
98
+ * cannot later trip that check for a listed source.
99
+ */
100
+ export declare function missingOracleCredentials(sources: readonly OracleSource[], creds: {
101
+ pythApiKey?: string;
102
+ }): {
103
+ source: OracleSource;
104
+ credential: OracleCredentialKind;
105
+ }[];