@waterx/sdk 4.3.3 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/.claude/skills/waterx-sdk-integration/SKILL.md +41 -35
  2. package/README.md +81 -77
  3. package/dist/cjs/src/account/config.d.ts +3 -5
  4. package/dist/cjs/src/account/funding/wormhole.d.ts +1 -2
  5. package/dist/cjs/src/account/funding/wormhole.js +1 -2
  6. package/dist/cjs/src/oracle/aggregate.d.ts +45 -61
  7. package/dist/cjs/src/oracle/aggregate.js +138 -138
  8. package/dist/cjs/src/oracle/config.d.ts +35 -49
  9. package/dist/cjs/src/oracle/config.js +1 -1
  10. package/dist/cjs/src/oracle/host.d.ts +14 -19
  11. package/dist/cjs/src/oracle/host.js +3 -3
  12. package/dist/cjs/src/oracle/index.d.ts +25 -16
  13. package/dist/cjs/src/oracle/index.js +79 -66
  14. package/dist/cjs/src/oracle/price-update-rule.d.ts +104 -66
  15. package/dist/cjs/src/oracle/price-update-rule.js +24 -16
  16. package/dist/cjs/src/oracle/pyth-pro-history.d.ts +36 -0
  17. package/dist/cjs/src/oracle/pyth-pro-history.js +41 -0
  18. package/dist/cjs/src/oracle/read-plane.d.ts +35 -53
  19. package/dist/cjs/src/oracle/read-plane.js +26 -48
  20. package/dist/cjs/src/oracle/read-prices.d.ts +105 -0
  21. package/dist/cjs/src/oracle/read-prices.js +162 -0
  22. package/dist/cjs/src/oracle/rule-registry.d.ts +12 -12
  23. package/dist/cjs/src/oracle/rule-registry.js +12 -14
  24. package/dist/cjs/src/oracle/rules/pyth-lazer-rule.d.ts +24 -3
  25. package/dist/cjs/src/oracle/rules/pyth-lazer-rule.js +40 -20
  26. package/dist/cjs/src/oracle/rules/waterx-rule.d.ts +137 -12
  27. package/dist/cjs/src/oracle/rules/waterx-rule.js +318 -55
  28. package/dist/cjs/src/oracle/schedule.d.ts +109 -0
  29. package/dist/cjs/src/oracle/schedule.js +991 -0
  30. package/dist/cjs/src/oracle/source-list.d.ts +31 -27
  31. package/dist/cjs/src/oracle/source-list.js +43 -45
  32. package/dist/cjs/src/oracle/symbol-catalog.d.ts +54 -0
  33. package/dist/cjs/src/oracle/symbol-catalog.js +69 -0
  34. package/dist/cjs/src/oracle/update-fetch.d.ts +11 -15
  35. package/dist/cjs/src/oracle/update-fetch.js +12 -19
  36. package/dist/cjs/src/oracle/validate.d.ts +105 -0
  37. package/dist/cjs/src/oracle/validate.js +185 -0
  38. package/dist/cjs/src/oracle/weight-coverage.d.ts +79 -0
  39. package/dist/cjs/src/oracle/weight-coverage.js +173 -0
  40. package/dist/cjs/src/perp/client.d.ts +35 -52
  41. package/dist/cjs/src/perp/client.js +44 -17
  42. package/dist/cjs/src/perp/config-view.d.ts +5 -14
  43. package/dist/cjs/src/perp/config-view.js +5 -16
  44. package/dist/cjs/src/perp/config.d.ts +3 -3
  45. package/dist/cjs/src/perp/config.js +11 -13
  46. package/dist/cjs/src/perp/index.d.ts +4 -6
  47. package/dist/cjs/src/perp/index.js +35 -18
  48. package/dist/cjs/src/perp/tx-builders/common.d.ts +86 -48
  49. package/dist/cjs/src/perp/tx-builders/common.js +115 -47
  50. package/dist/cjs/src/perp/tx-builders/wlp.d.ts +3 -14
  51. package/dist/cjs/src/perp/tx-builders/wlp.js +6 -35
  52. package/dist/cjs/src/perp/tx-builders.d.ts +6 -5
  53. package/dist/cjs/src/perp/tx-builders.js +16 -10
  54. package/dist/cjs/src/unified-client.d.ts +8 -34
  55. package/dist/cjs/src/unified-client.js +3 -4
  56. package/dist/cjs/src/utils/config.d.ts +0 -8
  57. package/dist/cjs/src/utils/config.js +5 -13
  58. package/dist/src/account/config.d.ts +3 -5
  59. package/dist/src/account/funding/wormhole.d.ts +1 -2
  60. package/dist/src/account/funding/wormhole.js +1 -2
  61. package/dist/src/oracle/aggregate.d.ts +45 -61
  62. package/dist/src/oracle/aggregate.js +138 -137
  63. package/dist/src/oracle/config.d.ts +35 -49
  64. package/dist/src/oracle/config.js +1 -1
  65. package/dist/src/oracle/host.d.ts +14 -19
  66. package/dist/src/oracle/host.js +3 -3
  67. package/dist/src/oracle/index.d.ts +25 -16
  68. package/dist/src/oracle/index.js +55 -50
  69. package/dist/src/oracle/price-update-rule.d.ts +104 -66
  70. package/dist/src/oracle/price-update-rule.js +23 -16
  71. package/dist/src/oracle/pyth-pro-history.d.ts +36 -0
  72. package/dist/src/oracle/pyth-pro-history.js +38 -0
  73. package/dist/src/oracle/read-plane.d.ts +35 -53
  74. package/dist/src/oracle/read-plane.js +25 -47
  75. package/dist/src/oracle/read-prices.d.ts +105 -0
  76. package/dist/src/oracle/read-prices.js +156 -0
  77. package/dist/src/oracle/rule-registry.d.ts +12 -12
  78. package/dist/src/oracle/rule-registry.js +12 -14
  79. package/dist/src/oracle/rules/pyth-lazer-rule.d.ts +24 -3
  80. package/dist/src/oracle/rules/pyth-lazer-rule.js +39 -20
  81. package/dist/src/oracle/rules/waterx-rule.d.ts +137 -12
  82. package/dist/src/oracle/rules/waterx-rule.js +314 -56
  83. package/dist/src/oracle/schedule.d.ts +109 -0
  84. package/dist/src/oracle/schedule.js +985 -0
  85. package/dist/src/oracle/source-list.d.ts +31 -27
  86. package/dist/src/oracle/source-list.js +42 -43
  87. package/dist/src/oracle/symbol-catalog.d.ts +54 -0
  88. package/dist/src/oracle/symbol-catalog.js +65 -0
  89. package/dist/src/oracle/update-fetch.d.ts +11 -15
  90. package/dist/src/oracle/update-fetch.js +12 -18
  91. package/dist/src/oracle/validate.d.ts +105 -0
  92. package/dist/src/oracle/validate.js +177 -0
  93. package/dist/src/oracle/weight-coverage.d.ts +79 -0
  94. package/dist/src/oracle/weight-coverage.js +166 -0
  95. package/dist/src/perp/client.d.ts +35 -52
  96. package/dist/src/perp/client.js +45 -18
  97. package/dist/src/perp/config-view.d.ts +5 -14
  98. package/dist/src/perp/config-view.js +5 -16
  99. package/dist/src/perp/config.d.ts +3 -3
  100. package/dist/src/perp/config.js +11 -13
  101. package/dist/src/perp/index.d.ts +4 -6
  102. package/dist/src/perp/index.js +11 -5
  103. package/dist/src/perp/tx-builders/common.d.ts +86 -48
  104. package/dist/src/perp/tx-builders/common.js +114 -48
  105. package/dist/src/perp/tx-builders/wlp.d.ts +3 -14
  106. package/dist/src/perp/tx-builders/wlp.js +6 -35
  107. package/dist/src/perp/tx-builders.d.ts +6 -5
  108. package/dist/src/perp/tx-builders.js +15 -6
  109. package/dist/src/unified-client.d.ts +8 -34
  110. package/dist/src/unified-client.js +3 -4
  111. package/dist/src/utils/config.d.ts +0 -8
  112. package/dist/src/utils/config.js +5 -12
  113. package/package.json +1 -1
  114. package/dist/cjs/src/generated/pyth_sponsor_rule/deps/sui/balance.d.ts +0 -12
  115. package/dist/cjs/src/generated/pyth_sponsor_rule/deps/sui/balance.js +0 -17
  116. package/dist/cjs/src/generated/pyth_sponsor_rule/pyth_sponsor_rule.d.ts +0 -68
  117. package/dist/cjs/src/generated/pyth_sponsor_rule/pyth_sponsor_rule.js +0 -113
  118. package/dist/cjs/src/generated/waterx_pyth_rule/deps/sui/vec_map.d.ts +0 -36
  119. package/dist/cjs/src/generated/waterx_pyth_rule/deps/sui/vec_map.js +0 -31
  120. package/dist/cjs/src/generated/waterx_pyth_rule/pyth_rule.d.ts +0 -192
  121. package/dist/cjs/src/generated/waterx_pyth_rule/pyth_rule.js +0 -214
  122. package/dist/cjs/src/oracle/pyth.d.ts +0 -227
  123. package/dist/cjs/src/oracle/pyth.js +0 -652
  124. package/dist/cjs/src/oracle/rules/pyth-core-rule.d.ts +0 -15
  125. package/dist/cjs/src/oracle/rules/pyth-core-rule.js +0 -100
  126. package/dist/cjs/src/oracle/rules/pyth-rule.d.ts +0 -11
  127. package/dist/cjs/src/oracle/rules/pyth-rule.js +0 -29
  128. package/dist/cjs/src/oracle/rules/sponsor.d.ts +0 -32
  129. package/dist/cjs/src/oracle/rules/sponsor.js +0 -56
  130. package/dist/src/generated/pyth_sponsor_rule/deps/sui/balance.d.ts +0 -12
  131. package/dist/src/generated/pyth_sponsor_rule/deps/sui/balance.js +0 -14
  132. package/dist/src/generated/pyth_sponsor_rule/pyth_sponsor_rule.d.ts +0 -68
  133. package/dist/src/generated/pyth_sponsor_rule/pyth_sponsor_rule.js +0 -73
  134. package/dist/src/generated/waterx_pyth_rule/deps/sui/vec_map.d.ts +0 -36
  135. package/dist/src/generated/waterx_pyth_rule/deps/sui/vec_map.js +0 -27
  136. package/dist/src/generated/waterx_pyth_rule/pyth_rule.d.ts +0 -192
  137. package/dist/src/generated/waterx_pyth_rule/pyth_rule.js +0 -170
  138. package/dist/src/oracle/pyth.d.ts +0 -227
  139. package/dist/src/oracle/pyth.js +0 -638
  140. package/dist/src/oracle/rules/pyth-core-rule.d.ts +0 -15
  141. package/dist/src/oracle/rules/pyth-core-rule.js +0 -97
  142. package/dist/src/oracle/rules/pyth-rule.d.ts +0 -11
  143. package/dist/src/oracle/rules/pyth-rule.js +0 -26
  144. package/dist/src/oracle/rules/sponsor.d.ts +0 -32
  145. package/dist/src/oracle/rules/sponsor.js +0 -52
