@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
@@ -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 { PythFetchPolicy } 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,29 @@ export interface ClientCreateOptions {
363
365
  waterxConfigUrl?: string;
364
366
  /** Memoize the fetched config JSON. */
365
367
  cache?: boolean;
368
+ /**
369
+ * The perp line's oracle price-update source (perp-line only — the
370
+ * prediction line has no oracle leg), forwarded to `PerpClient.create`.
371
+ * Source-neutral by design: a future source need not be Pyth.
372
+ *
373
+ * - `'pyth_rule'` (default) — Pyth Core updates on Core infra.
374
+ * - `'pyth_lazer_rule'` — Pyth Lazer signed updates (pair with `pythApiKey`
375
+ * and a config carrying `packages.pyth_lazer_rule`).
376
+ *
377
+ * Each source is self-contained with no cross-source fallback; selecting a
378
+ * source whose feed for a ticker is absent fails at tx-build (not at init).
379
+ * See perp `CreateClientOptions.oracleSource` for the full note.
380
+ */
381
+ oracleSource?: OracleSource;
382
+ /**
383
+ * Pyth Lazer access token, forwarded to the perp line. Required under
384
+ * `oracleSource: 'pyth_lazer_rule'`, unused by `'pyth_rule'`. A SECRET —
385
+ * pass it at init from your own env var; it is never read from the config
386
+ * JSON or `process.env`.
387
+ */
388
+ pythApiKey?: string;
389
+ /** Retry/timeout policy for the perp line's off-chain oracle fetches. */
390
+ pythFetch?: PythFetchPolicy;
366
391
  /** Perp-line overrides (network, grpcUrl, waterxConfigUrl, cache, …). */
367
392
  perp?: PerpLineOptions;
368
393
  /** Prediction-line overrides (network, grpcUrl, waterxConfigUrl, cache, settlement, …). */
@@ -204,6 +204,9 @@ class WaterXClient {
204
204
  grpcUrl: opts.grpcUrl,
205
205
  waterxConfigUrl: opts.waterxConfigUrl,
206
206
  cache: opts.cache,
207
+ oracleSource: opts.oracleSource,
208
+ pythApiKey: opts.pythApiKey,
209
+ pythFetch: opts.pythFetch,
207
210
  ...perpRest,
208
211
  });
