@morpho-org/midnight-sdk 0.1.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/LICENSE +21 -0
- package/README.md +56 -46
- package/lib/cjs/abis.d.ts +1185 -996
- package/lib/cjs/abis.js +1445 -1211
- package/lib/cjs/api/MidnightApi.d.ts +22 -16
- package/lib/cjs/api/MidnightApi.js +89 -19
- package/lib/cjs/api/helpers.d.ts +11 -1
- package/lib/cjs/api/helpers.js +54 -4
- package/lib/cjs/api/types.d.ts +35 -9
- package/lib/cjs/constants.d.ts +12 -11
- package/lib/cjs/constants.js +14 -12
- package/lib/cjs/errors.d.ts +89 -1
- package/lib/cjs/errors.js +114 -3
- package/lib/cjs/fetch/Market.js +37 -3
- package/lib/cjs/fetch/index.d.ts +0 -1
- package/lib/cjs/fetch/index.js +0 -1
- package/lib/cjs/fetch/utils.d.ts +1 -1
- package/lib/cjs/fetch/utils.js +9 -2
- package/lib/cjs/index.d.ts +1 -1
- package/lib/cjs/index.js +2 -23
- package/lib/cjs/market/Market.d.ts +99 -33
- package/lib/cjs/market/Market.js +137 -28
- package/lib/cjs/market/MarketUtils.d.ts +61 -41
- package/lib/cjs/market/MarketUtils.js +108 -48
- package/lib/cjs/market/Position.d.ts +16 -11
- package/lib/cjs/market/Position.js +18 -12
- package/lib/cjs/market/PositionUtils.d.ts +6 -4
- package/lib/cjs/market/PositionUtils.js +9 -6
- package/lib/cjs/math/TickLib.d.ts +22 -0
- package/lib/cjs/math/TickLib.js +30 -0
- package/lib/cjs/math/index.d.ts +0 -1
- package/lib/cjs/math/index.js +0 -1
- package/lib/cjs/offers/Offer.d.ts +218 -12
- package/lib/cjs/offers/Offer.js +215 -7
- package/lib/cjs/offers/OfferUtils.d.ts +249 -143
- package/lib/cjs/offers/OfferUtils.js +326 -45
- package/lib/cjs/queries/GetPosition.d.ts +1 -1
- package/lib/cjs/queries/GetPosition.js +1 -1
- package/lib/cjs/signatures/EcrecoverRatifierUtils.d.ts +184 -38
- package/lib/cjs/signatures/EcrecoverRatifierUtils.js +296 -80
- package/lib/cjs/signatures/Group.d.ts +12 -4
- package/lib/cjs/signatures/Group.js +17 -7
- package/lib/cjs/signatures/GroupUtils.d.ts +60 -4
- package/lib/cjs/signatures/GroupUtils.js +57 -2
- package/lib/cjs/signatures/Payload.d.ts +277 -277
- package/lib/cjs/signatures/Payload.js +517 -534
- package/lib/cjs/signatures/RatifierUtils.d.ts +57 -0
- package/lib/cjs/signatures/RatifierUtils.js +105 -0
- package/lib/cjs/signatures/SetterRatifierUtils.d.ts +52 -13
- package/lib/cjs/signatures/SetterRatifierUtils.js +62 -19
- package/lib/cjs/signatures/Tree.d.ts +28 -16
- package/lib/cjs/signatures/Tree.js +35 -15
- package/lib/cjs/signatures/TreeUtils.d.ts +185 -63
- package/lib/cjs/signatures/TreeUtils.js +89 -79
- package/lib/cjs/signatures/eip712.d.ts +22 -0
- package/lib/cjs/signatures/eip712.js +26 -0
- package/lib/cjs/signatures/index.d.ts +2 -0
- package/lib/cjs/signatures/index.js +4 -0
- package/lib/cjs/signatures/offerStructInternal.d.ts +14 -0
- package/lib/cjs/signatures/offerStructInternal.js +72 -0
- package/lib/esm/abis.d.ts +1185 -996
- package/lib/esm/abis.js +1445 -1211
- package/lib/esm/api/MidnightApi.d.ts +22 -16
- package/lib/esm/api/MidnightApi.js +91 -21
- package/lib/esm/api/helpers.d.ts +11 -1
- package/lib/esm/api/helpers.js +54 -5
- package/lib/esm/api/types.d.ts +35 -9
- package/lib/esm/constants.d.ts +12 -11
- package/lib/esm/constants.js +13 -11
- package/lib/esm/errors.d.ts +89 -1
- package/lib/esm/errors.js +108 -2
- package/lib/esm/fetch/Market.js +38 -4
- package/lib/esm/fetch/index.d.ts +0 -1
- package/lib/esm/fetch/index.js +0 -1
- package/lib/esm/fetch/utils.d.ts +1 -1
- package/lib/esm/fetch/utils.js +9 -2
- package/lib/esm/index.d.ts +1 -1
- package/lib/esm/index.js +1 -1
- package/lib/esm/market/Market.d.ts +99 -33
- package/lib/esm/market/Market.js +137 -28
- package/lib/esm/market/MarketUtils.d.ts +61 -41
- package/lib/esm/market/MarketUtils.js +109 -49
- package/lib/esm/market/Position.d.ts +16 -11
- package/lib/esm/market/Position.js +19 -13
- package/lib/esm/market/PositionUtils.d.ts +6 -4
- package/lib/esm/market/PositionUtils.js +9 -6
- package/lib/esm/math/TickLib.d.ts +22 -0
- package/lib/esm/math/TickLib.js +31 -1
- package/lib/esm/math/index.d.ts +0 -1
- package/lib/esm/math/index.js +0 -1
- package/lib/esm/offers/Offer.d.ts +218 -12
- package/lib/esm/offers/Offer.js +216 -8
- package/lib/esm/offers/OfferUtils.d.ts +249 -143
- package/lib/esm/offers/OfferUtils.js +329 -48
- package/lib/esm/queries/GetPosition.d.ts +1 -1
- package/lib/esm/queries/GetPosition.js +1 -1
- package/lib/esm/signatures/EcrecoverRatifierUtils.d.ts +184 -38
- package/lib/esm/signatures/EcrecoverRatifierUtils.js +298 -82
- package/lib/esm/signatures/Group.d.ts +12 -4
- package/lib/esm/signatures/Group.js +17 -7
- package/lib/esm/signatures/GroupUtils.d.ts +60 -4
- package/lib/esm/signatures/GroupUtils.js +57 -2
- package/lib/esm/signatures/Payload.d.ts +277 -277
- package/lib/esm/signatures/Payload.js +517 -531
- package/lib/esm/signatures/RatifierUtils.d.ts +57 -0
- package/lib/esm/signatures/RatifierUtils.js +105 -0
- package/lib/esm/signatures/SetterRatifierUtils.d.ts +52 -13
- package/lib/esm/signatures/SetterRatifierUtils.js +62 -19
- package/lib/esm/signatures/Tree.d.ts +28 -16
- package/lib/esm/signatures/Tree.js +36 -16
- package/lib/esm/signatures/TreeUtils.d.ts +185 -63
- package/lib/esm/signatures/TreeUtils.js +91 -81
- package/lib/esm/signatures/eip712.d.ts +22 -0
- package/lib/esm/signatures/eip712.js +22 -0
- package/lib/esm/signatures/index.d.ts +2 -0
- package/lib/esm/signatures/index.js +2 -0
- package/lib/esm/signatures/offerStructInternal.d.ts +14 -0
- package/lib/esm/signatures/offerStructInternal.js +67 -0
- package/package.json +31 -34
- package/lib/cjs/fetch/Offer.d.ts +0 -74
- package/lib/cjs/fetch/Offer.js +0 -119
- package/lib/cjs/math/ConsumableUnitsLib.d.ts +0 -57
- package/lib/cjs/math/ConsumableUnitsLib.js +0 -77
- package/lib/esm/fetch/Offer.d.ts +0 -74
- package/lib/esm/fetch/Offer.js +0 -116
- package/lib/esm/math/ConsumableUnitsLib.d.ts +0 -57
- package/lib/esm/math/ConsumableUnitsLib.js +0 -74
- package/src/index.ts +0 -19
|
@@ -1,30 +1,35 @@
|
|
|
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 { InvalidTreeError, InvalidTreeHeightError, InvalidTypedDataSignatureError, } from "../errors.js";
|
|
5
|
+
import { ChainIdMismatchError, InvalidEcrecoverSignatureVError, InvalidTreeError, InvalidTreeHeightError, InvalidTypedDataSignatureError, } from "../errors.js";
|
|
6
|
+
import { MarketParams } from "../market/index.js";
|
|
7
|
+
import { Offer } from "../offers/index.js";
|
|
8
|
+
import { eip712Digest } from "./eip712.js";
|
|
9
|
+
import { RatifierUtils } from "./RatifierUtils.js";
|
|
10
|
+
import { TreeUtils, } from "./TreeUtils.js";
|
|
6
11
|
const treeTypeHashes = [
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
12
|
+
"0x270da1ebafc0f24637af3612fb8c3a1d828fcb56d3637c24e86dd006b12ca7f9",
|
|
13
|
+
"0x828b9cdf8326a1cf234328e4d5229546a98fb72ef73624f5b6b31538e555b96c",
|
|
14
|
+
"0xfcb7a3ca4094246b8185620c4cf025c93032b6f0384805aa3f22afe04290e982",
|
|
15
|
+
"0xcc97cb1955496a5269b5a7afca62ba694edcab26ba838a1adbd257931249de92",
|
|
16
|
+
"0xda3feb08db360ad9e09540132ff04d2b6a596fdaa4747892217aaa4c7c9bcc31",
|
|
17
|
+
"0x15bd6e2aa1a7a61614187ac16d2cbf8610c8f2f3c3d9eaa380ae7a501ee3cf06",
|
|
18
|
+
"0xb726cb7fab1a24c28213cbd482fa5a301f127fb25feb01da341919983a72711a",
|
|
19
|
+
"0xcea9cd557c6f821868ea287304199d0e0554af630bfa8fe36c64eb3bbacca418",
|
|
20
|
+
"0xf7dbde8234e8e345cec8fc0a8ac5909ee336b214882751ecd51e7b37df4f6cdd",
|
|
21
|
+
"0x5400a5d43d39e6bfe910af8cb84ac77bf501d310413769dffd62ccecda8b00c6",
|
|
22
|
+
"0x0754209b60d99d0822b3ecd5a970f9db09df9c8998a8441e24b81f06d6c76fee",
|
|
23
|
+
"0xf5d561d88647c3b38ed6636709d3166819fc66f8ed52a0daf4ae186387b4646c",
|
|
24
|
+
"0x5801c07a6c7df039ce00a7a2b8bd92aa1cf333c30b0bc3d78768590b6063d09e",
|
|
25
|
+
"0xc9da7190eaf4b14c7cb1c14f9898256c0adb6b1dc303afe79594dea64fe199c0",
|
|
26
|
+
"0xa47534c85ac57c583568465d40fd46683d2d558d8129fe1aca01e93023afca92",
|
|
27
|
+
"0xb1e841691fb54f4ef85e2ed9de45d610e57f49e1e6eb2510ceead16e447dd519",
|
|
28
|
+
"0x4fa4f16f09f0c36c7670449a4032073380d28a60071e12ee8874bb3e5a8318fc",
|
|
29
|
+
"0x817bbaac8bb863670f488b454cdd5d0990d9d81871a68e9df381c3c13d3f2ba2",
|
|
30
|
+
"0xc447f06079bddf4b011523c4bce119e9e90fdf937de4ee88f48010406560e9c1",
|
|
31
|
+
"0x1608d5eb56943c667c34b413f9f8a1c24a84ddfe1301a9c25487e638de1f5822",
|
|
32
|
+
"0x3a677100d2e855c24a62d1e9c365bff90d02287f066a07064843ca1ee70ea113",
|
|
28
33
|
];
|
|
29
34
|
const signatureAbi = [
|
|
30
35
|
{
|
|
@@ -56,10 +61,12 @@ const typedDataTypes = {
|
|
|
56
61
|
CollateralParams: [
|
|
57
62
|
{ name: "token", type: "address" },
|
|
58
63
|
{ name: "lltv", type: "uint256" },
|
|
59
|
-
{ name: "
|
|
64
|
+
{ name: "liquidationCursor", type: "uint256" },
|
|
60
65
|
{ name: "oracle", type: "address" },
|
|
61
66
|
],
|
|
62
67
|
Market: [
|
|
68
|
+
{ name: "chainId", type: "uint256" },
|
|
69
|
+
{ name: "midnight", type: "address" },
|
|
63
70
|
{ name: "loanToken", type: "address" },
|
|
64
71
|
{ name: "collateralParams", type: "CollateralParams[]" },
|
|
65
72
|
{ name: "maturity", type: "uint256" },
|
|
@@ -80,8 +87,8 @@ const typedDataTypes = {
|
|
|
80
87
|
{ name: "receiverIfMakerIsSeller", type: "address" },
|
|
81
88
|
{ name: "ratifier", type: "address" },
|
|
82
89
|
{ name: "reduceOnly", type: "bool" },
|
|
83
|
-
{ name: "maxUnits", type: "
|
|
84
|
-
{ name: "maxAssets", type: "
|
|
90
|
+
{ name: "maxUnits", type: "uint128" },
|
|
91
|
+
{ name: "maxAssets", type: "uint128" },
|
|
85
92
|
{ name: "continuousFeeCap", type: "uint256" },
|
|
86
93
|
],
|
|
87
94
|
};
|
|
@@ -94,21 +101,13 @@ const buildTreeValue = (offers) => {
|
|
|
94
101
|
buildTreeValue(offers.slice(mid)),
|
|
95
102
|
];
|
|
96
103
|
};
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const comparableRatifier = ratifier.toLowerCase();
|
|
105
|
-
for (const offer of offers.slice(1)) {
|
|
106
|
-
if (offer.ratifier.toLowerCase() !== comparableRatifier) {
|
|
107
|
-
throw new InvalidTreeError(`All offers in an Ecrecover tree must use one ratifier; expected "${ratifier}", got "${offer.ratifier}". Build separate trees per ratifier.`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return ratifier;
|
|
111
|
-
};
|
|
104
|
+
function toCanonicalYParity(v) {
|
|
105
|
+
if (v === 27)
|
|
106
|
+
return 0;
|
|
107
|
+
if (v === 28)
|
|
108
|
+
return 1;
|
|
109
|
+
throw new InvalidEcrecoverSignatureVError(v);
|
|
110
|
+
}
|
|
112
111
|
/**
|
|
113
112
|
* EcrecoverRatifier-specific pure utilities.
|
|
114
113
|
*
|
|
@@ -117,7 +116,10 @@ const getTreeRatifier = (tree) => {
|
|
|
117
116
|
* validate the tree, sign the typed data, call `ratify`, then pass the returned
|
|
118
117
|
* items to `Payload.encode`. Ecrecover trees must contain one ratifier; split
|
|
119
118
|
* trees by ratifier before signing. The signer may be the maker or an address
|
|
120
|
-
* authorized by every maker in the tree.
|
|
119
|
+
* authorized by every maker in the tree. Ratifier helpers accept tree-like
|
|
120
|
+
* inputs rather than requiring the `Tree` class. Passing an existing `Tree`
|
|
121
|
+
* remains the optimal path because its cached offers, leaves, root, and height
|
|
122
|
+
* are reused for typed data and per-leaf proofs.
|
|
121
123
|
*
|
|
122
124
|
* @example
|
|
123
125
|
* ```ts
|
|
@@ -149,14 +151,14 @@ export var EcrecoverRatifierUtils;
|
|
|
149
151
|
}
|
|
150
152
|
EcrecoverRatifierUtils.treeTypeHash = treeTypeHash;
|
|
151
153
|
/**
|
|
152
|
-
* Builds EcrecoverRatifier typed data for a tree.
|
|
154
|
+
* Builds EcrecoverRatifier typed data for a tree-like input.
|
|
153
155
|
*
|
|
154
156
|
* Use after the tree is built and validated, before requesting the signer
|
|
155
157
|
* signature. The EIP-712 verifier is derived from the shared ratifier address
|
|
156
158
|
* on the tree offers. `ratify` calls this for you when given a client and
|
|
157
159
|
* account.
|
|
158
160
|
*
|
|
159
|
-
* @param params.tree - Ecrecover-ratified offer tree to sign.
|
|
161
|
+
* @param params.tree - Ecrecover-ratified offer tree-like input to sign.
|
|
160
162
|
* @param params.chainId - EIP-155 chain id included in the EIP-712 domain.
|
|
161
163
|
* @returns EIP-712 typed-data descriptor.
|
|
162
164
|
* @throws {InvalidTreeError} when the tree is invalid or contains multiple ratifiers.
|
|
@@ -169,12 +171,14 @@ export var EcrecoverRatifierUtils;
|
|
|
169
171
|
*
|
|
170
172
|
* const offer = Offer.create({
|
|
171
173
|
* market: {
|
|
174
|
+
* chainId: 8453,
|
|
175
|
+
* midnight: "0x0000000000000000000000000000000000001000",
|
|
172
176
|
* loanToken: "0x0000000000000000000000000000000000006000",
|
|
173
177
|
* collateralParams: [
|
|
174
178
|
* {
|
|
175
179
|
* token: "0x0000000000000000000000000000000000007000",
|
|
176
180
|
* lltv: 770000000000000000n,
|
|
177
|
-
*
|
|
181
|
+
* liquidationCursor: 250000000000000000n,
|
|
178
182
|
* oracle: "0x0000000000000000000000000000000000008000",
|
|
179
183
|
* },
|
|
180
184
|
* ],
|
|
@@ -199,10 +203,11 @@ export var EcrecoverRatifierUtils;
|
|
|
199
203
|
* ```
|
|
200
204
|
*/
|
|
201
205
|
function typedData(params) {
|
|
202
|
-
const verifyingContract =
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
+
const { tree, ratifier: verifyingContract } = RatifierUtils.normalizeRatifierTree({
|
|
207
|
+
tree: params.tree,
|
|
208
|
+
label: "Ecrecover",
|
|
209
|
+
});
|
|
210
|
+
const treeType = tree.height === 0 ? "Offer" : `Offer${"[2]".repeat(tree.height)}`;
|
|
206
211
|
return deepFreeze({
|
|
207
212
|
domain: {
|
|
208
213
|
chainId: BigInt(params.chainId),
|
|
@@ -214,7 +219,7 @@ export var EcrecoverRatifierUtils;
|
|
|
214
219
|
},
|
|
215
220
|
primaryType: "OfferTree",
|
|
216
221
|
message: {
|
|
217
|
-
offerTree: buildTreeValue(
|
|
222
|
+
offerTree: buildTreeValue(tree.paddedOffers),
|
|
218
223
|
},
|
|
219
224
|
});
|
|
220
225
|
}
|
|
@@ -222,7 +227,7 @@ export var EcrecoverRatifierUtils;
|
|
|
222
227
|
/**
|
|
223
228
|
* Builds the EcrecoverRatifier digest used by the Solidity ratifier.
|
|
224
229
|
*
|
|
225
|
-
* @param params.tree - Ecrecover-ratified offer tree to hash.
|
|
230
|
+
* @param params.tree - Ecrecover-ratified offer tree-like input to hash.
|
|
226
231
|
* @param params.chainId - EIP-155 chain id included in the EIP-712 domain.
|
|
227
232
|
* @returns EIP-712 digest.
|
|
228
233
|
* @throws {InvalidTreeError} when the tree contains multiple ratifiers.
|
|
@@ -235,12 +240,14 @@ export var EcrecoverRatifierUtils;
|
|
|
235
240
|
*
|
|
236
241
|
* const offer = Offer.create({
|
|
237
242
|
* market: {
|
|
243
|
+
* chainId: 8453,
|
|
244
|
+
* midnight: "0x0000000000000000000000000000000000001000",
|
|
238
245
|
* loanToken: "0x0000000000000000000000000000000000006000",
|
|
239
246
|
* collateralParams: [
|
|
240
247
|
* {
|
|
241
248
|
* token: "0x0000000000000000000000000000000000007000",
|
|
242
249
|
* lltv: 770000000000000000n,
|
|
243
|
-
*
|
|
250
|
+
* liquidationCursor: 250000000000000000n,
|
|
244
251
|
* oracle: "0x0000000000000000000000000000000000008000",
|
|
245
252
|
* },
|
|
246
253
|
* ],
|
|
@@ -265,19 +272,161 @@ export var EcrecoverRatifierUtils;
|
|
|
265
272
|
* ```
|
|
266
273
|
*/
|
|
267
274
|
function digest(params) {
|
|
268
|
-
const verifyingContract =
|
|
275
|
+
const { tree, ratifier: verifyingContract } = RatifierUtils.normalizeRatifierTree({
|
|
276
|
+
tree: params.tree,
|
|
277
|
+
label: "Ecrecover",
|
|
278
|
+
});
|
|
269
279
|
const domainSeparator = keccak256(encodeAbiParameters(domainSeparatorAbi, [
|
|
270
280
|
EIP712_DOMAIN_TYPEHASH,
|
|
271
281
|
BigInt(params.chainId),
|
|
272
282
|
verifyingContract,
|
|
273
283
|
]));
|
|
274
284
|
const structHash = keccak256(encodeAbiParameters(treeStructHashAbi, [
|
|
275
|
-
treeTypeHash(
|
|
276
|
-
|
|
285
|
+
treeTypeHash(tree.height),
|
|
286
|
+
tree.root,
|
|
277
287
|
]));
|
|
278
|
-
return
|
|
288
|
+
return eip712Digest(domainSeparator, structHash);
|
|
279
289
|
}
|
|
280
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;
|
|
281
430
|
/**
|
|
282
431
|
* Signs EcrecoverRatifier typed data through a viem client.
|
|
283
432
|
*
|
|
@@ -286,12 +435,13 @@ export var EcrecoverRatifierUtils;
|
|
|
286
435
|
* client and account. The account may be the maker or an address authorized
|
|
287
436
|
* by every maker in the tree; the protocol checks that authorization onchain.
|
|
288
437
|
*
|
|
289
|
-
* @param params.tree - Ecrecover-ratified offer tree to sign.
|
|
438
|
+
* @param params.tree - Ecrecover-ratified offer tree-like input to sign.
|
|
290
439
|
* @param params.client - Viem client whose transport signs the tree typed data.
|
|
291
440
|
* @param params.account - Account used to sign the tree typed data.
|
|
292
441
|
* @returns Signature returned by the client.
|
|
293
|
-
* @throws {InvalidTreeError} when the tree is invalid or
|
|
442
|
+
* @throws {InvalidTreeError} when the tree is invalid, contains multiple ratifiers, or spans multiple chain ids.
|
|
294
443
|
* @throws {InvalidTreeHeightError} when the tree height is unsupported.
|
|
444
|
+
* @throws {ChainIdMismatchError} when `params.client.chain?.id` does not match the tree offer chain id.
|
|
295
445
|
* @throws {InvalidTypedDataSignatureError} when the returned signature does not recover to `params.account`.
|
|
296
446
|
* @example
|
|
297
447
|
* ```ts
|
|
@@ -302,12 +452,14 @@ export var EcrecoverRatifierUtils;
|
|
|
302
452
|
*
|
|
303
453
|
* const offer = Offer.create({
|
|
304
454
|
* market: {
|
|
455
|
+
* chainId: 8453,
|
|
456
|
+
* midnight: "0x0000000000000000000000000000000000001000",
|
|
305
457
|
* loanToken: "0x0000000000000000000000000000000000006000",
|
|
306
458
|
* collateralParams: [
|
|
307
459
|
* {
|
|
308
460
|
* token: "0x0000000000000000000000000000000000007000",
|
|
309
461
|
* lltv: 770000000000000000n,
|
|
310
|
-
*
|
|
462
|
+
* liquidationCursor: 250000000000000000n,
|
|
311
463
|
* oracle: "0x0000000000000000000000000000000000008000",
|
|
312
464
|
* },
|
|
313
465
|
* ],
|
|
@@ -337,24 +489,46 @@ export var EcrecoverRatifierUtils;
|
|
|
337
489
|
* ```
|
|
338
490
|
*/
|
|
339
491
|
async function sign(params) {
|
|
492
|
+
const { tree } = RatifierUtils.normalizeRatifierTree({
|
|
493
|
+
tree: params.tree,
|
|
494
|
+
label: "Ecrecover",
|
|
495
|
+
});
|
|
496
|
+
const expectedChainId = MarketParams.from(tree.offers[0].market).chainId;
|
|
497
|
+
for (const offer of tree.offers.slice(1)) {
|
|
498
|
+
const offerChainId = MarketParams.from(offer.market).chainId;
|
|
499
|
+
if (offerChainId !== expectedChainId) {
|
|
500
|
+
throw new InvalidTreeError(`All offers in an Ecrecover tree must use one chain id; expected "${expectedChainId}", got "${offerChainId}". Build separate trees per chain.`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const clientChainId = params.client.chain?.id;
|
|
504
|
+
if (clientChainId == null || BigInt(clientChainId) !== expectedChainId) {
|
|
505
|
+
throw new ChainIdMismatchError(clientChainId, expectedChainId);
|
|
506
|
+
}
|
|
340
507
|
const signer = typeof params.account === "string"
|
|
341
508
|
? params.account
|
|
342
509
|
: params.account.address;
|
|
343
510
|
const data = typedData({
|
|
344
|
-
tree
|
|
345
|
-
chainId:
|
|
511
|
+
tree,
|
|
512
|
+
chainId: expectedChainId,
|
|
346
513
|
});
|
|
347
514
|
const signature = await signTypedData(params.client, {
|
|
348
515
|
account: params.account,
|
|
349
516
|
...data,
|
|
350
517
|
});
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
518
|
+
let isValidSignature = false;
|
|
519
|
+
try {
|
|
520
|
+
isValidSignature = await verifyHash({
|
|
521
|
+
address: signer,
|
|
522
|
+
hash: digest({ tree, chainId: expectedChainId }),
|
|
523
|
+
signature,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
catch (cause) {
|
|
527
|
+
throw new InvalidTypedDataSignatureError(signer, cause);
|
|
528
|
+
}
|
|
529
|
+
if (!isValidSignature) {
|
|
357
530
|
throw new InvalidTypedDataSignatureError(signer);
|
|
531
|
+
}
|
|
358
532
|
return signature;
|
|
359
533
|
}
|
|
360
534
|
EcrecoverRatifierUtils.sign = sign;
|
|
@@ -378,17 +552,17 @@ export var EcrecoverRatifierUtils;
|
|
|
378
552
|
*/
|
|
379
553
|
function toSignature(signature) {
|
|
380
554
|
if (typeof signature !== "string") {
|
|
555
|
+
const v = signature.v != null ? Number(signature.v) : Number(signature.yParity);
|
|
381
556
|
return {
|
|
382
|
-
v:
|
|
383
|
-
? Number(signature.v)
|
|
384
|
-
: Number(signature.yParity) + 27,
|
|
557
|
+
v: v < 27 ? v + 27 : v,
|
|
385
558
|
r: signature.r,
|
|
386
559
|
s: signature.s,
|
|
387
560
|
};
|
|
388
561
|
}
|
|
389
562
|
const parsed = parseSignature(signature);
|
|
563
|
+
const v = Number(parsed.v ?? BigInt(parsed.yParity));
|
|
390
564
|
return {
|
|
391
|
-
v:
|
|
565
|
+
v: v < 27 ? v + 27 : v,
|
|
392
566
|
r: parsed.r,
|
|
393
567
|
s: parsed.s,
|
|
394
568
|
};
|
|
@@ -479,7 +653,7 @@ export var EcrecoverRatifierUtils;
|
|
|
479
653
|
* Use after a tree has been signed when a caller needs data for one offer
|
|
480
654
|
* leaf. Use `ratify` to produce payload-ready items for the whole tree.
|
|
481
655
|
*
|
|
482
|
-
* @param params.tree - Ecrecover-ratified offer tree that produced the proof.
|
|
656
|
+
* @param params.tree - Ecrecover-ratified offer tree-like input that produced the proof.
|
|
483
657
|
* @param params.leafIndex - Leaf index to prove.
|
|
484
658
|
* @param params.signature - Ecrecover signature for the tree root.
|
|
485
659
|
* @returns ABI-encoded EcrecoverRatifier data.
|
|
@@ -491,12 +665,14 @@ export var EcrecoverRatifierUtils;
|
|
|
491
665
|
*
|
|
492
666
|
* const offer = Offer.create({
|
|
493
667
|
* market: {
|
|
668
|
+
* chainId: 8453,
|
|
669
|
+
* midnight: "0x0000000000000000000000000000000000001000",
|
|
494
670
|
* loanToken: "0x0000000000000000000000000000000000006000",
|
|
495
671
|
* collateralParams: [
|
|
496
672
|
* {
|
|
497
673
|
* token: "0x0000000000000000000000000000000000007000",
|
|
498
674
|
* lltv: 770000000000000000n,
|
|
499
|
-
*
|
|
675
|
+
* liquidationCursor: 250000000000000000n,
|
|
500
676
|
* oracle: "0x0000000000000000000000000000000000008000",
|
|
501
677
|
* },
|
|
502
678
|
* ],
|
|
@@ -521,8 +697,11 @@ export var EcrecoverRatifierUtils;
|
|
|
521
697
|
* ```
|
|
522
698
|
*/
|
|
523
699
|
function ratifierData(params) {
|
|
524
|
-
|
|
525
|
-
|
|
700
|
+
const { tree } = RatifierUtils.normalizeRatifierTree({
|
|
701
|
+
tree: params.tree,
|
|
702
|
+
label: "Ecrecover",
|
|
703
|
+
});
|
|
704
|
+
const proof = TreeUtils.buildProof({ tree, leafIndex: params.leafIndex });
|
|
526
705
|
return encodeRatifierData({
|
|
527
706
|
signature: toSignature(params.signature),
|
|
528
707
|
root: proof.root,
|
|
@@ -538,14 +717,14 @@ export var EcrecoverRatifierUtils;
|
|
|
538
717
|
* The returned items preserve tree leaf order and include ratifier data
|
|
539
718
|
* required by takers. The group id is stored on each inline offer.
|
|
540
719
|
*
|
|
541
|
-
* @param params.tree - Ecrecover-ratified offer tree to ratify.
|
|
720
|
+
* @param params.tree - Ecrecover-ratified offer tree-like input to ratify.
|
|
542
721
|
* @param params.client - Optional viem client whose transport signs typed data built from `params.tree`.
|
|
543
722
|
* @param params.account - Optional account used to sign typed data built from `params.tree`.
|
|
544
723
|
* @param params.signature - Optional precomputed signature for `params.tree`.
|
|
545
724
|
* @returns Items containing each offer and its ratifier data.
|
|
546
725
|
* @throws {InvalidTreeError} when the tree is invalid or contains multiple ratifiers.
|
|
547
726
|
* @throws {InvalidTreeHeightError} when the tree height is unsupported.
|
|
548
|
-
* @throws {InvalidTypedDataSignatureError} when client signing
|
|
727
|
+
* @throws {InvalidTypedDataSignatureError} when client signing or a precomputed signature does not recover to `params.account`.
|
|
549
728
|
* @example
|
|
550
729
|
* ```ts
|
|
551
730
|
* import { EcrecoverRatifierUtils, Tree } from "@morpho-org/midnight-sdk";
|
|
@@ -555,12 +734,14 @@ export var EcrecoverRatifierUtils;
|
|
|
555
734
|
*
|
|
556
735
|
* const offer = Offer.create({
|
|
557
736
|
* market: {
|
|
737
|
+
* chainId: 8453,
|
|
738
|
+
* midnight: "0x0000000000000000000000000000000000001000",
|
|
558
739
|
* loanToken: "0x0000000000000000000000000000000000006000",
|
|
559
740
|
* collateralParams: [
|
|
560
741
|
* {
|
|
561
742
|
* token: "0x0000000000000000000000000000000000007000",
|
|
562
743
|
* lltv: 770000000000000000n,
|
|
563
|
-
*
|
|
744
|
+
* liquidationCursor: 250000000000000000n,
|
|
564
745
|
* oracle: "0x0000000000000000000000000000000000008000",
|
|
565
746
|
* },
|
|
566
747
|
* ],
|
|
@@ -590,20 +771,55 @@ export var EcrecoverRatifierUtils;
|
|
|
590
771
|
* ```
|
|
591
772
|
*/
|
|
592
773
|
async function ratify(params) {
|
|
774
|
+
const { tree } = RatifierUtils.normalizeRatifierTree({
|
|
775
|
+
tree: params.tree,
|
|
776
|
+
label: "Ecrecover",
|
|
777
|
+
});
|
|
593
778
|
let signature;
|
|
594
779
|
if (params.signature != null) {
|
|
595
|
-
getTreeRatifier(params.tree);
|
|
596
780
|
signature = toSignature(params.signature);
|
|
781
|
+
const expectedChainId = MarketParams.from(tree.offers[0].market).chainId;
|
|
782
|
+
for (const offer of tree.offers.slice(1)) {
|
|
783
|
+
const offerChainId = MarketParams.from(offer.market).chainId;
|
|
784
|
+
if (offerChainId !== expectedChainId) {
|
|
785
|
+
throw new InvalidTreeError(`All offers in an Ecrecover tree must use one chain id; expected "${expectedChainId}", got "${offerChainId}". Build separate trees per chain.`);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
const signer = typeof params.account === "string"
|
|
789
|
+
? params.account
|
|
790
|
+
: params.account.address;
|
|
791
|
+
let isValidSignature = false;
|
|
792
|
+
try {
|
|
793
|
+
isValidSignature = await verifyHash({
|
|
794
|
+
address: signer,
|
|
795
|
+
hash: digest({ tree, chainId: expectedChainId }),
|
|
796
|
+
signature: serializeSignature({
|
|
797
|
+
r: signature.r,
|
|
798
|
+
s: signature.s,
|
|
799
|
+
yParity: signature.v - 27,
|
|
800
|
+
}),
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
catch (cause) {
|
|
804
|
+
throw new InvalidTypedDataSignatureError(signer, cause);
|
|
805
|
+
}
|
|
806
|
+
if (!isValidSignature) {
|
|
807
|
+
throw new InvalidTypedDataSignatureError(signer);
|
|
808
|
+
}
|
|
597
809
|
}
|
|
598
810
|
else {
|
|
599
|
-
signature = toSignature(await sign(
|
|
811
|
+
signature = toSignature(await sign({
|
|
812
|
+
tree,
|
|
813
|
+
client: params.client,
|
|
814
|
+
account: params.account,
|
|
815
|
+
}));
|
|
600
816
|
}
|
|
601
817
|
const items = [];
|
|
602
|
-
for (const offer of
|
|
818
|
+
for (const offer of tree.offers) {
|
|
603
819
|
items.push({
|
|
604
820
|
offer,
|
|
605
821
|
ratifierData: ratifierData({
|
|
606
|
-
tree
|
|
822
|
+
tree,
|
|
607
823
|
leafIndex: items.length,
|
|
608
824
|
signature,
|
|
609
825
|
}),
|
|
@@ -18,12 +18,14 @@ import { type GroupInput, type IGroup } from "./GroupUtils.js";
|
|
|
18
18
|
*
|
|
19
19
|
* const offer = Offer.create({
|
|
20
20
|
* market: {
|
|
21
|
+
* chainId: 8453,
|
|
22
|
+
* midnight: "0x0000000000000000000000000000000000001000",
|
|
21
23
|
* loanToken: "0x0000000000000000000000000000000000006000",
|
|
22
24
|
* collateralParams: [
|
|
23
25
|
* {
|
|
24
26
|
* token: "0x0000000000000000000000000000000000007000",
|
|
25
27
|
* lltv: 770000000000000000n,
|
|
26
|
-
*
|
|
28
|
+
* liquidationCursor: 250000000000000000n,
|
|
27
29
|
* oracle: "0x0000000000000000000000000000000000008000",
|
|
28
30
|
* },
|
|
29
31
|
* ],
|
|
@@ -61,12 +63,14 @@ export declare class Group implements IGroup {
|
|
|
61
63
|
*
|
|
62
64
|
* const offer = Offer.create({
|
|
63
65
|
* market: {
|
|
66
|
+
* chainId: 8453,
|
|
67
|
+
* midnight: "0x0000000000000000000000000000000000001000",
|
|
64
68
|
* loanToken: "0x0000000000000000000000000000000000006000",
|
|
65
69
|
* collateralParams: [
|
|
66
70
|
* {
|
|
67
71
|
* token: "0x0000000000000000000000000000000000007000",
|
|
68
72
|
* lltv: 770000000000000000n,
|
|
69
|
-
*
|
|
73
|
+
* liquidationCursor: 250000000000000000n,
|
|
70
74
|
* oracle: "0x0000000000000000000000000000000000008000",
|
|
71
75
|
* },
|
|
72
76
|
* ],
|
|
@@ -104,12 +108,14 @@ export declare class Group implements IGroup {
|
|
|
104
108
|
*
|
|
105
109
|
* const offer = Offer.create({
|
|
106
110
|
* market: {
|
|
111
|
+
* chainId: 8453,
|
|
112
|
+
* midnight: "0x0000000000000000000000000000000000001000",
|
|
107
113
|
* loanToken: "0x0000000000000000000000000000000000006000",
|
|
108
114
|
* collateralParams: [
|
|
109
115
|
* {
|
|
110
116
|
* token: "0x0000000000000000000000000000000000007000",
|
|
111
117
|
* lltv: 770000000000000000n,
|
|
112
|
-
*
|
|
118
|
+
* liquidationCursor: 250000000000000000n,
|
|
113
119
|
* oracle: "0x0000000000000000000000000000000000008000",
|
|
114
120
|
* },
|
|
115
121
|
* ],
|
|
@@ -149,12 +155,14 @@ export declare class Group implements IGroup {
|
|
|
149
155
|
*
|
|
150
156
|
* const offer = Offer.create({
|
|
151
157
|
* market: {
|
|
158
|
+
* chainId: 8453,
|
|
159
|
+
* midnight: "0x0000000000000000000000000000000000001000",
|
|
152
160
|
* loanToken: "0x0000000000000000000000000000000000006000",
|
|
153
161
|
* collateralParams: [
|
|
154
162
|
* {
|
|
155
163
|
* token: "0x0000000000000000000000000000000000007000",
|
|
156
164
|
* lltv: 770000000000000000n,
|
|
157
|
-
*
|
|
165
|
+
* liquidationCursor: 250000000000000000n,
|
|
158
166
|
* oracle: "0x0000000000000000000000000000000000008000",
|
|
159
167
|
* },
|
|
160
168
|
* ],
|