@@ -2,12 +2,12 @@
2
2
  * Oracle aggregation — the orchestrator that composes rules into the shared
3
3
  * `Oracle`. This is the ONE file that knows about every rule: it builds a
4
4
  * `PriceCollector`, feeds whichever rules a ticker is configured for
5
- * (Pyth / Lazer / Supra / Constant), then `aggregate`s.
5
+ * (Lazer / Waterx / Supra / Constant), then `aggregate`s.
6
6
  *
7
7
  * Per ticker:
8
8
  * collector = oracle::new_collector(ticker)
9
- * [pyth_rule::feed] when the ticker has a pyth_rule.feeds entry
10
9
  * [pyth_lazer_rule::feed] when the update leg produced a verified lazer Update
10
+ * [waterx_rule::collect_*] when the update leg fetched signed waterx data
11
11
  * [supra_rule::feed] when supra is enabled + wired
12
12
  * [constant_rule::feed] when the ticker is a constant ticker
13
13
  * oracle::aggregate(oracle, collector)
@@ -24,38 +24,32 @@
24
24
  */
25
25
  import type { Transaction, TransactionArgument } from "@mysten/sui/transactions";
26
26
  import type { OracleHost } from "./host.ts";
27
- import type { OracleSource, PriceUpdateRule, UpdateDataProvider } from "./price-update-rule.ts";
28
- import { type OracleFeeSource, type PythCache } from "./pyth.ts";
27
+ import { type OracleSource, type PriceUpdateRule, type UpdateDataProvider } from "./price-update-rule.ts";
29
28
  import { type WaterxSignedEnvelope, type WaterxSignedLeaf } from "./rules/waterx-rule.ts";
