@morpho-org/midnight-sdk 1.7.0 → 2.0.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.
Files changed (103) hide show
  1. package/MIGRATION-v1-to-v2.md +6 -0
  2. package/README.md +63 -61
  3. package/lib/cjs/abis.d.ts +1962 -759
  4. package/lib/cjs/abis.js +2021 -995
  5. package/lib/cjs/api/helpers.d.ts +1 -17
  6. package/lib/cjs/api/helpers.js +0 -4
  7. package/lib/cjs/api/types.d.ts +5 -4
  8. package/lib/cjs/api/version.generated.d.ts +1 -1
  9. package/lib/cjs/api/version.generated.js +2 -2
  10. package/lib/cjs/bundlesV2.d.ts +15 -0
  11. package/lib/cjs/bundlesV2.js +2 -0
  12. package/lib/cjs/constants.d.ts +34 -0
  13. package/lib/cjs/constants.js +35 -1
  14. package/lib/cjs/errors.d.ts +107 -0
  15. package/lib/cjs/errors.js +139 -1
  16. package/lib/cjs/fetch/Ratifier.d.ts +4 -3
  17. package/lib/cjs/fetch/Ratifier.js +6 -4
  18. package/lib/cjs/index.d.ts +8 -0
  19. package/lib/cjs/index.js +16 -1
  20. package/lib/cjs/offers/Offer.d.ts +10 -0
  21. package/lib/cjs/offers/Offer.js +15 -1
  22. package/lib/cjs/offers/OfferUtils.d.ts +3 -0
  23. package/lib/cjs/offers/OfferUtils.js +3 -0
  24. package/lib/cjs/signatures/{EcrecoverRatifierUtils.d.ts → EcrecoverRatifier.d.ts} +118 -2
  25. package/lib/cjs/signatures/{EcrecoverRatifierUtils.js → EcrecoverRatifier.js} +146 -9
  26. package/lib/cjs/signatures/GroupUtils.d.ts +55 -2
  27. package/lib/cjs/signatures/GroupUtils.js +64 -5
  28. package/lib/cjs/signatures/Payload.d.ts +3 -3
  29. package/lib/cjs/signatures/PriceRatifierV1.d.ts +472 -0
  30. package/lib/cjs/signatures/PriceRatifierV1.js +521 -0
  31. package/lib/cjs/signatures/RateRatifierV1.d.ts +581 -0
  32. package/lib/cjs/signatures/RateRatifierV1.js +696 -0
  33. package/lib/{esm/signatures/RatifierUtils.d.ts → cjs/signatures/Ratifier.d.ts} +14 -12
  34. package/lib/cjs/signatures/{RatifierUtils.js → Ratifier.js} +29 -29
  35. package/lib/cjs/signatures/{SetterRatifierUtils.d.ts → SetterRatifier.d.ts} +50 -3
  36. package/lib/cjs/signatures/{SetterRatifierUtils.js → SetterRatifier.js} +65 -7
  37. package/lib/cjs/signatures/Tree.d.ts +94 -17
  38. package/lib/cjs/signatures/Tree.js +146 -111
  39. package/lib/cjs/signatures/TreeUtils.d.ts +191 -21
  40. package/lib/cjs/signatures/TreeUtils.js +320 -134
  41. package/lib/cjs/signatures/index.d.ts +7 -3
  42. package/lib/cjs/signatures/index.js +5 -3
  43. package/lib/cjs/signatures/ratifierRequests.d.ts +55 -0
  44. package/lib/cjs/signatures/ratifierRequests.js +2 -0
  45. package/lib/cjs/signatures/ratifierV1Internal.d.ts +57 -0
  46. package/lib/cjs/signatures/ratifierV1Internal.js +183 -0
  47. package/lib/cjs/signatures/standardRatifierInternal.d.ts +4 -0
  48. package/lib/cjs/signatures/standardRatifierInternal.js +12 -0
  49. package/lib/cjs/signatures/treeMathInternal.d.ts +4 -0
  50. package/lib/cjs/signatures/treeMathInternal.js +12 -0
  51. package/lib/cjs/signatures/treeTypes.d.ts +109 -0
  52. package/lib/cjs/signatures/treeTypes.js +2 -0
  53. package/lib/esm/abis.d.ts +1962 -759
  54. package/lib/esm/abis.js +2020 -994
  55. package/lib/esm/api/helpers.d.ts +1 -17
  56. package/lib/esm/api/helpers.js +4 -4
  57. package/lib/esm/api/types.d.ts +5 -4
  58. package/lib/esm/api/version.generated.d.ts +1 -1
  59. package/lib/esm/api/version.generated.js +2 -2
  60. package/lib/esm/bundlesV2.d.ts +15 -0
  61. package/lib/esm/bundlesV2.js +1 -0
  62. package/lib/esm/constants.d.ts +34 -0
  63. package/lib/esm/constants.js +34 -0
  64. package/lib/esm/errors.d.ts +107 -0
  65. package/lib/esm/errors.js +132 -0
  66. package/lib/esm/fetch/Ratifier.d.ts +4 -3
  67. package/lib/esm/fetch/Ratifier.js +6 -4
  68. package/lib/esm/index.d.ts +8 -0
  69. package/lib/esm/index.js +5 -0
  70. package/lib/esm/offers/Offer.d.ts +10 -0
  71. package/lib/esm/offers/Offer.js +15 -1
  72. package/lib/esm/offers/OfferUtils.d.ts +3 -0
  73. package/lib/esm/offers/OfferUtils.js +3 -0
  74. package/lib/esm/signatures/{EcrecoverRatifierUtils.d.ts → EcrecoverRatifier.d.ts} +118 -2
  75. package/lib/esm/signatures/{EcrecoverRatifierUtils.js → EcrecoverRatifier.js} +146 -9
  76. package/lib/esm/signatures/GroupUtils.d.ts +55 -2
  77. package/lib/esm/signatures/GroupUtils.js +64 -5
  78. package/lib/esm/signatures/Payload.d.ts +3 -3
  79. package/lib/esm/signatures/PriceRatifierV1.d.ts +472 -0
  80. package/lib/esm/signatures/PriceRatifierV1.js +520 -0
  81. package/lib/esm/signatures/RateRatifierV1.d.ts +581 -0
  82. package/lib/esm/signatures/RateRatifierV1.js +695 -0
  83. package/lib/{cjs/signatures/RatifierUtils.d.ts → esm/signatures/Ratifier.d.ts} +14 -12
  84. package/lib/esm/signatures/{RatifierUtils.js → Ratifier.js} +31 -29
  85. package/lib/esm/signatures/{SetterRatifierUtils.d.ts → SetterRatifier.d.ts} +50 -3
  86. package/lib/esm/signatures/{SetterRatifierUtils.js → SetterRatifier.js} +65 -7
  87. package/lib/esm/signatures/Tree.d.ts +94 -17
  88. package/lib/esm/signatures/Tree.js +147 -112
  89. package/lib/esm/signatures/TreeUtils.d.ts +191 -21
  90. package/lib/esm/signatures/TreeUtils.js +318 -132
  91. package/lib/esm/signatures/index.d.ts +7 -3
  92. package/lib/esm/signatures/index.js +5 -3
  93. package/lib/esm/signatures/ratifierRequests.d.ts +55 -0
  94. package/lib/esm/signatures/ratifierRequests.js +1 -0
  95. package/lib/esm/signatures/ratifierV1Internal.d.ts +57 -0
  96. package/lib/esm/signatures/ratifierV1Internal.js +176 -0
  97. package/lib/esm/signatures/standardRatifierInternal.d.ts +4 -0
  98. package/lib/esm/signatures/standardRatifierInternal.js +9 -0
  99. package/lib/esm/signatures/treeMathInternal.d.ts +4 -0
  100. package/lib/esm/signatures/treeMathInternal.js +8 -0
  101. package/lib/esm/signatures/treeTypes.d.ts +109 -0
  102. package/lib/esm/signatures/treeTypes.js +1 -0
  103. package/package.json +9 -8
