@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,14 +1,14 @@
1
1
  /**
2
2
  * `PriceUpdateRule` — the strategy port for one oracle rule generation: fetch
3
3
  * its off-chain update payload and emit the PTB calls that verify/push that
4
- * update on-chain (e.g. Pyth's wormhole-verify + price-feed-update block).
5
- * Feeding the refreshed price into an oracle `PriceCollector` is a separate
6
- * step that stays in `aggregate.ts` — this port covers fetch + verify/push
7
- * only (`buildUpdateCalls` may hand the feed step a PTB value via
8
- * {@link RuleUpdateHandle}). Implementations: `PythCoreRule` (Hermes VAA) and
9
- * `PythLazerRule` (Lazer signed updates), with `WaterxRule` (ed25519) to
10
- * follow. `ConstantRule` and `SupraRule` do NOT implement this port — they
11
- * remain plain collector-feed helpers wired directly into `aggregate.ts`.
4
+ * update on-chain (e.g. Lazer's single signature-verify step). Feeding the
5
+ * refreshed price into an oracle `PriceCollector` is a separate step that
6
+ * stays in `aggregate.ts` — this port covers fetch + verify/push only
7
+ * (`buildUpdateCalls` may hand the feed step a PTB value via
8
+ * {@link RuleUpdateHandle}). Implementations: `PythLazerRule` (Lazer signed
9
+ * updates) and `WaterxRule` (quote-center ed25519). `ConstantRule` and
10
+ * `SupraRule` do NOT implement this port — they remain plain collector-feed
11
+ * helpers wired directly into `aggregate.ts`.
12
12
  *
13
13
  * This file defines the port only — routing IS wired: `aggregate.ts`'s
14
14
  * `refreshOraclePrices` resolves a concrete rule per `host.oracleSources`
@@ -17,25 +17,29 @@
17
17
  */
18
18
  import type { Transaction, TransactionArgument } from "@mysten/sui/transactions";
19
19
  import type { OracleHost } from "./host.ts";
20
- import type { OracleFeeSource, PythCache } from "./pyth.ts";
21
- export type PriceUpdateRuleKind = "pyth_rule" | "pyth_lazer_rule" | "supra_rule" | "constant_rule" | "waterx_rule";
20
+ export type PriceUpdateRuleKind = "pyth_lazer_rule" | "supra_rule" | "constant_rule" | "waterx_rule";
22
21
  /**
23
22
  * The canonical list of selectable oracle sources — the SINGLE authority the
24
23
  * {@link OracleSource} union derives from (the value-list→union derive
25
24
  * idiom of `unified-client.ts`'s `NON_CLIENT_FIRST`, plus `Object.freeze`
26
25
  * 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`.
26
+ * consumer push into the array, and `deriveOracleSources` (`source-list.ts`)
27
+ * walks exactly this list to decide the fed set — a pushed entry would name a
28
+ * source with no rule module behind it.
30
29
  * Only sources belong here: `supra_rule` and `constant_rule` are auxiliary
31
30
  * rules fed alongside whichever sources are selected (see
32
31
  * `aggregateTicker`), not sources themselves — the `satisfies` keeps
33
32
  * entries inside `PriceUpdateRuleKind` but adding an auxiliary rule to this
34
33
  * list is an (incorrect) editorial decision this comment exists to prevent.
34
+ *
35
+ * `pyth_rule` (Pyth Core, Hermes VAA) was RETIRED in 5.0.0 — it is no longer
36
+ * a `PriceUpdateRuleKind` at all. Its config block is still published in the
37
+ * live deployments and is inert precisely because it is absent from this
38
+ * list: `deriveOracleSources` can never select it.
35
39
  */
