@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,285 @@
1
+ "use strict";
2
+ /**
3
+ * `fetchWithPolicy` — resilience wrapper around `fetch` for the oracle money
4
+ * path's off-chain update-data fetches. Every order/position/collateral
5
+ * tx-build depends on one of these REST calls landing (Hermes VAA for
6
+ * `pyth_rule`, Lazer signed updates for `pyth_lazer_rule`); a bare `fetch`
7
+ * with a single attempt and no retry means one Hermes 429/5xx or timeout
8
+ * fails every trade. `fetchPriceFeedsUpdateData` (`./pyth.ts`),
9
+ * `PythLazerRule.fetchUpdateData`'s Lazer POST (`./rules/pyth-lazer-rule.ts`),
10
+ * and `loadConfig` (`../perp/config.ts`) all delegate here instead of calling
11
+ * `fetch` directly — this is the ONE place a retry/timeout/auth policy is
12
+ * implemented for these fetches.
13
+ *
14
+ * Policy semantics:
15
+ * - Bearer auth is attached iff `policy.apiKey` is a non-empty string —
16
+ * absent/empty is byte-identical to today's keyless request (no
17
+ * `Authorization` header at all). This is the Phase-0 invariant of the
18
+ * Pyth Pro migration: existing keyless deployments see no behavior change.
19
+ * - Retries on network errors, HTTP 429, and HTTP 5xx, with exponential
20
+ * backoff (`retryDelayMs * 2^attempt`, capped at `MAX_BACKOFF_MS`). A 429
21
+ * carrying a numeric `Retry-After` header uses the SERVER'S delay instead,
22
+ * when it fits under the same cap — a longer ask degrades to normal
23
+ * backoff rather than stalling a money-path build for tens of seconds.
24
+ * Other
25
+ * 4xx statuses (401/400/403/404/…) are NOT retried — auth/bad-request
26
+ * failures are deterministic, so that `Response` (`ok: false`) is handed
27
+ * back on the first attempt for the caller to format its own
28
+ * domain-specific error, exactly as it did before this wrapper existed.
29
+ * - `init.body`, if set, MUST be replayable across attempts — a retry
30
+ * re-sends the SAME `init` object to `fetch` on every attempt. A string /
31
+ * `URLSearchParams` / BCS-serialized `Uint8Array` body (every caller today)
32
+ * is fine; a one-shot `ReadableStream` body would not survive a second
33
+ * attempt and must not be passed through this function.
34
+ * - Each attempt gets its own `AbortSignal.timeout(policy.timeoutMs)`
35
+ * combined with whichever of `init.signal` / the `externalSignal` param are
36
+ * set — ALL of them can end the whole policy (not just the in-flight
37
+ * attempt), including a queued backoff sleep, via `AbortSignal.any`
38
+ * (runtime floor: Node ≥20.3 / any modern browser — matches this repo's
39
+ * `target: ES2023` + `lib: ["dom", "esnext"]`).
40
+ * - Exhausting retries with no successful/non-retryable response (i.e. every
41
+ * attempt was a network error, or the final attempt was still a retryable
42
+ * HTTP failure) throws a {@link FetchPolicyError} naming the target's
43
+ * `host + pathname` (never the query string — feed ids are off-chain
44
+ * noise, not diagnostic value), the attempt count, and whichever of
45
+ * `status` (a retryable HTTP failure — plus a truncated response-body
46
+ * snippet, when the final attempt's response carried one) or `cause` (a
47
+ * network error) the final attempt produced. An INTERMEDIATE (non-final)
48
+ * retryable response's body is discarded via `response.body?.cancel()`
49
+ * instead of read, so a doomed-to-retry response doesn't pin its
50
+ * connection's socket open for no reason.
51
+ * - Retry worst case: with the defaults (15s timeout × 3 attempts + ~0.75s of
52
+ * backoff between them) a FULL outage takes up to ~46s to surface as a
53
+ * `FetchPolicyError`, vs ~15s pre-3.2.0's single bare-`fetch` attempt.
54
+ * Tunable per client via the `pythFetch` create option (`{timeoutMs,retries}`).
55
+ */
56
+ Object.defineProperty(exports, "__esModule", { value: true });
57
+ exports.FetchPolicyError = void 0;
58
+ exports.rethrowExhaustedFetch = rethrowExhaustedFetch;
59
+ exports.trimTrailingSlashes = trimTrailingSlashes;
60
+ exports.joinEndpointPath = joinEndpointPath;
61
+ exports.fetchWithPolicy = fetchWithPolicy;
62
+ const DEFAULT_TIMEOUT_MS = 15_000;
63
+ const DEFAULT_RETRIES = 2;
64
+ const DEFAULT_RETRY_DELAY_MS = 250;
65
+ const MAX_BACKOFF_MS = 2_000;
66
+ /** Final-error diagnostic only — not a protocol limit. */
67
+ const MAX_BODY_SNIPPET_LENGTH = 200;
68
+ /** Thrown by {@link fetchWithPolicy} when every attempt failed. */
69
+ class FetchPolicyError extends Error {
70
+ /** HTTP status of the final attempt, when it got a (retryable-but-failing) response. */
71
+ status;
72
+ /** Truncated (~200 char) body of the final attempt's response, when one was readable. */
73
+ bodySnippet;
74
+ /** Total attempts made (first try + retries actually used). */
75
+ attempts;
76
+ constructor(message, opts) {
77
+ super(message, opts.cause !== undefined ? { cause: opts.cause } : undefined);
78
+ this.name = "FetchPolicyError";
79
+ this.status = opts.status;
80
+ this.bodySnippet = opts.bodySnippet;
81
+ this.attempts = opts.attempts;
82
+ }
83
+ }
84
+ exports.FetchPolicyError = FetchPolicyError;
85
+ /**
86
+ * Rethrow a `catch`-ed {@link fetchWithPolicy} failure. When it is a
87
+ * status-carrying `FetchPolicyError` — a retryable status (429/5xx) that never
88
+ * recovered — throw a new Error `${describe(err)} (retries exhausted after N
89
+ * attempts)` with the original as `cause`; otherwise (a network-level
90
+ * exhaustion with no status, or any non-`FetchPolicyError`) rethrow it verbatim,
91
+ * since there is no domain reframing to add. `describe` builds the
92
+ * status-bearing prefix so each caller keeps its own message shape (the e2e
93
+ * transient detector keys off those prefixes) while the guard, the `retries
94
+ * exhausted` suffix, and the `cause` wrapping live in one place. `: never` so a
95
+ * caller's `catch` block is understood not to fall through.
96
+ */
97
+ function rethrowExhaustedFetch(err, describe) {
98
+ if (err instanceof FetchPolicyError && err.status !== undefined) {
99
+ throw new Error(`${describe(err)} (retries exhausted after ${err.attempts} attempts)`, {
100
+ cause: err,
101
+ });
102
+ }
103
+ throw err;
104
+ }
105
+ /**
106
+ * Join an API `path` onto an `endpoint` PRESERVING the endpoint's own base
107
+ * path. `new URL(path, endpoint)` is the footgun this replaces: a
108
+ * leading-slash path is *absolute* and silently discards the endpoint's path
109
+ * — harmless for a bare-origin endpoint (`https://hermes.pyth.network`) but
110
+ * it dropped the `/hermes` prefix of the Pyth Pro compat endpoint and 404'd
111
+ * every feed (see `fetchPriceFeedsUpdateData`). Every oracle fetch that
112
+ * targets `<endpoint><fixed path>` must build its URL here.
113
+ */
114
+ /** One canonical trailing-slash trim — `joinEndpointPath` (URL building) and
115
+ * `pyth.ts`'s `memoKey` (endpoint identity) must never drift apart on it. */
116
+ function trimTrailingSlashes(endpoint) {
117
+ return endpoint.replace(/\/+$/, "");
118
+ }
119
+ function joinEndpointPath(endpoint, path) {
120
+ return new URL(`${trimTrailingSlashes(endpoint)}/${path.replace(/^\/+/, "")}`);
121
+ }
122
+ function isRetryableStatus(status) {
123
+ return status === 429 || status >= 500;
124
+ }
125
+ function backoffMs(retryDelayMs, attempt) {
126
+ return Math.min(retryDelayMs * 2 ** attempt, MAX_BACKOFF_MS);
127
+ }
128
+ /**
129
+ * The server's own `Retry-After` (numeric-seconds form only), when present
130
+ * and within `MAX_BACKOFF_MS` — `undefined` otherwise (absent, HTTP-date
131
+ * form, zero/garbage, or an ask too long to honor inside a build path).
132
+ */
133
+ function retryAfterMs(response) {
134
+ // Optional-chained: minimal test doubles (and some fetch shims) carry no
135
+ // `headers` — a missing header must read as "no hint", never throw.
136
+ const ms = Number(response.headers?.get?.("retry-after")) * 1_000;
137
+ return ms > 0 && ms <= MAX_BACKOFF_MS ? ms : undefined;
138
+ }
139
+ /** `host + pathname` only — never the query string (feed ids are noise, not diagnostic). */
140
+ function describeTarget(url) {
141
+ const parsed = new URL(url);
142
+ return `${parsed.host}${parsed.pathname}`;
143
+ }
144
+ function causeMessage(cause) {
145
+ return cause instanceof Error ? cause.message : String(cause);
146
+ }
147
+ /**
148
+ * Best-effort, truncated body text for the FINAL failed attempt's error
149
+ * message — restores the diagnostic a plain `if (!res.ok) throw new
150
+ * Error(...res.status, await res.text())` had before this wrapper existed.
151
+ * Swallows any read failure (missing/consumed body, a test double with no
152
+ * `.text()`, …) — a diagnostic snippet is never worth failing the request
153
+ * differently than the status/cause already dictate.
154
+ */
155
+ async function readBodySnippet(response) {
156
+ try {
157
+ const text = await response.text();
158
+ return text.length > MAX_BODY_SNIPPET_LENGTH
159
+ ? `${text.slice(0, MAX_BODY_SNIPPET_LENGTH)}…`
160
+ : text;
161
+ }
162
+ catch {
163
+ return "";
164
+ }
165
+ }
166
+ /** Resolves after `ms`, or rejects immediately (with `signal.reason`) if `signal` fires first. */
167
+ function sleep(ms, signal) {
168
+ if (!signal)
169
+ return new Promise((resolve) => setTimeout(resolve, ms));
170
+ const abortSignal = signal;
171
+ if (abortSignal.aborted)
172
+ return Promise.reject(abortSignal.reason);
173
+ return new Promise((resolve, reject) => {
174
+ const onAbort = () => {
175
+ clearTimeout(timer);
176
+ reject(abortSignal.reason);
177
+ };
178
+ const timer = setTimeout(() => {
179
+ abortSignal.removeEventListener("abort", onAbort);
180
+ resolve();
181
+ }, ms);
182
+ abortSignal.addEventListener("abort", onAbort, { once: true });
183
+ });
184
+ }
185
+ /** `undefined` if none are set; the lone signal if exactly one is; `AbortSignal.any(...)` otherwise. */
186
+ function combineSignals(signals) {
187
+ const present = signals.filter((s) => s != null);
188
+ if (present.length === 0)
189
+ return undefined;
190
+ if (present.length === 1)
191
+ return present[0];
192
+ return AbortSignal.any(present);
193
+ }
194
+ /**
195
+ * Merge `Authorization: Bearer <apiKey>` into `base` iff `apiKey` is
196
+ * non-empty; `base` is returned UNTOUCHED otherwise (byte-identical to
197
+ * today's keyless requests — no header, whatever shape the caller passed).
198
+ * Always builds a plain object (never a `Headers` instance) so callers that
199
+ * assert on the exact `init` they handed `fetch` keep working unchanged.
200
+ */
201
+ function withBearerAuth(base, apiKey) {
202
+ if (!apiKey)
203
+ return base;
204
+ const merged = {};
205
+ if (base instanceof Headers) {
206
+ base.forEach((value, key) => {
207
+ merged[key] = value;
208
+ });
209
+ }
210
+ else if (Array.isArray(base)) {
211
+ for (const [key, value] of base)
212
+ merged[key] = value;
213
+ }
214
+ else if (base) {
215
+ Object.assign(merged, base);
216
+ }
217
+ merged.Authorization = `Bearer ${apiKey}`;
218
+ return merged;
219
+ }
220
+ /**
221
+ * `fetch` with per-attempt timeout, bounded retry + backoff, and optional
222
+ * Bearer auth. See the module header for the full policy. Both `init.signal`
223
+ * (if the caller set one) AND the separate `externalSignal` param cancel the
224
+ * WHOLE policy — in-flight attempts AND queued backoff sleeps — not just a
225
+ * single attempt.
226
+ */
227
+ async function fetchWithPolicy(url, init = {}, policy = {}, externalSignal) {
228
+ const timeoutMs = policy.timeoutMs ?? DEFAULT_TIMEOUT_MS;
229
+ const retries = policy.retries ?? DEFAULT_RETRIES;
230
+ const retryDelayMs = policy.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
231
+ const doFetch = policy.fetchImpl ?? fetch;
232
+ const headers = withBearerAuth(init.headers, policy.apiKey);
233
+ // init.signal is folded in here (not spread through per-attempt below) so
234
+ // it gates retries/backoff exactly like externalSignal, instead of being
235
+ // silently dropped by the `{ ...init, signal }` override per attempt.
236
+ const combinedExternalSignal = combineSignals([externalSignal, init.signal]);
237
+ for (let attempt = 0; attempt <= retries; attempt++) {
238
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
239
+ const signal = combinedExternalSignal
240
+ ? AbortSignal.any([timeoutSignal, combinedExternalSignal])
241
+ : timeoutSignal;
242
+ let status;
243
+ let bodySnippet;
244
+ let cause;
245
+ let serverRetryDelay;
246
+ try {
247
+ const response = await doFetch(url, { ...init, headers, signal });
248
+ if (response.ok || !isRetryableStatus(response.status))
249
+ return response;
250
+ status = response.status;
251
+ serverRetryDelay = retryAfterMs(response);
252
+ if (attempt === retries) {
253
+ bodySnippet = await readBodySnippet(response);
254
+ }
255
+ else {
256
+ // Doomed to be retried — discard rather than read, so this
257
+ // response's connection/socket isn't held open for a body nobody
258
+ // will consume. `.catch()` is mandatory here: an errored stream's
259
+ // `cancel()` returns a REJECTED promise, and a bare `void` on that
260
+ // is an unhandled rejection — process-fatal in Node — triggered by
261
+ // exactly the degraded-upstream condition this wrapper exists to
262
+ // survive.
263
+ void response.body?.cancel().catch(() => { });
264
+ }
265
+ }
266
+ catch (err) {
267
+ if (combinedExternalSignal?.aborted)
268
+ throw err;
269
+ cause = err;
270
+ }
271
+ if (attempt === retries) {
272
+ const statusDetail = status !== undefined
273
+ ? `last status ${status}${bodySnippet ? ` — ${bodySnippet}` : ""}`
274
+ : undefined;
275
+ const detail = statusDetail ?? causeMessage(cause);
276
+ throw new FetchPolicyError(`fetchWithPolicy: ${describeTarget(url)} failed after ${attempt + 1} attempt(s) — ${detail}`, { status, bodySnippet, cause, attempts: attempt + 1 });
277
+ }
278
+ await sleep(serverRetryDelay ?? backoffMs(retryDelayMs, attempt), combinedExternalSignal);
279
+ }
280
+ // Unreachable: the loop above always returns or throws on its final
281
+ // (attempt === retries) iteration — this satisfies the compiler only.
282
+ throw new FetchPolicyError(`fetchWithPolicy: ${describeTarget(url)} exhausted retries`, {
283
+ attempts: retries + 1,
284
+ });
285
+ }
@@ -11,24 +11,59 @@
11
11
  * {@link PerpConfigView}. This class is just the wiring + factory between them.
