@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.
@@ -1,6 +1,6 @@
1
1
  import { t as __export } from "./chunk-jass6xSI.mjs";
2
2
  import { z } from "zod/v4";
3
- import { bytesToHex, decodeAbiParameters, encodeAbiParameters, getAddress, hashTypedData, hexToBytes, isAddress, isHex, keccak256, maxUint256, parseAbi, publicActions, zeroAddress } from "viem";
3
+ import { bytesToHex, decodeAbiParameters, encodeAbiParameters, getAddress, hashMessage, hashTypedData, hexToBytes, isAddress, isHex, keccak256, maxUint256, parseAbi, publicActions, recoverAddress, zeroAddress } from "viem";
4
4
  import "reflect-metadata";
5
5
  import { generateDocument } from "openapi-metadata";
6
6
  import { ApiBody, ApiOperation, ApiProperty, ApiQuery, ApiResponse, ApiTags } from "openapi-metadata/decorators";
@@ -9,7 +9,6 @@ import { Base64 } from "js-base64";
9
9
  import createOpenApiFetchClient from "openapi-fetch";
10
10
  import { getBlock, getLogs, multicall } from "viem/actions";
11
11
  import { anvil, base, mainnet } from "viem/chains";
12
- import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
13
12
  import { StandardMerkleTree } from "@openzeppelin/merkle-tree";
14
13
  import { gzip, ungzip } from "pako";
15
14
 
@@ -117,13 +116,16 @@ var OfferResponse_exports = /* @__PURE__ */ __export({ from: () => from$11 });
117
116
  * Creates an `OfferResponse` from an `Offer`.
118
117
  * @constructor
119
118
  * @param offer - {@link Offer}
119
+ * @param attestation - {@link Attestation}
120
120
  * @returns The created `OfferResponse`. {@link OfferResponse}
121
121
  */
122
- function from$11(offer) {
123
- const result = toSnakeCase$1(offer);
122
+ function from$11(offer, attestation) {
123
+ const { signature: _, ...rest } = toSnakeCase$1(offer);
124
124
  return {
125
- ...result,
126
- signature: result.signature ?? null
125
+ ...rest,
126
+ root: attestation?.root.toLowerCase() ?? null,
127
+ proof: attestation?.proof.map((p) => p.toLowerCase()) ?? null,
128
+ signature: attestation?.signature.toLowerCase() ?? null
127
129
  };
128
130
  }
129
131
 
@@ -172,10 +174,12 @@ const offerExample = {
172
174
  data: "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000034cf890db685fc536e05652fb41f02090c3fb751000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000108e644e3ab01184155270aa92a00000000000",
173
175
  gas_limit: "500000"
174
176
  },
175
- signature: "0x1234567890123456789012345678901234567890123456789012345678901234123456789012345678901234567890123456789012345678901234567890123400",
176
177
  consumed: "0",
177
178
  takeable: "369216000000000000000000",
178
- block_number: 0xa7495128adfb1
179
+ block_number: 0xa7495128adfb1,
180
+ root: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
181
+ proof: ["0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba"],
182
+ signature: "0x1234567890123456789012345678901234567890123456789012345678901234123456789012345678901234567890123456789012345678901234567890123400"
179
183
  };
180
184
  const collectorsHealthExample = {
181
185
  name: "offers",
@@ -320,6 +324,16 @@ __decorate([ApiProperty({
320
324
  type: "number",
321
325
  example: offerExample.block_number
322
326
  })], OfferListItemResponse.prototype, "block_number", void 0);
327
+ __decorate([ApiProperty({
328
+ type: "string",
329
+ nullable: true,
330
+ example: offerExample.root
331
+ })], OfferListItemResponse.prototype, "root", void 0);
332
+ __decorate([ApiProperty({
333
+ type: [String],
334
+ nullable: true,
335
+ example: offerExample.proof
336
+ })], OfferListItemResponse.prototype, "proof", void 0);
323
337
  __decorate([ApiProperty({
324
338
  type: "string",
325
339
  nullable: true,
@@ -503,44 +517,61 @@ __decorate([ApiProperty({
503
517
  var ValidateOffersRequest = class {};
504
518
  __decorate([ApiProperty({
505
519
  type: () => [ValidateOfferRequest],
506
- description: "Array of offers in snake_case format. Mutually exclusive with 'calldata'.",
507
- required: false
520
+ description: "Array of offers in snake_case format. Required, non-empty.",
521
+ required: true
508
522
  })], ValidateOffersRequest.prototype, "offers", void 0);
523
+ var ValidationSuccessDataResponse = class {};
509
524
  __decorate([ApiProperty({
510
525
  type: "string",
511
- description: "Encoded tree calldata as a hex string (0x-prefixed). Mutually exclusive with 'offers'.",
512
- example: "0x01...",
513
- required: false
514
- })], ValidateOffersRequest.prototype, "calldata", void 0);
515
- var ValidateOfferResultResponse = class {};
526
+ description: "Unsigned payload: version (1B) + gzip(offers) + root (32B).",
527
+ example: "0x01789c..."
528
+ })], ValidationSuccessDataResponse.prototype, "payload", void 0);
516
529
  __decorate([ApiProperty({
517
530
  type: "string",
518
- example: offerExample.hash
519
- })], ValidateOfferResultResponse.prototype, "offer_hash", void 0);
531
+ description: "Merkle tree root to sign with EIP-191.",
532
+ example: "0xac4bd8318ec914f89f8af913f162230575b0ac0696a19256bc12138c5cfe1427"
533
+ })], ValidationSuccessDataResponse.prototype, "root", void 0);
534
+ var ValidationSuccessResponse = class extends SuccessResponse {};
520
535
  __decorate([ApiProperty({
521
- type: "boolean",
522
- example: false
523
- })], ValidateOfferResultResponse.prototype, "valid", void 0);
536
+ type: "string",
537
+ nullable: true,
538
+ example: null
539
+ })], ValidationSuccessResponse.prototype, "cursor", void 0);
540
+ __decorate([ApiProperty({
541
+ type: () => ValidationSuccessDataResponse,
542
+ description: "Payload and root for client-side signing."
543
+ })], ValidationSuccessResponse.prototype, "data", void 0);
544
+ var ValidationIssueResponse = class {};
545
+ __decorate([ApiProperty({
546
+ type: "number",
547
+ description: "0-indexed position of the failed offer in the request array.",
548
+ example: 0
549
+ })], ValidationIssueResponse.prototype, "index", void 0);
524
550
  __decorate([ApiProperty({
525
551
  type: "string",
526
- example: "parse_error",
527
- nullable: true
528
- })], ValidateOfferResultResponse.prototype, "rule", void 0);
552
+ description: "Gatekeeper rule name that rejected the offer.",
553
+ example: "no_buy"
554
+ })], ValidationIssueResponse.prototype, "rule", void 0);
529
555
  __decorate([ApiProperty({
530
556
  type: "string",
531
- example: "Invalid offer. 'offering': invalid address",
532
- nullable: true
533
- })], ValidateOfferResultResponse.prototype, "message", void 0);
534
- var ValidateOffersListResponse = class extends SuccessResponse {};
557
+ description: "Human-readable rejection reason.",
558
+ example: "Buy offers are not supported"
559
+ })], ValidationIssueResponse.prototype, "message", void 0);
560
+ var ValidationFailureDataResponse = class {};
561
+ __decorate([ApiProperty({
562
+ type: () => [ValidationIssueResponse],
563
+ description: "List of validation issues. Returned when any offer fails validation."
564
+ })], ValidationFailureDataResponse.prototype, "issues", void 0);
565
+ var ValidationFailureResponse = class extends SuccessResponse {};
535
566
  __decorate([ApiProperty({
536
567
  type: "string",
537
568
  nullable: true,
538
569
  example: null
539
- })], ValidateOffersListResponse.prototype, "cursor", void 0);
570
+ })], ValidationFailureResponse.prototype, "cursor", void 0);
540
571
  __decorate([ApiProperty({
541
- type: () => [ValidateOfferResultResponse],
542
- description: "Validation results for each offer."
543
- })], ValidateOffersListResponse.prototype, "data", void 0);
572
+ type: () => ValidationFailureDataResponse,
573
+ description: "List of validation issues. Returned when any offer fails validation."
574
+ })], ValidationFailureResponse.prototype, "data", void 0);
544
575
  var BookLevelResponse = class {};
