@waterx/sdk 4.3.1 → 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.
Files changed (36) hide show
  1. package/.claude/skills/waterx-sdk-integration/SKILL.md +219 -0
  2. package/README.md +177 -10
  3. package/SKILLS.md +34 -0
  4. package/dist/cjs/src/generated/waterx_rule/waterx_rule.d.ts +140 -7
  5. package/dist/cjs/src/generated/waterx_rule/waterx_rule.js +163 -8
  6. package/dist/cjs/src/oracle/aggregate.d.ts +8 -4
  7. package/dist/cjs/src/oracle/aggregate.js +74 -29
  8. package/dist/cjs/src/oracle/config.d.ts +3 -2
  9. package/dist/cjs/src/oracle/index.d.ts +2 -2
  10. package/dist/cjs/src/oracle/index.js +10 -5
  11. package/dist/cjs/src/oracle/price-update-rule.d.ts +10 -2
  12. package/dist/cjs/src/oracle/rules/pyth-lazer-rule.d.ts +25 -7
  13. package/dist/cjs/src/oracle/rules/pyth-lazer-rule.js +41 -17
  14. package/dist/cjs/src/oracle/rules/waterx-rule.d.ts +136 -47
  15. package/dist/cjs/src/oracle/rules/waterx-rule.js +450 -114
  16. package/dist/cjs/src/perp/client.d.ts +10 -8
  17. package/dist/cjs/src/perp/index.d.ts +2 -2
  18. package/dist/cjs/src/perp/index.js +10 -2
  19. package/dist/cjs/src/unified-client.d.ts +1 -1
  20. package/dist/src/generated/waterx_rule/waterx_rule.d.ts +140 -7
  21. package/dist/src/generated/waterx_rule/waterx_rule.js +151 -7
  22. package/dist/src/oracle/aggregate.d.ts +8 -4
  23. package/dist/src/oracle/aggregate.js +75 -30
  24. package/dist/src/oracle/config.d.ts +3 -2
  25. package/dist/src/oracle/index.d.ts +2 -2
  26. package/dist/src/oracle/index.js +8 -6
  27. package/dist/src/oracle/price-update-rule.d.ts +10 -2
  28. package/dist/src/oracle/rules/pyth-lazer-rule.d.ts +25 -7
  29. package/dist/src/oracle/rules/pyth-lazer-rule.js +41 -17
  30. package/dist/src/oracle/rules/waterx-rule.d.ts +136 -47
  31. package/dist/src/oracle/rules/waterx-rule.js +447 -114
  32. package/dist/src/perp/client.d.ts +10 -8
  33. package/dist/src/perp/index.d.ts +2 -2
  34. package/dist/src/perp/index.js +7 -1
  35. package/dist/src/unified-client.d.ts +1 -1
  36. package/package.json +8 -2
