@morpho-org/midnight-sdk 1.0.0 → 1.1.1

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 (54) hide show
  1. package/lib/cjs/api/MidnightApi.js +67 -3
  2. package/lib/cjs/api/helpers.d.ts +11 -1
  3. package/lib/cjs/api/helpers.js +48 -1
  4. package/lib/cjs/errors.d.ts +15 -0
  5. package/lib/cjs/errors.js +21 -1
  6. package/lib/cjs/fetch/Market.js +37 -2
  7. package/lib/cjs/fetch/utils.d.ts +1 -1
  8. package/lib/cjs/fetch/utils.js +9 -2
  9. package/lib/cjs/offers/Offer.d.ts +1 -1
  10. package/lib/cjs/offers/OfferUtils.d.ts +5 -5
  11. package/lib/cjs/offers/OfferUtils.js +26 -13
  12. package/lib/cjs/signatures/EcrecoverRatifierUtils.d.ts +118 -0
  13. package/lib/cjs/signatures/EcrecoverRatifierUtils.js +149 -1
  14. package/lib/cjs/signatures/Group.js +5 -3
  15. package/lib/cjs/signatures/GroupUtils.d.ts +48 -0
  16. package/lib/cjs/signatures/GroupUtils.js +51 -0
  17. package/lib/cjs/signatures/RatifierUtils.js +14 -3
  18. package/lib/cjs/signatures/SetterRatifierUtils.d.ts +31 -0
  19. package/lib/cjs/signatures/SetterRatifierUtils.js +38 -0
  20. package/lib/cjs/signatures/Tree.js +10 -2
  21. package/lib/cjs/signatures/TreeUtils.d.ts +4 -1
  22. package/lib/cjs/signatures/TreeUtils.js +19 -6
  23. package/lib/cjs/signatures/eip712.d.ts +22 -0
  24. package/lib/cjs/signatures/eip712.js +26 -0
  25. package/lib/cjs/signatures/index.d.ts +1 -0
  26. package/lib/cjs/signatures/index.js +1 -0
  27. package/lib/esm/api/MidnightApi.js +68 -4
  28. package/lib/esm/api/helpers.d.ts +11 -1
  29. package/lib/esm/api/helpers.js +48 -2
  30. package/lib/esm/errors.d.ts +15 -0
  31. package/lib/esm/errors.js +19 -0
  32. package/lib/esm/fetch/Market.js +38 -3
  33. package/lib/esm/fetch/utils.d.ts +1 -1
  34. package/lib/esm/fetch/utils.js +9 -2
  35. package/lib/esm/offers/Offer.d.ts +1 -1
  36. package/lib/esm/offers/OfferUtils.d.ts +5 -5
  37. package/lib/esm/offers/OfferUtils.js +28 -15
  38. package/lib/esm/signatures/EcrecoverRatifierUtils.d.ts +118 -0
  39. package/lib/esm/signatures/EcrecoverRatifierUtils.js +151 -3
  40. package/lib/esm/signatures/Group.js +5 -3
  41. package/lib/esm/signatures/GroupUtils.d.ts +48 -0
  42. package/lib/esm/signatures/GroupUtils.js +51 -0
  43. package/lib/esm/signatures/RatifierUtils.js +15 -4
  44. package/lib/esm/signatures/SetterRatifierUtils.d.ts +31 -0
  45. package/lib/esm/signatures/SetterRatifierUtils.js +38 -0
  46. package/lib/esm/signatures/Tree.js +11 -3
  47. package/lib/esm/signatures/TreeUtils.d.ts +4 -1
  48. package/lib/esm/signatures/TreeUtils.js +19 -6
  49. package/lib/esm/signatures/eip712.d.ts +22 -0
  50. package/lib/esm/signatures/eip712.js +22 -0
  51. package/lib/esm/signatures/index.d.ts +1 -0
  52. package/lib/esm/signatures/index.js +1 -0
  53. package/package.json +8 -7
  54. package/src/index.ts +0 -19
@@ -7,6 +7,8 @@ const actions_1 = require("viem/actions");
7
7
  const constants_js_1 = require("../constants.js");
8
8
  const errors_js_1 = require("../errors.js");