545
576
  __decorate([ApiProperty({
546
577
  type: "string",
@@ -605,13 +636,18 @@ __decorate([
605
636
  methods: ["post"],
606
637
  path: "/v1/validate",
607
638
  summary: "Validate offers",
608
- description: "Validates offers against router validation rules. Returns validation status for each offer. Accepts either an array of offers or encoded calldata (mutually exclusive)."
639
+ description: "Validates offers against router validation rules. Returns unsigned payload + root on success, or issues only on validation failure."
609
640
  }),
610
641
  ApiBody({ type: ValidateOffersRequest }),
611
642
  ApiResponse({
612
643
  status: 200,
613
644
  description: "Success",
614
- type: ValidateOffersListResponse
645
+ type: ValidationSuccessResponse
646
+ }),
647
+ ApiResponse({
648
+ status: 200,
649
+ description: "Validation issues",
650
+ type: ValidationFailureResponse
615
651
  })
616
652
  ], ValidateController.prototype, "validateOffers", null);
617
653
  ValidateController = __decorate([ApiTags("Validate"), ApiResponse({
@@ -780,7 +816,7 @@ const OpenApi = async (options = {}) => {
780
816
  if (options.rules && options.rules.length > 0) {
781
817
  const rulesDescription = options.rules.map((rule) => `- **${rule.name}**: ${rule.description}`).join("\n");
782
818
  const validatePath = document.paths?.["/v1/validate"];
783
- 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}`;
819
+ 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}`;
784
820
  }
785
821
  return document;
786
822
  };
@@ -792,17 +828,23 @@ var Cursor_exports = /* @__PURE__ */ __export({
792
828
  encode: () => encode$3,
793
829
  validate: () => validate
794
830
  });
795
- function validate(cursor) {
796
- if (!cursor || typeof cursor !== "object") throw new Error("Cursor must be an object");
797
- const c = cursor;
798
- if (![
831
+ const isSort = (value) => {
832
+ return [
799
833
  "rate",
800
834
  "maturity",
801
835
  "expiry",
802
836
  "amount"
803
- ].includes(c.sort)) throw new Error(`Invalid sort field: ${c.sort}. Must be one of: rate, maturity, expiry, amount`);
804
- if (!["asc", "desc"].includes(c.dir)) throw new Error(`Invalid direction: ${c.dir}. Must be one of: asc, desc`);
805
- 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`);
837
+ ].includes(value);
838
+ };
839
+ function validate(cursor) {
840
+ if (!cursor || typeof cursor !== "object") throw new Error("Cursor must be an object");
841
+ const c = cursor;
842
+ const sort = c.sort;
843
+ const dir = c.dir;
844
+ const hash$1 = c.hash;
845
+ if (typeof sort !== "string" || !isSort(sort)) throw new Error(`Invalid sort field: ${String(sort)}. Must be one of: rate, maturity, expiry, amount`);
846
+ if (typeof dir !== "string" || !["asc", "desc"].includes(dir)) throw new Error(`Invalid direction: ${String(dir)}. Must be one of: asc, desc`);
847
+ 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`);
806
848
  const validation = {
807
849
  rate: {
808
850
  field: "rate",
@@ -819,24 +861,30 @@ function validate(cursor) {
819
861
  maturity: {
820
862
  field: "maturity",
821
863
  type: "number",
822
- validator: (val) => val > 0,
864
+ min: 1,
823
865
  error: "positive number"
824
866
  },
825
867
  expiry: {
826
868
  field: "expiry",
827
869
  type: "number",
828
- validator: (val) => val > 0,
870
+ min: 1,
829
871
  error: "positive number"
830
872
  }
831
- }[c.sort];
832
- if (!validation) throw new Error(`Invalid sort field: ${c.sort}`);
873
+ }[sort];
874
+ if (!validation) throw new Error(`Invalid sort field: ${sort}`);
833
875
  const fieldValue = c[validation.field];
834
- if (!fieldValue) throw new Error(`${c.sort} sort requires '${validation.field}' field to be present`);
835
- if (typeof fieldValue !== validation.type) throw new Error(`${c.sort} sort requires '${validation.field}' field of type ${validation.type}`);
836
- if (validation.pattern && !validation.pattern.test(fieldValue)) throw new Error(`Invalid ${validation.field} format: ${fieldValue}. Must be a ${validation.error}`);
837
- if (validation.validator && !validation.validator(fieldValue)) throw new Error(`Invalid ${validation.field} value: ${fieldValue}. Must be a ${validation.error}`);
838
- if (c.page !== void 0) {
839
- if (typeof c.page !== "number" || !Number.isInteger(c.page) || c.page < 1) throw new Error("Invalid page: must be a positive integer");
876
+ if (fieldValue === void 0 || fieldValue === null) throw new Error(`${sort} sort requires '${validation.field}' field to be present`);
877
+ if (validation.type === "string") {
878
+ if (typeof fieldValue !== "string") throw new Error(`${sort} sort requires '${validation.field}' field of type ${validation.type}`);
879
+ if (!validation.pattern.test(fieldValue)) throw new Error(`Invalid ${validation.field} format: ${fieldValue}. Must be a ${validation.error}`);
880
+ }
881
+ if (validation.type === "number") {
882
+ if (typeof fieldValue !== "number") throw new Error(`${sort} sort requires '${validation.field}' field of type ${validation.type}`);
883
+ if (fieldValue < validation.min) throw new Error(`Invalid ${validation.field} value: ${fieldValue}. Must be a ${validation.error}`);
884
+ }
885
+ const page = c.page;
886
+ if (page !== void 0) {
887
+ if (typeof page !== "number" || !Number.isInteger(page) || page < 1) throw new Error("Invalid page: must be a positive integer");
840
888
  }
841
889
  return true;
842
890
  }
@@ -952,21 +1000,7 @@ const schemas = {
952
1000
  get_obligations: GetObligationsQueryParams,
953
1001
  get_obligation: GetObligationParams,
954
1002
  get_book: GetBookParams,
955
- validate_offers: z$1.object({
956
- offers: z$1.any().refine((val) => val === void 0 || Array.isArray(val), { message: "'offers' must be an array" }),
957
- calldata: z$1.string().regex(/^0x[a-fA-F0-9]*$/, { message: "'calldata' must be a hex string starting with '0x'" }).optional()
958
- }).superRefine((val, ctx) => {
959
- const hasOffers = val.offers !== void 0;
960
- const hasCalldata = val.calldata !== void 0;
961
- if (hasOffers && hasCalldata) ctx.addIssue({
962
- code: "custom",
963
- message: "Request body must contain either 'offers' or 'calldata', not both"
964
- });
965
- if (!hasOffers && !hasCalldata) ctx.addIssue({
966
- code: "custom",
967
- message: "Request body must contain either 'offers' array or 'calldata' hex string"
968
- });
969
- })
1003
+ validate_offers: z$1.object({ offers: z$1.array(z$1.unknown()).min(1, { message: "'offers' must contain at least 1 offer" }) }).strict()
970
1004
  };
971
1005
  function parse(action, query) {
972
1006
  return schemas[action].parse(query);
@@ -1193,8 +1227,12 @@ function decode$2(type, data) {
1193
1227
  }
1194
1228
  function encode$2(type, data) {
1195
1229
  switch (type) {
1196
- case CallbackType.BuyVaultV1Callback: return encodeBuyVaultV1Callback(data);
1197
- case CallbackType.SellERC20Callback: return encodeSellERC20Callback(data);
1230
+ case CallbackType.BuyVaultV1Callback:
1231
+ if (!("vaults" in data)) throw new Error("Invalid callback data");
1232
+ return encodeBuyVaultV1Callback(data);
1233
+ case CallbackType.SellERC20Callback:
1234
+ if (!("collaterals" in data)) throw new Error("Invalid callback data");
1235
+ return encodeSellERC20Callback(data);
1198
1236
  default: throw new Error("Invalid callback type");
1199
1237
  }
1200
1238
  }
@@ -1522,22 +1560,22 @@ const DEFAULT_BATCH_SIZE$1 = 2500;
1522
1560
  const MAX_BLOCK_WINDOW = 1e4;
1523
1561
  const DEFAULT_BLOCK_WINDOW = 8e3;
1524
1562
  async function* streamLogs(parameters) {
1525
- const { client, contractAddress, event, blockNumberGte, blockNumberLte, order: order$1 = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE$1, blockWindow = DEFAULT_BLOCK_WINDOW } = {} } = parameters;
1563
+ const { client, contractAddress, event, blockNumberGte, blockNumberLte, order = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE$1, blockWindow = DEFAULT_BLOCK_WINDOW } = {} } = parameters;
1526
1564
  if (maxBatchSize > MAX_BATCH_SIZE) throw new InvalidBatchSizeError(maxBatchSize);
1527
1565
  if (blockWindow > MAX_BLOCK_WINDOW) throw new InvalidBlockWindowError(blockWindow);
1528
- if (order$1 === "asc" && blockNumberGte === void 0) throw new MissingBlockNumberError();
1566
+ if (order === "asc" && blockNumberGte === void 0) throw new MissingBlockNumberError();
1529
1567
  const latestBlock = (await getBlock(client, {
1530
1568
  blockTag: "latest",
1531
1569
  includeTransactions: false
1532
1570
  })).number;
1533
1571
  let toBlock = 0n;
1534
- if (order$1 === "asc") toBlock = min(BigInt(blockNumberGte) + BigInt(blockWindow), blockNumberLte ? BigInt(blockNumberLte) : latestBlock);
1535
- if (order$1 === "desc") toBlock = blockNumberLte === void 0 ? latestBlock : min(BigInt(blockNumberLte), latestBlock);
1572
+ if (order === "asc") toBlock = min(BigInt(blockNumberGte) + BigInt(blockWindow), blockNumberLte ? BigInt(blockNumberLte) : latestBlock);
1573
+ if (order === "desc") toBlock = blockNumberLte === void 0 ? latestBlock : min(BigInt(blockNumberLte), latestBlock);
1536
1574
  let fromBlock = 0n;
1537
- if (order$1 === "asc") fromBlock = min(BigInt(blockNumberGte), latestBlock);
1538
- if (order$1 === "desc") fromBlock = max$1(BigInt(blockNumberGte || toBlock - BigInt(blockWindow)), 0n);
1539
- if (order$1 === "asc") toBlock = min(toBlock, fromBlock + BigInt(blockWindow));
1540
- if (order$1 === "desc") fromBlock = max$1(fromBlock, toBlock - BigInt(blockWindow));
1575
+ if (order === "asc") fromBlock = min(BigInt(blockNumberGte), latestBlock);
1576
+ if (order === "desc") fromBlock = max$1(BigInt(blockNumberGte || toBlock - BigInt(blockWindow)), 0n);
1577
+ if (order === "asc") toBlock = min(toBlock, fromBlock + BigInt(blockWindow));
1578
+ if (order === "desc") fromBlock = max$1(fromBlock, toBlock - BigInt(blockWindow));
1541
1579
  if (fromBlock > toBlock) throw new InvalidBlockRangeError(fromBlock, toBlock);
1542
1580
  let streaming = true;
1543
1581
  while (streaming) {
@@ -1547,29 +1585,29 @@ async function* streamLogs(parameters) {
1547
1585
  fromBlock,
1548
1586
  toBlock
1549
1587
  });
1550
- streaming = order$1 === "asc" ? toBlock < (blockNumberLte || latestBlock) : fromBlock > (blockNumberGte || 0n);
1588
+ streaming = order === "asc" ? toBlock < (blockNumberLte || latestBlock) : fromBlock > (blockNumberGte || 0n);
1551
1589
  if (logs.length === 0 && !streaming) break;
1552
1590
  if (logs.length === 0 && streaming) yield {
1553
1591
  logs: [],
1554
- blockNumber: order$1 === "asc" ? Number(toBlock) : Number(fromBlock)
1592
+ blockNumber: order === "asc" ? Number(toBlock) : Number(fromBlock)
1555
1593
  };
1556
1594
  logs.sort((a, b) => {
1557
- if (a.blockNumber !== b.blockNumber) return order$1 === "asc" ? Number(a.blockNumber - b.blockNumber) : Number(b.blockNumber - a.blockNumber);
1558
- if (a.transactionIndex !== b.transactionIndex) return order$1 === "asc" ? a.transactionIndex - b.transactionIndex : b.transactionIndex - a.transactionIndex;
1559
- return order$1 === "asc" ? a.logIndex - b.logIndex : b.logIndex - a.logIndex;
1595
+ if (a.blockNumber !== b.blockNumber) return order === "asc" ? Number(a.blockNumber - b.blockNumber) : Number(b.blockNumber - a.blockNumber);
1596
+ if (a.transactionIndex !== b.transactionIndex) return order === "asc" ? a.transactionIndex - b.transactionIndex : b.transactionIndex - a.transactionIndex;
1597
+ return order === "asc" ? a.logIndex - b.logIndex : b.logIndex - a.logIndex;
1560
1598
  });
1561
1599
  for (const logBatch of batch(logs, maxBatchSize)) yield {
1562
1600
  logs: logBatch,
1563
- blockNumber: logBatch.length === maxBatchSize ? Number(logBatch[logBatch.length - 1]?.blockNumber) : order$1 === "asc" ? Number(toBlock) : Number(fromBlock)
1601
+ blockNumber: logBatch.length === maxBatchSize ? Number(logBatch[logBatch.length - 1]?.blockNumber) : order === "asc" ? Number(toBlock) : Number(fromBlock)
1564
1602
  };
1565
- if (order$1 === "asc") {
1603
+ if (order === "asc") {
1566
1604
  const upperBound = BigInt(blockNumberLte || latestBlock);
1567
1605
  const nextFromBlock = min(BigInt(toBlock) + 1n, upperBound);
1568
1606
  const nextToBlock = min(toBlock + BigInt(blockWindow) + 1n, upperBound);
1569
1607
  fromBlock = nextFromBlock;
1570
1608
  toBlock = nextToBlock;
1571
1609
  }
1572
- if (order$1 === "desc") {
1610
+ if (order === "desc") {
1573
1611
  const lowerBound = BigInt(blockNumberGte || 0);
1574
1612
  const nextToBlock = max$1(fromBlock - 1n, lowerBound);
1575
1613
  const nextFromBlock = max$1(fromBlock - BigInt(blockWindow) - 1n, lowerBound);
@@ -1579,7 +1617,7 @@ async function* streamLogs(parameters) {
1579
1617
  }
1580
1618
  yield {
1581
1619
  logs: [],
1582
- blockNumber: order$1 === "asc" ? Number(toBlock) : Number(fromBlock)
1620
+ blockNumber: order === "asc" ? Number(toBlock) : Number(fromBlock)
1583
1621
  };
1584
1622
  }
1585
1623
  var InvalidBlockRangeError = class extends BaseError {
@@ -1607,6 +1645,96 @@ var MissingBlockNumberError = class extends BaseError {
1607
1645
  }
1608
1646
  };
1609
1647
 
1648
+ //#endregion
1649
+ //#region src/utils/Random.ts
1650
+ var Random_exports = /* @__PURE__ */ __export({
1651
+ address: () => address,
1652
+ bool: () => bool,
1653
+ bytes: () => bytes,
1654
+ float: () => float,
1655
+ hex: () => hex,
1656
+ int: () => int,
1657
+ seed: () => seed,
1658
+ withSeed: () => withSeed
1659
+ });
1660
+ let currentRng = Math.random;
1661
+ const FNV_OFFSET_BASIS = 2166136261;
1662
+ const FNV_PRIME = 16777619;
1663
+ const hashSeed = (seed$1) => {
1664
+ let hash$1 = FNV_OFFSET_BASIS;
1665
+ for (let i = 0; i < seed$1.length; i += 1) {
1666
+ hash$1 ^= seed$1.charCodeAt(i);
1667
+ hash$1 = Math.imul(hash$1, FNV_PRIME);
1668
+ }
1669
+ return hash$1 >>> 0;
1670
+ };
1671
+ const createSeededRng = (seed$1) => {
1672
+ let state = hashSeed(seed$1);
1673
+ return () => {
1674
+ state += 1831565813;
1675
+ let t = Math.imul(state ^ state >>> 15, state | 1);
1676
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
1677
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
1678
+ };
1679
+ };
1680
+ /**
1681
+ * Runs a function with a deterministic RNG derived from the given seed.
1682
+ */
1683
+ function withSeed(seed$1, fn) {
1684
+ const previous = currentRng;
1685
+ currentRng = createSeededRng(seed$1);
1686
+ try {
1687
+ return fn();
1688
+ } finally {
1689
+ currentRng = previous;
1690
+ }
1691
+ }
1692
+ /**
1693
+ * Seeds the global RNG for deterministic test runs.
1694
+ */
1695
+ function seed(seed$1) {
1696
+ currentRng = createSeededRng(seed$1);
1697
+ }
1698
+ /**
1699
+ * Returns a deterministic random float in [0, 1).
1700
+ */
1701
+ function float() {
1702
+ return currentRng();
1703
+ }
1704
+ /**
1705
+ * Returns a deterministic random integer in [min, maxExclusive).
1706
+ */
1707
+ function int(maxExclusive, min$1 = 0) {
1708
+ return Math.floor(float() * (maxExclusive - min$1)) + min$1;
1709
+ }
1710
+ /**
1711
+ * Returns a deterministic random boolean.
1712
+ */
1713
+ function bool(probability = .5) {
1714
+ return float() < probability;
1715
+ }
1716
+ /**
1717
+ * Returns deterministic random bytes.
1718
+ */
1719
+ function bytes(length) {
1720
+ const output = new Uint8Array(length);
1721
+ for (let i = 0; i < length; i += 1) output[i] = int(256);
1722
+ return output;
1723
+ }
1724
+ /**
1725
+ * Returns a deterministic random hex string for the given byte length.
1726
+ */
1727
+ function hex(byteLength) {
1728
+ const output = bytes(byteLength);
1729
+ return `0x${Array.from(output, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
1730
+ }
1731
+ /**
1732
+ * Returns a deterministic random address.
1733
+ */
1734
+ function address() {
1735
+ return hex(20);
1736
+ }
1737
+
1610
1738
  //#endregion
1611
1739
  //#region src/utils/zod.ts
1612
1740
  const transformHex = (val, ctx) => {
@@ -1728,8 +1856,8 @@ const from$9 = (parameters) => {
1728
1856
  */
1729
1857
  function random$3() {
1730
1858
  return from$9({
1731
- asset: privateKeyToAccount(generatePrivateKey()).address,
1732
- oracle: privateKeyToAccount(generatePrivateKey()).address,
1859
+ asset: address(),
1860
+ oracle: address(),
1733
1861
  lltv: .965
1734
1862
  });
1735
1863
  }
@@ -2144,12 +2272,8 @@ function id(obligation) {
2144
2272
  function random$2() {
2145
2273
  return from$7({
2146
2274
  chainId: 1,
2147
- loanToken: privateKeyToAccount(generatePrivateKey()).address,
2148
- collaterals: [from$9({
2149
- asset: privateKeyToAccount(generatePrivateKey()).address,
2150
- oracle: privateKeyToAccount(generatePrivateKey()).address,
2151
- lltv: .965
2152
- })],
2275
+ loanToken: address(),
2276
+ collaterals: [random$3()],
2153
2277
  maturity: from$8("end_of_next_quarter")
2154
2278
  });
2155
2279
  }
@@ -2169,101 +2293,249 @@ var CollateralsAreNotSortedError = class extends BaseError {
2169
2293
  //#endregion
2170
2294
  //#region src/core/Tree.ts
2171
2295
  var Tree_exports = /* @__PURE__ */ __export({
2296
+ DecodeError: () => DecodeError,
2297
+ EncodeError: () => EncodeError,
2298
+ TreeError: () => TreeError,
2172
2299
  VERSION: () => VERSION,
2173
2300
  decode: () => decode$1,
2174
2301
  encode: () => encode$1,
2175
- from: () => from$6
2302
+ encodeUnsigned: () => encodeUnsigned,
2303
+ from: () => from$6,
2304
+ proofs: () => proofs
2176
2305
  });
2177
2306
  const VERSION = 1;
2307
+ const normalizeHash = (hash$1) => hash$1.toLowerCase();
2178
2308
  /**
2179
2309
  * Builds a Merkle tree from a list of offers.
2180
2310
  *
2181
2311
  * Leaves are the offer `hash` values as `bytes32` and are deterministically
2182
- * ordered in ascending lexicographic order so that the resulting root is stable
2183
- * regardless of the input order.
2312
+ * ordered following the StandardMerkleTree leaf ordering so that the resulting
2313
+ * root is stable regardless of the input order.
2184
2314
  *
2185
2315
  * @param offers - Offers to include in the tree.
2186
2316
  * @returns A `StandardMerkleTree` of `bytes32` leaves representing the offers.
2317
+ * @throws {TreeError} If tree building fails due to offer inconsistencies.
2187
2318
  */
2188
2319
  const from$6 = (offers) => {
2189
- const leaves = order(offers).map((offer) => {
2190
- return [offer.hash];
2191
- });
2320
+ const leaves = offers.map((offer) => [offer.hash]);
2192
2321
  const tree = StandardMerkleTree.of(leaves, ["bytes32"]);
2193
- return Object.assign(tree, { offers });
2322
+ const orderedOffers = orderOffers(tree, offers);
2323
+ return Object.assign(tree, { offers: orderedOffers });
2194
2324
  };
2195
- const byHashAsc = (a, b) => a.localeCompare(b);
2196
- const order = (offers) => {
2197
- return offers.sort((a, b) => byHashAsc(a.hash, b.hash));
2325
+ const orderOffers = (tree, offers) => {
2326
+ const offerByHash = /* @__PURE__ */ new Map();
2327
+ for (const offer of offers) offerByHash.set(normalizeHash(offer.hash), offer);
2328
+ const entries = tree.dump().values.map((value) => {
2329
+ const hash$1 = normalizeHash(value.value[0]);
2330
+ const offer = offerByHash.get(hash$1);
2331
+ if (!offer) throw new TreeError(`missing offer for leaf ${hash$1}`);
2332
+ return {
2333
+ offer,
2334
+ treeIndex: value.treeIndex
2335
+ };
2336
+ });
2337
+ entries.sort((a, b) => b.treeIndex - a.treeIndex);
2338
+ return entries.map((item) => item.offer);
2198
2339
  };
2199
2340
  /**
2200
- * Encodes an `Tree` into a Hex string with a version byte prefix and gzipped payload.
2341
+ * Generates merkle proofs for all offers in a tree.
2201
2342
  *
2202
- * - Layout: `0x{vv}{zip...}` where `{vv}` is one-byte version as two hex chars.
2203
- * - Payload is gzip(JSON.stringify([root, ...offers])) with bigint stringified.
2343
+ * Each proof allows independent verification that an offer is included in the tree
2344
+ * without requiring the full tree. Proofs are ordered by StandardMerkleTree leaf ordering.
2204
2345
  *
2205
- * @param tree - The offer Merkle tree to encode.
2206
- * @returns Hex string starting with `0x{vv}` followed by gzipped payload bytes.
2207
- * @throws Error if the given `root` does not match the offers.
2208
- */
2209
- const encode$1 = (tree) => {
2210
- assertRoot(tree.root, tree.offers);
2211
- const offersPayload = tree.offers.map((offer) => ({
2212
- offering: offer.offering,
2213
- assets: offer.assets.toString(),
2214
- rate: offer.rate.toString(),
2215
- maturity: Number(offer.maturity),
2216
- expiry: Number(offer.expiry),
2217
- start: Number(offer.start),
2218
- nonce: offer.nonce.toString(),
2219
- buy: offer.buy,
2220
- chainId: offer.chainId,
2221
- loanToken: offer.loanToken,
2222
- collaterals: offer.collaterals.map((c) => ({
2223
- asset: c.asset,
2224
- oracle: c.oracle,
2225
- lltv: c.lltv.toString()
2226
- })),
2227
- callback: {
2228
- address: offer.callback.address,
2229
- data: offer.callback.data,
2230
- gasLimit: offer.callback.gasLimit.toString()
2231
- },
2232
- signature: offer.signature,
2233
- hash: offer.hash
2234
- }));
2235
- const compressed = gzip(JSON.stringify([tree.root, ...offersPayload]));
2236
- const encoded = new Uint8Array(1 + compressed.length);
2237
- if (VERSION > 255) throw new Error(`Version overflow: ${VERSION}`);
2238
- encoded[0] = VERSION;
2239
- encoded.set(compressed, 1);
2346
+ * @param tree - The {@link Tree} to generate proofs for.
2347
+ * @returns Array of proofs - {@link Proof}
2348
+ */
2349
+ const proofs = (tree) => {
2350
+ return tree.offers.map((offer) => {
2351
+ return {
2352
+ offer,
2353
+ path: tree.getProof([offer.hash])
2354
+ };
2355
+ });
2356
+ };
2357
+ const assertHex = (value, expectedBytes, name) => {
2358
+ if (typeof value !== "string" || !isHex(value)) throw new DecodeError(`${name} is not a valid hex string`);
2359
+ if (hexToBytes(value).length !== expectedBytes) throw new DecodeError(`${name}: expected ${expectedBytes} bytes`);
2360
+ };
2361
+ const verifySignatureAndRecoverAddress = async (params) => {
2362
+ const { root, signature } = params;
2363
+ assertHex(signature, 65, "signature");
2364
+ const hash$1 = hashMessage({ raw: root });
2365
+ try {
2366
+ return await recoverAddress({
2367
+ hash: hash$1,
2368
+ signature
2369
+ });
2370
+ } catch {
2371
+ throw new DecodeError("signature recovery failed");
2372
+ }
2373
+ };
2374
+ /**
2375
+ * Encodes a merkle tree with signature into hex calldata for onchain broadcast.
2376
+ *
2377
+ * Layout: `0x{vv}{gzip([...offers])}{root}{signature}` where:
2378
+ * - `{vv}`: 1-byte version (currently 0x01)
2379
+ * - `{gzip([...offers])}`: gzipped JSON array of serialized offers
2380
+ * - `{root}`: 32-byte merkle root
2381
+ * - `{signature}`: 65-byte EIP-191 signature over raw root bytes
2382
+ *
2383
+ * Validates signature authenticity and root integrity before encoding.
2384
+ *
2385
+ * @example
2386
+ * ```typescript
2387
+ * const tree = Tree.from(offers);
2388
+ * const signature = await wallet.signMessage({ message: { raw: tree.root } });
2389
+ * const calldata = await Tree.encode(tree, signature);
2390
+ * await broadcast(calldata);
2391
+ * ```
2392
+ *
2393
+ * @example
2394
+ * Manual construction (for advanced users):
2395
+ * ```typescript
2396
+ * const tree = Tree.from(offers);
2397
+ * const compressed = gzip(JSON.stringify(tree.offers.map(Offer.serialize)));
2398
+ * const partial = `0x01${bytesToHex(compressed)}${tree.root.slice(2)}`;
2399
+ * const signature = await wallet.signMessage({ message: { raw: tree.root } });
2400
+ * const calldata = `${partial}${signature.slice(2)}`;
2401
+ * ```
2402
+ *
2403
+ * @param tree - Merkle tree of offers
2404
+ * @param signature - EIP-191 signature over raw root bytes
2405
+ * @returns Hex-encoded calldata ready for onchain broadcast
2406
+ * @throws {EncodeError} If signature verification fails or root mismatch
2407
+ */
2408
+ const encode$1 = async (tree, signature) => {
2409
+ validateTreeForEncoding(tree);
2410
+ await verifySignatureAndRecoverAddress({
2411
+ root: tree.root,
2412
+ signature
2413
+ });
2414
+ const unsigned = encodeUnsignedBytes(tree);
2415
+ const sigBytes = hexToBytes(signature);
2416
+ const encoded = new Uint8Array(unsigned.length + sigBytes.length);
2417
+ encoded.set(unsigned, 0);
2418
+ encoded.set(sigBytes, unsigned.length);
2240
2419
  return bytesToHex(encoded);
2241
2420
  };
2242
- const assertRoot = (root, offers) => {
2243
- const tree = from$6(offers);
2244
- if (root !== tree.root) throw new Error(`Invalid root: expected ${tree.root}, got ${root}`);
2421
+ /**
2422
+ * Encodes a merkle tree without a signature into hex payload for client-side signing.
2423
+ *
2424
+ * Layout: `0x{vv}{gzip([...offers])}{root}` where:
2425
+ * - `{vv}`: 1-byte version (currently 0x01)
2426
+ * - `{gzip([...offers])}`: gzipped JSON array of serialized offers
2427
+ * - `{root}`: 32-byte merkle root
2428
+ *
2429
+ * Validates root integrity before encoding.
2430
+ *
2431
+ * @param tree - Merkle tree of offers
2432
+ * @returns Hex-encoded unsigned payload
2433
+ * @throws {EncodeError} If root mismatch
2434
+ */
2435
+ const encodeUnsigned = (tree) => {
2436
+ validateTreeForEncoding(tree);
2437
+ return bytesToHex(encodeUnsignedBytes(tree));
2438
+ };
2439
+ const validateTreeForEncoding = (tree) => {
2440
+ if (VERSION > 255) throw new EncodeError(`version overflow: ${VERSION} exceeds 255`);
2441
+ const computed = from$6(tree.offers);
2442
+ if (tree.root !== computed.root) throw new EncodeError(`root mismatch: expected ${computed.root}, got ${tree.root}`);
2443
+ };
2444
+ const encodeUnsignedBytes = (tree) => {
2445
+ const offersPayload = tree.offers.map(serialize);
2446
+ const compressed = gzip(JSON.stringify(offersPayload));
2447
+ const rootBytes = hexToBytes(tree.root);
2448
+ const encoded = new Uint8Array(1 + compressed.length + 32);
2449
+ encoded[0] = VERSION;
2450
+ encoded.set(compressed, 1);
2451
+ encoded.set(rootBytes, 1 + compressed.length);
2452
+ return encoded;
2245
2453
  };
2246
2454
  /**
2247
- * Decodes a Hex string produced by {@link encode} back into an `Tree`.
2455
+ * Decodes hex calldata into a validated merkle tree.
2456
+ *
2457
+ * Validates signature before decompression for fail-fast rejection of invalid payloads.
2458
+ * Returns the tree with separately validated signature and recovered signer address.
2459
+ *
2460
+ * Validation order:
2461
+ * 1. Version check
2462
+ * 2. Signature verification (fail-fast, before decompression)
2463
+ * 3. Decompression (only if signature valid)
2464
+ * 4. Root verification (computed from offers vs embedded root)
2248
2465
  *
2249
- * - Ensures the first byte version matches {@link VERSION}.
2250
- * - Decompresses with gunzip, parses JSON, validates offers, and re-checks the root.
2466
+ * @example
2467
+ * ```typescript
2468
+ * const { tree, signature, signer } = await Tree.decode(calldata);
2469
+ * console.log(`Tree signed by ${signer} with ${tree.offers.length} offers`);
2470
+ * ```
2251
2471
  *
2252
- * @param encoded - Hex string in the form `0x{vv}{zip...}`.
2253
- * @returns A validated `Tree` rebuilt from the offers.
2254
- * @throws Error if the version is invalid or the root does not match the offers.
2255
- */
2256
- const decode$1 = (encoded) => {
2257
- const bytes = hexToBytes(encoded);
2258
- if (bytes.length < 2) throw new Error("Invalid payload: too short");
2259
- const version = bytes[0];
2260
- if (version !== (VERSION & 255)) throw new Error(`Invalid version: expected ${VERSION}, got ${version}`);
2261
- const decoded = ungzip(bytes.slice(1), { to: "string" });
2262
- const data = JSON.parse(decoded);
2263
- const root = data[0];
2264
- const tree = from$6(data.slice(1).map((o) => OfferSchema().parse(o)));
2265
- if (root !== tree.root) throw new Error(`Invalid root: expected ${tree.root}, got ${root}`);
2266
- return tree;
2472
+ * @param encoded - Hex calldata in format `0x{vv}{gzip}{root}{signature}`
2473
+ * @returns Validated tree, signature, and recovered signer address
2474
+ * @throws {DecodeError} If version invalid, signature invalid, or root mismatch
2475
+ */
2476
+ const decode$1 = async (encoded) => {
2477
+ const bytes$1 = hexToBytes(encoded);
2478
+ if (bytes$1.length < 98) throw new DecodeError("payload too short");
2479
+ const version = bytes$1[0];
2480
+ if (version !== (VERSION & 255)) throw new DecodeError(`invalid version: expected ${VERSION}, got ${version ?? 0}`);
2481
+ const signature = bytesToHex(bytes$1.slice(-65));
2482
+ const root = bytesToHex(bytes$1.slice(-97, -65));
2483
+ assertHex(root, 32, "root");
2484
+ assertHex(signature, 65, "signature");
2485
+ const signer = await verifySignatureAndRecoverAddress({
2486
+ root,
2487
+ signature
2488
+ });
2489
+ const compressed = bytes$1.slice(1, -97);
2490
+ let decoded;
2491
+ try {
2492
+ decoded = ungzip(compressed, { to: "string" });
2493
+ } catch {
2494
+ throw new DecodeError("decompression failed");
2495
+ }
2496
+ let rawOffers;
2497
+ try {
2498
+ rawOffers = JSON.parse(decoded);
2499
+ } catch {
2500
+ throw new DecodeError("JSON parse failed");
2501
+ }
2502
+ const tree = from$6(rawOffers.map((o) => OfferSchema().parse(o)));
2503
+ if (root !== tree.root) throw new DecodeError(`root mismatch: expected ${tree.root}, got ${root}`);
2504
+ return {
2505
+ tree,
2506
+ signature,
2507
+ signer
2508
+ };
2509
+ };
2510
+ /**
2511
+ * Error thrown during tree building operations.
2512
+ * Indicates structural issues with the tree (missing offers, inconsistent state).
2513
+ */
2514
+ var TreeError = class extends BaseError {
2515
+ constructor(reason) {
2516
+ super(`Tree error: ${reason}`);
2517
+ _defineProperty(this, "name", "Tree.TreeError");
2518
+ }
2519
+ };
2520
+ /**
2521
+ * Error thrown during tree encoding.
2522
+ * Indicates validation failures (signature, root mismatch, mixed makers).
2523
+ */
2524
+ var EncodeError = class extends BaseError {
2525
+ constructor(reason) {
2526
+ super(`Failed to encode tree: ${reason}`);
2527
+ _defineProperty(this, "name", "Tree.EncodeError");
2528
+ }
2529
+ };
2530
+ /**
2531
+ * Error thrown during tree decoding.
2532
+ * Indicates payload corruption, version mismatch, or validation failures.
2533
+ */
2534
+ var DecodeError = class extends BaseError {
2535
+ constructor(reason) {
2536
+ super(`Failed to decode tree: ${reason}`);
2537
+ _defineProperty(this, "name", "Tree.DecodeError");
2538
+ }
2267
2539
  };
2268
2540
 
2269
2541
  //#endregion
@@ -2283,6 +2555,7 @@ var Offer_exports = /* @__PURE__ */ __export({
2283
2555
  hash: () => hash,
2284
2556
  obligationId: () => obligationId,
2285
2557
  random: () => random$1,
2558
+ serialize: () => serialize,
2286
2559
  sign: () => sign,
2287
2560
  signatureMsg: () => signatureMsg,
2288
2561
  toSnakeCase: () => toSnakeCase,
@@ -2363,16 +2636,47 @@ function toSnakeCase(offer) {
2363
2636
  return toSnakeCase$1(offer);
2364
2637
  }
2365
2638
  /**
2639
+ * Serializes an offer for merkle tree encoding.
2640
+ * Converts BigInt fields to strings for JSON compatibility.
2641
+ *
2642
+ * @param offer - Offer to serialize
2643
+ * @returns JSON-serializable offer object
2644
+ */
2645
+ const serialize = (offer) => ({
2646
+ offering: offer.offering,
2647
+ assets: offer.assets.toString(),
2648
+ rate: offer.rate.toString(),
2649
+ maturity: Number(offer.maturity),
2650
+ expiry: Number(offer.expiry),
2651
+ start: Number(offer.start),
2652
+ nonce: offer.nonce.toString(),
2653
+ buy: offer.buy,
2654
+ chainId: offer.chainId,
2655
+ loanToken: offer.loanToken,
2656
+ collaterals: offer.collaterals.map((c) => ({
2657
+ asset: c.asset,
2658
+ oracle: c.oracle,
2659
+ lltv: c.lltv.toString()
2660
+ })),
2661
+ callback: {
2662
+ address: offer.callback.address,
2663
+ data: offer.callback.data,
2664
+ gasLimit: offer.callback.gasLimit.toString()
2665
+ },
2666
+ signature: offer.signature,
2667
+ hash: offer.hash
2668
+ });
2669
+ /**
2366
2670
  * Generates a random Offer.
2367
2671
  * The returned Offer contains randomly generated values.
2368
2672
  * @warning The generated Offer should not be used for production usage.
2369
2673
  * @returns {Offer} A randomly generated Offer object.
2370
2674
  */
2371
2675
  function random$1(config) {
2372
- const chain = config?.chains ? config.chains[Math.floor(Math.random() * config.chains.length)] : chains$1.ethereum;
2373
- const loanToken = config?.loanTokens ? config.loanTokens[Math.floor(Math.random() * config.loanTokens.length)] : privateKeyToAccount(generatePrivateKey()).address;
2374
- const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [privateKeyToAccount(generatePrivateKey()).address];
2375
- const collateralAsset = collateralCandidates[Math.floor(Math.random() * collateralCandidates.length)];
2676
+ const chain = config?.chains ? config.chains[int(config.chains.length)] : chains$1.ethereum;
2677
+ const loanToken = config?.loanTokens ? config.loanTokens[int(config.loanTokens.length)] : address();
2678
+ const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [address()];
2679
+ const collateralAsset = collateralCandidates[int(collateralCandidates.length)];
2376
2680
  const maturityOption = weightedChoice([["end_of_month", 1], ["end_of_next_month", 1]]);
2377
2681
  const maturity$1 = config?.maturity ?? from$8(maturityOption);
2378
2682
  const lltv = from$10(weightedChoice([
@@ -2386,7 +2690,7 @@ function random$1(config) {
2386
2690
  [.965, 4],
2387
2691
  [.98, 2]
2388
2692
  ]));
2389
- const buy = config?.buy !== void 0 ? config.buy : Math.random() > .5;
2693
+ const buy = config?.buy !== void 0 ? config.buy : bool();
2390
2694
  const ONE = 1000000000000000000n;
2391
2695
  const qMin = buy ? 16 : 4;
2392
2696
  const len = (buy ? 32 : 16) - qMin + 1;
@@ -2397,9 +2701,9 @@ function random$1(config) {
2397
2701
  const rate = config?.rate ?? weightedChoice(ratePairs);
2398
2702
  const loanTokenDecimals = config?.assetsDecimals?.[loanToken] ?? 18;
2399
2703
  const unit = BigInt(10) ** BigInt(loanTokenDecimals);
2400
- const amountBase = BigInt(100 + Math.floor(Math.random() * 999901));
2704
+ const amountBase = BigInt(100 + int(999901));
2401
2705
  const assetsScaled = config?.assets ?? amountBase * unit;
2402
- const consumed = config?.consumed !== void 0 ? config.consumed : Math.random() < .8 ? 0n : assetsScaled * BigInt(1 + Math.floor(Math.random() * 900)) / 1000n;
2706
+ const consumed = config?.consumed !== void 0 ? config.consumed : float() < .8 ? 0n : assetsScaled * BigInt(1 + int(900)) / 1000n;
2403
2707
  const callbackBySide = (() => {
2404
2708
  if (buy) return {
2405
2709
  address: zeroAddress,
@@ -2418,29 +2722,29 @@ function random$1(config) {
2418
2722
  };
2419
2723
  })();
2420
2724
  return from$5({
2421
- offering: config?.offering ?? privateKeyToAccount(generatePrivateKey()).address,
2725
+ offering: config?.offering ?? address(),
2422
2726
  assets: assetsScaled,
2423
2727
  rate,
2424
2728
  maturity: maturity$1,
2425
2729
  expiry: config?.expiry ?? maturity$1 - 1,
2426
2730
  start: config?.start ?? maturity$1 - 10,
2427
- nonce: BigInt(Math.floor(Math.random() * 1e6)),
2731
+ nonce: BigInt(int(1e6)),
2428
2732
  buy,
2429
2733
  chainId: chain.id,
2430
2734
  loanToken,
2431
- collaterals: config?.collaterals ?? Array.from({ length: Math.floor(Math.random() * 3) + 1 }, () => ({
2735
+ collaterals: config?.collaterals ?? Array.from({ length: int(3) + 1 }, () => ({
2432
2736
  ...random$3(),
2433
2737
  lltv
2434
2738
  })).sort((a, b) => a.asset.localeCompare(b.asset)),
2435
2739
  callback: config?.callback ?? callbackBySide,
2436
2740
  consumed,
2437
2741
  takeable: config?.takeable ?? assetsScaled - consumed,
2438
- blockNumber: config?.blockNumber ?? Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)
2742
+ blockNumber: config?.blockNumber ?? int(Number.MAX_SAFE_INTEGER)
2439
2743
  });
2440
2744
  }
2441
2745
  const weightedChoice = (pairs) => {
2442
2746
  const total = pairs.reduce((sum, [, weight]) => sum + weight, 0);
2443
- let roll = Math.random() * total;
2747
+ let roll = float() * total;
2444
2748
  for (const [value, weight] of pairs) {
2445
2749
  roll -= weight;
2446
2750
  if (roll < 0) return value;
@@ -2908,8 +3212,8 @@ function fromSnakeCase(snake) {
2908
3212
  function random() {
2909
3213
  return from$2({
2910
3214
  obligationId: id(random$2()),
2911
- ask: { rate: BigInt(Math.floor(Math.random() * 1e6)) },
2912
- bid: { rate: BigInt(Math.floor(Math.random() * 1e6)) }
3215
+ ask: { rate: BigInt(int(1e6)) },
3216
+ bid: { rate: BigInt(int(1e6)) }
2913
3217
  });
2914
3218
  }
2915
3219
  var InvalidQuoteError = class extends BaseError {
@@ -3009,24 +3313,28 @@ async function getOffers(apiClient, parameters) {
3009
3313
  throw new HttpGetApiFailedError(`GET request returned ${response.status}`, { details: JSON.stringify(error) });
3010
3314
  }
3011
3315
  const offers = data?.data.map((item) => {
3012
- const { signature, ...rest } = item;
3013
- return fromSnakeCase$1({
3014
- ...rest,
3015
- offering: item.offering,
3016
- maturity: from$8(item.maturity),
3017
- loan_token: item.loan_token,
3018
- collaterals: item.collaterals.map((collateral) => ({
3019
- asset: collateral.asset,
3020
- oracle: collateral.oracle,
3021
- lltv: collateral.lltv
3022
- })),
3023
- callback: {
3024
- ...item.callback,
3025
- address: item.callback.address,
3026
- data: item.callback.data
3027
- },
3028
- ...signature !== null ? { signature: item.signature } : void 0
3029
- });
3316
+ const { root, proof, signature, ...rest } = item;
3317
+ return {
3318
+ ...fromSnakeCase$1({
3319
+ ...rest,
3320
+ offering: item.offering,
3321
+ maturity: from$8(item.maturity),
3322
+ loan_token: item.loan_token,
3323
+ collaterals: item.collaterals.map((collateral) => ({
3324
+ asset: collateral.asset,
3325
+ oracle: collateral.oracle,
3326
+ lltv: collateral.lltv
3327
+ })),
3328
+ callback: {
3329
+ ...item.callback,
3330
+ address: item.callback.address,
3331
+ data: item.callback.data
3332
+ },
3333
+ signature: signature?.toLowerCase()
3334
+ }),
3335
+ root: root?.toLowerCase() || void 0,
3336
+ proof: proof?.map((p) => p.toLowerCase()) || void 0
3337
+ };
3030
3338
  }) ?? [];
3031
3339
  return {
3032
3340
  cursor: data?.cursor ?? null,
@@ -3217,8 +3525,8 @@ function getCallback(chain, type) {
3217
3525
  * @param address - Callback contract address
3218
3526
  * @returns The callback type when found, otherwise undefined
3219
3527
  */
3220
- function getCallbackType(chain, address) {
3221
- return configs[chain].callbacks?.find((c) => c.type !== CallbackType.BuyWithEmptyCallback && c.addresses.includes(address?.toLowerCase()))?.type;
3528
+ function getCallbackType(chain, address$1) {
3529
+ return configs[chain].callbacks?.find((c) => c.type !== CallbackType.BuyWithEmptyCallback && c.addresses.includes(address$1?.toLowerCase()))?.type;
3222
3530
  }
3223
3531
  /**
3224
3532
  * Returns the callback addresses for a given chain and callback type, if it exists.
@@ -3351,6 +3659,7 @@ var Rules_exports = /* @__PURE__ */ __export({
3351
3659
  callback: () => callback,
3352
3660
  chains: () => chains,
3353
3661
  maturity: () => maturity,
3662
+ sameMaker: () => sameMaker,
3354
3663
  token: () => token,
3355
3664
  validity: () => validity
3356
3665
  });
@@ -3487,10 +3796,29 @@ const token = ({ assets: assets$1 }) => single("token", "Validates that offer lo
3487
3796
  if (!allowedAssets.includes(offer.loanToken.toLowerCase())) return { message: "Loan token is not allowed" };
3488
3797
  if (offer.collaterals.some((collateral) => !allowedAssets.includes(collateral.asset.toLowerCase()))) return { message: "Collateral is not allowed" };
3489
3798
  });
3799
+ /**
3800
+ * A batch validation rule that ensures all offers in a tree have the same maker (offering address).
3801
+ * Returns an issue only for the first non-conforming offer.
3802
+ * This rule is signing-agnostic; signer verification is handled at the collector level.
3803
+ */
3804
+ const sameMaker = () => batch$1("mixed_maker", "Validates that all offers in a batch have the same maker (offering address)", (offers) => {
3805
+ const issues = /* @__PURE__ */ new Map();
3806
+ if (offers.length === 0) return issues;
3807
+ const firstMaker = offers[0].offering.toLowerCase();
3808
+ for (let i = 1; i < offers.length; i++) {
3809
+ const offer = offers[i];
3810
+ if (offer.offering.toLowerCase() !== firstMaker) {
3811
+ issues.set(i, { message: `Offer has different maker ${offer.offering} than first offer ${offers[0].offering}` });
3812
+ return issues;
3813
+ }
3814
+ }
3815
+ return issues;
3816
+ });
3490
3817
 
3491
3818
  //#endregion
3492
3819
  //#region src/gatekeeper/morphoRules.ts
3493
3820
  const morphoRules = (chains$2) => [
3821
+ sameMaker(),
3494
3822
  chains({ chains: chains$2 }),
3495
3823
  maturity({ maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth] }),
3496
3824
  callback({
@@ -3531,22 +3859,24 @@ async function add(config, offers) {
3531
3859
  const tree = from$6(offers.map((o) => from$5(o)));
3532
3860
  const chainId = await getChainId(config.client);
3533
3861
  for (const offer of tree.offers) if (chainId !== offer.chainId) throw new ChainIdMismatchError(offer.chainId, chainId);
3862
+ const signature = await sign(tree.offers, config.client);
3863
+ const encoded = await encode$1(tree, signature);
3534
3864
  try {
3535
3865
  return await config.client.sendTransaction({
3536
3866
  chain: config.client.chain,
3537
3867
  account: config.client.account,
3538
3868
  to: config.mempoolAddress,
3539
- data: encode$1(tree)
3869
+ data: encoded
3540
3870
  });
3541
3871
  } catch (error) {
3542
3872
  throw new ViemClientError(error instanceof Error ? error.message : "Unknown error");
3543
3873
  }
3544
3874
  }
3545
3875
  async function* get(config, parameters) {
3546
- const { loanToken, blockNumberGte, blockNumberLte, order: order$1 = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE } = {} } = parameters || {};
3876
+ const { loanToken, blockNumberGte, blockNumberLte, order = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE } = {} } = parameters || {};
3547
3877
  yield* streamOffers(config, {
3548
3878
  loanToken,
3549
- order: order$1,
3879
+ order,
3550
3880
  blockNumberGte,
3551
3881
  blockNumberLte,
3552
3882
  options: {
@@ -3568,7 +3898,7 @@ const getChainId = async (client) => {
3568
3898
  return chainId;
3569
3899
  };
3570
3900
  async function* streamOffers(config, parameters) {
3571
- const { loanToken, blockNumberGte, blockNumberLte, order: order$1 = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE, blockWindow = config.blockWindow } = {} } = parameters;
3901
+ const { loanToken, blockNumberGte, blockNumberLte, order = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE, blockWindow = config.blockWindow } = {} } = parameters;
3572
3902
  const stream = streamLogs({
3573
3903
  client: config.client.extend(publicActions),
3574
3904
  contractAddress: config.mempoolAddress,
@@ -3585,13 +3915,13 @@ async function* streamOffers(config, parameters) {
3585
3915
  },
3586
3916
  blockNumberGte,
3587
3917
  blockNumberLte,
3588
- order: order$1,
3918
+ order,
3589
3919
  options: {
3590
3920
  maxBatchSize,
3591
3921
  blockWindow
3592
3922
  }
3593
3923
  });
3594
- let blockNumber = order$1 === "asc" ? blockNumberGte : blockNumberLte;
3924
+ let blockNumber = order === "asc" ? blockNumberGte : blockNumberLte;
3595
3925
  for await (const { logs, blockNumber: newBlockNumber } of stream) {
3596
3926
  blockNumber = newBlockNumber;
3597
3927
  if (logs.length === 0) continue;
@@ -3600,7 +3930,7 @@ async function* streamOffers(config, parameters) {
3600
3930
  if (!log) continue;
3601
3931
  const [payload] = decodeAbiParameters([{ type: "bytes" }], log.data);
3602
3932
  try {
3603
- const tree = decode$1(payload);
3933
+ const { tree } = await decode$1(payload);
3604
3934
  for (const offer of tree.offers) {
3605
3935
  if (loanToken && offer.loanToken.toLowerCase() !== loanToken.toLowerCase()) continue;
3606
3936
  offers.push({
@@ -3769,6 +4099,7 @@ function max() {
3769
4099
  //#region src/utils/index.ts
3770
4100
  var utils_exports = /* @__PURE__ */ __export({
3771
4101
  BaseError: () => BaseError,
4102
+ Random: () => Random_exports,
3772
4103
  ReorgError: () => ReorgError,
3773
4104
  Time: () => time_exports,
3774
4105
  batch: () => batch,