209
212
  const predictClient = await client_ts_2.PredictClient.create(resolvedPredictNetwork, {
@@ -48,28 +48,12 @@ export interface NativeCustodyPackage {
48
48
  /** Backing assets registered via `add_asset` (array, identified by `type`). */
49
49
  assets: NativeCustodyAsset[];
50
50
  }
51
- export interface TrustedEmitterRow {
52
- /** Source EVM chain's Wormhole chain id (e.g. 10002 = Sepolia). */
53
- chain_id: number;
54
- /** 32-byte left-padded EVM bridge address (0x form). */
55
- evm_bridge_address_32b: string;
56
- /** Whitelisted 20-byte EVM token addresses (0x form). */
57
- evm_tokens_20b: string[];
58
- }
59
51
  export interface WormholeBridgePackage {
60
52
  published_at: string;
61
53
  /** Shared Sui Wormhole `State` object id for this deployment. */
62
54
  wormhole_state: string;
63
- hourly_mint_limit?: string;
64
55
  max_mint_per_tx?: string;
65
- hourly_burn_limit?: string;
66
56
  max_burn_per_tx?: string;
67
- /**
68
- * @deprecated EVM emitter↔token config now lives solely under `evm.bridge.chains`
69
- * (deposit_vault = emitter, wormhole_chain_id = chain key). The runtime allowlist is
70
- * read from the on-chain `Bridge` object, not from config. Kept optional for back-compat.
71
- */
72
- trusted_emitters?: TrustedEmitterRow[];
73
57
  /** Shared `Bridge` (phase-5 output). */
74
58
  bridge?: string;
75
59
  /**
@@ -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,75 @@ 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 ONE selected source serves every ticker in its
87
+ * `supportedTickers(host)`. There is **no cross-source fallback** — a requested
88
+ * ticker the selected source does not serve, and that is not a constant-only
89
+ * ticker (which needs no price-update leg), fails the build immediately with a
90
+ * clear error naming the ticker and source. That is the deliberate "fail the
91
+ * tx-build, don't silently reroute" contract: a wrong-but-present feed id is
92
+ * NOT validated here (it surfaces on-chain at dry-run); a MISSING feed for the
93
+ * selected source is caught here. When the selected source's feed exists but is
94
+ * wrong, this function does nothing special — the on-chain aggregate aborts at
95
+ * dry-run, which is correct.
96
+ *
97
+ * The selected source's fetch + build runs against its own infra, guaranteeing
98
+ * per-rule PTB atomicity. A fee-source pre-check runs first (the source's
99
+ * `requiresFeeSource`) BEFORE any off-chain fetch or PTB mutation — so a
100
+ * fee-charging source with no `opts.feeSource` throws
101
+ * `OracleFeeSourceUnavailable` with zero wasted network calls and zero stray
102
+ * moveCalls. Only once that check passes does the off-chain fetch run and
103
+ * complete before any PTB mutation; on-chain reads inside `buildUpdateCalls`
104
+ * can still fail mid-append for other reasons — callers discard the tx on any
105
+ * throw.
106
+ *
107
+ * **Collector-feed leg is rule-aware:** a lazer-served group's
108
+ * `buildUpdateCalls` returns the verified `Update` PTB value
109
+ * ({@link RuleUpdateHandle}), and every ticker in that group is aggregated
110
+ * with `lazerUpdate` set so {@link aggregateTicker} appends
111
+ * `pyth_lazer_rule::feed` against it. A lazer-routed ticker that still has a
112
+ * `pyth_rule.feeds` entry ALSO keeps its `pyth_rule::feed` leg — required
113
+ * on-chain while `pyth_rule` stays in the ticker's weighted set
114
+ * (`aggregator::remove_outliers` aborts `EMissingPriceSource` unless every
115
+ * weighted rule appears in the collector; an abstention counts as
116
+ * appearing), and safe: `pyth_rule::feed` only READS the `PriceInfoObject`
117
+ * this PTB never refreshed and abstains when it is stale rather than
118
+ * aborting. Conversely, a lazer feed call on an aggregator that does not
119
+ * (yet) weight `PythLazerRule` is silently dropped on-chain — so
120
+ * lazer-routing a ticker ahead of its on-chain weight migration prices it
121
+ * from the remaining weighted rules instead of failing.
67
122
  */
68
123
  export declare function refreshOraclePrices(tx: Transaction, host: OracleHost, tickers: string[], opts?: {
69
124
  cache?: PythCache;
70
- sponsorFund?: {
71
- fund: TransactionArgument;
72
- packageId: string;
73
- };
125
+ /**
126
+ * The single resolved fee source for the Pyth update fee, forwarded
127
+ * verbatim to each group's `PriceUpdateRule.buildUpdateCalls` as
128
+ * `BuildUpdateOpts.feeSource`. Already-resolved by the caller (see
129
+ * {@link OracleFeeSource}'s own doc for where/how) — this function makes
130
+ * no sponsor-vs-gas decision itself, it only checks whether a source was
131
+ * resolved at all. Ignored by rules with no update fee (e.g.
132
+ * `pyth_lazer_rule`). Building with `feeSource` unset throws
133
+ * `OracleFeeSourceUnavailable` (see `oracle/pyth.ts`) instead of
134
+ * silently drawing from `tx.gas`.
135
+ */
136
+ feeSource?: OracleFeeSource;
137
+ /**
138
+ * @internal Test-only: layer fake `PriceUpdateRule`s on top of the
139
+ * production registry (see `rule-registry.ts`'s `resolveOracleRule`).
140
+ * Production callers never set this — routing is by `host.oracleSource`
141
+ * alone.
142
+ */
143
+ ruleOverrides?: Partial<Record<OracleSource, PriceUpdateRule>>;
144
+ /**
145
+ * BE prefetch-cache seam: checked per group BEFORE that group's live
146
+ * `rule.fetchUpdateData`. See {@link UpdateDataProvider}. A cache miss
147
+ * (`null`) or a throw from the provider falls back to the live fetch —
148
+ * a degraded/broken cache must never break the money path; a
149
+ * kind-mismatched hit (the provider handed back the wrong rule's
150
+ * payload) throws instead, since that is a caller bug, not a cache miss.
151
+ */
152
+ updateDataProvider?: UpdateDataProvider;
74
153
  }): Promise<void>;