@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
|
@@ -22,6 +22,24 @@ export interface PythRulePackage extends BasePackageEntry {
|
|
|
22
22
|
export interface PythSponsorRulePackage extends BasePackageEntry {
|
|
23
23
|
pyth_sponsor: string;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* `pyth_lazer_rule` deployment entry — present in the deployed testnet
|
|
27
|
+
* `waterx-config` JSON. Read by `PythLazerRule` (`rules/pyth-lazer-rule.ts`):
|
|
28
|
+
* `feeds` for ticker support + integer feed-id resolution, `state` for the
|
|
29
|
+
* verify call, `published_at`/`config` for the per-ticker feed call.
|
|
30
|
+
*
|
|
31
|
+
* `enabled` mirrors the JSON field verbatim but MUST NOT be read for routing —
|
|
32
|
+
* which rule prices a ticker is decided solely by the client's `oracleSource`
|
|
33
|
+
* create option (see `OracleHost.oracleSource`), never by this flag or any
|
|
34
|
+
* other config value.
|
|
35
|
+
*/
|
|
36
|
+
export interface PythLazerRulePackage extends BasePackageEntry {
|
|
37
|
+
config: string;
|
|
38
|
+
state: string;
|
|
39
|
+
enabled?: boolean;
|
|
40
|
+
/** Oracle ticker → integer Pyth Lazer feed id (distinct id scheme from `pyth_rule`'s hex `feed_id`). */
|
|
41
|
+
feeds: Record<string, number>;
|
|
42
|
+
}
|
|
25
43
|
/** Per-ticker `constant_rule` feed entry (mirrors the `pyth_rule.feeds` shape). */
|
|
26
44
|
export interface ConstantFeedEntry {
|
|
27
45
|
/**
|
|
@@ -77,6 +95,8 @@ export interface WaterxOraclePackage extends BasePackageEntry {
|
|
|
77
95
|
export interface OraclePackages {
|
|
78
96
|
pyth_rule: PythRulePackage;
|
|
79
97
|
pyth_sponsor_rule?: PythSponsorRulePackage;
|
|
98
|
+
/** See {@link PythLazerRulePackage} — typed only, not read for routing. */
|
|
99
|
+
pyth_lazer_rule?: PythLazerRulePackage;
|
|
80
100
|
constant_rule?: WaterxConstantRulePackage;
|
|
81
101
|
supra_rule?: SupraRulePackage;
|
|
82
102
|
waterx_oracle: WaterxOraclePackage;
|
|
@@ -85,8 +105,91 @@ export interface PythInfraConfig {
|
|
|
85
105
|
state_id: string;
|
|
86
106
|
wormhole_state_id: string;
|
|
87
107
|
hermes_endpoint: string;
|
|
108
|
+
/**
|
|
109
|
+
* Pyth Pro / Lazer access token (`Authorization: Bearer …`) for
|
|
110
|
+
* `PythLazerRule`'s signed-update fetch — Lazer is auth-first, so there is
|
|
111
|
+
* no keyless default. Optional: Pyth-Core-only deployments never need it.
|
|
112
|
+
* Consumers pass it through client config (`config.pyth`); the SDK never
|
|
113
|
+
* reads `process.env`. Absent when a lazer-routed fetch runs →
|
|
114
|
+
* `LazerApiKeyMissing` is thrown at fetch time. As of the Pyth Pro
|
|
115
|
+
* migration (post-2026-08-18, per
|
|
116
|
+
* https://docs.pyth.network/price-feeds/core/upgrade) this is ALSO required
|
|
117
|
+
* for `pyth_rule`'s Hermes fetch (`fetchPriceFeedsUpdateData`) — see
|
|
118
|
+
* `fetch` below.
|
|
119
|
+
*/
|
|
120
|
+
api_key?: string;
|
|
121
|
+
/**
|
|
122
|
+
* Retry/timeout policy override for the Hermes (`fetchPriceFeedsUpdateData`)
|
|
123
|
+
* and Lazer (`PythLazerRule`) off-chain update fetches — see
|
|
124
|
+
* `fetchWithPolicy` (`./update-fetch.ts`) for the full policy (backoff,
|
|
125
|
+
* which statuses retry, Bearer attachment). Optional: both fetches default
|
|
126
|
+
* to `fetchWithPolicy`'s built-in defaults (15s timeout, 2 retries) when
|
|
127
|
+
* unset.
|
|
128
|
+
*/
|
|
129
|
+
fetch?: {
|
|
130
|
+
timeoutMs?: number;
|
|
131
|
+
retries?: number;
|
|
132
|
+
};
|
|
88
133
|
}
|
|
89
134
|
export declare const PYTH_DEFAULTS: Record<Network, PythInfraConfig>;
|
|
135
|
+
/**
|
|
136
|
+
* Which Pyth Core contract generation feeds `host.pyth` when the config JSON
|
|
137
|
+
* carries no explicit `pyth` override:
|
|
138
|
+
*
|
|
139
|
+
* - `'core'` (default) — the original contracts + keyless Hermes
|
|
140
|
+
* ({@link PYTH_DEFAULTS}).
|
|
141
|
+
* - `'pro'` — the Pro-compatible upgraded contracts + the Hermes-compatible
|
|
142
|
+
* endpoint ({@link PYTH_PRO_DEFAULTS}); pair with `pyth.api_key` after the
|
|
143
|
+
* 2026-08-18 cutover.
|
|
144
|
+
*
|
|
145
|
+
* Resolved once at client creation from the `pythGeneration` create option.
|
|
146
|
+
* Orthogonal to `oracleSource` — this flips the Pyth-Core *infra* (state ids
|
|
147
|
+
* + endpoint), not which `PriceUpdateRule` routes tickers.
|
|
148
|
+
*/
|
|
149
|
+
export type PythGeneration = "core" | "pro";
|
|
150
|
+
/**
|
|
151
|
+
* Pyth **Pro-generation** Core-compatible infra — the post-2026-08-18
|
|
152
|
+
* contracts (cutover date per
|
|
153
|
+
* https://docs.pyth.network/price-feeds/core/upgrade) from Pyth's Core-Upgrade
|
|
154
|
+
* docs
|
|
155
|
+
* (https://docs.pyth.network/price-feeds/core/upgrade/contracts, Sui section;
|
|
156
|
+
* package revs `sui-pro-compatible-contract-mainnet` /
|
|
157
|
+
* `sui-pro-compatible-contract-testnet`). Selected via the client's
|
|
158
|
+
* `pythGeneration: 'pro'` create option; `config.pyth` still overrides
|
|
159
|
+
* wholesale (see `PerpClient`). All four state ids were verified on-chain
|
|
160
|
+
* (shared `state::State` objects under the docs' upgraded package ids).
|
|
161
|
+
*
|
|
162
|
+
* Kept as a second flat map beside {@link PYTH_DEFAULTS} rather than a nested
|
|
163
|
+
* `PYTH_INFRA[network][generation]` — `PYTH_DEFAULTS` is a published export
|
|
164
|
+
* with external consumers, so the smallest honest surface is an additive
|
|
165
|
+
* sibling (same deferral note as {@link LAZER_DEFAULTS}).
|
|
166
|
+
*
|
|
167
|
+
* The Hermes-compatible endpoint (`pyth.dourolabs.app/hermes`) serves the
|
|
168
|
+
* same REST surface as `hermes.pyth.network` but requires `pyth.api_key`
|
|
169
|
+
* (`Authorization: Bearer …`) after the cutover — see
|
|
170
|
+
* {@link PythInfraConfig.api_key}.
|
|
171
|
+
*/
|
|
172
|
+
export declare const PYTH_PRO_DEFAULTS: Record<Network, PythInfraConfig>;
|
|
173
|
+
/**
|
|
174
|
+
* Pyth Lazer (Pyth Pro) external infra the `PythLazerRule` needs, by network.
|
|
175
|
+
* Mirrors {@link PYTH_DEFAULTS}: per-network constants for infrastructure Pyth
|
|
176
|
+
* operates (not part of the `waterx-config` JSON). A fuller `PYTH_INFRA`
|
|
177
|
+
* restructure is deferred — this stays a minimal map until then.
|
|
178
|
+
*
|
|
179
|
+
* - `endpoint` — Lazer HTTP API base; signed updates come from
|
|
180
|
+
* `POST /v1/latest_price` (Bearer-authenticated). The service is
|
|
181
|
+
* network-agnostic (one signed payload verifies on any chain that trusts the
|
|
182
|
+
* Lazer signers), so both networks share the production host.
|
|
183
|
+
* - `verifier_package` — the Sui package carrying
|
|
184
|
+
* `pyth_lazer::parse_and_verify_le_ecdsa_update`. Per-network: testnet is
|
|
185
|
+
* still the original v1 publish; mainnet is the v2-upgraded package (which
|
|
186
|
+
* still exposes the v1 entry `pyth_lazer_rule` binds). Values mirror the
|
|
187
|
+
* contract repo's `pyth_lazer_rule/Move.toml` published-at pins.
|
|
188
|
+
*/
|
|
189
|
+
export declare const LAZER_DEFAULTS: Record<Network, {
|
|
190
|
+
endpoint: string;
|
|
191
|
+
verifier_package: string;
|
|
192
|
+
}>;
|
|
90
193
|
/**
|
|
91
194
|
* The narrow config shape the oracle/refresh code needs. `WaterXConfig`
|
|
92
195
|
* (the perp line's full config) is assignable to this, so `PerpClient` satisfies
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* mirrors the earlier account-config hoist.
|
|
12
12
|
*/
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.PYTH_DEFAULTS = void 0;
|
|
14
|
+
exports.LAZER_DEFAULTS = exports.PYTH_PRO_DEFAULTS = exports.PYTH_DEFAULTS = void 0;
|
|
15
15
|
exports.PYTH_DEFAULTS = {
|
|
16
16
|
MAINNET: {
|
|
17
17
|
state_id: "0x1f9310238ee9298fb703c3419030b35b22bb1cc37113e3bb5007c99aec79e5b8",
|
|
@@ -24,3 +24,66 @@ exports.PYTH_DEFAULTS = {
|
|
|
24
24
|
hermes_endpoint: "https://hermes-beta.pyth.network",
|
|
25
25
|
},
|
|
26
26
|
};
|
|
27
|
+
/**
|
|
28
|
+
* Pyth **Pro-generation** Core-compatible infra — the post-2026-08-18
|
|
29
|
+
* contracts (cutover date per
|
|
30
|
+
* https://docs.pyth.network/price-feeds/core/upgrade) from Pyth's Core-Upgrade
|
|
31
|
+
* docs
|
|
32
|
+
* (https://docs.pyth.network/price-feeds/core/upgrade/contracts, Sui section;
|
|
33
|
+
* package revs `sui-pro-compatible-contract-mainnet` /
|
|
34
|
+
* `sui-pro-compatible-contract-testnet`). Selected via the client's
|
|
35
|
+
* `pythGeneration: 'pro'` create option; `config.pyth` still overrides
|
|
36
|
+
* wholesale (see `PerpClient`). All four state ids were verified on-chain
|
|
37
|
+
* (shared `state::State` objects under the docs' upgraded package ids).
|
|
38
|
+
*
|
|
39
|
+
* Kept as a second flat map beside {@link PYTH_DEFAULTS} rather than a nested
|
|
40
|
+
* `PYTH_INFRA[network][generation]` — `PYTH_DEFAULTS` is a published export
|
|
41
|
+
* with external consumers, so the smallest honest surface is an additive
|
|
42
|
+
* sibling (same deferral note as {@link LAZER_DEFAULTS}).
|
|
43
|
+
*
|
|
44
|
+
* The Hermes-compatible endpoint (`pyth.dourolabs.app/hermes`) serves the
|
|
45
|
+
* same REST surface as `hermes.pyth.network` but requires `pyth.api_key`
|
|
46
|
+
* (`Authorization: Bearer …`) after the cutover — see
|
|
47
|
+
* {@link PythInfraConfig.api_key}.
|
|
48
|
+
*/
|
|
49
|
+
exports.PYTH_PRO_DEFAULTS = {
|
|
50
|
+
MAINNET: {
|
|
51
|
+
state_id: "0x03719fae774ddab3cfcaa53bbc046f0cbe21410019b6280811bf3f9f4b05839d",
|
|
52
|
+
wormhole_state_id: "0xdbca52b9fb4f712e25f61f974586d93ac541bcf8389564f0323bb07215168b5c",
|
|
53
|
+
hermes_endpoint: "https://pyth.dourolabs.app/hermes",
|
|
54
|
+
},
|
|
55
|
+
TESTNET: {
|
|
56
|
+
state_id: "0x3c48fe392912de6c18087a2b3f5fdbfbfdb4598e180947feff1f12f8e9ea073e",
|
|
57
|
+
wormhole_state_id: "0x750da8e6d16b6a363a39fe2eaa8295ac224a1e6fce4e47b58845e2e8746164f0",
|
|
58
|
+
hermes_endpoint: "https://pyth.dourolabs.app/hermes",
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// Pyth Lazer — external infra, defaults by network
|
|
63
|
+
// ============================================================================
|
|
64
|
+
/**
|
|
65
|
+
* Pyth Lazer (Pyth Pro) external infra the `PythLazerRule` needs, by network.
|
|
66
|
+
* Mirrors {@link PYTH_DEFAULTS}: per-network constants for infrastructure Pyth
|
|
67
|
+
* operates (not part of the `waterx-config` JSON). A fuller `PYTH_INFRA`
|
|
68
|
+
* restructure is deferred — this stays a minimal map until then.
|
|
69
|
+
*
|
|
70
|
+
* - `endpoint` — Lazer HTTP API base; signed updates come from
|
|
71
|
+
* `POST /v1/latest_price` (Bearer-authenticated). The service is
|
|
72
|
+
* network-agnostic (one signed payload verifies on any chain that trusts the
|
|
73
|
+
* Lazer signers), so both networks share the production host.
|
|
74
|
+
* - `verifier_package` — the Sui package carrying
|
|
75
|
+
* `pyth_lazer::parse_and_verify_le_ecdsa_update`. Per-network: testnet is
|
|
76
|
+
* still the original v1 publish; mainnet is the v2-upgraded package (which
|
|
77
|
+
* still exposes the v1 entry `pyth_lazer_rule` binds). Values mirror the
|
|
78
|
+
* contract repo's `pyth_lazer_rule/Move.toml` published-at pins.
|
|
79
|
+
*/
|
|
80
|
+
exports.LAZER_DEFAULTS = {
|
|
81
|
+
MAINNET: {
|
|
82
|
+
endpoint: "https://pyth-lazer.dourolabs.app",
|
|
83
|
+
verifier_package: "0xefbfd064480777699fd9c557a5804d72ace7bc82661fdc8d1f1a44ea6d92ee10",
|
|
84
|
+
},
|
|
85
|
+
TESTNET: {
|
|
86
|
+
endpoint: "https://pyth-lazer.dourolabs.app",
|
|
87
|
+
verifier_package: "0xf5bd2141967507050a91b58de3d95e77c432cd90d1799ee46effc27430a68c21",
|
|
88
|
+
},
|
|
89
|
+
};
|
|
@@ -8,14 +8,27 @@
|
|
|
8
8
|
* clause, and a future `PredictClient` (or a test double) can too.
|
|
9
9
|
*/
|
|
10
10
|
import type { SuiGrpcClient } from "@mysten/sui/grpc";
|
|
11
|
+
import type { Network } from "../constants.ts";
|
|
11
12
|
import type { OracleConfig, PythInfraConfig } from "./config.ts";
|
|
13
|
+
import type { OracleSource } from "./price-update-rule.ts";
|
|
12
14
|
export interface OracleHost {
|
|
15
|
+
/** Sui network this client targets — selects per-network external-infra defaults (e.g. `LAZER_DEFAULTS`). */
|
|
16
|
+
readonly network: Network;
|
|
13
17
|
/** Oracle slice of the canonical `waterx-config` JSON (rule packages + per-ticker feeds). */
|
|
14
18
|
readonly config: OracleConfig;
|
|
15
19
|
/** External Pyth/Wormhole/Hermes infra (network default, overridable via config). */
|
|
16
20
|
readonly pyth: PythInfraConfig;
|
|
17
21
|
/** gRPC client for the on-chain reads the Pyth update path needs. */
|
|
18
22
|
readonly grpcClient: SuiGrpcClient;
|
|
23
|
+
/**
|
|
24
|
+
* Client-selected oracle rule source for `refreshOraclePrices`'s on-chain
|
|
25
|
+
* update leg — resolved at client creation from the `oracleSource` create
|
|
26
|
+
* option (default `'pyth_rule'`). Routing is driven by this value ALONE:
|
|
27
|
+
* never by a config JSON `enabled` flag (e.g. a future `pyth_lazer_rule.enabled`)
|
|
28
|
+
* and never by `process.env` — the SDK never reads it; consumers (BE/FE) wire
|
|
29
|
+
* this option from their own env var.
|
|
30
|
+
*/
|
|
31
|
+
readonly oracleSource: OracleSource;
|
|
19
32
|
/** True when `ticker` is priced by `constant_rule`. */
|
|
20
33
|
isConstantTicker(ticker: string): boolean;
|
|
21
34
|
/** The `supra_rule` config when deployed, enabled, and fully wired; else `undefined`. */
|
|
@@ -2,15 +2,29 @@
|
|
|
2
2
|
* Oracle module — the single source of truth for price freshness.
|
|
3
3
|
*
|
|
4
4
|
* Layering (no cross-imports between siblings except via `aggregate.ts`):
|
|
5
|
-
* - `host.ts`
|
|
6
|
-
* - `
|
|
7
|
-
*
|
|
8
|
-
* - `
|
|
5
|
+
* - `host.ts` — `OracleHost`, the narrow client slice this module reads.
|
|
6
|
+
* - `update-fetch.ts` — `fetchWithPolicy`, the shared retry/timeout/Bearer resilience
|
|
7
|
+
* wrapper every off-chain oracle (and config) fetch goes through.
|
|
8
|
+
* - `pyth.ts` — Pyth as a price source: Hermes REST + on-chain update PTB.
|
|
9
|
+
* - `price-update-rule.ts`— `PriceUpdateRule`, the fetch/build strategy port a rule
|
|
10
|
+
* implements; `rule-registry.ts` + `aggregate.ts` wire
|
|
11
|
+
* routing across rules.
|
|
12
|
+
* - `rules/*` — one file per oracle rule (pyth / supra / constant / sponsor).
|
|
13
|
+
* - `aggregate.ts` — the orchestrator that feeds rules into a collector + aggregates.
|
|
9
14
|
*
|
|
10
15
|
* `pyth.ts` deliberately imports NO rule package — Pyth-the-source and the
|
|
11
16
|
* rules that consume it are separate concerns.
|
|
12
17
|
*/
|
|
13
18
|
export type { OracleHost } from "./host.ts";
|
|
14
|
-
export {
|
|
19
|
+
export { FetchPolicyError } from "./update-fetch.ts";
|
|
20
|
+
export type { FetchPolicy } from "./update-fetch.ts";
|
|
21
|
+
export { PythCache, fetchPriceFeedsUpdateData, buildPythPriceUpdateCalls, updatePythPrices, OracleFeeSourceUnavailableError, } from "./pyth.ts";
|
|
22
|
+
export type { OracleFeeSource } from "./pyth.ts";
|
|
23
|
+
export type { PriceUpdateRule, PriceUpdateRuleKind, RuleUpdateData, RuleUpdateHandle, BuildUpdateOpts, OracleSource, UpdateDataProvider, } from "./price-update-rule.ts";
|
|
24
|
+
export { PythCoreRule } from "./rules/pyth-core-rule.ts";
|
|
25
|
+
export type { PythCoreUpdatePayload } from "./rules/pyth-core-rule.ts";
|
|
26
|
+
export { PythLazerRule, LazerApiKeyMissingError } from "./rules/pyth-lazer-rule.ts";
|
|
27
|
+
export type { PythLazerUpdatePayload } from "./rules/pyth-lazer-rule.ts";
|
|
28
|
+
export { OracleSourceNotImplementedError } from "./rule-registry.ts";
|
|
15
29
|
export { aggregateTicker, aggregateTickerWithPyth, aggregateTickerWithConstant, refreshOraclePrices, } from "./aggregate.ts";
|
|
16
30
|
export { openPythSponsorFund, reimbursePythSponsor } from "./rules/sponsor.ts";
|
|
@@ -3,22 +3,56 @@
|
|
|
3
3
|
* Oracle module — the single source of truth for price freshness.
|
|
4
4
|
*
|
|
5
5
|
* Layering (no cross-imports between siblings except via `aggregate.ts`):
|
|
6
|
-
* - `host.ts`
|
|
7
|
-
* - `
|
|
8
|
-
*
|
|
9
|
-
* - `
|
|
6
|
+
* - `host.ts` — `OracleHost`, the narrow client slice this module reads.
|
|
7
|
+
* - `update-fetch.ts` — `fetchWithPolicy`, the shared retry/timeout/Bearer resilience
|
|
8
|
+
* wrapper every off-chain oracle (and config) fetch goes through.
|
|
9
|
+
* - `pyth.ts` — Pyth as a price source: Hermes REST + on-chain update PTB.
|
|
10
|
+
* - `price-update-rule.ts`— `PriceUpdateRule`, the fetch/build strategy port a rule
|
|
11
|
+
* implements; `rule-registry.ts` + `aggregate.ts` wire
|
|
12
|
+
* routing across rules.
|
|
13
|
+
* - `rules/*` — one file per oracle rule (pyth / supra / constant / sponsor).
|
|
14
|
+
* - `aggregate.ts` — the orchestrator that feeds rules into a collector + aggregates.
|
|
10
15
|
*
|
|
11
16
|
* `pyth.ts` deliberately imports NO rule package — Pyth-the-source and the
|
|
12
17
|
* rules that consume it are separate concerns.
|
|
13
18
|
*/
|
|
14
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.reimbursePythSponsor = exports.openPythSponsorFund = exports.refreshOraclePrices = exports.aggregateTickerWithConstant = exports.aggregateTickerWithPyth = exports.aggregateTicker = exports.updatePythPrices = exports.buildPythPriceUpdateCalls = exports.fetchPriceFeedsUpdateData = exports.PythCache = void 0;
|
|
16
|
-
//
|
|
20
|
+
exports.reimbursePythSponsor = exports.openPythSponsorFund = exports.refreshOraclePrices = exports.aggregateTickerWithConstant = exports.aggregateTickerWithPyth = exports.aggregateTicker = exports.OracleSourceNotImplementedError = exports.LazerApiKeyMissingError = exports.PythLazerRule = exports.PythCoreRule = exports.OracleFeeSourceUnavailableError = exports.updatePythPrices = exports.buildPythPriceUpdateCalls = exports.fetchPriceFeedsUpdateData = exports.PythCache = exports.FetchPolicyError = void 0;
|
|
21
|
+
// Shared fetch resilience wrapper — `FetchPolicyError` is re-exported (not
|
|
22
|
+
// just the type) so a consumer (e.g. a BE prefetch cache) can `instanceof`
|
|
23
|
+
// it off the failure `fetchPriceFeedsUpdateData` / `PythLazerRule` /
|
|
24
|
+
// `loadConfig` surface, without a deep import of `./update-fetch.ts`.
|
|
25
|
+
// `fetchWithPolicy` itself stays module-internal (no external caller yet).
|
|
26
|
+
var update_fetch_ts_1 = require("./update-fetch.js");
|
|
27
|
+
Object.defineProperty(exports, "FetchPolicyError", { enumerable: true, get: function () { return update_fetch_ts_1.FetchPolicyError; } });
|
|
28
|
+
// Pyth source — `OracleFeeSourceUnavailableError` is re-exported (not just
|
|
29
|
+
// the type) for the same `instanceof` reason as `FetchPolicyError` above: a
|
|
30
|
+
// consumer of `buildPythPriceUpdateCalls` / `updatePythPrices` /
|
|
31
|
+
// `refreshOraclePrices` can branch on the fee-source failure directly.
|
|
17
32
|
var pyth_ts_1 = require("./pyth.js");
|
|
18
33
|
Object.defineProperty(exports, "PythCache", { enumerable: true, get: function () { return pyth_ts_1.PythCache; } });
|
|
19
34
|
Object.defineProperty(exports, "fetchPriceFeedsUpdateData", { enumerable: true, get: function () { return pyth_ts_1.fetchPriceFeedsUpdateData; } });
|
|
20
35
|
Object.defineProperty(exports, "buildPythPriceUpdateCalls", { enumerable: true, get: function () { return pyth_ts_1.buildPythPriceUpdateCalls; } });
|
|
21
36
|
Object.defineProperty(exports, "updatePythPrices", { enumerable: true, get: function () { return pyth_ts_1.updatePythPrices; } });
|
|
37
|
+
Object.defineProperty(exports, "OracleFeeSourceUnavailableError", { enumerable: true, get: function () { return pyth_ts_1.OracleFeeSourceUnavailableError; } });
|
|
38
|
+
// Pyth Core rule (PriceUpdateRule wrapper over the Pyth source above)
|
|
39
|
+
var pyth_core_rule_ts_1 = require("./rules/pyth-core-rule.js");
|
|
40
|
+
Object.defineProperty(exports, "PythCoreRule", { enumerable: true, get: function () { return pyth_core_rule_ts_1.PythCoreRule; } });
|
|
41
|
+
// Pyth Lazer rule (signed-update generation; `feedLazerRule` stays internal to `aggregate.ts`)
|
|
42
|
+
// `LazerApiKeyMissingError` is re-exported (not just the type) for the same
|
|
43
|
+
// `instanceof` reason as `OracleFeeSourceUnavailableError` above.
|
|
44
|
+
var pyth_lazer_rule_ts_1 = require("./rules/pyth-lazer-rule.js");
|
|
45
|
+
Object.defineProperty(exports, "PythLazerRule", { enumerable: true, get: function () { return pyth_lazer_rule_ts_1.PythLazerRule; } });
|
|
46
|
+
Object.defineProperty(exports, "LazerApiKeyMissingError", { enumerable: true, get: function () { return pyth_lazer_rule_ts_1.LazerApiKeyMissingError; } });
|
|
47
|
+
// `resolveOracleRule` (rule-registry.ts) is NOT re-exported here — it has one
|
|
48
|
+
// production consumer (`refreshOraclePrices` below) and no external caller;
|
|
49
|
+
// per repo convention ("no unused exports") it stays module-internal. Tests
|
|
50
|
+
// import it directly from `./rule-registry.ts`. `OracleSourceNotImplementedError`
|
|
51
|
+
// IS re-exported (the same `instanceof` reason as `OracleFeeSourceUnavailableError`
|
|
52
|
+
// above) — a consumer of `refreshOraclePrices` can catch it without importing
|
|
53
|
+
// `resolveOracleRule` itself.
|
|
54
|
+
var rule_registry_ts_1 = require("./rule-registry.js");
|
|
55
|
+
Object.defineProperty(exports, "OracleSourceNotImplementedError", { enumerable: true, get: function () { return rule_registry_ts_1.OracleSourceNotImplementedError; } });
|
|
22
56
|
// Aggregation orchestrator
|
|
23
57
|
var aggregate_ts_1 = require("./aggregate.js");
|
|
24
58
|
Object.defineProperty(exports, "aggregateTicker", { enumerable: true, get: function () { return aggregate_ts_1.aggregateTicker; } });
|
|
@@ -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,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `PriceUpdateRule` — the strategy port for one oracle rule generation: fetch
|
|
4
|
+
* its off-chain update payload and emit the PTB calls that verify/push that
|
|
5
|
+
* update on-chain (e.g. Pyth's wormhole-verify + price-feed-update block).
|
|
6
|
+
* Feeding the refreshed price into an oracle `PriceCollector` is a separate
|
|
7
|
+
* step that stays in `aggregate.ts` — this port covers fetch + verify/push
|
|
8
|
+
* only (`buildUpdateCalls` may hand the feed step a PTB value via
|
|
9
|
+
* {@link RuleUpdateHandle}). Implementations: `PythCoreRule` (Hermes VAA) and
|
|
10
|
+
* `PythLazerRule` (Lazer signed updates), with `WaterxRule` (ed25519) to
|
|
11
|
+
* follow. `ConstantRule` and `SupraRule` do NOT implement this port — they
|
|
12
|
+
* remain plain collector-feed helpers wired directly into `aggregate.ts`.
|
|
13
|
+
*
|
|
14
|
+
* This file defines the port only — routing IS wired: `aggregate.ts`'s
|
|
15
|
+
* `refreshOraclePrices` selects the concrete rule per `host.oracleSource` via
|
|
16
|
+
* `rule-registry.ts`, then drives fetch + `buildUpdateCalls` through this
|
|
17
|
+
* port; `aggregate.ts` stays the sole orchestrator.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.assertRuleUpdateData = assertRuleUpdateData;
|
|
21
|
+
/**
|
|
22
|
+
* Shared null → kind → shape guard ladder for a `PriceUpdateRule.buildUpdateCalls`
|
|
23
|
+
* payload — every rule's `buildUpdateCalls` needs the exact same three checks,
|
|
24
|
+
* in the exact same order, before it can trust `data.payload`:
|
|
25
|
+
*
|
|
26
|
+
* 1. `data === null` passes straight through as `null` — the no-op case (an
|
|
27
|
+
* empty ticker list upstream produced nothing to build).
|
|
28
|
+
* 2. `data.kind !== kind` throws BEFORE the shape check runs. This order is
|
|
29
|
+
* load-bearing, not stylistic: two rules' payloads can share an identical
|
|
30
|
+
* shape (e.g. Pyth Core's `{ updates, feedIds }` also satisfies a
|
|
31
|
+
* hypothetical same-shaped rule), so checking shape first would let a
|
|
32
|
+
* wrong-kind payload silently pass as this rule's own.
|
|
33
|
+
* 3. `!isShape(data.payload)` throws for a same-`kind` payload whose shape
|
|
34
|
+
* doesn't match this rule's own (e.g. a hand-built test double).
|
|
35
|
+
*
|
|
36
|
+
* Returns `data.payload` narrowed to `T` once both checks pass.
|
|
37
|
+
*
|
|
38
|
+
* @param data - The `RuleUpdateData` handed to `buildUpdateCalls`.
|
|
39
|
+
* @param kind - This rule's own {@link PriceUpdateRuleKind} — the only `kind`
|
|
40
|
+
* `data` may carry past step 2.
|
|
41
|
+
* @param isShape - Type predicate narrowing `data.payload` to `T`.
|
|
42
|
+
* @param shapeDescription - Human-readable shape, quoted verbatim into the
|
|
43
|
+
* shape-mismatch error (e.g. `"{ updates: Uint8Array[]; feedIds: string[] }"`).
|
|
44
|
+
*/
|
|
45
|
+
function assertRuleUpdateData(data, kind, isShape, shapeDescription) {
|
|
46
|
+
if (!data)
|
|
47
|
+
return null;
|
|
48
|
+
if (data.kind !== kind) {
|
|
49
|
+
throw new Error(`assertRuleUpdateData: received a payload of kind '${data.kind}', expected '${kind}'`);
|
|
50
|
+
}
|
|
51
|
+
if (!isShape(data.payload)) {
|
|
52
|
+
throw new Error(`assertRuleUpdateData: received a '${kind}' payload with an unexpected shape ` +
|
|
53
|
+
`(expected ${shapeDescription})`);
|
|
54
|
+
}
|
|
55
|
+
return data.payload;
|
|
56
|
+
}
|