@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.
- package/lib/cjs/api/MidnightApi.js +67 -3
- package/lib/cjs/api/helpers.d.ts +11 -1
- package/lib/cjs/api/helpers.js +48 -1
- package/lib/cjs/errors.d.ts +15 -0
- package/lib/cjs/errors.js +21 -1
- package/lib/cjs/fetch/Market.js +37 -2
- package/lib/cjs/fetch/utils.d.ts +1 -1
- package/lib/cjs/fetch/utils.js +9 -2
- package/lib/cjs/offers/Offer.d.ts +1 -1
- package/lib/cjs/offers/OfferUtils.d.ts +5 -5
- package/lib/cjs/offers/OfferUtils.js +26 -13
- package/lib/cjs/signatures/EcrecoverRatifierUtils.d.ts +118 -0
- package/lib/cjs/signatures/EcrecoverRatifierUtils.js +149 -1
- package/lib/cjs/signatures/Group.js +5 -3
- package/lib/cjs/signatures/GroupUtils.d.ts +48 -0
- package/lib/cjs/signatures/GroupUtils.js +51 -0
- package/lib/cjs/signatures/RatifierUtils.js +14 -3
- package/lib/cjs/signatures/SetterRatifierUtils.d.ts +31 -0
- package/lib/cjs/signatures/SetterRatifierUtils.js +38 -0
- package/lib/cjs/signatures/Tree.js +10 -2
- package/lib/cjs/signatures/TreeUtils.d.ts +4 -1
- package/lib/cjs/signatures/TreeUtils.js +19 -6
- package/lib/cjs/signatures/eip712.d.ts +22 -0
- package/lib/cjs/signatures/eip712.js +26 -0
- package/lib/cjs/signatures/index.d.ts +1 -0
- package/lib/cjs/signatures/index.js +1 -0
- package/lib/esm/api/MidnightApi.js +68 -4
- package/lib/esm/api/helpers.d.ts +11 -1
- package/lib/esm/api/helpers.js +48 -2
- package/lib/esm/errors.d.ts +15 -0
- package/lib/esm/errors.js +19 -0
- package/lib/esm/fetch/Market.js +38 -3
- package/lib/esm/fetch/utils.d.ts +1 -1
- package/lib/esm/fetch/utils.js +9 -2
- package/lib/esm/offers/Offer.d.ts +1 -1
- package/lib/esm/offers/OfferUtils.d.ts +5 -5
- package/lib/esm/offers/OfferUtils.js +28 -15
- package/lib/esm/signatures/EcrecoverRatifierUtils.d.ts +118 -0
- package/lib/esm/signatures/EcrecoverRatifierUtils.js +151 -3
- package/lib/esm/signatures/Group.js +5 -3
- package/lib/esm/signatures/GroupUtils.d.ts +48 -0
- package/lib/esm/signatures/GroupUtils.js +51 -0
- package/lib/esm/signatures/RatifierUtils.js +15 -4
- package/lib/esm/signatures/SetterRatifierUtils.d.ts +31 -0
- package/lib/esm/signatures/SetterRatifierUtils.js +38 -0
- package/lib/esm/signatures/Tree.js +11 -3
- package/lib/esm/signatures/TreeUtils.d.ts +4 -1
- package/lib/esm/signatures/TreeUtils.js +19 -6
- package/lib/esm/signatures/eip712.d.ts +22 -0
- package/lib/esm/signatures/eip712.js +22 -0
- package/lib/esm/signatures/index.d.ts +1 -0
- package/lib/esm/signatures/index.js +1 -0
- package/package.json +8 -7
- package/src/index.ts +0 -19
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { deepFreeze } from "@morpho-org/morpho-ts";
|
|
2
|
-
import {
|
|
2
|
+
import { decodeAbiParameters, encodeAbiParameters, keccak256, parseSignature, recoverAddress, serializeSignature, verifyHash, } from "viem";
|
|
3
3
|
import { signTypedData } from "viem/actions";
|
|
4
4
|
import { EIP712_DOMAIN_TYPEHASH } from "../constants.js";
|
|
5
|
-
import { ChainIdMismatchError, InvalidTreeError, InvalidTreeHeightError, InvalidTypedDataSignatureError, } from "../errors.js";
|
|
5
|
+
import { ChainIdMismatchError, InvalidEcrecoverSignatureVError, InvalidTreeError, InvalidTreeHeightError, InvalidTypedDataSignatureError, } from "../errors.js";
|
|
6
6
|
import { MarketParams } from "../market/index.js";
|
|
7
|
+
import { Offer } from "../offers/index.js";
|
|
8
|
+
import { eip712Digest } from "./eip712.js";
|
|
7
9
|
import { RatifierUtils } from "./RatifierUtils.js";
|
|
8
10
|
import { TreeUtils, } from "./TreeUtils.js";
|
|
9
11
|
const treeTypeHashes = [
|
|
@@ -99,6 +101,13 @@ const buildTreeValue = (offers) => {
|
|
|
99
101
|
buildTreeValue(offers.slice(mid)),
|
|
100
102
|
];
|
|
101
103
|
};
|
|
104
|
+
function toCanonicalYParity(v) {
|
|
105
|
+
if (v === 27)
|
|
106
|
+
return 0;
|
|
107
|
+
if (v === 28)
|
|
108
|
+
return 1;
|
|
109
|
+
throw new InvalidEcrecoverSignatureVError(v);
|
|
110
|
+
}
|
|
102
111
|
/**
|
|
103
112
|
* EcrecoverRatifier-specific pure utilities.
|
|
104
113
|
*
|
|
@@ -276,9 +285,148 @@ export var EcrecoverRatifierUtils;
|
|
|
276
285
|
treeTypeHash(tree.height),
|
|
277
286
|
tree.root,
|
|
278
287
|
]));
|
|
279
|
-
return
|
|
288
|
+
return eip712Digest(domainSeparator, structHash);
|
|
280
289
|
}
|
|
281
290
|
EcrecoverRatifierUtils.digest = digest;
|
|
291
|
+
/**
|
|
292
|
+
* Builds the EcrecoverRatifier digest from a payload offer, root, and proof height.
|
|
293
|
+
*
|
|
294
|
+
* Use this on the take-side or in indexers after decoding ratifier data, when
|
|
295
|
+
* the original full tree is not available but the payload item includes the
|
|
296
|
+
* offer and proof metadata needed to reconstruct the signed digest. Pass the
|
|
297
|
+
* observed log or execution chain id so cross-chain-spoofed payloads are
|
|
298
|
+
* rejected before signature recovery.
|
|
299
|
+
*
|
|
300
|
+
* @param params.chainId - Observed EIP-155 chain id from the log or execution context.
|
|
301
|
+
* @param params.offer - Offer carried by the payload item.
|
|
302
|
+
* @param params.root - Merkle root embedded in the ratifier data.
|
|
303
|
+
* @param params.proofLength - Number of sibling hashes in the Merkle proof.
|
|
304
|
+
* @returns EIP-712 digest signed by the maker or authorized signer.
|
|
305
|
+
* @throws {ChainIdMismatchError} when `params.chainId` does not match `offer.market.chainId`.
|
|
306
|
+
* @throws {InvalidTreeHeightError} when `proofLength` exceeds 20.
|
|
307
|
+
* @example
|
|
308
|
+
* ```ts
|
|
309
|
+
* import { EcrecoverRatifierUtils } from "@morpho-org/midnight-sdk";
|
|
310
|
+
* import { zeroHash } from "viem";
|
|
311
|
+
*
|
|
312
|
+
* const digest = EcrecoverRatifierUtils.digestForRoot({
|
|
313
|
+
* chainId: 8453n,
|
|
314
|
+
* offer,
|
|
315
|
+
* root: zeroHash,
|
|
316
|
+
* proofLength: 0,
|
|
317
|
+
* });
|
|
318
|
+
* console.log(digest);
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
function digestForRoot(params) {
|
|
322
|
+
const offer = Offer.from(params.offer);
|
|
323
|
+
const market = MarketParams.from(offer.market);
|
|
324
|
+
const chainId = BigInt(params.chainId);
|
|
325
|
+
if (chainId !== market.chainId) {
|
|
326
|
+
throw new ChainIdMismatchError(Number(chainId), market.chainId);
|
|
327
|
+
}
|
|
328
|
+
const domainSeparator = keccak256(encodeAbiParameters(domainSeparatorAbi, [
|
|
329
|
+
EIP712_DOMAIN_TYPEHASH,
|
|
330
|
+
chainId,
|
|
331
|
+
offer.ratifier,
|
|
332
|
+
]));
|
|
333
|
+
const structHash = keccak256(encodeAbiParameters(treeStructHashAbi, [
|
|
334
|
+
treeTypeHash(params.proofLength),
|
|
335
|
+
params.root,
|
|
336
|
+
]));
|
|
337
|
+
return eip712Digest(domainSeparator, structHash);
|
|
338
|
+
}
|
|
339
|
+
EcrecoverRatifierUtils.digestForRoot = digestForRoot;
|
|
340
|
+
/**
|
|
341
|
+
* Builds the EcrecoverRatifier digest from one payload item's encoded ratifier data.
|
|
342
|
+
*
|
|
343
|
+
* @param params.chainId - Observed EIP-155 chain id from the log or execution context.
|
|
344
|
+
* @param params.offer - Offer carried by the payload item.
|
|
345
|
+
* @param params.ratifierData - ABI-encoded Ecrecover ratifier data.
|
|
346
|
+
* @returns EIP-712 digest signed by the maker or authorized signer.
|
|
347
|
+
* @throws {ChainIdMismatchError} when `params.chainId` does not match `offer.market.chainId`.
|
|
348
|
+
* @throws {InvalidTreeHeightError} when the proof height exceeds 20.
|
|
349
|
+
* @example
|
|
350
|
+
* ```ts
|
|
351
|
+
* import { EcrecoverRatifierUtils } from "@morpho-org/midnight-sdk";
|
|
352
|
+
*
|
|
353
|
+
* const digest = EcrecoverRatifierUtils.digestRatifierData({
|
|
354
|
+
* chainId: 8453n,
|
|
355
|
+
* offer,
|
|
356
|
+
* ratifierData,
|
|
357
|
+
* });
|
|
358
|
+
* console.log(digest);
|
|
359
|
+
* ```
|
|
360
|
+
*/
|
|
361
|
+
function digestRatifierData(params) {
|
|
362
|
+
const decoded = decodeRatifierData(params.ratifierData);
|
|
363
|
+
return digestForRoot({
|
|
364
|
+
chainId: params.chainId,
|
|
365
|
+
offer: params.offer,
|
|
366
|
+
root: decoded.root,
|
|
367
|
+
proofLength: decoded.proof.length,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
EcrecoverRatifierUtils.digestRatifierData = digestRatifierData;
|
|
371
|
+
/**
|
|
372
|
+
* Verifies an Ecrecover ratifier-data proof and recovers its signer.
|
|
373
|
+
*
|
|
374
|
+
* This helper intentionally does not check `Midnight.isAuthorized` or
|
|
375
|
+
* `EcrecoverRatifier.isRootCanceled` state. Consumers must query both at the
|
|
376
|
+
* observed block before treating a recovered item as executable: the returned
|
|
377
|
+
* signer must be the maker or authorized by the maker, the offer ratifier must
|
|
378
|
+
* be authorized by the maker, and `isRootCanceled(maker, root)` must be false.
|
|
379
|
+
*
|
|
380
|
+
* @param params.chainId - Observed EIP-155 chain id from the log or execution context.
|
|
381
|
+
* @param params.offer - Offer carried by the payload item.
|
|
382
|
+
* @param params.ratifierData - ABI-encoded Ecrecover ratifier data.
|
|
383
|
+
* @returns Decoded ratifier data plus recovered signer.
|
|
384
|
+
* @throws {ChainIdMismatchError} when `params.chainId` does not match `offer.market.chainId`.
|
|
385
|
+
* @throws {InvalidEcrecoverSignatureVError} when the decoded signature `v` is not 27 or 28.
|
|
386
|
+
* @throws {InvalidTreeError} when the proof does not include `offer` in `root`.
|
|
387
|
+
* @throws {InvalidTreeHeightError} when the proof height exceeds 20.
|
|
388
|
+
* @example
|
|
389
|
+
* ```ts
|
|
390
|
+
* import { EcrecoverRatifierUtils } from "@morpho-org/midnight-sdk";
|
|
391
|
+
*
|
|
392
|
+
* const verified = await EcrecoverRatifierUtils.verifyRatifierData({
|
|
393
|
+
* chainId: 8453n,
|
|
394
|
+
* offer,
|
|
395
|
+
* ratifierData,
|
|
396
|
+
* });
|
|
397
|
+
* console.log(verified.signer);
|
|
398
|
+
* ```
|
|
399
|
+
*/
|
|
400
|
+
async function verifyRatifierData(params) {
|
|
401
|
+
const offer = Offer.from(params.offer);
|
|
402
|
+
const decoded = decodeRatifierData(params.ratifierData);
|
|
403
|
+
if (!TreeUtils.verifyProof({
|
|
404
|
+
offer,
|
|
405
|
+
root: decoded.root,
|
|
406
|
+
leafIndex: decoded.leafIndex,
|
|
407
|
+
proof: decoded.proof,
|
|
408
|
+
})) {
|
|
409
|
+
throw new InvalidTreeError("Ratifier data proof does not include offer.");
|
|
410
|
+
}
|
|
411
|
+
const signer = await recoverAddress({
|
|
412
|
+
hash: digestForRoot({
|
|
413
|
+
chainId: params.chainId,
|
|
414
|
+
offer,
|
|
415
|
+
root: decoded.root,
|
|
416
|
+
proofLength: decoded.proof.length,
|
|
417
|
+
}),
|
|
418
|
+
signature: serializeSignature({
|
|
419
|
+
r: decoded.signature.r,
|
|
420
|
+
s: decoded.signature.s,
|
|
421
|
+
yParity: toCanonicalYParity(decoded.signature.v),
|
|
422
|
+
}),
|
|
423
|
+
});
|
|
424
|
+
return deepFreeze({
|
|
425
|
+
...decoded,
|
|
426
|
+
signer,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
EcrecoverRatifierUtils.verifyRatifierData = verifyRatifierData;
|
|
282
430
|
/**
|
|
283
431
|
* Signs EcrecoverRatifier typed data through a viem client.
|
|
284
432
|
*
|
|
@@ -141,9 +141,11 @@ export class Group {
|
|
|
141
141
|
* ```
|
|
142
142
|
*/
|
|
143
143
|
static from(entry) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
if (entry instanceof Group)
|
|
145
|
+
return entry;
|
|
146
|
+
if (GroupUtils.isGroupInput(entry))
|
|
147
|
+
return new Group(entry.offers);
|
|
148
|
+
return new Group([entry]);
|
|
147
149
|
}
|
|
148
150
|
/**
|
|
149
151
|
* 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
|
*
|
|
@@ -19,6 +19,57 @@ import { OfferUtils } from "../offers/index.js";
|
|
|
19
19
|
*/
|
|
20
20
|
export var GroupUtils;
|
|
21
21
|
(function (GroupUtils) {
|
|
22
|
+
/**
|
|
23
|
+
* Returns whether an entry is an explicit group rather than a standalone offer.
|
|
24
|
+
*
|
|
25
|
+
* Plain offer objects can also contain incidental `offers` properties, so the
|
|
26
|
+
* discriminant must reject values that still carry the offer `market` field.
|
|
27
|
+
*
|
|
28
|
+
* @param entry - Group or offer input to inspect.
|
|
29
|
+
* @returns Whether `entry` is an explicit group input.
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { GroupUtils, Offer } from "@morpho-org/midnight-sdk";
|
|
33
|
+
*
|
|
34
|
+
* const offer = Offer.from({
|
|
35
|
+
* market: {
|
|
36
|
+
* chainId: 8453,
|
|
37
|
+
* midnight: "0x0000000000000000000000000000000000001000",
|
|
38
|
+
* loanToken: "0x0000000000000000000000000000000000006000",
|
|
39
|
+
* collateralParams: [
|
|
40
|
+
* {
|
|
41
|
+
* token: "0x0000000000000000000000000000000000007000",
|
|
42
|
+
* lltv: 770000000000000000n,
|
|
43
|
+
* liquidationCursor: 250000000000000000n,
|
|
44
|
+
* oracle: "0x0000000000000000000000000000000000008000",
|
|
45
|
+
* },
|
|
46
|
+
* ],
|
|
47
|
+
* maturity: 54_000n,
|
|
48
|
+
* rcfThreshold: 0n,
|
|
49
|
+
* enterGate: "0x0000000000000000000000000000000000000000",
|
|
50
|
+
* liquidatorGate: "0x0000000000000000000000000000000000000000",
|
|
51
|
+
* },
|
|
52
|
+
* buy: true,
|
|
53
|
+
* maker: "0x0000000000000000000000000000000000009000",
|
|
54
|
+
* start: 0n,
|
|
55
|
+
* expiry: 3_600n,
|
|
56
|
+
* tick: 5_000n,
|
|
57
|
+
* callback: "0x0000000000000000000000000000000000000000",
|
|
58
|
+
* callbackData: "0x",
|
|
59
|
+
* receiverIfMakerIsSeller: "0x0000000000000000000000000000000000000000",
|
|
60
|
+
* ratifier: "0x0000000000000000000000000000000000004000",
|
|
61
|
+
* reduceOnly: false,
|
|
62
|
+
* maxUnits: 100n,
|
|
63
|
+
* maxAssets: 0n,
|
|
64
|
+
* continuousFeeCap: 0n,
|
|
65
|
+
* });
|
|
66
|
+
* console.log(GroupUtils.isGroupInput(offer));
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
function isGroupInput(entry) {
|
|
70
|
+
return "offers" in entry && !("market" in entry);
|
|
71
|
+
}
|
|
72
|
+
GroupUtils.isGroupInput = isGroupInput;
|
|
22
73
|
/**
|
|
23
74
|
* Derives the deterministic content-addressed id for a group of offers.
|
|
24
75
|
*
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { InvalidTreeError } from "../errors.js";
|
|
2
|
-
import { Offer } from "../offers/index.js";
|
|
2
|
+
import { Offer, OfferUtils } from "../offers/index.js";
|
|
3
3
|
import { Group } from "./Group.js";
|
|
4
|
+
import { GroupUtils } from "./GroupUtils.js";
|
|
4
5
|
import { TreeUtils } from "./TreeUtils.js";
|
|
5
6
|
function isTreeLike(tree) {
|
|
6
7
|
return (!Array.isArray(tree) &&
|
|
@@ -11,10 +12,20 @@ function isTreeLike(tree) {
|
|
|
11
12
|
"height" in tree);
|
|
12
13
|
}
|
|
13
14
|
function normalizeTree(tree) {
|
|
14
|
-
if (isTreeLike(tree))
|
|
15
|
+
if (isTreeLike(tree)) {
|
|
15
16
|
return tree;
|
|
16
|
-
|
|
17
|
-
const
|
|
17
|
+
}
|
|
18
|
+
const offers = tree.flatMap((entry) => GroupUtils.isGroupInput(entry)
|
|
19
|
+
? Group.from(entry).offers
|
|
20
|
+
: [
|
|
21
|
+
new Offer({
|
|
22
|
+
...Offer.from(entry),
|
|
23
|
+
group: OfferUtils.groupHash(entry),
|
|
24
|
+
}),
|
|
25
|
+
]);
|
|
26
|
+
const descriptor = TreeUtils.buildDescriptor(offers, {
|
|
27
|
+
preserveStandaloneGroups: true,
|
|
28
|
+
});
|
|
18
29
|
return {
|
|
19
30
|
offers,
|
|
20
31
|
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
|
*
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { deepFreeze } from "@morpho-org/morpho-ts";
|
|
2
2
|
import { decodeAbiParameters, encodeAbiParameters, } from "viem";
|
|
3
|
+
import { InvalidTreeError } from "../errors.js";
|
|
4
|
+
import { Offer } from "../offers/index.js";
|
|
3
5
|
import { RatifierUtils } from "./RatifierUtils.js";
|
|
4
6
|
import { TreeUtils, } from "./TreeUtils.js";
|
|
5
7
|
const setterRatifierDataAbi = [
|
|
@@ -85,6 +87,42 @@ export var SetterRatifierUtils;
|
|
|
85
87
|
return deepFreeze({ root, leafIndex, proof: [...proof] });
|
|
86
88
|
}
|
|
87
89
|
SetterRatifierUtils.decodeRatifierData = decodeRatifierData;
|
|
90
|
+
/**
|
|
91
|
+
* Verifies that Setter ratifier data proves one payload offer belongs to its root.
|
|
92
|
+
*
|
|
93
|
+
* This helper intentionally does not check `SetterRatifier.isRootRatified` or
|
|
94
|
+
* `Midnight.isAuthorized` state. Consumers can query those values at their
|
|
95
|
+
* own block context after local proof verification.
|
|
96
|
+
*
|
|
97
|
+
* @param params.offer - Offer carried by the payload item.
|
|
98
|
+
* @param params.ratifierData - ABI-encoded Setter ratifier data.
|
|
99
|
+
* @returns Decoded Setter ratifier data after proof verification.
|
|
100
|
+
* @throws {InvalidTreeError} when the proof does not include `offer` in `root`.
|
|
101
|
+
* @example
|
|
102
|
+
* ```ts
|
|
103
|
+
* import { SetterRatifierUtils } from "@morpho-org/midnight-sdk";
|
|
104
|
+
*
|
|
105
|
+
* const decoded = SetterRatifierUtils.verifyRatifierData({
|
|
106
|
+
* offer,
|
|
107
|
+
* ratifierData,
|
|
108
|
+
* });
|
|
109
|
+
* console.log(decoded.root);
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
function verifyRatifierData(params) {
|
|
113
|
+
const offer = Offer.from(params.offer);
|
|
114
|
+
const decoded = decodeRatifierData(params.ratifierData);
|
|
115
|
+
if (!TreeUtils.verifyProof({
|
|
116
|
+
offer,
|
|
117
|
+
root: decoded.root,
|
|
118
|
+
leafIndex: decoded.leafIndex,
|
|
119
|
+
proof: decoded.proof,
|
|
120
|
+
})) {
|
|
121
|
+
throw new InvalidTreeError("Ratifier data proof does not include offer.");
|
|
122
|
+
}
|
|
123
|
+
return decoded;
|
|
124
|
+
}
|
|
125
|
+
SetterRatifierUtils.verifyRatifierData = verifyRatifierData;
|
|
88
126
|
/**
|
|
89
127
|
* Builds one ratifier-data value for a tree leaf.
|
|
90
128
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Offer } from "../offers/index.js";
|
|
1
|
+
import { Offer, OfferUtils, } from "../offers/index.js";
|
|
2
2
|
import { Group } from "./Group.js";
|
|
3
|
+
import { GroupUtils } from "./GroupUtils.js";
|
|
3
4
|
import { TreeUtils, } from "./TreeUtils.js";
|
|
4
5
|
/**
|
|
5
6
|
* Maker-side Merkle tree used by Midnight ratifiers.
|
|
@@ -54,8 +55,15 @@ export class Tree {
|
|
|
54
55
|
/** Tree height. */
|
|
55
56
|
height;
|
|
56
57
|
constructor(params) {
|
|
57
|
-
this.offers = params.flatMap((entry) =>
|
|
58
|
-
|
|
58
|
+
this.offers = params.flatMap((entry) => GroupUtils.isGroupInput(entry)
|
|
59
|
+
? Group.from(entry).offers
|
|
60
|
+
: [
|
|
61
|
+
new Offer({
|
|
62
|
+
...Offer.from(entry),
|
|
63
|
+
group: OfferUtils.groupHash(entry),
|
|
64
|
+
}),
|
|
65
|
+
]);
|
|
66
|
+
const descriptor = TreeUtils.buildDescriptor(params);
|
|
59
67
|
this.paddedOffers = descriptor.offers;
|
|
60
68
|
this.leaves = descriptor.leaves;
|
|
61
69
|
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
|
|
535
|
+
function buildDescriptor(entries: TreeCreateParams, options?: {
|
|
536
|
+
readonly preserveStandaloneGroups?: boolean;
|
|
537
|
+
}): TreeDescriptor;
|
|
535
538
|
/**
|
|
536
539
|
* Builds only the tree root.
|
|
537
540
|
*
|
|
@@ -120,11 +120,16 @@ export var TreeUtils;
|
|
|
120
120
|
if (params.ratification == null) {
|
|
121
121
|
const offers = "paddedOffers" in params.tree
|
|
122
122
|
? params.tree.offers
|
|
123
|
-
: params.tree.flatMap((entry) =>
|
|
123
|
+
: params.tree.flatMap((entry) => GroupUtils.isGroupInput(entry)
|
|
124
124
|
? Group.from(entry).offers
|
|
125
|
-
: [
|
|
125
|
+
: [
|
|
126
|
+
new Offer({
|
|
127
|
+
...Offer.from(entry),
|
|
128
|
+
group: OfferUtils.groupHash(entry),
|
|
129
|
+
}),
|
|
130
|
+
]);
|
|
126
131
|
if (!("paddedOffers" in params.tree)) {
|
|
127
|
-
buildDescriptor(
|
|
132
|
+
buildDescriptor(offers, { preserveStandaloneGroups: true });
|
|
128
133
|
}
|
|
129
134
|
items = offers.map((offer) => ({
|
|
130
135
|
offer,
|
|
@@ -198,6 +203,7 @@ export var TreeUtils;
|
|
|
198
203
|
* make-side code.
|
|
199
204
|
*
|
|
200
205
|
* @param entries - Groups or standalone offers in leaf order.
|
|
206
|
+
* @param options.preserveStandaloneGroups - Whether standalone offer group ids should be preserved instead of normalized.
|
|
201
207
|
* @returns Tree descriptor.
|
|
202
208
|
* @throws {InvalidTreeError} when the offer count is empty, all padding, or duplicated.
|
|
203
209
|
* @throws {InvalidTreeHeightError} when the padded tree exceeds supported ratifier typehashes.
|
|
@@ -235,10 +241,17 @@ export var TreeUtils;
|
|
|
235
241
|
* console.log(tree.root);
|
|
236
242
|
* ```
|
|
237
243
|
*/
|
|
238
|
-
function buildDescriptor(entries) {
|
|
239
|
-
const structs = entries.flatMap((entry) =>
|
|
244
|
+
function buildDescriptor(entries, options = {}) {
|
|
245
|
+
const structs = entries.flatMap((entry) => GroupUtils.isGroupInput(entry)
|
|
240
246
|
? GroupUtils.toStructs(entry)
|
|
241
|
-
: [
|
|
247
|
+
: [
|
|
248
|
+
OfferUtils.toStruct({
|
|
249
|
+
offer: entry,
|
|
250
|
+
group: options.preserveStandaloneGroups
|
|
251
|
+
? undefined
|
|
252
|
+
: OfferUtils.groupHash(entry),
|
|
253
|
+
}),
|
|
254
|
+
]);
|
|
242
255
|
if (structs.length === 0) {
|
|
243
256
|
throw new InvalidTreeError("Tree must not be empty.");
|
|
244
257
|
}
|
|
@@ -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,22 @@
|
|
|
1
|
+
import { concat, keccak256 } 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 const eip712Digest = (domainSeparator, structHash) => keccak256(concat(["0x1901", domainSeparator, structHash]));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morpho-org/midnight-sdk",
|
|
3
3
|
"description": "Viem-based package for Morpho Midnight that exports protocol utilities, fetch helpers, and Midnight API utilities under a dedicated API subpath.",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.1.1",
|
|
5
5
|
"author": "Morpho Association <contact@morpho.org>",
|
|
6
6
|
"contributors": [
|
|
7
7
|
"Rubilmax <rmilon@gmail.com>"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"type": "module",
|
|
20
|
-
"main": "
|
|
20
|
+
"main": "./lib/cjs/index.js",
|
|
21
21
|
"exports": {
|
|
22
22
|
".": {
|
|
23
23
|
"types": "./lib/esm/index.d.ts",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"typesVersions": {
|
|
35
35
|
"*": {
|
|
36
36
|
"api": [
|
|
37
|
-
"
|
|
37
|
+
"lib/esm/api/index.d.ts"
|
|
38
38
|
]
|
|
39
39
|
}
|
|
40
40
|
},
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"lib"
|
|
44
44
|
],
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@morpho-org/morpho-ts": "^2.
|
|
46
|
+
"@morpho-org/morpho-ts": "^2.8.0",
|
|
47
47
|
"viem": "^2.0.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"typescript": "^6.0.3",
|
|
52
52
|
"viem": "^2.54.2",
|
|
53
53
|
"vitest": "^4.1.9",
|
|
54
|
-
"@morpho-org/morpho-ts": "^2.
|
|
55
|
-
"@morpho-org/test": "^2.8.
|
|
54
|
+
"@morpho-org/morpho-ts": "^2.8.0",
|
|
55
|
+
"@morpho-org/test": "^2.8.2"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"prepublish": "$npm_execpath build",
|
|
@@ -61,5 +61,6 @@
|
|
|
61
61
|
"build:cjs": "tsc --build tsconfig.build.cjs.json && echo '{\"type\":\"commonjs\"}' > lib/cjs/package.json",
|
|
62
62
|
"build:esm": "tsc --build tsconfig.build.esm.json && echo '{\"type\":\"module\"}' > lib/esm/package.json",
|
|
63
63
|
"test": "vitest --root ../.. --project midnight-sdk"
|
|
64
|
-
}
|
|
64
|
+
},
|
|
65
|
+
"types": "./lib/esm/index.d.ts"
|
|
65
66
|
}
|
package/src/index.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export * from "./abis.js";
|
|
2
|
-
export * from "./constants.js";
|
|
3
|
-
export * from "./errors.js";
|
|
4
|
-
export * from "./fetch/index.js";
|
|
5
|
-
export * from "./market/index.js";
|
|
6
|
-
export * from "./math/index.js";
|
|
7
|
-
export * from "./offers/index.js";
|
|
8
|
-
export * from "./signatures/index.js";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Namespace for Midnight mempool payload utilities.
|
|
12
|
-
*
|
|
13
|
-
* Use `Payload.encode` to turn ratified offer items into wire bytes for mempool
|
|
14
|
-
* publication or raw payload API validation, and `Payload.decode` to inspect
|
|
15
|
-
* published maker offers, ratifier data, or attribution-tagged payloads.
|
|
16
|
-
* Encoded payloads are always `Hex` strings ready to be included in onchain
|
|
17
|
-
* mempool submission calldata.
|
|
18
|
-
*/
|
|
19
|
-
export { Payload } from "./signatures/Payload.js";
|