@waterx/sdk 3.1.1 → 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 +36 -0
- 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 +9 -4
- package/dist/cjs/src/perp/config.js +82 -18
- package/dist/cjs/src/perp/index.d.ts +4 -3
- package/dist/cjs/src/perp/index.js +8 -3
- 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/unified-client.d.ts +17 -0
- package/dist/cjs/src/unified-client.js +2 -0
- 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 +9 -4
- package/dist/src/perp/config.js +81 -18
- 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/unified-client.d.ts +17 -0
- package/dist/src/unified-client.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fetchWithPolicy` — resilience wrapper around `fetch` for the oracle money
|
|
3
|
+
* path's off-chain update-data fetches. Every order/position/collateral
|
|
4
|
+
* tx-build depends on one of these REST calls landing (Hermes VAA for
|
|
5
|
+
* `pyth_rule`, Lazer signed updates for `pyth_lazer_rule`); a bare `fetch`
|
|
6
|
+
* with a single attempt and no retry means one Hermes 429/5xx or timeout
|
|
7
|
+
* fails every trade. `fetchPriceFeedsUpdateData` (`./pyth.ts`),
|
|
8
|
+
* `PythLazerRule.fetchUpdateData`'s Lazer POST (`./rules/pyth-lazer-rule.ts`),
|
|
9
|
+
* and `loadConfig` (`../perp/config.ts`) all delegate here instead of calling
|
|
10
|
+
* `fetch` directly — this is the ONE place a retry/timeout/auth policy is
|
|
11
|
+
* implemented for these fetches.
|
|
12
|
+
*
|
|
13
|
+
* Policy semantics:
|
|
14
|
+
* - Bearer auth is attached iff `policy.apiKey` is a non-empty string —
|
|
15
|
+
* absent/empty is byte-identical to today's keyless request (no
|
|
16
|
+
* `Authorization` header at all). This is the Phase-0 invariant of the
|
|
17
|
+
* Pyth Pro migration: existing keyless deployments see no behavior change.
|
|
18
|
+
* - Retries on network errors, HTTP 429, and HTTP 5xx, with exponential
|
|
19
|
+
* backoff (`retryDelayMs * 2^attempt`, capped at `MAX_BACKOFF_MS`). Other
|
|
20
|
+
* 4xx statuses (401/400/403/404/…) are NOT retried — auth/bad-request
|
|
21
|
+
* failures are deterministic, so that `Response` (`ok: false`) is handed
|
|
22
|
+
* back on the first attempt for the caller to format its own
|
|
23
|
+
* domain-specific error, exactly as it did before this wrapper existed.
|
|
24
|
+
* - `init.body`, if set, MUST be replayable across attempts — a retry
|
|
25
|
+
* re-sends the SAME `init` object to `fetch` on every attempt. A string /
|
|
26
|
+
* `URLSearchParams` / BCS-serialized `Uint8Array` body (every caller today)
|
|
27
|
+
* is fine; a one-shot `ReadableStream` body would not survive a second
|
|
28
|
+
* attempt and must not be passed through this function.
|
|
29
|
+
* - Each attempt gets its own `AbortSignal.timeout(policy.timeoutMs)`
|
|
30
|
+
* combined with whichever of `init.signal` / the `externalSignal` param are
|
|
31
|
+
* set — ALL of them can end the whole policy (not just the in-flight
|
|
32
|
+
* attempt), including a queued backoff sleep, via `AbortSignal.any`
|
|
33
|
+
* (runtime floor: Node ≥20.3 / any modern browser — matches this repo's
|
|
34
|
+
* `target: ES2023` + `lib: ["dom", "esnext"]`).
|
|
35
|
+
* - Exhausting retries with no successful/non-retryable response (i.e. every
|
|
36
|
+
* attempt was a network error, or the final attempt was still a retryable
|
|
37
|
+
* HTTP failure) throws a {@link FetchPolicyError} naming the target's
|
|
38
|
+
* `host + pathname` (never the query string — feed ids are off-chain
|
|
39
|
+
* noise, not diagnostic value), the attempt count, and whichever of
|
|
40
|
+
* `status` (a retryable HTTP failure — plus a truncated response-body
|
|
41
|
+
* snippet, when the final attempt's response carried one) or `cause` (a
|
|
42
|
+
* network error) the final attempt produced. An INTERMEDIATE (non-final)
|
|
43
|
+
* retryable response's body is discarded via `response.body?.cancel()`
|
|
44
|
+
* instead of read, so a doomed-to-retry response doesn't pin its
|
|
45
|
+
* connection's socket open for no reason.
|
|
46
|
+
* - Retry worst case: with the defaults (15s timeout × 3 attempts + ~0.75s of
|
|
47
|
+
* backoff between them) a FULL outage takes up to ~46s to surface as a
|
|
48
|
+
* `FetchPolicyError`, vs ~15s pre-3.2.0's single bare-`fetch` attempt.
|
|
49
|
+
* Tunable per client via `config.pyth.fetch.{timeoutMs,retries}`.
|
|
50
|
+
*/
|
|
51
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
52
|
+
const DEFAULT_RETRIES = 2;
|
|
53
|
+
const DEFAULT_RETRY_DELAY_MS = 250;
|
|
54
|
+
const MAX_BACKOFF_MS = 2_000;
|
|
55
|
+
/** Final-error diagnostic only — not a protocol limit. */
|
|
56
|
+
const MAX_BODY_SNIPPET_LENGTH = 200;
|
|
57
|
+
/** Thrown by {@link fetchWithPolicy} when every attempt failed. */
|
|
58
|
+
export class FetchPolicyError extends Error {
|
|
59
|
+
/** HTTP status of the final attempt, when it got a (retryable-but-failing) response. */
|
|
60
|
+
status;
|
|
61
|
+
/** Truncated (~200 char) body of the final attempt's response, when one was readable. */
|
|
62
|
+
bodySnippet;
|
|
63
|
+
/** Total attempts made (first try + retries actually used). */
|
|
64
|
+
attempts;
|
|
65
|
+
constructor(message, opts) {
|
|
66
|
+
super(message, opts.cause !== undefined ? { cause: opts.cause } : undefined);
|
|
67
|
+
this.name = "FetchPolicyError";
|
|
68
|
+
this.status = opts.status;
|
|
69
|
+
this.bodySnippet = opts.bodySnippet;
|
|
70
|
+
this.attempts = opts.attempts;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function isRetryableStatus(status) {
|
|
74
|
+
return status === 429 || status >= 500;
|
|
75
|
+
}
|
|
76
|
+
function backoffMs(retryDelayMs, attempt) {
|
|
77
|
+
return Math.min(retryDelayMs * 2 ** attempt, MAX_BACKOFF_MS);
|
|
78
|
+
}
|
|
79
|
+
/** `host + pathname` only — never the query string (feed ids are noise, not diagnostic). */
|
|
80
|
+
function describeTarget(url) {
|
|
81
|
+
const parsed = new URL(url);
|
|
82
|
+
return `${parsed.host}${parsed.pathname}`;
|
|
83
|
+
}
|
|
84
|
+
function causeMessage(cause) {
|
|
85
|
+
return cause instanceof Error ? cause.message : String(cause);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Best-effort, truncated body text for the FINAL failed attempt's error
|
|
89
|
+
* message — restores the diagnostic a plain `if (!res.ok) throw new
|
|
90
|
+
* Error(...res.status, await res.text())` had before this wrapper existed.
|
|
91
|
+
* Swallows any read failure (missing/consumed body, a test double with no
|
|
92
|
+
* `.text()`, …) — a diagnostic snippet is never worth failing the request
|
|
93
|
+
* differently than the status/cause already dictate.
|
|
94
|
+
*/
|
|
95
|
+
async function readBodySnippet(response) {
|
|
96
|
+
try {
|
|
97
|
+
const text = await response.text();
|
|
98
|
+
return text.length > MAX_BODY_SNIPPET_LENGTH
|
|
99
|
+
? `${text.slice(0, MAX_BODY_SNIPPET_LENGTH)}…`
|
|
100
|
+
: text;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return "";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Resolves after `ms`, or rejects immediately (with `signal.reason`) if `signal` fires first. */
|
|
107
|
+
function sleep(ms, signal) {
|
|
108
|
+
if (!signal)
|
|
109
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
110
|
+
const abortSignal = signal;
|
|
111
|
+
if (abortSignal.aborted)
|
|
112
|
+
return Promise.reject(abortSignal.reason);
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
const onAbort = () => {
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
reject(abortSignal.reason);
|
|
117
|
+
};
|
|
118
|
+
const timer = setTimeout(() => {
|
|
119
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
120
|
+
resolve();
|
|
121
|
+
}, ms);
|
|
122
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/** `undefined` if none are set; the lone signal if exactly one is; `AbortSignal.any(...)` otherwise. */
|
|
126
|
+
function combineSignals(signals) {
|
|
127
|
+
const present = signals.filter((s) => s != null);
|
|
128
|
+
if (present.length === 0)
|
|
129
|
+
return undefined;
|
|
130
|
+
if (present.length === 1)
|
|
131
|
+
return present[0];
|
|
132
|
+
return AbortSignal.any(present);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Merge `Authorization: Bearer <apiKey>` into `base` iff `apiKey` is
|
|
136
|
+
* non-empty; `base` is returned UNTOUCHED otherwise (byte-identical to
|
|
137
|
+
* today's keyless requests — no header, whatever shape the caller passed).
|
|
138
|
+
* Always builds a plain object (never a `Headers` instance) so callers that
|
|
139
|
+
* assert on the exact `init` they handed `fetch` keep working unchanged.
|
|
140
|
+
*/
|
|
141
|
+
function withBearerAuth(base, apiKey) {
|
|
142
|
+
if (!apiKey)
|
|
143
|
+
return base;
|
|
144
|
+
const merged = {};
|
|
145
|
+
if (base instanceof Headers) {
|
|
146
|
+
base.forEach((value, key) => {
|
|
147
|
+
merged[key] = value;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
else if (Array.isArray(base)) {
|
|
151
|
+
for (const [key, value] of base)
|
|
152
|
+
merged[key] = value;
|
|
153
|
+
}
|
|
154
|
+
else if (base) {
|
|
155
|
+
Object.assign(merged, base);
|
|
156
|
+
}
|
|
157
|
+
merged.Authorization = `Bearer ${apiKey}`;
|
|
158
|
+
return merged;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* `fetch` with per-attempt timeout, bounded retry + backoff, and optional
|
|
162
|
+
* Bearer auth. See the module header for the full policy. Both `init.signal`
|
|
163
|
+
* (if the caller set one) AND the separate `externalSignal` param cancel the
|
|
164
|
+
* WHOLE policy — in-flight attempts AND queued backoff sleeps — not just a
|
|
165
|
+
* single attempt.
|
|
166
|
+
*/
|
|
167
|
+
export async function fetchWithPolicy(url, init = {}, policy = {}, externalSignal) {
|
|
168
|
+
const timeoutMs = policy.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
169
|
+
const retries = policy.retries ?? DEFAULT_RETRIES;
|
|
170
|
+
const retryDelayMs = policy.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
171
|
+
const doFetch = policy.fetchImpl ?? fetch;
|
|
172
|
+
const headers = withBearerAuth(init.headers, policy.apiKey);
|
|
173
|
+
// init.signal is folded in here (not spread through per-attempt below) so
|
|
174
|
+
// it gates retries/backoff exactly like externalSignal, instead of being
|
|
175
|
+
// silently dropped by the `{ ...init, signal }` override per attempt.
|
|
176
|
+
const combinedExternalSignal = combineSignals([externalSignal, init.signal]);
|
|
177
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
178
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
179
|
+
const signal = combinedExternalSignal
|
|
180
|
+
? AbortSignal.any([timeoutSignal, combinedExternalSignal])
|
|
181
|
+
: timeoutSignal;
|
|
182
|
+
let status;
|
|
183
|
+
let bodySnippet;
|
|
184
|
+
let cause;
|
|
185
|
+
try {
|
|
186
|
+
const response = await doFetch(url, { ...init, headers, signal });
|
|
187
|
+
if (response.ok || !isRetryableStatus(response.status))
|
|
188
|
+
return response;
|
|
189
|
+
status = response.status;
|
|
190
|
+
if (attempt === retries) {
|
|
191
|
+
bodySnippet = await readBodySnippet(response);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
// Doomed to be retried — discard rather than read, so this
|
|
195
|
+
// response's connection/socket isn't held open for a body nobody
|
|
196
|
+
// will consume. `.catch()` is mandatory here: an errored stream's
|
|
197
|
+
// `cancel()` returns a REJECTED promise, and a bare `void` on that
|
|
198
|
+
// is an unhandled rejection — process-fatal in Node — triggered by
|
|
199
|
+
// exactly the degraded-upstream condition this wrapper exists to
|
|
200
|
+
// survive.
|
|
201
|
+
void response.body?.cancel().catch(() => { });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
if (combinedExternalSignal?.aborted)
|
|
206
|
+
throw err;
|
|
207
|
+
cause = err;
|
|
208
|
+
}
|
|
209
|
+
if (attempt === retries) {
|
|
210
|
+
const statusDetail = status !== undefined
|
|
211
|
+
? `last status ${status}${bodySnippet ? ` — ${bodySnippet}` : ""}`
|
|
212
|
+
: undefined;
|
|
213
|
+
const detail = statusDetail ?? causeMessage(cause);
|
|
214
|
+
throw new FetchPolicyError(`fetchWithPolicy: ${describeTarget(url)} failed after ${attempt + 1} attempt(s) — ${detail}`, { status, bodySnippet, cause, attempts: attempt + 1 });
|
|
215
|
+
}
|
|
216
|
+
await sleep(backoffMs(retryDelayMs, attempt), combinedExternalSignal);
|
|
217
|
+
}
|
|
218
|
+
// Unreachable: the loop above always returns or throws on its final
|
|
219
|
+
// (attempt === retries) iteration — this satisfies the compiler only.
|
|
220
|
+
throw new FetchPolicyError(`fetchWithPolicy: ${describeTarget(url)} exhausted retries`, {
|
|
221
|
+
attempts: retries + 1,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
@@ -11,20 +11,41 @@
|
|
|
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
|
|
14
|
+
import type { OracleSource } from "../oracle/price-update-rule.ts";
|
|
15
|
+
import { type LoadConfigOptions, type PythGeneration, 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
|
+
* Selects which `PriceUpdateRule` `refreshOraclePrices` uses for the
|
|
21
|
+
* on-chain price-update leg (see `OracleHost.oracleSource`). Default:
|
|
22
|
+
* `'pyth_rule'`. The SDK never reads `process.env` — pass this from your
|
|
23
|
+
* own env var (e.g. `ORACLE_SOURCE`).
|
|
24
|
+
*/
|
|
25
|
+
oracleSource?: OracleSource;
|
|
26
|
+
/**
|
|
27
|
+
* Selects which Pyth Core contract generation feeds `client.pyth` when the
|
|
28
|
+
* config JSON has no explicit `pyth` override: `'core'` (default,
|
|
29
|
+
* `PYTH_DEFAULTS`) or `'pro'` (`PYTH_PRO_DEFAULTS` — the post-2026-08-18
|
|
30
|
+
* Pro-compatible contracts + Hermes-compatible endpoint; pair with
|
|
31
|
+
* `pyth.api_key`). Orthogonal to `oracleSource`. An explicit `config.pyth`
|
|
32
|
+
* always wins wholesale (see `PythGeneration`).
|
|
33
|
+
*/
|
|
34
|
+
pythGeneration?: PythGeneration;
|
|
18
35
|
}
|
|
19
36
|
export declare class PerpClient extends BaseLineClient<WaterXConfig> {
|
|
20
37
|
/** Pyth infra (network defaults unless overridden in JSON). */
|
|
21
38
|
pyth: PythInfraConfig;
|
|
22
39
|
/** Wormhole infra for the credit bridge (network defaults unless overridden). */
|
|
23
40
|
wormhole: WormholeInfraConfig;
|
|
41
|
+
/** Selected oracle rule source (client option, resolved at creation; default `'pyth_rule'`). See `OracleHost.oracleSource`. */
|
|
42
|
+
readonly oracleSource: OracleSource;
|
|
24
43
|
/** Canonical-schema lookups (delegated to below); no transport. */
|
|
25
44
|
private readonly view;
|
|
26
45
|
constructor(network: Network, config: WaterXConfig, opts?: {
|
|
27
46
|
grpcUrl?: string;
|
|
47
|
+
oracleSource?: OracleSource;
|
|
48
|
+
pythGeneration?: PythGeneration;
|
|
28
49
|
});
|
|
29
50
|
/**
|
|
30
51
|
* Async factory: fetches the deployment config for `network` and returns
|
package/dist/src/perp/client.js
CHANGED
|
@@ -12,18 +12,23 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { BaseLineClient } from "../base-client.js";
|
|
14
14
|
import { PerpConfigView } from "./config-view.js";
|
|
15
|
-
import { loadConfig, PYTH_DEFAULTS, WORMHOLE_DEFAULTS, } from "./config.js";
|
|
15
|
+
import { loadConfig, PYTH_DEFAULTS, PYTH_PRO_DEFAULTS, WORMHOLE_DEFAULTS, } from "./config.js";
|
|
16
16
|
export class PerpClient extends BaseLineClient {
|
|
17
17
|
/** Pyth infra (network defaults unless overridden in JSON). */
|
|
18
18
|
pyth;
|
|
19
19
|
/** Wormhole infra for the credit bridge (network defaults unless overridden). */
|
|
20
20
|
wormhole;
|
|
21
|
+
/** Selected oracle rule source (client option, resolved at creation; default `'pyth_rule'`). See `OracleHost.oracleSource`. */
|
|
22
|
+
oracleSource;
|
|
21
23
|
/** Canonical-schema lookups (delegated to below); no transport. */
|
|
22
24
|
view;
|
|
23
25
|
constructor(network, config, opts = {}) {
|
|
24
26
|
super(network, config, opts);
|
|
25
|
-
|
|
27
|
+
// Precedence: explicit config.pyth override > generation constants.
|
|
28
|
+
this.pyth =
|
|
29
|
+
config.pyth ?? (opts.pythGeneration === "pro" ? PYTH_PRO_DEFAULTS : PYTH_DEFAULTS)[network];
|
|
26
30
|
this.wormhole = config.wormhole ?? WORMHOLE_DEFAULTS[network];
|
|
31
|
+
this.oracleSource = opts.oracleSource ?? "pyth_rule";
|
|
27
32
|
this.view = new PerpConfigView(() => this.config, () => this.wormhole);
|
|
28
33
|
}
|
|
29
34
|
/**
|
|
@@ -32,7 +37,11 @@ export class PerpClient extends BaseLineClient {
|
|
|
32
37
|
*/
|
|
33
38
|
static async create(network, opts = {}) {
|
|
34
39
|
const config = await loadConfig(network, opts);
|
|
35
|
-
return new PerpClient(network, config, {
|
|
40
|
+
return new PerpClient(network, config, {
|
|
41
|
+
grpcUrl: opts.grpcUrl,
|
|
42
|
+
oracleSource: opts.oracleSource,
|
|
43
|
+
pythGeneration: opts.pythGeneration,
|
|
44
|
+
});
|
|
36
45
|
}
|
|
37
46
|
static mainnet(opts = {}) {
|
|
38
47
|
return PerpClient.create("MAINNET", opts);
|
|
@@ -12,8 +12,8 @@ import type { AccountPackages, BasePackageEntry, WormholeInfraConfig } from "../
|
|
|
12
12
|
import type { OraclePackages, PythInfraConfig } from "../oracle/config.ts";
|
|
13
13
|
import type { Network } from "./constants.ts";
|
|
14
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";
|
|
16
|
-
export { PYTH_DEFAULTS } from "../oracle/config.ts";
|
|
15
|
+
export type { ConstantFeedEntry, OracleConfig, OraclePackages, PythGeneration, PythInfraConfig, PythLazerRulePackage, PythRulePackage, PythSponsorRulePackage, SupraFeedEntry, SupraRulePackage, WaterxConstantRulePackage, WaterxOraclePackage, } from "../oracle/config.ts";
|
|
16
|
+
export { PYTH_DEFAULTS, PYTH_PRO_DEFAULTS } from "../oracle/config.ts";
|
|
17
17
|
export interface WaterxPerpMarketEntry {
|
|
18
18
|
market: string;
|
|
19
19
|
config: string;
|
|
@@ -104,12 +104,17 @@ export interface LoadConfigOptions {
|
|
|
104
104
|
waterxConfigUrl?: string;
|
|
105
105
|
/**
|
|
106
106
|
* Reuse a previously-fetched config from the in-memory cache (keyed by
|
|
107
|
-
* the effective URL). Default: false (always fetch fresh).
|
|
107
|
+
* network + the effective URL). Default: false (always fetch fresh).
|
|
108
108
|
*/
|
|
109
109
|
cache?: boolean;
|
|
110
110
|
/** Optional fetch implementation (for tests or environments without global `fetch`). */
|
|
111
111
|
fetchImpl?: typeof fetch;
|
|
112
|
-
/**
|
|
112
|
+
/**
|
|
113
|
+
* Optional PER-ATTEMPT timeout in ms. Default 10_000. {@link loadConfig}
|
|
114
|
+
* retries a transient failure (network error / 429 / 5xx) via
|
|
115
|
+
* `fetchWithPolicy` (2 retries, exponential backoff) before falling back to
|
|
116
|
+
* the last successfully-validated config for this network+URL, if one exists.
|
|
117
|
+
*/
|
|
113
118
|
timeoutMs?: number;
|
|
114
119
|
}
|
|
115
120
|
export declare function clearConfigCache(): void;
|
package/dist/src/perp/config.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* **not** in the JSON — it lives in `PYTH_DEFAULTS` / `WORMHOLE_DEFAULTS`
|
|
9
9
|
* below, keyed by network.
|
|
10
10
|
*/
|
|
11
|
-
|
|
11
|
+
import { FetchPolicyError, fetchWithPolicy } from "../oracle/update-fetch.js";
|
|
12
|
+
export { PYTH_DEFAULTS, PYTH_PRO_DEFAULTS } from "../oracle/config.js";
|
|
12
13
|
// ============================================================================
|
|
13
14
|
// Wormhole / Hermes — external chain infra, defaults by network
|
|
14
15
|
// ============================================================================
|
|
@@ -33,38 +34,97 @@ export const WORMHOLE_DEFAULTS = {
|
|
|
33
34
|
wormholescan_api: "https://api.testnet.wormholescan.io/api/v1",
|
|
34
35
|
},
|
|
35
36
|
};
|
|
36
|
-
|
|
37
|
+
// Last successfully-validated config per `${network}:${url}` — the ONE module
|
|
38
|
+
// map, written UNCONDITIONALLY on every successful load regardless of
|
|
39
|
+
// `opts.cache`. It serves two roles at once: the resilience fallback for a
|
|
40
|
+
// refresh failure (see `loadConfig` below) AND the opt-in fast-path read
|
|
41
|
+
// `opts.cache: true` gates at the top of `loadConfig`. `opts.cache` therefore
|
|
42
|
+
// only gates whether a call *reads* this map early (skipping the fetch
|
|
43
|
+
// entirely) — it never gates whether a call *writes* it; every successful
|
|
44
|
+
// load writes here. The network prefix keeps one network's snapshot from ever
|
|
45
|
+
// satisfying another's request (see the cache-key note in `loadConfig`).
|
|
46
|
+
//
|
|
47
|
+
// Deliberate (benign) semantic refinement from the prior two-map design: a
|
|
48
|
+
// `cache: true` call can now hit an entry that was populated by an earlier
|
|
49
|
+
// `cache: false` call for the SAME network+url. That's fine — it's still that
|
|
50
|
+
// key's latest successfully-validated fetch, strictly FRESHER than any
|
|
51
|
+
// fallback read would have been, so a `cache: true` caller never observes
|
|
52
|
+
// staler data than before; it can only observe MORE-recent data sooner.
|
|
53
|
+
const configCache = new Map();
|
|
37
54
|
export function clearConfigCache() {
|
|
38
|
-
|
|
55
|
+
configCache.clear();
|
|
39
56
|
}
|
|
40
57
|
export async function loadConfig(network, opts = {}) {
|
|
41
58
|
const url = opts.waterxConfigUrl;
|
|
42
59
|
if (!url) {
|
|
43
60
|
throw new Error("loadConfig: no config URL — pass opts.waterxConfigUrl");
|
|
44
61
|
}
|
|
45
|
-
|
|
46
|
-
|
|
62
|
+
// Key by network AND url, never url alone: the same url can legitimately be
|
|
63
|
+
// requested for two networks (and `validateConfig` enforces network/url
|
|
64
|
+
// coherence on the success path), so a url-only key would let a testnet
|
|
65
|
+
// snapshot satisfy a mainnet request — both on this fast-path read and on
|
|
66
|
+
// the resilience fallback below — handing back wrong-CHAIN object ids to
|
|
67
|
+
// build transactions against. A wrong-network request simply misses here
|
|
68
|
+
// and fetches fresh.
|
|
69
|
+
const cacheKey = `${network}:${url}`;
|
|
70
|
+
if (opts.cache && configCache.has(cacheKey)) {
|
|
71
|
+
return configCache.get(cacheKey);
|
|
47
72
|
}
|
|
48
73
|
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
49
74
|
if (!fetchImpl) {
|
|
50
75
|
throw new Error("loadConfig: no global `fetch` available; pass opts.fetchImpl");
|
|
51
76
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
77
|
+
// Same resilience policy as the oracle money-path fetches (see
|
|
78
|
+
// `fetchWithPolicy`): bounded retry with backoff instead of one bare
|
|
79
|
+
// attempt. A refresh failure (network exhaustion, a non-ok response, OR a
|
|
80
|
+
// 200 response that fails to parse/validate — see below) falls back to the
|
|
81
|
+
// last successfully-validated config for this network+URL when one exists — a
|
|
82
|
+
// config-endpoint blip must not crash a long-running process that already
|
|
83
|
+
// has a working deployment snapshot. First load (nothing cached yet) has
|
|
84
|
+
// no fallback and still throws. Intentionally no log line on the fallback
|
|
85
|
+
// path — the SDK never logs (see every other oracle error in this
|
|
86
|
+
// codebase); a caller that cares can tell it got a stale snapshot by
|
|
87
|
+
// re-deriving staleness itself if it needs to.
|
|
88
|
+
//
|
|
89
|
+
// Deliberate limitation (not fixed here — a follow-up): this treats EVERY
|
|
90
|
+
// failure mode identically, including a DETERMINISTIC one (404/403 — the
|
|
91
|
+
// URL moved, or access was revoked) once a `configCache` snapshot exists.
|
|
92
|
+
// Unlike a transient blip, a deterministic failure will never self-heal on
|
|
93
|
+
// the next retry, so a long-running process with a stale snapshot will
|
|
94
|
+
// keep serving it FOREVER and silently mask what is actually a permanent
|
|
95
|
+
// deployment problem. Disambiguating "blip" from "moved/revoked" (e.g. via
|
|
96
|
+
// a max-staleness budget, or treating non-retryable 4xx specially) is
|
|
97
|
+
// intentionally deferred rather than folded into this change.
|
|
98
|
+
//
|
|
99
|
+
// fetch → ok-check → parse → validate all run in ONE try, so any failure
|
|
100
|
+
// along that chain (network exhaustion, a non-ok response, malformed JSON,
|
|
101
|
+
// or a `validateConfig` rejection) lands in the same catch and takes the
|
|
102
|
+
// same single last-known-good lookup below.
|
|
103
|
+
let raw;
|
|
55
104
|
try {
|
|
56
|
-
response = await
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
105
|
+
const response = await fetchWithPolicy(url, {}, { timeoutMs: opts.timeoutMs ?? 10_000, retries: 2, fetchImpl });
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
throw new Error(`loadConfig: HTTP ${response.status} fetching ${url}`);
|
|
108
|
+
}
|
|
109
|
+
raw = (await response.json());
|
|
110
|
+
validateConfig(raw, network, url);
|
|
60
111
|
}
|
|
61
|
-
|
|
62
|
-
|
|
112
|
+
catch (err) {
|
|
113
|
+
const stale = configCache.get(cacheKey);
|
|
114
|
+
if (stale)
|
|
115
|
+
return stale;
|
|
116
|
+
// Reformat a status-carrying FetchPolicyError (retries exhausted on a
|
|
117
|
+
// retryable status) into this function's own message shape, mirroring
|
|
118
|
+
// the non-retried `!response.ok` throw above. A network-level
|
|
119
|
+
// exhaustion (no status), a `!response.ok` throw, or a JSON parse /
|
|
120
|
+
// `validateConfig` failure has no domain-specific reframing to add —
|
|
121
|
+
// propagate that error's own message as-is.
|
|
122
|
+
if (err instanceof FetchPolicyError && err.status !== undefined) {
|
|
123
|
+
throw new Error(`loadConfig: HTTP ${err.status} fetching ${url} (retries exhausted after ${err.attempts} attempts)`, { cause: err });
|
|
124
|
+
}
|
|
125
|
+
throw err;
|
|
63
126
|
}
|
|
64
|
-
|
|
65
|
-
validateConfig(raw, network, url);
|
|
66
|
-
if (opts.cache)
|
|
67
|
-
cache.set(url, raw);
|
|
127
|
+
configCache.set(cacheKey, raw);
|
|
68
128
|
return raw;
|
|
69
129
|
}
|
|
70
130
|
function validateConfig(cfg, expected, url) {
|
|
@@ -82,6 +142,9 @@ function validateConfig(cfg, expected, url) {
|
|
|
82
142
|
};
|
|
83
143
|
// Validate by deployment kind. A perp config must carry the perp set; a
|
|
84
144
|
// credit config the credit set. `waterx_account` is common to both.
|
|
145
|
+
// `pyth_lazer_rule` is intentionally NOT required (and never will be by
|
|
146
|
+
// presence alone) — it's an optional/experimental package selected only via
|
|
147
|
+
// a client's `oracleSource` option, never by its presence in the config.
|
|
85
148
|
const isPerp = "waterx_perp" in cfg.packages;
|
|
86
149
|
const isCredit = "waterx_credit" in cfg.packages || "wormhole_bridge" in cfg.packages;
|
|
87
150
|
const required = isPerp
|
package/dist/src/perp/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { PerpClient } from "./client.ts";
|
|
2
2
|
export type { CreateClientOptions } from "./client.ts";
|
|
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";
|
|
3
|
+
export { PYTH_DEFAULTS, PYTH_PRO_DEFAULTS, WORMHOLE_DEFAULTS, clearConfigCache, loadConfig, } from "./config.ts";
|
|
4
|
+
export type { BasePackageEntry, ConstantFeedEntry, WaterxReferralPackage, LoadConfigOptions, NativeCustodyAsset, NativeCustodyPackage, PythGeneration, PythInfraConfig, PythLazerRulePackage, PythRulePackage, PythSponsorRulePackage, SupraFeedEntry, SupraRulePackage, TestnetFaucetPackage, TrustedEmitterRow, 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";
|
package/dist/src/perp/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// ======== Core ========
|
|
2
2
|
export { PerpClient } from "./client.js";
|
|
3
|
-
export { PYTH_DEFAULTS, WORMHOLE_DEFAULTS, clearConfigCache, loadConfig } from "./config.js";
|
|
3
|
+
export { PYTH_DEFAULTS, PYTH_PRO_DEFAULTS, WORMHOLE_DEFAULTS, clearConfigCache, loadConfig, } from "./config.js";
|
|
4
4
|
// ======== Constants & enums ========
|
|
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.js";
|
|
6
6
|
// ======== Utilities ========
|
|
@@ -13,7 +13,7 @@ export * from "./tx-builders.js";
|
|
|
13
13
|
// ======== Read-only queries ========
|
|
14
14
|
export * from "./fetch.js";
|
|
15
15
|
// ======== Oracle utilities (Pyth source + rule aggregation) ========
|
|
16
|
-
export { PythCache, aggregateTicker, aggregateTickerWithConstant, aggregateTickerWithPyth, buildPythPriceUpdateCalls, fetchPriceFeedsUpdateData, refreshOraclePrices, updatePythPrices, } from "../oracle/index.js";
|
|
16
|
+
export { FetchPolicyError, LazerApiKeyMissingError, OracleFeeSourceUnavailableError, OracleSourceNotImplementedError, PythCache, aggregateTicker, aggregateTickerWithConstant, aggregateTickerWithPyth, buildPythPriceUpdateCalls, fetchPriceFeedsUpdateData, refreshOraclePrices, updatePythPrices, } from "../oracle/index.js";
|
|
17
17
|
// ======== Wormhole / Wormholescan utilities (credit bridge) ========
|
|
18
18
|
export { fetchDepositVaa, fetchVaa, listBridgeWithdrawalVaas, listVaasByEmitter, padEvmEmitter, toWormholescanEmitter, vaaBase64ToBytes, vaaBase64ToHex, vaaBytesToBase64, waitForVaa, } from "../account/funding/wormhole.js";
|
|
19
19
|
// ======== Generated BCS types (sui-ts-codegen) ========
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* and the request+execute envelope (optional Pyth-sponsor flow + pre-sweep).
|
|
5
5
|
*/
|
|
6
6
|
import { Transaction, type TransactionArgument } from "@mysten/sui/transactions";
|
|
7
|
-
import { PythCache } from "../../oracle/index.ts";
|
|
7
|
+
import { PythCache, type OracleFeeSource, type UpdateDataProvider } from "../../oracle/index.ts";
|
|
8
8
|
import type { PerpClient } from "../client.ts";
|
|
9
9
|
export interface CommonBuildOpts {
|
|
10
10
|
/** Append to an existing PTB instead of creating a new one. */
|
|
@@ -14,16 +14,31 @@ export interface CommonBuildOpts {
|
|
|
14
14
|
/** Share a `PythCache` across builders to avoid redundant pyth_state reads. */
|
|
15
15
|
pythCache?: PythCache;
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
17
|
+
* @deprecated No longer a fee-source decision. `wrapRequestAndExecute` now
|
|
18
|
+
* opens (and reimburses) the `pyth_sponsor_rule` Fund purely from config
|
|
19
|
+
* presence — whenever `client.config.packages.pyth_sponsor_rule` is
|
|
20
|
+
* deployed, the fund is ALWAYS opened, regardless of this flag (see
|
|
21
|
+
* `OracleFeeSourceUnavailable` in `oracle/pyth.ts`). This closes the gap
|
|
22
|
+
* where a market whose checklist required `PythSponsorRule`, or a caller
|
|
23
|
+
* that mis-set this flag, silently drew from `tx.gas` and failed
|
|
24
|
+
* ON-CHAIN instead of at build time. Use `allowGasFee` for the
|
|
25
|
+
* non-sponsored case instead. Kept accepted (as a no-op) only so existing
|
|
26
|
+
* callers keep compiling; will be removed in a future major version.
|
|
25
27
|
*/
|
|
26
28
|
useSponsor?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Explicit opt-in to draw the Pyth update fee from `tx.gas` when this
|
|
31
|
+
* client's config has no `pyth_sponsor_rule` deployed. Ignored whenever a
|
|
32
|
+
* sponsor fund IS available — the sponsor pool always wins over `tx.gas`
|
|
33
|
+
* when one can be opened (see `useSponsor`'s deprecation note above).
|
|
34
|
+
* Required for flows with no `TradingRequest` to reimburse a sponsor fund
|
|
35
|
+
* against (e.g. `buildMintWlpTx` — see its doc comment). Building an
|
|
36
|
+
* oracle refresh with neither a sponsor fund nor this flag throws
|
|
37
|
+
* `OracleFeeSourceUnavailable` instead of silently drawing from `tx.gas`
|
|
38
|
+
* (Enoki-sponsored transactions reject any `tx.gas` draw). Default:
|
|
39
|
+
* `false`.
|
|
40
|
+
*/
|
|
41
|
+
allowGasFee?: boolean;
|
|
27
42
|
/**
|
|
28
43
|
* Pre-sweep parked backing assets (USDC, USDsui, …) at the wxa account's
|
|
29
44
|
* address into USD credit, plus any CREDIT coins/funds at the address into
|
|
@@ -42,6 +57,15 @@ export interface CommonBuildOpts {
|
|
|
42
57
|
* {@link buildConsolidateToUsdTx} separately.
|
|
43
58
|
*/
|
|
44
59
|
consolidateToUsd?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* BE prefetch-cache seam for the oracle update-data fetch — forwarded
|
|
62
|
+
* verbatim into `refreshOraclePrices`'s `updateDataProvider` opt (see
|
|
63
|
+
* `UpdateDataProvider` in `oracle/price-update-rule.ts`). Default: none
|
|
64
|
+
* (always a live fetch). A caller-supplied provider that misses or throws
|
|
65
|
+
* still falls back to a live fetch — this option can only make a refresh
|
|
66
|
+
* faster, never break it.
|
|
67
|
+
*/
|
|
68
|
+
updateDataProvider?: UpdateDataProvider;
|
|
45
69
|
}
|
|
46
70
|
interface RequestParams {
|
|
47
71
|
ticker: string;
|
|
@@ -57,20 +81,33 @@ export declare function newTx(opts?: CommonBuildOpts): Transaction;
|
|
|
57
81
|
*/
|
|
58
82
|
export declare function refreshWlpPoolOracles(tx: Transaction, client: PerpClient, extraTickers: string[], opts: {
|
|
59
83
|
cache?: PythCache;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
packageId: string;
|
|
63
|
-
};
|
|
84
|
+
/** Forwarded to `refreshOraclePrices` — already resolved by the caller (see `OracleFeeSource`). */
|
|
85
|
+
feeSource?: OracleFeeSource;
|
|
64
86
|
lpType?: string;
|
|
87
|
+
updateDataProvider?: UpdateDataProvider;
|
|
65
88
|
}): Promise<void>;
|
|
66
89
|
/**
|
|
67
|
-
* Build the *Request + execute envelope with
|
|
90
|
+
* Build the *Request + execute envelope with the config-driven Pyth sponsor flow:
|
|
68
91
|
*
|
|
92
|
+
* [maybeConsolidate(tx)]
|
|
69
93
|
* [fund = sponsor.request()]
|
|
70
|
-
* refreshOraclePrices(...,
|
|
94
|
+
* refreshOraclePrices(..., feeSource?)
|
|
71
95
|
* req = buildRequest()
|
|
72
96
|
* [sponsor.reimburse(fund, req)]
|
|
73
97
|
* trading::execute(req)
|
|
98
|
+
*
|
|
99
|
+
* Accepted ordering caveat: `maybeConsolidate` runs FIRST and can itself
|
|
100
|
+
* append PTB commands (the consolidation sweep) before the fee-source check
|
|
101
|
+
* inside `refreshOraclePrices` ever runs — so an `OracleFeeSourceUnavailable`
|
|
102
|
+
* throw here is NOT the "zero commands appended" guarantee
|
|
103
|
+
* `refreshOraclePrices` gives its own callers (see its docblock in
|
|
104
|
+
* `aggregate.ts`); `tx` can already carry the sweep. This is the same
|
|
105
|
+
* discard-tx-on-throw contract every `build*Tx` composer already has for
|
|
106
|
+
* mid-build on-chain-read failures — not a new hole. It matters only for a
|
|
107
|
+
* caller that passed in their OWN `opts.tx` (reusing one `Transaction`
|
|
108
|
+
* across builder calls, e.g. to compose several actions in one PTB); such a
|
|
109
|
+
* caller must discard the whole `tx` on any throw from this function, not
|
|
110
|
+
* just retry the failed step.
|
|
74
111
|
*/
|
|
75
112
|
export declare function wrapRequestAndExecute(client: PerpClient, tx: Transaction, req: RequestParams & {
|
|
76
113
|
accountId: string;
|