@waterx/sdk 3.1.1 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +54 -18
  2. package/dist/cjs/src/account/config.d.ts +0 -16
  3. package/dist/cjs/src/oracle/aggregate.d.ts +96 -17
  4. package/dist/cjs/src/oracle/aggregate.js +179 -21
  5. package/dist/cjs/src/oracle/config.d.ts +85 -2
  6. package/dist/cjs/src/oracle/config.js +30 -1
  7. package/dist/cjs/src/oracle/host.d.ts +14 -1
  8. package/dist/cjs/src/oracle/index.d.ts +19 -5
  9. package/dist/cjs/src/oracle/index.js +54 -6
  10. package/dist/cjs/src/oracle/price-update-rule.d.ts +180 -0
  11. package/dist/cjs/src/oracle/price-update-rule.js +56 -0
  12. package/dist/cjs/src/oracle/pyth.d.ts +142 -11
  13. package/dist/cjs/src/oracle/pyth.js +408 -25
  14. package/dist/cjs/src/oracle/rule-registry.d.ts +41 -0
  15. package/dist/cjs/src/oracle/rule-registry.js +65 -0
  16. package/dist/cjs/src/oracle/rules/pyth-core-rule.d.ts +15 -0
  17. package/dist/cjs/src/oracle/rules/pyth-core-rule.js +99 -0
  18. package/dist/cjs/src/oracle/rules/pyth-lazer-rule.d.ts +41 -0
  19. package/dist/cjs/src/oracle/rules/pyth-lazer-rule.js +197 -0
  20. package/dist/cjs/src/oracle/rules/pyth-rule.js +5 -0
  21. package/dist/cjs/src/oracle/rules/sponsor.d.ts +11 -7
  22. package/dist/cjs/src/oracle/rules/sponsor.js +11 -7
  23. package/dist/cjs/src/oracle/update-fetch.d.ts +115 -0
  24. package/dist/cjs/src/oracle/update-fetch.js +285 -0
  25. package/dist/cjs/src/perp/client.d.ts +40 -5
  26. package/dist/cjs/src/perp/client.js +18 -3
  27. package/dist/cjs/src/perp/config.d.ts +10 -7
  28. package/dist/cjs/src/perp/config.js +77 -17
  29. package/dist/cjs/src/perp/index.d.ts +3 -2
  30. package/dist/cjs/src/perp/index.js +6 -2
  31. package/dist/cjs/src/perp/tx-builders/common.d.ts +52 -15
  32. package/dist/cjs/src/perp/tx-builders/common.js +39 -6
  33. package/dist/cjs/src/perp/tx-builders/wlp.d.ts +11 -3
  34. package/dist/cjs/src/perp/tx-builders/wlp.js +29 -3
  35. package/dist/cjs/src/perp/tx-builders.d.ts +3 -3
  36. package/dist/cjs/src/perp/tx-builders.js +3 -3
  37. package/dist/cjs/src/unified-client.d.ts +25 -0
  38. package/dist/cjs/src/unified-client.js +3 -0
  39. package/dist/src/account/config.d.ts +0 -16
  40. package/dist/src/oracle/aggregate.d.ts +96 -17
  41. package/dist/src/oracle/aggregate.js +180 -22
  42. package/dist/src/oracle/config.d.ts +85 -2
  43. package/dist/src/oracle/config.js +29 -0
  44. package/dist/src/oracle/host.d.ts +14 -1
  45. package/dist/src/oracle/index.d.ts +19 -5
  46. package/dist/src/oracle/index.js +42 -6
  47. package/dist/src/oracle/price-update-rule.d.ts +180 -0
  48. package/dist/src/oracle/price-update-rule.js +53 -0
  49. package/dist/src/oracle/pyth.d.ts +142 -11
  50. package/dist/src/oracle/pyth.js +402 -24
  51. package/dist/src/oracle/rule-registry.d.ts +41 -0
  52. package/dist/src/oracle/rule-registry.js +60 -0
  53. package/dist/src/oracle/rules/pyth-core-rule.d.ts +15 -0
  54. package/dist/src/oracle/rules/pyth-core-rule.js +96 -0
  55. package/dist/src/oracle/rules/pyth-lazer-rule.d.ts +41 -0
  56. package/dist/src/oracle/rules/pyth-lazer-rule.js +192 -0
  57. package/dist/src/oracle/rules/pyth-rule.js +5 -0
  58. package/dist/src/oracle/rules/sponsor.d.ts +11 -7
  59. package/dist/src/oracle/rules/sponsor.js +11 -7
  60. package/dist/src/oracle/update-fetch.d.ts +115 -0
  61. package/dist/src/oracle/update-fetch.js +277 -0
  62. package/dist/src/perp/client.d.ts +40 -5
  63. package/dist/src/perp/client.js +18 -3
  64. package/dist/src/perp/config.d.ts +10 -7
  65. package/dist/src/perp/config.js +77 -17
  66. package/dist/src/perp/index.d.ts +3 -2
  67. package/dist/src/perp/index.js +1 -1
  68. package/dist/src/perp/tx-builders/common.d.ts +52 -15
  69. package/dist/src/perp/tx-builders/common.js +39 -6
  70. package/dist/src/perp/tx-builders/wlp.d.ts +11 -3
  71. package/dist/src/perp/tx-builders/wlp.js +29 -3
  72. package/dist/src/perp/tx-builders.d.ts +3 -3
  73. package/dist/src/perp/tx-builders.js +3 -3
  74. package/dist/src/unified-client.d.ts +25 -0
  75. package/dist/src/unified-client.js +3 -0
  76. package/package.json +1 -1
