@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.
Files changed (72) hide show
  1. package/README.md +36 -0
  2. package/dist/cjs/src/oracle/aggregate.d.ts +98 -17
  3. package/dist/cjs/src/oracle/aggregate.js +191 -21
  4. package/dist/cjs/src/oracle/config.d.ts +103 -0
  5. package/dist/cjs/src/oracle/config.js +64 -1
  6. package/dist/cjs/src/oracle/host.d.ts +13 -0
  7. package/dist/cjs/src/oracle/index.d.ts +19 -5
  8. package/dist/cjs/src/oracle/index.js +40 -6
  9. package/dist/cjs/src/oracle/price-update-rule.d.ts +180 -0
  10. package/dist/cjs/src/oracle/price-update-rule.js +56 -0
  11. package/dist/cjs/src/oracle/pyth.d.ts +80 -11
  12. package/dist/cjs/src/oracle/pyth.js +84 -17
  13. package/dist/cjs/src/oracle/rule-registry.d.ts +37 -0
  14. package/dist/cjs/src/oracle/rule-registry.js +61 -0
  15. package/dist/cjs/src/oracle/rules/pyth-core-rule.d.ts +15 -0
  16. package/dist/cjs/src/oracle/rules/pyth-core-rule.js +84 -0
  17. package/dist/cjs/src/oracle/rules/pyth-lazer-rule.d.ts +41 -0
  18. package/dist/cjs/src/oracle/rules/pyth-lazer-rule.js +194 -0
  19. package/dist/cjs/src/oracle/rules/sponsor.d.ts +11 -7
  20. package/dist/cjs/src/oracle/rules/sponsor.js +11 -7
  21. package/dist/cjs/src/oracle/update-fetch.d.ts +85 -0
  22. package/dist/cjs/src/oracle/update-fetch.js +228 -0
  23. package/dist/cjs/src/perp/client.d.ts +22 -1
  24. package/dist/cjs/src/perp/client.js +11 -2
  25. package/dist/cjs/src/perp/config.d.ts +9 -4
  26. package/dist/cjs/src/perp/config.js +82 -18
  27. package/dist/cjs/src/perp/index.d.ts +4 -3
  28. package/dist/cjs/src/perp/index.js +8 -3
  29. package/dist/cjs/src/perp/tx-builders/common.d.ts +52 -15
  30. package/dist/cjs/src/perp/tx-builders/common.js +39 -6
  31. package/dist/cjs/src/perp/tx-builders/wlp.d.ts +11 -3
  32. package/dist/cjs/src/perp/tx-builders/wlp.js +29 -3
  33. package/dist/cjs/src/perp/tx-builders.d.ts +3 -3
  34. package/dist/cjs/src/perp/tx-builders.js +3 -3
  35. package/dist/cjs/src/unified-client.d.ts +17 -0
  36. package/dist/cjs/src/unified-client.js +2 -0
  37. package/dist/src/oracle/aggregate.d.ts +98 -17
  38. package/dist/src/oracle/aggregate.js +192 -22
  39. package/dist/src/oracle/config.d.ts +103 -0
  40. package/dist/src/oracle/config.js +63 -0
  41. package/dist/src/oracle/host.d.ts +13 -0
  42. package/dist/src/oracle/index.d.ts +19 -5
  43. package/dist/src/oracle/index.js +34 -6
  44. package/dist/src/oracle/price-update-rule.d.ts +180 -0
  45. package/dist/src/oracle/price-update-rule.js +53 -0
  46. package/dist/src/oracle/pyth.d.ts +80 -11
  47. package/dist/src/oracle/pyth.js +82 -16
  48. package/dist/src/oracle/rule-registry.d.ts +37 -0
  49. package/dist/src/oracle/rule-registry.js +56 -0
  50. package/dist/src/oracle/rules/pyth-core-rule.d.ts +15 -0
  51. package/dist/src/oracle/rules/pyth-core-rule.js +81 -0
  52. package/dist/src/oracle/rules/pyth-lazer-rule.d.ts +41 -0
  53. package/dist/src/oracle/rules/pyth-lazer-rule.js +189 -0
  54. package/dist/src/oracle/rules/sponsor.d.ts +11 -7
  55. package/dist/src/oracle/rules/sponsor.js +11 -7
  56. package/dist/src/oracle/update-fetch.d.ts +85 -0
  57. package/dist/src/oracle/update-fetch.js +223 -0
  58. package/dist/src/perp/client.d.ts +22 -1
  59. package/dist/src/perp/client.js +12 -3
  60. package/dist/src/perp/config.d.ts +9 -4
  61. package/dist/src/perp/config.js +81 -18
  62. package/dist/src/perp/index.d.ts +4 -3
  63. package/dist/src/perp/index.js +2 -2
  64. package/dist/src/perp/tx-builders/common.d.ts +52 -15
  65. package/dist/src/perp/tx-builders/common.js +39 -6
  66. package/dist/src/perp/tx-builders/wlp.d.ts +11 -3
  67. package/dist/src/perp/tx-builders/wlp.js +29 -3
  68. package/dist/src/perp/tx-builders.d.ts +3 -3
  69. package/dist/src/perp/tx-builders.js +3 -3
  70. package/dist/src/unified-client.d.ts +17 -0
  71. package/dist/src/unified-client.js +2 -0
  72. package/package.json +1 -1
