@morpho-dev/router 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -41,7 +41,6 @@ let openapi_fetch = require("openapi-fetch");
41
41
  openapi_fetch = __toESM(openapi_fetch);
42
42
  let viem_actions = require("viem/actions");
43
43
  let viem_chains = require("viem/chains");
44
- let viem_accounts = require("viem/accounts");
45
44
  let __openzeppelin_merkle_tree = require("@openzeppelin/merkle-tree");
46
45
  let pako = require("pako");
47
46
 
@@ -149,13 +148,16 @@ var OfferResponse_exports = /* @__PURE__ */ __export({ from: () => from$11 });
149
148
  * Creates an `OfferResponse` from an `Offer`.
150
149
  * @constructor
151
150
  * @param offer - {@link Offer}
151
+ * @param attestation - {@link Attestation}
152
152
  * @returns The created `OfferResponse`. {@link OfferResponse}
153
153
  */
154
- function from$11(offer) {
155
- const result = toSnakeCase$1(offer);
154
+ function from$11(offer, attestation) {
155
+ const { signature: _, ...rest } = toSnakeCase$1(offer);
156
156
  return {
157
- ...result,
158
- signature: result.signature ?? null
157
+ ...rest,
158
+ root: attestation?.root.toLowerCase() ?? null,
159
+ proof: attestation?.proof.map((p) => p.toLowerCase()) ?? null,
160
+ signature: attestation?.signature.toLowerCase() ?? null
159
161
  };
160
162
  }
161
163
 
@@ -204,10 +206,12 @@ const offerExample = {
204
206
  data: "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000034cf890db685fc536e05652fb41f02090c3fb751000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000108e644e3ab01184155270aa92a00000000000",
205
207
  gas_limit: "500000"
206
208
  },
207
- signature: "0x1234567890123456789012345678901234567890123456789012345678901234123456789012345678901234567890123456789012345678901234567890123400",
208
209
  consumed: "0",
209
210
  takeable: "369216000000000000000000",
210
- block_number: 0xa7495128adfb1
211
+ block_number: 0xa7495128adfb1,
212
+ root: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
213
+ proof: ["0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba"],
214
+ signature: "0x1234567890123456789012345678901234567890123456789012345678901234123456789012345678901234567890123456789012345678901234567890123400"
211
215
  };
212
216
  const collectorsHealthExample = {
213
217
  name: "offers",
@@ -352,6 +356,16 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
352
356
  type: "number",
353
357
  example: offerExample.block_number
354
358
  })], OfferListItemResponse.prototype, "block_number", void 0);
359
+ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
360
+ type: "string",
361
+ nullable: true,
362
+ example: offerExample.root
363
+ })], OfferListItemResponse.prototype, "root", void 0);
364
+ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
365
+ type: [String],
366
+ nullable: true,
367
+ example: offerExample.proof
368
+ })], OfferListItemResponse.prototype, "proof", void 0);
355
369
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
356
370
  type: "string",
357
371
  nullable: true,
@@ -535,44 +549,61 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
535
549
  var ValidateOffersRequest = class {};
536
550
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
537
551
  type: () => [ValidateOfferRequest],
538
- description: "Array of offers in snake_case format. Mutually exclusive with 'calldata'.",
539
- required: false
552
+ description: "Array of offers in snake_case format. Required, non-empty.",
553
+ required: true
540
554
  })], ValidateOffersRequest.prototype, "offers", void 0);
555
+ var ValidationSuccessDataResponse = class {};
541
556
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
542
557
  type: "string",
543
- description: "Encoded tree calldata as a hex string (0x-prefixed). Mutually exclusive with 'offers'.",
544
- example: "0x01...",
545
- required: false
546
- })], ValidateOffersRequest.prototype, "calldata", void 0);
547
- var ValidateOfferResultResponse = class {};
558
+ description: "Unsigned payload: version (1B) + gzip(offers) + root (32B).",
559
+ example: "0x01789c..."
560
+ })], ValidationSuccessDataResponse.prototype, "payload", void 0);
548
561
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
549
562
  type: "string",
550
- example: offerExample.hash
551
- })], ValidateOfferResultResponse.prototype, "offer_hash", void 0);
563
+ description: "Merkle tree root to sign with EIP-191.",
564
+ example: "0xac4bd8318ec914f89f8af913f162230575b0ac0696a19256bc12138c5cfe1427"
565
+ })], ValidationSuccessDataResponse.prototype, "root", void 0);
566
+ var ValidationSuccessResponse = class extends SuccessResponse {};
552
567
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
553
- type: "boolean",
554
- example: false
555
- })], ValidateOfferResultResponse.prototype, "valid", void 0);
568
+ type: "string",
569
+ nullable: true,
570
+ example: null
571
+ })], ValidationSuccessResponse.prototype, "cursor", void 0);
572
+ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
573
+ type: () => ValidationSuccessDataResponse,
574
+ description: "Payload and root for client-side signing."
575
+ })], ValidationSuccessResponse.prototype, "data", void 0);
576
+ var ValidationIssueResponse = class {};
577
+ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
578
+ type: "number",
579
+ description: "0-indexed position of the failed offer in the request array.",
580
+ example: 0
581
+ })], ValidationIssueResponse.prototype, "index", void 0);
556
582
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
557
583
  type: "string",
558
- example: "parse_error",
559
- nullable: true
560
- })], ValidateOfferResultResponse.prototype, "rule", void 0);
584
+ description: "Gatekeeper rule name that rejected the offer.",
585
+ example: "no_buy"
586
+ })], ValidationIssueResponse.prototype, "rule", void 0);
561
587
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
562
588
  type: "string",
563
- example: "Invalid offer. 'offering': invalid address",
564
- nullable: true
565
- })], ValidateOfferResultResponse.prototype, "message", void 0);
566
- var ValidateOffersListResponse = class extends SuccessResponse {};
589
+ description: "Human-readable rejection reason.",
590
+ example: "Buy offers are not supported"
591
+ })], ValidationIssueResponse.prototype, "message", void 0);
592
+ var ValidationFailureDataResponse = class {};
593
+ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
594
+ type: () => [ValidationIssueResponse],
595
+ description: "List of validation issues. Returned when any offer fails validation."
596
+ })], ValidationFailureDataResponse.prototype, "issues", void 0);
597
+ var ValidationFailureResponse = class extends SuccessResponse {};
567
598
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
568
599
  type: "string",
569
600
  nullable: true,
570
601
  example: null
571
- })], ValidateOffersListResponse.prototype, "cursor", void 0);
602
+ })], ValidationFailureResponse.prototype, "cursor", void 0);
572
603
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
573
- type: () => [ValidateOfferResultResponse],
574
- description: "Validation results for each offer."
575
- })], ValidateOffersListResponse.prototype, "data", void 0);
604
+ type: () => ValidationFailureDataResponse,
605
+ description: "List of validation issues. Returned when any offer fails validation."
606
+ })], ValidationFailureResponse.prototype, "data", void 0);
576
607
  var BookLevelResponse = class {};
577
608
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
578
609
  type: "string",
@@ -637,13 +668,18 @@ __decorate([
637
668
  methods: ["post"],
638
669
  path: "/v1/validate",
639
670
  summary: "Validate offers",
640
- description: "Validates offers against router validation rules. Returns validation status for each offer. Accepts either an array of offers or encoded calldata (mutually exclusive)."
671
+ description: "Validates offers against router validation rules. Returns unsigned payload + root on success, or issues only on validation failure."
641
672
  }),
642
673
  (0, openapi_metadata_decorators.ApiBody)({ type: ValidateOffersRequest }),
