@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
@@ -1,13 +1,17 @@
1
- import { Offer } from "../offers/index.js";
2
- import { Group } from "./Group.js";
3
- import { GroupUtils } from "./GroupUtils.js";
1
+ import { deepFreeze } from "@morpho-org/morpho-ts";
2
+ import { InvalidTreeError } from "../errors.js";
3
+ import { Offer, OfferUtils } from "../offers/index.js";
4
+ import { PriceRatifierV1 } from "./PriceRatifierV1.js";
5
+ import { RateRatifierV1 } from "./RateRatifierV1.js";
6
+ import { Ratifier } from "./Ratifier.js";
4
7
  import { TreeUtils, } from "./TreeUtils.js";
5
8
  /**
6
9
  * Maker-side Merkle tree used by Midnight ratifiers.
7
10
  *
8
11
  * Build a tree after offers have been created and related offers have been
9
12
  * grouped. The tree root is what Ecrecover makers sign, Setter makers approve,
10
- * and payload items later prove with per-leaf ratifier data.
13
+ * and payload items later prove with per-leaf ratifier data. Price/Rate V1
14
+ * trees carry their additional leaf commitments and use onchain root approval.
11
15
  *
12
16
  * @example
13
17
  * ```ts
@@ -39,135 +43,144 @@ import { TreeUtils, } from "./TreeUtils.js";
39
43
  * ratifier: "0x0000000000000000000000000000000000004000",
40
44
  * maxUnits: 100n,
41
45
  * });
42
- * const tree = Tree.create([offer]);
46
+ * const tree = Tree.create({ type: "ecrecover", entries: [offer] });
43
47
  * console.log(tree.root);
44
48
  * ```
45
49
  */