@@ -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?: [
@@ -36,9 +36,11 @@ var __importStar = (this && this.__importStar) || (function () {
36
36
  };
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.BatchLatestUpdated = exports.HistoricalPriceVerified = exports.SignedTsHwmKey = exports.Config = exports.VerifiedPrice = exports.FeedConfig = exports.BatchPricePayload = exports.BatchPriceItem = exports.PricePayload = exports.WaterxRule = exports.WATERX_RULE = void 0;
39
+ exports.BatchLatestUpdated = exports.HistoricalPriceVerified = exports.SignedTsHwmKey = exports.Config = exports.VerifiedPrice = exports.FeedConfig = exports.MerkleRoot = exports.BatchPricePayload = exports.BatchPriceItem = exports.PricePayload = exports.WaterxRule = exports.WATERX_RULE = void 0;
40
40
  exports.feedBatchLatest = feedBatchLatest;
41
41
  exports.collectBatchLatest = collectBatchLatest;
42
+ exports.feedSingleWithProof = feedSingleWithProof;
43
+ exports.collectSingleWithProof = collectSingleWithProof;
42
44
  exports.quoteForTimestamp = quoteForTimestamp;
43
45
  exports.destroyVerifiedPrice = destroyVerifiedPrice;
44
46
  exports.collectVerifiedPrice = collectVerifiedPrice;
@@ -50,6 +52,8 @@ exports.newBatchItem = newBatchItem;
50
52
  exports.pushBatchItem = pushBatchItem;
51
53
  exports.feedConfig = feedConfig;
52
54
  exports.batchPriceIntent = batchPriceIntent;
55
+ exports.merkleRootIntent = merkleRootIntent;
56
+ exports.leafHashOf = leafHashOf;
53
57
  exports.maxBatchSize = maxBatchSize;
54
58
  exports.batchItems = batchItems;
55
59
  exports.batchItemSymbol = batchItemSymbol;
@@ -82,6 +86,13 @@ exports.sourceBinanceUsdmPerpWs = sourceBinanceUsdmPerpWs;
82
86
  exports.sourceBybitLinearPerpWs = sourceBybitLinearPerpWs;
83
87
  exports.sourceGateioUsdtPerpWs = sourceGateioUsdtPerpWs;
84
88
  exports.sourceGataUsdtPerpWs = sourceGataUsdtPerpWs;
89
+ exports.sourceBybitSpotWs = sourceBybitSpotWs;
90
+ exports.sourceXstockEquityRest = sourceXstockEquityRest;
91
+ exports.sourceOkxSpotWs = sourceOkxSpotWs;
92
+ exports.sourceHyperliquidPerpWs = sourceHyperliquidPerpWs;
93
+ exports.sourceGateioSpotWs = sourceGateioSpotWs;
94
+ exports.sourceKrakenSpotWs = sourceKrakenSpotWs;
95
+ exports.sourcePythLazerWs = sourcePythLazerWs;
85
96
  exports.methodDirect = methodDirect;
86
97
  exports.methodMedian = methodMedian;
87
98
  exports.methodConfidence = methodConfidence;
@@ -132,6 +143,9 @@ exports.BatchPriceItem = new index_ts_1.MoveStruct({ name: `${$moduleName}::Batc
132
143
  exports.BatchPricePayload = new index_ts_1.MoveStruct({ name: `${$moduleName}::BatchPricePayload`, fields: {
133
144
  items: bcs_1.bcs.vector(exports.BatchPriceItem)
134
145
  } });
146
+ exports.MerkleRoot = new index_ts_1.MoveStruct({ name: `${$moduleName}::MerkleRoot`, fields: {
147
+ root: bcs_1.bcs.vector(bcs_1.bcs.u8())
148
+ } });
135
149
  exports.FeedConfig = new index_ts_1.MoveStruct({ name: `${$moduleName}::FeedConfig`, fields: {
136
150
  ticker: bcs_1.bcs.string(),
137
151
  sources: bcs_1.bcs.vector(bcs_1.bcs.u64()),
@@ -217,15 +231,17 @@ function feedBatchLatest(options) {
217
231
  * and so aborts on any stale item, this path runs alongside Pyth/Supra. To let
218
232
  * those cover a lagging TEE — the blast-radius bound in the GCP ADR — a
219
233
  * _freshness_ miss ABSTAINS (records `none`, which `aggregator::remove_outliers`
220
- * drops before the `weight_threshold` fail-closed check). A _config/integrity_
221
- * mismatch (source / ticker / method / confidence / deviation) still ABORTS: an
222
- * enclave whose signed payload disagrees with the on-chain feed config is a red
223
- * flag, not a liveness blip. Bad signature, malformed batch, a future timestamp,
224
- * and replay always abort.
234
+ * drops before the `weight_threshold` fail-closed check), and so does a _replayed_
235
+ * signed timestamp (the chain already accepted that snapshot the on-chain price
236
+ * is at least as fresh, and two concurrent keeper PTBs sharing one envelope must
237
+ * not kill each other). A _config/integrity_ mismatch (source / ticker / method /
238
+ * confidence / deviation) still ABORTS: an enclave whose signed payload disagrees
239
+ * with the on-chain feed config is a red flag, not a liveness blip. Bad signature,
240
+ * malformed batch, and a future timestamp always abort.
225
241
  *
226
242
  * Every gate is identical to `feed_batch_item_latest`; only the abort-vs-abstain
227
- * disposition of the two freshness gates differs, and the sink is the caller's
228
- * collector rather than a fresh single-rule one.
243
+ * disposition of the freshness + replay gates differs, and the sink is the
244
+ * caller's collector rather than a fresh single-rule one.
229
245
  */
230
246
  function collectBatchLatest(options) {
231
247
  const packageAddress = options.package ?? '@waterx/rule';
@@ -247,6 +263,63 @@ function collectBatchLatest(options) {
247
263
  arguments: (0, index_ts_1.normalizeMoveArguments)(options.arguments, argumentsTypes, parameterNames),
248
264
  });
249
265
  }
266
+ /**
267
+ * Streaming per-symbol sibling of `feed_batch_latest`. Verifies ONE enclave
268
+ * signature over a Merkle `root`, checks `item` is a leaf of that root via
269
+ * `proof`, then runs the identical per-item gates + `oracle::aggregate`. Use for a
270
+ * WaterxRule-only aggregator; for a Pyth-coexisting aggregator use
271
+ * `collect_single_with_proof`.
272
+ */
273
+ function feedSingleWithProof(options) {
274
+ const packageAddress = options.package ?? '@waterx/rule';
275
+ const argumentsTypes = [
276
+ null,
277
+ null,
278
+ '0x2::clock::Clock',
279
+ null,
280
+ null,
281
+ 'u64',
282
+ null,
283
+ 'vector<vector<u8>>',
284
+ 'vector<u8>'
285
+ ];
286
+ const parameterNames = ["oracleObj", "config", "enclaveConfig", "enclave", "timestampMs", "item", "proof", "sig"];
287
+ return (tx) => tx.moveCall({
288
+ package: packageAddress,
289
+ module: 'waterx_rule',
290
+ function: 'feed_single_with_proof',
291
+ arguments: (0, index_ts_1.normalizeMoveArguments)(options.arguments, argumentsTypes, parameterNames),
292
+ });
293
+ }
294
+ /**
295
+ * Collect-only sibling of `feed_single_with_proof` for the dual-rule latest path
296
+ * (WaterX alongside Pyth on one aggregator), mirroring `collect_batch_latest`:
297
+ * verifies root + proof, feeds the leaf into the caller's `PriceCollector` WITHOUT
298
+ * calling `oracle::aggregate`. Abort vs abstain matches `collect_batch_latest`
299
+ * exactly — a config/integrity mismatch aborts, a freshness miss abstains so Pyth
300
+ * can cover a lagging TEE.
301
+ */
302
+ function collectSingleWithProof(options) {
303
+ const packageAddress = options.package ?? '@waterx/rule';
304
+ const argumentsTypes = [
305
+ null,
306
+ null,
307
+ '0x2::clock::Clock',
308
+ null,
309
+ null,
310
+ 'u64',
311
+ null,
312
+ 'vector<vector<u8>>',
313
+ 'vector<u8>'
314
+ ];
315
+ const parameterNames = ["collector", "config", "enclaveConfig", "enclave", "timestampMs", "item", "proof", "sig"];
316
+ return (tx) => tx.moveCall({
317
+ package: packageAddress,
318
+ module: 'waterx_rule',
319
+ function: 'collect_single_with_proof',
320
+ arguments: (0, index_ts_1.normalizeMoveArguments)(options.arguments, argumentsTypes, parameterNames),
321
+ });
322
+ }
250
323
  /**
251
324
  * Verifies a historical quote without touching `waterx_oracle`.
252
325
  *
@@ -438,6 +511,32 @@ function batchPriceIntent(options = {}) {
438
511
  function: 'batch_price_intent',
439
512
  });
440
513
  }
514
+ function merkleRootIntent(options = {}) {
515
+ const packageAddress = options.package ?? '@waterx/rule';
516
+ return (tx) => tx.moveCall({
517
+ package: packageAddress,
518
+ module: 'waterx_rule',
519
+ function: 'merkle_root_intent',
520
+ });
521
+ }
522
+ /**
523
+ * The keccak256 leaf hash for `item` — `keccak256(0x00 || BCS(item))`. Exposed so
524
+ * the off-chain builder / SDK can be pinned against the on-chain encoding in a
525
+ * cross-implementation golden test.
526
+ */
527
+ function leafHashOf(options) {
528
+ const packageAddress = options.package ?? '@waterx/rule';
529
+ const argumentsTypes = [
530
+ null
531
+ ];
532
+ const parameterNames = ["item"];
533
+ return (tx) => tx.moveCall({
534
+ package: packageAddress,
535
+ module: 'waterx_rule',
536
+ function: 'leaf_hash_of',
537
+ arguments: (0, index_ts_1.normalizeMoveArguments)(options.arguments, argumentsTypes, parameterNames),
538
+ });
539
+ }
441
540
  function maxBatchSize(options = {}) {
442
541
  const packageAddress = options.package ?? '@waterx/rule';
443
542
  return (tx) => tx.moveCall({
@@ -824,6 +923,62 @@ function sourceGataUsdtPerpWs(options = {}) {
824
923
  function: 'source_gata_usdt_perp_ws',
825
924
  });
826
925
  }
926
+ function sourceBybitSpotWs(options = {}) {
927
+ const packageAddress = options.package ?? '@waterx/rule';
928
+ return (tx) => tx.moveCall({
929
+ package: packageAddress,
930
+ module: 'waterx_rule',
931
+ function: 'source_bybit_spot_ws',
932
+ });
933
+ }
934
+ function sourceXstockEquityRest(options = {}) {
935
+ const packageAddress = options.package ?? '@waterx/rule';
936
+ return (tx) => tx.moveCall({
937
+ package: packageAddress,
938
+ module: 'waterx_rule',
939
+ function: 'source_xstock_equity_rest',
940
+ });
941
+ }
942
+ function sourceOkxSpotWs(options = {}) {
943
+ const packageAddress = options.package ?? '@waterx/rule';
944
+ return (tx) => tx.moveCall({
945
+ package: packageAddress,
946
+ module: 'waterx_rule',
947
+ function: 'source_okx_spot_ws',
948
+ });
949
+ }
950
+ function sourceHyperliquidPerpWs(options = {}) {
951
+ const packageAddress = options.package ?? '@waterx/rule';
952
+ return (tx) => tx.moveCall({
953
+ package: packageAddress,
954
+ module: 'waterx_rule',
955
+ function: 'source_hyperliquid_perp_ws',
956
+ });
957
+ }
958
+ function sourceGateioSpotWs(options = {}) {
959
+ const packageAddress = options.package ?? '@waterx/rule';
960
+ return (tx) => tx.moveCall({
961
+ package: packageAddress,
962
+ module: 'waterx_rule',
963
+ function: 'source_gateio_spot_ws',
964
+ });
965
+ }
966
+ function sourceKrakenSpotWs(options = {}) {
967
+ const packageAddress = options.package ?? '@waterx/rule';
968
+ return (tx) => tx.moveCall({
969
+ package: packageAddress,
970
+ module: 'waterx_rule',
971
+ function: 'source_kraken_spot_ws',
972
+ });
973
+ }
974
+ function sourcePythLazerWs(options = {}) {
975
+ const packageAddress = options.package ?? '@waterx/rule';
976
+ return (tx) => tx.moveCall({
977
+ package: packageAddress,
978
+ module: 'waterx_rule',
979
+ function: 'source_pyth_lazer_ws',
980
+ });
981
+ }
827
982
  function methodDirect(options = {}) {
828
983
  const packageAddress = options.package ?? '@waterx/rule';
829
984
  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`.
@@ -38,9 +38,10 @@ import { type WaterxSignedEnvelope } from "./rules/waterx-rule.ts";
38
38
  * if it is stale — it never aborts — so the call stays mandatory while
39
39
  * `pyth_rule` remains in the ticker's on-chain weighted set
40
40
  * (`EMissingPriceSource` requires every weighted rule to appear).
41
- * - **Lazer** — fed when `lazerUpdate` is supplied: the verified
42
- * `pyth_lazer::update::Update` produced by this PTB's lazer update leg
43
- * (see `PythLazerRule.buildUpdateCalls`). If the ticker's aggregator does
41
+ * - **Lazer** — fed when `lazerUpdate` is supplied: the verified update this
42
+ * PTB's lazer update leg produced with the network's verify entry
43
+ * (`update_v2::Update` on mainnet, `update::Update` on testnet see
44
+ * `PythLazerRule.buildUpdateCalls`). If the ticker's aggregator does
44
45
  * not (yet) weight `PythLazerRule`, the contribution is silently dropped
45
46
  * on-chain — feeding ahead of the weight migration is harmless.
46
47
  * - **Supra** — fed alongside Pyth/Lazer when supra is enabled + wired
@@ -56,6 +57,9 @@ export declare function aggregateTicker(tx: Transaction, host: OracleHost, args:
56
57
  ticker: string;
57
58
  priceInfoObjectId?: string;
58
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. */
59
63
  waterxEnvelope?: WaterxSignedEnvelope;
60
64
  }): void;
61
65
  /**
@@ -90,9 +90,10 @@ async function resolveGroupUpdateData(host, group, provider) {
90
90
  * if it is stale — it never aborts — so the call stays mandatory while
91
91
  * `pyth_rule` remains in the ticker's on-chain weighted set
92
92
  * (`EMissingPriceSource` requires every weighted rule to appear).
93
- * - **Lazer** — fed when `lazerUpdate` is supplied: the verified
94
- * `pyth_lazer::update::Update` produced by this PTB's lazer update leg
95
- * (see `PythLazerRule.buildUpdateCalls`). If the ticker's aggregator does
93
+ * - **Lazer** — fed when `lazerUpdate` is supplied: the verified update this
94
+ * PTB's lazer update leg produced with the network's verify entry
95
+ * (`update_v2::Update` on mainnet, `update::Update` on testnet see
96
+ * `PythLazerRule.buildUpdateCalls`). If the ticker's aggregator does
96
97
  * not (yet) weight `PythLazerRule`, the contribution is silently dropped
97
98
  * on-chain — feeding ahead of the weight migration is harmless.
98
99
  * - **Supra** — fed alongside Pyth/Lazer when supra is enabled + wired
@@ -119,17 +120,25 @@ function aggregateTicker(tx, host, args) {
119
120
  (0, pyth_lazer_rule_ts_1.feedLazerRule)(tx, host, collector, args.lazerUpdate);
120
121
  fed = true;
121
122
  }
122
- if (args.waterxEnvelope !== undefined) {
123
- // waterx_rule::collect_batch_latest verifies the batch signature and feeds
124
- // this collector's symbol from the batch. If the ticker's aggregator does
125
- // not (yet) weight `WaterxRule`, the contribution is silently dropped
126
- // on-chain feeding ahead of the weight migration is safe for THIS tx.
127
- // CAVEAT (unlike lazer): the feed call records a per-symbol signed-
128
- // timestamp high-water mark REGARDLESS of weights, and a replayed
129
- // timestamp ABORTS (`EReplayedSignature`, audit F-014) so two PTBs
130
- // carrying the same envelope for the same symbol cannot both land; the
131
- // second aborts even where waterx is unweighted. See WaterxRule's module
132
- // header.
123
+ // One waterx leg at most, and the leaf shape wins: both entries record the
124
+ // same per-symbol signed-timestamp high-water mark and feed the same rule
125
+ // witness into this collector, so emitting both would make the second one
126
+ // abstain on its own predecessor's mark for no gain. `refreshOraclePrices`
127
+ // only ever supplies one; a caller that passes both gets the cheaper leg.
128
+ if (args.waterxLeaf !== undefined) {
129
+ // waterx_rule::collect_single_with_proof re-derives the snapshot root from
130
+ // this leaf + its proof, verifies the enclave signature over that root, and
131
+ // feeds the price. If the ticker's aggregator does not (yet) weight
132
+ // `WaterxRule`, the contribution is silently dropped on-chain feeding
133
+ // ahead of the weight migration is safe. See WaterxRule's module header for
134
+ // the abort-vs-abstain split.
135
+ (0, waterx_rule_ts_1.feedWaterxRuleWithProof)(tx, host, collector, args.waterxLeaf);
136
+ fed = true;
137
+ }
138
+ else if (args.waterxEnvelope !== undefined) {
139
+ // Fallback shape: collect_batch_latest re-verifies the WHOLE batch
140
+ // signature (every item rebuilt in-PTB) and feeds this collector's symbol
141
+ // out of it. Only reached against a quote-center with no leaf route.
133
142
  (0, waterx_rule_ts_1.feedWaterxRule)(tx, host, collector, args.waterxEnvelope);
134
143
  fed = true;
135
144
  }
@@ -222,11 +231,11 @@ async function refreshOraclePrices(tx, host, tickers, opts = {}) {
222
231
  if (tickers.length === 0)
223
232
  return;
224
233
  // Dedupe the caller's list (order-preserving): a repeated ticker would
225
- // otherwise aggregate TWICE in this one PTB — wasted gas for every rule,
226
- // and a hard ABORT under waterx: the second `collect_batch_latest` carries
227
- // the same envelope, and the on-chain per-symbol replay guard rejects an
228
- // already-accepted signed timestamp (`EReplayedSignature`, F-014) even
229
- // inside a single transaction.
234
+ // otherwise aggregate TWICE in this one PTB — wasted gas for every rule, and
235
+ // under waterx the second collect would be dead weight on top of that: the
236
+ // on-chain per-symbol replay guard (F-014) sees its own predecessor's
237
+ // high-water mark from earlier in this same transaction and abstains, so the
238
+ // repeat pays full verification cost to contribute nothing.
230
239
  tickers = [...new Set(tickers)];
231
240
  // price_info_object lookup for every ticker with a pyth_rule.feeds entry —
232
241
  // needed by aggregateTicker's (unchanged) Pyth feed step below regardless of
@@ -297,10 +306,12 @@ async function refreshOraclePrices(tx, host, tickers, opts = {}) {
297
306
  // leg needs per-ticker data from its update leg must decide its carry here
298
307
  // — falling through silently would starve its weighted tickers on-chain.
299
308
  const lazerUpdateByTicker = new Map();
300
- // Signed batch envelope per waterx-served ticker. Unlike Lazer's shared PTB
301
- // handle, waterx's verify+feed is bundled into `collect_batch_latest` in the
302
- // per-ticker feed leg, so its `buildUpdateCalls` emits nothing and the
303
- // envelope is carried straight from the group's fetched data.
309
+ // Signed waterx data per served ticker a per-symbol Merkle leaf normally, a
310
+ // shared batch envelope on the fallback shape. Unlike Lazer's shared PTB
311
+ // handle, waterx's verify+feed is bundled into the per-ticker collect call, so
312
+ // its `buildUpdateCalls` emits nothing and the signed data is carried straight
313
+ // from the group's fetched data.
314
+ const waterxLeafByTicker = new Map();
304
315
  const waterxEnvelopeByTicker = new Map();
305
316
  for (const [i, group] of groups.entries()) {
306
317
  const data = dataByGroup[i] ?? null;
@@ -324,12 +335,45 @@ async function refreshOraclePrices(tx, host, tickers, opts = {}) {
324
335
  break;
325
336
  case "waterx_rule": {
326
337
  // waterx_rule emits no shared handle (verify+feed is bundled into the
327
- // per-ticker `collect_batch_latest`), so the envelope is carried
328
- // straight from this group's fetched data to the feed leg below.
329
- const envelope = (0, waterx_rule_ts_1.waterxEnvelopeOf)(data);
330
- if (envelope) {
331
- for (const ticker of group.tickers)
332
- waterxEnvelopeByTicker.set(ticker, envelope);
338
+ // per-ticker collect call), so the signed data is carried straight from
339
+ // this group's fetched data to the feed leg below. Leaves are per-symbol
340
+ // and indexed BY symbol — never fanned out across the group like the
341
+ // envelope, since each leaf only verifies for its own symbol
342
+ // (`ECollectorSymbolMismatch`). A leaf for a symbol outside this group is
343
+ // dropped rather than carried: the feed leg is keyed by ticker anyway.
344
+ const leaves = (0, waterx_rule_ts_1.waterxLeavesOf)(data);
345
+ // `length > 0` matters: `{ leaves: [] }` is shape-valid (`[].every(...)`
346
+ // is `true`), so a bare truthiness test would TAKE this branch, carry
347
+ // nothing, and `break` past the envelope branch below.
348
+ if (leaves && leaves.length > 0) {
349
+ const served = new Set(group.tickers);
350
+ for (const leaf of leaves) {
351
+ if (served.has(leaf.symbol))
352
+ waterxLeafByTicker.set(leaf.symbol, leaf);
353
+ }
354
+ }
355
+ else {
356
+ const envelope = (0, waterx_rule_ts_1.waterxEnvelopeOf)(data);
357
+ if (envelope) {
358
+ for (const ticker of group.tickers)
359
+ waterxEnvelopeByTicker.set(ticker, envelope);
360
+ }
361
+ }
362
+ // Fail the BUILD, not the chain. Both suppliers of `data` already
363
+ // guarantee full coverage — the live fetch through `assertCoverage`, a
364
+ // cached payload through `narrowUpdateData` (which returns `null`, i.e.
365
+ // "miss → live fetch", rather than a partial) — so reaching here with a
366
+ // ticker uncarried means one of those invariants broke. Left alone it
367
+ // emits a collector with no waterx leg, which surfaces MUCH later as an
368
+ // opaque on-chain `EMissingPriceSource` (or, if the rule is unweighted
369
+ // for that ticker, as a silently thinner weighted set). This names the
370
+ // tickers instead.
371
+ const uncarried = group.tickers.filter((t) => !waterxLeafByTicker.has(t) && !waterxEnvelopeByTicker.has(t));
372
+ if (uncarried.length > 0) {
373
+ throw new Error(`waterx_rule update data carries no signed price for ticker(s): ${uncarried.join(", ")}. ` +
374
+ "Expected one signed leaf per ticker (or a batch envelope covering all of " +
375
+ "them) — a payload that serves none of a group's tickers must be reported " +
376
+ "as a miss, not fed.");
333
377
  }
334
378
  break;
335
379
  }
@@ -345,6 +389,7 @@ async function refreshOraclePrices(tx, host, tickers, opts = {}) {
345
389
  ticker,
346
390
  priceInfoObjectId: priceInfoByTicker.get(ticker),
347
391
  lazerUpdate: lazerUpdateByTicker.get(ticker),
392
+ waterxLeaf: waterxLeafByTicker.get(ticker),
348
393
  waterxEnvelope: waterxEnvelopeByTicker.get(ticker),
349
394
  });
350
395
  }
@@ -96,7 +96,8 @@ export interface WaterxRuleFeedEntry {
96
96
  /**
97
97
  * `waterx_rule` deployment entry — the first-party Nautilus-TEE oracle rule.
98
98
  * Read by `WaterxRule` (`rules/waterx-rule.ts`): `feeds` for ticker support,
99
- * `config`/`enclave_config`/`enclave` for the `collect_batch_latest` call,
99
+ * `config`/`enclave_config`/`enclave` for the collect call
100
+ * (`collect_single_with_proof`, or `collect_batch_latest` on the fallback shape),
100
101
  * `published_at` for the package address. The off-chain signed price is pulled
101
102
  * from the quote-center (endpoint from the rule-owned `WATERX_INFRA` table in
102
103
  * `rules/waterx-rule.ts`), not this JSON.
@@ -201,7 +202,7 @@ export interface WaterxAccessConfig {
201
202
  * Quote-center base URL override (`waterxEndpoint` create option). A base
202
203
  * PATH is preserved — the rule appends via `joinEndpointPath`, so
203
204
  * `https://app.example/api/quote-center` resolves to
204
- * `…/api/quote-center/v1/quotes/update` and a proxy route is not rewritten
205
+ * `…/api/quote-center/v1/quotes/leaves` and a proxy route is not rewritten
205
206
  * away. A trailing slash is trimmed.
206
207
  */
207
208
  endpoint?: string;
@@ -29,8 +29,8 @@ export { PythCoreRule } from "./rules/pyth-core-rule.ts";
29
29
  export type { PythCoreUpdatePayload } from "./rules/pyth-core-rule.ts";
30
30
  export { PythLazerRule, LazerApiKeyMissingError } from "./rules/pyth-lazer-rule.ts";
31
31
  export type { PythLazerUpdatePayload } from "./rules/pyth-lazer-rule.ts";
32
- export { WaterxRule, parseSignedEnvelope, BATCH_PRICE_INTENT, WATERX_INFRA, waterxQuoteCenterEndpoint, waterxEnvelopeOf, } from "./rules/waterx-rule.ts";
33
- export type { WaterxUpdatePayload, WaterxSignedEnvelope, WaterxBatchItem, } from "./rules/waterx-rule.ts";
32
+ export { WaterxRule, parseSignedEnvelope, parseSignedLeaves, BATCH_PRICE_INTENT, MERKLE_ROOT_INTENT, WATERX_INFRA, waterxQuoteCenterEndpoint, waterxLeavesOf, waterxEnvelopeOf, } from "./rules/waterx-rule.ts";
33
+ export type { WaterxUpdatePayload, WaterxLeafPayload, WaterxEnvelopePayload, WaterxSignedEnvelope, WaterxSignedLeaf, WaterxBatchItem, } from "./rules/waterx-rule.ts";
34
34
  export { OracleSourceNotImplementedError, resolveOracleRule } from "./rule-registry.ts";
35
35
  export { aggregateTicker, aggregateTickerWithPyth, aggregateTickerWithConstant, refreshOraclePrices, } from "./aggregate.ts";
36
36
  export { openPythSponsorFund, reimbursePythSponsor } from "./rules/sponsor.ts";