643
674
  (0, openapi_metadata_decorators.ApiResponse)({
644
675
  status: 200,
645
676
  description: "Success",
646
- type: ValidateOffersListResponse
677
+ type: ValidationSuccessResponse
678
+ }),
679
+ (0, openapi_metadata_decorators.ApiResponse)({
680
+ status: 200,
681
+ description: "Validation issues",
682
+ type: ValidationFailureResponse
647
683
  })
648
684
  ], ValidateController.prototype, "validateOffers", null);
649
685
  ValidateController = __decorate([(0, openapi_metadata_decorators.ApiTags)("Validate"), (0, openapi_metadata_decorators.ApiResponse)({
@@ -812,7 +848,7 @@ const OpenApi = async (options = {}) => {
812
848
  if (options.rules && options.rules.length > 0) {
813
849
  const rulesDescription = options.rules.map((rule) => `- **${rule.name}**: ${rule.description}`).join("\n");
814
850
  const validatePath = document.paths?.["/v1/validate"];
815
- if (validatePath && "post" in validatePath && validatePath.post) validatePath.post.description = `Validates offers against router validation rules. Returns validation status for each offer.\n\n**Available validation rules:**\n${rulesDescription}`;
851
+ if (validatePath && "post" in validatePath && validatePath.post) validatePath.post.description = `Validates offers against router validation rules. Returns unsigned payload + root on success, or issues only on validation failure.\n\n**Available validation rules:**\n${rulesDescription}`;
816
852
  }
817
853
  return document;
818
854
  };
@@ -824,17 +860,23 @@ var Cursor_exports = /* @__PURE__ */ __export({
824
860
  encode: () => encode$3,
825
861
  validate: () => validate
826
862
  });
827
- function validate(cursor) {
828
- if (!cursor || typeof cursor !== "object") throw new Error("Cursor must be an object");
829
- const c = cursor;
830
- if (![
863
+ const isSort = (value) => {
864
+ return [
831
865
  "rate",
832
866
  "maturity",
833
867
  "expiry",
834
868
  "amount"
835
- ].includes(c.sort)) throw new Error(`Invalid sort field: ${c.sort}. Must be one of: rate, maturity, expiry, amount`);
836
- if (!["asc", "desc"].includes(c.dir)) throw new Error(`Invalid direction: ${c.dir}. Must be one of: asc, desc`);
837
- if (!/^0x[a-fA-F0-9]{64}$/.test(c.hash)) throw new Error(`Invalid hash format: ${c.hash}. Must be a 64-character hex string starting with 0x`);
869
+ ].includes(value);
870
+ };
871
+ function validate(cursor) {
872
+ if (!cursor || typeof cursor !== "object") throw new Error("Cursor must be an object");
873
+ const c = cursor;
874
+ const sort = c.sort;
875
+ const dir = c.dir;
876
+ const hash$1 = c.hash;
877
+ if (typeof sort !== "string" || !isSort(sort)) throw new Error(`Invalid sort field: ${String(sort)}. Must be one of: rate, maturity, expiry, amount`);
878
+ if (typeof dir !== "string" || !["asc", "desc"].includes(dir)) throw new Error(`Invalid direction: ${String(dir)}. Must be one of: asc, desc`);
879
+ if (typeof hash$1 !== "string" || !/^0x[a-fA-F0-9]{64}$/.test(hash$1)) throw new Error(`Invalid hash format: ${String(hash$1)}. Must be a 64-character hex string starting with 0x`);
838
880
  const validation = {
839
881
  rate: {
840
882
  field: "rate",
@@ -851,24 +893,30 @@ function validate(cursor) {
851
893
  maturity: {
852
894
  field: "maturity",
853
895
  type: "number",
854
- validator: (val) => val > 0,
896
+ min: 1,
855
897
  error: "positive number"
856
898
  },
857
899
  expiry: {
858
900
  field: "expiry",
859
901
  type: "number",
860
- validator: (val) => val > 0,
902
+ min: 1,
861
903
  error: "positive number"
862
904
  }
863
- }[c.sort];
864
- if (!validation) throw new Error(`Invalid sort field: ${c.sort}`);
905
+ }[sort];
906
+ if (!validation) throw new Error(`Invalid sort field: ${sort}`);
865
907
  const fieldValue = c[validation.field];
866
- if (!fieldValue) throw new Error(`${c.sort} sort requires '${validation.field}' field to be present`);
867
- if (typeof fieldValue !== validation.type) throw new Error(`${c.sort} sort requires '${validation.field}' field of type ${validation.type}`);
868
- if (validation.pattern && !validation.pattern.test(fieldValue)) throw new Error(`Invalid ${validation.field} format: ${fieldValue}. Must be a ${validation.error}`);
869
- if (validation.validator && !validation.validator(fieldValue)) throw new Error(`Invalid ${validation.field} value: ${fieldValue}. Must be a ${validation.error}`);
870
- if (c.page !== void 0) {
871
- if (typeof c.page !== "number" || !Number.isInteger(c.page) || c.page < 1) throw new Error("Invalid page: must be a positive integer");
908
+ if (fieldValue === void 0 || fieldValue === null) throw new Error(`${sort} sort requires '${validation.field}' field to be present`);
909
+ if (validation.type === "string") {
910
+ if (typeof fieldValue !== "string") throw new Error(`${sort} sort requires '${validation.field}' field of type ${validation.type}`);
911
+ if (!validation.pattern.test(fieldValue)) throw new Error(`Invalid ${validation.field} format: ${fieldValue}. Must be a ${validation.error}`);
912
+ }
913
+ if (validation.type === "number") {
914
+ if (typeof fieldValue !== "number") throw new Error(`${sort} sort requires '${validation.field}' field of type ${validation.type}`);
915
+ if (fieldValue < validation.min) throw new Error(`Invalid ${validation.field} value: ${fieldValue}. Must be a ${validation.error}`);
916
+ }
917
+ const page = c.page;
918
+ if (page !== void 0) {
919
+ if (typeof page !== "number" || !Number.isInteger(page) || page < 1) throw new Error("Invalid page: must be a positive integer");
872
920
  }
873
921
  return true;
874
922
  }
@@ -984,21 +1032,7 @@ const schemas = {
984
1032
  get_obligations: GetObligationsQueryParams,
985
1033
  get_obligation: GetObligationParams,
986
1034
  get_book: GetBookParams,
987
- validate_offers: zod.object({
988
- offers: zod.any().refine((val) => val === void 0 || Array.isArray(val), { message: "'offers' must be an array" }),
989
- calldata: zod.string().regex(/^0x[a-fA-F0-9]*$/, { message: "'calldata' must be a hex string starting with '0x'" }).optional()
990
- }).superRefine((val, ctx) => {
991
- const hasOffers = val.offers !== void 0;
992
- const hasCalldata = val.calldata !== void 0;
993
- if (hasOffers && hasCalldata) ctx.addIssue({
994
- code: "custom",
995
- message: "Request body must contain either 'offers' or 'calldata', not both"
996
- });
997
- if (!hasOffers && !hasCalldata) ctx.addIssue({
998
- code: "custom",
999
- message: "Request body must contain either 'offers' array or 'calldata' hex string"
1000
- });
1001
- })
1035
+ validate_offers: zod.object({ offers: zod.array(zod.unknown()).min(1, { message: "'offers' must contain at least 1 offer" }) }).strict()
1002
1036
  };
1003
1037
  function parse(action, query) {
1004
1038
  return schemas[action].parse(query);
@@ -1225,8 +1259,12 @@ function decode$2(type, data) {
1225
1259
  }
1226
1260
  function encode$2(type, data) {
1227
1261
  switch (type) {
1228
- case CallbackType.BuyVaultV1Callback: return encodeBuyVaultV1Callback(data);
1229
- case CallbackType.SellERC20Callback: return encodeSellERC20Callback(data);
1262
+ case CallbackType.BuyVaultV1Callback:
1263
+ if (!("vaults" in data)) throw new Error("Invalid callback data");
1264
+ return encodeBuyVaultV1Callback(data);
1265
+ case CallbackType.SellERC20Callback:
1266
+ if (!("collaterals" in data)) throw new Error("Invalid callback data");
1267
+ return encodeSellERC20Callback(data);
1230
1268
  default: throw new Error("Invalid callback type");
1231
1269
  }
1232
1270
  }
@@ -1554,22 +1592,22 @@ const DEFAULT_BATCH_SIZE$1 = 2500;
1554
1592
  const MAX_BLOCK_WINDOW = 1e4;
1555
1593
  const DEFAULT_BLOCK_WINDOW = 8e3;
1556
1594
  async function* streamLogs(parameters) {
1557
- const { client, contractAddress, event, blockNumberGte, blockNumberLte, order: order$1 = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE$1, blockWindow = DEFAULT_BLOCK_WINDOW } = {} } = parameters;
1595
+ const { client, contractAddress, event, blockNumberGte, blockNumberLte, order = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE$1, blockWindow = DEFAULT_BLOCK_WINDOW } = {} } = parameters;
1558
1596
  if (maxBatchSize > MAX_BATCH_SIZE) throw new InvalidBatchSizeError(maxBatchSize);
1559
1597
  if (blockWindow > MAX_BLOCK_WINDOW) throw new InvalidBlockWindowError(blockWindow);
1560
- if (order$1 === "asc" && blockNumberGte === void 0) throw new MissingBlockNumberError();
1598
+ if (order === "asc" && blockNumberGte === void 0) throw new MissingBlockNumberError();
1561
1599
  const latestBlock = (await (0, viem_actions.getBlock)(client, {
1562
1600
  blockTag: "latest",
1563
1601
  includeTransactions: false
1564
1602
  })).number;
1565
1603
  let toBlock = 0n;
1566
- if (order$1 === "asc") toBlock = min(BigInt(blockNumberGte) + BigInt(blockWindow), blockNumberLte ? BigInt(blockNumberLte) : latestBlock);
1567
- if (order$1 === "desc") toBlock = blockNumberLte === void 0 ? latestBlock : min(BigInt(blockNumberLte), latestBlock);
1604
+ if (order === "asc") toBlock = min(BigInt(blockNumberGte) + BigInt(blockWindow), blockNumberLte ? BigInt(blockNumberLte) : latestBlock);
1605
+ if (order === "desc") toBlock = blockNumberLte === void 0 ? latestBlock : min(BigInt(blockNumberLte), latestBlock);
1568
1606
  let fromBlock = 0n;
1569
- if (order$1 === "asc") fromBlock = min(BigInt(blockNumberGte), latestBlock);
1570
- if (order$1 === "desc") fromBlock = max$1(BigInt(blockNumberGte || toBlock - BigInt(blockWindow)), 0n);
1571
- if (order$1 === "asc") toBlock = min(toBlock, fromBlock + BigInt(blockWindow));
1572
- if (order$1 === "desc") fromBlock = max$1(fromBlock, toBlock - BigInt(blockWindow));
1607
+ if (order === "asc") fromBlock = min(BigInt(blockNumberGte), latestBlock);
1608
+ if (order === "desc") fromBlock = max$1(BigInt(blockNumberGte || toBlock - BigInt(blockWindow)), 0n);
1609
+ if (order === "asc") toBlock = min(toBlock, fromBlock + BigInt(blockWindow));
1610
+ if (order === "desc") fromBlock = max$1(fromBlock, toBlock - BigInt(blockWindow));
1573
1611
  if (fromBlock > toBlock) throw new InvalidBlockRangeError(fromBlock, toBlock);
1574
1612
  let streaming = true;
1575
1613
  while (streaming) {
@@ -1579,29 +1617,29 @@ async function* streamLogs(parameters) {
1579
1617
  fromBlock,
1580
1618
  toBlock
1581
1619
  });
1582
- streaming = order$1 === "asc" ? toBlock < (blockNumberLte || latestBlock) : fromBlock > (blockNumberGte || 0n);
1620
+ streaming = order === "asc" ? toBlock < (blockNumberLte || latestBlock) : fromBlock > (blockNumberGte || 0n);
1583
1621
  if (logs.length === 0 && !streaming) break;
1584
1622
  if (logs.length === 0 && streaming) yield {
1585
1623
  logs: [],
1586
- blockNumber: order$1 === "asc" ? Number(toBlock) : Number(fromBlock)
1624
+ blockNumber: order === "asc" ? Number(toBlock) : Number(fromBlock)
1587
1625
  };
1588
1626
  logs.sort((a, b) => {
1589
- if (a.blockNumber !== b.blockNumber) return order$1 === "asc" ? Number(a.blockNumber - b.blockNumber) : Number(b.blockNumber - a.blockNumber);
1590
- if (a.transactionIndex !== b.transactionIndex) return order$1 === "asc" ? a.transactionIndex - b.transactionIndex : b.transactionIndex - a.transactionIndex;
1591
- return order$1 === "asc" ? a.logIndex - b.logIndex : b.logIndex - a.logIndex;
1627
+ if (a.blockNumber !== b.blockNumber) return order === "asc" ? Number(a.blockNumber - b.blockNumber) : Number(b.blockNumber - a.blockNumber);
1628
+ if (a.transactionIndex !== b.transactionIndex) return order === "asc" ? a.transactionIndex - b.transactionIndex : b.transactionIndex - a.transactionIndex;
1629
+ return order === "asc" ? a.logIndex - b.logIndex : b.logIndex - a.logIndex;
1592
1630
  });
1593
1631
  for (const logBatch of batch(logs, maxBatchSize)) yield {
1594
1632
  logs: logBatch,
1595
- blockNumber: logBatch.length === maxBatchSize ? Number(logBatch[logBatch.length - 1]?.blockNumber) : order$1 === "asc" ? Number(toBlock) : Number(fromBlock)
1633
+ blockNumber: logBatch.length === maxBatchSize ? Number(logBatch[logBatch.length - 1]?.blockNumber) : order === "asc" ? Number(toBlock) : Number(fromBlock)
1596
1634
  };
1597
- if (order$1 === "asc") {
1635
+ if (order === "asc") {
1598
1636
  const upperBound = BigInt(blockNumberLte || latestBlock);
1599
1637
  const nextFromBlock = min(BigInt(toBlock) + 1n, upperBound);
1600
1638
  const nextToBlock = min(toBlock + BigInt(blockWindow) + 1n, upperBound);
1601
1639
  fromBlock = nextFromBlock;
1602
1640
  toBlock = nextToBlock;
1603
1641
  }
1604
- if (order$1 === "desc") {
1642
+ if (order === "desc") {
1605
1643
  const lowerBound = BigInt(blockNumberGte || 0);
1606
1644
  const nextToBlock = max$1(fromBlock - 1n, lowerBound);
1607
1645
  const nextFromBlock = max$1(fromBlock - BigInt(blockWindow) - 1n, lowerBound);
@@ -1611,7 +1649,7 @@ async function* streamLogs(parameters) {
1611
1649
  }
1612
1650
  yield {
1613
1651
  logs: [],
1614
- blockNumber: order$1 === "asc" ? Number(toBlock) : Number(fromBlock)
1652
+ blockNumber: order === "asc" ? Number(toBlock) : Number(fromBlock)
1615
1653
  };
1616
1654
  }
1617
1655
  var InvalidBlockRangeError = class extends BaseError {
@@ -1639,6 +1677,96 @@ var MissingBlockNumberError = class extends BaseError {
1639
1677
  }
1640
1678
  };
1641
1679
 
1680
+ //#endregion
1681
+ //#region src/utils/Random.ts
1682
+ var Random_exports = /* @__PURE__ */ __export({
1683
+ address: () => address,
1684
+ bool: () => bool,
1685
+ bytes: () => bytes,
1686
+ float: () => float,
1687
+ hex: () => hex,
1688
+ int: () => int,
1689
+ seed: () => seed,
1690
+ withSeed: () => withSeed
1691
+ });
1692
+ let currentRng = Math.random;
1693
+ const FNV_OFFSET_BASIS = 2166136261;
1694
+ const FNV_PRIME = 16777619;
1695
+ const hashSeed = (seed$1) => {
1696
+ let hash$1 = FNV_OFFSET_BASIS;
1697
+ for (let i = 0; i < seed$1.length; i += 1) {
1698
+ hash$1 ^= seed$1.charCodeAt(i);
1699
+ hash$1 = Math.imul(hash$1, FNV_PRIME);
1700
+ }
1701
+ return hash$1 >>> 0;
1702
+ };
1703
+ const createSeededRng = (seed$1) => {
1704
+ let state = hashSeed(seed$1);
1705
+ return () => {
1706
+ state += 1831565813;
1707
+ let t = Math.imul(state ^ state >>> 15, state | 1);
1708
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
1709
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
1710
+ };
1711
+ };
1712
+ /**
1713
+ * Runs a function with a deterministic RNG derived from the given seed.
1714
+ */
1715
+ function withSeed(seed$1, fn) {
1716
+ const previous = currentRng;
1717
+ currentRng = createSeededRng(seed$1);
1718
+ try {
1719
+ return fn();
1720
+ } finally {
1721
+ currentRng = previous;
1722
+ }
1723
+ }
1724
+ /**
1725
+ * Seeds the global RNG for deterministic test runs.
1726
+ */
1727
+ function seed(seed$1) {
1728
+ currentRng = createSeededRng(seed$1);
1729
+ }
1730
+ /**
1731
+ * Returns a deterministic random float in [0, 1).
1732
+ */
1733
+ function float() {
1734
+ return currentRng();
1735
+ }
1736
+ /**
1737
+ * Returns a deterministic random integer in [min, maxExclusive).
1738
+ */
1739
+ function int(maxExclusive, min$1 = 0) {
1740
+ return Math.floor(float() * (maxExclusive - min$1)) + min$1;
1741
+ }
1742
+ /**
1743
+ * Returns a deterministic random boolean.
1744
+ */
1745
+ function bool(probability = .5) {
1746
+ return float() < probability;
1747
+ }
1748
+ /**
1749
+ * Returns deterministic random bytes.
1750
+ */
1751
+ function bytes(length) {
1752
+ const output = new Uint8Array(length);
1753
+ for (let i = 0; i < length; i += 1) output[i] = int(256);
1754
+ return output;
1755
+ }
1756
+ /**
1757
+ * Returns a deterministic random hex string for the given byte length.
1758
+ */
1759
+ function hex(byteLength) {
1760
+ const output = bytes(byteLength);
1761
+ return `0x${Array.from(output, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
1762
+ }
1763
+ /**
1764
+ * Returns a deterministic random address.
1765
+ */
1766
+ function address() {
1767
+ return hex(20);
1768
+ }
1769
+
1642
1770
  //#endregion
1643
1771
  //#region src/utils/zod.ts
1644
1772
  const transformHex = (val, ctx) => {
@@ -1760,8 +1888,8 @@ const from$9 = (parameters) => {
1760
1888
  */
1761
1889
  function random$3() {
1762
1890
  return from$9({
1763
- asset: (0, viem_accounts.privateKeyToAccount)((0, viem_accounts.generatePrivateKey)()).address,
1764
- oracle: (0, viem_accounts.privateKeyToAccount)((0, viem_accounts.generatePrivateKey)()).address,
1891
+ asset: address(),
1892
+ oracle: address(),
1765
1893
  lltv: .965
1766
1894
  });
1767
1895
  }
@@ -2176,12 +2304,8 @@ function id(obligation) {
2176
2304
  function random$2() {
2177
2305
  return from$7({
2178
2306
  chainId: 1,
2179
- loanToken: (0, viem_accounts.privateKeyToAccount)((0, viem_accounts.generatePrivateKey)()).address,
2180
- collaterals: [from$9({
2181
- asset: (0, viem_accounts.privateKeyToAccount)((0, viem_accounts.generatePrivateKey)()).address,
2182
- oracle: (0, viem_accounts.privateKeyToAccount)((0, viem_accounts.generatePrivateKey)()).address,
2183
- lltv: .965
2184
- })],
2307
+ loanToken: address(),
2308
+ collaterals: [random$3()],
2185
2309
  maturity: from$8("end_of_next_quarter")
2186
2310
  });
2187
2311
  }
@@ -2201,101 +2325,249 @@ var CollateralsAreNotSortedError = class extends BaseError {
2201
2325
  //#endregion
2202
2326
  //#region src/core/Tree.ts
2203
2327
  var Tree_exports = /* @__PURE__ */ __export({
2328
+ DecodeError: () => DecodeError,
2329
+ EncodeError: () => EncodeError,
2330
+ TreeError: () => TreeError,
2204
2331
  VERSION: () => VERSION,
2205
2332
  decode: () => decode$1,
2206
2333
  encode: () => encode$1,
2207
- from: () => from$6
2334
+ encodeUnsigned: () => encodeUnsigned,
2335
+ from: () => from$6,
2336
+ proofs: () => proofs
2208
2337
  });
2209
2338
  const VERSION = 1;
2339
+ const normalizeHash = (hash$1) => hash$1.toLowerCase();
2210
2340
  /**
2211
2341
  * Builds a Merkle tree from a list of offers.
2212
2342
  *
2213
2343
  * Leaves are the offer `hash` values as `bytes32` and are deterministically
2214
- * ordered in ascending lexicographic order so that the resulting root is stable
2215
- * regardless of the input order.
2344
+ * ordered following the StandardMerkleTree leaf ordering so that the resulting
2345
+ * root is stable regardless of the input order.
2216
2346
  *
2217
2347
  * @param offers - Offers to include in the tree.
2218
2348
  * @returns A `StandardMerkleTree` of `bytes32` leaves representing the offers.
2349
+ * @throws {TreeError} If tree building fails due to offer inconsistencies.
2219
2350
  */
2220
2351
  const from$6 = (offers) => {
2221
- const leaves = order(offers).map((offer) => {
2222
- return [offer.hash];
2223
- });
2352
+ const leaves = offers.map((offer) => [offer.hash]);
2224
2353
  const tree = __openzeppelin_merkle_tree.StandardMerkleTree.of(leaves, ["bytes32"]);
2225
- return Object.assign(tree, { offers });
2354
+ const orderedOffers = orderOffers(tree, offers);
2355
+ return Object.assign(tree, { offers: orderedOffers });
2226
2356
  };
2227
- const byHashAsc = (a, b) => a.localeCompare(b);
2228
- const order = (offers) => {
2229
- return offers.sort((a, b) => byHashAsc(a.hash, b.hash));
2357
+ const orderOffers = (tree, offers) => {
2358
+ const offerByHash = /* @__PURE__ */ new Map();
2359
+ for (const offer of offers) offerByHash.set(normalizeHash(offer.hash), offer);
2360
+ const entries = tree.dump().values.map((value) => {
2361
+ const hash$1 = normalizeHash(value.value[0]);
2362
+ const offer = offerByHash.get(hash$1);
2363
+ if (!offer) throw new TreeError(`missing offer for leaf ${hash$1}`);
2364
+ return {
2365
+ offer,
2366
+ treeIndex: value.treeIndex
2367
+ };
2368
+ });
2369
+ entries.sort((a, b) => b.treeIndex - a.treeIndex);
2370
+ return entries.map((item) => item.offer);
2230
2371
  };
2231
2372
  /**
2232
- * Encodes an `Tree` into a Hex string with a version byte prefix and gzipped payload.
2373
+ * Generates merkle proofs for all offers in a tree.
2233
2374
  *
2234
- * - Layout: `0x{vv}{zip...}` where `{vv}` is one-byte version as two hex chars.
2235
- * - Payload is gzip(JSON.stringify([root, ...offers])) with bigint stringified.
2375
+ * Each proof allows independent verification that an offer is included in the tree
2376
+ * without requiring the full tree. Proofs are ordered by StandardMerkleTree leaf ordering.
2236
2377
  *
2237
- * @param tree - The offer Merkle tree to encode.
2238
- * @returns Hex string starting with `0x{vv}` followed by gzipped payload bytes.
2239
- * @throws Error if the given `root` does not match the offers.
2240
- */
2241
- const encode$1 = (tree) => {
2242
- assertRoot(tree.root, tree.offers);
2243
- const offersPayload = tree.offers.map((offer) => ({
2244
- offering: offer.offering,
2245
- assets: offer.assets.toString(),
2246
- rate: offer.rate.toString(),
2247
- maturity: Number(offer.maturity),
2248
- expiry: Number(offer.expiry),
2249
- start: Number(offer.start),
2250
- nonce: offer.nonce.toString(),
2251
- buy: offer.buy,
2252
- chainId: offer.chainId,
2253
- loanToken: offer.loanToken,
2254
- collaterals: offer.collaterals.map((c) => ({
2255
- asset: c.asset,
2256
- oracle: c.oracle,
2257
- lltv: c.lltv.toString()
2258
- })),
2259
- callback: {
2260
- address: offer.callback.address,
2261
- data: offer.callback.data,
2262
- gasLimit: offer.callback.gasLimit.toString()
2263
- },
2264
- signature: offer.signature,
2265
- hash: offer.hash
2266
- }));
2267
- const compressed = (0, pako.gzip)(JSON.stringify([tree.root, ...offersPayload]));
2268
- const encoded = new Uint8Array(1 + compressed.length);
2269
- if (VERSION > 255) throw new Error(`Version overflow: ${VERSION}`);
2270
- encoded[0] = VERSION;
2271
- encoded.set(compressed, 1);
2378
+ * @param tree - The {@link Tree} to generate proofs for.
2379
+ * @returns Array of proofs - {@link Proof}
2380
+ */
2381
+ const proofs = (tree) => {
2382
+ return tree.offers.map((offer) => {
2383
+ return {
2384
+ offer,
2385
+ path: tree.getProof([offer.hash])
2386
+ };
2387
+ });
2388
+ };
2389
+ const assertHex = (value, expectedBytes, name) => {
2390
+ if (typeof value !== "string" || !(0, viem.isHex)(value)) throw new DecodeError(`${name} is not a valid hex string`);
2391
+ if ((0, viem.hexToBytes)(value).length !== expectedBytes) throw new DecodeError(`${name}: expected ${expectedBytes} bytes`);
2392
+ };
2393
+ const verifySignatureAndRecoverAddress = async (params) => {
2394
+ const { root, signature } = params;
2395
+ assertHex(signature, 65, "signature");
2396
+ const hash$1 = (0, viem.hashMessage)({ raw: root });
2397
+ try {
2398
+ return await (0, viem.recoverAddress)({
2399
+ hash: hash$1,
2400
+ signature
2401
+ });
2402
+ } catch {
2403
+ throw new DecodeError("signature recovery failed");
2404
+ }
2405
+ };
2406
+ /**
2407
+ * Encodes a merkle tree with signature into hex calldata for onchain broadcast.
2408
+ *
2409
+ * Layout: `0x{vv}{gzip([...offers])}{root}{signature}` where:
2410
+ * - `{vv}`: 1-byte version (currently 0x01)
2411
+ * - `{gzip([...offers])}`: gzipped JSON array of serialized offers
2412
+ * - `{root}`: 32-byte merkle root
2413
+ * - `{signature}`: 65-byte EIP-191 signature over raw root bytes
2414
+ *
2415
+ * Validates signature authenticity and root integrity before encoding.
2416
+ *
2417
+ * @example
2418
+ * ```typescript
2419
+ * const tree = Tree.from(offers);
2420
+ * const signature = await wallet.signMessage({ message: { raw: tree.root } });
2421
+ * const calldata = await Tree.encode(tree, signature);
2422
+ * await broadcast(calldata);
2423
+ * ```
2424
+ *
2425
+ * @example
2426
+ * Manual construction (for advanced users):
2427
+ * ```typescript
2428
+ * const tree = Tree.from(offers);
2429
+ * const compressed = gzip(JSON.stringify(tree.offers.map(Offer.serialize)));
2430
+ * const partial = `0x01${bytesToHex(compressed)}${tree.root.slice(2)}`;
2431
+ * const signature = await wallet.signMessage({ message: { raw: tree.root } });
2432
+ * const calldata = `${partial}${signature.slice(2)}`;
2433
+ * ```
2434
+ *
2435
+ * @param tree - Merkle tree of offers
2436
+ * @param signature - EIP-191 signature over raw root bytes
2437
+ * @returns Hex-encoded calldata ready for onchain broadcast
2438
+ * @throws {EncodeError} If signature verification fails or root mismatch
2439
+ */
2440
+ const encode$1 = async (tree, signature) => {
2441
+ validateTreeForEncoding(tree);
2442
+ await verifySignatureAndRecoverAddress({
2443
+ root: tree.root,
2444
+ signature
2445
+ });
2446
+ const unsigned = encodeUnsignedBytes(tree);
2447
+ const sigBytes = (0, viem.hexToBytes)(signature);
2448
+ const encoded = new Uint8Array(unsigned.length + sigBytes.length);
2449
+ encoded.set(unsigned, 0);
2450
+ encoded.set(sigBytes, unsigned.length);
2272
2451
  return (0, viem.bytesToHex)(encoded);
2273
2452
  };
2274
- const assertRoot = (root, offers) => {
2275
- const tree = from$6(offers);
2276
- if (root !== tree.root) throw new Error(`Invalid root: expected ${tree.root}, got ${root}`);
2453
+ /**
2454
+ * Encodes a merkle tree without a signature into hex payload for client-side signing.
2455
+ *
2456
+ * Layout: `0x{vv}{gzip([...offers])}{root}` where:
2457
+ * - `{vv}`: 1-byte version (currently 0x01)
2458
+ * - `{gzip([...offers])}`: gzipped JSON array of serialized offers
2459
+ * - `{root}`: 32-byte merkle root
2460
+ *
2461
+ * Validates root integrity before encoding.
2462
+ *
2463
+ * @param tree - Merkle tree of offers
2464
+ * @returns Hex-encoded unsigned payload
2465
+ * @throws {EncodeError} If root mismatch
2466
+ */
2467
+ const encodeUnsigned = (tree) => {
2468
+ validateTreeForEncoding(tree);
2469
+ return (0, viem.bytesToHex)(encodeUnsignedBytes(tree));
2470
+ };
2471
+ const validateTreeForEncoding = (tree) => {
2472
+ if (VERSION > 255) throw new EncodeError(`version overflow: ${VERSION} exceeds 255`);
2473
+ const computed = from$6(tree.offers);
2474
+ if (tree.root !== computed.root) throw new EncodeError(`root mismatch: expected ${computed.root}, got ${tree.root}`);
2475
+ };
2476
+ const encodeUnsignedBytes = (tree) => {
2477
+ const offersPayload = tree.offers.map(serialize);
2478
+ const compressed = (0, pako.gzip)(JSON.stringify(offersPayload));
2479
+ const rootBytes = (0, viem.hexToBytes)(tree.root);
2480
+ const encoded = new Uint8Array(1 + compressed.length + 32);
2481
+ encoded[0] = VERSION;
2482
+ encoded.set(compressed, 1);
2483
+ encoded.set(rootBytes, 1 + compressed.length);
2484
+ return encoded;
2277
2485
  };
2278
2486
  /**
2279
- * Decodes a Hex string produced by {@link encode} back into an `Tree`.
2487
+ * Decodes hex calldata into a validated merkle tree.
2488
+ *
2489
+ * Validates signature before decompression for fail-fast rejection of invalid payloads.
2490
+ * Returns the tree with separately validated signature and recovered signer address.
2491
+ *
2492
+ * Validation order:
2493
+ * 1. Version check
2494
+ * 2. Signature verification (fail-fast, before decompression)
2495
+ * 3. Decompression (only if signature valid)
2496
+ * 4. Root verification (computed from offers vs embedded root)
2280
2497
  *
2281
- * - Ensures the first byte version matches {@link VERSION}.
2282
- * - Decompresses with gunzip, parses JSON, validates offers, and re-checks the root.
2498
+ * @example
2499
+ * ```typescript
2500
+ * const { tree, signature, signer } = await Tree.decode(calldata);
2501
+ * console.log(`Tree signed by ${signer} with ${tree.offers.length} offers`);
2502
+ * ```
2283
2503
  *
2284
- * @param encoded - Hex string in the form `0x{vv}{zip...}`.
2285
- * @returns A validated `Tree` rebuilt from the offers.
2286
- * @throws Error if the version is invalid or the root does not match the offers.
2287
- */
2288
- const decode$1 = (encoded) => {
2289
- const bytes = (0, viem.hexToBytes)(encoded);
2290
- if (bytes.length < 2) throw new Error("Invalid payload: too short");
2291
- const version = bytes[0];
2292
- if (version !== (VERSION & 255)) throw new Error(`Invalid version: expected ${VERSION}, got ${version}`);
2293
- const decoded = (0, pako.ungzip)(bytes.slice(1), { to: "string" });
2294
- const data = JSON.parse(decoded);
2295
- const root = data[0];
2296
- const tree = from$6(data.slice(1).map((o) => OfferSchema().parse(o)));
2297
- if (root !== tree.root) throw new Error(`Invalid root: expected ${tree.root}, got ${root}`);
2298
- return tree;
2504
+ * @param encoded - Hex calldata in format `0x{vv}{gzip}{root}{signature}`
2505
+ * @returns Validated tree, signature, and recovered signer address
2506
+ * @throws {DecodeError} If version invalid, signature invalid, or root mismatch
2507
+ */
2508
+ const decode$1 = async (encoded) => {
2509
+ const bytes$1 = (0, viem.hexToBytes)(encoded);
2510
+ if (bytes$1.length < 98) throw new DecodeError("payload too short");
2511
+ const version = bytes$1[0];
2512
+ if (version !== (VERSION & 255)) throw new DecodeError(`invalid version: expected ${VERSION}, got ${version ?? 0}`);
2513
+ const signature = (0, viem.bytesToHex)(bytes$1.slice(-65));
2514
+ const root = (0, viem.bytesToHex)(bytes$1.slice(-97, -65));
2515
+ assertHex(root, 32, "root");
2516
+ assertHex(signature, 65, "signature");
2517
+ const signer = await verifySignatureAndRecoverAddress({
2518
+ root,
2519
+ signature
2520
+ });
2521
+ const compressed = bytes$1.slice(1, -97);
2522
+ let decoded;
2523
+ try {
2524
+ decoded = (0, pako.ungzip)(compressed, { to: "string" });
2525
+ } catch {
2526
+ throw new DecodeError("decompression failed");
2527
+ }
2528
+ let rawOffers;
2529
+ try {
2530
+ rawOffers = JSON.parse(decoded);
2531
+ } catch {
2532
+ throw new DecodeError("JSON parse failed");
2533
+ }
2534
+ const tree = from$6(rawOffers.map((o) => OfferSchema().parse(o)));
2535
+ if (root !== tree.root) throw new DecodeError(`root mismatch: expected ${tree.root}, got ${root}`);
2536
+ return {
2537
+ tree,
2538
+ signature,
2539
+ signer
2540
+ };
2541
+ };
2542
+ /**
2543
+ * Error thrown during tree building operations.
2544
+ * Indicates structural issues with the tree (missing offers, inconsistent state).
2545
+ */
2546
+ var TreeError = class extends BaseError {
2547
+ constructor(reason) {
2548
+ super(`Tree error: ${reason}`);
2549
+ _defineProperty(this, "name", "Tree.TreeError");
2550
+ }
2551
+ };
2552
+ /**
2553
+ * Error thrown during tree encoding.
2554
+ * Indicates validation failures (signature, root mismatch, mixed makers).
2555
+ */
2556
+ var EncodeError = class extends BaseError {
2557
+ constructor(reason) {
2558
+ super(`Failed to encode tree: ${reason}`);
2559
+ _defineProperty(this, "name", "Tree.EncodeError");
2560
+ }
2561
+ };
2562
+ /**
2563
+ * Error thrown during tree decoding.
2564
+ * Indicates payload corruption, version mismatch, or validation failures.
2565
+ */
2566
+ var DecodeError = class extends BaseError {
2567
+ constructor(reason) {
2568
+ super(`Failed to decode tree: ${reason}`);
2569
+ _defineProperty(this, "name", "Tree.DecodeError");
2570
+ }
2299
2571
  };
2300
2572
 
2301
2573
  //#endregion
@@ -2315,6 +2587,7 @@ var Offer_exports = /* @__PURE__ */ __export({
2315
2587
  hash: () => hash,
2316
2588
  obligationId: () => obligationId,
2317
2589
  random: () => random$1,
2590
+ serialize: () => serialize,
2318
2591
  sign: () => sign,
2319
2592
  signatureMsg: () => signatureMsg,
2320
2593
  toSnakeCase: () => toSnakeCase,
@@ -2395,16 +2668,47 @@ function toSnakeCase(offer) {
2395
2668
  return toSnakeCase$1(offer);
2396
2669
  }
2397
2670
  /**
2671
+ * Serializes an offer for merkle tree encoding.
2672
+ * Converts BigInt fields to strings for JSON compatibility.
2673
+ *
2674
+ * @param offer - Offer to serialize
2675
+ * @returns JSON-serializable offer object
2676
+ */
2677
+ const serialize = (offer) => ({
2678
+ offering: offer.offering,
2679
+ assets: offer.assets.toString(),
2680
+ rate: offer.rate.toString(),
2681
+ maturity: Number(offer.maturity),
2682
+ expiry: Number(offer.expiry),
2683
+ start: Number(offer.start),
2684
+ nonce: offer.nonce.toString(),
2685
+ buy: offer.buy,
2686
+ chainId: offer.chainId,
2687
+ loanToken: offer.loanToken,
2688
+ collaterals: offer.collaterals.map((c) => ({
2689
+ asset: c.asset,
2690
+ oracle: c.oracle,
2691
+ lltv: c.lltv.toString()
2692
+ })),
2693
+ callback: {
2694
+ address: offer.callback.address,
2695
+ data: offer.callback.data,
2696
+ gasLimit: offer.callback.gasLimit.toString()
2697
+ },
2698
+ signature: offer.signature,
2699
+ hash: offer.hash
2700
+ });
2701
+ /**
2398
2702
  * Generates a random Offer.
2399
2703
  * The returned Offer contains randomly generated values.
2400
2704
  * @warning The generated Offer should not be used for production usage.
2401
2705
  * @returns {Offer} A randomly generated Offer object.
2402
2706
  */
2403
2707
  function random$1(config) {
2404
- const chain = config?.chains ? config.chains[Math.floor(Math.random() * config.chains.length)] : chains$1.ethereum;
2405
- const loanToken = config?.loanTokens ? config.loanTokens[Math.floor(Math.random() * config.loanTokens.length)] : (0, viem_accounts.privateKeyToAccount)((0, viem_accounts.generatePrivateKey)()).address;
2406
- const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [(0, viem_accounts.privateKeyToAccount)((0, viem_accounts.generatePrivateKey)()).address];
2407
- const collateralAsset = collateralCandidates[Math.floor(Math.random() * collateralCandidates.length)];
2708
+ const chain = config?.chains ? config.chains[int(config.chains.length)] : chains$1.ethereum;
2709
+ const loanToken = config?.loanTokens ? config.loanTokens[int(config.loanTokens.length)] : address();
2710
+ const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [address()];
2711
+ const collateralAsset = collateralCandidates[int(collateralCandidates.length)];
2408
2712
  const maturityOption = weightedChoice([["end_of_month", 1], ["end_of_next_month", 1]]);
2409
2713
  const maturity$1 = config?.maturity ?? from$8(maturityOption);
2410
2714
  const lltv = from$10(weightedChoice([
@@ -2418,7 +2722,7 @@ function random$1(config) {
2418
2722
  [.965, 4],
2419
2723
  [.98, 2]
2420
2724
  ]));
2421
- const buy = config?.buy !== void 0 ? config.buy : Math.random() > .5;
2725
+ const buy = config?.buy !== void 0 ? config.buy : bool();
2422
2726
  const ONE = 1000000000000000000n;
2423
2727
  const qMin = buy ? 16 : 4;
2424
2728
  const len = (buy ? 32 : 16) - qMin + 1;
@@ -2429,9 +2733,9 @@ function random$1(config) {
2429
2733
  const rate = config?.rate ?? weightedChoice(ratePairs);
2430
2734
  const loanTokenDecimals = config?.assetsDecimals?.[loanToken] ?? 18;
2431
2735
  const unit = BigInt(10) ** BigInt(loanTokenDecimals);
2432
- const amountBase = BigInt(100 + Math.floor(Math.random() * 999901));
2736
+ const amountBase = BigInt(100 + int(999901));
2433
2737
  const assetsScaled = config?.assets ?? amountBase * unit;
2434
- const consumed = config?.consumed !== void 0 ? config.consumed : Math.random() < .8 ? 0n : assetsScaled * BigInt(1 + Math.floor(Math.random() * 900)) / 1000n;
2738
+ const consumed = config?.consumed !== void 0 ? config.consumed : float() < .8 ? 0n : assetsScaled * BigInt(1 + int(900)) / 1000n;
2435
2739
  const callbackBySide = (() => {
2436
2740
  if (buy) return {
2437
2741
  address: viem.zeroAddress,
@@ -2450,29 +2754,29 @@ function random$1(config) {
2450
2754
  };
2451
2755
  })();
2452
2756
  return from$5({
2453
- offering: config?.offering ?? (0, viem_accounts.privateKeyToAccount)((0, viem_accounts.generatePrivateKey)()).address,
2757
+ offering: config?.offering ?? address(),
2454
2758
  assets: assetsScaled,
2455
2759
  rate,
2456
2760
  maturity: maturity$1,
2457
2761
  expiry: config?.expiry ?? maturity$1 - 1,
2458
2762
  start: config?.start ?? maturity$1 - 10,
2459
- nonce: BigInt(Math.floor(Math.random() * 1e6)),
2763
+ nonce: BigInt(int(1e6)),
2460
2764
  buy,
2461
2765
  chainId: chain.id,
2462
2766
  loanToken,
2463
- collaterals: config?.collaterals ?? Array.from({ length: Math.floor(Math.random() * 3) + 1 }, () => ({
2767
+ collaterals: config?.collaterals ?? Array.from({ length: int(3) + 1 }, () => ({
2464
2768
  ...random$3(),
2465
2769
  lltv
2466
2770
  })).sort((a, b) => a.asset.localeCompare(b.asset)),
2467
2771
  callback: config?.callback ?? callbackBySide,
2468
2772
  consumed,
2469
2773
  takeable: config?.takeable ?? assetsScaled - consumed,
2470
- blockNumber: config?.blockNumber ?? Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)
2774
+ blockNumber: config?.blockNumber ?? int(Number.MAX_SAFE_INTEGER)
2471
2775
  });
2472
2776
  }
2473
2777
  const weightedChoice = (pairs) => {
2474
2778
  const total = pairs.reduce((sum, [, weight]) => sum + weight, 0);
2475
- let roll = Math.random() * total;
2779
+ let roll = float() * total;
2476
2780
  for (const [value, weight] of pairs) {
2477
2781
  roll -= weight;
2478
2782
  if (roll < 0) return value;
@@ -2940,8 +3244,8 @@ function fromSnakeCase(snake) {
2940
3244
  function random() {
2941
3245
  return from$2({
2942
3246
  obligationId: id(random$2()),
2943
- ask: { rate: BigInt(Math.floor(Math.random() * 1e6)) },
2944
- bid: { rate: BigInt(Math.floor(Math.random() * 1e6)) }
3247
+ ask: { rate: BigInt(int(1e6)) },
3248
+ bid: { rate: BigInt(int(1e6)) }
2945
3249
  });
2946
3250
  }
2947
3251
  var InvalidQuoteError = class extends BaseError {
@@ -3041,24 +3345,28 @@ async function getOffers(apiClient, parameters) {
3041
3345
  throw new HttpGetApiFailedError(`GET request returned ${response.status}`, { details: JSON.stringify(error) });
3042
3346
  }
3043
3347
  const offers = data?.data.map((item) => {
3044
- const { signature, ...rest } = item;
3045
- return fromSnakeCase$1({
3046
- ...rest,
3047
- offering: item.offering,
3048
- maturity: from$8(item.maturity),
3049
- loan_token: item.loan_token,
3050
- collaterals: item.collaterals.map((collateral) => ({
3051
- asset: collateral.asset,
3052
- oracle: collateral.oracle,
3053
- lltv: collateral.lltv
3054
- })),
3055
- callback: {
3056
- ...item.callback,
3057
- address: item.callback.address,
3058
- data: item.callback.data
3059
- },
3060
- ...signature !== null ? { signature: item.signature } : void 0
3061
- });
3348
+ const { root, proof, signature, ...rest } = item;
3349
+ return {
3350
+ ...fromSnakeCase$1({
3351
+ ...rest,
3352
+ offering: item.offering,
3353
+ maturity: from$8(item.maturity),
3354
+ loan_token: item.loan_token,
3355
+ collaterals: item.collaterals.map((collateral) => ({
3356
+ asset: collateral.asset,
3357
+ oracle: collateral.oracle,
3358
+ lltv: collateral.lltv
3359
+ })),
3360
+ callback: {
3361
+ ...item.callback,
3362
+ address: item.callback.address,
3363
+ data: item.callback.data
3364
+ },
3365
+ signature: signature?.toLowerCase()
3366
+ }),
3367
+ root: root?.toLowerCase() || void 0,
3368
+ proof: proof?.map((p) => p.toLowerCase()) || void 0
3369
+ };
3062
3370
  }) ?? [];
3063
3371
  return {
3064
3372
  cursor: data?.cursor ?? null,
@@ -3249,8 +3557,8 @@ function getCallback(chain, type) {
3249
3557
  * @param address - Callback contract address
3250
3558
  * @returns The callback type when found, otherwise undefined
3251
3559
  */
3252
- function getCallbackType(chain, address) {
3253
- return configs[chain].callbacks?.find((c) => c.type !== CallbackType.BuyWithEmptyCallback && c.addresses.includes(address?.toLowerCase()))?.type;
3560
+ function getCallbackType(chain, address$1) {
3561
+ return configs[chain].callbacks?.find((c) => c.type !== CallbackType.BuyWithEmptyCallback && c.addresses.includes(address$1?.toLowerCase()))?.type;
3254
3562
  }
3255
3563
  /**
3256
3564
  * Returns the callback addresses for a given chain and callback type, if it exists.
@@ -3383,6 +3691,7 @@ var Rules_exports = /* @__PURE__ */ __export({
3383
3691
  callback: () => callback,
3384
3692
  chains: () => chains,
3385
3693
  maturity: () => maturity,
3694
+ sameMaker: () => sameMaker,
3386
3695
  token: () => token,
3387
3696
  validity: () => validity
3388
3697
  });
@@ -3519,10 +3828,29 @@ const token = ({ assets: assets$1 }) => single("token", "Validates that offer lo
3519
3828
  if (!allowedAssets.includes(offer.loanToken.toLowerCase())) return { message: "Loan token is not allowed" };
3520
3829
  if (offer.collaterals.some((collateral) => !allowedAssets.includes(collateral.asset.toLowerCase()))) return { message: "Collateral is not allowed" };
3521
3830
  });
3831
+ /**
3832
+ * A batch validation rule that ensures all offers in a tree have the same maker (offering address).
3833
+ * Returns an issue only for the first non-conforming offer.
3834
+ * This rule is signing-agnostic; signer verification is handled at the collector level.
3835
+ */
3836
+ const sameMaker = () => batch$1("mixed_maker", "Validates that all offers in a batch have the same maker (offering address)", (offers) => {
3837
+ const issues = /* @__PURE__ */ new Map();
3838
+ if (offers.length === 0) return issues;
3839
+ const firstMaker = offers[0].offering.toLowerCase();
3840
+ for (let i = 1; i < offers.length; i++) {
3841
+ const offer = offers[i];
3842
+ if (offer.offering.toLowerCase() !== firstMaker) {
3843
+ issues.set(i, { message: `Offer has different maker ${offer.offering} than first offer ${offers[0].offering}` });
3844
+ return issues;
3845
+ }
3846
+ }
3847
+ return issues;
3848
+ });
3522
3849
 
3523
3850
  //#endregion
3524
3851
  //#region src/gatekeeper/morphoRules.ts
3525
3852
  const morphoRules = (chains$2) => [
3853
+ sameMaker(),
3526
3854
  chains({ chains: chains$2 }),
3527
3855
  maturity({ maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth] }),
3528
3856
  callback({
@@ -3563,22 +3891,24 @@ async function add(config, offers) {
3563
3891
  const tree = from$6(offers.map((o) => from$5(o)));
3564
3892
  const chainId = await getChainId(config.client);
3565
3893
  for (const offer of tree.offers) if (chainId !== offer.chainId) throw new ChainIdMismatchError(offer.chainId, chainId);
3894
+ const signature = await sign(tree.offers, config.client);
3895
+ const encoded = await encode$1(tree, signature);
3566
3896
  try {
3567
3897
  return await config.client.sendTransaction({
3568
3898
  chain: config.client.chain,
3569
3899
  account: config.client.account,
3570
3900
  to: config.mempoolAddress,
3571
- data: encode$1(tree)
3901
+ data: encoded
3572
3902
  });
3573
3903
  } catch (error) {
3574
3904
  throw new ViemClientError(error instanceof Error ? error.message : "Unknown error");
3575
3905
  }
3576
3906
  }
3577
3907
  async function* get(config, parameters) {
3578
- const { loanToken, blockNumberGte, blockNumberLte, order: order$1 = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE } = {} } = parameters || {};
3908
+ const { loanToken, blockNumberGte, blockNumberLte, order = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE } = {} } = parameters || {};
3579
3909
  yield* streamOffers(config, {
3580
3910
  loanToken,
3581
- order: order$1,
3911
+ order,
3582
3912
  blockNumberGte,
3583
3913
  blockNumberLte,
3584
3914
  options: {
@@ -3600,7 +3930,7 @@ const getChainId = async (client) => {
3600
3930
  return chainId;
3601
3931
  };
3602
3932
  async function* streamOffers(config, parameters) {
3603
- const { loanToken, blockNumberGte, blockNumberLte, order: order$1 = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE, blockWindow = config.blockWindow } = {} } = parameters;
3933
+ const { loanToken, blockNumberGte, blockNumberLte, order = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE, blockWindow = config.blockWindow } = {} } = parameters;
3604
3934
  const stream = streamLogs({
3605
3935
  client: config.client.extend(viem.publicActions),
3606
3936
  contractAddress: config.mempoolAddress,
@@ -3617,13 +3947,13 @@ async function* streamOffers(config, parameters) {
3617
3947
  },
3618
3948
  blockNumberGte,
3619
3949
  blockNumberLte,
3620
- order: order$1,
3950
+ order,
3621
3951
  options: {
3622
3952
  maxBatchSize,
3623
3953
  blockWindow
3624
3954
  }
3625
3955
  });
3626
- let blockNumber = order$1 === "asc" ? blockNumberGte : blockNumberLte;
3956
+ let blockNumber = order === "asc" ? blockNumberGte : blockNumberLte;
3627
3957
  for await (const { logs, blockNumber: newBlockNumber } of stream) {
3628
3958
  blockNumber = newBlockNumber;
3629
3959
  if (logs.length === 0) continue;
@@ -3632,7 +3962,7 @@ async function* streamOffers(config, parameters) {
3632
3962
  if (!log) continue;
3633
3963
  const [payload] = (0, viem.decodeAbiParameters)([{ type: "bytes" }], log.data);
3634
3964
  try {
3635
- const tree = decode$1(payload);
3965
+ const { tree } = await decode$1(payload);
3636
3966
  for (const offer of tree.offers) {
3637
3967
  if (loanToken && offer.loanToken.toLowerCase() !== loanToken.toLowerCase()) continue;
3638
3968
  offers.push({
@@ -3801,6 +4131,7 @@ function max() {
3801
4131
  //#region src/utils/index.ts
3802
4132
  var utils_exports = /* @__PURE__ */ __export({
3803
4133
  BaseError: () => BaseError,
4134
+ Random: () => Random_exports,
3804
4135
  ReorgError: () => ReorgError,
3805
4136
  Time: () => time_exports,
3806
4137
  batch: () => batch,