@waterx/sdk 4.3.2 → 4.3.3

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.
@@ -32,10 +32,11 @@ export interface CreateClientOptions extends LoadConfigOptions {
32
32
  * and a `pythApiKey` (Lazer is auth-first); Lazer infra lives in the
33
33
  * source's own `LAZER_INFRA` table.
34
34
  * - `'waterx_rule'` — the first-party WaterX quote-center (Nautilus-TEE,
35
- * ed25519-signed batches): ONE envelope covering the build's tickers,
36
- * verified AND fed by a single `collect_batch_latest` per collector. No
37
- * credential and no per-update fee; needs `packages.waterx_rule` with
38
- * feeds. Quote-center infra lives in the source's own `WATERX_INFRA`
35
+ * ed25519-signed prices): one signed Merkle LEAF per ticker, verified AND
36
+ * fed by a single `collect_single_with_proof` per collector (falling back to
37
+ * one whole-batch envelope + `collect_batch_latest` against a quote-center
38
+ * with no leaf route). No credential and no per-update fee; needs
39
+ * `packages.waterx_rule` with feeds. Quote-center infra lives in the source's own `WATERX_INFRA`
39
40
  * table; endpoint/transport overridable via
40
41
  * {@link CreateClientOptions.waterxEndpoint} /
41
42
  * {@link CreateClientOptions.waterxFetch} — the browser-CORS proxy hook,
@@ -72,15 +73,16 @@ export interface CreateClientOptions extends LoadConfigOptions {
72
73
  * Quote-center base URL for `oracleSource: 'waterx_rule'` — overrides the
73
74
  * source's own per-network `WATERX_INFRA` default.
74
75
  *
75
- * This is the one source a BROWSER fetches itself (the signed envelope is
76
- * pulled from the page), so it is bound by the quote-center deployment's CORS
76
+ * This is the one source a BROWSER fetches itself (the signed price is pulled
77
+ * from the page), so it is bound by the quote-center deployment's CORS
77
78
  * allowlist. A front end whose origin is not allowed — or one that must route
78
79
  * egress through its own backend — points this at a same-origin proxy that
79
- * forwards `GET /v1/quotes/update`. Unused by the Pyth sources.
80
+ * forwards `GET /v1/quotes/leaves` (and `GET /v1/quotes/update`, the fallback
81
+ * route). Unused by the Pyth sources.
80
82
  *
81
83
  * An absolute URL. Any base PATH is preserved (`joinEndpointPath`), so
82
84
  * `https://app.example/api/quote-center` fetches
83
- * `https://app.example/api/quote-center/v1/quotes/update` — a proxy route
85
+ * `https://app.example/api/quote-center/v1/quotes/leaves` — a proxy route
84
86
  * survives instead of being rewritten to the origin root.
85
87
  */
86
88
  waterxEndpoint?: string;
@@ -13,8 +13,8 @@ export type { EstLiqPriceViewOpts } from "./liq-view.ts";
13
13
  export * from "./user/index.ts";
14
14
  export * from "./tx-builders.ts";
15
15
  export * from "./fetch.ts";
16
- export { FetchPolicyError, LazerApiKeyMissingError, OracleFeeSourceUnavailableError, OracleSourceNotImplementedError, PythCache, aggregateTicker, aggregateTickerWithConstant, aggregateTickerWithPyth, ORACLE_SOURCES, buildPythPriceUpdateCalls, fetchPriceFeedsUpdateData, isOracleSource, parseOracleSourceList, pythCoreHermesEndpoint, pythProHermesEndpoint, refreshOraclePrices, resolveHermesReadEndpoint, updatePythPrices, waterxEnvelopeOf, waterxQuoteCenterEndpoint, } from "../oracle/index.ts";
17
- export type { FetchPolicy, OracleFeeSource, OracleSource, UpdateDataProvider, } from "../oracle/index.ts";
16
+ export { FetchPolicyError, LazerApiKeyMissingError, OracleFeeSourceUnavailableError, OracleSourceNotImplementedError, PythCache, aggregateTicker, aggregateTickerWithConstant, aggregateTickerWithPyth, ORACLE_SOURCES, buildPythPriceUpdateCalls, fetchPriceFeedsUpdateData, isOracleSource, parseOracleSourceList, pythCoreHermesEndpoint, pythProHermesEndpoint, refreshOraclePrices, resolveHermesReadEndpoint, updatePythPrices, waterxEnvelopeOf, waterxLeavesOf, parseSignedLeaves, MERKLE_ROOT_INTENT, waterxQuoteCenterEndpoint, } from "../oracle/index.ts";
17
+ export type { FetchPolicy, OracleFeeSource, OracleSource, UpdateDataProvider, WaterxBatchItem, WaterxEnvelopePayload, WaterxLeafPayload, WaterxSignedEnvelope, WaterxSignedLeaf, WaterxUpdatePayload, } from "../oracle/index.ts";
18
18
  export { fetchDepositVaa, fetchVaa, listBridgeWithdrawalVaas, listVaasByEmitter, padEvmEmitter, toWormholescanEmitter, vaaBase64ToBytes, vaaBase64ToHex, vaaBytesToBase64, waitForVaa, } from "../account/funding/wormhole.ts";
19
19
  export type { VaaListItem, VaaResponse, WormholescanOptions } from "../account/funding/wormhole.ts";
20
20
  export { AccountData as AccountDataBcs, GlobalConfigData as GlobalConfigDataBcs, MarketData as MarketDataBcs, OrderData as OrderDataBcs, PoolData as PoolDataBcs, PositionData as PositionDataBcs, RedeemRequestData as RedeemRequestDataBcs, TokenPoolData as TokenPoolDataBcs, } from "../generated/waterx_perp_view/view.ts";
@@ -37,8 +37,8 @@ var __importStar = (this && this.__importStar) || (function () {
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.getCollateralAssets = exports.getMarketTickers = exports.TOKEN_DECIMALS = exports.COLLATERAL_DECIMALS = exports.WLP_DECIMALS = exports.SUI_DECIMALS = exports.MS_PER_YEAR = exports.MS_PER_HOUR = exports.MS_PER_MINUTE = exports.STAKING_PERM_ALL = exports.STAKING_PERM_CLAIM_REWARD = exports.STAKING_PERM_REDEEM_STAKE = exports.STAKING_PERM_DEPOSIT_STAKE = exports.PERM_WITHDRAW_COLLATERAL = exports.PERM_REDEEM_WLP = exports.PERM_PLACE_ORDER = exports.PERM_OPEN_POSITION = exports.PERM_MINT_WLP = exports.PERM_INCREASE_POSITION = exports.PERM_DEPOSIT_COLLATERAL = exports.PERM_DECREASE_POSITION = exports.PERM_CLOSE_POSITION = exports.PERM_CANCEL_ORDER = exports.PERM_ALL_TRADING = exports.PERM_ALL = exports.ORDER_TAG_WILDCARD = exports.ORDER_STOP_SELL = exports.ORDER_STOP_BUY = exports.ORDER_LIMIT_SELL = exports.ORDER_LIMIT_BUY = exports.FLOAT_SCALE = exports.DRY_RUN_SENDER = exports.DOUBLE_SCALE = exports.BPS_SCALE = exports.ACTION_WITHDRAW_COLLATERAL = exports.ACTION_UPDATE_ORDER = exports.ACTION_PLACE_ORDER = exports.ACTION_OPEN_POSITION = exports.ACTION_LIQUIDATE = exports.ACTION_INCREASE_POSITION = exports.ACTION_DEPOSIT_COLLATERAL = exports.ACTION_DECREASE_POSITION = exports.ACTION_CLOSE_POSITION = exports.ACTION_CANCEL_PRE_ORDER = exports.ACTION_CANCEL_ORDER = exports.ACTION_ADD_PRE_ORDER = exports.loadConfig = exports.clearConfigCache = exports.WORMHOLE_DEFAULTS = exports.PerpClient = void 0;
40
- exports.fetchDepositVaa = exports.waterxQuoteCenterEndpoint = exports.waterxEnvelopeOf = exports.updatePythPrices = exports.resolveHermesReadEndpoint = exports.refreshOraclePrices = exports.pythProHermesEndpoint = exports.pythCoreHermesEndpoint = exports.parseOracleSourceList = exports.isOracleSource = exports.fetchPriceFeedsUpdateData = exports.buildPythPriceUpdateCalls = exports.ORACLE_SOURCES = exports.aggregateTickerWithPyth = exports.aggregateTickerWithConstant = exports.aggregateTicker = exports.PythCache = exports.OracleSourceNotImplementedError = exports.OracleFeeSourceUnavailableError = exports.LazerApiKeyMissingError = exports.FetchPolicyError = exports.calcEstLiqPriceRawFromView = exports.formatFundingInterval = exports.rawPrice = exports.decodeFundingIndexDelta = exports.calcWlpRedeemOut = exports.calcWlpPrice = exports.calcWlpMintOut = exports.calcWlpIncentiveApy = exports.calcViewEstLiqFeesUsd = exports.calcUnrealizedPnl = exports.calcTotalTradingFeeRate = exports.calcTokenUtilizationBps = exports.calcRealLiqNetCostUsd = exports.calcPositionBorrowFee = exports.calcNotional = exports.calcMaxReducibleCollateralUsd = exports.calcLeverage = exports.calcImpactFeeRate = exports.calcFundingRate = exports.calcFundingFeeUsd = exports.calcFee = exports.calcEstLiqPriceRaw = exports.calcEstLiqPrice = exports.calcEffectiveCollateralUsd = exports.calcDynamicFeeBps = exports.calcBorrowRateAccrual = exports.calcBorrowRate = exports.annualizeFundingRate = exports.annualizedApyFromRatio = void 0;
41
- exports.withdrawalQueueCalls = exports.nativeCustodyCalls = exports.referralCalls = exports.pythSponsorRuleCalls = exports.pythRuleCalls = exports.oracleCalls = exports.stakingCalls = exports.wxaAccountCalls = exports.viewCalls = exports.lpPoolCalls = exports.tradingCalls = exports.MarketConfigBcs = exports.MarketBcs = exports.OrderBcs = exports.PositionBcs = exports.TokenPoolDataBcs = exports.RedeemRequestDataBcs = exports.PositionDataBcs = exports.PoolDataBcs = exports.OrderDataBcs = exports.MarketDataBcs = exports.GlobalConfigDataBcs = exports.AccountDataBcs = exports.waitForVaa = exports.vaaBytesToBase64 = exports.vaaBase64ToHex = exports.vaaBase64ToBytes = exports.toWormholescanEmitter = exports.padEvmEmitter = exports.listVaasByEmitter = exports.listBridgeWithdrawalVaas = exports.fetchVaa = void 0;
40
+ exports.parseSignedLeaves = exports.waterxLeavesOf = exports.waterxEnvelopeOf = exports.updatePythPrices = exports.resolveHermesReadEndpoint = exports.refreshOraclePrices = exports.pythProHermesEndpoint = exports.pythCoreHermesEndpoint = exports.parseOracleSourceList = exports.isOracleSource = exports.fetchPriceFeedsUpdateData = exports.buildPythPriceUpdateCalls = exports.ORACLE_SOURCES = exports.aggregateTickerWithPyth = exports.aggregateTickerWithConstant = exports.aggregateTicker = exports.PythCache = exports.OracleSourceNotImplementedError = exports.OracleFeeSourceUnavailableError = exports.LazerApiKeyMissingError = exports.FetchPolicyError = exports.calcEstLiqPriceRawFromView = exports.formatFundingInterval = exports.rawPrice = exports.decodeFundingIndexDelta = exports.calcWlpRedeemOut = exports.calcWlpPrice = exports.calcWlpMintOut = exports.calcWlpIncentiveApy = exports.calcViewEstLiqFeesUsd = exports.calcUnrealizedPnl = exports.calcTotalTradingFeeRate = exports.calcTokenUtilizationBps = exports.calcRealLiqNetCostUsd = exports.calcPositionBorrowFee = exports.calcNotional = exports.calcMaxReducibleCollateralUsd = exports.calcLeverage = exports.calcImpactFeeRate = exports.calcFundingRate = exports.calcFundingFeeUsd = exports.calcFee = exports.calcEstLiqPriceRaw = exports.calcEstLiqPrice = exports.calcEffectiveCollateralUsd = exports.calcDynamicFeeBps = exports.calcBorrowRateAccrual = exports.calcBorrowRate = exports.annualizeFundingRate = exports.annualizedApyFromRatio = void 0;
41
+ exports.withdrawalQueueCalls = exports.nativeCustodyCalls = exports.referralCalls = exports.pythSponsorRuleCalls = exports.pythRuleCalls = exports.oracleCalls = exports.stakingCalls = exports.wxaAccountCalls = exports.viewCalls = exports.lpPoolCalls = exports.tradingCalls = exports.MarketConfigBcs = exports.MarketBcs = exports.OrderBcs = exports.PositionBcs = exports.TokenPoolDataBcs = exports.RedeemRequestDataBcs = exports.PositionDataBcs = exports.PoolDataBcs = exports.OrderDataBcs = exports.MarketDataBcs = exports.GlobalConfigDataBcs = exports.AccountDataBcs = exports.waitForVaa = exports.vaaBytesToBase64 = exports.vaaBase64ToHex = exports.vaaBase64ToBytes = exports.toWormholescanEmitter = exports.padEvmEmitter = exports.listVaasByEmitter = exports.listBridgeWithdrawalVaas = exports.fetchVaa = exports.fetchDepositVaa = exports.waterxQuoteCenterEndpoint = exports.MERKLE_ROOT_INTENT = void 0;
42
42
  // ======== Core ========
43
43
  var client_ts_1 = require("./client.js");
44
44
  Object.defineProperty(exports, "PerpClient", { enumerable: true, get: function () { return client_ts_1.PerpClient; } });
@@ -155,6 +155,14 @@ Object.defineProperty(exports, "refreshOraclePrices", { enumerable: true, get: f
155
155
  Object.defineProperty(exports, "resolveHermesReadEndpoint", { enumerable: true, get: function () { return index_ts_1.resolveHermesReadEndpoint; } });
156
156
  Object.defineProperty(exports, "updatePythPrices", { enumerable: true, get: function () { return index_ts_1.updatePythPrices; } });
157
157
  Object.defineProperty(exports, "waterxEnvelopeOf", { enumerable: true, get: function () { return index_ts_1.waterxEnvelopeOf; } });
158
+ // Leaves are the DEFAULT waterx wire shape, so their accessor + parser belong
159
+ // on the same surface as `waterxEnvelopeOf` (and on the root, which re-exports
160
+ // this file). A prefetch cache written against the envelope-only surface would
161
+ // otherwise get `null` from its only accessor on the normal path and have to
162
+ // discover `@waterx/sdk/oracle` to fix it.
163
+ Object.defineProperty(exports, "waterxLeavesOf", { enumerable: true, get: function () { return index_ts_1.waterxLeavesOf; } });
164
+ Object.defineProperty(exports, "parseSignedLeaves", { enumerable: true, get: function () { return index_ts_1.parseSignedLeaves; } });
165
+ Object.defineProperty(exports, "MERKLE_ROOT_INTENT", { enumerable: true, get: function () { return index_ts_1.MERKLE_ROOT_INTENT; } });
158
166
  Object.defineProperty(exports, "waterxQuoteCenterEndpoint", { enumerable: true, get: function () { return index_ts_1.waterxQuoteCenterEndpoint; } });
159
167
  // ======== Wormhole / Wormholescan utilities (credit bridge) ========
160
168
  var wormhole_ts_1 = require("../account/funding/wormhole.js");
@@ -406,7 +406,7 @@ export interface ClientCreateOptions {
406
406
  * the quote-center deployment's CORS allowlist: a front end whose origin is
407
407
  * not allowed points this at its own same-origin proxy. An absolute URL whose
408
408
  * base PATH is preserved — `https://app.example/api/quote-center` fetches
409
- * `…/api/quote-center/v1/quotes/update`. Unused by the Pyth sources.
409
+ * `…/api/quote-center/v1/quotes/leaves`. Unused by the Pyth sources.
410
410
  */
411
411
  waterxEndpoint?: string;
412
412
  /**
@@ -70,6 +70,11 @@ export declare const BatchPricePayload: MoveStruct<{
70
70
  length: number;
71
71
  }, string>;
72
72
  }, "@waterx/rule::waterx_rule::BatchPricePayload">;
73
+ export declare const MerkleRoot: MoveStruct<{
74
+ root: import("@mysten/sui/bcs").BcsType<number[], Iterable<number> & {
75
+ length: number;
76
+ }, string>;
77
+ }, "@waterx/rule::waterx_rule::MerkleRoot">;
73
78
  export declare const FeedConfig: MoveStruct<{
74
79
  ticker: import("@mysten/sui/bcs").BcsType<string, string, "string">;
75
80
  sources: import("@mysten/sui/bcs").BcsType<string[], Iterable<string | number | bigint> & {
@@ -214,17 +219,82 @@ export interface CollectBatchLatestOptions {
214
219
  * and so aborts on any stale item, this path runs alongside Pyth/Supra. To let
215
220
  * those cover a lagging TEE — the blast-radius bound in the GCP ADR — a
216
221
  * _freshness_ miss ABSTAINS (records `none`, which `aggregator::remove_outliers`
217
- * drops before the `weight_threshold` fail-closed check). A _config/integrity_
218
- * mismatch (source / ticker / method / confidence / deviation) still ABORTS: an
219
- * enclave whose signed payload disagrees with the on-chain feed config is a red
220
- * flag, not a liveness blip. Bad signature, malformed batch, a future timestamp,
221
- * and replay always abort.
222
+ * drops before the `weight_threshold` fail-closed check), and so does a _replayed_
223
+ * signed timestamp (the chain already accepted that snapshot the on-chain price
224
+ * is at least as fresh, and two concurrent keeper PTBs sharing one envelope must
225
+ * not kill each other). A _config/integrity_ mismatch (source / ticker / method /
226
+ * confidence / deviation) still ABORTS: an enclave whose signed payload disagrees
227
+ * with the on-chain feed config is a red flag, not a liveness blip. Bad signature,
228
+ * malformed batch, and a future timestamp always abort.
222
229
  *
223
230
  * Every gate is identical to `feed_batch_item_latest`; only the abort-vs-abstain
224
- * disposition of the two freshness gates differs, and the sink is the caller's
225
- * collector rather than a fresh single-rule one.
231
+ * disposition of the freshness + replay gates differs, and the sink is the
232
+ * caller's collector rather than a fresh single-rule one.
226
233
  */
227
234
  export declare function collectBatchLatest(options: CollectBatchLatestOptions): (tx: Transaction) => import("@mysten/sui/transactions").TransactionResult;
235
+ export interface FeedSingleWithProofArguments {
236
+ oracleObj: RawTransactionArgument<string>;
237
+ config: RawTransactionArgument<string>;
238
+ enclaveConfig: RawTransactionArgument<string>;
239
+ enclave: RawTransactionArgument<string>;
240
+ timestampMs: RawTransactionArgument<number | bigint>;
241
+ item: TransactionArgument;
242
+ proof: RawTransactionArgument<Array<Array<number>>>;
243
+ sig: RawTransactionArgument<Array<number>>;
244
+ }
245
+ export interface FeedSingleWithProofOptions {
246
+ package?: string;
247
+ arguments: FeedSingleWithProofArguments | [
248
+ oracleObj: RawTransactionArgument<string>,
249
+ config: RawTransactionArgument<string>,
250
+ enclaveConfig: RawTransactionArgument<string>,
251
+ enclave: RawTransactionArgument<string>,
252
+ timestampMs: RawTransactionArgument<number | bigint>,
253
+ item: TransactionArgument,
254
+ proof: RawTransactionArgument<Array<Array<number>>>,
255
+ sig: RawTransactionArgument<Array<number>>
256
+ ];
257
+ }
258
+ /**
259
+ * Streaming per-symbol sibling of `feed_batch_latest`. Verifies ONE enclave
260
+ * signature over a Merkle `root`, checks `item` is a leaf of that root via
261
+ * `proof`, then runs the identical per-item gates + `oracle::aggregate`. Use for a
262
+ * WaterxRule-only aggregator; for a Pyth-coexisting aggregator use
263
+ * `collect_single_with_proof`.
264
+ */
265
+ export declare function feedSingleWithProof(options: FeedSingleWithProofOptions): (tx: Transaction) => import("@mysten/sui/transactions").TransactionResult;
266
+ export interface CollectSingleWithProofArguments {
267
+ collector: TransactionArgument;
268
+ config: RawTransactionArgument<string>;
269
+ enclaveConfig: RawTransactionArgument<string>;
270
+ enclave: RawTransactionArgument<string>;
271
+ timestampMs: RawTransactionArgument<number | bigint>;
272
+ item: TransactionArgument;
273
+ proof: RawTransactionArgument<Array<Array<number>>>;
274
+ sig: RawTransactionArgument<Array<number>>;
275
+ }
276
+ export interface CollectSingleWithProofOptions {
277
+ package?: string;
278
+ arguments: CollectSingleWithProofArguments | [
279
+ collector: TransactionArgument,
280
+ config: RawTransactionArgument<string>,
281
+ enclaveConfig: RawTransactionArgument<string>,
282
+ enclave: RawTransactionArgument<string>,
283
+ timestampMs: RawTransactionArgument<number | bigint>,
284
+ item: TransactionArgument,
285
+ proof: RawTransactionArgument<Array<Array<number>>>,
286
+ sig: RawTransactionArgument<Array<number>>
287
+ ];
288
+ }
289
+ /**
290
+ * Collect-only sibling of `feed_single_with_proof` for the dual-rule latest path
291
+ * (WaterX alongside Pyth on one aggregator), mirroring `collect_batch_latest`:
292
+ * verifies root + proof, feeds the leaf into the caller's `PriceCollector` WITHOUT
293
+ * calling `oracle::aggregate`. Abort vs abstain matches `collect_batch_latest`
294
+ * exactly — a config/integrity mismatch aborts, a freshness miss abstains so Pyth
295
+ * can cover a lagging TEE.
296
+ */
297
+ export declare function collectSingleWithProof(options: CollectSingleWithProofOptions): (tx: Transaction) => import("@mysten/sui/transactions").TransactionResult;
228
298
  export interface QuoteForTimestampArguments {
229
299
  config: RawTransactionArgument<string>;
230
300
  enclaveConfig: RawTransactionArgument<string>;
@@ -421,6 +491,27 @@ export interface BatchPriceIntentOptions {
421
491
  ];
422
492
  }
423
493
  export declare function batchPriceIntent(options?: BatchPriceIntentOptions): (tx: Transaction) => import("@mysten/sui/transactions").TransactionResult;
494
+ export interface MerkleRootIntentOptions {
495
+ package?: string;
496
+ arguments?: [
497
+ ];
498
+ }
499
+ export declare function merkleRootIntent(options?: MerkleRootIntentOptions): (tx: Transaction) => import("@mysten/sui/transactions").TransactionResult;
500
+ export interface LeafHashOfArguments {
501
+ item: TransactionArgument;
502
+ }
503
+ export interface LeafHashOfOptions {
504
+ package?: string;
505
+ arguments: LeafHashOfArguments | [
506
+ item: TransactionArgument
507
+ ];
508
+ }
509
+ /**
510
+ * The keccak256 leaf hash for `item` — `keccak256(0x00 || BCS(item))`. Exposed so
511
+ * the off-chain builder / SDK can be pinned against the on-chain encoding in a
512
+ * cross-implementation golden test.
513
+ */
514
+ export declare function leafHashOf(options: LeafHashOfOptions): (tx: Transaction) => import("@mysten/sui/transactions").TransactionResult;
424
515
  export interface MaxBatchSizeOptions {
425
516
  package?: string;
426
517
  arguments?: [
@@ -717,6 +808,48 @@ export interface SourceGataUsdtPerpWsOptions {
717
808
  ];
718
809
  }
719
810
  export declare function sourceGataUsdtPerpWs(options?: SourceGataUsdtPerpWsOptions): (tx: Transaction) => import("@mysten/sui/transactions").TransactionResult;
811
+ export interface SourceBybitSpotWsOptions {
812
+ package?: string;
813
+ arguments?: [
814
+ ];
815
+ }
816
+ export declare function sourceBybitSpotWs(options?: SourceBybitSpotWsOptions): (tx: Transaction) => import("@mysten/sui/transactions").TransactionResult;
817
+ export interface SourceXstockEquityRestOptions {
818
+ package?: string;
819
+ arguments?: [
820
+ ];
821
+ }
822
+ export declare function sourceXstockEquityRest(options?: SourceXstockEquityRestOptions): (tx: Transaction) => import("@mysten/sui/transactions").TransactionResult;
823
+ export interface SourceOkxSpotWsOptions {
824
+ package?: string;
825
+ arguments?: [
826
+ ];
827
+ }
828
+ export declare function sourceOkxSpotWs(options?: SourceOkxSpotWsOptions): (tx: Transaction) => import("@mysten/sui/transactions").TransactionResult;
829
+ export interface SourceHyperliquidPerpWsOptions {
830
+ package?: string;
831
+ arguments?: [
832
+ ];
833
+ }
834
+ export declare function sourceHyperliquidPerpWs(options?: SourceHyperliquidPerpWsOptions): (tx: Transaction) => import("@mysten/sui/transactions").TransactionResult;
835
+ export interface SourceGateioSpotWsOptions {
836
+ package?: string;
837
+ arguments?: [
838
+ ];
839
+ }
840
+ export declare function sourceGateioSpotWs(options?: SourceGateioSpotWsOptions): (tx: Transaction) => import("@mysten/sui/transactions").TransactionResult;
841
+ export interface SourceKrakenSpotWsOptions {
842
+ package?: string;
843
+ arguments?: [
844
+ ];
845
+ }
846
+ export declare function sourceKrakenSpotWs(options?: SourceKrakenSpotWsOptions): (tx: Transaction) => import("@mysten/sui/transactions").TransactionResult;
847
+ export interface SourcePythLazerWsOptions {
848
+ package?: string;
849
+ arguments?: [
850
+ ];
851
+ }
852
+ export declare function sourcePythLazerWs(options?: SourcePythLazerWsOptions): (tx: Transaction) => import("@mysten/sui/transactions").TransactionResult;
720
853
  export interface MethodDirectOptions {
721
854
  package?: string;
722
855
  arguments?: [
@@ -41,6 +41,9 @@ export const BatchPriceItem = new MoveStruct({ name: `${$moduleName}::BatchPrice
41
41
  export const BatchPricePayload = new MoveStruct({ name: `${$moduleName}::BatchPricePayload`, fields: {
42
42
  items: bcs.vector(BatchPriceItem)
43
43
  } });
44
+ export const MerkleRoot = new MoveStruct({ name: `${$moduleName}::MerkleRoot`, fields: {
45
+ root: bcs.vector(bcs.u8())
46
+ } });
44
47
  export const FeedConfig = new MoveStruct({ name: `${$moduleName}::FeedConfig`, fields: {
45
48
  ticker: bcs.string(),
46
49
  sources: bcs.vector(bcs.u64()),
@@ -126,15 +129,17 @@ export function feedBatchLatest(options) {
126
129
  * and so aborts on any stale item, this path runs alongside Pyth/Supra. To let
127
130
  * those cover a lagging TEE — the blast-radius bound in the GCP ADR — a
128
131
  * _freshness_ miss ABSTAINS (records `none`, which `aggregator::remove_outliers`
129
- * drops before the `weight_threshold` fail-closed check). A _config/integrity_
130
- * mismatch (source / ticker / method / confidence / deviation) still ABORTS: an
131
- * enclave whose signed payload disagrees with the on-chain feed config is a red
132
- * flag, not a liveness blip. Bad signature, malformed batch, a future timestamp,
133
- * and replay always abort.
132
+ * drops before the `weight_threshold` fail-closed check), and so does a _replayed_
133
+ * signed timestamp (the chain already accepted that snapshot the on-chain price
134
+ * is at least as fresh, and two concurrent keeper PTBs sharing one envelope must
135
+ * not kill each other). A _config/integrity_ mismatch (source / ticker / method /
136
+ * confidence / deviation) still ABORTS: an enclave whose signed payload disagrees
137
+ * with the on-chain feed config is a red flag, not a liveness blip. Bad signature,
138
+ * malformed batch, and a future timestamp always abort.
134
139
  *
135
140
  * Every gate is identical to `feed_batch_item_latest`; only the abort-vs-abstain
136
- * disposition of the two freshness gates differs, and the sink is the caller's
137
- * collector rather than a fresh single-rule one.
141
+ * disposition of the freshness + replay gates differs, and the sink is the
142
+ * caller's collector rather than a fresh single-rule one.
138
143
  */
139
144
  export function collectBatchLatest(options) {
140
145
  const packageAddress = options.package ?? '@waterx/rule';
@@ -156,6 +161,63 @@ export function collectBatchLatest(options) {
156
161
  arguments: normalizeMoveArguments(options.arguments, argumentsTypes, parameterNames),
157
162
  });
158
163
  }
164
+ /**
165
+ * Streaming per-symbol sibling of `feed_batch_latest`. Verifies ONE enclave
166
+ * signature over a Merkle `root`, checks `item` is a leaf of that root via
167
+ * `proof`, then runs the identical per-item gates + `oracle::aggregate`. Use for a
168
+ * WaterxRule-only aggregator; for a Pyth-coexisting aggregator use
169
+ * `collect_single_with_proof`.
170
+ */
171
+ export function feedSingleWithProof(options) {
172
+ const packageAddress = options.package ?? '@waterx/rule';
173
+ const argumentsTypes = [
174
+ null,
175
+ null,
176
+ '0x2::clock::Clock',
177
+ null,
178
+ null,
179
+ 'u64',
180
+ null,
181
+ 'vector<vector<u8>>',
182
+ 'vector<u8>'
183
+ ];
184
+ const parameterNames = ["oracleObj", "config", "enclaveConfig", "enclave", "timestampMs", "item", "proof", "sig"];
185
+ return (tx) => tx.moveCall({
186
+ package: packageAddress,
187
+ module: 'waterx_rule',
188
+ function: 'feed_single_with_proof',
189
+ arguments: normalizeMoveArguments(options.arguments, argumentsTypes, parameterNames),
190
+ });
191
+ }
192
+ /**
193
+ * Collect-only sibling of `feed_single_with_proof` for the dual-rule latest path
194
+ * (WaterX alongside Pyth on one aggregator), mirroring `collect_batch_latest`:
195
+ * verifies root + proof, feeds the leaf into the caller's `PriceCollector` WITHOUT
196
+ * calling `oracle::aggregate`. Abort vs abstain matches `collect_batch_latest`
197
+ * exactly — a config/integrity mismatch aborts, a freshness miss abstains so Pyth
198
+ * can cover a lagging TEE.
199
+ */
200
+ export function collectSingleWithProof(options) {
201
+ const packageAddress = options.package ?? '@waterx/rule';
202
+ const argumentsTypes = [
203
+ null,
204
+ null,
205
+ '0x2::clock::Clock',
206
+ null,
207
+ null,
208
+ 'u64',
209
+ null,
210
+ 'vector<vector<u8>>',
211
+ 'vector<u8>'
212
+ ];
213
+ const parameterNames = ["collector", "config", "enclaveConfig", "enclave", "timestampMs", "item", "proof", "sig"];
214
+ return (tx) => tx.moveCall({
215
+ package: packageAddress,
216
+ module: 'waterx_rule',
217
+ function: 'collect_single_with_proof',
218
+ arguments: normalizeMoveArguments(options.arguments, argumentsTypes, parameterNames),
219
+ });
220
+ }
159
221
  /**
160
222
  * Verifies a historical quote without touching `waterx_oracle`.
161
223
  *
@@ -347,6 +409,32 @@ export function batchPriceIntent(options = {}) {
347
409
  function: 'batch_price_intent',
348
410
  });
349
411
  }
412
+ export function merkleRootIntent(options = {}) {
413
+ const packageAddress = options.package ?? '@waterx/rule';
414
+ return (tx) => tx.moveCall({
415
+ package: packageAddress,
416
+ module: 'waterx_rule',
417
+ function: 'merkle_root_intent',
418
+ });
419
+ }
420
+ /**
421
+ * The keccak256 leaf hash for `item` — `keccak256(0x00 || BCS(item))`. Exposed so
422
+ * the off-chain builder / SDK can be pinned against the on-chain encoding in a
423
+ * cross-implementation golden test.
424
+ */
425
+ export function leafHashOf(options) {
426
+ const packageAddress = options.package ?? '@waterx/rule';
427
+ const argumentsTypes = [
428
+ null
429
+ ];
430
+ const parameterNames = ["item"];
431
+ return (tx) => tx.moveCall({
432
+ package: packageAddress,
433
+ module: 'waterx_rule',
434
+ function: 'leaf_hash_of',
435
+ arguments: normalizeMoveArguments(options.arguments, argumentsTypes, parameterNames),
436
+ });
437
+ }
350
438
  export function maxBatchSize(options = {}) {
351
439
  const packageAddress = options.package ?? '@waterx/rule';
352
440
  return (tx) => tx.moveCall({
@@ -733,6 +821,62 @@ export function sourceGataUsdtPerpWs(options = {}) {
733
821
  function: 'source_gata_usdt_perp_ws',
734
822
  });
735
823
  }
824
+ export function sourceBybitSpotWs(options = {}) {
825
+ const packageAddress = options.package ?? '@waterx/rule';
826
+ return (tx) => tx.moveCall({
827
+ package: packageAddress,
828
+ module: 'waterx_rule',
829
+ function: 'source_bybit_spot_ws',
830
+ });
831
+ }
832
+ export function sourceXstockEquityRest(options = {}) {
833
+ const packageAddress = options.package ?? '@waterx/rule';
834
+ return (tx) => tx.moveCall({
835
+ package: packageAddress,
836
+ module: 'waterx_rule',
837
+ function: 'source_xstock_equity_rest',
838
+ });
839
+ }
840
+ export function sourceOkxSpotWs(options = {}) {
841
+ const packageAddress = options.package ?? '@waterx/rule';
842
+ return (tx) => tx.moveCall({
843
+ package: packageAddress,
844
+ module: 'waterx_rule',
845
+ function: 'source_okx_spot_ws',
846
+ });
847
+ }
848
+ export function sourceHyperliquidPerpWs(options = {}) {
849
+ const packageAddress = options.package ?? '@waterx/rule';
850
+ return (tx) => tx.moveCall({
851
+ package: packageAddress,
852
+ module: 'waterx_rule',
853
+ function: 'source_hyperliquid_perp_ws',
854
+ });
855
+ }
856
+ export function sourceGateioSpotWs(options = {}) {
857
+ const packageAddress = options.package ?? '@waterx/rule';
858
+ return (tx) => tx.moveCall({
859
+ package: packageAddress,
860
+ module: 'waterx_rule',
861
+ function: 'source_gateio_spot_ws',
862
+ });
863
+ }
864
+ export function sourceKrakenSpotWs(options = {}) {
865
+ const packageAddress = options.package ?? '@waterx/rule';
866
+ return (tx) => tx.moveCall({
867
+ package: packageAddress,
868
+ module: 'waterx_rule',
869
+ function: 'source_kraken_spot_ws',
870
+ });
871
+ }
872
+ export function sourcePythLazerWs(options = {}) {
873
+ const packageAddress = options.package ?? '@waterx/rule';
874
+ return (tx) => tx.moveCall({
875
+ package: packageAddress,
876
+ module: 'waterx_rule',
877
+ function: 'source_pyth_lazer_ws',
878
+ });
879
+ }
736
880
  export function methodDirect(options = {}) {
737
881
  const packageAddress = options.package ?? '@waterx/rule';
738
882
  return (tx) => tx.moveCall({
@@ -26,7 +26,7 @@ import type { Transaction, TransactionArgument } from "@mysten/sui/transactions"
26
26
  import type { OracleHost } from "./host.ts";
27
27
  import type { OracleSource, PriceUpdateRule, UpdateDataProvider } from "./price-update-rule.ts";
28
28
  import { type OracleFeeSource, type PythCache } from "./pyth.ts";
29
- import { type WaterxSignedEnvelope } from "./rules/waterx-rule.ts";
29
+ import { type WaterxSignedEnvelope, type WaterxSignedLeaf } from "./rules/waterx-rule.ts";
30
30
  /**
31
31
  * Aggregate one ticker's price into the shared `Oracle`: build a collector, feed
32
32
  * every rule the ticker is configured for, then `aggregate`.
@@ -57,6 +57,9 @@ export declare function aggregateTicker(tx: Transaction, host: OracleHost, args:
57
57
  ticker: string;
58
58
  priceInfoObjectId?: string;
59
59
  lazerUpdate?: TransactionArgument;
60
+ /** This ticker's signed Merkle leaf — the default waterx shape. */
61
+ waterxLeaf?: WaterxSignedLeaf;
62
+ /** Batch envelope covering this ticker — the fallback waterx shape. */
60
63
  waterxEnvelope?: WaterxSignedEnvelope;
61
64
  }): void;
62
65
  /**
@@ -30,7 +30,7 @@ import { feedConstantRule } from "./rules/constant-rule.js";
30
30
  import { feedLazerRule } from "./rules/pyth-lazer-rule.js";
31
31
  import { feedPythRule } from "./rules/pyth-rule.js";
32
32
  import { maybeFeedSupra } from "./rules/supra-rule.js";
33
- import { feedWaterxRule, waterxEnvelopeOf, } from "./rules/waterx-rule.js";
33
+ import { feedWaterxRule, feedWaterxRuleWithProof, waterxEnvelopeOf, waterxLeavesOf, } from "./rules/waterx-rule.js";
34
34
  /**
35
35
  * Resolve one group's off-chain update payload for {@link refreshOraclePrices}:
36
36
  * try `provider.get(source, tickers)` first (when a provider is configured),
@@ -114,17 +114,25 @@ export function aggregateTicker(tx, host, args) {
114
114
  feedLazerRule(tx, host, collector, args.lazerUpdate);
115
115
  fed = true;
116
116
  }
117
- if (args.waterxEnvelope !== undefined) {
118
- // waterx_rule::collect_batch_latest verifies the batch signature and feeds
119
- // this collector's symbol from the batch. If the ticker's aggregator does
120
- // not (yet) weight `WaterxRule`, the contribution is silently dropped
121
- // on-chain feeding ahead of the weight migration is safe for THIS tx.
122
- // CAVEAT (unlike lazer): the feed call records a per-symbol signed-
123
- // timestamp high-water mark REGARDLESS of weights, and a replayed
124
- // timestamp ABORTS (`EReplayedSignature`, audit F-014) so two PTBs
125
- // carrying the same envelope for the same symbol cannot both land; the
126
- // second aborts even where waterx is unweighted. See WaterxRule's module
127
- // header.
117
+ // One waterx leg at most, and the leaf shape wins: both entries record the
118
+ // same per-symbol signed-timestamp high-water mark and feed the same rule
119
+ // witness into this collector, so emitting both would make the second one
120
+ // abstain on its own predecessor's mark for no gain. `refreshOraclePrices`
121
+ // only ever supplies one; a caller that passes both gets the cheaper leg.
122
+ if (args.waterxLeaf !== undefined) {
123
+ // waterx_rule::collect_single_with_proof re-derives the snapshot root from
124
+ // this leaf + its proof, verifies the enclave signature over that root, and
125
+ // feeds the price. If the ticker's aggregator does not (yet) weight
126
+ // `WaterxRule`, the contribution is silently dropped on-chain feeding
127
+ // ahead of the weight migration is safe. See WaterxRule's module header for
128
+ // the abort-vs-abstain split.
129
+ feedWaterxRuleWithProof(tx, host, collector, args.waterxLeaf);
130
+ fed = true;
131
+ }
132
+ else if (args.waterxEnvelope !== undefined) {
133
+ // Fallback shape: collect_batch_latest re-verifies the WHOLE batch
134
+ // signature (every item rebuilt in-PTB) and feeds this collector's symbol
135
+ // out of it. Only reached against a quote-center with no leaf route.
128
136
  feedWaterxRule(tx, host, collector, args.waterxEnvelope);
129
137
  fed = true;
130
138
  }
@@ -217,11 +225,11 @@ export async function refreshOraclePrices(tx, host, tickers, opts = {}) {
217
225
  if (tickers.length === 0)
218
226
  return;
219
227
  // Dedupe the caller's list (order-preserving): a repeated ticker would
220
- // otherwise aggregate TWICE in this one PTB — wasted gas for every rule,
221
- // and a hard ABORT under waterx: the second `collect_batch_latest` carries
222
- // the same envelope, and the on-chain per-symbol replay guard rejects an
223
- // already-accepted signed timestamp (`EReplayedSignature`, F-014) even
224
- // inside a single transaction.
228
+ // otherwise aggregate TWICE in this one PTB — wasted gas for every rule, and
229
+ // under waterx the second collect would be dead weight on top of that: the
230
+ // on-chain per-symbol replay guard (F-014) sees its own predecessor's
231
+ // high-water mark from earlier in this same transaction and abstains, so the
232
+ // repeat pays full verification cost to contribute nothing.
225
233
  tickers = [...new Set(tickers)];
226
234
  // price_info_object lookup for every ticker with a pyth_rule.feeds entry —
227
235
  // needed by aggregateTicker's (unchanged) Pyth feed step below regardless of
@@ -292,10 +300,12 @@ export async function refreshOraclePrices(tx, host, tickers, opts = {}) {
292
300
  // leg needs per-ticker data from its update leg must decide its carry here
293
301
  // — falling through silently would starve its weighted tickers on-chain.
294
302
  const lazerUpdateByTicker = new Map();
295
- // Signed batch envelope per waterx-served ticker. Unlike Lazer's shared PTB
296
- // handle, waterx's verify+feed is bundled into `collect_batch_latest` in the
297
- // per-ticker feed leg, so its `buildUpdateCalls` emits nothing and the
298
- // envelope is carried straight from the group's fetched data.
303
+ // Signed waterx data per served ticker a per-symbol Merkle leaf normally, a
304
+ // shared batch envelope on the fallback shape. Unlike Lazer's shared PTB
305
+ // handle, waterx's verify+feed is bundled into the per-ticker collect call, so
306
+ // its `buildUpdateCalls` emits nothing and the signed data is carried straight
307
+ // from the group's fetched data.
308
+ const waterxLeafByTicker = new Map();
299
309
  const waterxEnvelopeByTicker = new Map();
300
310
  for (const [i, group] of groups.entries()) {
301
311
  const data = dataByGroup[i] ?? null;
@@ -319,12 +329,45 @@ export async function refreshOraclePrices(tx, host, tickers, opts = {}) {
319
329
  break;
320
330
  case "waterx_rule": {
321
331
  // waterx_rule emits no shared handle (verify+feed is bundled into the
322
- // per-ticker `collect_batch_latest`), so the envelope is carried
323
- // straight from this group's fetched data to the feed leg below.
324
- const envelope = waterxEnvelopeOf(data);
325
- if (envelope) {
326
- for (const ticker of group.tickers)
327
- waterxEnvelopeByTicker.set(ticker, envelope);
332
+ // per-ticker collect call), so the signed data is carried straight from
333
+ // this group's fetched data to the feed leg below. Leaves are per-symbol
334
+ // and indexed BY symbol — never fanned out across the group like the
335
+ // envelope, since each leaf only verifies for its own symbol
336
+ // (`ECollectorSymbolMismatch`). A leaf for a symbol outside this group is
337
+ // dropped rather than carried: the feed leg is keyed by ticker anyway.
338
+ const leaves = waterxLeavesOf(data);
339
+ // `length > 0` matters: `{ leaves: [] }` is shape-valid (`[].every(...)`
340
+ // is `true`), so a bare truthiness test would TAKE this branch, carry
341
+ // nothing, and `break` past the envelope branch below.
342
+ if (leaves && leaves.length > 0) {
343
+ const served = new Set(group.tickers);
344
+ for (const leaf of leaves) {
345
+ if (served.has(leaf.symbol))
346
+ waterxLeafByTicker.set(leaf.symbol, leaf);
347
+ }
348
+ }
349
+ else {
350
+ const envelope = waterxEnvelopeOf(data);
351
+ if (envelope) {
352
+ for (const ticker of group.tickers)
353
+ waterxEnvelopeByTicker.set(ticker, envelope);
354
+ }
355
+ }
356
+ // Fail the BUILD, not the chain. Both suppliers of `data` already
357
+ // guarantee full coverage — the live fetch through `assertCoverage`, a
358
+ // cached payload through `narrowUpdateData` (which returns `null`, i.e.
359
+ // "miss → live fetch", rather than a partial) — so reaching here with a
360
+ // ticker uncarried means one of those invariants broke. Left alone it
361
+ // emits a collector with no waterx leg, which surfaces MUCH later as an
362
+ // opaque on-chain `EMissingPriceSource` (or, if the rule is unweighted
363
+ // for that ticker, as a silently thinner weighted set). This names the
364
+ // tickers instead.
365
+ const uncarried = group.tickers.filter((t) => !waterxLeafByTicker.has(t) && !waterxEnvelopeByTicker.has(t));
366
+ if (uncarried.length > 0) {
367
+ throw new Error(`waterx_rule update data carries no signed price for ticker(s): ${uncarried.join(", ")}. ` +
368
+ "Expected one signed leaf per ticker (or a batch envelope covering all of " +
369
+ "them) — a payload that serves none of a group's tickers must be reported " +
370
+ "as a miss, not fed.");
328
371
  }
329
372
  break;
330
373
  }
@@ -340,6 +383,7 @@ export async function refreshOraclePrices(tx, host, tickers, opts = {}) {
340
383
  ticker,
341
384
  priceInfoObjectId: priceInfoByTicker.get(ticker),
342
385
  lazerUpdate: lazerUpdateByTicker.get(ticker),
386
+ waterxLeaf: waterxLeafByTicker.get(ticker),
343
387
  waterxEnvelope: waterxEnvelopeByTicker.get(ticker),
344
388
  });
345
389
  }