@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.
- package/MIGRATION-v1-to-v2.md +6 -0
- package/README.md +63 -61
- package/lib/cjs/abis.d.ts +1962 -759
- package/lib/cjs/abis.js +2021 -995
- package/lib/cjs/api/helpers.d.ts +1 -17
- package/lib/cjs/api/helpers.js +0 -4
- package/lib/cjs/api/types.d.ts +5 -4
- package/lib/cjs/api/version.generated.d.ts +1 -1
- package/lib/cjs/api/version.generated.js +2 -2
- package/lib/cjs/bundlesV2.d.ts +15 -0
- package/lib/cjs/bundlesV2.js +2 -0
- package/lib/cjs/constants.d.ts +34 -0
- package/lib/cjs/constants.js +35 -1
- package/lib/cjs/errors.d.ts +107 -0
- package/lib/cjs/errors.js +139 -1
- package/lib/cjs/fetch/Ratifier.d.ts +4 -3
- package/lib/cjs/fetch/Ratifier.js +6 -4
- package/lib/cjs/index.d.ts +8 -0
- package/lib/cjs/index.js +16 -1
- package/lib/cjs/offers/Offer.d.ts +10 -0
- package/lib/cjs/offers/Offer.js +15 -1
- package/lib/cjs/offers/OfferUtils.d.ts +3 -0
- package/lib/cjs/offers/OfferUtils.js +3 -0
- package/lib/cjs/signatures/{EcrecoverRatifierUtils.d.ts → EcrecoverRatifier.d.ts} +118 -2
- package/lib/cjs/signatures/{EcrecoverRatifierUtils.js → EcrecoverRatifier.js} +146 -9
- package/lib/cjs/signatures/GroupUtils.d.ts +55 -2
- package/lib/cjs/signatures/GroupUtils.js +64 -5
- package/lib/cjs/signatures/Payload.d.ts +3 -3
- package/lib/cjs/signatures/PriceRatifierV1.d.ts +472 -0
- package/lib/cjs/signatures/PriceRatifierV1.js +521 -0
- package/lib/cjs/signatures/RateRatifierV1.d.ts +581 -0
- package/lib/cjs/signatures/RateRatifierV1.js +696 -0
- package/lib/{esm/signatures/RatifierUtils.d.ts → cjs/signatures/Ratifier.d.ts} +14 -12
- package/lib/cjs/signatures/{RatifierUtils.js → Ratifier.js} +29 -29
- package/lib/cjs/signatures/{SetterRatifierUtils.d.ts → SetterRatifier.d.ts} +50 -3
- package/lib/cjs/signatures/{SetterRatifierUtils.js → SetterRatifier.js} +65 -7
- package/lib/cjs/signatures/Tree.d.ts +94 -17
- package/lib/cjs/signatures/Tree.js +146 -111
- package/lib/cjs/signatures/TreeUtils.d.ts +191 -21
- package/lib/cjs/signatures/TreeUtils.js +320 -134
- package/lib/cjs/signatures/index.d.ts +7 -3
- package/lib/cjs/signatures/index.js +5 -3
- package/lib/cjs/signatures/ratifierRequests.d.ts +55 -0
- package/lib/cjs/signatures/ratifierRequests.js +2 -0
- package/lib/cjs/signatures/ratifierV1Internal.d.ts +57 -0
- package/lib/cjs/signatures/ratifierV1Internal.js +183 -0
- package/lib/cjs/signatures/standardRatifierInternal.d.ts +4 -0
- package/lib/cjs/signatures/standardRatifierInternal.js +12 -0
- package/lib/cjs/signatures/treeMathInternal.d.ts +4 -0
- package/lib/cjs/signatures/treeMathInternal.js +12 -0
- package/lib/cjs/signatures/treeTypes.d.ts +109 -0
- package/lib/cjs/signatures/treeTypes.js +2 -0
- package/lib/esm/abis.d.ts +1962 -759
- package/lib/esm/abis.js +2020 -994
- package/lib/esm/api/helpers.d.ts +1 -17
- package/lib/esm/api/helpers.js +4 -4
- package/lib/esm/api/types.d.ts +5 -4
- package/lib/esm/api/version.generated.d.ts +1 -1
- package/lib/esm/api/version.generated.js +2 -2
- package/lib/esm/bundlesV2.d.ts +15 -0
- package/lib/esm/bundlesV2.js +1 -0
- package/lib/esm/constants.d.ts +34 -0
- package/lib/esm/constants.js +34 -0
- package/lib/esm/errors.d.ts +107 -0
- package/lib/esm/errors.js +132 -0
- package/lib/esm/fetch/Ratifier.d.ts +4 -3
- package/lib/esm/fetch/Ratifier.js +6 -4
- package/lib/esm/index.d.ts +8 -0
- package/lib/esm/index.js +5 -0
- package/lib/esm/offers/Offer.d.ts +10 -0
- package/lib/esm/offers/Offer.js +15 -1
- package/lib/esm/offers/OfferUtils.d.ts +3 -0
- package/lib/esm/offers/OfferUtils.js +3 -0
- package/lib/esm/signatures/{EcrecoverRatifierUtils.d.ts → EcrecoverRatifier.d.ts} +118 -2
- package/lib/esm/signatures/{EcrecoverRatifierUtils.js → EcrecoverRatifier.js} +146 -9
- package/lib/esm/signatures/GroupUtils.d.ts +55 -2
- package/lib/esm/signatures/GroupUtils.js +64 -5
- package/lib/esm/signatures/Payload.d.ts +3 -3
- package/lib/esm/signatures/PriceRatifierV1.d.ts +472 -0
- package/lib/esm/signatures/PriceRatifierV1.js +520 -0
- package/lib/esm/signatures/RateRatifierV1.d.ts +581 -0
- package/lib/esm/signatures/RateRatifierV1.js +695 -0
- package/lib/{cjs/signatures/RatifierUtils.d.ts → esm/signatures/Ratifier.d.ts} +14 -12
- package/lib/esm/signatures/{RatifierUtils.js → Ratifier.js} +31 -29
- package/lib/esm/signatures/{SetterRatifierUtils.d.ts → SetterRatifier.d.ts} +50 -3
- package/lib/esm/signatures/{SetterRatifierUtils.js → SetterRatifier.js} +65 -7
- package/lib/esm/signatures/Tree.d.ts +94 -17
- package/lib/esm/signatures/Tree.js +147 -112
- package/lib/esm/signatures/TreeUtils.d.ts +191 -21
- package/lib/esm/signatures/TreeUtils.js +318 -132
- package/lib/esm/signatures/index.d.ts +7 -3
- package/lib/esm/signatures/index.js +5 -3
- package/lib/esm/signatures/ratifierRequests.d.ts +55 -0
- package/lib/esm/signatures/ratifierRequests.js +1 -0
- package/lib/esm/signatures/ratifierV1Internal.d.ts +57 -0
- package/lib/esm/signatures/ratifierV1Internal.js +176 -0
- package/lib/esm/signatures/standardRatifierInternal.d.ts +4 -0
- package/lib/esm/signatures/standardRatifierInternal.js +9 -0
- package/lib/esm/signatures/treeMathInternal.d.ts +4 -0
- package/lib/esm/signatures/treeMathInternal.js +8 -0
- package/lib/esm/signatures/treeTypes.d.ts +109 -0
- package/lib/esm/signatures/treeTypes.js +1 -0
- package/package.json +9 -8
|
@@ -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 "./
|
|
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 {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
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.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
339
|
-
* built-in
|
|
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
|
-
|
|
389
|
-
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
458
|
-
|
|
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.
|
|
659
|
+
TreeUtils.verifyLeafProof = verifyLeafProof;
|
|
474
660
|
})(TreeUtils || (TreeUtils = {}));
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
export * from "./
|
|
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 "./
|
|
7
|
-
export * from "./
|
|
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 "./
|
|
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 "./
|
|
7
|
-
export * from "./
|
|
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 {};
|