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