30
29
  /**
31
30
  * Aggregate one ticker's price into the shared `Oracle`: build a collector, feed
32
31
  * every rule the ticker is configured for, then `aggregate`.
33
32
  *
34
- * - **Pyth** — fed when `priceInfoObjectId` is supplied (i.e. the ticker has a
35
- * `pyth_rule.feeds` entry). When this PTB's update leg refreshed the
36
- * `PriceInfoObject` it contributes a fresh price; when it did not (a
37
- * lazer-routed ticker), the on-chain rule only READS the object and abstains
38
- * if it is stale — it never aborts — so the call stays mandatory while
39
- * `pyth_rule` remains in the ticker's on-chain weighted set
40
- * (`EMissingPriceSource` requires every weighted rule to appear).
41
33
  * - **Lazer** — fed when `lazerUpdate` is supplied: the verified update this
42
34
  * PTB's lazer update leg produced with the network's verify entry
43
35
  * (`update_v2::Update` on mainnet, `update::Update` on testnet — see
44
36
  * `PythLazerRule.buildUpdateCalls`). If the ticker's aggregator does
45
37
  * not (yet) weight `PythLazerRule`, the contribution is silently dropped
46
38
  * on-chain — feeding ahead of the weight migration is harmless.
47
- * - **Supra** — fed alongside Pyth/Lazer when supra is enabled + wired
39
+ * - **Waterx** — fed when `waterxLeaf` (default shape) or `waterxEnvelope`
40
+ * (fallback shape) is supplied; verify AND feed are bundled into the one
41
+ * collect call per collector.
42
+ * - **Supra** — fed alongside the sources when supra is enabled + wired
48
43
  * (abstains on-chain for symbols it has no pair for).
49
44
  * - **Constant** — fed when the ticker is a constant ticker
50
45
  * ({@link OracleHost.isConstantTicker}).
51
46
  *
52
- * "Dual-feed" (Pyth + Constant, or Pyth + Lazer) and "constant-only" are not
47
+ * "Dual-feed" (Lazer + Constant, or Lazer + Waterx) and "constant-only" are not
53
48
  * special cases — they fall out of which rules the ticker is in. Throws if no
54
49
  * rule applies.
55
50
  */