12
12
  */
13
13
  import { BaseLineClient } from "../base-client.ts";
14
- import { type LoadConfigOptions, type PythInfraConfig, type WaterXConfig, type WormholeInfraConfig } from "./config.ts";
14
+ import type { OracleSource } from "../oracle/price-update-rule.ts";
15
+ import { type LoadConfigOptions, type PythFetchPolicy, type PythInfraConfig, type WaterXConfig, type WormholeInfraConfig } from "./config.ts";
15
16
  import type { Network } from "./constants.ts";
16
17
  export interface CreateClientOptions extends LoadConfigOptions {
17
18
  grpcUrl?: string;
19
+ /**
20
+ * Which oracle price-update source drives `refreshOraclePrices`. Each source
21
+ * is self-contained (own infra + config) with NO cross-source fallback:
22
+ *
23
+ * - `'pyth_rule'` (default) — Pyth Core `pyth_rule` updates (Hermes VAA +
24
+ * per-feed update fees), Core state + keyless Core Hermes.
25
+ * - `'pyth_lazer_rule'` — Pyth Lazer signed updates (ONE `leEcdsa` verify
26
+ * per PTB, no per-feed fees); needs `packages.pyth_lazer_rule` with feeds
27
+ * and a `pythApiKey` (Lazer is auth-first).
28
+ *
29
+ * A source-neutral name on purpose — a future source need not be Pyth.
30
+ * Selecting a source whose feed for a requested ticker is absent is NOT an
31
+ * error at client creation: it fails at tx-build time for exactly those
32
+ * tickers (see `refreshOraclePrices`). The Pyth Core infra is fixed per
33
+ * network by `PYTH_DEFAULTS` and is not deployment-overridable.
34
+ */
35
+ oracleSource?: OracleSource;
36
+ /**
37
+ * Pyth Lazer access token (`Authorization: Bearer …`). Required under
38
+ * `oracleSource: 'pyth_lazer_rule'` (Lazer is auth-first) and unused by
39
+ * `'pyth_rule'` (keyless Core Hermes). This is a SECRET and never belongs in
40
+ * the canonical `waterx-config` JSON — pass it at client init from your own
41
+ * env var (e.g. `PYTH_API_KEY`); the SDK never reads `process.env`.
42
+ */
43
+ pythApiKey?: string;
44
+ /**
45
+ * Retry/timeout policy for the off-chain Hermes / Lazer update fetches (see
46
+ * `fetchWithPolicy`). Optional — defaults to 15s timeout, 2 retries.
47
+ */
48
+ pythFetch?: PythFetchPolicy;
18
49
  }
19
50
  export declare class PerpClient extends BaseLineClient<WaterXConfig> {
20
- /** Pyth infra (network defaults unless overridden in JSON). */
51
+ /** Pyth Core infra (fixed per network) plus the caller-supplied credential/policy. */
21
52
  pyth: PythInfraConfig;
22
53
  /** Wormhole infra for the credit bridge (network defaults unless overridden). */
23
54
  wormhole: WormholeInfraConfig;
55
+ /** Selected oracle price-update source (`oracleSource` create option; default `'pyth_rule'`). */
56
+ readonly oracleSource: OracleSource;
24
57
  /** Canonical-schema lookups (delegated to below); no transport. */
25
58
  private readonly view;
26
- constructor(network: Network, config: WaterXConfig, opts?: {
27
- grpcUrl?: string;
28
- });
59
+ constructor(network: Network, config: WaterXConfig, opts?: CreateClientOptions);
29
60
  /**
30
61
  * Async factory: fetches the deployment config for `network` and returns
31
62
  * a ready-to-use client. Pass `opts.cache=true` to memoize the JSON.
63
+ *
64
+ * No oracle-config guard here: selecting a source whose feeds are absent is
65
+ * not an error at init — it surfaces at tx-build time for the specific
66
+ * tickers that source can't serve (see `refreshOraclePrices`).
32
67
  */
33
68
  static create(network: Network, opts?: CreateClientOptions): Promise<PerpClient>;
34
69
  static mainnet(opts?: CreateClientOptions): Promise<PerpClient>;
@@ -17,25 +17,40 @@ const base_client_ts_1 = require("../base-client.js");
17
17
  const config_view_ts_1 = require("./config-view.js");
18
18
  const config_ts_1 = require("./config.js");
19
19
  class PerpClient extends base_client_ts_1.BaseLineClient {
20
- /** Pyth infra (network defaults unless overridden in JSON). */
20
+ /** Pyth Core infra (fixed per network) plus the caller-supplied credential/policy. */
21
21
  pyth;
22
22
  /** Wormhole infra for the credit bridge (network defaults unless overridden). */
23
23
  wormhole;
24
+ /** Selected oracle price-update source (`oracleSource` create option; default `'pyth_rule'`). */
25
+ oracleSource;
24
26
  /** Canonical-schema lookups (delegated to below); no transport. */
25
27
  view;
26
28
  constructor(network, config, opts = {}) {
27
29
  super(network, config, opts);
28
- this.pyth = config.pyth ?? config_ts_1.PYTH_DEFAULTS[network];
30
+ // Pyth Core infra is fixed per network — NOT deployment-overridable and
31
+ // NOT source-dependent (the pyth_lazer_rule source reads only api_key/fetch
32
+ // from here). The api_key + fetch policy are caller-supplied at init: a
33
+ // secret has no place in the canonical waterx-config JSON.
34
+ this.pyth = {
35
+ ...config_ts_1.PYTH_DEFAULTS[network],
36
+ ...(opts.pythApiKey !== undefined ? { api_key: opts.pythApiKey } : {}),
37
+ ...(opts.pythFetch !== undefined ? { fetch: opts.pythFetch } : {}),
38
+ };
29
39
  this.wormhole = config.wormhole ?? config_ts_1.WORMHOLE_DEFAULTS[network];
40
+ this.oracleSource = opts.oracleSource ?? "pyth_rule";
30
41
  this.view = new config_view_ts_1.PerpConfigView(() => this.config, () => this.wormhole);
31
42
  }
32
43
  /**
33
44
  * Async factory: fetches the deployment config for `network` and returns
34
45
  * a ready-to-use client. Pass `opts.cache=true` to memoize the JSON.
46
+ *
47
+ * No oracle-config guard here: selecting a source whose feeds are absent is
48
+ * not an error at init — it surfaces at tx-build time for the specific
49
+ * tickers that source can't serve (see `refreshOraclePrices`).
35
50
  */
36
51
  static async create(network, opts = {}) {
37
52
  const config = await (0, config_ts_1.loadConfig)(network, opts);
38
- return new PerpClient(network, config, { grpcUrl: opts.grpcUrl });
53
+ return new PerpClient(network, config, opts);
39
54
  }
40
55
  static mainnet(opts = {}) {
41
56
  return PerpClient.create("MAINNET", opts);
@@ -9,10 +9,10 @@
9
9
  * below, keyed by network.
10
10
  */
11
11
  import type { AccountPackages, BasePackageEntry, WormholeInfraConfig } from "../account/config.ts";
12
- import type { OraclePackages, PythInfraConfig } from "../oracle/config.ts";
12
+ import type { OraclePackages } from "../oracle/config.ts";
13
13
  import type { Network } from "./constants.ts";
14
- export type { AccountConfig, AccountPackages, BasePackageEntry, NativeCustodyAsset, NativeCustodyPackage, TrustedEmitterRow, WaterxCreditPackage, WaterxReferralPackage, WithdrawalQueuePackage, WormholeBridgePackage, WormholeInfraConfig, WxaAccountPackage, } from "../account/config.ts";
15
- export type { ConstantFeedEntry, OracleConfig, OraclePackages, PythInfraConfig, PythRulePackage, PythSponsorRulePackage, SupraFeedEntry, SupraRulePackage, WaterxConstantRulePackage, WaterxOraclePackage, } from "../oracle/config.ts";
14
+ export type { AccountConfig, AccountPackages, BasePackageEntry, NativeCustodyAsset, NativeCustodyPackage, WaterxCreditPackage, WaterxReferralPackage, WithdrawalQueuePackage, WormholeBridgePackage, WormholeInfraConfig, WxaAccountPackage, } from "../account/config.ts";
15
+ export type { ConstantFeedEntry, OracleConfig, OraclePackages, PythFetchPolicy, PythInfraConfig, PythLazerRulePackage, PythRulePackage, PythSponsorRulePackage, SupraFeedEntry, SupraRulePackage, WaterxConstantRulePackage, WaterxOraclePackage, } from "../oracle/config.ts";
16
16
  export { PYTH_DEFAULTS } from "../oracle/config.ts";
17
17
  export interface WaterxPerpMarketEntry {
18
18
  market: string;
@@ -82,8 +82,6 @@ export interface WaterXConfig {
82
82
  /** Sui gRPC base URL (default: public Mysten fullnode for the network). */
83
83
  grpcUrl?: string;
84
84
  packages: WaterXPackages;
85
- /** Pyth infra override (defaults from `PYTH_DEFAULTS[network]`). */
86
- pyth?: PythInfraConfig;
87
85
  /** Wormhole infra override (defaults from `WORMHOLE_DEFAULTS[network]`). */
88
86
  wormhole?: WormholeInfraConfig;
89
87
  /** Sui `CoinRegistry` shared object (credit deployments). */
@@ -104,12 +102,17 @@ export interface LoadConfigOptions {
104
102
  waterxConfigUrl?: string;
105
103
  /**
106
104
  * Reuse a previously-fetched config from the in-memory cache (keyed by
107
- * the effective URL). Default: false (always fetch fresh).
105
+ * network + the effective URL). Default: false (always fetch fresh).
108
106
  */
109
107
  cache?: boolean;
110
108
  /** Optional fetch implementation (for tests or environments without global `fetch`). */
111
109
  fetchImpl?: typeof fetch;
112
- /** Optional request timeout in ms. Default 10_000. */
110
+ /**
111
+ * Optional PER-ATTEMPT timeout in ms. Default 10_000. {@link loadConfig}
112
+ * retries a transient failure (network error / 429 / 5xx) via
113
+ * `fetchWithPolicy` (2 retries, exponential backoff) before falling back to
114
+ * the last successfully-validated config for this network+URL, if one exists.
115
+ */
113
116
  timeoutMs?: number;
114
117
  }
115
118
  export declare function clearConfigCache(): void;
@@ -13,6 +13,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.WORMHOLE_DEFAULTS = exports.PYTH_DEFAULTS = void 0;
14
14
  exports.clearConfigCache = clearConfigCache;
15
15
  exports.loadConfig = loadConfig;
16
+ const update_fetch_ts_1 = require("../oracle/update-fetch.js");
16
17
  var config_ts_1 = require("../oracle/config.js");
17
18
  Object.defineProperty(exports, "PYTH_DEFAULTS", { enumerable: true, get: function () { return config_ts_1.PYTH_DEFAULTS; } });
18
19
  // ============================================================================
@@ -39,38 +40,94 @@ exports.WORMHOLE_DEFAULTS = {
39
40
  wormholescan_api: "https://api.testnet.wormholescan.io/api/v1",
40
41
  },
41
42
  };
42
- const cache = new Map();
43
+ // Last successfully-validated config per `${network}:${url}` — the ONE module
44
+ // map, written UNCONDITIONALLY on every successful load regardless of
45
+ // `opts.cache`. It serves two roles at once: the resilience fallback for a
46
+ // refresh failure (see `loadConfig` below) AND the opt-in fast-path read
47
+ // `opts.cache: true` gates at the top of `loadConfig`. `opts.cache` therefore
48
+ // only gates whether a call *reads* this map early (skipping the fetch
49
+ // entirely) — it never gates whether a call *writes* it; every successful
50
+ // load writes here. The network prefix keeps one network's snapshot from ever
51
+ // satisfying another's request (see the cache-key note in `loadConfig`).
52
+ //
53
+ // Deliberate (benign) semantic refinement from the prior two-map design: a
54
+ // `cache: true` call can now hit an entry that was populated by an earlier
55
+ // `cache: false` call for the SAME network+url. That's fine — it's still that
56
+ // key's latest successfully-validated fetch, strictly FRESHER than any
57
+ // fallback read would have been, so a `cache: true` caller never observes
58
+ // staler data than before; it can only observe MORE-recent data sooner.
59
+ const configCache = new Map();
43
60
  function clearConfigCache() {
44
- cache.clear();
61
+ configCache.clear();
45
62
  }
46
63
  async function loadConfig(network, opts = {}) {
47
64
  const url = opts.waterxConfigUrl;
48
65
  if (!url) {
49
66
  throw new Error("loadConfig: no config URL — pass opts.waterxConfigUrl");
50
67
  }
51
- if (opts.cache && cache.has(url)) {
52
- return cache.get(url);
68
+ // Key by network AND url, never url alone: the same url can legitimately be
69
+ // requested for two networks (and `validateConfig` enforces network/url
70
+ // coherence on the success path), so a url-only key would let a testnet
71
+ // snapshot satisfy a mainnet request — both on this fast-path read and on
72
+ // the resilience fallback below — handing back wrong-CHAIN object ids to
73
+ // build transactions against. A wrong-network request simply misses here
74
+ // and fetches fresh.
75
+ const cacheKey = `${network}:${url}`;
76
+ if (opts.cache && configCache.has(cacheKey)) {
77
+ return configCache.get(cacheKey);
53
78
  }
54
79
  const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
55
80
  if (!fetchImpl) {
56
81
  throw new Error("loadConfig: no global `fetch` available; pass opts.fetchImpl");
57
82
  }
58
- const controller = new AbortController();
59
- const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 10_000);
60
- let response;
83
+ // Same resilience policy as the oracle money-path fetches (see
84
+ // `fetchWithPolicy`): bounded retry with backoff instead of one bare
85
+ // attempt. A refresh failure (network exhaustion, a non-ok response, OR a
86
+ // 200 response that fails to parse/validate — see below) falls back to the
87
+ // last successfully-validated config for this network+URL when one exists — a
88
+ // config-endpoint blip must not crash a long-running process that already
89
+ // has a working deployment snapshot. First load (nothing cached yet) has
90
+ // no fallback and still throws. Intentionally no log line on the fallback
91
+ // path — the SDK never logs (see every other oracle error in this
92
+ // codebase); a caller that cares can tell it got a stale snapshot by
93
+ // re-deriving staleness itself if it needs to.
94
+ //
95
+ // Deliberate limitation (not fixed here — a follow-up): this treats EVERY
96
+ // failure mode identically, including a DETERMINISTIC one (404/403 — the
97
+ // URL moved, or access was revoked) once a `configCache` snapshot exists.
98
+ // Unlike a transient blip, a deterministic failure will never self-heal on
99
+ // the next retry, so a long-running process with a stale snapshot will
100
+ // keep serving it FOREVER and silently mask what is actually a permanent
101
+ // deployment problem. Disambiguating "blip" from "moved/revoked" (e.g. via
102
+ // a max-staleness budget, or treating non-retryable 4xx specially) is
103
+ // intentionally deferred rather than folded into this change.
104
+ //
105
+ // fetch → ok-check → parse → validate all run in ONE try, so any failure
106
+ // along that chain (network exhaustion, a non-ok response, malformed JSON,
107
+ // or a `validateConfig` rejection) lands in the same catch and takes the
108
+ // same single last-known-good lookup below.
109
+ let raw;
61
110
  try {
62
- response = await fetchImpl(url, { signal: controller.signal });
63
- }
64
- finally {
65
- clearTimeout(timer);
111
+ const response = await (0, update_fetch_ts_1.fetchWithPolicy)(url, {}, { timeoutMs: opts.timeoutMs ?? 10_000, retries: 2, fetchImpl });
112
+ if (!response.ok) {
113
+ throw new Error(`loadConfig: HTTP ${response.status} fetching ${url}`);
114
+ }
115
+ raw = (await response.json());
116
+ validateConfig(raw, network, url);
66
117
  }
67
- if (!response.ok) {
68
- throw new Error(`loadConfig: HTTP ${response.status} fetching ${url}`);
118
+ catch (err) {
119
+ const stale = configCache.get(cacheKey);
120
+ if (stale)
121
+ return stale;
122
+ // Reframe a status-carrying FetchPolicyError into this function's own
123
+ // message shape, mirroring the non-retried `!response.ok` throw above and
124
+ // carrying the URL (the key datum for a config-fetch failure). A
125
+ // network-level exhaustion (no status), a `!response.ok` throw, or a JSON
126
+ // parse / `validateConfig` failure has no reframing to add — the helper
127
+ // propagates those verbatim.
128
+ (0, update_fetch_ts_1.rethrowExhaustedFetch)(err, (e) => `loadConfig: HTTP ${e.status} fetching ${url}`);
69
129
  }
70
- const raw = (await response.json());
71
- validateConfig(raw, network, url);
72
- if (opts.cache)
73
- cache.set(url, raw);
130
+ configCache.set(cacheKey, raw);
74
131
  return raw;
75
132
  }
76
133
  function validateConfig(cfg, expected, url) {
@@ -88,6 +145,9 @@ function validateConfig(cfg, expected, url) {
88
145
  };
89
146
  // Validate by deployment kind. A perp config must carry the perp set; a
90
147
  // credit config the credit set. `waterx_account` is common to both.
148
+ // `pyth_lazer_rule` is intentionally NOT required (and never will be by
149
+ // presence alone) — it's an optional/experimental package selected only via
150
+ // a client's `oracleSource` option, never by its presence in the config.
91
151
  const isPerp = "waterx_perp" in cfg.packages;
92
152
  const isCredit = "waterx_credit" in cfg.packages || "wormhole_bridge" in cfg.packages;
93
153
  const required = isPerp
@@ -1,7 +1,7 @@
1
1
  export { PerpClient } from "./client.ts";
2
2
  export type { CreateClientOptions } from "./client.ts";
3
3
  export { PYTH_DEFAULTS, WORMHOLE_DEFAULTS, clearConfigCache, loadConfig } from "./config.ts";
4
- export type { BasePackageEntry, ConstantFeedEntry, WaterxReferralPackage, LoadConfigOptions, NativeCustodyAsset, NativeCustodyPackage, PythInfraConfig, PythRulePackage, PythSponsorRulePackage, SupraFeedEntry, SupraRulePackage, TestnetFaucetPackage, TrustedEmitterRow, WaterXConfig, WaterXPackages, WaterxCreditPackage, WaterxOraclePackage, WaterxPerpMarketEntry, WaterxPerpPackage, WaterxStakingPackage, WithdrawalQueuePackage, WlpPackage, WormholeBridgePackage, WormholeInfraConfig, WxaAccountPackage, } from "./config.ts";
4
+ export type { BasePackageEntry, ConstantFeedEntry, WaterxReferralPackage, LoadConfigOptions, NativeCustodyAsset, NativeCustodyPackage, PythFetchPolicy, PythInfraConfig, PythLazerRulePackage, PythRulePackage, PythSponsorRulePackage, SupraFeedEntry, SupraRulePackage, TestnetFaucetPackage, WaterXConfig, WaterXPackages, WaterxCreditPackage, WaterxOraclePackage, WaterxPerpMarketEntry, WaterxPerpPackage, WaterxStakingPackage, WithdrawalQueuePackage, WlpPackage, WormholeBridgePackage, WormholeInfraConfig, WxaAccountPackage, } from "./config.ts";
5
5
  export { ACTION_ADD_PRE_ORDER, ACTION_CANCEL_ORDER, ACTION_CANCEL_PRE_ORDER, ACTION_CLOSE_POSITION, ACTION_DECREASE_POSITION, ACTION_DEPOSIT_COLLATERAL, ACTION_INCREASE_POSITION, ACTION_LIQUIDATE, ACTION_OPEN_POSITION, ACTION_PLACE_ORDER, ACTION_UPDATE_ORDER, ACTION_WITHDRAW_COLLATERAL, BPS_SCALE, CRYPTO_FEE_RATE, DOUBLE_SCALE, DRY_RUN_SENDER, FLOAT_SCALE, MAINTENANCE_MARGIN_RATE, ORDER_LIMIT_BUY, ORDER_LIMIT_SELL, ORDER_STOP_BUY, ORDER_STOP_SELL, ORDER_TAG_WILDCARD, PERM_ALL, PERM_ALL_TRADING, PERM_CANCEL_ORDER, PERM_CLOSE_POSITION, PERM_DECREASE_POSITION, PERM_DEPOSIT_COLLATERAL, PERM_INCREASE_POSITION, PERM_MINT_WLP, PERM_OPEN_POSITION, PERM_PLACE_ORDER, PERM_REDEEM_WLP, PERM_WITHDRAW_COLLATERAL, STAKING_PERM_DEPOSIT_STAKE, STAKING_PERM_REDEEM_STAKE, STAKING_PERM_CLAIM_REWARD, STAKING_PERM_ALL, STOCK_FEE_RATE, MS_PER_YEAR, SUI_DECIMALS, WLP_DECIMALS, COLLATERAL_DECIMALS, TOKEN_DECIMALS, } from "./constants.ts";
6
6
  export type { Network } from "./constants.ts";
7
7
  export { getMarketTickers, getCollateralAssets } from "../utils/config.ts";
@@ -9,7 +9,8 @@ export { annualizedApyFromRatio, annualizeFundingRate, calcBorrowRate, calcBorro
9
9
  export * from "./user/index.ts";
10
10
  export * from "./tx-builders.ts";
11
11
  export * from "./fetch.ts";
12
- export { PythCache, aggregateTicker, aggregateTickerWithConstant, aggregateTickerWithPyth, buildPythPriceUpdateCalls, fetchPriceFeedsUpdateData, refreshOraclePrices, updatePythPrices, } from "../oracle/index.ts";
12
+ export { FetchPolicyError, LazerApiKeyMissingError, OracleFeeSourceUnavailableError, OracleSourceNotImplementedError, PythCache, aggregateTicker, aggregateTickerWithConstant, aggregateTickerWithPyth, buildPythPriceUpdateCalls, fetchPriceFeedsUpdateData, refreshOraclePrices, updatePythPrices, } from "../oracle/index.ts";
13
+ export type { FetchPolicy, OracleFeeSource, OracleSource, UpdateDataProvider, } from "../oracle/index.ts";
13
14
  export { fetchDepositVaa, fetchVaa, listBridgeWithdrawalVaas, listVaasByEmitter, padEvmEmitter, toWormholescanEmitter, vaaBase64ToBytes, vaaBase64ToHex, vaaBytesToBase64, waitForVaa, } from "../account/funding/wormhole.ts";
14
15
  export type { VaaListItem, VaaResponse, WormholescanOptions } from "../account/funding/wormhole.ts";
15
16
  export { AccountData as AccountDataBcs, GlobalConfigData as GlobalConfigDataBcs, MarketData as MarketDataBcs, OrderData as OrderDataBcs, PoolData as PoolDataBcs, PositionData as PositionDataBcs, RedeemRequestData as RedeemRequestDataBcs, TokenPoolData as TokenPoolDataBcs, } from "../generated/waterx_perp_view/view.ts";
@@ -37,8 +37,8 @@ var __importStar = (this && this.__importStar) || (function () {
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.TOKEN_DECIMALS = exports.COLLATERAL_DECIMALS = exports.WLP_DECIMALS = exports.SUI_DECIMALS = exports.MS_PER_YEAR = exports.STOCK_FEE_RATE = exports.STAKING_PERM_ALL = exports.STAKING_PERM_CLAIM_REWARD = exports.STAKING_PERM_REDEEM_STAKE = exports.STAKING_PERM_DEPOSIT_STAKE = exports.PERM_WITHDRAW_COLLATERAL = exports.PERM_REDEEM_WLP = exports.PERM_PLACE_ORDER = exports.PERM_OPEN_POSITION = exports.PERM_MINT_WLP = exports.PERM_INCREASE_POSITION = exports.PERM_DEPOSIT_COLLATERAL = exports.PERM_DECREASE_POSITION = exports.PERM_CLOSE_POSITION = exports.PERM_CANCEL_ORDER = exports.PERM_ALL_TRADING = exports.PERM_ALL = exports.ORDER_TAG_WILDCARD = exports.ORDER_STOP_SELL = exports.ORDER_STOP_BUY = exports.ORDER_LIMIT_SELL = exports.ORDER_LIMIT_BUY = exports.MAINTENANCE_MARGIN_RATE = exports.FLOAT_SCALE = exports.DRY_RUN_SENDER = exports.DOUBLE_SCALE = exports.CRYPTO_FEE_RATE = exports.BPS_SCALE = exports.ACTION_WITHDRAW_COLLATERAL = exports.ACTION_UPDATE_ORDER = exports.ACTION_PLACE_ORDER = exports.ACTION_OPEN_POSITION = exports.ACTION_LIQUIDATE = exports.ACTION_INCREASE_POSITION = exports.ACTION_DEPOSIT_COLLATERAL = exports.ACTION_DECREASE_POSITION = exports.ACTION_CLOSE_POSITION = exports.ACTION_CANCEL_PRE_ORDER = exports.ACTION_CANCEL_ORDER = exports.ACTION_ADD_PRE_ORDER = exports.loadConfig = exports.clearConfigCache = exports.WORMHOLE_DEFAULTS = exports.PYTH_DEFAULTS = exports.PerpClient = void 0;
40
- exports.PositionDataBcs = exports.PoolDataBcs = exports.OrderDataBcs = exports.MarketDataBcs = exports.GlobalConfigDataBcs = exports.AccountDataBcs = exports.waitForVaa = exports.vaaBytesToBase64 = exports.vaaBase64ToHex = exports.vaaBase64ToBytes = exports.toWormholescanEmitter = exports.padEvmEmitter = exports.listVaasByEmitter = exports.listBridgeWithdrawalVaas = exports.fetchVaa = exports.fetchDepositVaa = exports.updatePythPrices = exports.refreshOraclePrices = exports.fetchPriceFeedsUpdateData = exports.buildPythPriceUpdateCalls = exports.aggregateTickerWithPyth = exports.aggregateTickerWithConstant = exports.aggregateTicker = exports.PythCache = exports.rawPrice = exports.decodeFundingIndexDelta = exports.calcWlpRedeemOut = exports.calcWlpPrice = exports.calcWlpMintOut = exports.calcWlpIncentiveApy = exports.calcUnrealizedPnl = exports.calcTotalTradingFeeRate = exports.calcTokenUtilizationBps = exports.calcPositionBorrowFee = exports.calcNotional = exports.calcMaxReducibleCollateralUsd = exports.calcLeverage = exports.calcImpactFeeRate = exports.calcFundingRate = exports.calcFundingFeeUsd = exports.calcFee = exports.calcEstLiqPrice = exports.calcEffectiveCollateralUsd = exports.calcDynamicFeeBps = exports.calcBorrowRateAccrual = exports.calcBorrowRate = exports.annualizeFundingRate = exports.annualizedApyFromRatio = exports.getCollateralAssets = exports.getMarketTickers = void 0;
41
- exports.withdrawalQueueCalls = exports.nativeCustodyCalls = exports.referralCalls = exports.pythSponsorRuleCalls = exports.pythRuleCalls = exports.oracleCalls = exports.stakingCalls = exports.wxaAccountCalls = exports.viewCalls = exports.lpPoolCalls = exports.tradingCalls = exports.MarketConfigBcs = exports.MarketBcs = exports.OrderBcs = exports.PositionBcs = exports.TokenPoolDataBcs = exports.RedeemRequestDataBcs = void 0;
40
+ exports.GlobalConfigDataBcs = exports.AccountDataBcs = exports.waitForVaa = exports.vaaBytesToBase64 = exports.vaaBase64ToHex = exports.vaaBase64ToBytes = exports.toWormholescanEmitter = exports.padEvmEmitter = exports.listVaasByEmitter = exports.listBridgeWithdrawalVaas = exports.fetchVaa = exports.fetchDepositVaa = exports.updatePythPrices = exports.refreshOraclePrices = exports.fetchPriceFeedsUpdateData = exports.buildPythPriceUpdateCalls = exports.aggregateTickerWithPyth = exports.aggregateTickerWithConstant = exports.aggregateTicker = exports.PythCache = exports.OracleSourceNotImplementedError = exports.OracleFeeSourceUnavailableError = exports.LazerApiKeyMissingError = exports.FetchPolicyError = exports.rawPrice = exports.decodeFundingIndexDelta = exports.calcWlpRedeemOut = exports.calcWlpPrice = exports.calcWlpMintOut = exports.calcWlpIncentiveApy = exports.calcUnrealizedPnl = exports.calcTotalTradingFeeRate = exports.calcTokenUtilizationBps = exports.calcPositionBorrowFee = exports.calcNotional = exports.calcMaxReducibleCollateralUsd = exports.calcLeverage = exports.calcImpactFeeRate = exports.calcFundingRate = exports.calcFundingFeeUsd = exports.calcFee = exports.calcEstLiqPrice = exports.calcEffectiveCollateralUsd = exports.calcDynamicFeeBps = exports.calcBorrowRateAccrual = exports.calcBorrowRate = exports.annualizeFundingRate = exports.annualizedApyFromRatio = exports.getCollateralAssets = exports.getMarketTickers = void 0;
41
+ exports.withdrawalQueueCalls = exports.nativeCustodyCalls = exports.referralCalls = exports.pythSponsorRuleCalls = exports.pythRuleCalls = exports.oracleCalls = exports.stakingCalls = exports.wxaAccountCalls = exports.viewCalls = exports.lpPoolCalls = exports.tradingCalls = exports.MarketConfigBcs = exports.MarketBcs = exports.OrderBcs = exports.PositionBcs = exports.TokenPoolDataBcs = exports.RedeemRequestDataBcs = exports.PositionDataBcs = exports.PoolDataBcs = exports.OrderDataBcs = exports.MarketDataBcs = void 0;
42
42
  // ======== Core ========
43
43
  var client_ts_1 = require("./client.js");
44
44
  Object.defineProperty(exports, "PerpClient", { enumerable: true, get: function () { return client_ts_1.PerpClient; } });
@@ -131,6 +131,10 @@ __exportStar(require("./tx-builders.js"), exports);
131
131
  __exportStar(require("./fetch.js"), exports);
132
132
  // ======== Oracle utilities (Pyth source + rule aggregation) ========
133
133
  var index_ts_1 = require("../oracle/index.js");
134
+ Object.defineProperty(exports, "FetchPolicyError", { enumerable: true, get: function () { return index_ts_1.FetchPolicyError; } });
135
+ Object.defineProperty(exports, "LazerApiKeyMissingError", { enumerable: true, get: function () { return index_ts_1.LazerApiKeyMissingError; } });
136
+ Object.defineProperty(exports, "OracleFeeSourceUnavailableError", { enumerable: true, get: function () { return index_ts_1.OracleFeeSourceUnavailableError; } });
137
+ Object.defineProperty(exports, "OracleSourceNotImplementedError", { enumerable: true, get: function () { return index_ts_1.OracleSourceNotImplementedError; } });
134
138
  Object.defineProperty(exports, "PythCache", { enumerable: true, get: function () { return index_ts_1.PythCache; } });
135
139
  Object.defineProperty(exports, "aggregateTicker", { enumerable: true, get: function () { return index_ts_1.aggregateTicker; } });
136
140
  Object.defineProperty(exports, "aggregateTickerWithConstant", { enumerable: true, get: function () { return index_ts_1.aggregateTickerWithConstant; } });