@waterx/sdk 3.1.0 → 4.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.
- package/README.md +53 -7
- package/dist/cjs/src/oracle/aggregate.d.ts +98 -17
- package/dist/cjs/src/oracle/aggregate.js +191 -21
- package/dist/cjs/src/oracle/config.d.ts +103 -0
- package/dist/cjs/src/oracle/config.js +64 -1
- package/dist/cjs/src/oracle/host.d.ts +13 -0
- package/dist/cjs/src/oracle/index.d.ts +19 -5
- package/dist/cjs/src/oracle/index.js +40 -6
- package/dist/cjs/src/oracle/price-update-rule.d.ts +180 -0
- package/dist/cjs/src/oracle/price-update-rule.js +56 -0
- package/dist/cjs/src/oracle/pyth.d.ts +80 -11
- package/dist/cjs/src/oracle/pyth.js +84 -17
- package/dist/cjs/src/oracle/rule-registry.d.ts +37 -0
- package/dist/cjs/src/oracle/rule-registry.js +61 -0
- package/dist/cjs/src/oracle/rules/pyth-core-rule.d.ts +15 -0
- package/dist/cjs/src/oracle/rules/pyth-core-rule.js +84 -0
- package/dist/cjs/src/oracle/rules/pyth-lazer-rule.d.ts +41 -0
- package/dist/cjs/src/oracle/rules/pyth-lazer-rule.js +194 -0
- package/dist/cjs/src/oracle/rules/sponsor.d.ts +11 -7
- package/dist/cjs/src/oracle/rules/sponsor.js +11 -7
- package/dist/cjs/src/oracle/update-fetch.d.ts +85 -0
- package/dist/cjs/src/oracle/update-fetch.js +228 -0
- package/dist/cjs/src/perp/client.d.ts +22 -1
- package/dist/cjs/src/perp/client.js +11 -2
- package/dist/cjs/src/perp/config.d.ts +15 -20
- package/dist/cjs/src/perp/config.js +86 -30
- package/dist/cjs/src/perp/index.d.ts +4 -3
- package/dist/cjs/src/perp/index.js +8 -4
- package/dist/cjs/src/perp/tx-builders/common.d.ts +52 -15
- package/dist/cjs/src/perp/tx-builders/common.js +39 -6
- package/dist/cjs/src/perp/tx-builders/wlp.d.ts +11 -3
- package/dist/cjs/src/perp/tx-builders/wlp.js +29 -3
- package/dist/cjs/src/perp/tx-builders.d.ts +3 -3
- package/dist/cjs/src/perp/tx-builders.js +3 -3
- package/dist/cjs/src/prediction/config.d.ts +5 -15
- package/dist/cjs/src/prediction/config.js +4 -12
- package/dist/cjs/src/prediction/fetch.d.ts +6 -1
- package/dist/cjs/src/prediction/fetch.js +64 -0
- package/dist/cjs/src/prediction/index.d.ts +2 -2
- package/dist/cjs/src/prediction/index.js +7 -4
- package/dist/cjs/src/prediction/types.d.ts +19 -0
- package/dist/cjs/src/unified-client.d.ts +26 -4
- package/dist/cjs/src/unified-client.js +4 -2
- package/dist/src/oracle/aggregate.d.ts +98 -17
- package/dist/src/oracle/aggregate.js +192 -22
- package/dist/src/oracle/config.d.ts +103 -0
- package/dist/src/oracle/config.js +63 -0
- package/dist/src/oracle/host.d.ts +13 -0
- package/dist/src/oracle/index.d.ts +19 -5
- package/dist/src/oracle/index.js +34 -6
- package/dist/src/oracle/price-update-rule.d.ts +180 -0
- package/dist/src/oracle/price-update-rule.js +53 -0
- package/dist/src/oracle/pyth.d.ts +80 -11
- package/dist/src/oracle/pyth.js +82 -16
- package/dist/src/oracle/rule-registry.d.ts +37 -0
- package/dist/src/oracle/rule-registry.js +56 -0
- package/dist/src/oracle/rules/pyth-core-rule.d.ts +15 -0
- package/dist/src/oracle/rules/pyth-core-rule.js +81 -0
- package/dist/src/oracle/rules/pyth-lazer-rule.d.ts +41 -0
- package/dist/src/oracle/rules/pyth-lazer-rule.js +189 -0
- package/dist/src/oracle/rules/sponsor.d.ts +11 -7
- package/dist/src/oracle/rules/sponsor.js +11 -7
- package/dist/src/oracle/update-fetch.d.ts +85 -0
- package/dist/src/oracle/update-fetch.js +223 -0
- package/dist/src/perp/client.d.ts +22 -1
- package/dist/src/perp/client.js +12 -3
- package/dist/src/perp/config.d.ts +15 -20
- package/dist/src/perp/config.js +85 -29
- package/dist/src/perp/index.d.ts +4 -3
- package/dist/src/perp/index.js +2 -2
- package/dist/src/perp/tx-builders/common.d.ts +52 -15
- package/dist/src/perp/tx-builders/common.js +39 -6
- package/dist/src/perp/tx-builders/wlp.d.ts +11 -3
- package/dist/src/perp/tx-builders/wlp.js +29 -3
- package/dist/src/perp/tx-builders.d.ts +3 -3
- package/dist/src/perp/tx-builders.js +3 -3
- package/dist/src/prediction/config.d.ts +5 -15
- package/dist/src/prediction/config.js +4 -11
- package/dist/src/prediction/fetch.d.ts +6 -1
- package/dist/src/prediction/fetch.js +60 -0
- package/dist/src/prediction/index.d.ts +2 -2
- package/dist/src/prediction/index.js +2 -2
- package/dist/src/prediction/types.d.ts +19 -0
- package/dist/src/unified-client.d.ts +26 -4
- package/dist/src/unified-client.js +4 -2
- 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
|
+
}
|
|
@@ -29,24 +29,93 @@ export declare class PythCache {
|
|
|
29
29
|
priceTableInfo?: PriceTableInfo;
|
|
30
30
|
priceFeedObjectIdCache: Map<string, string | undefined>;
|
|
31
31
|
}
|
|
32
|
-
export declare function fetchPriceFeedsUpdateData(endpoint: string, priceIds: string[]
|
|
32
|
+
export declare function fetchPriceFeedsUpdateData(endpoint: string, priceIds: string[], opts?: {
|
|
33
|
+
apiKey?: string;
|
|
34
|
+
fetch?: {
|
|
35
|
+
timeoutMs?: number;
|
|
36
|
+
retries?: number;
|
|
37
|
+
};
|
|
38
|
+
}): Promise<Uint8Array[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Resolved source for the Pyth Core on-chain update fee. Deliberately a
|
|
41
|
+
* closed two-variant union, not a `{ sponsorFund?, allowGasFee? }` pair — a
|
|
42
|
+
* caller can no longer construct the "both supplied" or "neither supplied
|
|
43
|
+
* but some other truthy flag" shapes that used to require a priority rule to
|
|
44
|
+
* disambiguate.
|
|
45
|
+
*
|
|
46
|
+
* Resolved exactly ONCE, at the edges (`wrapRequestAndExecute` and the WLP
|
|
47
|
+
* builders' equivalent in `perp/tx-builders/`) from config presence
|
|
48
|
+
* (`pyth_sponsor_rule` deployed → open a fund → `'sponsor'`) and the
|
|
49
|
+
* caller's ergonomic `allowGasFee` opt-in (→ `'gas'`), then threaded
|
|
50
|
+
* verbatim through `refreshOraclePrices` → `BuildUpdateOpts` →
|
|
51
|
+
* `PythCoreRule` → {@link buildPythPriceUpdateCalls}. The sponsor-beats-gas
|
|
52
|
+
* priority from the old two-flag design is now structural — whichever edge
|
|
53
|
+
* resolves this value decides once; no downstream layer re-derives or
|
|
54
|
+
* re-documents a priority because none of them ever see more than one
|
|
55
|
+
* candidate source.
|
|
56
|
+
*/
|
|
57
|
+
export type OracleFeeSource = {
|
|
58
|
+
readonly kind: "sponsor";
|
|
59
|
+
readonly fund: TransactionArgument;
|
|
60
|
+
readonly packageId: string;
|
|
61
|
+
} | {
|
|
62
|
+
readonly kind: "gas";
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Thrown when no {@link OracleFeeSource} is available for the Pyth update fee
|
|
66
|
+
* — from `buildPythPriceUpdateCalls`'s own per-call guard, or `aggregate.ts`'s
|
|
67
|
+
* hoisted `refreshOraclePrices` pre-check (see its docblock). `instanceof`-able
|
|
68
|
+
* (mirrors `FetchPolicyError` in `update-fetch.ts`) so a consumer — e.g. a BE
|
|
69
|
+
* integration wiring its own `allowGasFee` decision — can branch on the error
|
|
70
|
+
* type directly instead of string-matching `error.message`.
|
|
71
|
+
*/
|
|
72
|
+
export declare class OracleFeeSourceUnavailableError extends Error {
|
|
73
|
+
constructor();
|
|
74
|
+
}
|
|
33
75
|
/**
|
|
34
76
|
* Append the on-chain Pyth update PTB block. Returns `PriceInfoObject` IDs
|
|
35
77
|
* (one per `feedIds`, same order). After this you can feed `pyth_rule` per
|
|
36
78
|
* ticker against the matching `PriceInfoObject` (see `rules/pyth-rule.ts`).
|
|
37
79
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
80
|
+
* `opts.feeSource` is resolved BEFORE any PTB mutation and is never silently
|
|
81
|
+
* defaulted — this function trusts whatever single {@link OracleFeeSource}
|
|
82
|
+
* it's handed, it does not choose between competing candidates:
|
|
83
|
+
* - `{ kind: 'sponsor' }` → the per-feed update fee is drawn from the
|
|
84
|
+
* sponsor pool (`pyth_sponsor_rule::split`) instead of `tx.gas`. Opening
|
|
85
|
+
* and reimbursing that fund is the caller's job (`rules/sponsor.ts` /
|
|
86
|
+
* `wrapRequestAndExecute`, which opens it whenever the client's config
|
|
87
|
+
* has `pyth_sponsor_rule` deployed) — this function only draws a fee
|
|
88
|
+
* coin from the already-open `fund` hot potato.
|
|
89
|
+
* - `{ kind: 'gas' }` → the fee is drawn from `tx.gas` via `tx.splitCoins`.
|
|
90
|
+
* Only safe in a non-sponsored context — Enoki-sponsored transactions
|
|
91
|
+
* reject any `tx.gas` draw.
|
|
92
|
+
* - `undefined` → throws `OracleFeeSourceUnavailable` instead of silently
|
|
93
|
+
* drawing from `tx.gas` (the old default), which broke under Enoki and,
|
|
94
|
+
* worse, could fail ON-CHAIN when the market's `request_checklist`
|
|
95
|
+
* requires the `PythSponsorRule` witness that only a real sponsor fund
|
|
96
|
+
* attaches.
|
|
97
|
+
*
|
|
98
|
+
* This function's own check runs AFTER `updates`/`feedIds` are already in
|
|
99
|
+
* hand, so for `updatePythPrices` (which fetches from Hermes, then calls
|
|
100
|
+
* straight into this function) the off-chain fetch has already completed by
|
|
101
|
+
* the time this throws — a wasted network call, never a stray PTB command.
|
|
102
|
+
* `refreshOraclePrices` avoids that waste entirely: it hoists an EQUIVALENT
|
|
103
|
+
* check ABOVE its off-chain fetch AND its per-group build loop (see its
|
|
104
|
+
* docblock in `aggregate.ts`), keyed on `PriceUpdateRule.requiresFeeSource`
|
|
105
|
+
* rather than waiting for a specific rule's fetch to complete — so for that
|
|
106
|
+
* route neither the network call NOR any PTB command happens before the
|
|
107
|
+
* throw. This function's own (later, per-call) guard alone could not
|
|
108
|
+
* provide that "before any group builds" guarantee in a mixed shape (e.g. a
|
|
109
|
+
* fee-free Lazer group ordered ahead of a Pyth Core fallback group in the
|
|
110
|
+
* same PTB) — `refreshOraclePrices`'s pre-check is what closes it.
|
|
42
111
|
*/
|
|
43
|
-
export declare function buildPythPriceUpdateCalls(tx: Transaction, host: OracleHost, updates: Uint8Array[], feedIds: string[],
|
|
44
|
-
|
|
45
|
-
|
|
112
|
+
export declare function buildPythPriceUpdateCalls(tx: Transaction, host: OracleHost, updates: Uint8Array[], feedIds: string[], opts?: {
|
|
113
|
+
cache?: PythCache;
|
|
114
|
+
feeSource?: OracleFeeSource;
|
|
46
115
|
}): Promise<string[]>;
|
|
47
116
|
/** All-in-one: fetch from Hermes, append update calls. Returns PriceInfoObject IDs. */
|
|
48
|
-
export declare function updatePythPrices(tx: Transaction, host: OracleHost, feedIds: string[],
|
|
49
|
-
|
|
50
|
-
|
|
117
|
+
export declare function updatePythPrices(tx: Transaction, host: OracleHost, feedIds: string[], opts?: {
|
|
118
|
+
cache?: PythCache;
|
|
119
|
+
feeSource?: OracleFeeSource;
|
|
51
120
|
}): Promise<string[]>;
|
|
52
121
|
export {};
|
package/dist/src/oracle/pyth.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { fromHex, toHex } from "@mysten/bcs";
|
|
17
17
|
import { bcs } from "@mysten/sui/bcs";
|
|
18
|
+
import { FetchPolicyError, fetchWithPolicy } from "./update-fetch.js";
|
|
18
19
|
export class PythCache {
|
|
19
20
|
pythStateInfo;
|
|
20
21
|
wormholePackageId;
|
|
@@ -24,12 +25,28 @@ export class PythCache {
|
|
|
24
25
|
// ============================================================================
|
|
25
26
|
// Hermes REST
|
|
26
27
|
// ============================================================================
|
|
27
|
-
export async function fetchPriceFeedsUpdateData(endpoint, priceIds) {
|
|
28
|
+
export async function fetchPriceFeedsUpdateData(endpoint, priceIds, opts) {
|
|
28
29
|
if (priceIds.length === 0)
|
|
29
30
|
return [];
|
|
30
31
|
const url = new URL("/v2/updates/price/latest", endpoint);
|
|
31
32
|
priceIds.forEach((id) => url.searchParams.append("ids[]", id));
|
|
32
|
-
|
|
33
|
+
let res;
|
|
34
|
+
try {
|
|
35
|
+
res = await fetchWithPolicy(url.toString(), {}, { apiKey: opts?.apiKey, ...opts?.fetch });
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
// A retryable status (429/5xx) that never recovered surfaces as a
|
|
39
|
+
// FetchPolicyError with `status` set — reformat it into this function's
|
|
40
|
+
// own message shape so callers (and the e2e transient-failure detector,
|
|
41
|
+
// which keys off "Hermes price fetch failed") see the same text whether
|
|
42
|
+
// the failure was retried or not. A network-level exhaustion (no status)
|
|
43
|
+
// has no domain-specific reframing to add — propagate it as-is.
|
|
44
|
+
if (err instanceof FetchPolicyError && err.status !== undefined) {
|
|
45
|
+
const body = err.bodySnippet ? ` ${err.bodySnippet}` : "";
|
|
46
|
+
throw new Error(`Hermes price fetch failed: ${err.status}${body} (retries exhausted after ${err.attempts} attempts)`, { cause: err });
|
|
47
|
+
}
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
33
50
|
if (!res.ok)
|
|
34
51
|
throw new Error(`Hermes price fetch failed: ${res.status} ${await res.text()}`);
|
|
35
52
|
const json = (await res.json());
|
|
@@ -137,26 +154,72 @@ function extractVaaBytes(accumulatorMessage) {
|
|
|
137
154
|
const vaaOffset = vaaSizeOffset + 2;
|
|
138
155
|
return accumulatorMessage.subarray(vaaOffset, vaaOffset + vaaSize);
|
|
139
156
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Thrown when no {@link OracleFeeSource} is available for the Pyth update fee
|
|
159
|
+
* — from `buildPythPriceUpdateCalls`'s own per-call guard, or `aggregate.ts`'s
|
|
160
|
+
* hoisted `refreshOraclePrices` pre-check (see its docblock). `instanceof`-able
|
|
161
|
+
* (mirrors `FetchPolicyError` in `update-fetch.ts`) so a consumer — e.g. a BE
|
|
162
|
+
* integration wiring its own `allowGasFee` decision — can branch on the error
|
|
163
|
+
* type directly instead of string-matching `error.message`.
|
|
164
|
+
*/
|
|
165
|
+
export class OracleFeeSourceUnavailableError extends Error {
|
|
166
|
+
constructor() {
|
|
167
|
+
super("OracleFeeSourceUnavailable: no fee source available for the Pyth update fee — " +
|
|
168
|
+
"deploy pyth_sponsor_rule to config so a sponsor fund can be opened (see " +
|
|
169
|
+
"openPythSponsorFund / wrapRequestAndExecute), or pass allowGasFee: true to draw " +
|
|
170
|
+
"the fee from tx.gas in a non-sponsored context");
|
|
171
|
+
this.name = "OracleFeeSourceUnavailableError";
|
|
172
|
+
}
|
|
173
|
+
}
|
|
143
174
|
/**
|
|
144
175
|
* Append the on-chain Pyth update PTB block. Returns `PriceInfoObject` IDs
|
|
145
176
|
* (one per `feedIds`, same order). After this you can feed `pyth_rule` per
|
|
146
177
|
* ticker against the matching `PriceInfoObject` (see `rules/pyth-rule.ts`).
|
|
147
178
|
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
179
|
+
* `opts.feeSource` is resolved BEFORE any PTB mutation and is never silently
|
|
180
|
+
* defaulted — this function trusts whatever single {@link OracleFeeSource}
|
|
181
|
+
* it's handed, it does not choose between competing candidates:
|
|
182
|
+
* - `{ kind: 'sponsor' }` → the per-feed update fee is drawn from the
|
|
183
|
+
* sponsor pool (`pyth_sponsor_rule::split`) instead of `tx.gas`. Opening
|
|
184
|
+
* and reimbursing that fund is the caller's job (`rules/sponsor.ts` /
|
|
185
|
+
* `wrapRequestAndExecute`, which opens it whenever the client's config
|
|
186
|
+
* has `pyth_sponsor_rule` deployed) — this function only draws a fee
|
|
187
|
+
* coin from the already-open `fund` hot potato.
|
|
188
|
+
* - `{ kind: 'gas' }` → the fee is drawn from `tx.gas` via `tx.splitCoins`.
|
|
189
|
+
* Only safe in a non-sponsored context — Enoki-sponsored transactions
|
|
190
|
+
* reject any `tx.gas` draw.
|
|
191
|
+
* - `undefined` → throws `OracleFeeSourceUnavailable` instead of silently
|
|
192
|
+
* drawing from `tx.gas` (the old default), which broke under Enoki and,
|
|
193
|
+
* worse, could fail ON-CHAIN when the market's `request_checklist`
|
|
194
|
+
* requires the `PythSponsorRule` witness that only a real sponsor fund
|
|
195
|
+
* attaches.
|
|
196
|
+
*
|
|
197
|
+
* This function's own check runs AFTER `updates`/`feedIds` are already in
|
|
198
|
+
* hand, so for `updatePythPrices` (which fetches from Hermes, then calls
|
|
199
|
+
* straight into this function) the off-chain fetch has already completed by
|
|
200
|
+
* the time this throws — a wasted network call, never a stray PTB command.
|
|
201
|
+
* `refreshOraclePrices` avoids that waste entirely: it hoists an EQUIVALENT
|
|
202
|
+
* check ABOVE its off-chain fetch AND its per-group build loop (see its
|
|
203
|
+
* docblock in `aggregate.ts`), keyed on `PriceUpdateRule.requiresFeeSource`
|
|
204
|
+
* rather than waiting for a specific rule's fetch to complete — so for that
|
|
205
|
+
* route neither the network call NOR any PTB command happens before the
|
|
206
|
+
* throw. This function's own (later, per-call) guard alone could not
|
|
207
|
+
* provide that "before any group builds" guarantee in a mixed shape (e.g. a
|
|
208
|
+
* fee-free Lazer group ordered ahead of a Pyth Core fallback group in the
|
|
209
|
+
* same PTB) — `refreshOraclePrices`'s pre-check is what closes it.
|
|
152
210
|
*/
|
|
153
|
-
export async function buildPythPriceUpdateCalls(tx, host, updates, feedIds,
|
|
211
|
+
export async function buildPythPriceUpdateCalls(tx, host, updates, feedIds, opts) {
|
|
154
212
|
if (updates.length === 0) {
|
|
155
213
|
throw new Error("No price update data provided; Hermes returned empty results");
|
|
156
214
|
}
|
|
157
215
|
if (updates.length > 1) {
|
|
158
216
|
throw new Error("Only a single accumulator message is supported per transaction");
|
|
159
217
|
}
|
|
218
|
+
const feeSource = opts?.feeSource;
|
|
219
|
+
if (!feeSource) {
|
|
220
|
+
throw new OracleFeeSourceUnavailableError();
|
|
221
|
+
}
|
|
222
|
+
const cache = opts?.cache;
|
|
160
223
|
const pyth = host.pyth;
|
|
161
224
|
const [stateInfo, wormholePackageId, table] = await Promise.all([
|
|
162
225
|
getPythStateInfo(host.grpcClient, pyth.state_id, cache),
|
|
@@ -188,10 +251,10 @@ export async function buildPythPriceUpdateCalls(tx, host, updates, feedIds, cach
|
|
|
188
251
|
if (!priceInfoObjectId) {
|
|
189
252
|
throw new Error(`Pyth feed ${feedIds[i]} not registered on-chain in Pyth state`);
|
|
190
253
|
}
|
|
191
|
-
const feeCoin =
|
|
254
|
+
const feeCoin = feeSource.kind === "sponsor"
|
|
192
255
|
? tx.moveCall({
|
|
193
|
-
target: `${
|
|
194
|
-
arguments: [
|
|
256
|
+
target: `${feeSource.packageId}::pyth_sponsor_rule::split`,
|
|
257
|
+
arguments: [feeSource.fund],
|
|
195
258
|
})[0]
|
|
196
259
|
: tx.splitCoins(tx.gas, [tx.pure.u64(baseUpdateFee)])[0];
|
|
197
260
|
[hotPotato] = tx.moveCall({
|
|
@@ -214,7 +277,10 @@ export async function buildPythPriceUpdateCalls(tx, host, updates, feedIds, cach
|
|
|
214
277
|
return priceInfoObjectIds;
|
|
215
278
|
}
|
|
216
279
|
/** All-in-one: fetch from Hermes, append update calls. Returns PriceInfoObject IDs. */
|
|
217
|
-
export async function updatePythPrices(tx, host, feedIds,
|
|
218
|
-
const updates = await fetchPriceFeedsUpdateData(host.pyth.hermes_endpoint, feedIds
|
|
219
|
-
|
|
280
|
+
export async function updatePythPrices(tx, host, feedIds, opts) {
|
|
281
|
+
const updates = await fetchPriceFeedsUpdateData(host.pyth.hermes_endpoint, feedIds, {
|
|
282
|
+
apiKey: host.pyth.api_key,
|
|
283
|
+
fetch: host.pyth.fetch,
|
|
284
|
+
});
|
|
285
|
+
return buildPythPriceUpdateCalls(tx, host, updates, feedIds, opts);
|
|
220
286
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rule-registry.ts` — maps a client-selected `OracleSource` to its concrete
|
|
3
|
+
* `PriceUpdateRule` implementation. `refreshOraclePrices` (`aggregate.ts`) is
|
|
4
|
+
* the only production caller; this is the one place `OracleSource` values are
|
|
5
|
+
* wired to a rule instance. Selection is driven purely by the value passed in
|
|
6
|
+
* (ultimately `OracleHost.oracleSource`, a client create option) — never by a
|
|
7
|
+
* config JSON `enabled` flag and never by `process.env`.
|
|
8
|
+
*
|
|
9
|
+
* Both sources are registered: `pyth_rule` (`PythCoreRule`, Hermes VAA) and
|
|
10
|
+
* `pyth_lazer_rule` (`PythLazerRule`, Lazer signed updates). Resolving a
|
|
11
|
+
* source with no registered rule throws a clear `OracleSourceNotImplemented`
|
|
12
|
+
* error instead of silently falling back to Pyth Core.
|
|
13
|
+
*/
|
|
14
|
+
import type { OracleSource, PriceUpdateRule } from "./price-update-rule.ts";
|
|
15
|
+
/**
|
|
16
|
+
* Thrown by {@link resolveOracleRule} when `source` has no `PriceUpdateRule`
|
|
17
|
+
* registered in either the production registry or a test's `overrides` map.
|
|
18
|
+
* `instanceof`-able (mirrors `OracleFeeSourceUnavailableError` in `pyth.ts`)
|
|
19
|
+
* so a consumer can branch on the failure type directly instead of
|
|
20
|
+
* string-matching `error.message`.
|
|
21
|
+
*/
|
|
22
|
+
export declare class OracleSourceNotImplementedError extends Error {
|
|
23
|
+
/** The unregistered `OracleSource` that was requested. */
|
|
24
|
+
readonly source: OracleSource;
|
|
25
|
+
constructor(source: OracleSource);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the `PriceUpdateRule` registered for `source`.
|
|
29
|
+
*
|
|
30
|
+
* `overrides` — test-only — layers on top of the production registry so a
|
|
31
|
+
* spec can inject a fake rule (e.g. a stub `pyth_lazer_rule`) without
|
|
32
|
+
* touching {@link DEFAULT_RULES}; production callers never pass it.
|
|
33
|
+
*
|
|
34
|
+
* Throws {@link OracleSourceNotImplementedError} (`OracleSourceNotImplemented:
|
|
35
|
+
* <source>`) when nothing is registered for `source` in either map.
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveOracleRule(source: OracleSource, overrides?: Partial<Record<OracleSource, PriceUpdateRule>>): PriceUpdateRule;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rule-registry.ts` — maps a client-selected `OracleSource` to its concrete
|
|
3
|
+
* `PriceUpdateRule` implementation. `refreshOraclePrices` (`aggregate.ts`) is
|
|
4
|
+
* the only production caller; this is the one place `OracleSource` values are
|
|
5
|
+
* wired to a rule instance. Selection is driven purely by the value passed in
|
|
6
|
+
* (ultimately `OracleHost.oracleSource`, a client create option) — never by a
|
|
7
|
+
* config JSON `enabled` flag and never by `process.env`.
|
|
8
|
+
*
|
|
9
|
+
* Both sources are registered: `pyth_rule` (`PythCoreRule`, Hermes VAA) and
|
|
10
|
+
* `pyth_lazer_rule` (`PythLazerRule`, Lazer signed updates). Resolving a
|
|
11
|
+
* source with no registered rule throws a clear `OracleSourceNotImplemented`
|
|
12
|
+
* error instead of silently falling back to Pyth Core.
|
|
13
|
+
*/
|
|
14
|
+
import { PythCoreRule } from "./rules/pyth-core-rule.js";
|
|
15
|
+
import { PythLazerRule } from "./rules/pyth-lazer-rule.js";
|
|
16
|
+
/**
|
|
17
|
+
* Production registry. Frozen — tests inject a fake rule via
|
|
18
|
+
* `resolveOracleRule`'s `overrides` param instead of mutating this.
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_RULES = Object.freeze({
|
|
21
|
+
pyth_rule: PythCoreRule,
|
|
22
|
+
pyth_lazer_rule: PythLazerRule,
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* Thrown by {@link resolveOracleRule} when `source` has no `PriceUpdateRule`
|
|
26
|
+
* registered in either the production registry or a test's `overrides` map.
|
|
27
|
+
* `instanceof`-able (mirrors `OracleFeeSourceUnavailableError` in `pyth.ts`)
|
|
28
|
+
* so a consumer can branch on the failure type directly instead of
|
|
29
|
+
* string-matching `error.message`.
|
|
30
|
+
*/
|
|
31
|
+
export class OracleSourceNotImplementedError extends Error {
|
|
32
|
+
/** The unregistered `OracleSource` that was requested. */
|
|
33
|
+
source;
|
|
34
|
+
constructor(source) {
|
|
35
|
+
super(`OracleSourceNotImplemented: ${source}`);
|
|
36
|
+
this.name = "OracleSourceNotImplementedError";
|
|
37
|
+
this.source = source;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolve the `PriceUpdateRule` registered for `source`.
|
|
42
|
+
*
|
|
43
|
+
* `overrides` — test-only — layers on top of the production registry so a
|
|
44
|
+
* spec can inject a fake rule (e.g. a stub `pyth_lazer_rule`) without
|
|
45
|
+
* touching {@link DEFAULT_RULES}; production callers never pass it.
|
|
46
|
+
*
|
|
47
|
+
* Throws {@link OracleSourceNotImplementedError} (`OracleSourceNotImplemented:
|
|
48
|
+
* <source>`) when nothing is registered for `source` in either map.
|
|
49
|
+
*/
|
|
50
|
+
export function resolveOracleRule(source, overrides) {
|
|
51
|
+
const rule = overrides?.[source] ?? DEFAULT_RULES[source];
|
|
52
|
+
if (!rule) {
|
|
53
|
+
throw new OracleSourceNotImplementedError(source);
|
|
54
|
+
}
|
|
55
|
+
return rule;
|
|
56
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `PythCoreRule` — `PriceUpdateRule` wrapper around the existing Pyth Core
|
|
3
|
+
* (Hermes VAA) source in `../pyth.ts`. Delegates to `fetchPriceFeedsUpdateData`
|
|
4
|
+
* / `buildPythPriceUpdateCalls` verbatim; this file only adapts them to the
|
|
5
|
+
* `PriceUpdateRule` port shape (fetch → build) so a future router can select
|
|
6
|
+
* across rules by `kind`. Mechanical wrap only — no on-chain/off-chain logic
|
|
7
|
+
* changes vs `../pyth.ts` / `./pyth-rule.ts`.
|
|
8
|
+
*/
|
|
9
|
+
import { type PriceUpdateRule } from "../price-update-rule.ts";
|
|
10
|
+
/** `pyth_rule`'s narrowed `RuleUpdateData.payload` shape. */
|
|
11
|
+
export interface PythCoreUpdatePayload {
|
|
12
|
+
readonly updates: Uint8Array[];
|
|
13
|
+
readonly feedIds: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare const PythCoreRule: PriceUpdateRule;
|