36
- export declare const ORACLE_SOURCES: readonly ["pyth_rule", "pyth_lazer_rule", "waterx_rule"];
40
+ export declare const ORACLE_SOURCES: readonly ["pyth_lazer_rule", "waterx_rule"];
37
41
  /**
38
- * The kinds listable in a client's `oracleSource` create option (see
42
+ * The implementable price-update sources the candidates `deriveOracleSources` picks from (see
39
43
  * `OracleHost.oracleSources`) — i.e. rules that can serve as the on-chain
40
44
  * price *update* leg `refreshOraclePrices` runs before aggregating. Derived
41
45
  * from {@link ORACLE_SOURCES}. The SDK never reads `process.env` — consumers
@@ -45,8 +49,8 @@ export type OracleSource = (typeof ORACLE_SOURCES)[number];
45
49
  /**
46
50
  * Off-chain payload fetched by a rule, tagged by `kind` so a caller holding
47
51
  * several rules' results can tell them apart. `payload` is `unknown` here —
48
- * each rule implementation narrows it to its own shape (e.g. `PythCoreRule`'s
49
- * `{ updates: Uint8Array[]; feedIds: string[] }`). `null` for rules with no
52
+ * each rule implementation narrows it to its own shape (e.g. `PythLazerRule`'s
53
+ * `{ update: Uint8Array; feedIds: number[] }`). `null` for rules with no
50
54
  * off-chain fetch (e.g. `ConstantRule`) or when there is nothing to fetch.
51
55
  */