9
9
  const index_js_1 = require("../market/index.js");
10
+ const index_js_2 = require("../offers/index.js");
11
+ const eip712_js_1 = require("./eip712.js");
10
12
  const RatifierUtils_js_1 = require("./RatifierUtils.js");
11
13
  const TreeUtils_js_1 = require("./TreeUtils.js");
12
14
  const treeTypeHashes = [
@@ -102,6 +104,13 @@ const buildTreeValue = (offers) => {
102
104
  buildTreeValue(offers.slice(mid)),
103
105
  ];
104
106
  };
107
+ function toCanonicalYParity(v) {
108
+ if (v === 27)
109
+ return 0;
110
+ if (v === 28)
111
+ return 1;
112
+ throw new errors_js_1.InvalidEcrecoverSignatureVError(v);
113
+ }
105
114
  /**
106
115
  * EcrecoverRatifier-specific pure utilities.
107
116
  *
@@ -279,9 +288,148 @@ var EcrecoverRatifierUtils;
279
288
  treeTypeHash(tree.height),
280
289
  tree.root,
281
290
  ]));
282
- return (0, viem_1.keccak256)((0, viem_1.concat)(["0x1901", domainSeparator, structHash]));
291
+ return (0, eip712_js_1.eip712Digest)(domainSeparator, structHash);
283
292
  }
284
293
  EcrecoverRatifierUtils.digest = digest;
294
+ /**
295
+ * Builds the EcrecoverRatifier digest from a payload offer, root, and proof height.
296
+ *
297
+ * Use this on the take-side or in indexers after decoding ratifier data, when
298
+ * the original full tree is not available but the payload item includes the
299
+ * offer and proof metadata needed to reconstruct the signed digest. Pass the
300
+ * observed log or execution chain id so cross-chain-spoofed payloads are
301
+ * rejected before signature recovery.
302
+ *
303
+ * @param params.chainId - Observed EIP-155 chain id from the log or execution context.
304
+ * @param params.offer - Offer carried by the payload item.
305
+ * @param params.root - Merkle root embedded in the ratifier data.
306
+ * @param params.proofLength - Number of sibling hashes in the Merkle proof.
307
+ * @returns EIP-712 digest signed by the maker or authorized signer.
308
+ * @throws {ChainIdMismatchError} when `params.chainId` does not match `offer.market.chainId`.
309
+ * @throws {InvalidTreeHeightError} when `proofLength` exceeds 20.
310
+ * @example
311
+ * ```ts
312
+ * import { EcrecoverRatifierUtils } from "@morpho-org/midnight-sdk";
313
+ * import { zeroHash } from "viem";
314
+ *
315
+ * const digest = EcrecoverRatifierUtils.digestForRoot({
316
+ * chainId: 8453n,
317
+ * offer,
318
+ * root: zeroHash,
319
+ * proofLength: 0,
320
+ * });
321
+ * console.log(digest);
322
+ * ```
323
+ */
324
+ function digestForRoot(params) {
325
+ const offer = index_js_2.Offer.from(params.offer);
326
+ const market = index_js_1.MarketParams.from(offer.market);
327
+ const chainId = BigInt(params.chainId);
328
+ if (chainId !== market.chainId) {
329
+ throw new errors_js_1.ChainIdMismatchError(Number(chainId), market.chainId);
330
+ }
331
+ const domainSeparator = (0, viem_1.keccak256)((0, viem_1.encodeAbiParameters)(domainSeparatorAbi, [
332
+ constants_js_1.EIP712_DOMAIN_TYPEHASH,
333
+ chainId,
334
+ offer.ratifier,
335
+ ]));
336
+ const structHash = (0, viem_1.keccak256)((0, viem_1.encodeAbiParameters)(treeStructHashAbi, [
337
+ treeTypeHash(params.proofLength),
338
+ params.root,
339
+ ]));
340
+ return (0, eip712_js_1.eip712Digest)(domainSeparator, structHash);
341
+ }
342
+ EcrecoverRatifierUtils.digestForRoot = digestForRoot;
343
+ /**
344
+ * Builds the EcrecoverRatifier digest from one payload item's encoded ratifier data.
345
+ *
346
+ * @param params.chainId - Observed EIP-155 chain id from the log or execution context.
347
+ * @param params.offer - Offer carried by the payload item.
348
+ * @param params.ratifierData - ABI-encoded Ecrecover ratifier data.
349
+ * @returns EIP-712 digest signed by the maker or authorized signer.
350
+ * @throws {ChainIdMismatchError} when `params.chainId` does not match `offer.market.chainId`.
351
+ * @throws {InvalidTreeHeightError} when the proof height exceeds 20.
352
+ * @example
353
+ * ```ts
354
+ * import { EcrecoverRatifierUtils } from "@morpho-org/midnight-sdk";
355
+ *
356
+ * const digest = EcrecoverRatifierUtils.digestRatifierData({
357
+ * chainId: 8453n,
358
+ * offer,
359
+ * ratifierData,
360
+ * });
361
+ * console.log(digest);
362
+ * ```
363
+ */
364
+ function digestRatifierData(params) {
365
+ const decoded = decodeRatifierData(params.ratifierData);
366
+ return digestForRoot({
367
+ chainId: params.chainId,
368
+ offer: params.offer,
369
+ root: decoded.root,
370
+ proofLength: decoded.proof.length,
371
+ });
372
+ }
373
+ EcrecoverRatifierUtils.digestRatifierData = digestRatifierData;
374
+ /**
375
+ * Verifies an Ecrecover ratifier-data proof and recovers its signer.
376
+ *
377
+ * This helper intentionally does not check `Midnight.isAuthorized` or
378
+ * `EcrecoverRatifier.isRootCanceled` state. Consumers must query both at the
379
+ * observed block before treating a recovered item as executable: the returned
380
+ * signer must be the maker or authorized by the maker, the offer ratifier must
381
+ * be authorized by the maker, and `isRootCanceled(maker, root)` must be false.
382
+ *
383
+ * @param params.chainId - Observed EIP-155 chain id from the log or execution context.
384
+ * @param params.offer - Offer carried by the payload item.
385
+ * @param params.ratifierData - ABI-encoded Ecrecover ratifier data.
386
+ * @returns Decoded ratifier data plus recovered signer.
387
+ * @throws {ChainIdMismatchError} when `params.chainId` does not match `offer.market.chainId`.
388
+ * @throws {InvalidEcrecoverSignatureVError} when the decoded signature `v` is not 27 or 28.
389
+ * @throws {InvalidTreeError} when the proof does not include `offer` in `root`.
390
+ * @throws {InvalidTreeHeightError} when the proof height exceeds 20.
391
+ * @example
392
+ * ```ts
393
+ * import { EcrecoverRatifierUtils } from "@morpho-org/midnight-sdk";
394
+ *
395
+ * const verified = await EcrecoverRatifierUtils.verifyRatifierData({
396
+ * chainId: 8453n,
397
+ * offer,
398
+ * ratifierData,
399
+ * });
400
+ * console.log(verified.signer);
401
+ * ```
402
+ */
403
+ async function verifyRatifierData(params) {
404
+ const offer = index_js_2.Offer.from(params.offer);
405
+ const decoded = decodeRatifierData(params.ratifierData);
406
+ if (!TreeUtils_js_1.TreeUtils.verifyProof({
407
+ offer,
408
+ root: decoded.root,
409
+ leafIndex: decoded.leafIndex,
410
+ proof: decoded.proof,
411
+ })) {
412
+ throw new errors_js_1.InvalidTreeError("Ratifier data proof does not include offer.");
413
+ }
414
+ const signer = await (0, viem_1.recoverAddress)({
415
+ hash: digestForRoot({
416
+ chainId: params.chainId,
417
+ offer,
418
+ root: decoded.root,
419
+ proofLength: decoded.proof.length,
420
+ }),
421
+ signature: (0, viem_1.serializeSignature)({
422
+ r: decoded.signature.r,
423
+ s: decoded.signature.s,
424
+ yParity: toCanonicalYParity(decoded.signature.v),
425
+ }),
426
+ });
427
+ return (0, morpho_ts_1.deepFreeze)({
428
+ ...decoded,
429
+ signer,
430
+ });
431
+ }
432
+ EcrecoverRatifierUtils.verifyRatifierData = verifyRatifierData;
285
433
  /**
286
434
  * Signs EcrecoverRatifier typed data through a viem client.
287
435
  *
@@ -144,9 +144,11 @@ class Group {
144
144
  * ```
145
145
  */