56
51
  export declare function aggregateTicker(tx: Transaction, host: OracleHost, args: {
57
52
  ticker: string;
58
- priceInfoObjectId?: string;
59
53
  lazerUpdate?: TransactionArgument;
60
54
  /** This ticker's signed Merkle leaf — the default waterx shape. */
61
55
  waterxLeaf?: WaterxSignedLeaf;
@@ -63,31 +57,35 @@ export declare function aggregateTicker(tx: Transaction, host: OracleHost, args:
63
57
  waterxEnvelope?: WaterxSignedEnvelope;
64
58
  }): void;
65
59
  /**
66
- * Thin wrapper over {@link aggregateTicker} for a Pyth-fed ticker. Kept for
67
- * back-compat (e.g. WLP mint builds). Caller must run the Pyth update first.
60
+ * {@link aggregateTicker} for a **constant-only** ticker (no source update
61
+ * needed the price comes from the on-chain `constant_rule::Config`). Kept as
62
+ * a named entry so constant-only call sites (e.g. WLP builders refreshing the
63
+ * USDCUSD pool token) read as what they are; it adds nothing over
64
+ * `aggregateTicker(tx, host, { ticker })`.
68
65
  */
69
- export declare function aggregateTickerWithPyth(tx: Transaction, host: OracleHost, args: {
66
+ export declare function aggregateTickerWithConstant(tx: Transaction, host: OracleHost, args: {
70
67
  ticker: string;
71
- priceInfoObjectId: string;
72
68
  }): void;
73
69
  /**
74
- * {@link aggregateTicker} for a **constant-only** ticker (no Pyth update needed —
75
- * the price comes from the on-chain `constant_rule::Config`).
70
+ * What {@link refreshOraclePrices} actually put on chain.
76
71
  *
77
- * Throws if the ticker ALSO has a `pyth_rule.feeds` entry (a dual-feed transition
78
- * ticker): feeding only the constant leg would leave the still-weighted Pyth rule
79
- * absent from the collector and abort `aggregate` with `EMissingPriceSource`. Such
80
- * tickers must go through {@link aggregateTicker} with a `priceInfoObjectId` (or
81
- * {@link refreshOraclePrices}), which feeds both.
72
+ * `skipped` holds the requested tickers NO listed source can price (and that
73
+ * `constant_rule` does not pin). They got no collector and no aggregate, so
74
+ * their on-chain price is whatever a previous transaction left — which is why
75
+ * every caller whose ACTION depends on a ticker must check this rather than
76
+ * assume the refresh covered its whole request.
82
77
  */
83
- export declare function aggregateTickerWithConstant(tx: Transaction, host: OracleHost, args: {
84
- ticker: string;
85
- }): void;
78
+ export interface OracleRefreshSummary {
79
+ /** Tickers aggregated in this PTB — includes constant-pinned ones. */
80
+ refreshed: string[];
81
+ /** Requested tickers no listed source serves. */
82
+ skipped: string[];
83
+ }
86
84
  /**
87
85
  * Refresh multiple tickers in one PTB. For each ticker {@link aggregateTicker}
88
- * feeds whichever rules it is configured for (Pyth if it has a `pyth_rule.feeds`
89
- * entry, Lazer if the lazer update leg served it — see below — Supra when
90
- * enabled, Constant when it's a constant ticker).
86
+ * feeds whichever rules it is configured for (Lazer if the lazer update leg
87
+ * served it, Waterx if the waterx leg fetched signed data for it — see below —
88
+ * Supra when enabled, Constant when it's a constant ticker).
91
89
  *
92
90
  * Before that, the on-chain price *update* leg is routed by the
93
91
  * `host.oracleSources` fed set (see `rule-registry.ts`): EVERY listed source
@@ -101,11 +99,15 @@ export declare function aggregateTickerWithConstant(tx: Transaction, host: Oracl
101
99
  * source's feeds is caught here.
102
100
  *
103
101
  * Each source's fetch + build runs against its own infra, guaranteeing
104
- * per-rule PTB atomicity. A fee-source pre-check runs first (any listed
105
- * source's `requiresFeeSource`) BEFORE any off-chain fetch or PTB mutation
106
- * so a fee-charging source with no `opts.feeSource` throws
107
- * `OracleFeeSourceUnavailable` with zero wasted network calls and zero stray
108
- * moveCalls. Only once that check passes do the off-chain fetches run — in
102
+ * per-rule PTB atomicity. A credential pre-check runs early: any group that
103
+ * still needs to fetch, whose rule declares a `credential` the host does not
104
+ * carry while `host.pyth.api_key` is unset, throws `LazerApiKeyMissing`
105
+ * BEFORE any ORACLE fetch or PTB mutation — zero wasted oracle calls, zero
106
+ * stray moveCalls. (With an `updateDataProvider` configured, its per-source
107
+ * lookups run first so the check can be scoped to the groups the cache did
108
+ * NOT serve; a consumer-implemented provider may do I/O of its own, so the
109
+ * guarantee is about oracle fetches, not about every possible round trip.)
110
+ * Only once that check passes do the off-chain fetches run — in
109
111
  * parallel across sources — and ALL settle before the first PTB mutation;
110
112
  * on-chain reads inside `buildUpdateCalls` can still fail mid-append for
111
113
  * other reasons — callers discard the tx on any throw.
@@ -114,32 +116,14 @@ export declare function aggregateTickerWithConstant(tx: Transaction, host: Oracl
114
116
  * `buildUpdateCalls` returns the verified `Update` PTB value
115
117
  * ({@link RuleUpdateHandle}), and every ticker in that group is aggregated
116
118
  * with `lazerUpdate` set so {@link aggregateTicker} appends
117
- * `pyth_lazer_rule::feed` against it. A lazer-routed ticker that still has a
118
- * `pyth_rule.feeds` entry ALSO keeps its `pyth_rule::feed` leg required
119
- * on-chain while `pyth_rule` stays in the ticker's weighted set
120
- * (`aggregator::remove_outliers` aborts `EMissingPriceSource` unless every
121
- * weighted rule appears in the collector; an abstention counts as
122
- * appearing), and safe: `pyth_rule::feed` only READS the `PriceInfoObject`
123
- * this PTB never refreshed and abstains when it is stale rather than
124
- * aborting. Conversely, a lazer feed call on an aggregator that does not
125
- * (yet) weight `PythLazerRule` is silently dropped on-chain — so
126
- * lazer-routing a ticker ahead of its on-chain weight migration prices it
127
- * from the remaining weighted rules instead of failing.
119
+ * `pyth_lazer_rule::feed` against it. A waterx-served group's signed data is
120
+ * carried straight from its fetched payload to the per-ticker collect call.
121
+ * A feed call on an aggregator that does not (yet) weight that rule is
122
+ * silently dropped on-chain so routing a ticker ahead of its on-chain
123
+ * weight migration prices it from the remaining weighted rules instead of
124
+ * failing.
128
125
  */
129
126
  export declare function refreshOraclePrices(tx: Transaction, host: OracleHost, tickers: string[], opts?: {
130
- cache?: PythCache;
131
- /**
132
- * The single resolved fee source for the Pyth update fee, forwarded
133
- * verbatim to each group's `PriceUpdateRule.buildUpdateCalls` as
134
- * `BuildUpdateOpts.feeSource`. Already-resolved by the caller (see
135
- * {@link OracleFeeSource}'s own doc for where/how) — this function makes
136
- * no sponsor-vs-gas decision itself, it only checks whether a source was
137
- * resolved at all. Ignored by rules with no update fee (e.g.
138
- * `pyth_lazer_rule`). Building with `feeSource` unset throws
139
- * `OracleFeeSourceUnavailable` (see `oracle/pyth.ts`) instead of
140
- * silently drawing from `tx.gas`.
141
- */
142
- feeSource?: OracleFeeSource;
143
127
  /**
144
128
  * @internal Test-only: layer fake `PriceUpdateRule`s on top of the
145
129
  * production registry (see `rule-registry.ts`'s `resolveOracleRule`).
@@ -156,4 +140,4 @@ export declare function refreshOraclePrices(tx: Transaction, host: OracleHost, t
156
140
  * payload) throws instead, since that is a caller bug, not a cache miss.
157
141
  */
158
142
  updateDataProvider?: UpdateDataProvider;
159
- }): Promise<void>;
143
+ }): Promise<OracleRefreshSummary>;
@@ -3,12 +3,12 @@
3
3
  * Oracle aggregation — the orchestrator that composes rules into the shared
4
4
  * `Oracle`. This is the ONE file that knows about every rule: it builds a
5
5
  * `PriceCollector`, feeds whichever rules a ticker is configured for
6
- * (Pyth / Lazer / Supra / Constant), then `aggregate`s.
6
+ * (Lazer / Waterx / Supra / Constant), then `aggregate`s.
7
7
  *
8
8
  * Per ticker:
9
9
  * collector = oracle::new_collector(ticker)
10
- * [pyth_rule::feed] when the ticker has a pyth_rule.feeds entry
11
10
  * [pyth_lazer_rule::feed] when the update leg produced a verified lazer Update
11
+ * [waterx_rule::collect_*] when the update leg fetched signed waterx data
12
12
  * [supra_rule::feed] when supra is enabled + wired
13
13
  * [constant_rule::feed] when the ticker is a constant ticker
14
14
  * oracle::aggregate(oracle, collector)
@@ -25,18 +25,16 @@
25
25
  */
26
26
  Object.defineProperty(exports, "__esModule", { value: true });
27
27
  exports.aggregateTicker = aggregateTicker;
28
- exports.aggregateTickerWithPyth = aggregateTickerWithPyth;
29
28
  exports.aggregateTickerWithConstant = aggregateTickerWithConstant;
30
29
  exports.refreshOraclePrices = refreshOraclePrices;
31
30
  const oracle_ts_1 = require("../generated/waterx_oracle/oracle.js");
32
- const record_ts_1 = require("../utils/record.js");
33
- const pyth_ts_1 = require("./pyth.js");
31
+ const price_update_rule_ts_1 = require("./price-update-rule.js");
34
32
  const rule_registry_ts_1 = require("./rule-registry.js");
35
33
  const constant_rule_ts_1 = require("./rules/constant-rule.js");
36
34
  const pyth_lazer_rule_ts_1 = require("./rules/pyth-lazer-rule.js");
37
- const pyth_rule_ts_1 = require("./rules/pyth-rule.js");
38
35
  const supra_rule_ts_1 = require("./rules/supra-rule.js");
39
36
  const waterx_rule_ts_1 = require("./rules/waterx-rule.js");
37
+ const validate_ts_1 = require("./validate.js");
40
38
  /**
41
39
  * Resolve one group's off-chain update payload for {@link refreshOraclePrices}:
42
40
  * try `provider.get(source, tickers)` first (when a provider is configured),
@@ -48,60 +46,53 @@ const waterx_rule_ts_1 = require("./rules/waterx-rule.js");
48
46
  * provider commonly caches one whole-universe payload per source — see
49
47
  * {@link UpdateDataProvider}), so it is narrowed to exactly `group.tickers`
50
48
  * via `rule.narrowUpdateData` before use. This is load-bearing, not
51
- * defensive: without it a Pyth Core hit would emit an
52
- * `update_single_price_feed` and charge its fee for every cached feed
53
- * instead of just this group's, and a payload that cannot cover the group
54
- * (`narrowUpdateData` `null`) would never reach the live-fetch fallback.
55
- * Each rule owns its own subsetting (Core subsets per-feed entries; Lazer's
56
- * indivisible payload passes whole iff fully covered), so the orchestrator
57
- * never branches on `kind` here. A hit whose `kind` doesn't match the
58
- * group's rule is a caller bug (the provider handed back a different rule's
59
- * payload), so that throws — via `narrowUpdateData`'s own
49
+ * defensive: a payload that cannot cover the group (`narrowUpdateData`
50
+ * `null`) would never reach the live-fetch fallback without it, and a
51
+ * divisible payload (waterx leaves) is subset instead of fanned out whole.
52
+ * Each rule owns its own subsetting (waterx subsets per-symbol leaves;
53
+ * Lazer's indivisible payload passes whole iff fully covered), so the
54
+ * orchestrator never branches on `kind` here. A hit whose `kind` doesn't
55
+ * match the group's rule is a caller bug (the provider handed back a
56
+ * different rule's payload), so that throws via `narrowUpdateData`'s own
60
57
  * `assertRuleUpdateData` guard — instead of silently falling back.
61
58
  */
62
- async function resolveGroupUpdateData(host, group, provider) {
63
- if (provider) {
64
- let cached = null;
65
- try {
66
- cached = await provider.get(group.source, group.tickers);
67
- }
68
- catch {
69
- // Provider errors must never break the money path — fall through to
70
- // the live fetch below exactly as a cache miss (`null`) would.
71
- }
72
- if (cached !== null) {
73
- // Wrong-kind hit throws inside narrowUpdateData (assertRuleUpdateData);
74
- // a hit that can't cover the group narrows to null → live-fetch below.
75
- const narrowed = group.rule.narrowUpdateData(host, cached, group.tickers);
76
- if (narrowed !== null)
77
- return narrowed;
78
- }
59
+ async function resolveCachedUpdateData(host, group, provider) {
60
+ if (!provider)
61
+ return null;
62
+ let cached;
63
+ try {
64
+ cached = await provider.get(group.source, group.tickers);
65
+ }
66
+ catch {
67
+ // Provider errors must never break the money path report a miss and let
68
+ // the caller live-fetch, exactly as a `null` return would.
69
+ return null;
79
70
  }
80
- return group.rule.fetchUpdateData(host, group.tickers);
71
+ if (cached === null)
72
+ return null;
73
+ // Wrong-kind hit throws inside narrowUpdateData (assertRuleUpdateData); a
74
+ // hit that can't cover the group narrows to null, i.e. a miss.
75
+ return group.rule.narrowUpdateData(host, cached, group.tickers);
81
76
  }
82
77
  /**
83
78
  * Aggregate one ticker's price into the shared `Oracle`: build a collector, feed
84
79
  * every rule the ticker is configured for, then `aggregate`.
85
80
  *
86
- * - **Pyth** — fed when `priceInfoObjectId` is supplied (i.e. the ticker has a
87
- * `pyth_rule.feeds` entry). When this PTB's update leg refreshed the
88
- * `PriceInfoObject` it contributes a fresh price; when it did not (a
89
- * lazer-routed ticker), the on-chain rule only READS the object and abstains
90
- * if it is stale — it never aborts — so the call stays mandatory while
91
- * `pyth_rule` remains in the ticker's on-chain weighted set
92
- * (`EMissingPriceSource` requires every weighted rule to appear).
93
81
  * - **Lazer** — fed when `lazerUpdate` is supplied: the verified update this
94
82
  * PTB's lazer update leg produced with the network's verify entry
95
83
  * (`update_v2::Update` on mainnet, `update::Update` on testnet — see
96
84
  * `PythLazerRule.buildUpdateCalls`). If the ticker's aggregator does
97
85
  * not (yet) weight `PythLazerRule`, the contribution is silently dropped
98
86
  * on-chain — feeding ahead of the weight migration is harmless.
99
- * - **Supra** — fed alongside Pyth/Lazer when supra is enabled + wired
87
+ * - **Waterx** — fed when `waterxLeaf` (default shape) or `waterxEnvelope`
88
+ * (fallback shape) is supplied; verify AND feed are bundled into the one
89
+ * collect call per collector.
90
+ * - **Supra** — fed alongside the sources when supra is enabled + wired
100
91
  * (abstains on-chain for symbols it has no pair for).
101
92
  * - **Constant** — fed when the ticker is a constant ticker
102
93
  * ({@link OracleHost.isConstantTicker}).
103
94
  *
104
- * "Dual-feed" (Pyth + Constant, or Pyth + Lazer) and "constant-only" are not
95
+ * "Dual-feed" (Lazer + Constant, or Lazer + Waterx) and "constant-only" are not
105
96
  * special cases — they fall out of which rules the ticker is in. Throws if no
106
97
  * rule applies.
107
98
  */
@@ -112,10 +103,6 @@ function aggregateTicker(tx, host, args) {
112
103
  arguments: { symbol: args.ticker },
113
104
  })(tx);
114
105
  let fed = false;
115
- if (args.priceInfoObjectId) {
116
- (0, pyth_rule_ts_1.feedPythRule)(tx, host, collector, args.priceInfoObjectId);
117
- fed = true;
118
- }
119
106
  if (args.lazerUpdate !== undefined) {
120
107
  (0, pyth_lazer_rule_ts_1.feedLazerRule)(tx, host, collector, args.lazerUpdate);
121
108
  fed = true;
@@ -151,7 +138,7 @@ function aggregateTicker(tx, host, args) {
151
138
  fed = true;
152
139
  }
153
140
  if (!fed) {
154
- throw new Error(`no oracle rule configured for ticker '${args.ticker}' (no pyth feed, no lazer update, not a constant ticker)`);
141
+ throw new Error(`no oracle rule configured for ticker '${args.ticker}' (no lazer update, no waterx data, not a constant ticker)`);
155
142
  }
156
143
  (0, oracle_ts_1.aggregate)({
157
144
  package: oraclePkg,
@@ -162,33 +149,20 @@ function aggregateTicker(tx, host, args) {
162
149
  })(tx);
163
150
  }
164
151
  /**
165
- * Thin wrapper over {@link aggregateTicker} for a Pyth-fed ticker. Kept for
166
- * back-compat (e.g. WLP mint builds). Caller must run the Pyth update first.
167
- */
168
- function aggregateTickerWithPyth(tx, host, args) {
169
- aggregateTicker(tx, host, args);
170
- }
171
- /**
172
- * {@link aggregateTicker} for a **constant-only** ticker (no Pyth update needed —
173
- * the price comes from the on-chain `constant_rule::Config`).
174
- *
175
- * Throws if the ticker ALSO has a `pyth_rule.feeds` entry (a dual-feed transition
176
- * ticker): feeding only the constant leg would leave the still-weighted Pyth rule
177
- * absent from the collector and abort `aggregate` with `EMissingPriceSource`. Such
178
- * tickers must go through {@link aggregateTicker} with a `priceInfoObjectId` (or
179
- * {@link refreshOraclePrices}), which feeds both.
152
+ * {@link aggregateTicker} for a **constant-only** ticker (no source update
153
+ * needed the price comes from the on-chain `constant_rule::Config`). Kept as
154
+ * a named entry so constant-only call sites (e.g. WLP builders refreshing the
155
+ * USDCUSD pool token) read as what they are; it adds nothing over
156
+ * `aggregateTicker(tx, host, { ticker })`.
180
157
  */
181
158
  function aggregateTickerWithConstant(tx, host, args) {
182
- if ((0, record_ts_1.ownEntry)(host.config.packages.pyth_rule?.feeds, args.ticker) !== undefined) {
183
- throw new Error(`'${args.ticker}' is in pyth_rule.feeds (dual-feed) — feed both via aggregateTicker({ priceInfoObjectId }) / refreshOraclePrices, not aggregateTickerWithConstant`);
184
- }
185
159
  aggregateTicker(tx, host, { ticker: args.ticker });
186
160
  }
187
161
  /**
188
162
  * Refresh multiple tickers in one PTB. For each ticker {@link aggregateTicker}
189
- * feeds whichever rules it is configured for (Pyth if it has a `pyth_rule.feeds`
190
- * entry, Lazer if the lazer update leg served it — see below — Supra when
191
- * enabled, Constant when it's a constant ticker).
163
+ * feeds whichever rules it is configured for (Lazer if the lazer update leg
164
+ * served it, Waterx if the waterx leg fetched signed data for it — see below —
165
+ * Supra when enabled, Constant when it's a constant ticker).
192
166
  *
193
167
  * Before that, the on-chain price *update* leg is routed by the
194
168
  * `host.oracleSources` fed set (see `rule-registry.ts`): EVERY listed source
@@ -202,11 +176,15 @@ function aggregateTickerWithConstant(tx, host, args) {
202
176
  * source's feeds is caught here.
203
177
  *
204
178
  * Each source's fetch + build runs against its own infra, guaranteeing
205
- * per-rule PTB atomicity. A fee-source pre-check runs first (any listed
206
- * source's `requiresFeeSource`) BEFORE any off-chain fetch or PTB mutation
207
- * so a fee-charging source with no `opts.feeSource` throws
208
- * `OracleFeeSourceUnavailable` with zero wasted network calls and zero stray
209
- * moveCalls. Only once that check passes do the off-chain fetches run — in
179
+ * per-rule PTB atomicity. A credential pre-check runs early: any group that
180
+ * still needs to fetch, whose rule declares a `credential` the host does not
181
+ * carry while `host.pyth.api_key` is unset, throws `LazerApiKeyMissing`
182
+ * BEFORE any ORACLE fetch or PTB mutation — zero wasted oracle calls, zero
183
+ * stray moveCalls. (With an `updateDataProvider` configured, its per-source
184
+ * lookups run first so the check can be scoped to the groups the cache did
185
+ * NOT serve; a consumer-implemented provider may do I/O of its own, so the
186
+ * guarantee is about oracle fetches, not about every possible round trip.)
187
+ * Only once that check passes do the off-chain fetches run — in
210
188
  * parallel across sources — and ALL settle before the first PTB mutation;
211
189
  * on-chain reads inside `buildUpdateCalls` can still fail mid-append for
212
190
  * other reasons — callers discard the tx on any throw.
@@ -215,21 +193,16 @@ function aggregateTickerWithConstant(tx, host, args) {
215
193
  * `buildUpdateCalls` returns the verified `Update` PTB value
216
194
  * ({@link RuleUpdateHandle}), and every ticker in that group is aggregated
217
195
  * with `lazerUpdate` set so {@link aggregateTicker} appends
218
- * `pyth_lazer_rule::feed` against it. A lazer-routed ticker that still has a
219
- * `pyth_rule.feeds` entry ALSO keeps its `pyth_rule::feed` leg required
220
- * on-chain while `pyth_rule` stays in the ticker's weighted set
221
- * (`aggregator::remove_outliers` aborts `EMissingPriceSource` unless every
222
- * weighted rule appears in the collector; an abstention counts as
223
- * appearing), and safe: `pyth_rule::feed` only READS the `PriceInfoObject`
224
- * this PTB never refreshed and abstains when it is stale rather than
225
- * aborting. Conversely, a lazer feed call on an aggregator that does not
226
- * (yet) weight `PythLazerRule` is silently dropped on-chain — so
227
- * lazer-routing a ticker ahead of its on-chain weight migration prices it
228
- * from the remaining weighted rules instead of failing.
196
+ * `pyth_lazer_rule::feed` against it. A waterx-served group's signed data is
197
+ * carried straight from its fetched payload to the per-ticker collect call.
198
+ * A feed call on an aggregator that does not (yet) weight that rule is
199
+ * silently dropped on-chain so routing a ticker ahead of its on-chain
200
+ * weight migration prices it from the remaining weighted rules instead of
201
+ * failing.
229
202
  */
230
203
  async function refreshOraclePrices(tx, host, tickers, opts = {}) {
231
204
  if (tickers.length === 0)
232
- return;
205
+ return { refreshed: [], skipped: [] };
233
206
  // Dedupe the caller's list (order-preserving): a repeated ticker would
234
207
  // otherwise aggregate TWICE in this one PTB — wasted gas for every rule, and
235
208
  // under waterx the second collect would be dead weight on top of that: the
@@ -237,13 +210,7 @@ async function refreshOraclePrices(tx, host, tickers, opts = {}) {
237
210
  // high-water mark from earlier in this same transaction and abstains, so the
238
211
  // repeat pays full verification cost to contribute nothing.
239
212
  tickers = [...new Set(tickers)];
240
- // price_info_object lookup for every ticker with a pyth_rule.feeds entry
241
- // needed by aggregateTicker's (unchanged) Pyth feed step below regardless of
242
- // which rule performed the on-chain update for that ticker.
243
- const pythTickers = tickers.filter((t) => (0, record_ts_1.ownEntry)(host.config.packages.pyth_rule?.feeds, t) !== undefined);
244
- const priceInfoByTicker = new Map();
245
- pythTickers.forEach((t) => priceInfoByTicker.set(t, host.getPythFeed(t).price_info_object));
246
- // The fed set is a LIST (`host.oracleSources`, normalized + deduped at
213
+ // The fed set is a LIST (`host.oracleSources`, derived from the config at
247
214
  // client creation): ONE build carries every listed source's data, and the
248
215
  // chain's per-ticker weight tables decide which contributions count —
249
216
  // feeding an unweighted rule's PRICE is dropped on-chain, while starving a
@@ -253,8 +220,8 @@ async function refreshOraclePrices(tx, host, tickers, opts = {}) {
253
220
  // burns a per-symbol signed-timestamp high-water mark regardless of
254
221
  // weights — see aggregateTicker's waterx branch.) Still NO fallback
255
222
  // BETWEEN sources: each group serves only the tickers its own feeds list.
256
- // Zero-ticker groups are dropped here so everything downstream (fee check,
257
- // fetch fan-out, update-leg build) can assume every group has work.
223
+ // Zero-ticker groups are dropped here so everything downstream (credential
224
+ // check, fetch fan-out, update-leg build) can assume every group has work.
258
225
  const groups = host.oracleSources
259
226
  .map((source) => {
260
227
  const rule = (0, rule_registry_ts_1.resolveOracleRule)(source, opts.ruleOverrides);
@@ -262,44 +229,82 @@ async function refreshOraclePrices(tx, host, tickers, opts = {}) {
262
229
  return { source, rule, tickers: tickers.filter((t) => supported.has(t)) };
263
230
  })
264
231
  .filter((group) => group.tickers.length > 0);
265
- // Fail the tx-build (NOT client init, NOT a silent reroute) when NO listed
266
- // source has a feed for a requested ticker that actually needs a price
267
- // update. Only a CONSTANT-ONLY ticker is exempt — priced entirely by
268
- // `constant_rule`, it needs no update leg from any source. A DUAL-FEED ticker
269
- // (constant AND pyth) still needs its Pyth leg refreshed, so `isConstantTicker`
270
- // alone must NOT exempt it: with no source able to serve it, feeding an
271
- // unrefreshed Pyth leg would price it stale (or abort on a missing weighted
272
- // source). `priceInfoByTicker.has(t)` the ticker has a `pyth_rule.feeds`
273
- // entry, so `constant && !hasPyth` is exactly constant-only. This catches a
274
- // MISSING feed; a present-but-WRONG feed id is deliberately not validated
275
- // here (it aborts on-chain at dry-run).
232
+ // A ticker no listed source can price is SKIPPED, not thrown on, and named
233
+ // in the returned summary.
234
+ //
235
+ // This is a broad primitive: callers sweep whole market lists through it, and
236
+ // losing 29 tickers because the 30th is unconfigured is the wrong trade — the
237
+ // 29 still need their prices on chain. Safety lives one level up, where the
238
+ // ACTION is known: the `build*Tx` composers fail closed on the tickers their
239
+ // specific call actually depends on (`assertTickersRefreshed` /
240
+ // `assertWlpPoolRefreshed` in `perp/tx-builders/common.ts`). A bare
241
+ // `refreshOraclePrices` caller composing its own PTB reads `skipped` and
242
+ // decides for itself.
243
+ //
244
+ // Exemption: a CONSTANT-ONLY ticker needs no update leg from any source, so
245
+ // it counts as refreshed with just its constant feed.
246
+ //
247
+ // "Constant-only" is deliberately stricter than "constant-pinned". A ticker
248
+ // that constant_rule pins AND some other rule also has a feed for is NOT
249
+ // exempt, even when that other rule is outside the fed set: the on-chain
250
+ // aggregator very likely weights the rule whose feed the config carries, and
251
+ // aggregating a constant-only collector for it aborts `EMissingPriceSource`.
252
+ // Exempting on `isConstantTicker` alone would be correct only while the fed
253
+ // set covers every ticker's weighted set — an operator-maintained property
254
+ // this function cannot verify without reading chain state. So the strict
255
+ // reading fails SAFE: the ticker is skipped, named, and the composers turn
256
+ // that into a build error instead of an opaque on-chain abort.
257
+ //
258
+ // Catches a MISSING feed only; a present-but-WRONG feed id is deliberately
259
+ // left to abort on-chain at dry-run.
276
260
  const covered = new Set(groups.flatMap((group) => group.tickers));
277
- const isConstantOnly = (t) => host.isConstantTicker(t) && !priceInfoByTicker.has(t);
278
- const unservable = tickers.filter((t) => !covered.has(t) && !isConstantOnly(t));
279
- if (unservable.length > 0) {
280
- const sources = host.oracleSources.join(", ");
281
- throw new Error(`oracleSource [${sources}] has no feed configured for ticker(s): ` +
282
- `${unservable.join(", ")}. Sources are self-contained with no fallback add ` +
283
- `feeds for them under a listed source, or list a source that serves them.`);
284
- }
285
- // Fee-source pre-check, hoisted ABOVE the off-chain fetches and PTB build
286
- // below. It consults only `rule.requiresFeeSource` known before any fetch
287
- // or PTB mutation — so a fee-charging source (Pyth Core) in the fed set with
288
- // no `feeSource` throws with ZERO wasted network calls and zero PTB
289
- // commands, rather than waiting for `buildPythPriceUpdateCalls`'s own
290
- // per-call guard to fire after the off-chain fetches already ran.
291
- if (!opts.feeSource && groups.some((group) => group.rule.requiresFeeSource)) {
292
- throw new pyth_ts_1.OracleFeeSourceUnavailableError();
261
+ const { servable: refreshed, unservable: skipped } = (0, validate_ts_1.partitionServableTickers)(host, tickers, covered);
262
+ // Credential pre-check, hoisted ABOVE the oracle fetches and PTB build below
263
+ // (the position the retired fee-source pre-check held). It consults only
264
+ // `rule.credential` known without fetching anything — so a keyless build
265
+ // against an auth-first source (Lazer) in the fed set throws with ZERO
266
+ // wasted ORACLE calls and zero PTB commands, rather than waiting for that
267
+ // group's own fetch guard to fire after sibling groups' fetches already ran.
268
+ // Fully generic: the kind→value mapping is the port's
269
+ // (`oracleCredentialsFromHost`) and the ERROR is the rule's own, so this
270
+ // loop names neither a credential kind nor a rule.
271
+ //
272
+ // It does NOT run before absolutely everything, and cannot: scoping it to
273
+ // the groups that still need to fetch means knowing which ones the cache
274
+ // served, and `UpdateDataProvider` is consumer-implemented, so its lookup
275
+ // may do I/O. That is the trade — a provider round trip may precede the
276
+ // throw, an oracle fetch never does — and it buys the scoping below.
277
+ //
278
+ // The check is scoped to the groups that will actually FETCH. An
279
+ // `updateDataProvider` is a per-SOURCE cache (`get(source, tickers)`), so a
280
+ // consumer holding the credential out-of-band for one source must not
281
+ // exempt the whole fed set — with a waterx-only cache and a keyless Lazer
282
+ // group, a blanket skip would let the quote-center GET fire before the
283
+ // Lazer group failed. So cache lookups (no network) run first, and only the
284
+ // groups that missed are credential-checked.
285
+ //
286
+ // Phase 0 — resolve cache hits. Cheap and network-free by contract.
287
+ const cachedByGroup = await Promise.all(groups.map((group) => resolveCachedUpdateData(host, group, opts.updateDataProvider)));
288
+ const needsFetch = groups.filter((_, i) => cachedByGroup[i] === null);
289
+ const credentials = (0, price_update_rule_ts_1.oracleCredentialsFromHost)(host);
290
+ for (const { rule } of needsFetch) {
291
+ if (rule.credential && !credentials[rule.credential.kind]) {
292
+ throw rule.credential.missing();
293
+ }
293
294
  }
294
- // Phase 1 — resolve every group's update data IN PARALLEL: the per-source
295
- // fetches (Hermes VAA / Lazer POST / quote-center GET) are independent
296
- // network calls on the tx-build money path, so a multi-source fed set must
297
- // not pay one RTT per source sequentially. ALL fetches settle before the
298
- // first PTB mutation below, so a fetch failure never strands moveCalls in a
295
+ // Phase 1 — live-fetch whatever the cache did not serve, IN PARALLEL: the
296
+ // per-source fetches (Lazer POST / quote-center GET) are independent network
297
+ // calls on the tx-build money path, so a multi-source fed set must not pay
298
+ // one RTT per source sequentially. ALL of them settle before the first PTB
299
+ // mutation below, so a fetch failure never strands moveCalls in a
299
300
  // caller-owned tx — and a failure in ANY group fails the whole build (a
300
301
  // listed source is load-bearing; silently building without it would starve
301
302
  // its weighted tickers on-chain).
302
- const dataByGroup = await Promise.all(groups.map((group) => resolveGroupUpdateData(host, group, opts.updateDataProvider)));
303
+ const fetched = new Map();
304
+ await Promise.all(needsFetch.map(async (group) => {
305
+ fetched.set(group.source, await group.rule.fetchUpdateData(host, group.tickers));
306
+ }));
307
+ const dataByGroup = groups.map((group, i) => cachedByGroup[i] ?? fetched.get(group.source) ?? null);
303
308
  // Phase 2 — build each group's update leg sequentially, in list order, so
304
309
  // PTB command order stays deterministic. The carry step below is an
305
310
  // exhaustive switch over the group's rule kind: a future source whose feed
@@ -315,15 +320,8 @@ async function refreshOraclePrices(tx, host, tickers, opts = {}) {
315
320
  const waterxEnvelopeByTicker = new Map();
316
321
  for (const [i, group] of groups.entries()) {
317
322
  const data = dataByGroup[i] ?? null;
318
- const handle = (await group.rule.buildUpdateCalls(tx, host, data, {
319
- cache: opts.cache,
320
- feeSource: opts.feeSource,
321
- })) ?? undefined;
323
+ const handle = (await group.rule.buildUpdateCalls(tx, host, data)) ?? undefined;
322
324
  switch (group.rule.kind) {
323
- case "pyth_rule":
324
- // Core's update leg wrote the PriceInfoObjects in place — the feed
325
- // leg reads them by id (`priceInfoByTicker`), nothing to carry.
326
- break;
327
325
  case "pyth_lazer_rule":
328
326
  // Route by the handle's kind discriminant — the tag exists so a
329
327
  // non-lazer handle can never be silently fed into
@@ -383,14 +381,16 @@ async function refreshOraclePrices(tx, host, tickers, opts = {}) {
383
381
  }
384
382
  }
385
383
  }
386
- // Aggregate each ticker, feeding whichever rules it is configured for.
387
- for (const ticker of tickers) {
384
+ // Aggregate each REFRESHED ticker, feeding whichever rules it is configured
385
+ // for. A skipped ticker gets no collector at all — aggregating one with no
386
+ // feed leg would write an empty collector and abort `EMissingPriceSource`.
387
+ for (const ticker of refreshed) {
388
388
  aggregateTicker(tx, host, {
389
389
  ticker,
390
- priceInfoObjectId: priceInfoByTicker.get(ticker),
391
390
  lazerUpdate: lazerUpdateByTicker.get(ticker),
392
391
  waterxLeaf: waterxLeafByTicker.get(ticker),
393
392
  waterxEnvelope: waterxEnvelopeByTicker.get(ticker),
394
393
  });
395
394
  }
395
+ return { refreshed, skipped };
396
396
  }