52
56
  export type RuleUpdateData = {
@@ -62,9 +66,8 @@ export type RuleUpdateData = {
62
66
  * empty ticker list upstream produced nothing to build).
63
67
  * 2. `data.kind !== kind` throws BEFORE the shape check runs. This order is
64
68
  * load-bearing, not stylistic: two rules' payloads can share an identical
65
- * shape (e.g. Pyth Core's `{ updates, feedIds }` also satisfies a
66
- * hypothetical same-shaped rule), so checking shape first would let a
67
- * wrong-kind payload silently pass as this rule's own.
69
+ * shape, so checking shape first would let a wrong-kind payload silently
70
+ * pass as this rule's own.
68
71
  * 3. `!isShape(data.payload)` throws for a same-`kind` payload whose shape
69
72
  * doesn't match this rule's own (e.g. a hand-built test double).
70
73
  *
@@ -75,18 +78,18 @@ export type RuleUpdateData = {
75
78
  * `data` may carry past step 2.
76
79
  * @param isShape - Type predicate narrowing `data.payload` to `T`.
77
80
  * @param shapeDescription - Human-readable shape, quoted verbatim into the
78
- * shape-mismatch error (e.g. `"{ updates: Uint8Array[]; feedIds: string[] }"`).
81
+ * shape-mismatch error (e.g. `"{ update: Uint8Array; feedIds: number[] }"`).
79
82
  */
80
83
  export declare function assertRuleUpdateData<T>(data: RuleUpdateData, kind: PriceUpdateRuleKind, isShape: (payload: unknown) => payload is T, shapeDescription: string): T | null;
81
84
  /**
82
85
  * PTB value handle a rule's {@link PriceUpdateRule.buildUpdateCalls} may
83
86
  * return when its collector-feed leg needs a value produced by the update leg
84
- * *within the same PTB*. Pyth Core needs none (its feed leg reads the shared
85
- * `PriceInfoObject` the update leg refreshed), so it returns `void`. The Lazer
86
- * rule returns the verified-update result of its network's verify entry — one
87
- * signature verification covers every feed in the payload, and
88
- * `pyth_lazer_rule::feed` takes it by reference per ticker (see
89
- * `aggregateTicker`'s `lazerUpdate` arg).
87
+ * *within the same PTB*. The Lazer rule returns the verified-update result of
88
+ * its network's verify entry one signature verification covers every feed
89
+ * in the payload, and `pyth_lazer_rule::feed` takes it by reference per
90
+ * ticker (see `aggregateTicker`'s `lazerUpdate` arg). `WaterxRule` needs none
91
+ * (its verify+feed is bundled into the per-ticker collect call), so it
92
+ * returns `void`.
90
93
  */
91
94
  export type RuleUpdateHandle = {
92
95
  readonly kind: "pyth_lazer_rule";
@@ -101,26 +104,12 @@ export type RuleUpdateHandle = {
101
104
  */
102
105
  readonly update: TransactionArgument;
103
106
  };
104
- /**
105
- * Options for {@link PriceUpdateRule.buildUpdateCalls}. Mirrors
106
- * `buildPythPriceUpdateCalls` / `updatePythPrices`'s own opts shape in
107
- * `./pyth.ts` — `cache` shares on-chain Pyth state reads across builders;
108
- * `feeSource` is the single {@link OracleFeeSource} already resolved by the
109
- * caller (see its own doc for where/how). Both fields are Pyth-Core-specific
110
- * mechanics; `refreshOraclePrices` passes the same `BuildUpdateOpts` to every
111
- * rule uniformly, so a non-Pyth-Core rule (e.g. `PythLazerRule`, which
112
- * charges no update fee) simply ignores whichever fields it has no use for.
113
- */
114
- export interface BuildUpdateOpts {
115
- readonly cache?: PythCache;
116
- readonly feeSource?: OracleFeeSource;
117
- }
118
107
  /**
119
108
  * Injectable update-data cache seam for `refreshOraclePrices` (`aggregate.ts`).
120
- * A BE consumer (e.g. a prefetch cache that polls Hermes/Lazer out-of-band and
121
- * keeps a hot in-memory/Redis entry per source) implements this and passes it
122
- * as `refreshOraclePrices`'s `updateDataProvider` opt; the SDK itself never
123
- * implements one. `get` is checked before the rule's own live
109
+ * A BE consumer (e.g. a prefetch cache that polls Lazer/quote-center
110
+ * out-of-band and keeps a hot in-memory/Redis entry per source) implements
111
+ * this and passes it as `refreshOraclePrices`'s `updateDataProvider` opt; the
112
+ * SDK itself never implements one. `get` is checked before the rule's own live
124
113
  * `fetchUpdateData` for that group of tickers — a `null` return means "no
125
114
  * cached data, fetch live" (mirrors {@link RuleUpdateData}'s own `null`
126
115
  * variant: there is no separate signal for "the cache legitimately has
@@ -137,6 +126,40 @@ export interface BuildUpdateOpts {
137
126
  export interface UpdateDataProvider {
138
127
  get(source: OracleSource, tickers: string[]): Promise<RuleUpdateData | null>;
139
128
  }
129
+ /** The credential kinds a rule can declare via {@link PriceUpdateRule.credential}. */
130
+ export type OracleCredentialKind = "pyth_api_key";
131
+ /**
132
+ * Caller-supplied credential values keyed BY {@link OracleCredentialKind} —
133
+ * the one shape both enforcement points check against, so neither has to
134
+ * branch on the kind. `refreshOraclePrices` builds it from a live
135
+ * `OracleHost` ({@link oracleCredentialsFromHost}); `missingOracleCredentials`
136
+ * builds it from a consumer's raw env values. Adding a kind means extending
137
+ * the union above plus those two adapters — never an `if` chain at a check
138
+ * site.
139
+ */
140
+ export type OracleCredentials = Partial<Record<OracleCredentialKind, string>>;
141
+ /** The credentials a live client carries, in {@link OracleCredentials} shape. */
142
+ export declare function oracleCredentialsFromHost(host: OracleHost): OracleCredentials;
143
+ /**
144
+ * A rule's off-chain credential requirement — the KIND it needs plus the error
145
+ * it wants raised when that credential is absent, as ONE object so the two can
146
+ * never drift apart.
147
+ *
148
+ * Both enforcement points are fully generic over this: neither names a kind,
149
+ * and neither constructs another rule's error. `refreshOraclePrices`'s hoisted
150
+ * pre-check (`aggregate.ts`) throws `missing()` before any fetch or PTB
151
+ * mutation; `missingOracleCredentials` (`validate.ts`) reports `kind` for a
152
+ * consumer's boot-time env audit.
153
+ */
154
+ export interface OracleCredentialRequirement {
155
+ readonly kind: OracleCredentialKind;
156
+ /**
157
+ * The rule's OWN error for "declared credential absent". Owning it here is
158
+ * what keeps the orchestrator from importing (and mis-attributing) one
159
+ * rule's error type on behalf of every rule that shares a kind.
160
+ */
161
+ missing(): Error;
162
+ }
140
163
  export interface PriceUpdateRule {
141
164
  /**
142
165
  * `OracleSource`, not the wider `PriceUpdateRuleKind`: only selectable
@@ -147,22 +170,18 @@ export interface PriceUpdateRule {
147
170
  */
148
171
  readonly kind: OracleSource;
149
172
  /**
150
- * `true` when this rule's on-chain update leg charges a per-update fee
151
- * that must be paid from either a sponsor fund or `tx.gas` (Pyth Core:
152
- * `true`, via `pyth::update_single_price_feed`'s `base_update_fee`).
153
- * `false` for a fee-free update leg (Lazer: signature verification only,
154
- * no `Coin` argument). `refreshOraclePrices` (`aggregate.ts`) reads this
155
- * BEFORE fetching any group's off-chain payload — for every group whose
156
- * rule sets it `true`, `opts.feeSource` must already be resolved, or the
157
- * whole call throws `OracleFeeSourceUnavailable` before
158
- * any group builds (mixed-shape atomicity: a fee-free group ordered
159
- * ahead of a fee-charging one in the same PTB must never get to mutate
160
- * `tx` while the fee-charging group is left unpayable). A referential
161
- * check against a specific rule instance (e.g. `=== PythCoreRule`) would
162
- * silently stop protecting a future fee-charging rule, or a test double
163
- * standing in for one — this field is the honest, extensible signal.
173
+ * The credential this rule's `fetchUpdateData` cannot run without, or absent
174
+ * for a credential-free rule. `PythLazerRule` declares `"pyth_api_key"`
175
+ * (Lazer is auth-first: `host.pyth.api_key` Bearer) with its own
176
+ * `LazerApiKeyMissingError`; `WaterxRule` declares nothing (the quote-center
177
+ * read surface is public).
178
+ *
179
+ * Two consumers key off this instead of hardcoding per-rule knowledge
180
+ * `refreshOraclePrices`'s hoisted pre-check and `missingOracleCredentials`
181
+ * and neither branches on the kind or names an error type. See
182
+ * {@link OracleCredentialRequirement}.
164
183
  */
165
- readonly requiresFeeSource: boolean;
184
+ readonly credential?: OracleCredentialRequirement;
166
185
  /** Tickers this rule can serve in this environment (from config feeds + enabled). */
167
186
  supportedTickers(host: OracleHost): string[];
168
187
  /**
@@ -182,9 +201,10 @@ export interface PriceUpdateRule {
182
201
  * altitude violation this method removes):
183
202
  *
184
203
  * - A non-null result MUST be valid {@link buildUpdateCalls} input covering
185
- * exactly `tickers` — a divisible payload (Pyth Core's per-feed entries)
186
- * returns a subset; an indivisible payload (Lazer's single signed message)
187
- * returns the whole payload iff every requested ticker is covered.
204
+ * exactly `tickers` — a divisible payload (waterx's per-symbol leaves)
205
+ * returns a subset; an indivisible payload (Lazer's single signed message,
206
+ * waterx's batch envelope) returns the whole payload iff every requested
207
+ * ticker is covered.
188
208
  * - A ticker this payload cannot serve → `null` (miss), NEVER a silent
189
209
  * partial. `null` mirrors {@link UpdateDataProvider.get}'s convention: the
190
210
  * caller falls back to a live {@link fetchUpdateData} for those tickers.
@@ -195,14 +215,32 @@ export interface PriceUpdateRule {
195
215
  * bug), it does not miss.
196
216
  */
197
217
  narrowUpdateData(host: OracleHost, data: RuleUpdateData, tickers: string[]): RuleUpdateData;
218
+ /**
219
+ * The per-symbol single-use identity of a payload's signed data, or `null`
220
+ * when this rule's updates carry no replay-guarded identity (then the
221
+ * method may also be absent entirely). This is the rule-owned key of the
222
+ * on-chain F-014 replay guard: submitting the SAME identity twice for a
223
+ * symbol is at best a paid-for abstain (the dual-rule collect entries) and
224
+ * at worst an `EReplayedSignature` abort (the single-rule feed entries) —
225
+ * so a consumer serving cached update data (e.g. a BE serve-at-most-once
226
+ * cache) keys its guard off this map instead of re-implementing each
227
+ * rule's payload anatomy.
228
+ *
229
+ * `WaterxRule`: leaves → `symbol → signed_timestamp_ms`; envelope →
230
+ * `symbol → timestamp_ms` (per item; the envelope's one signing timestamp
231
+ * is every covered symbol's identity). `PythLazerRule` does not implement
232
+ * it — a Lazer verify is not identity-replay-guarded on-chain.
233
+ */
234
+ updateIdentityBySymbol?(data: RuleUpdateData): Map<string, bigint> | null;
198
235
  /**
199
236
  * Emit verify/update moveCalls + any per-rule setup into the PTB. Returns a
200
237
  * {@link RuleUpdateHandle} when the rule's collector-feed leg needs a PTB
201
238
  * value from this step (Lazer's verified `Update`); rules whose feed leg
202
- * reads shared on-chain objects return `void`. Takes no `tickers` param —
239
+ * needs nothing from it return `void`. Takes no `tickers` param —
203
240
  * every implementation derives everything it needs from `data.payload`
204
241
  * (the tickers a group covers were already fixed when `fetchUpdateData`
205
- * built that payload).
242
+ * built that payload) — and no options: nothing a rule needs at build time
243
+ * is caller-tunable, so there is no bag to thread.
206
244
  */
207
- buildUpdateCalls(tx: Transaction, host: OracleHost, data: RuleUpdateData, opts?: BuildUpdateOpts): Promise<RuleUpdateHandle | void> | RuleUpdateHandle | void;
245
+ buildUpdateCalls(tx: Transaction, host: OracleHost, data: RuleUpdateData): Promise<RuleUpdateHandle | void> | RuleUpdateHandle | void;
208
246
  }
@@ -2,14 +2,14 @@
2
2
  /**
3
3
  * `PriceUpdateRule` — the strategy port for one oracle rule generation: fetch
4
4
  * its off-chain update payload and emit the PTB calls that verify/push that
5
- * update on-chain (e.g. Pyth's wormhole-verify + price-feed-update block).
6
- * Feeding the refreshed price into an oracle `PriceCollector` is a separate
7
- * step that stays in `aggregate.ts` — this port covers fetch + verify/push
8
- * only (`buildUpdateCalls` may hand the feed step a PTB value via
9
- * {@link RuleUpdateHandle}). Implementations: `PythCoreRule` (Hermes VAA) and
10
- * `PythLazerRule` (Lazer signed updates), with `WaterxRule` (ed25519) to
11
- * follow. `ConstantRule` and `SupraRule` do NOT implement this port — they
12
- * remain plain collector-feed helpers wired directly into `aggregate.ts`.
5
+ * update on-chain (e.g. Lazer's single signature-verify step). Feeding the
6
+ * refreshed price into an oracle `PriceCollector` is a separate step that
7
+ * stays in `aggregate.ts` — this port covers fetch + verify/push only
8
+ * (`buildUpdateCalls` may hand the feed step a PTB value via
9
+ * {@link RuleUpdateHandle}). Implementations: `PythLazerRule` (Lazer signed
10
+ * updates) and `WaterxRule` (quote-center ed25519). `ConstantRule` and
11
+ * `SupraRule` do NOT implement this port — they remain plain collector-feed
12
+ * helpers wired directly into `aggregate.ts`.
13
13
  *
14
14
  * This file defines the port only — routing IS wired: `aggregate.ts`'s
15
15
  * `refreshOraclePrices` resolves a concrete rule per `host.oracleSources`
@@ -19,22 +19,27 @@
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
20
  exports.ORACLE_SOURCES = void 0;
21
21
  exports.assertRuleUpdateData = assertRuleUpdateData;
22
+ exports.oracleCredentialsFromHost = oracleCredentialsFromHost;
22
23
  /**
23
24
  * The canonical list of selectable oracle sources — the SINGLE authority the
24
25
  * {@link OracleSource} union derives from (the value-list→union derive
25
26
  * idiom of `unified-client.ts`'s `NON_CLIENT_FIRST`, plus `Object.freeze`
26
27
  * 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`.
28
+ * consumer push into the array, and `deriveOracleSources` (`source-list.ts`)
29
+ * walks exactly this list to decide the fed set — a pushed entry would name a
30
+ * source with no rule module behind it.
30
31
  * Only sources belong here: `supra_rule` and `constant_rule` are auxiliary
31
32
  * rules fed alongside whichever sources are selected (see
32
33
  * `aggregateTicker`), not sources themselves — the `satisfies` keeps
33
34
  * entries inside `PriceUpdateRuleKind` but adding an auxiliary rule to this
34
35
  * list is an (incorrect) editorial decision this comment exists to prevent.
36
+ *
37
+ * `pyth_rule` (Pyth Core, Hermes VAA) was RETIRED in 5.0.0 — it is no longer
38
+ * a `PriceUpdateRuleKind` at all. Its config block is still published in the
39
+ * live deployments and is inert precisely because it is absent from this
40
+ * list: `deriveOracleSources` can never select it.
35
41
  */
36
42
  exports.ORACLE_SOURCES = Object.freeze([
37
- "pyth_rule",
38
43
  "pyth_lazer_rule",
39
44
  "waterx_rule",
40
45
  ]);
@@ -47,9 +52,8 @@ exports.ORACLE_SOURCES = Object.freeze([
47
52
  * empty ticker list upstream produced nothing to build).
48
53
  * 2. `data.kind !== kind` throws BEFORE the shape check runs. This order is
49
54
  * load-bearing, not stylistic: two rules' payloads can share an identical
50
- * shape (e.g. Pyth Core's `{ updates, feedIds }` also satisfies a
51
- * hypothetical same-shaped rule), so checking shape first would let a
52
- * wrong-kind payload silently pass as this rule's own.
55
+ * shape, so checking shape first would let a wrong-kind payload silently
56
+ * pass as this rule's own.
53
57
  * 3. `!isShape(data.payload)` throws for a same-`kind` payload whose shape
54
58
  * doesn't match this rule's own (e.g. a hand-built test double).
55
59
  *
@@ -60,7 +64,7 @@ exports.ORACLE_SOURCES = Object.freeze([
60
64
  * `data` may carry past step 2.
61
65
  * @param isShape - Type predicate narrowing `data.payload` to `T`.
62
66
  * @param shapeDescription - Human-readable shape, quoted verbatim into the
63
- * shape-mismatch error (e.g. `"{ updates: Uint8Array[]; feedIds: string[] }"`).
67
+ * shape-mismatch error (e.g. `"{ update: Uint8Array; feedIds: number[] }"`).
64
68
  */
65
69
  function assertRuleUpdateData(data, kind, isShape, shapeDescription) {
66
70
  if (!data)
@@ -74,3 +78,7 @@ function assertRuleUpdateData(data, kind, isShape, shapeDescription) {
74
78
  }
75
79
  return data.payload;
76
80
  }
81
+ /** The credentials a live client carries, in {@link OracleCredentials} shape. */
82
+ function oracleCredentialsFromHost(host) {
83
+ return { pyth_api_key: host.pyth.api_key };
84
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * `pyth-pro-history.ts` — Pyth Pro chart history
3
+ * (`GET /v1/{channel}/history`), the TradingView-UDF-shaped bar source that
4
+ * replaces Benchmarks for chart backfill. Bearer-REQUIRED (unlike the symbol
5
+ * catalog): consumers call this server-side with their `PYTH_API_KEY` and
6
+ * keep their own fallback policy (e.g. Benchmarks on 403/5xx while its
7
+ * keyless window lasts).
8
+ *
9
+ * The `symbol` must be FULLY QUALIFIED (`Crypto.BTC/USD`,
10
+ * `Equity.US.AAPL/USD` — the catalog's `symbol` field); a bare pair 404s.
11
+ * `channel` picks the aggregation cadence the key's grant allows (e.g.
12
+ * `fixed_rate@1000ms`).
13
+ */
14
+ import { type FetchPolicy } from "./update-fetch.ts";
15
+ /**
16
+ * Fetch one history window. Returns the endpoint's TradingView-UDF-style JSON
17
+ * body VERBATIM (`unknown` — e.g. `{ s: "ok", t: [...], o: [...], h: [...],
18
+ * l: [...], c: [...] }`): bar-shape interpretation stays with the charting
19
+ * consumer, the SDK only owns transport + auth. Throws on non-2xx with the
20
+ * body attached (a 403 here is the caller's fallback trigger).
21
+ */
22
+ export declare function fetchPythProHistory(opts: {
23
+ /** Aggregation channel path segment, e.g. `"fixed_rate@1000ms"`. */
24
+ channel: string;
25
+ /** Fully-qualified reference symbol, e.g. `"Crypto.BTC/USD"`. */
26
+ symbol: string;
27
+ /** UDF resolution, e.g. `"1"`, `"60"`, `"1D"`. */
28
+ resolution: string;
29
+ /** Window start (unix seconds, inclusive). */
30
+ fromSec: number;
31
+ /** Window end (unix seconds, inclusive). */
32
+ toSec: number;
33
+ /** Pyth Pro Bearer key — REQUIRED by the endpoint. */
34
+ apiKey: string;
35
+ fetch?: FetchPolicy;
36
+ }): Promise<unknown>;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ /**
3
+ * `pyth-pro-history.ts` — Pyth Pro chart history
4
+ * (`GET /v1/{channel}/history`), the TradingView-UDF-shaped bar source that
5
+ * replaces Benchmarks for chart backfill. Bearer-REQUIRED (unlike the symbol
6
+ * catalog): consumers call this server-side with their `PYTH_API_KEY` and
7
+ * keep their own fallback policy (e.g. Benchmarks on 403/5xx while its
8
+ * keyless window lasts).
9
+ *
10
+ * The `symbol` must be FULLY QUALIFIED (`Crypto.BTC/USD`,
11
+ * `Equity.US.AAPL/USD` — the catalog's `symbol` field); a bare pair 404s.
12
+ * `channel` picks the aggregation cadence the key's grant allows (e.g.
13
+ * `fixed_rate@1000ms`).
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.fetchPythProHistory = fetchPythProHistory;
17
+ const symbol_catalog_ts_1 = require("./symbol-catalog.js");
18
+ const update_fetch_ts_1 = require("./update-fetch.js");
19
+ /**
20
+ * Fetch one history window. Returns the endpoint's TradingView-UDF-style JSON
21
+ * body VERBATIM (`unknown` — e.g. `{ s: "ok", t: [...], o: [...], h: [...],
22
+ * l: [...], c: [...] }`): bar-shape interpretation stays with the charting
23
+ * consumer, the SDK only owns transport + auth. Throws on non-2xx with the
24
+ * body attached (a 403 here is the caller's fallback trigger).
25
+ */
26
+ async function fetchPythProHistory(opts) {
27
+ const url = (0, update_fetch_ts_1.joinEndpointPath)(symbol_catalog_ts_1.PYTH_PRO_API_ENDPOINT, `v1/${opts.channel}/history`);
28
+ url.searchParams.set("symbol", opts.symbol);
29
+ url.searchParams.set("resolution", opts.resolution);
30
+ url.searchParams.set("from", String(opts.fromSec));
31
+ url.searchParams.set("to", String(opts.toSec));
32
+ // `opts.fetch` is the full `FetchPolicy`, which itself has an `apiKey`, so
33
+ // spreading it AFTER the explicit one let `{ fetch: { apiKey: undefined } }`
34
+ // silently strip the Bearer this endpoint requires — a 403 with no clue why.
35
+ // The dedicated argument wins; the policy supplies it only as a fallback.
36
+ const res = await (0, update_fetch_ts_1.fetchWithPolicy)(url.toString(), {}, { ...opts.fetch, apiKey: opts.apiKey ?? opts.fetch?.apiKey });
37
+ if (!res.ok) {
38
+ throw new Error(`Pyth Pro history fetch failed: ${res.status} ${await res.text()}`);
39
+ }
40
+ return res.json();
41
+ }
@@ -1,70 +1,52 @@
1
1
  /**
2
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.
3
+ * tickers a source can PRICE off-chain, and with which ids. Every source
4
+ * reads through ITS OWN feeds namespace `pyth_lazer_rule` via its integer
5
+ * ids on the Lazer HTTP API, `waterx_rule` via its tickers on the
6
+ * quote-center so a source's write set and read set coincide by
7
+ * construction. (Until 5.0.0 Lazer reads borrowed `pyth_rule.feeds` hex ids
8
+ * on a Hermes-compatible endpoint; that cross-block dependency, the whole
9
+ * hermes plane, and the `unreadable` diagnostic are gone with the `pyth_rule`
10
+ * retirement.) Consumers (FE/BE price facades) resolve through this instead
11
+ * of hardcoding feed namespaces, and execute plans via the sibling
12
+ * `read-prices.ts` executors.
12
13
  */
13
- import type { Network } from "../constants.ts";
14
14
  import type { OracleHost } from "./host.ts";
15
15
  import type { OracleSource } from "./price-update-rule.ts";
16
16
  /**
17
17
  * One source's read plan for a requested ticker set.
18
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.
19
+ * - `plane: "lazer"` (`pyth_lazer_rule`) — price via the Lazer HTTP API
20
+ * (`readLazerPrices` in `read-prices.ts`), one entry per servable ticker
21
+ * mapped to its INTEGER Lazer feed id from `pyth_lazer_rule.feeds`. Auth is
22
+ * the caller's `pythApiKey` Bearer; the endpoint is `LAZER_INFRA`'s own.
23
+ * - `plane: "quote_center"` (`waterx_rule`) — price via the quote-center
24
+ * (`readQuoteCenterPrices` in `read-prices.ts`), keyed by ticker; served
25
+ * set = the `waterx_rule.feeds` block. An ABSENT block (source listed,
26
+ * package missing from the loaded config) serves NOTHING: claiming tickers
27
+ * would silently reroute reads to the quote-center — it happily serves
28
+ * symbols regardless of on-chain config and swallow tickers a
29
+ * later-listed source could price. The misconfiguration is caught loudly by
30
+ * `assertOracleWriteCoverage` (`validate.ts`) at client creation instead.
31
+ *
32
+ * A ticker absent from a plan is simply not servable by THIS source's read
33
+ * plane callers decide how to degrade (typically: ask the next source in
34
+ * their fed set, then omit). Because every source reads its own
35
+ * feeds, write set == read set — there is no separate read-coverage
36
+ * diagnostic to carry.
36
37
  */
37
38
  export type OracleReadPlan = {
38
- unreadable: string[];
39
- } & ({
40
- plane: "hermes";
41
- feedIdByTicker: Map<string, string>;
39
+ plane: "lazer";
40
+ feedIdByTicker: Map<string, number>;
42
41
  } | {
43
42
  plane: "quote_center";
44
43
  tickers: string[];
45
- });
44
+ };
45
+ /** The tickers a resolved plan can actually serve, regardless of plane. */
46
+ export declare function readPlanTickers(plan: OracleReadPlan): string[];
46
47
  /**
47
48
  * 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.
49
+ * network, no endpoint resolution (endpoints come from `LAZER_INFRA` /
50
+ * `waterxQuoteCenterEndpoint` / the deployment's own env).
68
51
  */
69
- export declare function resolveHermesReadEndpoint(network: Network, sources: readonly OracleSource[], override?: string): string;
70
52
  export declare function resolveOracleReadPlan(host: OracleHost, source: OracleSource, tickers: string[]): OracleReadPlan;