46
50
  export class Tree {
51
+ /** Explicit route, or undefined for a legacy standard tree. */
52
+ type;
53
+ /** Complete padded leaf commitments for this route. */
54
+ entries;
47
55
  /** Non-padding offers in leaf order. */
48
56
  offers;
49
57
  /** ABI-compatible offers in leaf order, including protocol-zero padding. */
50
58
  paddedOffers;
51
- /** Leaf hashes for `paddedOffers`. */
59
+ /** Leaf hashes for the complete padded `entries`. */
52
60
  leaves;
53
61
  /** Merkle root. */
54
62
  root;
55
63
  /** Tree height. */
56
64
  height;
57
- constructor(params) {
58
- this.offers = params.flatMap((entry) => GroupUtils.isGroupInput(entry)
59
- ? Group.from(entry).offers
60
- : [
61
- new Offer({
62
- ...Offer.from(entry),
63
- group: GroupUtils.hash([entry]),
64
- }),
65
- ]);
66
- const descriptor = TreeUtils.buildDescriptor(params);
67
- this.paddedOffers = descriptor.offers;
68
- this.leaves = descriptor.leaves;
65
+ constructor(type, descriptor) {
66
+ this.type = type;
67
+ this.offers = Object.freeze(descriptor.offers.map((offer) => Offer.from(offer)));
68
+ this.entries = deepFreeze(structuredClone(descriptor.entries));
69
+ const entries = this.entries;
70
+ this.paddedOffers = Object.freeze(entries.map((entry) => ("offer" in entry ? entry.offer : entry)));
71
+ this.leaves = Object.freeze([...descriptor.leaves]);
69
72
  this.root = descriptor.root;
70
73
  this.height = descriptor.height;
71
74
  }
72
- /**
73
- * Returns a tree instance from class or plain input.
74
- *
75
- * Use at boundaries that accept either a prebuilt tree or raw group/offer
76
- * inputs, such as API validation helpers. Existing `Tree` instances are
77
- * returned as-is.
78
- *
79
- * @param tree - Tree class or creation input.
80
- * @returns Tree instance.
81
- * @throws {InvalidTreeError} when the tree is empty, all padding, or duplicated.
82
- * @throws {InvalidTreeHeightError} when the resulting height is unsupported.
83
- * @example
84
- * ```ts
85
- * import { Offer, Tree } from "@morpho-org/midnight-sdk";
86
- * import { zeroAddress } from "viem";
87
- *
88
- * const offer = Offer.create({
89
- * market: {
90
- * chainId: 8453,
91
- * midnight: "0x0000000000000000000000000000000000001000",
92
- * loanToken: "0x0000000000000000000000000000000000006000",
93
- * collateralParams: [
94
- * {
95
- * token: "0x0000000000000000000000000000000000007000",
96
- * lltv: 770000000000000000n,
97
- * liquidationCursor: 250000000000000000n,
98
- * oracle: "0x0000000000000000000000000000000000008000",
99
- * },
100
- * ],
101
- * maturity: 54_000n,
102
- * rcfThreshold: 0n,
103
- * enterGate: zeroAddress,
104
- * liquidatorGate: zeroAddress,
105
- * },
106
- * buy: true,
107
- * maker: "0x0000000000000000000000000000000000009000",
108
- * tick: 5_000n,
109
- * expiry: 3_600n,
110
- * ratifier: "0x0000000000000000000000000000000000004000",
111
- * maxUnits: 100n,
112
- * });
113
- * const tree = Tree.from([offer]);
114
- * console.log(tree.root);
115
- * ```
116
- */
117
75
  static from(tree) {
118
76
  if (tree instanceof Tree)
119
77
  return tree;
78
+ // Portable snapshots resume through descriptor validation instead of rebuilding.
79
+ if (!Array.isArray(tree) &&
80
+ "entries" in tree &&
81
+ "leaves" in tree &&
82
+ "root" in tree) {
83
+ return "type" in tree && tree.type != null
84
+ ? Tree.fromDescriptor(tree)
85
+ : Tree.fromDescriptor(tree);
86
+ }
87
+ if ("type" in tree && tree.type != null)
88
+ return Tree.create(tree);
120
89
  return Tree.create(Array.isArray(tree) ? tree : [tree]);
121
90
  }
91
+ static create(params) {
92
+ if ("type" in params) {
93
+ const descriptor = TreeUtils.buildDescriptor(params);
94
+ switch (descriptor.type) {
95
+ case "ecrecover":
96
+ return new Tree("ecrecover", descriptor);
97
+ case "setter":
98
+ return new Tree("setter", descriptor);
99
+ case "priceV1":
100
+ return new Tree("priceV1", descriptor);
101
+ case "rateV1":
102
+ return new Tree("rateV1", descriptor);
103
+ }
104
+ }
105
+ const offers = TreeUtils.normalizeEntries(params);
106
+ const descriptor = TreeUtils.buildDescriptor(params);
107
+ return new Tree(undefined, {
108
+ ...descriptor,
109
+ offers,
110
+ entries: descriptor.offers,
111
+ });
112
+ }
122
113
  /**
123
- * Creates a tree from groups or standalone offers.
124
- *
125
- * Use after `Offer.create` and optional `Group.create`, before
126
- * `Tree.mempoolValidate`, `EcrecoverRatifierUtils.ratify`, or
127
- * `SetterRatifierUtils.ratify`. Groups are flattened, and every standalone
128
- * offer is normalized as a singleton group using the router-compatible group
129
- * id algorithm.
130
- *
131
- * @param params - Groups or standalone offers in leaf order.
132
- * @returns Tree instance.
133
- * @throws {InvalidTreeError} when the tree is empty, all padding, or duplicated.
134
- * @throws {InvalidTreeHeightError} when the resulting height is unsupported.
114
+ * Exports plain normalized data for transport; does not claim root authorization.
115
+ * @returns Descriptor with the same route and padded commitments.
135
116
  * @example
136
117
  * ```ts
137
- * import { Offer, Tree } from "@morpho-org/midnight-sdk";
138
- * import { zeroAddress } from "viem";
139
- *
140
- * const offer = Offer.create({
141
- * market: {
142
- * chainId: 8453,
143
- * midnight: "0x0000000000000000000000000000000000001000",
144
- * loanToken: "0x0000000000000000000000000000000000006000",
145
- * collateralParams: [
146
- * {
147
- * token: "0x0000000000000000000000000000000000007000",
148
- * lltv: 770000000000000000n,
149
- * liquidationCursor: 250000000000000000n,
150
- * oracle: "0x0000000000000000000000000000000000008000",
151
- * },
152
- * ],
153
- * maturity: 54_000n,
154
- * rcfThreshold: 0n,
155
- * enterGate: zeroAddress,
156
- * liquidatorGate: zeroAddress,
157
- * },
158
- * buy: true,
159
- * maker: "0x0000000000000000000000000000000000009000",
160
- * tick: 5_000n,
161
- * expiry: 3_600n,
162
- * ratifier: "0x0000000000000000000000000000000000004000",
163
- * maxUnits: 100n,
164
- * });
165
- * const tree = Tree.create([offer]);
166
- * console.log(tree.height);
118
+ * import { Tree, type IOffer } from "@morpho-org/midnight-sdk";
119
+ * function prepare(offers: readonly IOffer[]) {
120
+ * return Tree.create({ type: "setter", entries: offers }).toDescriptor();
121
+ * }
167
122
  * ```
168
123
  */
169
- static create(params) {
170
- return new Tree(params);
124
+ toDescriptor() {
125
+ return deepFreeze({
126
+ type: this.type,
127
+ entries: [...this.entries],
128
+ offers: this.paddedOffers.slice(0, this.offers.length),
129
+ leaves: [...this.leaves],
130
+ root: this.root,
131
+ height: this.height,
132
+ });
133
+ }
134
+ static fromDescriptor(descriptor) {
135
+ switch (descriptor.type) {
136
+ case "priceV1":
137
+ // Validate caller-controlled hashes, offers and padding before constructing an instance.
138
+ PriceRatifierV1.buildProof({ tree: descriptor, leafIndex: 0n });
139
+ return new Tree("priceV1", {
140
+ ...descriptor,
141
+ entries: descriptor.entries.map((entry, index) => ({
142
+ allowedTaker: entry.allowedTaker,
143
+ offer: index < descriptor.offers.length
144
+ ? OfferUtils.toStruct({ offer: entry.offer })
145
+ : entry.offer,
146
+ })),
147
+ });
148
+ case "rateV1":
149
+ // Validate caller-controlled hashes, offers and padding before constructing an instance.
150
+ RateRatifierV1.buildProof({ tree: descriptor, leafIndex: 0n });
151
+ return new Tree("rateV1", {
152
+ ...descriptor,
153
+ entries: descriptor.entries.map((entry, index) => ({
154
+ rate: entry.rate,
155
+ allowedTaker: entry.allowedTaker,
156
+ offer: index < descriptor.offers.length
157
+ ? OfferUtils.toStruct({ offer: entry.offer })
158
+ : entry.offer,
159
+ })),
160
+ });
161
+ case "ecrecover":
162
+ case "setter":
163
+ case undefined: {
164
+ // Validate the complete standard offer commitment without reassigning groups.
165
+ Ratifier.normalizeRatifierTree({
166
+ tree: { ...descriptor, paddedOffers: descriptor.entries },
167
+ label: descriptor.type === "setter" ? "Setter" : "Ecrecover",
168
+ });
169
+ const normalized = {
170
+ ...descriptor,
171
+ entries: descriptor.entries.map((offer, index) => index < descriptor.offers.length
172
+ ? OfferUtils.toStruct({ offer })
173
+ : offer),
174
+ };
175
+ if (descriptor.type === "ecrecover")
176
+ return new Tree("ecrecover", normalized);
177
+ if (descriptor.type === "setter")
178
+ return new Tree("setter", normalized);
179
+ return new Tree(undefined, normalized);
180
+ }
181
+ default:
182
+ throw new InvalidTreeError("Unsupported tree ratifier route.");
183
+ }
171
184
  }
172
185
  /**
173
186
  * Validates this tree against Midnight mempool API policy.
@@ -176,15 +189,18 @@ export class Tree {
176
189
  * with empty `ratifierData`, then sends the temporary payload to the Midnight
177
190
  * API `POST /mempool/validate` endpoint. Pass `ratification` after signing or
178
191
  * Setter root preparation to validate final payload bytes with real
179
- * `ratifierData`.
192
+ * `ratifierData`. Price/Rate V1 routes always post their real ratifier data
193
+ * because the router decodes it to identify each offer; their
194
+ * `ratification` only asserts the route.
180
195
  *
181
196
  * @param params.chainId - Chain id whose API policy should validate this tree.
182
197
  * @param params.apiUrl - Optional Midnight API URL override used for the validation HTTP request.
183
198
  * @param params.timestamp - Optional ISO-8601 timestamp or `Date` selecting the API policy snapshot.
184
199
  * @param params.fetch - Optional fetch implementation override used for the API call.
185
200
  * @param params.request - Optional fetch options forwarded to the API request.
186
- * @param params.ratification - Optional ratification inputs used to validate final payload bytes with real ratifier data.
201
+ * @param params.ratification - Optional route-matching authorization inputs; Price/Rate routes require their root to have been approved onchain.
187
202
  * @returns Successful API validation result with `valid: true`.
203
+ * @throws {InvalidTreeError} when the ratification route differs from the tree route.
188
204
  * @throws {PayloadDecodeError} when validation payload encoding fails.
189
205
  * @throws {MidnightApiError} when the API returns a non-2xx response.
190
206
  * @throws {InvalidMidnightApiResponseError} when the API returns malformed success JSON.
@@ -219,15 +235,34 @@ export class Tree {
219
235
  * ratifier: "0x0000000000000000000000000000000000004000",
220
236
  * maxUnits: 100n,
221
237
  * });
222
- * await Tree.create([offer]).mempoolValidate({
238
+ * await Tree.create({ type: "ecrecover", entries: [offer] }).mempoolValidate({
223
239
  * chainId: 8453,
224
240
  * });
225
241
  * ```
226
242
  */
227
- mempoolValidate(params) {
243
+ async mempoolValidate(params) {
244
+ const descriptor = this.toDescriptor();
245
+ if (params.ratification != null &&
246
+ (this.type == null
247
+ ? params.ratification.type !== "ecrecover" &&
248
+ params.ratification.type !== "setter"
249
+ : params.ratification.type !== this.type)) {
250
+ throw new InvalidTreeError("Ratification route does not match the tree route.");
251
+ }
252
+ if (this.type === "priceV1" || this.type === "rateV1") {
253
+ return TreeUtils.mempoolValidate({
254
+ ...params,
255
+ tree: descriptor,
256
+ });
257
+ }
228
258
  return TreeUtils.mempoolValidate({
229
259
  ...params,
230
- tree: this,
260
+ ratification: params.ratification,
261
+ tree: Tree.fromDescriptor({
262
+ ...descriptor,
263
+ type: undefined,
264
+ entries: this.paddedOffers,
265
+ }),
231
266
  });
232
267
  }
233
268
  /**
@@ -1,10 +1,11 @@
1
1
  import { type BigIntish } from "@morpho-org/morpho-ts";
2
2
  import { type Account, type Address, type Chain, type Client, type Hash, type Transport } from "viem";
3
3
  import type { MempoolPayloadValidationSuccess, MidnightApiFetch, MidnightApiRequestOptions } from "../api/types.js";
4
- import { type IOffer, type OfferStruct } from "../offers/index.js";
5
- import { type EcrecoverSignatureInput } from "./EcrecoverRatifierUtils.js";
4
+ import { type IOffer, Offer, type OfferStruct } from "../offers/index.js";
5
+ import { type EcrecoverSignatureInput } from "./EcrecoverRatifier.js";
6
6
  import { type GroupInput } from "./GroupUtils.js";
7
- import type { Tree } from "./Tree.js";
7
+ import { Tree } from "./Tree.js";
8
+ import type { AnyTreeSnapshot, EcrecoverTreeCreateRequest, TreeCreateRequest, TreeSnapshot } from "./treeTypes.js";
8
9
  /**
9
10
  * Fully materialized tree descriptor.
10
11
  *
@@ -105,7 +106,9 @@ export interface TreeProof {
105
106
  readonly proof: readonly Hash[];
106
107
  }
107
108
  /**
108
- * Entries accepted by {@link Tree.create}.
109
+ * Legacy untagged entries accepted by {@link Tree.create}.
110
+ *
111
+ * @deprecated Use EcrecoverTreeCreateRequest or SetterTreeCreateRequest with an explicit type and entries.
109
112
  *
110
113
  * Entries are either explicit groups or standalone offers. Explicit groups are
111
114
  * flattened, and standalone offers are hashed with their own group ids.
@@ -146,7 +149,7 @@ export interface TreeProof {
146
149
  * console.log(tree.root);
147
150
  * ```
148
151
  */
149
- export type TreeCreateParams = readonly GroupInput[];
152
+ export type TreeCreateParams = EcrecoverTreeCreateRequest["entries"];
150
153
  /**
151
154
  * Plain creation input or class tree accepted by {@link Tree.from}.
152
155
  *
@@ -291,7 +294,9 @@ export type RatifierTreeInput = TreeLike | TreeInput;
291
294
  * Omit this when validating offer policy before the maker signs or approves a
292
295
  * tree. Provide it when validating the final payload shape, including real
293
296
  * `ratifierData`, after the Ecrecover signature and signer address exist or the
294
- * Setter root is ready for publication.
297
+ * Setter root is ready for publication. Price/Rate V1 routes always post real
298
+ * ratifier data because the router identifies each offer from its decoded
299
+ * leaf fields; their `ratification` is only a route assertion.
295
300
  *
296
301
  * @example
297
302
  * ```ts
@@ -327,14 +332,23 @@ export type TreeMempoolValidateRatification = {
327
332
  } | {
328
333
  /** Setter ratifier route. */
329
334
  readonly type: "setter";
335
+ } | {
336
+ /** PriceRatifierV1 route assertion; validation always encodes its ratifier data. */
337
+ readonly type: "priceV1";
338
+ } | {
339
+ /** RateRatifierV1 route assertion; validation always encodes its ratifier data. */
340
+ readonly type: "rateV1";
330
341
  };
331
342
  /**
332
343
  * Parameters for {@link Tree.mempoolValidate}.
333
344
  *
334
345
  * Use this when an already-created tree should be validated by the Midnight
335
- * API. By default it validates the pre-ratification tree with empty
336
- * `ratifierData`; pass `ratification` to validate the final payload shape with
337
- * real ratifier data.
346
+ * API. Standard routes validate the pre-ratification tree with empty
347
+ * `ratifierData` by default; pass `ratification` to validate the final
348
+ * payload shape with real ratifier data. Price/Rate V1 routes always encode
349
+ * real ratifier data (root, leaf index, proof, allowed taker, and the rate for
350
+ * RateRatifierV1) because the router identifies each offer by decoding it; their
351
+ * `ratification` only asserts the route.
338
352
  *
339
353
  * @example
340
354
  * ```ts
@@ -401,6 +415,27 @@ export interface TreeMempoolValidateParams {
401
415
  * ```
402
416
  */
403
417
  export declare namespace TreeUtils {
418
+ /**
419
+ * Normalizes legacy standard tree entries into grouped offers.
420
+ *
421
+ * Explicit groups are flattened into their member offers, and every
422
+ * standalone offer is normalized as a singleton group using the
423
+ * router-compatible `GroupUtils.hash` group id. Use this when you need the
424
+ * exact offer list a standard Ecrecover/Setter tree commits to.
425
+ *
426
+ * @param entries - Groups or standalone offers in leaf order.
427
+ * @returns Offers in leaf order with their committed group ids.
428
+ * @throws {InvalidOfferGroupError} when an explicit group entry violates group mechanics.
429
+ * @example
430
+ * ```ts
431
+ * import { TreeUtils, type TreeCreateParams } from "@morpho-org/midnight-sdk";
432
+ *
433
+ * function normalize(entries: TreeCreateParams) {
434
+ * return TreeUtils.normalizeEntries(entries);
435
+ * }
436
+ * ```
437
+ */
438
+ function normalizeEntries(entries: TreeCreateParams): readonly Offer[];
404
439
  /**
405
440
  * Validates a tree against Midnight mempool API policy.
406
441
  *
@@ -408,17 +443,20 @@ export declare namespace TreeUtils {
408
443
  * with empty `ratifierData`, then sends the temporary payload to the Midnight
409
444
  * API `POST /mempool/validate` endpoint. Pass `ratification` after signing or
410
445
  * Setter root preparation to validate final payload bytes with real
411
- * `ratifierData`.
446
+ * `ratifierData`. Price/Rate V1 tree snapshots are supported directly and
447
+ * always post the corresponding V1 `ratify` output — there is no empty
448
+ * ratifier-data shape because the router decodes the leaf fields to identify
449
+ * each offer. A V1 `ratification` only asserts the matching route.
412
450
  *
413
451
  * @param params.chainId - Chain id whose API policy should validate the tree.
414
- * @param params.tree - Offer tree to validate.
452
+ * @param params.tree - Offer tree or route-typed tree snapshot to validate. Serialized standard snapshots resume without rewriting committed groups.
415
453
  * @param params.apiUrl - Optional Midnight API URL override used for the validation HTTP request.
416
454
  * @param params.timestamp - Optional ISO-8601 timestamp or `Date` selecting the API policy snapshot.
417
455
  * @param params.fetch - Optional fetch implementation override used for the API call.
418
456
  * @param params.request - Optional fetch options forwarded to the API request.
419
- * @param params.ratification - Optional ratification inputs used to validate final payload bytes with real ratifier data.
457
+ * @param params.ratification - Optional route-matching ratification inputs used to validate final payload bytes with real ratifier data.
420
458
  * @returns Successful API validation result with `valid: true`.
421
- * @throws {InvalidTreeError} when the tree is empty, all padding, or duplicated.
459
+ * @throws {InvalidTreeError} when the tree is empty, all padding, or duplicated, or when `ratification` does not match the tree route.
422
460
  * @throws {InvalidTreeHeightError} when the resulting height is unsupported.
423
461
  * @throws {PayloadDecodeError} when validation payload encoding fails.
424
462
  * @throws {MidnightApiError} when the API returns a non-2xx response.
@@ -461,7 +499,7 @@ export declare namespace TreeUtils {
461
499
  * ```
462
500
  */
463
501
  function mempoolValidate(params: Pick<TreeMempoolValidateParams, "chainId" | "apiUrl" | "timestamp" | "fetch" | "request"> & {
464
- readonly tree: TreeInput;
502
+ readonly tree: TreeInput | TreeSnapshot<"ecrecover"> | TreeSnapshot<"setter"> | TreeSnapshot<"priceV1"> | TreeSnapshot<"rateV1">;
465
503
  readonly ratification?: TreeMempoolValidateRatification;
466
504
  }): Promise<MempoolPayloadValidationSuccess>;
467
505
  /**
@@ -493,10 +531,12 @@ export declare namespace TreeUtils {
493
531
  * `Tree.create` calls this internally and is the simpler API for most
494
532
  * make-side code.
495
533
  *
496
- * @param entries - Groups or standalone offers in leaf order.
534
+ * @param entries - Tagged route and leaf inputs in leaf order.
497
535
  * @returns Tree descriptor.
498
536
  * @throws {InvalidTreeError} when the offer count is empty, all padding, or duplicated.
499
537
  * @throws {InvalidTreeHeightError} when the padded tree exceeds supported ratifier typehashes.
538
+ * @throws {InvalidRateRatifierV1RateError} when a Rate leaf has a negative rate.
539
+ * @throws {InvalidRateRatifierV1TickError} when a Rate leaf offer tick is below `RateRatifierV1.MIN_TICK`.
500
540
  * @example
501
541
  * ```ts
502
542
  * import { Offer, TreeUtils } from "@morpho-org/midnight-sdk";
@@ -527,10 +567,28 @@ export declare namespace TreeUtils {
527
567
  * ratifier: "0x0000000000000000000000000000000000004000",
528
568
  * maxUnits: 100n,
529
569
  * });
530
- * const tree = TreeUtils.buildDescriptor([offer]);
570
+ * const tree = TreeUtils.buildDescriptor({ type: "ecrecover", entries: [offer] });
531
571
  * console.log(tree.root);
532
572
  * ```
533
573
  */
574
+ function buildDescriptor<R extends TreeCreateRequest>(entries: R): Extract<AnyTreeSnapshot, {
575
+ readonly type: R["type"];
576
+ }>;
577
+ /**
578
+ * Builds a descriptor using the legacy standard-offer input.
579
+ * @deprecated Pass a TreeCreateRequest with an explicit ratifier type instead.
580
+ * @param entries - Untagged offers or groups in leaf order.
581
+ * @returns Legacy padded standard-offer descriptor.
582
+ * @throws {InvalidTreeError} When entries are empty, all padding, or duplicated.
583
+ * @throws {InvalidTreeHeightError} When the tree height is unsupported.
584
+ * @example
585
+ * ```ts
586
+ * import { TreeUtils, type TreeCreateParams } from "@morpho-org/midnight-sdk";
587
+ * function legacyDescriptor(entries: TreeCreateParams) {
588
+ * return TreeUtils.buildDescriptor(entries);
589
+ * }
590
+ * ```
591
+ */
534
592
  function buildDescriptor(entries: TreeCreateParams): TreeDescriptor;
535
593
  /**
536
594
  * Builds only the tree root.
@@ -577,18 +635,43 @@ export declare namespace TreeUtils {
577
635
  * console.log(root);
578
636
  * ```
579
637
  */
580
- function buildRoot(entries: TreeCreateParams): `0x${string}`;
638
+ function buildRoot(entries: TreeCreateParams): Hash;
639
+ /**
640
+ * Builds a Merkle root from already-hashed leaves.
641
+ *
642
+ * Use this leaf-hash-agnostic form when a ratifier's leaves are not plain
643
+ * offer hashes, for example `RateRatifierV1.hashLeaf` outputs. The leaf
644
+ * count must be a power of two; pad non-power-of-two lists beforehand.
645
+ *
646
+ * @param leaves - Leaf hashes in leaf order; length must be a power of two.
647
+ * @returns Merkle root and tree height.
648
+ * @throws {InvalidTreeError} when the leaf count is not a power of two.
649
+ * @throws {InvalidTreeHeightError} when the tree exceeds supported ratifier typehashes.
650
+ * @example
651
+ * ```ts
652
+ * import { TreeUtils } from "@morpho-org/midnight-sdk";
653
+ * import { zeroHash } from "viem";
654
+ *
655
+ * const { root, height } = TreeUtils.buildRootFromLeaves([zeroHash]);
656
+ * console.log(height);
657
+ * ```
658
+ */
659
+ function buildRootFromLeaves(leaves: readonly Hash[]): {
660
+ readonly root: Hash;
661
+ readonly height: number;
662
+ };
581
663
  /**
582
664
  * Builds a Merkle proof for one offer.
583
665
  *
584
- * Use after tree construction when a custom ratifier needs one proof. The
585
- * built-in Ecrecover and Setter helpers call this while generating
586
- * `ratifierData`.
666
+ * Use after tree construction when a custom ratifier needs one proof; the
667
+ * built-in ratify helpers use {@link TreeUtils.buildProofs} to generate
668
+ * `ratifierData` in bulk.
587
669
  *
588
670
  * @param params.tree - Tree-like data with the root and leaves that contain the leaf.
589
671
  * @param params.leafIndex - Leaf index to prove.
590
672
  * @returns Proof descriptor.
591
- * @throws {InvalidTreeError} when leaf index is out of range.
673
+ * @throws {InvalidTreeError} when leaf index is out of range or when the leaf count is not a power of two.
674
+ * @throws {InvalidTreeHeightError} when the tree height (log2 of the leaf count) exceeds 20.
592
675
  * @example
593
676
  * ```ts
594
677
  * import { Offer, Tree, TreeUtils } from "@morpho-org/midnight-sdk";
@@ -630,6 +713,60 @@ export declare namespace TreeUtils {
630
713
  readonly tree: Pick<TreeLike, "leaves" | "root">;
631
714
  readonly leafIndex: BigIntish;
632
715
  }): TreeProof;
716
+ /**
717
+ * Builds Merkle proofs for a range of leaves from one tree-layer pass.
718
+ *
719
+ * Use instead of calling {@link TreeUtils.buildProof} per leaf when many
720
+ * proofs are needed at once, for example inside `ratify` helpers: the
721
+ * parent layers are computed once instead of once per leaf. Each returned
722
+ * proof is byte-identical to `buildProof` on the same tree and leaf index.
723
+ *
724
+ * @param params.tree - Tree leaves and root to prove against.
725
+ * @param params.count - Number of leading leaf indices to prove; defaults to `tree.leaves.length`.
726
+ * @returns Frozen proofs for leaf indices `0` through `count - 1`.
727
+ * @throws {InvalidTreeError} when `count` is negative, non-integer, or exceeds the leaf count, or when the leaf count is not a power of two.
728
+ * @throws {InvalidTreeHeightError} when the tree height (log2 of the leaf count) exceeds 20.
729
+ * @example
730
+ * ```ts
731
+ * import { Offer, Tree, TreeUtils } from "@morpho-org/midnight-sdk";
732
+ * import { zeroAddress } from "viem";
733
+ *
734
+ * const offer = Offer.create({
735
+ * market: {
736
+ * chainId: 8453,
737
+ * midnight: "0x0000000000000000000000000000000000001000",
738
+ * loanToken: "0x0000000000000000000000000000000000006000",
739
+ * collateralParams: [
740
+ * {
741
+ * token: "0x0000000000000000000000000000000000007000",
742
+ * lltv: 770000000000000000n,
743
+ * liquidationCursor: 250000000000000000n,
744
+ * oracle: "0x0000000000000000000000000000000000008000",
745
+ * },
746
+ * ],
747
+ * maturity: 54_000n,
748
+ * rcfThreshold: 0n,
749
+ * enterGate: zeroAddress,
750
+ * liquidatorGate: zeroAddress,
751
+ * },
752
+ * buy: true,
753
+ * maker: "0x0000000000000000000000000000000000009000",
754
+ * tick: 5_000n,
755
+ * expiry: 3_600n,
756
+ * ratifier: "0x0000000000000000000000000000000000004000",
757
+ * maxUnits: 100n,
758
+ * });
759
+ * const proofs = TreeUtils.buildProofs({
760
+ * tree: Tree.create([offer]),
761
+ * count: 1,
762
+ * });
763
+ * console.log(proofs[0]!.proof.length);
764
+ * ```
765
+ */
766
+ function buildProofs(params: {
767
+ readonly tree: Pick<TreeLike, "leaves" | "root">;
768
+ readonly count?: number;
769
+ }): readonly TreeProof[];
633
770
  /**
634
771
  * Verifies a local Merkle proof against a root.
635
772
  *
@@ -689,4 +826,37 @@ export declare namespace TreeUtils {
689
826
  readonly leafIndex: BigIntish;
690
827
  readonly proof: readonly Hash[];
691
828
  }): boolean;
829
+ /**
830
+ * Verifies a local Merkle proof for an already-hashed leaf against a root.
831
+ *
832
+ * Use this leaf-hash-agnostic form when a ratifier's leaves are not plain
833
+ * offer hashes, for example `RateRatifierV1.hashLeaf` outputs. The leaf
834
+ * index determines each sibling's left/right position, matching
835
+ * `HashLib.isLeaf` onchain.
836
+ *
837
+ * @param params.leaf - Leaf hash that starts proof reconstruction.
838
+ * @param params.root - Expected Merkle root.
839
+ * @param params.leafIndex - Leaf index proven by `params.proof`.
840
+ * @param params.proof - Merkle proof siblings for the leaf.
841
+ * @returns Whether the proof reconstructs the supplied root.
842
+ * @example
843
+ * ```ts
844
+ * import { TreeUtils } from "@morpho-org/midnight-sdk";
845
+ * import { zeroHash } from "viem";
846
+ *
847
+ * const valid = TreeUtils.verifyLeafProof({
848
+ * leaf: zeroHash,
849
+ * root: zeroHash,
850
+ * leafIndex: 0n,
851
+ * proof: [],
852
+ * });
853
+ * console.log(valid);
854
+ * ```
855
+ */
856
+ function verifyLeafProof(params: {
857
+ readonly leaf: Hash;
858
+ readonly root: Hash;
859
+ readonly leafIndex: BigIntish;
860
+ readonly proof: readonly Hash[];
861
+ }): boolean;
692
862
  }