@@ -4,24 +4,24 @@ exports.TreeUtils = void 0;
4
4
  const morpho_ts_1 = require("@morpho-org/morpho-ts");
5
5
  const viem_1 = require("viem");
6
6
  const MidnightApi_js_1 = require("../api/MidnightApi.js");
7
+ const constants_js_1 = require("../constants.js");
7
8
  const errors_js_1 = require("../errors.js");
8
9
  const index_js_1 = require("../offers/index.js");
9
- const EcrecoverRatifierUtils_js_1 = require("./EcrecoverRatifierUtils.js");
10
+ const EcrecoverRatifier_js_1 = require("./EcrecoverRatifier.js");
10
11
  const Group_js_1 = require("./Group.js");
11
12
  const GroupUtils_js_1 = require("./GroupUtils.js");
12
13
  const offerStructInternal_js_1 = require("./offerStructInternal.js");
13
14
  const Payload_js_1 = require("./Payload.js");
14
- const SetterRatifierUtils_js_1 = require("./SetterRatifierUtils.js");
15
- function isPowerOfTwo(value) {
16
- return value > 0 && (value & (value - 1)) === 0;
17
- }
18
- function nextPowerOfTwo(value) {
19
- return 2 ** Math.ceil(Math.log2(value));
20
- }
15
+ const PriceRatifierV1_js_1 = require("./PriceRatifierV1.js");
16
+ const RateRatifierV1_js_1 = require("./RateRatifierV1.js");
17
+ const Ratifier_js_1 = require("./Ratifier.js");
18
+ const SetterRatifier_js_1 = require("./SetterRatifier.js");
19
+ const Tree_js_1 = require("./Tree.js");
20
+ const treeMathInternal_js_1 = require("./treeMathInternal.js");
21
21
  function padOfferStructs(offers) {
22
- if (isPowerOfTwo(offers.length))
22
+ if ((0, treeMathInternal_js_1.isPowerOfTwo)(offers.length))
23
23
  return [...offers];
24
- const paddedLength = nextPowerOfTwo(offers.length);
24
+ const paddedLength = (0, treeMathInternal_js_1.nextPowerOfTwo)(offers.length);
25
25
  return [
26
26
  ...offers,
27
27
  ...Array.from({ length: paddedLength - offers.length }, () => offerStructInternal_js_1.EMPTY_OFFER_STRUCT),
@@ -43,6 +43,33 @@ function assertLeafOffers(offers, leafHashes) {
43
43
  throw new errors_js_1.InvalidTreeError("Tree must not be empty.");
44
44
  }
45
45
  }
46
+ function buildLayers(leaves) {
47
+ const layers = [[...leaves]];
48
+ let level = layers[0];
49
+ while (level.length > 1) {
50
+ const next = [];
51
+ for (let i = 0; i < level.length; i += 2) {
52
+ next.push(TreeUtils.hashNode(level[i], level[i + 1]));
53
+ }
54
+ layers.push(next);
55
+ level = next;
56
+ }
57
+ return layers;
58
+ }
59
+ function assertProvableLeaves(leaves) {
60
+ if (!(0, treeMathInternal_js_1.isPowerOfTwo)(leaves.length)) {
61
+ throw new errors_js_1.InvalidTreeError(`Tree leaf count "${leaves.length}" is not a power of two.`);
62
+ }
63
+ const height = Math.log2(leaves.length);
64
+ if (height > constants_js_1.MAX_TREE_HEIGHT)
65
+ throw new errors_js_1.InvalidTreeHeightError(height);
66
+ return height;
67
+ }
68
+ function computeRootFromLeaves(leaves) {
69
+ const height = assertProvableLeaves(leaves);
70
+ const layers = buildLayers(leaves);
71
+ return { root: layers[height][0], height };
72
+ }
46
73
  /**
47
74
  * Object-compatible tree hashing, root, proof, and verification helpers.
48
75
  *
@@ -60,6 +87,37 @@ function assertLeafOffers(offers, leafHashes) {
60
87
  */
61
88
  var TreeUtils;
62
89
  (function (TreeUtils) {
90
+ /**
91
+ * Normalizes legacy standard tree entries into grouped offers.
92
+ *
93
+ * Explicit groups are flattened into their member offers, and every
94
+ * standalone offer is normalized as a singleton group using the
95
+ * router-compatible `GroupUtils.hash` group id. Use this when you need the
96
+ * exact offer list a standard Ecrecover/Setter tree commits to.
97
+ *
98
+ * @param entries - Groups or standalone offers in leaf order.
99
+ * @returns Offers in leaf order with their committed group ids.
100
+ * @throws {InvalidOfferGroupError} when an explicit group entry violates group mechanics.
101
+ * @example
102
+ * ```ts
103
+ * import { TreeUtils, type TreeCreateParams } from "@morpho-org/midnight-sdk";
104
+ *
105
+ * function normalize(entries: TreeCreateParams) {
106
+ * return TreeUtils.normalizeEntries(entries);
107
+ * }
108
+ * ```
109
+ */
110
+ function normalizeEntries(entries) {
111
+ return entries.flatMap((entry) => GroupUtils_js_1.GroupUtils.isGroupInput(entry)
112
+ ? Group_js_1.Group.from(entry).offers
113
+ : [
114
+ new index_js_1.Offer({
115
+ ...index_js_1.Offer.from(entry),
116
+ group: GroupUtils_js_1.GroupUtils.hash([entry]),
117
+ }),
118
+ ]);
119
+ }
120
+ TreeUtils.normalizeEntries = normalizeEntries;
63
121
  /**
64
122
  * Validates a tree against Midnight mempool API policy.
65
123
  *
@@ -67,17 +125,20 @@ var TreeUtils;
67
125
  * with empty `ratifierData`, then sends the temporary payload to the Midnight
68
126
  * API `POST /mempool/validate` endpoint. Pass `ratification` after signing or
69
127
  * Setter root preparation to validate final payload bytes with real
70
- * `ratifierData`.
128
+ * `ratifierData`. Price/Rate V1 tree snapshots are supported directly and
129
+ * always post the corresponding V1 `ratify` output — there is no empty
130
+ * ratifier-data shape because the router decodes the leaf fields to identify
131
+ * each offer. A V1 `ratification` only asserts the matching route.
71
132
  *
72
133
  * @param params.chainId - Chain id whose API policy should validate the tree.
73
- * @param params.tree - Offer tree to validate.
134
+ * @param params.tree - Offer tree or route-typed tree snapshot to validate. Serialized standard snapshots resume without rewriting committed groups.
74
135
  * @param params.apiUrl - Optional Midnight API URL override used for the validation HTTP request.
75
136
  * @param params.timestamp - Optional ISO-8601 timestamp or `Date` selecting the API policy snapshot.
76
137
  * @param params.fetch - Optional fetch implementation override used for the API call.
77
138
  * @param params.request - Optional fetch options forwarded to the API request.
78
- * @param params.ratification - Optional ratification inputs used to validate final payload bytes with real ratifier data.
139
+ * @param params.ratification - Optional route-matching ratification inputs used to validate final payload bytes with real ratifier data.
79
140
  * @returns Successful API validation result with `valid: true`.
80
- * @throws {InvalidTreeError} when the tree is empty, all padding, or duplicated.
141
+ * @throws {InvalidTreeError} when the tree is empty, all padding, or duplicated, or when `ratification` does not match the tree route.
81
142
  * @throws {InvalidTreeHeightError} when the resulting height is unsupported.
82
143
  * @throws {PayloadDecodeError} when validation payload encoding fails.
83
144
  * @throws {MidnightApiError} when the API returns a non-2xx response.
@@ -121,51 +182,72 @@ var TreeUtils;
121
182
  */
122
183
  async function mempoolValidate(params) {
123
184
  let items;
124
- if (params.ratification == null) {
125
- if ("paddedOffers" in params.tree) {
126
- items = params.tree.offers.map((offer) => ({
127
- offer,
128
- ratifierData: "0x",
129
- }));
130
- }
131
- else {
132
- const entries = Array.isArray(params.tree)
133
- ? params.tree
134
- : [params.tree];
135
- const offers = entries.flatMap((entry) => GroupUtils_js_1.GroupUtils.isGroupInput(entry)
136
- ? Group_js_1.Group.from(entry).offers
137
- : [
138
- new index_js_1.Offer({
139
- ...index_js_1.Offer.from(entry),
140
- group: GroupUtils_js_1.GroupUtils.hash([entry]),
141
- }),
142
- ]);
143
- buildDescriptor(entries);
144
- items = offers.map((offer) => ({
145
- offer,
146
- ratifierData: "0x",
147
- }));
185
+ if (!Array.isArray(params.tree) &&
186
+ "type" in params.tree &&
187
+ (params.tree.type === "priceV1" || params.tree.type === "rateV1")) {
188
+ const tree = params.tree;
189
+ if (params.ratification != null &&
190
+ params.ratification.type !== tree.type) {
191
+ throw new errors_js_1.InvalidTreeError("Ratification route does not match the tree route.");
148
192
  }
193
+ items =
194
+ tree.type === "priceV1"
195
+ ? PriceRatifierV1_js_1.PriceRatifierV1.ratify({ tree })
196
+ : RateRatifierV1_js_1.RateRatifierV1.ratify({ tree });
149
197
  }
150
- else if (params.ratification.type === "ecrecover") {
151
- if (params.ratification.signature != null) {
152
- items = await EcrecoverRatifierUtils_js_1.EcrecoverRatifierUtils.ratify({
153
- tree: params.tree,
154
- signature: params.ratification.signature,
155
- account: params.ratification.account,
156
- });
198
+ else {
199
+ // Only standard Ecrecover/Setter inputs remain beyond this point.
200
+ // Serialized standard snapshots resume through descriptor validation so committed groups are kept.
201
+ const rawTree = params.tree;
202
+ const tree = !(rawTree instanceof Tree_js_1.Tree) &&
203
+ !Array.isArray(rawTree) &&
204
+ "entries" in rawTree &&
205
+ "offers" in rawTree &&
206
+ "leaves" in rawTree &&
207
+ "root" in rawTree &&
208
+ "height" in rawTree
209
+ ? Tree_js_1.Tree.fromDescriptor(rawTree)
210
+ : rawTree;
211
+ if (params.ratification == null) {
212
+ if (Array.isArray(tree) || !("paddedOffers" in tree)) {
213
+ const entries = Array.isArray(tree) ? tree : [tree];
214
+ const offers = normalizeEntries(entries);
215
+ buildDescriptor(entries);
216
+ items = offers.map((offer) => ({
217
+ offer,
218
+ ratifierData: "0x",
219
+ }));
220
+ }
221
+ else {
222
+ items = tree.offers.map((offer) => ({
223
+ offer,
224
+ ratifierData: "0x",
225
+ }));
226
+ }
227
+ }
228
+ else if (params.ratification.type === "ecrecover") {
229
+ if (params.ratification.signature != null) {
230
+ items = await EcrecoverRatifier_js_1.EcrecoverRatifierUtils.ratify({
231
+ tree,
232
+ signature: params.ratification.signature,
233
+ account: params.ratification.account,
234
+ });
235
+ }
236
+ else {
237
+ items = await EcrecoverRatifier_js_1.EcrecoverRatifierUtils.ratify({
238
+ tree,
239
+ client: params.ratification.client,
240
+ account: params.ratification.account,
241
+ });
242
+ }
243
+ }
244
+ else if (params.ratification.type === "setter") {
245
+ items = SetterRatifier_js_1.SetterRatifierUtils.ratify({ tree });
157
246
  }
158
247
  else {
159
- items = await EcrecoverRatifierUtils_js_1.EcrecoverRatifierUtils.ratify({
160
- tree: params.tree,
161
- client: params.ratification.client,
162
- account: params.ratification.account,
163
- });
248
+ throw new errors_js_1.InvalidTreeError("Ratification route does not match the tree route.");
164
249
  }
165
250
  }
166
- else {
167
- items = SetterRatifierUtils_js_1.SetterRatifierUtils.ratify({ tree: params.tree });
168
- }
169
251
  const payload = await Payload_js_1.Payload.encode(items);
170
252
  const validation = await MidnightApi_js_1.MidnightApi.validateMempoolPayload({
171
253
  baseUrl: params.apiUrl,
@@ -202,56 +284,48 @@ var TreeUtils;
202
284
  return (0, viem_1.keccak256)((0, viem_1.concat)([left, right]));
203
285
  }
204
286
  TreeUtils.hashNode = hashNode;
205
- /**
206
- * Builds a tree descriptor and Merkle root. Non-power-of-two batches are
207
- * padded with protocol-zero offers at the highest leaf indices.
208
- *
209
- * Use after `Group.create` or standalone `Offer.create` when you need the
210
- * padded leaves for custom signing, root approval, or proof construction.
211
- * Groups are encoded with their derived shared group id, and standalone
212
- * offers are encoded as singleton groups with the same algorithm.
213
- * `Tree.create` calls this internally and is the simpler API for most
214
- * make-side code.
215
- *
216
- * @param entries - Groups or standalone offers in leaf order.
217
- * @returns Tree descriptor.
218
- * @throws {InvalidTreeError} when the offer count is empty, all padding, or duplicated.
219
- * @throws {InvalidTreeHeightError} when the padded tree exceeds supported ratifier typehashes.
220
- * @example
221
- * ```ts
222
- * import { Offer, TreeUtils } from "@morpho-org/midnight-sdk";
223
- * import { zeroAddress } from "viem";
224
- *
225
- * const offer = Offer.create({
226
- * market: {
227
- * chainId: 8453,
228
- * midnight: "0x0000000000000000000000000000000000001000",
229
- * loanToken: "0x0000000000000000000000000000000000006000",
230
- * collateralParams: [
231
- * {
232
- * token: "0x0000000000000000000000000000000000007000",
233
- * lltv: 770000000000000000n,
234
- * liquidationCursor: 250000000000000000n,
235
- * oracle: "0x0000000000000000000000000000000000008000",
236
- * },
237
- * ],
238
- * maturity: 54_000n,
239
- * rcfThreshold: 0n,
240
- * enterGate: zeroAddress,
241
- * liquidatorGate: zeroAddress,
242
- * },
243
- * buy: true,
244
- * maker: "0x0000000000000000000000000000000000009000",
245
- * tick: 5_000n,
246
- * expiry: 3_600n,
247
- * ratifier: "0x0000000000000000000000000000000000004000",
248
- * maxUnits: 100n,
249
- * });
250
- * const tree = TreeUtils.buildDescriptor([offer]);
251
- * console.log(tree.root);
252
- * ```
253
- */
254
287
  function buildDescriptor(entries) {
288
+ if ("type" in entries) {
289
+ switch (entries.type) {
290
+ case "priceV1": {
291
+ const descriptor = PriceRatifierV1_js_1.PriceRatifierV1.buildDescriptor(entries.entries);
292
+ return (0, morpho_ts_1.deepFreeze)({
293
+ ...descriptor,
294
+ type: "priceV1",
295
+ offers: descriptor.entries
296
+ .slice(0, descriptor.offers.length)
297
+ .map((entry) => entry.offer),
298
+ });
299
+ }
300
+ case "rateV1": {
301
+ const descriptor = RateRatifierV1_js_1.RateRatifierV1.buildDescriptor(entries.entries);
302
+ return (0, morpho_ts_1.deepFreeze)({
303
+ ...descriptor,
304
+ type: "rateV1",
305
+ offers: descriptor.entries
306
+ .slice(0, descriptor.offers.length)
307
+ .map((entry) => entry.offer),
308
+ });
309
+ }
310
+ case "ecrecover":
311
+ case "setter": {
312
+ const { tree } = Ratifier_js_1.Ratifier.normalizeRatifierTree({
313
+ tree: entries.entries,
314
+ label: entries.type === "ecrecover" ? "Ecrecover" : "Setter",
315
+ });
316
+ return (0, morpho_ts_1.deepFreeze)({
317
+ type: entries.type,
318
+ entries: tree.paddedOffers,
319
+ offers: tree.paddedOffers.slice(0, tree.offers.length),
320
+ leaves: tree.leaves,
321
+ root: tree.root,
322
+ height: tree.height,
323
+ });
324
+ }
325
+ default:
326
+ throw new errors_js_1.InvalidTreeError("Unsupported tree ratifier route.");
327
+ }
328
+ }
255
329
  const structs = entries.flatMap((entry) => GroupUtils_js_1.GroupUtils.isGroupInput(entry)
256
330
  ? GroupUtils_js_1.GroupUtils.toStructs(entry)
257
331
  : [
@@ -266,22 +340,11 @@ var TreeUtils;
266
340
  const offerStructs = padOfferStructs(structs);
267
341
  const leaves = offerStructs.map(index_js_1.OfferUtils.hashStruct);
268
342
  assertLeafOffers(offerStructs, leaves);
269
- const height = Math.log2(offerStructs.length);
270
- /* v8 ignore next: exercising this branch would require allocating more than 2^20 offer structs. */
271
- if (height > 20)
272
- throw new errors_js_1.InvalidTreeHeightError(height);
273
- let level = [...leaves];
274
- while (level.length > 1) {
275
- const next = [];
276
- for (let i = 0; i < level.length; i += 2) {
277
- next.push(hashNode(level[i], level[i + 1]));
278
- }
279
- level = next;
280
- }
343
+ const { root, height } = computeRootFromLeaves(leaves);
281
344
  return (0, morpho_ts_1.deepFreeze)({
282
345
  offers: offerStructs,
283
346
  leaves,
284
- root: level[0],
347
+ root,
285
348
  height,
286
349
  });
287
350
  }
@@ -335,17 +398,42 @@ var TreeUtils;
335
398
  return buildDescriptor(entries).root;
336
399
  }
337
400
  TreeUtils.buildRoot = buildRoot;
401
+ /**
402
+ * Builds a Merkle root from already-hashed leaves.
403
+ *
404
+ * Use this leaf-hash-agnostic form when a ratifier's leaves are not plain
405
+ * offer hashes, for example `RateRatifierV1.hashLeaf` outputs. The leaf
406
+ * count must be a power of two; pad non-power-of-two lists beforehand.
407
+ *
408
+ * @param leaves - Leaf hashes in leaf order; length must be a power of two.
409
+ * @returns Merkle root and tree height.
410
+ * @throws {InvalidTreeError} when the leaf count is not a power of two.
411
+ * @throws {InvalidTreeHeightError} when the tree exceeds supported ratifier typehashes.
412
+ * @example
413
+ * ```ts
414
+ * import { TreeUtils } from "@morpho-org/midnight-sdk";
415
+ * import { zeroHash } from "viem";
416
+ *
417
+ * const { root, height } = TreeUtils.buildRootFromLeaves([zeroHash]);
418
+ * console.log(height);
419
+ * ```
420
+ */
421
+ function buildRootFromLeaves(leaves) {
422
+ return computeRootFromLeaves(leaves);
423
+ }
424
+ TreeUtils.buildRootFromLeaves = buildRootFromLeaves;
338
425
  /**
339
426
  * Builds a Merkle proof for one offer.
340
427
  *
341
- * Use after tree construction when a custom ratifier needs one proof. The
342
- * built-in Ecrecover and Setter helpers call this while generating
343
- * `ratifierData`.
428
+ * Use after tree construction when a custom ratifier needs one proof; the
429
+ * built-in ratify helpers use {@link TreeUtils.buildProofs} to generate
430
+ * `ratifierData` in bulk.
344
431
  *
345
432
  * @param params.tree - Tree-like data with the root and leaves that contain the leaf.
346
433
  * @param params.leafIndex - Leaf index to prove.
347
434
  * @returns Proof descriptor.
348
- * @throws {InvalidTreeError} when leaf index is out of range.
435
+ * @throws {InvalidTreeError} when leaf index is out of range or when the leaf count is not a power of two.
436
+ * @throws {InvalidTreeHeightError} when the tree height (log2 of the leaf count) exceeds 20.
349
437
  * @example
350
438
  * ```ts
351
439
  * import { Offer, Tree, TreeUtils } from "@morpho-org/midnight-sdk";
@@ -388,21 +476,84 @@ var TreeUtils;
388
476
  if (leafIndex < 0n || leafIndex >= BigInt(params.tree.leaves.length)) {
389
477
  throw new errors_js_1.InvalidTreeError(`Leaf index "${leafIndex}" is outside the tree.`);
390
478
  }
391
- let index = Number(leafIndex);
392
- let level = [...params.tree.leaves];
393
- const proof = [];
394
- while (level.length > 1) {
395
- proof.push(level[index ^ 1]);
396
- const next = [];
397
- for (let i = 0; i < level.length; i += 2) {
398
- next.push(hashNode(level[i], level[i + 1]));
399
- }
400
- index = Math.floor(index / 2);
401
- level = next;
402
- }
479
+ // Height is unused; the call only validates the leaf set is provable.
480
+ assertProvableLeaves(params.tree.leaves);
481
+ const index = Number(leafIndex);
482
+ const layers = buildLayers(params.tree.leaves);
483
+ const proof = layers
484
+ .slice(0, -1)
485
+ .map((level, layerIndex) => level[(index >> layerIndex) ^ 1]);
403
486
  return (0, morpho_ts_1.deepFreeze)({ root: params.tree.root, leafIndex, proof });
404
487
  }
405
488
  TreeUtils.buildProof = buildProof;
489
+ /**
490
+ * Builds Merkle proofs for a range of leaves from one tree-layer pass.
491
+ *
492
+ * Use instead of calling {@link TreeUtils.buildProof} per leaf when many
493
+ * proofs are needed at once, for example inside `ratify` helpers: the
494
+ * parent layers are computed once instead of once per leaf. Each returned
495
+ * proof is byte-identical to `buildProof` on the same tree and leaf index.
496
+ *
497
+ * @param params.tree - Tree leaves and root to prove against.
498
+ * @param params.count - Number of leading leaf indices to prove; defaults to `tree.leaves.length`.
499
+ * @returns Frozen proofs for leaf indices `0` through `count - 1`.
500
+ * @throws {InvalidTreeError} when `count` is negative, non-integer, or exceeds the leaf count, or when the leaf count is not a power of two.
501
+ * @throws {InvalidTreeHeightError} when the tree height (log2 of the leaf count) exceeds 20.
502
+ * @example
503
+ * ```ts
504
+ * import { Offer, Tree, TreeUtils } from "@morpho-org/midnight-sdk";
505
+ * import { zeroAddress } from "viem";
506
+ *
507
+ * const offer = Offer.create({
508
+ * market: {
509
+ * chainId: 8453,
510
+ * midnight: "0x0000000000000000000000000000000000001000",
511
+ * loanToken: "0x0000000000000000000000000000000000006000",
512
+ * collateralParams: [
513
+ * {
514
+ * token: "0x0000000000000000000000000000000000007000",
515
+ * lltv: 770000000000000000n,
516
+ * liquidationCursor: 250000000000000000n,
517
+ * oracle: "0x0000000000000000000000000000000000008000",
518
+ * },
519
+ * ],
520
+ * maturity: 54_000n,
521
+ * rcfThreshold: 0n,
522
+ * enterGate: zeroAddress,
523
+ * liquidatorGate: zeroAddress,
524
+ * },
525
+ * buy: true,
526
+ * maker: "0x0000000000000000000000000000000000009000",
527
+ * tick: 5_000n,
528
+ * expiry: 3_600n,
529
+ * ratifier: "0x0000000000000000000000000000000000004000",
530
+ * maxUnits: 100n,
531
+ * });
532
+ * const proofs = TreeUtils.buildProofs({
533
+ * tree: Tree.create([offer]),
534
+ * count: 1,
535
+ * });
536
+ * console.log(proofs[0]!.proof.length);
537
+ * ```
538
+ */
539
+ function buildProofs(params) {
540
+ const count = params.count ?? params.tree.leaves.length;
541
+ if (!Number.isInteger(count) ||
542
+ count < 0 ||
543
+ count > params.tree.leaves.length) {
544
+ throw new errors_js_1.InvalidTreeError(`Proof count "${count}" is outside the tree.`);
545
+ }
546
+ // Height is unused; the call only validates the leaf set is provable.
547
+ assertProvableLeaves(params.tree.leaves);
548
+ const layers = buildLayers(params.tree.leaves);
549
+ const siblingLayers = layers.slice(0, -1);
550
+ return (0, morpho_ts_1.deepFreeze)(Array.from({ length: count }, (_, index) => ({
551
+ root: params.tree.root,
552
+ leafIndex: BigInt(index),
553
+ proof: siblingLayers.map((level, layerIndex) => level[(index >> layerIndex) ^ 1]),
554
+ })));
555
+ }
556
+ TreeUtils.buildProofs = buildProofs;
406
557
  /**
407
558
  * Verifies a local Merkle proof against a root.
408
559
  *
@@ -457,8 +608,43 @@ var TreeUtils;
457
608
  * ```
458
609
  */
459
610
  function verifyProof(params) {
460
- const offer = index_js_1.Offer.from(params.offer);
461
- let node = index_js_1.OfferUtils.hash(offer);
611
+ return verifyLeafProof({
612
+ leaf: index_js_1.OfferUtils.hash(index_js_1.Offer.from(params.offer)),
613
+ root: params.root,
614
+ leafIndex: params.leafIndex,
615
+ proof: params.proof,
616
+ });
617
+ }
618
+ TreeUtils.verifyProof = verifyProof;
619
+ /**
620
+ * Verifies a local Merkle proof for an already-hashed leaf against a root.
621
+ *
622
+ * Use this leaf-hash-agnostic form when a ratifier's leaves are not plain
623
+ * offer hashes, for example `RateRatifierV1.hashLeaf` outputs. The leaf
624
+ * index determines each sibling's left/right position, matching
625
+ * `HashLib.isLeaf` onchain.
626
+ *
627
+ * @param params.leaf - Leaf hash that starts proof reconstruction.
628
+ * @param params.root - Expected Merkle root.
629
+ * @param params.leafIndex - Leaf index proven by `params.proof`.
630
+ * @param params.proof - Merkle proof siblings for the leaf.
631
+ * @returns Whether the proof reconstructs the supplied root.
632
+ * @example
633
+ * ```ts
634
+ * import { TreeUtils } from "@morpho-org/midnight-sdk";
635
+ * import { zeroHash } from "viem";
636
+ *
637
+ * const valid = TreeUtils.verifyLeafProof({
638
+ * leaf: zeroHash,
639
+ * root: zeroHash,
640
+ * leafIndex: 0n,
641
+ * proof: [],
642
+ * });
643
+ * console.log(valid);
644
+ * ```
645
+ */
646
+ function verifyLeafProof(params) {
647
+ let node = params.leaf;
462
648
  const leafIndex = BigInt(params.leafIndex);
463
649
  if (leafIndex < 0n || leafIndex >> BigInt(params.proof.length) !== 0n) {
464
650
  return false;
@@ -473,5 +659,5 @@ var TreeUtils;
473
659
  }
474
660
  return node === params.root;
475
661
  }
476
- TreeUtils.verifyProof = verifyProof;
662
+ TreeUtils.verifyLeafProof = verifyLeafProof;
477
663
  })(TreeUtils || (exports.TreeUtils = TreeUtils = {}));
@@ -1,9 +1,13 @@
1
- export * from "./EcrecoverRatifierUtils.js";
1
+ export * from "./EcrecoverRatifier.js";
2
2
  export * from "./eip712.js";
3
3
  export * from "./Group.js";
4
4
  export * from "./GroupUtils.js";
5
5
  export { Payload } from "./Payload.js";
6
- export * from "./RatifierUtils.js";
7
- export * from "./SetterRatifierUtils.js";
6
+ export * from "./PriceRatifierV1.js";
7
+ export * from "./RateRatifierV1.js";
8
+ export * from "./Ratifier.js";
9
+ export type { EcrecoverRatifierDataRequest, EcrecoverRatifierRatifyRequest, EcrecoverRatifierSignRequest, EcrecoverRatifierTypedDataRequest, SetterRatifierDataRequest, SetterRatifierRatifyRequest, } from "./ratifierRequests.js";
10
+ export * from "./SetterRatifier.js";
8
11
  export * from "./Tree.js";
9
12
  export * from "./TreeUtils.js";
13
+ export type { AnyTree, AnyTreeSnapshot, EcrecoverTreeCreateRequest, PriceRatifierV1TreeCreateRequest, RateRatifierV1TreeCreateRequest, RatifierKind, RatifierTypes, SetterTreeCreateRequest, TreeCreateRequest, TreeData, TreeEntry, TreeSnapshot, TypedRatifierTreeInput, TypedTreeMempoolValidateParams, } from "./treeTypes.js";
@@ -15,13 +15,15 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.Payload = void 0;
18
- __exportStar(require("./EcrecoverRatifierUtils.js"), exports);
18
+ __exportStar(require("./EcrecoverRatifier.js"), exports);
19
19
  __exportStar(require("./eip712.js"), exports);
20
20
  __exportStar(require("./Group.js"), exports);
21
21
  __exportStar(require("./GroupUtils.js"), exports);
22
22
  var Payload_js_1 = require("./Payload.js");
23
23
  Object.defineProperty(exports, "Payload", { enumerable: true, get: function () { return Payload_js_1.Payload; } });
24
- __exportStar(require("./RatifierUtils.js"), exports);
25
- __exportStar(require("./SetterRatifierUtils.js"), exports);
24
+ __exportStar(require("./PriceRatifierV1.js"), exports);
25
+ __exportStar(require("./RateRatifierV1.js"), exports);
26
+ __exportStar(require("./Ratifier.js"), exports);
27
+ __exportStar(require("./SetterRatifier.js"), exports);
26
28
  __exportStar(require("./Tree.js"), exports);
27
29
  __exportStar(require("./TreeUtils.js"), exports);
@@ -0,0 +1,55 @@
1
+ import type { BigIntish } from "@morpho-org/morpho-ts";
2
+ import type { Account, Address, Chain, Client, Transport } from "viem";
3
+ import type { EcrecoverSignatureInput } from "./EcrecoverRatifier.js";
4
+ import type { TreeSnapshot } from "./treeTypes.js";
5
+ /** Tagged Ecrecover tree and chain used to construct signing data or a digest. */
6
+ export interface EcrecoverRatifierTypedDataRequest {
7
+ /** Complete Ecrecover leaf commitments, from a tree instance or portable snapshot. */
8
+ readonly tree: TreeSnapshot<"ecrecover">;
9
+ /** Chain defining the signing domain. */
10
+ readonly chainId: BigIntish;
11
+ }
12
+ /** Tagged Ecrecover tree and signer used to request a signature. */
13
+ export interface EcrecoverRatifierSignRequest {
14
+ /** Complete Ecrecover leaf commitments. */
15
+ readonly tree: TreeSnapshot<"ecrecover">;
16
+ /** Client used at the signing boundary. */
17
+ readonly client: Client<Transport, Chain, Account | undefined>;
18
+ /** Maker or authorized signer. */
19
+ readonly account: Account | Address;
20
+ }
21
+ /** Tagged Ecrecover tree with either a signing client or a precomputed signature. */
22
+ export type EcrecoverRatifierRatifyRequest = (EcrecoverRatifierSignRequest & {
23
+ /** Omit when requesting a signature through the client. */
24
+ readonly signature?: undefined;
25
+ }) | {
26
+ /** Complete Ecrecover leaf commitments. */
27
+ readonly tree: TreeSnapshot<"ecrecover">;
28
+ /** Maker or authorized signer that produced the signature. */
29
+ readonly account: Account | Address;
30
+ /** Signature over this tree's signing digest. */
31
+ readonly signature: EcrecoverSignatureInput;
32
+ /** Omit when supplying a precomputed signature. */
33
+ readonly client?: undefined;
34
+ /** The chain is derived from the offers when supplying a signature. */
35
+ readonly chainId?: undefined;
36
+ };
37
+ /** Tagged Ecrecover tree, signature and leaf index used to encode one proof. */
38
+ export interface EcrecoverRatifierDataRequest {
39
+ /** Complete Ecrecover leaf commitments. */
40
+ readonly tree: TreeSnapshot<"ecrecover">;
41
+ /** Leaf to prove. */
42
+ readonly leafIndex: BigIntish;
43
+ /** Signature over the tree's signing digest. */
44
+ readonly signature: EcrecoverSignatureInput;
45
+ }
46
+ /** Tagged Setter tree whose root has been approved onchain. */
47
+ export interface SetterRatifierRatifyRequest {
48
+ /** Complete Setter leaf commitments; the tag does not prove onchain approval. */
49
+ readonly tree: TreeSnapshot<"setter">;
50
+ }
51
+ /** Tagged Setter tree and leaf index used to encode one proof. */
52
+ export interface SetterRatifierDataRequest extends SetterRatifierRatifyRequest {
53
+ /** Leaf to prove. */
54
+ readonly leafIndex: BigIntish;
55
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });