@morpho-org/midnight-sdk 1.3.0 → 1.4.0-next.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/lib/cjs/api/MidnightApi.d.ts +12 -6
- package/lib/cjs/api/MidnightApi.js +39 -22
- package/lib/cjs/api/helpers.d.ts +11 -1
- package/lib/cjs/api/helpers.js +57 -1
- package/lib/cjs/api/types.d.ts +2 -0
- package/lib/cjs/api/version.generated.d.ts +1 -1
- package/lib/cjs/api/version.generated.js +1 -1
- package/lib/cjs/fetch/utils.d.ts +1 -1
- package/lib/cjs/signatures/EcrecoverRatifierUtils.d.ts +1 -1
- package/lib/cjs/signatures/EcrecoverRatifierUtils.js +7 -12
- package/lib/cjs/signatures/RatifierUtils.d.ts +2 -2
- package/lib/cjs/signatures/RatifierUtils.js +46 -2
- package/lib/cjs/signatures/SetterRatifierUtils.js +4 -11
- package/lib/cjs/signatures/offerStructInternal.js +4 -4
- package/lib/esm/api/MidnightApi.d.ts +12 -6
- package/lib/esm/api/MidnightApi.js +42 -25
- package/lib/esm/api/helpers.d.ts +11 -1
- package/lib/esm/api/helpers.js +55 -1
- package/lib/esm/api/types.d.ts +2 -0
- package/lib/esm/api/version.generated.d.ts +1 -1
- package/lib/esm/api/version.generated.js +1 -1
- package/lib/esm/fetch/utils.d.ts +1 -1
- package/lib/esm/signatures/EcrecoverRatifierUtils.d.ts +1 -1
- package/lib/esm/signatures/EcrecoverRatifierUtils.js +7 -12
- package/lib/esm/signatures/RatifierUtils.d.ts +2 -2
- package/lib/esm/signatures/RatifierUtils.js +48 -4
- package/lib/esm/signatures/SetterRatifierUtils.js +4 -11
- package/lib/esm/signatures/offerStructInternal.js +5 -5
- package/package.json +8 -8
|
@@ -67,7 +67,7 @@ export declare class MidnightApi {
|
|
|
67
67
|
* @param params.request - Optional fetch options forwarded to this request.
|
|
68
68
|
* @returns Paginated books mapped to SDK camelCase fields.
|
|
69
69
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
70
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON.
|
|
70
|
+
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when a returned book's `market_id` does not match, or cannot be derived from, its own market params, or when a returned book falls outside the supplied `marketIds` filter.
|
|
71
71
|
* @example
|
|
72
72
|
* ```ts
|
|
73
73
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -92,7 +92,7 @@ export declare class MidnightApi {
|
|
|
92
92
|
* @param params.request - Optional fetch options forwarded to this request.
|
|
93
93
|
* @returns Book snapshot mapped to SDK camelCase fields.
|
|
94
94
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
95
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON
|
|
95
|
+
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when the returned book's `market_id` does not match, or cannot be derived from, its own market params, or when it differs from the requested `marketId`.
|
|
96
96
|
* @example
|
|
97
97
|
* ```ts
|
|
98
98
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -166,12 +166,15 @@ export declare class MidnightApi {
|
|
|
166
166
|
* @param params.units - Optional target unit amount. Mutually exclusive with `params.assets`.
|
|
167
167
|
* @param params.averageWorstPrice - Optional WAD-scaled average worst price guard. Mutually exclusive with `params.slippage`.
|
|
168
168
|
* @param params.slippage - Optional slippage percentage used to derive the guard. Mutually exclusive with `params.averageWorstPrice`.
|
|
169
|
+
* @param params.settlementFee - Optional current WAD-scaled settlement fee used for local guard validation. Defaults to zero.
|
|
169
170
|
* @param params.baseUrl - Optional Midnight API base URL override.
|
|
170
171
|
* @param params.fetch - Optional fetch implementation override.
|
|
171
172
|
* @param params.request - Optional fetch options forwarded to this request.
|
|
172
173
|
* @returns Quote and signed ABI-ready take caps mapped from the API response.
|
|
174
|
+
* @throws {NegativeValueError} when `settlementFee` is negative.
|
|
173
175
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
174
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response
|
|
176
|
+
* @throws {InvalidMidnightApiResponseError} when the API returns a malformed success response or the returned offers imply a rounded aggregate settlement price outside the effective average-worst-price guard.
|
|
177
|
+
* @throws {SettlementFeeExceedsPriceError} when a bid's settlement fee exceeds its offer price.
|
|
175
178
|
* @example
|
|
176
179
|
* ```ts
|
|
177
180
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -320,7 +323,7 @@ export declare class MidnightApi {
|
|
|
320
323
|
* @param params.cursor - Optional opaque pagination cursor from a previous response.
|
|
321
324
|
* @returns Paginated books mapped to SDK camelCase fields.
|
|
322
325
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
323
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON.
|
|
326
|
+
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when a returned book's `market_id` does not match, or cannot be derived from, its own market params, or when a returned book falls outside the supplied `marketIds` filter.
|
|
324
327
|
* @example
|
|
325
328
|
* ```ts
|
|
326
329
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -340,7 +343,7 @@ export declare class MidnightApi {
|
|
|
340
343
|
* @param params.depth - Optional maximum levels returned per side.
|
|
341
344
|
* @returns Book snapshot mapped to SDK camelCase fields.
|
|
342
345
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
343
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON
|
|
346
|
+
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when the returned book's `market_id` does not match, or cannot be derived from, its own market params, or when it differs from the requested `marketId`.
|
|
344
347
|
* @example
|
|
345
348
|
* ```ts
|
|
346
349
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -411,9 +414,12 @@ export declare class MidnightApi {
|
|
|
411
414
|
* @param params.units - Optional target unit amount. Mutually exclusive with `params.assets`.
|
|
412
415
|
* @param params.averageWorstPrice - Optional WAD-scaled average worst price guard. Mutually exclusive with `params.slippage`.
|
|
413
416
|
* @param params.slippage - Optional slippage percentage used to derive the guard. Mutually exclusive with `params.averageWorstPrice`.
|
|
417
|
+
* @param params.settlementFee - Optional current WAD-scaled settlement fee used for local guard validation. Defaults to zero.
|
|
414
418
|
* @returns Quote and signed ABI-ready take caps mapped from the API response.
|
|
419
|
+
* @throws {NegativeValueError} when `settlementFee` is negative.
|
|
415
420
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
416
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response
|
|
421
|
+
* @throws {InvalidMidnightApiResponseError} when the API returns a malformed success response or the returned offers imply a rounded aggregate settlement price outside the effective average-worst-price guard.
|
|
422
|
+
* @throws {SettlementFeeExceedsPriceError} when a bid's settlement fee exceeds its offer price.
|
|
417
423
|
* @example
|
|
418
424
|
* ```ts
|
|
419
425
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -79,7 +79,7 @@ class MidnightApi {
|
|
|
79
79
|
* @param params.request - Optional fetch options forwarded to this request.
|
|
80
80
|
* @returns Paginated books mapped to SDK camelCase fields.
|
|
81
81
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
82
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON.
|
|
82
|
+
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when a returned book's `market_id` does not match, or cannot be derived from, its own market params, or when a returned book falls outside the supplied `marketIds` filter.
|
|
83
83
|
* @example
|
|
84
84
|
* ```ts
|
|
85
85
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -110,7 +110,7 @@ class MidnightApi {
|
|
|
110
110
|
});
|
|
111
111
|
return {
|
|
112
112
|
cursor: response.cursor,
|
|
113
|
-
data: response.data.
|
|
113
|
+
data: (0, helpers_js_1.mapBoundBooks)(response.data, input.marketIds),
|
|
114
114
|
};
|
|
115
115
|
}
|
|
116
116
|
/**
|
|
@@ -125,7 +125,7 @@ class MidnightApi {
|
|
|
125
125
|
* @param params.request - Optional fetch options forwarded to this request.
|
|
126
126
|
* @returns Book snapshot mapped to SDK camelCase fields.
|
|
127
127
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
128
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON
|
|
128
|
+
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when the returned book's `market_id` does not match, or cannot be derived from, its own market params, or when it differs from the requested `marketId`.
|
|
129
129
|
* @example
|
|
130
130
|
* ```ts
|
|
131
131
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -147,7 +147,7 @@ class MidnightApi {
|
|
|
147
147
|
},
|
|
148
148
|
});
|
|
149
149
|
return {
|
|
150
|
-
data: (0, helpers_js_1.
|
|
150
|
+
data: (0, helpers_js_1.mapBoundBookMarket)(response.data, input.marketId),
|
|
151
151
|
};
|
|
152
152
|
}
|
|
153
153
|
/**
|
|
@@ -242,12 +242,15 @@ class MidnightApi {
|
|
|
242
242
|
* @param params.units - Optional target unit amount. Mutually exclusive with `params.assets`.
|
|
243
243
|
* @param params.averageWorstPrice - Optional WAD-scaled average worst price guard. Mutually exclusive with `params.slippage`.
|
|
244
244
|
* @param params.slippage - Optional slippage percentage used to derive the guard. Mutually exclusive with `params.averageWorstPrice`.
|
|
245
|
+
* @param params.settlementFee - Optional current WAD-scaled settlement fee used for local guard validation. Defaults to zero.
|
|
245
246
|
* @param params.baseUrl - Optional Midnight API base URL override.
|
|
246
247
|
* @param params.fetch - Optional fetch implementation override.
|
|
247
248
|
* @param params.request - Optional fetch options forwarded to this request.
|
|
248
249
|
* @returns Quote and signed ABI-ready take caps mapped from the API response.
|
|
250
|
+
* @throws {NegativeValueError} when `settlementFee` is negative.
|
|
249
251
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
250
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response
|
|
252
|
+
* @throws {InvalidMidnightApiResponseError} when the API returns a malformed success response or the returned offers imply a rounded aggregate settlement price outside the effective average-worst-price guard.
|
|
253
|
+
* @throws {SettlementFeeExceedsPriceError} when a bid's settlement fee exceeds its offer price.
|
|
251
254
|
* @example
|
|
252
255
|
* ```ts
|
|
253
256
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -263,6 +266,8 @@ class MidnightApi {
|
|
|
263
266
|
*/
|
|
264
267
|
static async fetchBookQuote(params) {
|
|
265
268
|
const input = params;
|
|
269
|
+
const settlementFee = BigInt(input.settlementFee ?? 0n);
|
|
270
|
+
(0, morpho_ts_1.assertNonNegative)("settlementFee", settlementFee);
|
|
266
271
|
const response = await (0, helpers_js_1.requestMidnightApi)({
|
|
267
272
|
...input,
|
|
268
273
|
method: "GET",
|
|
@@ -290,16 +295,20 @@ class MidnightApi {
|
|
|
290
295
|
if (averageWorstPrice != null) {
|
|
291
296
|
const guard = BigInt(averageWorstPrice);
|
|
292
297
|
let filledUnits = 0n;
|
|
293
|
-
let
|
|
298
|
+
let filledAssets = 0n;
|
|
294
299
|
if ("units" in input && input.units != null) {
|
|
295
300
|
let remainingUnits = BigInt(input.units);
|
|
296
301
|
for (const take of takeableOffers) {
|
|
297
302
|
if (remainingUnits === 0n)
|
|
298
303
|
break;
|
|
299
|
-
const
|
|
304
|
+
const { buyerPrice, sellerPrice } = index_js_1.TakeAmountsLib.prices({
|
|
305
|
+
offer: take.offer,
|
|
306
|
+
settlementFee,
|
|
307
|
+
});
|
|
308
|
+
const price = input.side === "asks" ? buyerPrice : sellerPrice;
|
|
300
309
|
const filled = take.units < remainingUnits ? take.units : remainingUnits;
|
|
301
310
|
filledUnits += filled;
|
|
302
|
-
|
|
311
|
+
filledAssets += morpho_ts_1.MathLib.mulDiv(filled, price, morpho_ts_1.MathLib.WAD, input.side === "asks" ? "Up" : "Down");
|
|
303
312
|
remainingUnits -= filled;
|
|
304
313
|
}
|
|
305
314
|
}
|
|
@@ -308,25 +317,30 @@ class MidnightApi {
|
|
|
308
317
|
for (const take of takeableOffers) {
|
|
309
318
|
if (remainingAssets === 0n)
|
|
310
319
|
break;
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const
|
|
320
|
+
const { buyerPrice, sellerPrice } = index_js_1.TakeAmountsLib.prices({
|
|
321
|
+
offer: take.offer,
|
|
322
|
+
settlementFee,
|
|
323
|
+
});
|
|
324
|
+
const makerPrice = input.side === "asks" ? sellerPrice : buyerPrice;
|
|
325
|
+
const settlementPrice = input.side === "asks" ? buyerPrice : sellerPrice;
|
|
326
|
+
const takeMakerAssets = input.side === "asks"
|
|
327
|
+
? morpho_ts_1.MathLib.mulDivUp(take.units, makerPrice, morpho_ts_1.MathLib.WAD)
|
|
328
|
+
: morpho_ts_1.MathLib.mulDivDown(take.units, makerPrice, morpho_ts_1.MathLib.WAD);
|
|
329
|
+
const fillsEntireTake = takeMakerAssets <= remainingAssets;
|
|
318
330
|
const filled = fillsEntireTake
|
|
319
331
|
? take.units
|
|
320
|
-
: morpho_ts_1.MathLib.mulDiv(remainingAssets, morpho_ts_1.MathLib.WAD,
|
|
332
|
+
: morpho_ts_1.MathLib.mulDiv(remainingAssets, morpho_ts_1.MathLib.WAD, makerPrice, input.side === "asks" ? "Down" : "Up");
|
|
321
333
|
if (filled === 0n)
|
|
322
334
|
continue;
|
|
323
335
|
filledUnits += filled;
|
|
324
|
-
|
|
325
|
-
remainingAssets = fillsEntireTake
|
|
336
|
+
filledAssets += morpho_ts_1.MathLib.mulDiv(filled, settlementPrice, morpho_ts_1.MathLib.WAD, input.side === "asks" ? "Up" : "Down");
|
|
337
|
+
remainingAssets = fillsEntireTake
|
|
338
|
+
? remainingAssets - takeMakerAssets
|
|
339
|
+
: 0n;
|
|
326
340
|
}
|
|
327
341
|
}
|
|
328
342
|
if (filledUnits > 0n) {
|
|
329
|
-
const averagePrice = morpho_ts_1.MathLib.
|
|
343
|
+
const averagePrice = morpho_ts_1.MathLib.mulDiv(filledAssets, morpho_ts_1.MathLib.WAD, filledUnits, input.side === "asks" ? "Up" : "Down");
|
|
330
344
|
const violatesGuard = input.side === "asks" ? averagePrice > guard : averagePrice < guard;
|
|
331
345
|
if (violatesGuard) {
|
|
332
346
|
throw new errors_js_1.InvalidMidnightApiResponseError(`Midnight API quote takeable offers imply average price "${averagePrice}" outside average_worst_price guard "${guard}".`);
|
|
@@ -525,7 +539,7 @@ class MidnightApi {
|
|
|
525
539
|
* @param params.cursor - Optional opaque pagination cursor from a previous response.
|
|
526
540
|
* @returns Paginated books mapped to SDK camelCase fields.
|
|
527
541
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
528
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON.
|
|
542
|
+
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when a returned book's `market_id` does not match, or cannot be derived from, its own market params, or when a returned book falls outside the supplied `marketIds` filter.
|
|
529
543
|
* @example
|
|
530
544
|
* ```ts
|
|
531
545
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -550,7 +564,7 @@ class MidnightApi {
|
|
|
550
564
|
* @param params.depth - Optional maximum levels returned per side.
|
|
551
565
|
* @returns Book snapshot mapped to SDK camelCase fields.
|
|
552
566
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
553
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON
|
|
567
|
+
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when the returned book's `market_id` does not match, or cannot be derived from, its own market params, or when it differs from the requested `marketId`.
|
|
554
568
|
* @example
|
|
555
569
|
* ```ts
|
|
556
570
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -636,9 +650,12 @@ class MidnightApi {
|
|
|
636
650
|
* @param params.units - Optional target unit amount. Mutually exclusive with `params.assets`.
|
|
637
651
|
* @param params.averageWorstPrice - Optional WAD-scaled average worst price guard. Mutually exclusive with `params.slippage`.
|
|
638
652
|
* @param params.slippage - Optional slippage percentage used to derive the guard. Mutually exclusive with `params.averageWorstPrice`.
|
|
653
|
+
* @param params.settlementFee - Optional current WAD-scaled settlement fee used for local guard validation. Defaults to zero.
|
|
639
654
|
* @returns Quote and signed ABI-ready take caps mapped from the API response.
|
|
655
|
+
* @throws {NegativeValueError} when `settlementFee` is negative.
|
|
640
656
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
641
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response
|
|
657
|
+
* @throws {InvalidMidnightApiResponseError} when the API returns a malformed success response or the returned offers imply a rounded aggregate settlement price outside the effective average-worst-price guard.
|
|
658
|
+
* @throws {SettlementFeeExceedsPriceError} when a bid's settlement fee exceeds its offer price.
|
|
642
659
|
* @example
|
|
643
660
|
* ```ts
|
|
644
661
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
package/lib/cjs/api/helpers.d.ts
CHANGED
|
@@ -8,8 +8,18 @@ export declare function buildBookPath(params: {
|
|
|
8
8
|
readonly side?: MidnightApiBookSide;
|
|
9
9
|
readonly suffix?: "quote" | "takeable-offers";
|
|
10
10
|
}): string;
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* @internal Maps a book market API payload to the SDK response shape, rejecting any
|
|
13
|
+
* response whose advertised `market_id` cannot be derived from — or does not match —
|
|
14
|
+
* its own market params. Recomputing the id with `MarketUtils.toId` (mirroring the
|
|
15
|
+
* takeable-offers path) stops a hostile API from pairing a trusted id with foreign
|
|
16
|
+
* metadata; every book flows through here, so both bound paths reject the same substitution.
|
|
17
|
+
*/
|
|
12
18
|
export declare function mapBookMarket(book: ApiBookMarketResponse): MidnightApiBookMarket;
|
|
19
|
+
/** @internal Maps a book market, binding its verified id to the requested market. */
|
|
20
|
+
export declare function mapBoundBookMarket(book: ApiBookMarketResponse, requestedMarketId: Hash): MidnightApiBookMarket;
|
|
21
|
+
/** @internal Maps listed book markets, binding each verified id to the requested market_ids filter when present. */
|
|
22
|
+
export declare function mapBoundBooks(books: readonly ApiBookMarketResponse[], marketIds?: readonly Hash[]): MidnightApiBookMarket[];
|
|
13
23
|
/** @internal Maps a collateral API payload to the SDK response shape. */
|
|
14
24
|
export declare function mapCollateral(collateral: ApiCollateralResponse): MidnightApiCollateral;
|
|
15
25
|
/** @internal Maps a price-level API payload to the SDK response shape. */
|
package/lib/cjs/api/helpers.js
CHANGED
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.requestMidnightApi = requestMidnightApi;
|
|
4
4
|
exports.buildBookPath = buildBookPath;
|
|
5
5
|
exports.mapBookMarket = mapBookMarket;
|
|
6
|
+
exports.mapBoundBookMarket = mapBoundBookMarket;
|
|
7
|
+
exports.mapBoundBooks = mapBoundBooks;
|
|
6
8
|
exports.mapCollateral = mapCollateral;
|
|
7
9
|
exports.mapPriceLevel = mapPriceLevel;
|
|
8
10
|
exports.mapTakeableOffer = mapTakeableOffer;
|
|
@@ -90,8 +92,42 @@ function buildBookPath(params) {
|
|
|
90
92
|
].filter((segment) => segment !== undefined);
|
|
91
93
|
return segments.map(encodeURIComponent).join("/");
|
|
92
94
|
}
|
|
93
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* @internal Maps a book market API payload to the SDK response shape, rejecting any
|
|
97
|
+
* response whose advertised `market_id` cannot be derived from — or does not match —
|
|
98
|
+
* its own market params. Recomputing the id with `MarketUtils.toId` (mirroring the
|
|
99
|
+
* takeable-offers path) stops a hostile API from pairing a trusted id with foreign
|
|
100
|
+
* metadata; every book flows through here, so both bound paths reject the same substitution.
|
|
101
|
+
*/
|
|
94
102
|
function mapBookMarket(book) {
|
|
103
|
+
let derivedMarketId;
|
|
104
|
+
let matchesAdvertisedId;
|
|
105
|
+
try {
|
|
106
|
+
derivedMarketId = index_js_1.MarketUtils.toId({
|
|
107
|
+
chainId: book.chain_id,
|
|
108
|
+
midnight: book.midnight,
|
|
109
|
+
loanToken: book.loan_token,
|
|
110
|
+
collateralParams: book.collaterals.map((collateral) => ({
|
|
111
|
+
token: collateral.token,
|
|
112
|
+
lltv: collateral.lltv,
|
|
113
|
+
liquidationCursor: collateral.liquidation_cursor,
|
|
114
|
+
oracle: collateral.oracle,
|
|
115
|
+
})),
|
|
116
|
+
maturity: book.maturity,
|
|
117
|
+
rcfThreshold: book.rcf_threshold,
|
|
118
|
+
enterGate: book.enter_gate,
|
|
119
|
+
liquidatorGate: book.liquidator_gate,
|
|
120
|
+
});
|
|
121
|
+
// Comparison stays inside the try so a malformed advertised `market_id`
|
|
122
|
+
// (non-hex value) surfaces as a wrapped response error, not a raw TypeError.
|
|
123
|
+
matchesAdvertisedId = (0, morpho_ts_1.isHexEqual)(derivedMarketId, book.market_id);
|
|
124
|
+
}
|
|
125
|
+
catch (cause) {
|
|
126
|
+
throw new errors_js_1.InvalidMidnightApiResponseError(`Midnight API book market_id "${book.market_id}" could not be validated against its market params.`, { cause });
|
|
127
|
+
}
|
|
128
|
+
if (!matchesAdvertisedId) {
|
|
129
|
+
throw new errors_js_1.InvalidMidnightApiResponseError(`Midnight API book market_id "${book.market_id}" does not match the id "${derivedMarketId}" derived from its market params.`);
|
|
130
|
+
}
|
|
95
131
|
return {
|
|
96
132
|
marketId: book.market_id,
|
|
97
133
|
chainId: book.chain_id,
|
|
@@ -106,6 +142,26 @@ function mapBookMarket(book) {
|
|
|
106
142
|
bids: book.bids.map(mapPriceLevel),
|
|
107
143
|
};
|
|
108
144
|
}
|
|
145
|
+
/** @internal Maps a book market, binding its verified id to the requested market. */
|
|
146
|
+
function mapBoundBookMarket(book, requestedMarketId) {
|
|
147
|
+
const market = mapBookMarket(book);
|
|
148
|
+
if (!(0, morpho_ts_1.isHexEqual)(market.marketId, requestedMarketId)) {
|
|
149
|
+
throw new errors_js_1.InvalidMidnightApiResponseError(`Midnight API book market_id "${market.marketId}" does not match requested market "${requestedMarketId}".`);
|
|
150
|
+
}
|
|
151
|
+
return market;
|
|
152
|
+
}
|
|
153
|
+
/** @internal Maps listed book markets, binding each verified id to the requested market_ids filter when present. */
|
|
154
|
+
function mapBoundBooks(books, marketIds) {
|
|
155
|
+
return books.map((book) => {
|
|
156
|
+
const market = mapBookMarket(book);
|
|
157
|
+
if (marketIds != null &&
|
|
158
|
+
marketIds.length > 0 &&
|
|
159
|
+
!marketIds.some((marketId) => (0, morpho_ts_1.isHexEqual)(marketId, market.marketId))) {
|
|
160
|
+
throw new errors_js_1.InvalidMidnightApiResponseError(`Midnight API book market_id "${market.marketId}" is outside the requested market_ids filter.`);
|
|
161
|
+
}
|
|
162
|
+
return market;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
109
165
|
/** @internal Maps a collateral API payload to the SDK response shape. */
|
|
110
166
|
function mapCollateral(collateral) {
|
|
111
167
|
return {
|
package/lib/cjs/api/types.d.ts
CHANGED
|
@@ -177,6 +177,8 @@ export type FetchBookQuoteParams = MidnightApiConfig & {
|
|
|
177
177
|
readonly marketId: Hash;
|
|
178
178
|
/** Book side to quote. */
|
|
179
179
|
readonly side: MidnightApiBookSide;
|
|
180
|
+
/** Current WAD-scaled settlement fee used for local guard validation. Defaults to zero. */
|
|
181
|
+
readonly settlementFee?: BigIntish;
|
|
180
182
|
} & (MidnightApiQuoteAssetsTarget | MidnightApiQuoteUnitsTarget) & (MidnightApiQuoteAverageWorstPriceGuard | MidnightApiQuoteSlippageGuard | MidnightApiQuoteWithoutGuard);
|
|
181
183
|
/**
|
|
182
184
|
* Parameters for {@link MidnightApi.fetchTakeableOffers}.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** @internal Installed Midnight SDK package version. */
|
|
2
|
-
export declare const MIDNIGHT_SDK_VERSION = "1.
|
|
2
|
+
export declare const MIDNIGHT_SDK_VERSION = "1.4.0-next.0";
|
package/lib/cjs/fetch/utils.d.ts
CHANGED
|
@@ -3,6 +3,6 @@ import type { DeploylessFetchParameters, MidnightCallParameters } from "./types.
|
|
|
3
3
|
/** @internal Extracts viem call parameters from Midnight fetch inputs. */
|
|
4
4
|
export declare const callParameters: (params: MidnightCallParameters) => MidnightCallParameters;
|
|
5
5
|
/** @internal Returns the configured deployless mode, defaulting to enabled. */
|
|
6
|
-
export declare const shouldUseDeployless: (params: DeploylessFetchParameters) =>
|
|
6
|
+
export declare const shouldUseDeployless: (params: DeploylessFetchParameters) => "force" | boolean;
|
|
7
7
|
/** @internal Returns the RPC chain id and rejects configured-chain drift. */
|
|
8
8
|
export declare const resolveChainId: (client: Client) => Promise<number>;
|
|
@@ -434,7 +434,7 @@ export declare namespace EcrecoverRatifierUtils {
|
|
|
434
434
|
* console.log(EcrecoverRatifierUtils.treeTypeHash(0));
|
|
435
435
|
* ```
|
|
436
436
|
*/
|
|
437
|
-
function treeTypeHash(height: number): "
|
|
437
|
+
function treeTypeHash(height: number): "0x0754209b60d99d0822b3ecd5a970f9db09df9c8998a8441e24b81f06d6c76fee" | "0x15bd6e2aa1a7a61614187ac16d2cbf8610c8f2f3c3d9eaa380ae7a501ee3cf06" | "0x1608d5eb56943c667c34b413f9f8a1c24a84ddfe1301a9c25487e638de1f5822" | "0x270da1ebafc0f24637af3612fb8c3a1d828fcb56d3637c24e86dd006b12ca7f9" | "0x3a677100d2e855c24a62d1e9c365bff90d02287f066a07064843ca1ee70ea113" | "0x4fa4f16f09f0c36c7670449a4032073380d28a60071e12ee8874bb3e5a8318fc" | "0x5400a5d43d39e6bfe910af8cb84ac77bf501d310413769dffd62ccecda8b00c6" | "0x5801c07a6c7df039ce00a7a2b8bd92aa1cf333c30b0bc3d78768590b6063d09e" | "0x817bbaac8bb863670f488b454cdd5d0990d9d81871a68e9df381c3c13d3f2ba2" | "0x828b9cdf8326a1cf234328e4d5229546a98fb72ef73624f5b6b31538e555b96c" | "0xa47534c85ac57c583568465d40fd46683d2d558d8129fe1aca01e93023afca92" | "0xb1e841691fb54f4ef85e2ed9de45d610e57f49e1e6eb2510ceead16e447dd519" | "0xb726cb7fab1a24c28213cbd482fa5a301f127fb25feb01da341919983a72711a" | "0xc447f06079bddf4b011523c4bce119e9e90fdf937de4ee88f48010406560e9c1" | "0xc9da7190eaf4b14c7cb1c14f9898256c0adb6b1dc303afe79594dea64fe199c0" | "0xcc97cb1955496a5269b5a7afca62ba694edcab26ba838a1adbd257931249de92" | "0xcea9cd557c6f821868ea287304199d0e0554af630bfa8fe36c64eb3bbacca418" | "0xda3feb08db360ad9e09540132ff04d2b6a596fdaa4747892217aaa4c7c9bcc31" | "0xf5d561d88647c3b38ed6636709d3166819fc66f8ed52a0daf4ae186387b4646c" | "0xf7dbde8234e8e345cec8fc0a8ac5909ee336b214882751ecd51e7b37df4f6cdd" | "0xfcb7a3ca4094246b8185620c4cf025c93032b6f0384805aa3f22afe04290e982";
|
|
438
438
|
/**
|
|
439
439
|
* Builds EcrecoverRatifier typed data for a tree-like input.
|
|
440
440
|
*
|
|
@@ -817,18 +817,13 @@ var EcrecoverRatifierUtils;
|
|
|
817
817
|
account: params.account,
|
|
818
818
|
}));
|
|
819
819
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
signature,
|
|
828
|
-
}),
|
|
829
|
-
});
|
|
830
|
-
}
|
|
831
|
-
return items;
|
|
820
|
+
return tree.offers.map((offer, leafIndex) => ({
|
|
821
|
+
offer,
|
|
822
|
+
ratifierData: encodeRatifierData({
|
|
823
|
+
signature,
|
|
824
|
+
...TreeUtils_js_1.TreeUtils.buildProof({ tree, leafIndex }),
|
|
825
|
+
}),
|
|
826
|
+
}));
|
|
832
827
|
}
|
|
833
828
|
EcrecoverRatifierUtils.ratify = ratify;
|
|
834
829
|
})(EcrecoverRatifierUtils || (exports.EcrecoverRatifierUtils = EcrecoverRatifierUtils = {}));
|
|
@@ -76,8 +76,8 @@ export declare namespace RatifierUtils {
|
|
|
76
76
|
* @param params.tree - Tree-like object or raw offer/group input.
|
|
77
77
|
* @param params.label - Ratifier label used in validation errors.
|
|
78
78
|
* @returns Normalized tree-like data and its shared ratifier.
|
|
79
|
-
* @throws {InvalidTreeError} when the tree is empty or contains multiple ratifiers.
|
|
80
|
-
* @throws {InvalidTreeHeightError} when
|
|
79
|
+
* @throws {InvalidTreeError} when the tree fields disagree, the tree is empty, or it contains multiple ratifiers.
|
|
80
|
+
* @throws {InvalidTreeHeightError} when the tree height is unsupported.
|
|
81
81
|
* @example
|
|
82
82
|
* ```ts
|
|
83
83
|
* import { Offer, RatifierUtils } from "@morpho-org/midnight-sdk";
|
|
@@ -5,6 +5,7 @@ const errors_js_1 = require("../errors.js");
|
|
|
5
5
|
const index_js_1 = require("../offers/index.js");
|
|
6
6
|
const Group_js_1 = require("./Group.js");
|
|
7
7
|
const GroupUtils_js_1 = require("./GroupUtils.js");
|
|
8
|
+
const offerStructInternal_js_1 = require("./offerStructInternal.js");
|
|
8
9
|
const TreeUtils_js_1 = require("./TreeUtils.js");
|
|
9
10
|
function isTreeLike(tree) {
|
|
10
11
|
return (!Array.isArray(tree) &&
|
|
@@ -16,6 +17,49 @@ function isTreeLike(tree) {
|
|
|
16
17
|
}
|
|
17
18
|
function normalizeTree(tree) {
|
|
18
19
|
if (isTreeLike(tree)) {
|
|
20
|
+
if (!Number.isInteger(tree.height) || tree.height < 0 || tree.height > 20) {
|
|
21
|
+
throw new errors_js_1.InvalidTreeHeightError(tree.height);
|
|
22
|
+
}
|
|
23
|
+
const expectedLength = 2 ** tree.height;
|
|
24
|
+
if (tree.paddedOffers.length !== expectedLength ||
|
|
25
|
+
tree.leaves.length !== expectedLength ||
|
|
26
|
+
tree.offers.length > expectedLength) {
|
|
27
|
+
throw new errors_js_1.InvalidTreeError("Tree offers, leaves, and height describe different trees.");
|
|
28
|
+
}
|
|
29
|
+
const computedLeaves = tree.paddedOffers.map(index_js_1.OfferUtils.hashStruct);
|
|
30
|
+
const seen = new Set();
|
|
31
|
+
for (const [index, offer] of tree.offers.entries()) {
|
|
32
|
+
if ((0, offerStructInternal_js_1.isEmptyOfferStruct)(tree.paddedOffers[index], {
|
|
33
|
+
allowDefaultGroup: true,
|
|
34
|
+
})) {
|
|
35
|
+
throw new errors_js_1.InvalidTreeError("Visible offers must not contain tree padding.");
|
|
36
|
+
}
|
|
37
|
+
const hash = index_js_1.OfferUtils.hashStruct(index_js_1.OfferUtils.toStruct({ offer }));
|
|
38
|
+
if (hash.toLowerCase() !== computedLeaves[index].toLowerCase() ||
|
|
39
|
+
seen.has(hash.toLowerCase())) {
|
|
40
|
+
throw new errors_js_1.InvalidTreeError("Visible offers do not match the tree leaves.");
|
|
41
|
+
}
|
|
42
|
+
seen.add(hash.toLowerCase());
|
|
43
|
+
}
|
|
44
|
+
if (tree.paddedOffers
|
|
45
|
+
.slice(tree.offers.length)
|
|
46
|
+
.some((offer) => !(0, offerStructInternal_js_1.isEmptyOfferStruct)(offer, { allowDefaultGroup: true }))) {
|
|
47
|
+
throw new errors_js_1.InvalidTreeError("Tree padding contains offers hidden from the visible offer list.");
|
|
48
|
+
}
|
|
49
|
+
if (computedLeaves.some((leaf, index) => leaf.toLowerCase() !== tree.leaves[index].toLowerCase())) {
|
|
50
|
+
throw new errors_js_1.InvalidTreeError("Tree leaves do not match its padded offers.");
|
|
51
|
+
}
|
|
52
|
+
let level = computedLeaves;
|
|
53
|
+
while (level.length > 1) {
|
|
54
|
+
const next = [];
|
|
55
|
+
for (let index = 0; index < level.length; index += 2) {
|
|
56
|
+
next.push(TreeUtils_js_1.TreeUtils.hashNode(level[index], level[index + 1]));
|
|
57
|
+
}
|
|
58
|
+
level = next;
|
|
59
|
+
}
|
|
60
|
+
if (level[0]?.toLowerCase() !== tree.root.toLowerCase()) {
|
|
61
|
+
throw new errors_js_1.InvalidTreeError("Tree root does not match its leaves.");
|
|
62
|
+
}
|
|
19
63
|
return tree;
|
|
20
64
|
}
|
|
21
65
|
const entries = Array.isArray(tree) ? tree : [tree];
|
|
@@ -77,8 +121,8 @@ var RatifierUtils;
|
|
|
77
121
|
* @param params.tree - Tree-like object or raw offer/group input.
|
|
78
122
|
* @param params.label - Ratifier label used in validation errors.
|
|
79
123
|
* @returns Normalized tree-like data and its shared ratifier.
|
|
80
|
-
* @throws {InvalidTreeError} when the tree is empty or contains multiple ratifiers.
|
|
81
|
-
* @throws {InvalidTreeHeightError} when
|
|
124
|
+
* @throws {InvalidTreeError} when the tree fields disagree, the tree is empty, or it contains multiple ratifiers.
|
|
125
|
+
* @throws {InvalidTreeHeightError} when the tree height is unsupported.
|
|
82
126
|
* @example
|
|
83
127
|
* ```ts
|
|
84
128
|
* import { Offer, RatifierUtils } from "@morpho-org/midnight-sdk";
|
|
@@ -244,17 +244,10 @@ var SetterRatifierUtils;
|
|
|
244
244
|
tree: params.tree,
|
|
245
245
|
label: "Setter",
|
|
246
246
|
});
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
ratifierData: ratifierData({
|
|
252
|
-
tree,
|
|
253
|
-
leafIndex: items.length,
|
|
254
|
-
}),
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
return items;
|
|
247
|
+
return tree.offers.map((offer, leafIndex) => ({
|
|
248
|
+
offer,
|
|
249
|
+
ratifierData: encodeRatifierData(TreeUtils_js_1.TreeUtils.buildProof({ tree, leafIndex })),
|
|
250
|
+
}));
|
|
258
251
|
}
|
|
259
252
|
SetterRatifierUtils.ratify = ratify;
|
|
260
253
|
})(SetterRatifierUtils || (exports.SetterRatifierUtils = SetterRatifierUtils = {}));
|
|
@@ -32,7 +32,7 @@ exports.EMPTY_OFFER_STRUCT = {
|
|
|
32
32
|
maxAssets: 0n,
|
|
33
33
|
continuousFeeCap: 0n,
|
|
34
34
|
};
|
|
35
|
-
const EMPTY_OFFER_DEFAULT_GROUP = index_js_1.OfferUtils.hashStruct(exports.EMPTY_OFFER_STRUCT);
|
|
35
|
+
const EMPTY_OFFER_DEFAULT_GROUP = (0, viem_1.keccak256)(index_js_1.OfferUtils.hashStruct(exports.EMPTY_OFFER_STRUCT));
|
|
36
36
|
/** @internal Returns whether an address value is the zero address. */
|
|
37
37
|
function isZeroAddress(value) {
|
|
38
38
|
return value.toLowerCase() === viem_1.zeroAddress;
|
|
@@ -44,9 +44,9 @@ function isZeroAddress(value) {
|
|
|
44
44
|
* decoding only treats the canonical zero-group empty struct as padding.
|
|
45
45
|
*/
|
|
46
46
|
function isEmptyOfferStruct(offer, options = {}) {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
const group = offer.group.toLowerCase();
|
|
48
|
+
const hasEmptyGroup = group === viem_1.zeroHash ||
|
|
49
|
+
(options.allowDefaultGroup === true && group === EMPTY_OFFER_DEFAULT_GROUP);
|
|
50
50
|
return (offer.market.chainId === 0n &&
|
|
51
51
|
isZeroAddress(offer.market.midnight) &&
|
|
52
52
|
isZeroAddress(offer.market.loanToken) &&
|
|
@@ -67,7 +67,7 @@ export declare class MidnightApi {
|
|
|
67
67
|
* @param params.request - Optional fetch options forwarded to this request.
|
|
68
68
|
* @returns Paginated books mapped to SDK camelCase fields.
|
|
69
69
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
70
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON.
|
|
70
|
+
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when a returned book's `market_id` does not match, or cannot be derived from, its own market params, or when a returned book falls outside the supplied `marketIds` filter.
|
|
71
71
|
* @example
|
|
72
72
|
* ```ts
|
|
73
73
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -92,7 +92,7 @@ export declare class MidnightApi {
|
|
|
92
92
|
* @param params.request - Optional fetch options forwarded to this request.
|
|
93
93
|
* @returns Book snapshot mapped to SDK camelCase fields.
|
|
94
94
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
95
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON
|
|
95
|
+
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when the returned book's `market_id` does not match, or cannot be derived from, its own market params, or when it differs from the requested `marketId`.
|
|
96
96
|
* @example
|
|
97
97
|
* ```ts
|
|
98
98
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -166,12 +166,15 @@ export declare class MidnightApi {
|
|
|
166
166
|
* @param params.units - Optional target unit amount. Mutually exclusive with `params.assets`.
|
|
167
167
|
* @param params.averageWorstPrice - Optional WAD-scaled average worst price guard. Mutually exclusive with `params.slippage`.
|
|
168
168
|
* @param params.slippage - Optional slippage percentage used to derive the guard. Mutually exclusive with `params.averageWorstPrice`.
|
|
169
|
+
* @param params.settlementFee - Optional current WAD-scaled settlement fee used for local guard validation. Defaults to zero.
|
|
169
170
|
* @param params.baseUrl - Optional Midnight API base URL override.
|
|
170
171
|
* @param params.fetch - Optional fetch implementation override.
|
|
171
172
|
* @param params.request - Optional fetch options forwarded to this request.
|
|
172
173
|
* @returns Quote and signed ABI-ready take caps mapped from the API response.
|
|
174
|
+
* @throws {NegativeValueError} when `settlementFee` is negative.
|
|
173
175
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
174
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response
|
|
176
|
+
* @throws {InvalidMidnightApiResponseError} when the API returns a malformed success response or the returned offers imply a rounded aggregate settlement price outside the effective average-worst-price guard.
|
|
177
|
+
* @throws {SettlementFeeExceedsPriceError} when a bid's settlement fee exceeds its offer price.
|
|
175
178
|
* @example
|
|
176
179
|
* ```ts
|
|
177
180
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -320,7 +323,7 @@ export declare class MidnightApi {
|
|
|
320
323
|
* @param params.cursor - Optional opaque pagination cursor from a previous response.
|
|
321
324
|
* @returns Paginated books mapped to SDK camelCase fields.
|
|
322
325
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
323
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON.
|
|
326
|
+
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when a returned book's `market_id` does not match, or cannot be derived from, its own market params, or when a returned book falls outside the supplied `marketIds` filter.
|
|
324
327
|
* @example
|
|
325
328
|
* ```ts
|
|
326
329
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -340,7 +343,7 @@ export declare class MidnightApi {
|
|
|
340
343
|
* @param params.depth - Optional maximum levels returned per side.
|
|
341
344
|
* @returns Book snapshot mapped to SDK camelCase fields.
|
|
342
345
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
343
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON
|
|
346
|
+
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when the returned book's `market_id` does not match, or cannot be derived from, its own market params, or when it differs from the requested `marketId`.
|
|
344
347
|
* @example
|
|
345
348
|
* ```ts
|
|
346
349
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -411,9 +414,12 @@ export declare class MidnightApi {
|
|
|
411
414
|
* @param params.units - Optional target unit amount. Mutually exclusive with `params.assets`.
|
|
412
415
|
* @param params.averageWorstPrice - Optional WAD-scaled average worst price guard. Mutually exclusive with `params.slippage`.
|
|
413
416
|
* @param params.slippage - Optional slippage percentage used to derive the guard. Mutually exclusive with `params.averageWorstPrice`.
|
|
417
|
+
* @param params.settlementFee - Optional current WAD-scaled settlement fee used for local guard validation. Defaults to zero.
|
|
414
418
|
* @returns Quote and signed ABI-ready take caps mapped from the API response.
|
|
419
|
+
* @throws {NegativeValueError} when `settlementFee` is negative.
|
|
415
420
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
416
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response
|
|
421
|
+
* @throws {InvalidMidnightApiResponseError} when the API returns a malformed success response or the returned offers imply a rounded aggregate settlement price outside the effective average-worst-price guard.
|
|
422
|
+
* @throws {SettlementFeeExceedsPriceError} when a bid's settlement fee exceeds its offer price.
|
|
417
423
|
* @example
|
|
418
424
|
* ```ts
|
|
419
425
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { MathLib } from "@morpho-org/morpho-ts";
|
|
1
|
+
import { assertNonNegative, MathLib } from "@morpho-org/morpho-ts";
|
|
2
2
|
import { InvalidMidnightApiResponseError } from "../errors.js";
|
|
3
|
-
import {
|
|
3
|
+
import { TakeAmountsLib } from "../math/index.js";
|
|
4
4
|
import { Payload } from "../signatures/Payload.js";
|
|
5
|
-
import { buildBookPath,
|
|
5
|
+
import { buildBookPath, mapBoundBookMarket, mapBoundBooks, mapBoundTakeableOffers, mapPriceLevel, parseValidationResponse, requestMidnightApi, } from "./helpers.js";
|
|
6
6
|
export { MempoolPayloadValidationRule } from "./types.js";
|
|
7
7
|
/**
|
|
8
8
|
* Midnight API client and stateless helper surface for books and mempool data.
|
|
@@ -75,7 +75,7 @@ export class MidnightApi {
|
|
|
75
75
|
* @param params.request - Optional fetch options forwarded to this request.
|
|
76
76
|
* @returns Paginated books mapped to SDK camelCase fields.
|
|
77
77
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
78
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON.
|
|
78
|
+
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when a returned book's `market_id` does not match, or cannot be derived from, its own market params, or when a returned book falls outside the supplied `marketIds` filter.
|
|
79
79
|
* @example
|
|
80
80
|
* ```ts
|
|
81
81
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -106,7 +106,7 @@ export class MidnightApi {
|
|
|
106
106
|
});
|
|
107
107
|
return {
|
|
108
108
|
cursor: response.cursor,
|
|
109
|
-
data: response.data.
|
|
109
|
+
data: mapBoundBooks(response.data, input.marketIds),
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
112
|
/**
|
|
@@ -121,7 +121,7 @@ export class MidnightApi {
|
|
|
121
121
|
* @param params.request - Optional fetch options forwarded to this request.
|
|
122
122
|
* @returns Book snapshot mapped to SDK camelCase fields.
|
|
123
123
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
124
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON
|
|
124
|
+
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when the returned book's `market_id` does not match, or cannot be derived from, its own market params, or when it differs from the requested `marketId`.
|
|
125
125
|
* @example
|
|
126
126
|
* ```ts
|
|
127
127
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -143,7 +143,7 @@ export class MidnightApi {
|
|
|
143
143
|
},
|
|
144
144
|
});
|
|
145
145
|
return {
|
|
146
|
-
data:
|
|
146
|
+
data: mapBoundBookMarket(response.data, input.marketId),
|
|
147
147
|
};
|
|
148
148
|
}
|
|
149
149
|
/**
|
|
@@ -238,12 +238,15 @@ export class MidnightApi {
|
|
|
238
238
|
* @param params.units - Optional target unit amount. Mutually exclusive with `params.assets`.
|
|
239
239
|
* @param params.averageWorstPrice - Optional WAD-scaled average worst price guard. Mutually exclusive with `params.slippage`.
|
|
240
240
|
* @param params.slippage - Optional slippage percentage used to derive the guard. Mutually exclusive with `params.averageWorstPrice`.
|
|
241
|
+
* @param params.settlementFee - Optional current WAD-scaled settlement fee used for local guard validation. Defaults to zero.
|
|
241
242
|
* @param params.baseUrl - Optional Midnight API base URL override.
|
|
242
243
|
* @param params.fetch - Optional fetch implementation override.
|
|
243
244
|
* @param params.request - Optional fetch options forwarded to this request.
|
|
244
245
|
* @returns Quote and signed ABI-ready take caps mapped from the API response.
|
|
246
|
+
* @throws {NegativeValueError} when `settlementFee` is negative.
|
|
245
247
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
246
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response
|
|
248
|
+
* @throws {InvalidMidnightApiResponseError} when the API returns a malformed success response or the returned offers imply a rounded aggregate settlement price outside the effective average-worst-price guard.
|
|
249
|
+
* @throws {SettlementFeeExceedsPriceError} when a bid's settlement fee exceeds its offer price.
|
|
247
250
|
* @example
|
|
248
251
|
* ```ts
|
|
249
252
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -259,6 +262,8 @@ export class MidnightApi {
|
|
|
259
262
|
*/
|
|
260
263
|
static async fetchBookQuote(params) {
|
|
261
264
|
const input = params;
|
|
265
|
+
const settlementFee = BigInt(input.settlementFee ?? 0n);
|
|
266
|
+
assertNonNegative("settlementFee", settlementFee);
|
|
262
267
|
const response = await requestMidnightApi({
|
|
263
268
|
...input,
|
|
264
269
|
method: "GET",
|
|
@@ -286,16 +291,20 @@ export class MidnightApi {
|
|
|
286
291
|
if (averageWorstPrice != null) {
|
|
287
292
|
const guard = BigInt(averageWorstPrice);
|
|
288
293
|
let filledUnits = 0n;
|
|
289
|
-
let
|
|
294
|
+
let filledAssets = 0n;
|
|
290
295
|
if ("units" in input && input.units != null) {
|
|
291
296
|
let remainingUnits = BigInt(input.units);
|
|
292
297
|
for (const take of takeableOffers) {
|
|
293
298
|
if (remainingUnits === 0n)
|
|
294
299
|
break;
|
|
295
|
-
const
|
|
300
|
+
const { buyerPrice, sellerPrice } = TakeAmountsLib.prices({
|
|
301
|
+
offer: take.offer,
|
|
302
|
+
settlementFee,
|
|
303
|
+
});
|
|
304
|
+
const price = input.side === "asks" ? buyerPrice : sellerPrice;
|
|
296
305
|
const filled = take.units < remainingUnits ? take.units : remainingUnits;
|
|
297
306
|
filledUnits += filled;
|
|
298
|
-
|
|
307
|
+
filledAssets += MathLib.mulDiv(filled, price, MathLib.WAD, input.side === "asks" ? "Up" : "Down");
|
|
299
308
|
remainingUnits -= filled;
|
|
300
309
|
}
|
|
301
310
|
}
|
|
@@ -304,25 +313,30 @@ export class MidnightApi {
|
|
|
304
313
|
for (const take of takeableOffers) {
|
|
305
314
|
if (remainingAssets === 0n)
|
|
306
315
|
break;
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const
|
|
316
|
+
const { buyerPrice, sellerPrice } = TakeAmountsLib.prices({
|
|
317
|
+
offer: take.offer,
|
|
318
|
+
settlementFee,
|
|
319
|
+
});
|
|
320
|
+
const makerPrice = input.side === "asks" ? sellerPrice : buyerPrice;
|
|
321
|
+
const settlementPrice = input.side === "asks" ? buyerPrice : sellerPrice;
|
|
322
|
+
const takeMakerAssets = input.side === "asks"
|
|
323
|
+
? MathLib.mulDivUp(take.units, makerPrice, MathLib.WAD)
|
|
324
|
+
: MathLib.mulDivDown(take.units, makerPrice, MathLib.WAD);
|
|
325
|
+
const fillsEntireTake = takeMakerAssets <= remainingAssets;
|
|
314
326
|
const filled = fillsEntireTake
|
|
315
327
|
? take.units
|
|
316
|
-
: MathLib.mulDiv(remainingAssets, MathLib.WAD,
|
|
328
|
+
: MathLib.mulDiv(remainingAssets, MathLib.WAD, makerPrice, input.side === "asks" ? "Down" : "Up");
|
|
317
329
|
if (filled === 0n)
|
|
318
330
|
continue;
|
|
319
331
|
filledUnits += filled;
|
|
320
|
-
|
|
321
|
-
remainingAssets = fillsEntireTake
|
|
332
|
+
filledAssets += MathLib.mulDiv(filled, settlementPrice, MathLib.WAD, input.side === "asks" ? "Up" : "Down");
|
|
333
|
+
remainingAssets = fillsEntireTake
|
|
334
|
+
? remainingAssets - takeMakerAssets
|
|
335
|
+
: 0n;
|
|
322
336
|
}
|
|
323
337
|
}
|
|
324
338
|
if (filledUnits > 0n) {
|
|
325
|
-
const averagePrice = MathLib.
|
|
339
|
+
const averagePrice = MathLib.mulDiv(filledAssets, MathLib.WAD, filledUnits, input.side === "asks" ? "Up" : "Down");
|
|
326
340
|
const violatesGuard = input.side === "asks" ? averagePrice > guard : averagePrice < guard;
|
|
327
341
|
if (violatesGuard) {
|
|
328
342
|
throw new InvalidMidnightApiResponseError(`Midnight API quote takeable offers imply average price "${averagePrice}" outside average_worst_price guard "${guard}".`);
|
|
@@ -521,7 +535,7 @@ export class MidnightApi {
|
|
|
521
535
|
* @param params.cursor - Optional opaque pagination cursor from a previous response.
|
|
522
536
|
* @returns Paginated books mapped to SDK camelCase fields.
|
|
523
537
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
524
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON.
|
|
538
|
+
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when a returned book's `market_id` does not match, or cannot be derived from, its own market params, or when a returned book falls outside the supplied `marketIds` filter.
|
|
525
539
|
* @example
|
|
526
540
|
* ```ts
|
|
527
541
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -546,7 +560,7 @@ export class MidnightApi {
|
|
|
546
560
|
* @param params.depth - Optional maximum levels returned per side.
|
|
547
561
|
* @returns Book snapshot mapped to SDK camelCase fields.
|
|
548
562
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
549
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON
|
|
563
|
+
* @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when the returned book's `market_id` does not match, or cannot be derived from, its own market params, or when it differs from the requested `marketId`.
|
|
550
564
|
* @example
|
|
551
565
|
* ```ts
|
|
552
566
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
|
@@ -632,9 +646,12 @@ export class MidnightApi {
|
|
|
632
646
|
* @param params.units - Optional target unit amount. Mutually exclusive with `params.assets`.
|
|
633
647
|
* @param params.averageWorstPrice - Optional WAD-scaled average worst price guard. Mutually exclusive with `params.slippage`.
|
|
634
648
|
* @param params.slippage - Optional slippage percentage used to derive the guard. Mutually exclusive with `params.averageWorstPrice`.
|
|
649
|
+
* @param params.settlementFee - Optional current WAD-scaled settlement fee used for local guard validation. Defaults to zero.
|
|
635
650
|
* @returns Quote and signed ABI-ready take caps mapped from the API response.
|
|
651
|
+
* @throws {NegativeValueError} when `settlementFee` is negative.
|
|
636
652
|
* @throws {MidnightApiError} when the API returns a non-2xx response.
|
|
637
|
-
* @throws {InvalidMidnightApiResponseError} when the API success response
|
|
653
|
+
* @throws {InvalidMidnightApiResponseError} when the API returns a malformed success response or the returned offers imply a rounded aggregate settlement price outside the effective average-worst-price guard.
|
|
654
|
+
* @throws {SettlementFeeExceedsPriceError} when a bid's settlement fee exceeds its offer price.
|
|
638
655
|
* @example
|
|
639
656
|
* ```ts
|
|
640
657
|
* import { MidnightApi } from "@morpho-org/midnight-sdk/api";
|
package/lib/esm/api/helpers.d.ts
CHANGED
|
@@ -8,8 +8,18 @@ export declare function buildBookPath(params: {
|
|
|
8
8
|
readonly side?: MidnightApiBookSide;
|
|
9
9
|
readonly suffix?: "quote" | "takeable-offers";
|
|
10
10
|
}): string;
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* @internal Maps a book market API payload to the SDK response shape, rejecting any
|
|
13
|
+
* response whose advertised `market_id` cannot be derived from — or does not match —
|
|
14
|
+
* its own market params. Recomputing the id with `MarketUtils.toId` (mirroring the
|
|
15
|
+
* takeable-offers path) stops a hostile API from pairing a trusted id with foreign
|
|
16
|
+
* metadata; every book flows through here, so both bound paths reject the same substitution.
|
|
17
|
+
*/
|
|
12
18
|
export declare function mapBookMarket(book: ApiBookMarketResponse): MidnightApiBookMarket;
|
|
19
|
+
/** @internal Maps a book market, binding its verified id to the requested market. */
|
|
20
|
+
export declare function mapBoundBookMarket(book: ApiBookMarketResponse, requestedMarketId: Hash): MidnightApiBookMarket;
|
|
21
|
+
/** @internal Maps listed book markets, binding each verified id to the requested market_ids filter when present. */
|
|
22
|
+
export declare function mapBoundBooks(books: readonly ApiBookMarketResponse[], marketIds?: readonly Hash[]): MidnightApiBookMarket[];
|
|
13
23
|
/** @internal Maps a collateral API payload to the SDK response shape. */
|
|
14
24
|
export declare function mapCollateral(collateral: ApiCollateralResponse): MidnightApiCollateral;
|
|
15
25
|
/** @internal Maps a price-level API payload to the SDK response shape. */
|
package/lib/esm/api/helpers.js
CHANGED
|
@@ -80,8 +80,42 @@ export function buildBookPath(params) {
|
|
|
80
80
|
].filter((segment) => segment !== undefined);
|
|
81
81
|
return segments.map(encodeURIComponent).join("/");
|
|
82
82
|
}
|
|
83
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* @internal Maps a book market API payload to the SDK response shape, rejecting any
|
|
85
|
+
* response whose advertised `market_id` cannot be derived from — or does not match —
|
|
86
|
+
* its own market params. Recomputing the id with `MarketUtils.toId` (mirroring the
|
|
87
|
+
* takeable-offers path) stops a hostile API from pairing a trusted id with foreign
|
|
88
|
+
* metadata; every book flows through here, so both bound paths reject the same substitution.
|
|
89
|
+
*/
|
|
84
90
|
export function mapBookMarket(book) {
|
|
91
|
+
let derivedMarketId;
|
|
92
|
+
let matchesAdvertisedId;
|
|
93
|
+
try {
|
|
94
|
+
derivedMarketId = MarketUtils.toId({
|
|
95
|
+
chainId: book.chain_id,
|
|
96
|
+
midnight: book.midnight,
|
|
97
|
+
loanToken: book.loan_token,
|
|
98
|
+
collateralParams: book.collaterals.map((collateral) => ({
|
|
99
|
+
token: collateral.token,
|
|
100
|
+
lltv: collateral.lltv,
|
|
101
|
+
liquidationCursor: collateral.liquidation_cursor,
|
|
102
|
+
oracle: collateral.oracle,
|
|
103
|
+
})),
|
|
104
|
+
maturity: book.maturity,
|
|
105
|
+
rcfThreshold: book.rcf_threshold,
|
|
106
|
+
enterGate: book.enter_gate,
|
|
107
|
+
liquidatorGate: book.liquidator_gate,
|
|
108
|
+
});
|
|
109
|
+
// Comparison stays inside the try so a malformed advertised `market_id`
|
|
110
|
+
// (non-hex value) surfaces as a wrapped response error, not a raw TypeError.
|
|
111
|
+
matchesAdvertisedId = isHexEqual(derivedMarketId, book.market_id);
|
|
112
|
+
}
|
|
113
|
+
catch (cause) {
|
|
114
|
+
throw new InvalidMidnightApiResponseError(`Midnight API book market_id "${book.market_id}" could not be validated against its market params.`, { cause });
|
|
115
|
+
}
|
|
116
|
+
if (!matchesAdvertisedId) {
|
|
117
|
+
throw new InvalidMidnightApiResponseError(`Midnight API book market_id "${book.market_id}" does not match the id "${derivedMarketId}" derived from its market params.`);
|
|
118
|
+
}
|
|
85
119
|
return {
|
|
86
120
|
marketId: book.market_id,
|
|
87
121
|
chainId: book.chain_id,
|
|
@@ -96,6 +130,26 @@ export function mapBookMarket(book) {
|
|
|
96
130
|
bids: book.bids.map(mapPriceLevel),
|
|
97
131
|
};
|
|
98
132
|
}
|
|
133
|
+
/** @internal Maps a book market, binding its verified id to the requested market. */
|
|
134
|
+
export function mapBoundBookMarket(book, requestedMarketId) {
|
|
135
|
+
const market = mapBookMarket(book);
|
|
136
|
+
if (!isHexEqual(market.marketId, requestedMarketId)) {
|
|
137
|
+
throw new InvalidMidnightApiResponseError(`Midnight API book market_id "${market.marketId}" does not match requested market "${requestedMarketId}".`);
|
|
138
|
+
}
|
|
139
|
+
return market;
|
|
140
|
+
}
|
|
141
|
+
/** @internal Maps listed book markets, binding each verified id to the requested market_ids filter when present. */
|
|
142
|
+
export function mapBoundBooks(books, marketIds) {
|
|
143
|
+
return books.map((book) => {
|
|
144
|
+
const market = mapBookMarket(book);
|
|
145
|
+
if (marketIds != null &&
|
|
146
|
+
marketIds.length > 0 &&
|
|
147
|
+
!marketIds.some((marketId) => isHexEqual(marketId, market.marketId))) {
|
|
148
|
+
throw new InvalidMidnightApiResponseError(`Midnight API book market_id "${market.marketId}" is outside the requested market_ids filter.`);
|
|
149
|
+
}
|
|
150
|
+
return market;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
99
153
|
/** @internal Maps a collateral API payload to the SDK response shape. */
|
|
100
154
|
export function mapCollateral(collateral) {
|
|
101
155
|
return {
|
package/lib/esm/api/types.d.ts
CHANGED
|
@@ -177,6 +177,8 @@ export type FetchBookQuoteParams = MidnightApiConfig & {
|
|
|
177
177
|
readonly marketId: Hash;
|
|
178
178
|
/** Book side to quote. */
|
|
179
179
|
readonly side: MidnightApiBookSide;
|
|
180
|
+
/** Current WAD-scaled settlement fee used for local guard validation. Defaults to zero. */
|
|
181
|
+
readonly settlementFee?: BigIntish;
|
|
180
182
|
} & (MidnightApiQuoteAssetsTarget | MidnightApiQuoteUnitsTarget) & (MidnightApiQuoteAverageWorstPriceGuard | MidnightApiQuoteSlippageGuard | MidnightApiQuoteWithoutGuard);
|
|
181
183
|
/**
|
|
182
184
|
* Parameters for {@link MidnightApi.fetchTakeableOffers}.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** @internal Installed Midnight SDK package version. */
|
|
2
|
-
export declare const MIDNIGHT_SDK_VERSION = "1.
|
|
2
|
+
export declare const MIDNIGHT_SDK_VERSION = "1.4.0-next.0";
|
package/lib/esm/fetch/utils.d.ts
CHANGED
|
@@ -3,6 +3,6 @@ import type { DeploylessFetchParameters, MidnightCallParameters } from "./types.
|
|
|
3
3
|
/** @internal Extracts viem call parameters from Midnight fetch inputs. */
|
|
4
4
|
export declare const callParameters: (params: MidnightCallParameters) => MidnightCallParameters;
|
|
5
5
|
/** @internal Returns the configured deployless mode, defaulting to enabled. */
|
|
6
|
-
export declare const shouldUseDeployless: (params: DeploylessFetchParameters) =>
|
|
6
|
+
export declare const shouldUseDeployless: (params: DeploylessFetchParameters) => "force" | boolean;
|
|
7
7
|
/** @internal Returns the RPC chain id and rejects configured-chain drift. */
|
|
8
8
|
export declare const resolveChainId: (client: Client) => Promise<number>;
|
|
@@ -434,7 +434,7 @@ export declare namespace EcrecoverRatifierUtils {
|
|
|
434
434
|
* console.log(EcrecoverRatifierUtils.treeTypeHash(0));
|
|
435
435
|
* ```
|
|
436
436
|
*/
|
|
437
|
-
function treeTypeHash(height: number): "
|
|
437
|
+
function treeTypeHash(height: number): "0x0754209b60d99d0822b3ecd5a970f9db09df9c8998a8441e24b81f06d6c76fee" | "0x15bd6e2aa1a7a61614187ac16d2cbf8610c8f2f3c3d9eaa380ae7a501ee3cf06" | "0x1608d5eb56943c667c34b413f9f8a1c24a84ddfe1301a9c25487e638de1f5822" | "0x270da1ebafc0f24637af3612fb8c3a1d828fcb56d3637c24e86dd006b12ca7f9" | "0x3a677100d2e855c24a62d1e9c365bff90d02287f066a07064843ca1ee70ea113" | "0x4fa4f16f09f0c36c7670449a4032073380d28a60071e12ee8874bb3e5a8318fc" | "0x5400a5d43d39e6bfe910af8cb84ac77bf501d310413769dffd62ccecda8b00c6" | "0x5801c07a6c7df039ce00a7a2b8bd92aa1cf333c30b0bc3d78768590b6063d09e" | "0x817bbaac8bb863670f488b454cdd5d0990d9d81871a68e9df381c3c13d3f2ba2" | "0x828b9cdf8326a1cf234328e4d5229546a98fb72ef73624f5b6b31538e555b96c" | "0xa47534c85ac57c583568465d40fd46683d2d558d8129fe1aca01e93023afca92" | "0xb1e841691fb54f4ef85e2ed9de45d610e57f49e1e6eb2510ceead16e447dd519" | "0xb726cb7fab1a24c28213cbd482fa5a301f127fb25feb01da341919983a72711a" | "0xc447f06079bddf4b011523c4bce119e9e90fdf937de4ee88f48010406560e9c1" | "0xc9da7190eaf4b14c7cb1c14f9898256c0adb6b1dc303afe79594dea64fe199c0" | "0xcc97cb1955496a5269b5a7afca62ba694edcab26ba838a1adbd257931249de92" | "0xcea9cd557c6f821868ea287304199d0e0554af630bfa8fe36c64eb3bbacca418" | "0xda3feb08db360ad9e09540132ff04d2b6a596fdaa4747892217aaa4c7c9bcc31" | "0xf5d561d88647c3b38ed6636709d3166819fc66f8ed52a0daf4ae186387b4646c" | "0xf7dbde8234e8e345cec8fc0a8ac5909ee336b214882751ecd51e7b37df4f6cdd" | "0xfcb7a3ca4094246b8185620c4cf025c93032b6f0384805aa3f22afe04290e982";
|
|
438
438
|
/**
|
|
439
439
|
* Builds EcrecoverRatifier typed data for a tree-like input.
|
|
440
440
|
*
|
|
@@ -814,18 +814,13 @@ export var EcrecoverRatifierUtils;
|
|
|
814
814
|
account: params.account,
|
|
815
815
|
}));
|
|
816
816
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
signature,
|
|
825
|
-
}),
|
|
826
|
-
});
|
|
827
|
-
}
|
|
828
|
-
return items;
|
|
817
|
+
return tree.offers.map((offer, leafIndex) => ({
|
|
818
|
+
offer,
|
|
819
|
+
ratifierData: encodeRatifierData({
|
|
820
|
+
signature,
|
|
821
|
+
...TreeUtils.buildProof({ tree, leafIndex }),
|
|
822
|
+
}),
|
|
823
|
+
}));
|
|
829
824
|
}
|
|
830
825
|
EcrecoverRatifierUtils.ratify = ratify;
|
|
831
826
|
})(EcrecoverRatifierUtils || (EcrecoverRatifierUtils = {}));
|
|
@@ -76,8 +76,8 @@ export declare namespace RatifierUtils {
|
|
|
76
76
|
* @param params.tree - Tree-like object or raw offer/group input.
|
|
77
77
|
* @param params.label - Ratifier label used in validation errors.
|
|
78
78
|
* @returns Normalized tree-like data and its shared ratifier.
|
|
79
|
-
* @throws {InvalidTreeError} when the tree is empty or contains multiple ratifiers.
|
|
80
|
-
* @throws {InvalidTreeHeightError} when
|
|
79
|
+
* @throws {InvalidTreeError} when the tree fields disagree, the tree is empty, or it contains multiple ratifiers.
|
|
80
|
+
* @throws {InvalidTreeHeightError} when the tree height is unsupported.
|
|
81
81
|
* @example
|
|
82
82
|
* ```ts
|
|
83
83
|
* import { Offer, RatifierUtils } from "@morpho-org/midnight-sdk";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { InvalidTreeError } from "../errors.js";
|
|
2
|
-
import { Offer } from "../offers/index.js";
|
|
1
|
+
import { InvalidTreeError, InvalidTreeHeightError } from "../errors.js";
|
|
2
|
+
import { Offer, OfferUtils } from "../offers/index.js";
|
|
3
3
|
import { Group } from "./Group.js";
|
|
4
4
|
import { GroupUtils } from "./GroupUtils.js";
|
|
5
|
+
import { isEmptyOfferStruct } from "./offerStructInternal.js";
|
|
5
6
|
import { TreeUtils } from "./TreeUtils.js";
|
|
6
7
|
function isTreeLike(tree) {
|
|
7
8
|
return (!Array.isArray(tree) &&
|
|
@@ -13,6 +14,49 @@ function isTreeLike(tree) {
|
|
|
13
14
|
}
|
|
14
15
|
function normalizeTree(tree) {
|
|
15
16
|
if (isTreeLike(tree)) {
|
|
17
|
+
if (!Number.isInteger(tree.height) || tree.height < 0 || tree.height > 20) {
|
|
18
|
+
throw new InvalidTreeHeightError(tree.height);
|
|
19
|
+
}
|
|
20
|
+
const expectedLength = 2 ** tree.height;
|
|
21
|
+
if (tree.paddedOffers.length !== expectedLength ||
|
|
22
|
+
tree.leaves.length !== expectedLength ||
|
|
23
|
+
tree.offers.length > expectedLength) {
|
|
24
|
+
throw new InvalidTreeError("Tree offers, leaves, and height describe different trees.");
|
|
25
|
+
}
|
|
26
|
+
const computedLeaves = tree.paddedOffers.map(OfferUtils.hashStruct);
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
for (const [index, offer] of tree.offers.entries()) {
|
|
29
|
+
if (isEmptyOfferStruct(tree.paddedOffers[index], {
|
|
30
|
+
allowDefaultGroup: true,
|
|
31
|
+
})) {
|
|
32
|
+
throw new InvalidTreeError("Visible offers must not contain tree padding.");
|
|
33
|
+
}
|
|
34
|
+
const hash = OfferUtils.hashStruct(OfferUtils.toStruct({ offer }));
|
|
35
|
+
if (hash.toLowerCase() !== computedLeaves[index].toLowerCase() ||
|
|
36
|
+
seen.has(hash.toLowerCase())) {
|
|
37
|
+
throw new InvalidTreeError("Visible offers do not match the tree leaves.");
|
|
38
|
+
}
|
|
39
|
+
seen.add(hash.toLowerCase());
|
|
40
|
+
}
|
|
41
|
+
if (tree.paddedOffers
|
|
42
|
+
.slice(tree.offers.length)
|
|
43
|
+
.some((offer) => !isEmptyOfferStruct(offer, { allowDefaultGroup: true }))) {
|
|
44
|
+
throw new InvalidTreeError("Tree padding contains offers hidden from the visible offer list.");
|
|
45
|
+
}
|
|
46
|
+
if (computedLeaves.some((leaf, index) => leaf.toLowerCase() !== tree.leaves[index].toLowerCase())) {
|
|
47
|
+
throw new InvalidTreeError("Tree leaves do not match its padded offers.");
|
|
48
|
+
}
|
|
49
|
+
let level = computedLeaves;
|
|
50
|
+
while (level.length > 1) {
|
|
51
|
+
const next = [];
|
|
52
|
+
for (let index = 0; index < level.length; index += 2) {
|
|
53
|
+
next.push(TreeUtils.hashNode(level[index], level[index + 1]));
|
|
54
|
+
}
|
|
55
|
+
level = next;
|
|
56
|
+
}
|
|
57
|
+
if (level[0]?.toLowerCase() !== tree.root.toLowerCase()) {
|
|
58
|
+
throw new InvalidTreeError("Tree root does not match its leaves.");
|
|
59
|
+
}
|
|
16
60
|
return tree;
|
|
17
61
|
}
|
|
18
62
|
const entries = Array.isArray(tree) ? tree : [tree];
|
|
@@ -74,8 +118,8 @@ export var RatifierUtils;
|
|
|
74
118
|
* @param params.tree - Tree-like object or raw offer/group input.
|
|
75
119
|
* @param params.label - Ratifier label used in validation errors.
|
|
76
120
|
* @returns Normalized tree-like data and its shared ratifier.
|
|
77
|
-
* @throws {InvalidTreeError} when the tree is empty or contains multiple ratifiers.
|
|
78
|
-
* @throws {InvalidTreeHeightError} when
|
|
121
|
+
* @throws {InvalidTreeError} when the tree fields disagree, the tree is empty, or it contains multiple ratifiers.
|
|
122
|
+
* @throws {InvalidTreeHeightError} when the tree height is unsupported.
|
|
79
123
|
* @example
|
|
80
124
|
* ```ts
|
|
81
125
|
* import { Offer, RatifierUtils } from "@morpho-org/midnight-sdk";
|
|
@@ -241,17 +241,10 @@ export var SetterRatifierUtils;
|
|
|
241
241
|
tree: params.tree,
|
|
242
242
|
label: "Setter",
|
|
243
243
|
});
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
ratifierData: ratifierData({
|
|
249
|
-
tree,
|
|
250
|
-
leafIndex: items.length,
|
|
251
|
-
}),
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
return items;
|
|
244
|
+
return tree.offers.map((offer, leafIndex) => ({
|
|
245
|
+
offer,
|
|
246
|
+
ratifierData: encodeRatifierData(TreeUtils.buildProof({ tree, leafIndex })),
|
|
247
|
+
}));
|
|
255
248
|
}
|
|
256
249
|
SetterRatifierUtils.ratify = ratify;
|
|
257
250
|
})(SetterRatifierUtils || (SetterRatifierUtils = {}));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { zeroAddress, zeroHash } from "viem";
|
|
1
|
+
import { keccak256, zeroAddress, zeroHash } from "viem";
|
|
2
2
|
import { OfferUtils } from "../offers/index.js";
|
|
3
3
|
/** @internal Empty protocol offer struct used for Merkle padding. */
|
|
4
4
|
export const EMPTY_OFFER_STRUCT = {
|
|
@@ -27,7 +27,7 @@ export const EMPTY_OFFER_STRUCT = {
|
|
|
27
27
|
maxAssets: 0n,
|
|
28
28
|
continuousFeeCap: 0n,
|
|
29
29
|
};
|
|
30
|
-
const EMPTY_OFFER_DEFAULT_GROUP = OfferUtils.hashStruct(EMPTY_OFFER_STRUCT);
|
|
30
|
+
const EMPTY_OFFER_DEFAULT_GROUP = keccak256(OfferUtils.hashStruct(EMPTY_OFFER_STRUCT));
|
|
31
31
|
/** @internal Returns whether an address value is the zero address. */
|
|
32
32
|
export function isZeroAddress(value) {
|
|
33
33
|
return value.toLowerCase() === zeroAddress;
|
|
@@ -39,9 +39,9 @@ export function isZeroAddress(value) {
|
|
|
39
39
|
* decoding only treats the canonical zero-group empty struct as padding.
|
|
40
40
|
*/
|
|
41
41
|
export function isEmptyOfferStruct(offer, options = {}) {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
const group = offer.group.toLowerCase();
|
|
43
|
+
const hasEmptyGroup = group === zeroHash ||
|
|
44
|
+
(options.allowDefaultGroup === true && group === EMPTY_OFFER_DEFAULT_GROUP);
|
|
45
45
|
return (offer.market.chainId === 0n &&
|
|
46
46
|
isZeroAddress(offer.market.midnight) &&
|
|
47
47
|
isZeroAddress(offer.market.loanToken) &&
|
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.4.0-next.0",
|
|
5
5
|
"author": "Morpho Association <contact@morpho.org>",
|
|
6
6
|
"contributors": [
|
|
7
7
|
"Rubilmax <rmilon@gmail.com>"
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
"viem": "^2.0.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"fast-check": "^4.
|
|
51
|
-
"typescript": "
|
|
52
|
-
"viem": "^2.
|
|
53
|
-
"vitest": "^4.1.
|
|
54
|
-
"@morpho-org/morpho-ts": "^2.
|
|
55
|
-
"@morpho-org/test": "^2.
|
|
50
|
+
"fast-check": "^4.9.0",
|
|
51
|
+
"typescript": "7.0.2",
|
|
52
|
+
"viem": "^2.55.10",
|
|
53
|
+
"vitest": "^4.1.10",
|
|
54
|
+
"@morpho-org/morpho-ts": "^2.11.1",
|
|
55
|
+
"@morpho-org/test": "^2.10.0"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"prepublish": "$npm_execpath build",
|
|
@@ -61,7 +61,7 @@
|
|
|
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
|
"generate:version": "node ../../scripts/release/generate-midnight-package-version.mjs",
|
|
64
|
-
"test": "vitest --root ../.. --project midnight-sdk"
|
|
64
|
+
"test": "vitest --root ../.. --project midnight-sdk --project midnight-sdk-fork"
|
|
65
65
|
},
|
|
66
66
|
"types": "./lib/esm/index.d.ts"
|
|
67
67
|
}
|