@@ -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
- * Wire the `pyth_sponsor_rule` flow:
18
- * - open a Fund via `pyth_sponsor_rule::request(sponsor)`
19
- * - pay Pyth update fees from the Fund (instead of `tx.gas`)
20
- * - reimburse leftover into the sponsor pool + attach
21
- * `PythSponsorRule` witness to the TradingRequest so markets
22
- * whose `request_checklist` contains `PythSponsorRule` accept it
23
- * Default: true (matches the testnet/mainnet checklist config).
24
- * Pass `false` only when the market checklist is empty.
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
- sponsorFund?: {
61
- fund: TransactionArgument;
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 optional Pyth sponsor flow:
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(..., sponsorFund?)
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;
@@ -29,32 +29,65 @@ async function refreshWlpPoolOracles(tx, client, extraTickers, opts) {
29
29
  const oracleTickers = Array.from(new Set([...extraTickers, ...poolTickers]));
30
30
  await (0, index_ts_1.refreshOraclePrices)(tx, client, oracleTickers, {
31
31
  cache: opts.cache,
32
- sponsorFund: opts.sponsorFund,
32
+ feeSource: opts.feeSource,
33
+ updateDataProvider: opts.updateDataProvider,
33
34
  });
34
35
  for (const tokenType of Object.values(client.config.packages.wlp.pool_tokens)) {
35
36
  (0, wlp_ts_1.updateTokenValue)(client, tx, { tokenType, lpType: opts.lpType });
36
37
  }
37
38
  }
38
39
  /**
39
- * Build the *Request + execute envelope with optional Pyth sponsor flow:
40
+ * Build the *Request + execute envelope with the config-driven Pyth sponsor flow:
40
41
  *
42
+ * [maybeConsolidate(tx)]
41
43
  * [fund = sponsor.request()]
42
- * refreshOraclePrices(..., sponsorFund?)
44
+ * refreshOraclePrices(..., feeSource?)
43
45
  * req = buildRequest()
44
46
  * [sponsor.reimburse(fund, req)]
45
47
  * trading::execute(req)
48
+ *
49
+ * Accepted ordering caveat: `maybeConsolidate` runs FIRST and can itself
50
+ * append PTB commands (the consolidation sweep) before the fee-source check
51
+ * inside `refreshOraclePrices` ever runs — so an `OracleFeeSourceUnavailable`
52
+ * throw here is NOT the "zero commands appended" guarantee
53
+ * `refreshOraclePrices` gives its own callers (see its docblock in
54
+ * `aggregate.ts`); `tx` can already carry the sweep. This is the same
55
+ * discard-tx-on-throw contract every `build*Tx` composer already has for
56
+ * mid-build on-chain-read failures — not a new hole. It matters only for a
57
+ * caller that passed in their OWN `opts.tx` (reusing one `Transaction`
58
+ * across builder calls, e.g. to compose several actions in one PTB); such a
59
+ * caller must discard the whole `tx` on any throw from this function, not
60
+ * just retry the failed step.
46
61
  */
47
62
  async function wrapRequestAndExecute(client, tx, req, collateralTicker, opts, buildRequest) {
48
63
  await maybeConsolidate(client, tx, req.accountId, opts);
49
- const useSponsor = opts?.useSponsor ?? true;
64
+ // Fee source + witness attachment is config-driven, not a caller flag: the
65
+ // sponsor fund is opened (and later reimbursed) whenever this client's
66
+ // config has `pyth_sponsor_rule` deployed — regardless of the deprecated
67
+ // `useSponsor` flag (see its JSDoc). `allowGasFee` is the only caller lever
68
+ // left, and it only matters when config has NO sponsor rule to open (see
69
+ // `OracleFeeSourceUnavailable` in `oracle/pyth.ts`).
70
+ //
71
+ // `feeSource` is resolved HERE, once, from that same decision — sponsor
72
+ // beats gas structurally because this is the only branch that ever sees
73
+ // both candidates; everything downstream (`refreshWlpPoolOracles` →
74
+ // `refreshOraclePrices` → `BuildUpdateOpts` → `PythCoreRule` →
75
+ // `buildPythPriceUpdateCalls`) just carries the single resolved value.
50
76
  let sponsorFund;
51
- if (useSponsor)
77
+ if (client.config.packages.pyth_sponsor_rule) {
52
78
  sponsorFund = (0, index_ts_1.openPythSponsorFund)(tx, client);
79
+ }
80
+ const feeSource = sponsorFund
81
+ ? { kind: "sponsor", ...sponsorFund }
82
+ : opts?.allowGasFee
83
+ ? { kind: "gas" }
84
+ : undefined;
53
85
  if (!opts?.skipOraclePriceRefresh) {
54
86
  await refreshWlpPoolOracles(tx, client, [req.ticker, collateralTicker], {
55
87
  cache: opts?.pythCache,
56
- sponsorFund,
88
+ feeSource,
57
89
  lpType: req.lpType,
90
+ updateDataProvider: opts?.updateDataProvider,
58
91
  });
59
92
  }
60
93
  const tradingReq = buildRequest(sponsorFund);
@@ -17,9 +17,15 @@ export interface BuildMintWlpParams extends MintWlpParams, CommonBuildOpts {
17
17
  * `last_price_refresh_timestamp` so `assert_prices_fresh` inside
18
18
  * `mint_wlp` passes.
19
19
  *
20
- * Does NOT use the pyth_sponsor flow — `mint_wlp` produces no
20
+ * Never uses the pyth_sponsor flow — `mint_wlp` produces no
21
21
  * `TradingRequest`, so there's nothing for the sponsor to attach its
22
- * witness to. Pyth update fees come from `tx.gas`.
22
+ * witness to, and `pyth_sponsor_rule::reimburse` cannot consume a Fund
23
+ * without one. So when `skipOraclePriceRefresh` is `false` (the refresh
24
+ * actually runs), the caller MUST pass `allowGasFee: true` — the Pyth
25
+ * update fee is drawn from `tx.gas`, which Enoki-sponsored transactions
26
+ * reject; a sponsored caller should keep `skipOraclePriceRefresh: true`
27
+ * instead and rely on freshness from other trade traffic (see
28
+ * `OracleFeeSourceUnavailable` in `oracle/pyth.ts`).
23
29
  */
24
30
  export declare function buildMintWlpTx(client: PerpClient, params: BuildMintWlpParams): Promise<Transaction>;
25
31
  export interface BuildMintAndStakeWlpParams extends BuildMintWlpParams {
@@ -62,7 +68,9 @@ export interface BuildUnstakeAndRequestRedeemWlpParams extends Omit<RequestRedee
62
68
  * Refreshes every WLP pool-token oracle by default — `request_redeem` runs
63
69
  * `assert_prices_fresh` internally, so a stale oracle would abort the PTB.
64
70
  * Pass `skipOraclePriceRefresh: true` only when the caller is composing this
65
- * into a larger PTB that already pre-pumps prices.
71
+ * into a larger PTB that already pre-pumps prices. Like `buildMintWlpTx`,
72
+ * `request_redeem` produces no `TradingRequest`, so a non-skipped refresh
73
+ * requires `allowGasFee: true` (see `buildMintWlpTx`'s doc comment).
66
74
  */
67
75
  export declare function buildUnstakeAndRequestRedeemWlpTx(client: PerpClient, params: BuildUnstakeAndRequestRedeemWlpParams): Promise<Transaction>;
68
76
  export interface BuildCancelRedeemAndStakeWlpParams extends CancelRedeemWlpParams, CommonBuildOpts {
@@ -18,17 +18,29 @@ const common_ts_1 = require("./common.js");
18
18
  * `last_price_refresh_timestamp` so `assert_prices_fresh` inside
19
19
  * `mint_wlp` passes.
20
20
  *
21
- * Does NOT use the pyth_sponsor flow — `mint_wlp` produces no
21
+ * Never uses the pyth_sponsor flow — `mint_wlp` produces no
22
22
  * `TradingRequest`, so there's nothing for the sponsor to attach its
23
- * witness to. Pyth update fees come from `tx.gas`.
23
+ * witness to, and `pyth_sponsor_rule::reimburse` cannot consume a Fund
24
+ * without one. So when `skipOraclePriceRefresh` is `false` (the refresh
25
+ * actually runs), the caller MUST pass `allowGasFee: true` — the Pyth
26
+ * update fee is drawn from `tx.gas`, which Enoki-sponsored transactions
27
+ * reject; a sponsored caller should keep `skipOraclePriceRefresh: true`
28
+ * instead and rely on freshness from other trade traffic (see
29
+ * `OracleFeeSourceUnavailable` in `oracle/pyth.ts`).
24
30
  */
25
31
  async function buildMintWlpTx(client, params) {
26
32
  const tx = (0, common_ts_1.newTx)(params);
27
33
  await (0, common_ts_1.maybeConsolidate)(client, tx, params.accountId, params);
34
+ // `mint_wlp` has no sponsor flow to resolve against (see the doc comment
35
+ // above) — the only candidate source at this edge is the caller's
36
+ // ergonomic `allowGasFee` opt-in.
37
+ const feeSource = params.allowGasFee ? { kind: "gas" } : undefined;
28
38
  if (!params.skipOraclePriceRefresh) {
29
39
  await (0, common_ts_1.refreshWlpPoolOracles)(tx, client, [params.depositTicker], {
30
40
  cache: params.pythCache,
31
41
  lpType: params.lpType,
42
+ feeSource,
43
+ updateDataProvider: params.updateDataProvider,
32
44
  });
33
45
  }
34
46
  (0, wlp_ts_1.mintWlp)(client, tx, params);
@@ -46,10 +58,16 @@ async function buildMintWlpTx(client, params) {
46
58
  async function buildMintAndStakeWlpTx(client, params) {
47
59
  const tx = (0, common_ts_1.newTx)(params);
48
60
  await (0, common_ts_1.maybeConsolidate)(client, tx, params.accountId, params);
61
+ // `mint_wlp` has no sponsor flow to resolve against (see `buildMintWlpTx`'s
62
+ // doc comment) — the only candidate source at this edge is the caller's
63
+ // ergonomic `allowGasFee` opt-in.
64
+ const feeSource = params.allowGasFee ? { kind: "gas" } : undefined;
49
65
  if (!params.skipOraclePriceRefresh) {
50
66
  await (0, common_ts_1.refreshWlpPoolOracles)(tx, client, [params.depositTicker], {
51
67
  cache: params.pythCache,
52
68
  lpType: params.lpType,
69
+ feeSource,
70
+ updateDataProvider: params.updateDataProvider,
53
71
  });
54
72
  }
55
73
  const stakeAlias = params.stakeAlias ?? "WLP";
@@ -71,16 +89,24 @@ async function buildMintAndStakeWlpTx(client, params) {
71
89
  * Refreshes every WLP pool-token oracle by default — `request_redeem` runs
72
90
  * `assert_prices_fresh` internally, so a stale oracle would abort the PTB.
73
91
  * Pass `skipOraclePriceRefresh: true` only when the caller is composing this
74
- * into a larger PTB that already pre-pumps prices.
92
+ * into a larger PTB that already pre-pumps prices. Like `buildMintWlpTx`,
93
+ * `request_redeem` produces no `TradingRequest`, so a non-skipped refresh
94
+ * requires `allowGasFee: true` (see `buildMintWlpTx`'s doc comment).
75
95
  */
76
96
  async function buildUnstakeAndRequestRedeemWlpTx(client, params) {
77
97
  const tx = (0, common_ts_1.newTx)(params);
78
98
  const stakeAlias = params.stakeAlias ?? "WLP";
79
99
  await (0, common_ts_1.maybeConsolidate)(client, tx, params.accountId, params);
100
+ // `request_redeem` has no sponsor flow to resolve against (see
101
+ // `buildMintWlpTx`'s doc comment) — the only candidate source at this edge
102
+ // is the caller's ergonomic `allowGasFee` opt-in.
103
+ const feeSource = params.allowGasFee ? { kind: "gas" } : undefined;
80
104
  if (!params.skipOraclePriceRefresh) {
81
105
  await (0, common_ts_1.refreshWlpPoolOracles)(tx, client, [], {
82
106
  cache: params.pythCache,
83
107
  lpType: params.lpType,
108
+ feeSource,
109
+ updateDataProvider: params.updateDataProvider,
84
110
  });
85
111
  }
86
112
  (0, staking_ts_1.unstake)(client, tx, {
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * Each `build*Tx` composer creates (or appends to) a `Transaction`, refreshes
5
5
  * the on-chain `Oracle` via Pyth, optionally pre-sweeps parked balances
6
- * (`consolidateToUsd`), wires the `pyth_sponsor_rule` flow when enabled, and
7
- * calls the matching `*_request` + `execute`. Implementations are split by
8
- * domain under `tx-builders/`:
6
+ * (`consolidateToUsd`), wires the `pyth_sponsor_rule` flow when deployed in
7
+ * config, and calls the matching `*_request` + `execute`. Implementations are
8
+ * split by domain under `tx-builders/`:
9
9
  *
10
10
  * common.ts CommonBuildOpts + request/execute envelope + oracle refresh
11
11
  * consolidate.ts parked-balance → wxUSD pre-sweep (appendConsolidate*)
@@ -4,9 +4,9 @@
4
4
  *
5
5
  * Each `build*Tx` composer creates (or appends to) a `Transaction`, refreshes
6
6
  * the on-chain `Oracle` via Pyth, optionally pre-sweeps parked balances
7
- * (`consolidateToUsd`), wires the `pyth_sponsor_rule` flow when enabled, and
8
- * calls the matching `*_request` + `execute`. Implementations are split by
9
- * domain under `tx-builders/`:
7
+ * (`consolidateToUsd`), wires the `pyth_sponsor_rule` flow when deployed in
8
+ * config, and calls the matching `*_request` + `execute`. Implementations are
9
+ * split by domain under `tx-builders/`:
10
10
  *
11
11
  * common.ts CommonBuildOpts + request/execute envelope + oracle refresh
12
12
  * consolidate.ts parked-balance → wxUSD pre-sweep (appendConsolidate*)
@@ -32,6 +32,8 @@ import { Transaction } from "@mysten/sui/transactions";
32
32
  import * as accountOps from "./account/index.ts";
33
33
  import * as perpReferral from "./account/referral.ts";
34
34
  import type { Network } from "./constants.ts";
35
+ import type { PythGeneration } from "./oracle/config.ts";
36
+ import type { OracleSource } from "./oracle/price-update-rule.ts";
35
37
  import { PerpClient, type CreateClientOptions as PerpCreateOptions } from "./perp/client.ts";
36
38
  import * as perpFetch from "./perp/fetch.ts";
37
39
  import * as perpTx from "./perp/tx-builders.ts";
@@ -363,6 +365,21 @@ export interface ClientCreateOptions {
363
365
  waterxConfigUrl?: string;
364
366
  /** Memoize the fetched config JSON. */
365
367
  cache?: boolean;
368
+ /**
369
+ * Selects which `PriceUpdateRule` the perp line's `refreshOraclePrices` uses
370
+ * for the on-chain price-update leg (see `OracleHost.oracleSource`).
371
+ * Default: `'pyth_rule'`. Perp-line only. The SDK never reads `process.env`
372
+ * — pass this from your own env var (e.g. `ORACLE_SOURCE`).
373
+ */
374
+ oracleSource?: OracleSource;
375
+ /**
376
+ * Selects which Pyth Core contract generation feeds the perp line's
377
+ * `client.perp.pyth` when the config JSON has no explicit `pyth` override:
378
+ * `'core'` (default) or `'pro'` (post-2026-08-18 Pro-compatible contracts +
379
+ * Hermes-compatible endpoint; pair with `pyth.api_key`). Perp-line only.
380
+ * See `PythGeneration` / `PYTH_PRO_DEFAULTS`.
381
+ */
382
+ pythGeneration?: PythGeneration;
366
383
  /** Perp-line overrides (network, grpcUrl, waterxConfigUrl, cache, …). */
367
384
  perp?: PerpLineOptions;
368
385
  /** Prediction-line overrides (network, grpcUrl, waterxConfigUrl, cache, settlement, …). */
@@ -204,6 +204,8 @@ class WaterXClient {
204
204
  grpcUrl: opts.grpcUrl,
205
205
  waterxConfigUrl: opts.waterxConfigUrl,
206
206
  cache: opts.cache,
207
+ oracleSource: opts.oracleSource,
208
+ pythGeneration: opts.pythGeneration,
207
209
  ...perpRest,
208
210
  });
209
211
  const predictClient = await client_ts_2.PredictClient.create(resolvedPredictNetwork, {
@@ -2,40 +2,58 @@
2
2
  * Oracle aggregation — the orchestrator that composes rules into the shared
3
3
  * `Oracle`. This is the ONE file that knows about every rule: it builds a
4
4
  * `PriceCollector`, feeds whichever rules a ticker is configured for
5
- * (Pyth / Supra / Constant), then `aggregate`s.
5
+ * (Pyth / Lazer / Supra / Constant), then `aggregate`s.
6
6
  *
7
7
  * Per ticker:
8
8
  * collector = oracle::new_collector(ticker)
9
9
  * [pyth_rule::feed] when the ticker has a pyth_rule.feeds entry
10
+ * [pyth_lazer_rule::feed] when the update leg produced a verified lazer Update
10
11
  * [supra_rule::feed] when supra is enabled + wired
11
12
  * [constant_rule::feed] when the ticker is a constant ticker
12
13
  * oracle::aggregate(oracle, collector)
13
14
  *
14
- * The fed rule set must match the on-chain weighted set for the ticker —
15
+ * The fed rule set must cover the on-chain weighted set for the ticker —
15
16
  * `aggregator::remove_outliers` aborts `EMissingPriceSource` if a weighted rule
16
- * is missing from the collector.
17
+ * is missing from the collector (an abstaining feed call counts as present;
18
+ * a fed-but-unweighted rule is silently dropped).
19
+ *
20
+ * `refreshOraclePrices` additionally routes the on-chain price *update* leg
21
+ * (the fetch + verify/push step, before any of the above feeding) through the
22
+ * `PriceUpdateRule` selected by `host.oracleSource` — see `rule-registry.ts`.
17
23
  */
18
24
  import type { Transaction, TransactionArgument } from "@mysten/sui/transactions";
19
25
  import type { OracleHost } from "./host.ts";
20
- import { type PythCache } from "./pyth.ts";
26
+ import type { OracleSource, PriceUpdateRule, UpdateDataProvider } from "./price-update-rule.ts";
27
+ import { type OracleFeeSource, type PythCache } from "./pyth.ts";
21
28
  /**
22
29
  * Aggregate one ticker's price into the shared `Oracle`: build a collector, feed
23
30
  * every rule the ticker is configured for, then `aggregate`.
24
31
  *
25
32
  * - **Pyth** — fed when `priceInfoObjectId` is supplied (i.e. the ticker has a
26
- * `pyth_rule.feeds` entry). Caller must run the Pyth update first so the
27
- * `PriceInfoObject` is fresh.
28
- * - **Supra** fed alongside Pyth when supra is enabled + wired (abstains
29
- * on-chain for symbols it has no pair for).
33
+ * `pyth_rule.feeds` entry). When this PTB's update leg refreshed the
34
+ * `PriceInfoObject` it contributes a fresh price; when it did not (a
35
+ * lazer-routed ticker), the on-chain rule only READS the object and abstains
36
+ * if it is stale — it never aborts so the call stays mandatory while
37
+ * `pyth_rule` remains in the ticker's on-chain weighted set
38
+ * (`EMissingPriceSource` requires every weighted rule to appear).
39
+ * - **Lazer** — fed when `lazerUpdate` is supplied: the verified
40
+ * `pyth_lazer::update::Update` produced by this PTB's lazer update leg
41
+ * (see `PythLazerRule.buildUpdateCalls`). If the ticker's aggregator does
42
+ * not (yet) weight `PythLazerRule`, the contribution is silently dropped
43
+ * on-chain — feeding ahead of the weight migration is harmless.
44
+ * - **Supra** — fed alongside Pyth/Lazer when supra is enabled + wired
45
+ * (abstains on-chain for symbols it has no pair for).
30
46
  * - **Constant** — fed when the ticker is a constant ticker
31
47
  * ({@link OracleHost.isConstantTicker}).
32
48
  *
33
- * "Dual-feed" (Pyth + Constant) and "constant-only" are not special cases — they
34
- * fall out of which rules the ticker is in. Throws if no rule applies.
49
+ * "Dual-feed" (Pyth + Constant, or Pyth + Lazer) and "constant-only" are not
50
+ * special cases — they fall out of which rules the ticker is in. Throws if no
51
+ * rule applies.
35
52
  */
36
53
  export declare function aggregateTicker(tx: Transaction, host: OracleHost, args: {
37
54
  ticker: string;
38
55
  priceInfoObjectId?: string;
56
+ lazerUpdate?: TransactionArgument;
39
57
  }): void;
40
58
  /**
41
59
  * Thin wrapper over {@link aggregateTicker} for a Pyth-fed ticker. Kept for
@@ -61,14 +79,77 @@ export declare function aggregateTickerWithConstant(tx: Transaction, host: Oracl
61
79
  /**
62
80
  * Refresh multiple tickers in one PTB. For each ticker {@link aggregateTicker}
63
81
  * feeds whichever rules it is configured for (Pyth if it has a `pyth_rule.feeds`
64
- * entry, Supra when enabled, Constant when it's a constant ticker). Tickers with a
65
- * Pyth feed are updated on-chain via one shared Pyth accumulator first; the rest
66
- * (constant-only) skip Pyth entirely.
82
+ * entry, Lazer if the lazer update leg served it see below Supra when
83
+ * enabled, Constant when it's a constant ticker).
84
+ *
85
+ * Before that, the on-chain price *update* leg is routed by `host.oracleSource`
86
+ * (see `rule-registry.ts`): the selected rule serves every ticker in its
87
+ * `supportedTickers(host)`; tickers it doesn't cover fall back to `pyth_rule`
88
+ * (`PythCoreRule`) when THEY support it — so when `oracleSource` IS `'pyth_rule'`
89
+ * there is exactly one group, identical to the pre-routing behavior. A ticker
90
+ * supported by neither is simply skipped from this leg (no fetch/build call for
91
+ * it) — the same way today's non-pyth tickers (e.g. constant-only) always were;
92
+ * it still gets aggregated below via whichever rule {@link aggregateTicker} finds.
93
+ *
94
+ * Each group's fetch + build runs against its own rule, which guarantees
95
+ * per-rule PTB atomicity (no mixed-generation payload within one rule's calls).
96
+ * When `oracleSource` isn't `'pyth_rule'`, one PTB may legitimately carry BOTH a
97
+ * non-Pyth-Core block (selected group) and a Pyth Core block (fallback group) —
98
+ * each verifies against its own contract objects, so that's fine. A fee-source
99
+ * pre-check runs first, across every group's `requiresFeeSource` — BEFORE any
100
+ * off-chain fetch or PTB mutation — so a fee-charging group with no
101
+ * `opts.feeSource` throws `OracleFeeSourceUnavailable` with zero wasted
102
+ * network calls and zero stray moveCalls, even in a mixed shape (e.g. a
103
+ * fee-free Lazer group ordered ahead of a Pyth Core fallback group). Only once
104
+ * that check passes do all groups' off-chain fetches run concurrently
105
+ * (`Promise.all`) and complete before any PTB mutation; on-chain reads inside
106
+ * `buildUpdateCalls` can still fail mid-append for other reasons — callers
107
+ * discard the tx on any throw.
108
+ *
109
+ * **Collector-feed leg is rule-aware:** a lazer-served group's
110
+ * `buildUpdateCalls` returns the verified `Update` PTB value
111
+ * ({@link RuleUpdateHandle}), and every ticker in that group is aggregated
112
+ * with `lazerUpdate` set so {@link aggregateTicker} appends
113
+ * `pyth_lazer_rule::feed` against it. A lazer-routed ticker that still has a
114
+ * `pyth_rule.feeds` entry ALSO keeps its `pyth_rule::feed` leg — required
115
+ * on-chain while `pyth_rule` stays in the ticker's weighted set
116
+ * (`aggregator::remove_outliers` aborts `EMissingPriceSource` unless every
117
+ * weighted rule appears in the collector; an abstention counts as
118
+ * appearing), and safe: `pyth_rule::feed` only READS the `PriceInfoObject`
119
+ * this PTB never refreshed and abstains when it is stale rather than
120
+ * aborting. Conversely, a lazer feed call on an aggregator that does not
121
+ * (yet) weight `PythLazerRule` is silently dropped on-chain — so
122
+ * lazer-routing a ticker ahead of its on-chain weight migration prices it
123
+ * from the remaining weighted rules instead of failing.
67
124
  */
68
125
  export declare function refreshOraclePrices(tx: Transaction, host: OracleHost, tickers: string[], opts?: {
69
126
  cache?: PythCache;
70
- sponsorFund?: {
71
- fund: TransactionArgument;
72
- packageId: string;
73
- };
127
+ /**
128
+ * The single resolved fee source for the Pyth update fee, forwarded
129
+ * verbatim to each group's `PriceUpdateRule.buildUpdateCalls` as
130
+ * `BuildUpdateOpts.feeSource`. Already-resolved by the caller (see
131
+ * {@link OracleFeeSource}'s own doc for where/how) — this function makes
132
+ * no sponsor-vs-gas decision itself, it only checks whether a source was
133
+ * resolved at all. Ignored by rules with no update fee (e.g.
134
+ * `pyth_lazer_rule`). Building with `feeSource` unset throws
135
+ * `OracleFeeSourceUnavailable` (see `oracle/pyth.ts`) instead of
136
+ * silently drawing from `tx.gas`.
137
+ */
138
+ feeSource?: OracleFeeSource;
139
+ /**
140
+ * @internal Test-only: layer fake `PriceUpdateRule`s on top of the
141
+ * production registry (see `rule-registry.ts`'s `resolveOracleRule`).
142
+ * Production callers never set this — routing is by `host.oracleSource`
143
+ * alone.
144
+ */
145
+ ruleOverrides?: Partial<Record<OracleSource, PriceUpdateRule>>;
146
+ /**
147
+ * BE prefetch-cache seam: checked per group BEFORE that group's live
148
+ * `rule.fetchUpdateData`. See {@link UpdateDataProvider}. A cache miss
149
+ * (`null`) or a throw from the provider falls back to the live fetch —
150
+ * a degraded/broken cache must never break the money path; a
151
+ * kind-mismatched hit (the provider handed back the wrong rule's
152
+ * payload) throws instead, since that is a caller bug, not a cache miss.
153
+ */
154
+ updateDataProvider?: UpdateDataProvider;
74
155
  }): Promise<void>;