@@ -0,0 +1,180 @@
1
+ /**
2
+ * `PriceUpdateRule` — the strategy port for one oracle rule generation: fetch
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`.
12
+ *
13
+ * This file defines the port only — routing IS wired: `aggregate.ts`'s
14
+ * `refreshOraclePrices` selects the concrete rule per `host.oracleSource` via
15
+ * `rule-registry.ts`, then drives fetch + `buildUpdateCalls` through this
16
+ * port; `aggregate.ts` stays the sole orchestrator.
17
+ */
18
+ import type { Transaction, TransactionArgument } from "@mysten/sui/transactions";
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";
22
+ /**
23
+ * The subset of `PriceUpdateRuleKind`s selectable via a client's `oracleSource`
24
+ * create option (see `OracleHost.oracleSource`) — i.e. rules that can serve as
25
+ * the on-chain price *update* leg `refreshOraclePrices` runs before aggregating.
26
+ * `supra_rule` and `constant_rule` are auxiliary rules fed alongside whichever
27
+ * source is selected (see `aggregateTicker`), not sources themselves;
28
+ * `waterx_rule` has no `PriceUpdateRule` implementation yet. The SDK never
29
+ * reads `process.env` — consumers resolve their own env var to this type.
30
+ */
31
+ export type OracleSource = "pyth_rule" | "pyth_lazer_rule";
32
+ /**
33
+ * Off-chain payload fetched by a rule, tagged by `kind` so a caller holding
34
+ * several rules' results can tell them apart. `payload` is `unknown` here —
35
+ * each rule implementation narrows it to its own shape (e.g. `PythCoreRule`'s
36
+ * `{ updates: Uint8Array[]; feedIds: string[] }`). `null` for rules with no
37
+ * off-chain fetch (e.g. `ConstantRule`) or when there is nothing to fetch.
38
+ */
39
+ export type RuleUpdateData = {
40
+ kind: PriceUpdateRuleKind;
41
+ payload: unknown;
42
+ } | null;
43
+ /**
44
+ * Shared null → kind → shape guard ladder for a `PriceUpdateRule.buildUpdateCalls`
45
+ * payload — every rule's `buildUpdateCalls` needs the exact same three checks,
46
+ * in the exact same order, before it can trust `data.payload`:
47
+ *
48
+ * 1. `data === null` passes straight through as `null` — the no-op case (an
49
+ * empty ticker list upstream produced nothing to build).
50
+ * 2. `data.kind !== kind` throws BEFORE the shape check runs. This order is
51
+ * load-bearing, not stylistic: two rules' payloads can share an identical
52
+ * shape (e.g. Pyth Core's `{ updates, feedIds }` also satisfies a
53
+ * hypothetical same-shaped rule), so checking shape first would let a
54
+ * wrong-kind payload silently pass as this rule's own.
55
+ * 3. `!isShape(data.payload)` throws for a same-`kind` payload whose shape
56
+ * doesn't match this rule's own (e.g. a hand-built test double).
57
+ *
58
+ * Returns `data.payload` narrowed to `T` once both checks pass.
59
+ *
60
+ * @param data - The `RuleUpdateData` handed to `buildUpdateCalls`.
61
+ * @param kind - This rule's own {@link PriceUpdateRuleKind} — the only `kind`
62
+ * `data` may carry past step 2.
63
+ * @param isShape - Type predicate narrowing `data.payload` to `T`.
64
+ * @param shapeDescription - Human-readable shape, quoted verbatim into the
65
+ * shape-mismatch error (e.g. `"{ updates: Uint8Array[]; feedIds: string[] }"`).
66
+ */
67
+ export declare function assertRuleUpdateData<T>(data: RuleUpdateData, kind: PriceUpdateRuleKind, isShape: (payload: unknown) => payload is T, shapeDescription: string): T | null;
68
+ /**
69
+ * PTB value handle a rule's {@link PriceUpdateRule.buildUpdateCalls} may
70
+ * return when its collector-feed leg needs a value produced by the update leg
71
+ * *within the same PTB*. Pyth Core needs none (its feed leg reads the shared
72
+ * `PriceInfoObject` the update leg refreshed), so it returns `void`. The Lazer
73
+ * rule returns the verified `pyth_lazer::update::Update` result — one
74
+ * signature verification covers every feed in the payload, and
75
+ * `pyth_lazer_rule::feed` takes it by reference per ticker (see
76
+ * `aggregateTicker`'s `lazerUpdate` arg).
77
+ */
78
+ export type RuleUpdateHandle = {
79
+ readonly kind: "pyth_lazer_rule";
80
+ /** Result of `pyth_lazer::parse_and_verify_le_ecdsa_update` in this PTB. */
81
+ readonly update: TransactionArgument;
82
+ };
83
+ /**
84
+ * Options for {@link PriceUpdateRule.buildUpdateCalls}. Mirrors
85
+ * `buildPythPriceUpdateCalls` / `updatePythPrices`'s own opts shape in
86
+ * `./pyth.ts` — `cache` shares on-chain Pyth state reads across builders;
87
+ * `feeSource` is the single {@link OracleFeeSource} already resolved by the
88
+ * caller (see its own doc for where/how). Both fields are Pyth-Core-specific
89
+ * mechanics; `refreshOraclePrices` passes the same `BuildUpdateOpts` to every
90
+ * rule uniformly, so a non-Pyth-Core rule (e.g. `PythLazerRule`, which
91
+ * charges no update fee) simply ignores whichever fields it has no use for.
92
+ */
93
+ export interface BuildUpdateOpts {
94
+ readonly cache?: PythCache;
95
+ readonly feeSource?: OracleFeeSource;
96
+ }
97
+ /**
98
+ * Injectable update-data cache seam for `refreshOraclePrices` (`aggregate.ts`).
99
+ * A BE consumer (e.g. a prefetch cache that polls Hermes/Lazer out-of-band and
100
+ * keeps a hot in-memory/Redis entry per source) implements this and passes it
101
+ * as `refreshOraclePrices`'s `updateDataProvider` opt; the SDK itself never
102
+ * implements one. `get` is checked before the rule's own live
103
+ * `fetchUpdateData` for that group of tickers — a `null` return means "no
104
+ * cached data, fetch live" (mirrors {@link RuleUpdateData}'s own `null`
105
+ * variant: there is no separate signal for "the cache legitimately has
106
+ * nothing" vs "go fetch live", they're the same instruction to the caller).
107
+ *
108
+ * A non-null hit MAY be a payload for a WIDER ticker set than `tickers` — a
109
+ * provider is free to cache one whole-universe payload per source and return
110
+ * it verbatim; `refreshOraclePrices` narrows it down to exactly the requested
111
+ * tickers via the rule's {@link PriceUpdateRule.narrowUpdateData} before use,
112
+ * so an implementer need not (and should not) subset it by hand. The only
113
+ * hard requirement on a hit is that its `kind` matches the requested
114
+ * `source`'s rule — a mismatch is a routing bug and throws.
115
+ */
116
+ export interface UpdateDataProvider {
117
+ get(source: OracleSource, tickers: string[]): Promise<RuleUpdateData | null>;
118
+ }
119
+ export interface PriceUpdateRule {
120
+ readonly kind: PriceUpdateRuleKind;
121
+ /**
122
+ * `true` when this rule's on-chain update leg charges a per-update fee
123
+ * that must be paid from either a sponsor fund or `tx.gas` (Pyth Core:
124
+ * `true`, via `pyth::update_single_price_feed`'s `base_update_fee`).
125
+ * `false` for a fee-free update leg (Lazer: signature verification only,
126
+ * no `Coin` argument). `refreshOraclePrices` (`aggregate.ts`) reads this
127
+ * BEFORE fetching any group's off-chain payload — for every group whose
128
+ * rule sets it `true`, `opts.feeSource` must already be resolved, or the
129
+ * whole call throws `OracleFeeSourceUnavailable` before
130
+ * any group builds (mixed-shape atomicity: a fee-free group ordered
131
+ * ahead of a fee-charging one in the same PTB must never get to mutate
132
+ * `tx` while the fee-charging group is left unpayable). A referential
133
+ * check against a specific rule instance (e.g. `=== PythCoreRule`) would
134
+ * silently stop protecting a future fee-charging rule, or a test double
135
+ * standing in for one — this field is the honest, extensible signal.
136
+ */
137
+ readonly requiresFeeSource: boolean;
138
+ /** Tickers this rule can serve in this environment (from config feeds + enabled). */
139
+ supportedTickers(host: OracleHost): string[];
140
+ /**
141
+ * Fetch the off-chain payload for these tickers (no-op rules return null).
142
+ * `tickers` must already be a subset of {@link supportedTickers} — an
143
+ * unsupported ticker's feed lookup throws and that throw propagates
144
+ * uncaught; callers pre-filter via `supportedTickers`, this method does not
145
+ * re-validate.
146
+ */
147
+ fetchUpdateData(host: OracleHost, tickers: string[]): Promise<RuleUpdateData>;
148
+ /**
149
+ * Narrow a payload previously produced by {@link fetchUpdateData} — typically
150
+ * for a superset of tickers (e.g. a consumer's whole-universe prefetch cache)
151
+ * — down to exactly `tickers`, without any re-fetch. Each rule owns its
152
+ * payload's divisibility semantics, so consumers must never subset a payload
153
+ * themselves (that knowledge branching on `kind` in a consumer is exactly the
154
+ * altitude violation this method removes):
155
+ *
156
+ * - A non-null result MUST be valid {@link buildUpdateCalls} input covering
157
+ * exactly `tickers` — a divisible payload (Pyth Core's per-feed entries)
158
+ * returns a subset; an indivisible payload (Lazer's single signed message)
159
+ * returns the whole payload iff every requested ticker is covered.
160
+ * - A ticker this payload cannot serve → `null` (miss), NEVER a silent
161
+ * partial. `null` mirrors {@link UpdateDataProvider.get}'s convention: the
162
+ * caller falls back to a live {@link fetchUpdateData} for those tickers.
163
+ * - An empty `tickers` list → `null`, mirroring {@link fetchUpdateData}'s own
164
+ * empty-list convention (nothing to build); `data === null` → `null`.
165
+ * - `data` must be this rule's own payload: kind/shape are enforced via
166
+ * {@link assertRuleUpdateData}, so a wrong-`kind` payload throws (a routing
167
+ * bug), it does not miss.
168
+ */
169
+ narrowUpdateData(host: OracleHost, data: RuleUpdateData, tickers: string[]): RuleUpdateData;
170
+ /**
171
+ * Emit verify/update moveCalls + any per-rule setup into the PTB. Returns a
172
+ * {@link RuleUpdateHandle} when the rule's collector-feed leg needs a PTB
173
+ * value from this step (Lazer's verified `Update`); rules whose feed leg
174
+ * reads shared on-chain objects return `void`. Takes no `tickers` param —
175
+ * every implementation derives everything it needs from `data.payload`
176
+ * (the tickers a group covers were already fixed when `fetchUpdateData`
177
+ * built that payload).
178
+ */
179
+ buildUpdateCalls(tx: Transaction, host: OracleHost, data: RuleUpdateData, opts?: BuildUpdateOpts): Promise<RuleUpdateHandle | void> | RuleUpdateHandle | void;
180
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `PriceUpdateRule` — the strategy port for one oracle rule generation: fetch
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`.
12
+ *
13
+ * This file defines the port only — routing IS wired: `aggregate.ts`'s
14
+ * `refreshOraclePrices` selects the concrete rule per `host.oracleSource` via
15
+ * `rule-registry.ts`, then drives fetch + `buildUpdateCalls` through this
16
+ * port; `aggregate.ts` stays the sole orchestrator.
17
+ */
18
+ /**
19
+ * Shared null → kind → shape guard ladder for a `PriceUpdateRule.buildUpdateCalls`
20
+ * payload — every rule's `buildUpdateCalls` needs the exact same three checks,
21
+ * in the exact same order, before it can trust `data.payload`:
22
+ *
23
+ * 1. `data === null` passes straight through as `null` — the no-op case (an
24
+ * empty ticker list upstream produced nothing to build).
25
+ * 2. `data.kind !== kind` throws BEFORE the shape check runs. This order is
26
+ * load-bearing, not stylistic: two rules' payloads can share an identical
27
+ * shape (e.g. Pyth Core's `{ updates, feedIds }` also satisfies a
28
+ * hypothetical same-shaped rule), so checking shape first would let a
29
+ * wrong-kind payload silently pass as this rule's own.
30
+ * 3. `!isShape(data.payload)` throws for a same-`kind` payload whose shape
31
+ * doesn't match this rule's own (e.g. a hand-built test double).
32
+ *
33
+ * Returns `data.payload` narrowed to `T` once both checks pass.
34
+ *
35
+ * @param data - The `RuleUpdateData` handed to `buildUpdateCalls`.
36
+ * @param kind - This rule's own {@link PriceUpdateRuleKind} — the only `kind`
37
+ * `data` may carry past step 2.
38
+ * @param isShape - Type predicate narrowing `data.payload` to `T`.
39
+ * @param shapeDescription - Human-readable shape, quoted verbatim into the
40
+ * shape-mismatch error (e.g. `"{ updates: Uint8Array[]; feedIds: string[] }"`).
41
+ */
42
+ export function assertRuleUpdateData(data, kind, isShape, shapeDescription) {
43
+ if (!data)
44
+ return null;
45
+ if (data.kind !== kind) {
46
+ throw new Error(`assertRuleUpdateData: received a payload of kind '${data.kind}', expected '${kind}'`);
47
+ }
48
+ if (!isShape(data.payload)) {
49
+ throw new Error(`assertRuleUpdateData: received a '${kind}' payload with an unexpected shape ` +
50
+ `(expected ${shapeDescription})`);
51
+ }
52
+ return data.payload;
53
+ }
@@ -14,6 +14,7 @@
14
14
  * 4. hot_potato_vector::destroy
15
15
  */
16
16
  import type { Transaction, TransactionArgument } from "@mysten/sui/transactions";
17
+ import type { PythFetchPolicy } from "./config.ts";
17
18
  import type { OracleHost } from "./host.ts";
18
19
  type PriceTableInfo = {
19
20
  id: string;
@@ -29,24 +30,154 @@ export declare class PythCache {
29
30
  priceTableInfo?: PriceTableInfo;
30
31
  priceFeedObjectIdCache: Map<string, string | undefined>;
31
32
  }
32
- export declare function fetchPriceFeedsUpdateData(endpoint: string, priceIds: string[]): Promise<Uint8Array[]>;
33
+ type FetchOpts = {
34
+ apiKey?: string;
35
+ fetch?: PythFetchPolicy;
36
+ };
37
+ /**
38
+ * How long a "this endpoint lacks feed X" verdict stays memoized. The verdict
39
+ * is a claim about *someone else's* deployment — a feed can be added to the
40
+ * catalog, an entitlement can be granted, a Pro plan can be upgraded — so it
41
+ * must expire rather than bind the whole process lifetime. Long enough that a
42
+ * genuinely-absent feed costs one discovery per window instead of one per
43
+ * build; short enough that a recovered endpoint self-heals without a restart.
44
+ */
45
+ export declare const MISSING_FEED_MEMO_TTL_MS: number;
46
+ /**
47
+ * Thrown when discovery concludes that an endpoint rejects EVERY requested
48
+ * feed id without a catalog vouching for that verdict. `instanceof`-able
49
+ * (mirrors `FetchPolicyError` / {@link OracleFeeSourceUnavailableError}).
50
+ *
51
+ * "All of them are missing" is the signature of an endpoint/credential fault —
52
+ * a wrong base path (the Pyth Pro `/hermes` prefix dropped), a changed route,
53
+ * a revoked or downgraded entitlement — not of N individually-absent feeds.
54
+ * Memoizing it would convert a loud, fixable misconfiguration into a silent
55
+ * permanent one: every id marked missing ⇒ `fetchPriceFeedsUpdateData` returns
56
+ * `[]` ⇒ `buildPythPriceUpdateCalls` throws "Hermes returned empty results",
57
+ * blaming Hermes for having no data, for the rest of the process's life. So
58
+ * this case writes NOTHING to the memo and throws instead; the next call
59
+ * re-probes and recovers on its own once the endpoint does.
60
+ *
61
+ * The message keeps the `Hermes price fetch failed: <status>` prefix on its
62
+ * first line — the documented contract downstream consumers string-match (see
63
+ * {@link fetchPriceFeedsUpdateData} and the e2e transient detector).
64
+ */
65
+ export declare class HermesEndpointRejectedAllFeedsError extends Error {
66
+ readonly endpoint: string;
67
+ readonly requestedCount: number;
68
+ constructor(endpoint: string, requestedCount: number, catalogState: "unreadable" | "empty");
69
+ }
70
+ /**
71
+ * The subset of `feedIds` this `endpoint` is known to serve — i.e. minus any
72
+ * discovered to be absent within the last {@link MISSING_FEED_MEMO_TTL_MS}
73
+ * (see {@link fetchPriceFeedsUpdateData}). Callers building a
74
+ * `{ updates, feedIds }` payload use this to keep `feedIds` aligned with the
75
+ * feeds the fetch actually returned data for, so `buildPythPriceUpdateCalls`
76
+ * (one moveCall per feed id) never references a feed the accumulator blob
77
+ * doesn't cover.
78
+ *
79
+ * Expired entries are pruned here rather than on a timer: the memo is only
80
+ * ever consulted through this function, so a lazy sweep is both sufficient and
81
+ * free of a dangling interval in a library.
82
+ */
83
+ export declare function endpointSupportedFeedIds(endpoint: string, feedIds: string[], apiKey?: string): string[];
84
+ /** Test-only: forget everything learned about which feeds an endpoint lacks. */
85
+ export declare function __resetMissingFeedCacheForTest(): void;
86
+ /**
87
+ * Discovery-only entry for consumers that fetch Hermes THEMSELVES (e.g. a
88
+ * parsed latest-price reader) and just observed a whole-batch 404: resolves
89
+ * which ids the endpoint lacks, memoizes them (see {@link
90
+ * endpointSupportedFeedIds}), fetches NO survivor data. Without this, such a
91
+ * consumer's only way to populate the memo was calling {@link
92
+ * fetchPriceFeedsUpdateData} and discarding its accumulator blob — two full
93
+ * redundant transfers per cold discovery.
94
+ *
95
+ * @throws HermesEndpointRejectedAllFeedsError when the rejection looks
96
+ * endpoint-wide rather than per-feed — the caller's own 404 is then a
97
+ * misconfiguration to surface, not a set of feeds to quietly drop.
98
+ */
99
+ export declare function probeMissingFeeds(endpoint: string, ids: string[], opts?: FetchOpts): Promise<void>;
100
+ export declare function fetchPriceFeedsUpdateData(endpoint: string, priceIds: string[], opts?: FetchOpts): Promise<Uint8Array[]>;
101
+ /**
102
+ * Resolved source for the Pyth Core on-chain update fee. Deliberately a
103
+ * closed two-variant union, not a `{ sponsorFund?, allowGasFee? }` pair — a
104
+ * caller can no longer construct the "both supplied" or "neither supplied
105
+ * but some other truthy flag" shapes that used to require a priority rule to
106
+ * disambiguate.
107
+ *
108
+ * Resolved exactly ONCE, at the edges (`wrapRequestAndExecute` and the WLP
109
+ * builders' equivalent in `perp/tx-builders/`) from config presence
110
+ * (`pyth_sponsor_rule` deployed → open a fund → `'sponsor'`) and the
111
+ * caller's ergonomic `allowGasFee` opt-in (→ `'gas'`), then threaded
112
+ * verbatim through `refreshOraclePrices` → `BuildUpdateOpts` →
113
+ * `PythCoreRule` → {@link buildPythPriceUpdateCalls}. The sponsor-beats-gas
114
+ * priority from the old two-flag design is now structural — whichever edge
115
+ * resolves this value decides once; no downstream layer re-derives or
116
+ * re-documents a priority because none of them ever see more than one
117
+ * candidate source.
118
+ */
119
+ export type OracleFeeSource = {
120
+ readonly kind: "sponsor";
121
+ readonly fund: TransactionArgument;
122
+ readonly packageId: string;
123
+ } | {
124
+ readonly kind: "gas";
125
+ };
126
+ /**
127
+ * Thrown when no {@link OracleFeeSource} is available for the Pyth update fee
128
+ * — from `buildPythPriceUpdateCalls`'s own per-call guard, or `aggregate.ts`'s
129
+ * hoisted `refreshOraclePrices` pre-check (see its docblock). `instanceof`-able
130
+ * (mirrors `FetchPolicyError` in `update-fetch.ts`) so a consumer — e.g. a BE
131
+ * integration wiring its own `allowGasFee` decision — can branch on the error
132
+ * type directly instead of string-matching `error.message`.
133
+ */
134
+ export declare class OracleFeeSourceUnavailableError extends Error {
135
+ constructor();
136
+ }
33
137
  /**
34
138
  * Append the on-chain Pyth update PTB block. Returns `PriceInfoObject` IDs
35
139
  * (one per `feedIds`, same order). After this you can feed `pyth_rule` per
36
140
  * ticker against the matching `PriceInfoObject` (see `rules/pyth-rule.ts`).
37
141
  *
38
- * If `sponsorFund` is provided, the per-feed update fee comes from the
39
- * sponsor pool (`pyth_sponsor_rule::split`) instead of `tx.gas`. Opening and
40
- * reimbursing that fund is the sponsor rule's job (`rules/sponsor.ts`); here we
41
- * only draw a fee coin from the already-open `fund` hot potato.
142
+ * `opts.feeSource` is resolved BEFORE any PTB mutation and is never silently
143
+ * defaulted this function trusts whatever single {@link OracleFeeSource}
144
+ * it's handed, it does not choose between competing candidates:
145
+ * - `{ kind: 'sponsor' }` the per-feed update fee is drawn from the
146
+ * sponsor pool (`pyth_sponsor_rule::split`) instead of `tx.gas`. Opening
147
+ * and reimbursing that fund is the caller's job (`rules/sponsor.ts` /
148
+ * `wrapRequestAndExecute`, which opens it whenever the client's config
149
+ * has `pyth_sponsor_rule` deployed) — this function only draws a fee
150
+ * coin from the already-open `fund` hot potato.
151
+ * - `{ kind: 'gas' }` → the fee is drawn from `tx.gas` via `tx.splitCoins`.
152
+ * Only safe in a non-sponsored context — Enoki-sponsored transactions
153
+ * reject any `tx.gas` draw.
154
+ * - `undefined` → throws `OracleFeeSourceUnavailable` instead of silently
155
+ * drawing from `tx.gas` (the old default), which broke under Enoki and,
156
+ * worse, could fail ON-CHAIN when the market's `request_checklist`
157
+ * requires the `PythSponsorRule` witness that only a real sponsor fund
158
+ * attaches.
159
+ *
160
+ * This function's own check runs AFTER `updates`/`feedIds` are already in
161
+ * hand, so for `updatePythPrices` (which fetches from Hermes, then calls
162
+ * straight into this function) the off-chain fetch has already completed by
163
+ * the time this throws — a wasted network call, never a stray PTB command.
164
+ * `refreshOraclePrices` avoids that waste entirely: it hoists an EQUIVALENT
165
+ * check ABOVE its off-chain fetch AND its per-group build loop (see its
166
+ * docblock in `aggregate.ts`), keyed on `PriceUpdateRule.requiresFeeSource`
167
+ * rather than waiting for a specific rule's fetch to complete — so for that
168
+ * route neither the network call NOR any PTB command happens before the
169
+ * throw. This function's own (later, per-call) guard alone could not
170
+ * provide that "before any group builds" guarantee in a mixed shape (e.g. a
171
+ * fee-free Lazer group ordered ahead of a Pyth Core fallback group in the
172
+ * same PTB) — `refreshOraclePrices`'s pre-check is what closes it.
42
173
  */
43
- export declare function buildPythPriceUpdateCalls(tx: Transaction, host: OracleHost, updates: Uint8Array[], feedIds: string[], cache?: PythCache, sponsorFund?: {
44
- fund: TransactionArgument;
45
- packageId: string;
174
+ export declare function buildPythPriceUpdateCalls(tx: Transaction, host: OracleHost, updates: Uint8Array[], feedIds: string[], opts?: {
175
+ cache?: PythCache;
176
+ feeSource?: OracleFeeSource;
46
177
  }): Promise<string[]>;
47
178
  /** All-in-one: fetch from Hermes, append update calls. Returns PriceInfoObject IDs. */
48
- export declare function updatePythPrices(tx: Transaction, host: OracleHost, feedIds: string[], cache?: PythCache, sponsorFund?: {
49
- fund: TransactionArgument;
50
- packageId: string;
179
+ export declare function updatePythPrices(tx: Transaction, host: OracleHost, feedIds: string[], opts?: {
180
+ cache?: PythCache;
181
+ feeSource?: OracleFeeSource;
51
182
  }): Promise<string[]>;
52
183
  export {};