146
146
  static from(entry) {
147
- return entry instanceof Group
148
- ? entry
149
- : new Group("offers" in entry ? entry.offers : [entry]);
147
+ if (entry instanceof Group)
148
+ return entry;
149
+ if (GroupUtils_js_1.GroupUtils.isGroupInput(entry))
150
+ return new Group(entry.offers);
151
+ return new Group([entry]);
150
152
  }
151
153
  /**
152
154
  * Creates a protocol-valid offer group.
@@ -103,6 +103,54 @@ export type GroupInput = IGroup | IOffer;
103
103
  * ```
104
104
  */
105
105
  export declare namespace GroupUtils {
106
+ /**
107
+ * Returns whether an entry is an explicit group rather than a standalone offer.
108
+ *
109
+ * Plain offer objects can also contain incidental `offers` properties, so the
110
+ * discriminant must reject values that still carry the offer `market` field.
111
+ *
112
+ * @param entry - Group or offer input to inspect.
113
+ * @returns Whether `entry` is an explicit group input.
114
+ * @example
115
+ * ```ts
116
+ * import { GroupUtils, Offer } from "@morpho-org/midnight-sdk";
117
+ *
118
+ * const offer = Offer.from({
119
+ * market: {
120
+ * chainId: 8453,
121
+ * midnight: "0x0000000000000000000000000000000000001000",
122
+ * loanToken: "0x0000000000000000000000000000000000006000",
123
+ * collateralParams: [
124
+ * {
125
+ * token: "0x0000000000000000000000000000000000007000",
126
+ * lltv: 770000000000000000n,
127
+ * liquidationCursor: 250000000000000000n,
128
+ * oracle: "0x0000000000000000000000000000000000008000",
129
+ * },
130
+ * ],
131
+ * maturity: 54_000n,
132
+ * rcfThreshold: 0n,
133
+ * enterGate: "0x0000000000000000000000000000000000000000",
134
+ * liquidatorGate: "0x0000000000000000000000000000000000000000",
135
+ * },
136
+ * buy: true,
137
+ * maker: "0x0000000000000000000000000000000000009000",
138
+ * start: 0n,
139
+ * expiry: 3_600n,
140
+ * tick: 5_000n,
141
+ * callback: "0x0000000000000000000000000000000000000000",
142
+ * callbackData: "0x",
143
+ * receiverIfMakerIsSeller: "0x0000000000000000000000000000000000000000",
144
+ * ratifier: "0x0000000000000000000000000000000000004000",
145
+ * reduceOnly: false,
146
+ * maxUnits: 100n,
147
+ * maxAssets: 0n,
148
+ * continuousFeeCap: 0n,
149
+ * });
150
+ * console.log(GroupUtils.isGroupInput(offer));
151
+ * ```
152
+ */
153
+ function isGroupInput(entry: GroupInput): entry is IGroup;
106
154
  /**
107
155
  * Derives the deterministic content-addressed id for a group of offers.
108
156
  *
@@ -22,6 +22,57 @@ const index_js_1 = require("../offers/index.js");
22
22
  */
23
23
  var GroupUtils;
24
24
  (function (GroupUtils) {
25
+ /**
26
+ * Returns whether an entry is an explicit group rather than a standalone offer.
27
+ *
28
+ * Plain offer objects can also contain incidental `offers` properties, so the
29
+ * discriminant must reject values that still carry the offer `market` field.
30
+ *
31
+ * @param entry - Group or offer input to inspect.
32
+ * @returns Whether `entry` is an explicit group input.
33
+ * @example
34
+ * ```ts
35
+ * import { GroupUtils, Offer } from "@morpho-org/midnight-sdk";
36
+ *
37
+ * const offer = Offer.from({
38
+ * market: {
39
+ * chainId: 8453,
40
+ * midnight: "0x0000000000000000000000000000000000001000",
41
+ * loanToken: "0x0000000000000000000000000000000000006000",
42
+ * collateralParams: [
43
+ * {
44
+ * token: "0x0000000000000000000000000000000000007000",
45
+ * lltv: 770000000000000000n,
46
+ * liquidationCursor: 250000000000000000n,
47
+ * oracle: "0x0000000000000000000000000000000000008000",
48
+ * },
49
+ * ],
50
+ * maturity: 54_000n,
51
+ * rcfThreshold: 0n,
52
+ * enterGate: "0x0000000000000000000000000000000000000000",
53
+ * liquidatorGate: "0x0000000000000000000000000000000000000000",
54
+ * },
55
+ * buy: true,
56
+ * maker: "0x0000000000000000000000000000000000009000",
57
+ * start: 0n,
58
+ * expiry: 3_600n,
59
+ * tick: 5_000n,
60
+ * callback: "0x0000000000000000000000000000000000000000",
61
+ * callbackData: "0x",
62
+ * receiverIfMakerIsSeller: "0x0000000000000000000000000000000000000000",
63
+ * ratifier: "0x0000000000000000000000000000000000004000",
64
+ * reduceOnly: false,
65
+ * maxUnits: 100n,
66
+ * maxAssets: 0n,
67
+ * continuousFeeCap: 0n,
68
+ * });
69
+ * console.log(GroupUtils.isGroupInput(offer));
70
+ * ```
71
+ */
72
+ function isGroupInput(entry) {
73
+ return "offers" in entry && !("market" in entry);
74
+ }
75
+ GroupUtils.isGroupInput = isGroupInput;
25
76
  /**
26
77
  * Derives the deterministic content-addressed id for a group of offers.
27
78
  *
@@ -4,6 +4,7 @@ exports.RatifierUtils = void 0;
4
4
  const errors_js_1 = require("../errors.js");
5
5
  const index_js_1 = require("../offers/index.js");
6
6
  const Group_js_1 = require("./Group.js");
7
+ const GroupUtils_js_1 = require("./GroupUtils.js");
7
8
  const TreeUtils_js_1 = require("./TreeUtils.js");
8
9
  function isTreeLike(tree) {
9
10
  return (!Array.isArray(tree) &&
@@ -14,10 +15,20 @@ function isTreeLike(tree) {
14
15
  "height" in tree);
15
16
  }
16
17
  function normalizeTree(tree) {
17
- if (isTreeLike(tree))
18
+ if (isTreeLike(tree)) {
18
19
  return tree;
19
- const offers = tree.flatMap((entry) => "offers" in entry ? Group_js_1.Group.from(entry).offers : [index_js_1.Offer.from(entry)]);
20
- const descriptor = TreeUtils_js_1.TreeUtils.buildDescriptor(offers);
20
+ }
21
+ const offers = tree.flatMap((entry) => GroupUtils_js_1.GroupUtils.isGroupInput(entry)
22
+ ? Group_js_1.Group.from(entry).offers
23
+ : [
24
+ new index_js_1.Offer({
25
+ ...index_js_1.Offer.from(entry),
26
+ group: index_js_1.OfferUtils.groupHash(entry),
27
+ }),
28
+ ]);
29
+ const descriptor = TreeUtils_js_1.TreeUtils.buildDescriptor(offers, {
30
+ preserveStandaloneGroups: true,
31
+ });
21
32
  return {
22
33
  offers,
23
34
  paddedOffers: descriptor.offers,
@@ -1,5 +1,6 @@
1
1
  import { type BigIntish } from "@morpho-org/morpho-ts";
2
2
  import { type Hash, type Hex } from "viem";
3
+ import { type IOffer } from "../offers/index.js";
3
4
  import type { Payload } from "./Payload.js";
4
5
  import { type RatifierTreeInput, type TreeProof } from "./TreeUtils.js";
5
6
  /**
@@ -74,6 +75,13 @@ export interface SetterRatifierDataParams {
74
75
  /** Leaf index to prove. */
75
76
  readonly leafIndex: BigIntish;
76
77
  }
78
+ /** Parameters for locally verifying Setter ratifier data attached to one payload item. */
79
+ export interface SetterRatifierDataVerificationParams {
80
+ /** Offer carried by the payload item. */
81
+ readonly offer: IOffer;
82
+ /** ABI-encoded Setter ratifier data carried by the payload item. */
83
+ readonly ratifierData: Hex;
84
+ }
77
85
  /**
78
86
  * SetterRatifier-specific pure utilities.
79
87
  *
@@ -144,6 +152,29 @@ export declare namespace SetterRatifierUtils {
144
152
  * ```
145
153
  */
146
154
  function decodeRatifierData(data: Hex): DecodedSetterRatifierData;
155
+ /**
156
+ * Verifies that Setter ratifier data proves one payload offer belongs to its root.
157
+ *
158
+ * This helper intentionally does not check `SetterRatifier.isRootRatified` or
159
+ * `Midnight.isAuthorized` state. Consumers can query those values at their
160
+ * own block context after local proof verification.
161
+ *
162
+ * @param params.offer - Offer carried by the payload item.
163
+ * @param params.ratifierData - ABI-encoded Setter ratifier data.
164
+ * @returns Decoded Setter ratifier data after proof verification.
165
+ * @throws {InvalidTreeError} when the proof does not include `offer` in `root`.
166
+ * @example
167
+ * ```ts
168
+ * import { SetterRatifierUtils } from "@morpho-org/midnight-sdk";
169
+ *
170
+ * const decoded = SetterRatifierUtils.verifyRatifierData({
171
+ * offer,
172
+ * ratifierData,
173
+ * });
174
+ * console.log(decoded.root);
175
+ * ```
176
+ */
177
+ function verifyRatifierData(params: SetterRatifierDataVerificationParams): DecodedSetterRatifierData;
147
178
  /**
148
179
  * Builds one ratifier-data value for a tree leaf.
149
180
  *
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SetterRatifierUtils = void 0;
4
4
  const morpho_ts_1 = require("@morpho-org/morpho-ts");
5
5
  const viem_1 = require("viem");
6
+ const errors_js_1 = require("../errors.js");
7
+ const index_js_1 = require("../offers/index.js");
6
8
  const RatifierUtils_js_1 = require("./RatifierUtils.js");
7
9
  const TreeUtils_js_1 = require("./TreeUtils.js");
8
10
  const setterRatifierDataAbi = [
@@ -88,6 +90,42 @@ var SetterRatifierUtils;
88
90
  return (0, morpho_ts_1.deepFreeze)({ root, leafIndex, proof: [...proof] });
89
91
  }
90
92
  SetterRatifierUtils.decodeRatifierData = decodeRatifierData;
93
+ /**
94
+ * Verifies that Setter ratifier data proves one payload offer belongs to its root.
95
+ *
96
+ * This helper intentionally does not check `SetterRatifier.isRootRatified` or
97
+ * `Midnight.isAuthorized` state. Consumers can query those values at their
98
+ * own block context after local proof verification.
99
+ *
100
+ * @param params.offer - Offer carried by the payload item.
101
+ * @param params.ratifierData - ABI-encoded Setter ratifier data.
102
+ * @returns Decoded Setter ratifier data after proof verification.
103
+ * @throws {InvalidTreeError} when the proof does not include `offer` in `root`.
104
+ * @example
105
+ * ```ts
106
+ * import { SetterRatifierUtils } from "@morpho-org/midnight-sdk";
107
+ *
108
+ * const decoded = SetterRatifierUtils.verifyRatifierData({
109
+ * offer,
110
+ * ratifierData,
111
+ * });
112
+ * console.log(decoded.root);
113
+ * ```
114
+ */
115
+ function verifyRatifierData(params) {
116
+ const offer = index_js_1.Offer.from(params.offer);
117
+ const decoded = decodeRatifierData(params.ratifierData);
118
+ if (!TreeUtils_js_1.TreeUtils.verifyProof({
119
+ offer,
120
+ root: decoded.root,
121
+ leafIndex: decoded.leafIndex,
122
+ proof: decoded.proof,
123
+ })) {
124
+ throw new errors_js_1.InvalidTreeError("Ratifier data proof does not include offer.");
125
+ }
126
+ return decoded;
127
+ }
128
+ SetterRatifierUtils.verifyRatifierData = verifyRatifierData;
91
129
  /**
92
130
  * Builds one ratifier-data value for a tree leaf.
93
131
  *
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Tree = void 0;
4
4
  const index_js_1 = require("../offers/index.js");
5
5
  const Group_js_1 = require("./Group.js");
6
+ const GroupUtils_js_1 = require("./GroupUtils.js");
6
7
  const TreeUtils_js_1 = require("./TreeUtils.js");
7
8
  /**
8
9
  * Maker-side Merkle tree used by Midnight ratifiers.
@@ -57,8 +58,15 @@ class Tree {
57
58
  /** Tree height. */
58
59
  height;
59
60
  constructor(params) {
60
- this.offers = params.flatMap((entry) => "offers" in entry ? Group_js_1.Group.from(entry).offers : [index_js_1.Offer.from(entry)]);
61
- const descriptor = TreeUtils_js_1.TreeUtils.buildDescriptor(this.offers);
61
+ this.offers = params.flatMap((entry) => GroupUtils_js_1.GroupUtils.isGroupInput(entry)
62
+ ? Group_js_1.Group.from(entry).offers
63
+ : [
64
+ new index_js_1.Offer({
65
+ ...index_js_1.Offer.from(entry),
66
+ group: index_js_1.OfferUtils.groupHash(entry),
67
+ }),
68
+ ]);
69
+ const descriptor = TreeUtils_js_1.TreeUtils.buildDescriptor(params);
62
70
  this.paddedOffers = descriptor.offers;
63
71
  this.leaves = descriptor.leaves;
64
72
  this.root = descriptor.root;
@@ -494,6 +494,7 @@ export declare namespace TreeUtils {
494
494
  * make-side code.
495
495
  *
496
496
  * @param entries - Groups or standalone offers in leaf order.
497
+ * @param options.preserveStandaloneGroups - Whether standalone offer group ids should be preserved instead of normalized.
497
498
  * @returns Tree descriptor.
498
499
  * @throws {InvalidTreeError} when the offer count is empty, all padding, or duplicated.
499
500
  * @throws {InvalidTreeHeightError} when the padded tree exceeds supported ratifier typehashes.
@@ -531,7 +532,9 @@ export declare namespace TreeUtils {
531
532
  * console.log(tree.root);
532
533
  * ```
533
534
  */
534
- function buildDescriptor(entries: TreeCreateParams): TreeDescriptor;
535
+ function buildDescriptor(entries: TreeCreateParams, options?: {
536
+ readonly preserveStandaloneGroups?: boolean;
537
+ }): TreeDescriptor;
535
538
  /**
536
539
  * Builds only the tree root.
537
540
  *
@@ -123,11 +123,16 @@ var TreeUtils;
123
123
  if (params.ratification == null) {
124
124
  const offers = "paddedOffers" in params.tree
125
125
  ? params.tree.offers
126
- : params.tree.flatMap((entry) => "offers" in entry
126
+ : params.tree.flatMap((entry) => GroupUtils_js_1.GroupUtils.isGroupInput(entry)
127
127
  ? Group_js_1.Group.from(entry).offers
128
- : [index_js_1.Offer.from(entry)]);
128
+ : [
129
+ new index_js_1.Offer({
130
+ ...index_js_1.Offer.from(entry),
131
+ group: index_js_1.OfferUtils.groupHash(entry),
132
+ }),
133
+ ]);
129
134
  if (!("paddedOffers" in params.tree)) {
130
- buildDescriptor(params.tree);
135
+ buildDescriptor(offers, { preserveStandaloneGroups: true });
131
136
  }
132
137
  items = offers.map((offer) => ({
133
138
  offer,
@@ -201,6 +206,7 @@ var TreeUtils;
201
206
  * make-side code.
202
207
  *
203
208
  * @param entries - Groups or standalone offers in leaf order.
209
+ * @param options.preserveStandaloneGroups - Whether standalone offer group ids should be preserved instead of normalized.
204
210
  * @returns Tree descriptor.
205
211
  * @throws {InvalidTreeError} when the offer count is empty, all padding, or duplicated.
206
212
  * @throws {InvalidTreeHeightError} when the padded tree exceeds supported ratifier typehashes.
@@ -238,10 +244,17 @@ var TreeUtils;
238
244
  * console.log(tree.root);
239
245
  * ```
240
246
  */
241
- function buildDescriptor(entries) {
242
- const structs = entries.flatMap((entry) => "offers" in entry
247
+ function buildDescriptor(entries, options = {}) {
248
+ const structs = entries.flatMap((entry) => GroupUtils_js_1.GroupUtils.isGroupInput(entry)
243
249
  ? GroupUtils_js_1.GroupUtils.toStructs(entry)
244
- : [index_js_1.OfferUtils.toStruct({ offer: entry })]);
250
+ : [
251
+ index_js_1.OfferUtils.toStruct({
252
+ offer: entry,
253
+ group: options.preserveStandaloneGroups
254
+ ? undefined
255
+ : index_js_1.OfferUtils.groupHash(entry),
256
+ }),
257
+ ]);
245
258
  if (structs.length === 0) {
246
259
  throw new errors_js_1.InvalidTreeError("Tree must not be empty.");
247
260
  }
@@ -0,0 +1,22 @@
1
+ import { type Hash } from "viem";
2
+ /**
3
+ * Hashes EIP-712 digest parts as `keccak256(0x1901 || domainSeparator || structHash)`.
4
+ *
5
+ * Use this when the domain separator and struct hash are already available and
6
+ * rebuilding full typed-data values would duplicate protocol-specific type maps.
7
+ *
8
+ * @param domainSeparator - EIP-712 domain separator hash.
9
+ * @param structHash - EIP-712 struct hash.
10
+ * @returns Canonical EIP-712 digest.
11
+ * @example
12
+ * ```ts
13
+ * import { eip712Digest } from "@morpho-org/midnight-sdk";
14
+ *
15
+ * const digest = eip712Digest(
16
+ * "0x0000000000000000000000000000000000000000000000000000000000000001",
17
+ * "0x0000000000000000000000000000000000000000000000000000000000000002",
18
+ * );
19
+ * console.log(digest);
20
+ * ```
21
+ */
22
+ export declare const eip712Digest: (domainSeparator: Hash, structHash: Hash) => Hash;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.eip712Digest = void 0;
4
+ const viem_1 = require("viem");
5
+ /**
6
+ * Hashes EIP-712 digest parts as `keccak256(0x1901 || domainSeparator || structHash)`.
7
+ *
8
+ * Use this when the domain separator and struct hash are already available and
9
+ * rebuilding full typed-data values would duplicate protocol-specific type maps.
10
+ *
11
+ * @param domainSeparator - EIP-712 domain separator hash.
12
+ * @param structHash - EIP-712 struct hash.
13
+ * @returns Canonical EIP-712 digest.
14
+ * @example
15
+ * ```ts
16
+ * import { eip712Digest } from "@morpho-org/midnight-sdk";
17
+ *
18
+ * const digest = eip712Digest(
19
+ * "0x0000000000000000000000000000000000000000000000000000000000000001",
20
+ * "0x0000000000000000000000000000000000000000000000000000000000000002",
21
+ * );
22
+ * console.log(digest);
23
+ * ```
24
+ */
25
+ const eip712Digest = (domainSeparator, structHash) => (0, viem_1.keccak256)((0, viem_1.concat)(["0x1901", domainSeparator, structHash]));
26
+ exports.eip712Digest = eip712Digest;
@@ -1,4 +1,5 @@
1
1
  export * from "./EcrecoverRatifierUtils.js";
2
+ export * from "./eip712.js";
2
3
  export * from "./Group.js";
3
4
  export * from "./GroupUtils.js";
4
5
  export { Payload } from "./Payload.js";
@@ -16,6 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.Payload = void 0;
18
18
  __exportStar(require("./EcrecoverRatifierUtils.js"), exports);
19
+ __exportStar(require("./eip712.js"), exports);
19
20
  __exportStar(require("./Group.js"), exports);
20
21
  __exportStar(require("./GroupUtils.js"), exports);
21
22
  var Payload_js_1 = require("./Payload.js");