@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.
@@ -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 is not JSON.
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 is not JSON.
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.map(helpers_js_1.mapBookMarket),
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.mapBookMarket)(response.data),
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 is not JSON.
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 weightedPrice = 0n;
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 price = index_js_1.TickLib.tickToPrice(take.offer.tick);
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
- weightedPrice += filled * price;
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 price = index_js_1.TickLib.tickToPrice(take.offer.tick);
312
- const takeAssets = input.side === "asks"
313
- ? morpho_ts_1.MathLib.mulDivUp(take.units, price, morpho_ts_1.MathLib.WAD)
314
- : morpho_ts_1.MathLib.mulDivDown(take.units, price, morpho_ts_1.MathLib.WAD);
315
- if (takeAssets === 0n)
316
- continue;
317
- const fillsEntireTake = takeAssets <= remainingAssets;
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, price, input.side === "asks" ? "Down" : "Up");
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
- weightedPrice += filled * price;
325
- remainingAssets = fillsEntireTake ? remainingAssets - takeAssets : 0n;
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.mulDivDown(weightedPrice, 1n, filledUnits);
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 is not JSON.
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";
@@ -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
- /** @internal Maps a book market API payload to the SDK response shape. */
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. */
@@ -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
- /** @internal Maps a book market API payload to the SDK response shape. */
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 {
@@ -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.3.0";
2
+ export declare const MIDNIGHT_SDK_VERSION = "1.4.0-next.0";
@@ -3,4 +3,4 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.MIDNIGHT_SDK_VERSION = void 0;
5
5
  /** @internal Installed Midnight SDK package version. */
6
- exports.MIDNIGHT_SDK_VERSION = "1.3.0";
6
+ exports.MIDNIGHT_SDK_VERSION = "1.4.0-next.0";
@@ -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) => boolean | "force";
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): "0x270da1ebafc0f24637af3612fb8c3a1d828fcb56d3637c24e86dd006b12ca7f9" | "0x828b9cdf8326a1cf234328e4d5229546a98fb72ef73624f5b6b31538e555b96c" | "0xfcb7a3ca4094246b8185620c4cf025c93032b6f0384805aa3f22afe04290e982" | "0xcc97cb1955496a5269b5a7afca62ba694edcab26ba838a1adbd257931249de92" | "0xda3feb08db360ad9e09540132ff04d2b6a596fdaa4747892217aaa4c7c9bcc31" | "0x15bd6e2aa1a7a61614187ac16d2cbf8610c8f2f3c3d9eaa380ae7a501ee3cf06" | "0xb726cb7fab1a24c28213cbd482fa5a301f127fb25feb01da341919983a72711a" | "0xcea9cd557c6f821868ea287304199d0e0554af630bfa8fe36c64eb3bbacca418" | "0xf7dbde8234e8e345cec8fc0a8ac5909ee336b214882751ecd51e7b37df4f6cdd" | "0x5400a5d43d39e6bfe910af8cb84ac77bf501d310413769dffd62ccecda8b00c6" | "0x0754209b60d99d0822b3ecd5a970f9db09df9c8998a8441e24b81f06d6c76fee" | "0xf5d561d88647c3b38ed6636709d3166819fc66f8ed52a0daf4ae186387b4646c" | "0x5801c07a6c7df039ce00a7a2b8bd92aa1cf333c30b0bc3d78768590b6063d09e" | "0xc9da7190eaf4b14c7cb1c14f9898256c0adb6b1dc303afe79594dea64fe199c0" | "0xa47534c85ac57c583568465d40fd46683d2d558d8129fe1aca01e93023afca92" | "0xb1e841691fb54f4ef85e2ed9de45d610e57f49e1e6eb2510ceead16e447dd519" | "0x4fa4f16f09f0c36c7670449a4032073380d28a60071e12ee8874bb3e5a8318fc" | "0x817bbaac8bb863670f488b454cdd5d0990d9d81871a68e9df381c3c13d3f2ba2" | "0xc447f06079bddf4b011523c4bce119e9e90fdf937de4ee88f48010406560e9c1" | "0x1608d5eb56943c667c34b413f9f8a1c24a84ddfe1301a9c25487e638de1f5822" | "0x3a677100d2e855c24a62d1e9c365bff90d02287f066a07064843ca1ee70ea113";
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
- const items = [];
821
- for (const offer of tree.offers) {
822
- items.push({
823
- offer,
824
- ratifierData: ratifierData({
825
- tree,
826
- leafIndex: items.length,
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 normalized raw input exceeds the supported tree height.
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 normalized raw input exceeds the supported tree height.
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
- const items = [];
248
- for (const offer of tree.offers) {
249
- items.push({
250
- offer,
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 hasEmptyGroup = offer.group === viem_1.zeroHash ||
48
- (options.allowDefaultGroup === true &&
49
- offer.group === EMPTY_OFFER_DEFAULT_GROUP);
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 is not JSON.
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 is not JSON.
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 { TickLib } from "../math/index.js";
3
+ import { TakeAmountsLib } from "../math/index.js";
4
4
  import { Payload } from "../signatures/Payload.js";
5
- import { buildBookPath, mapBookMarket, mapBoundTakeableOffers, mapPriceLevel, parseValidationResponse, requestMidnightApi, } from "./helpers.js";
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.map(mapBookMarket),
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: mapBookMarket(response.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 is not JSON.
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 weightedPrice = 0n;
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 price = TickLib.tickToPrice(take.offer.tick);
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
- weightedPrice += filled * price;
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 price = TickLib.tickToPrice(take.offer.tick);
308
- const takeAssets = input.side === "asks"
309
- ? MathLib.mulDivUp(take.units, price, MathLib.WAD)
310
- : MathLib.mulDivDown(take.units, price, MathLib.WAD);
311
- if (takeAssets === 0n)
312
- continue;
313
- const fillsEntireTake = takeAssets <= remainingAssets;
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, price, input.side === "asks" ? "Down" : "Up");
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
- weightedPrice += filled * price;
321
- remainingAssets = fillsEntireTake ? remainingAssets - takeAssets : 0n;
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.mulDivDown(weightedPrice, 1n, filledUnits);
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 is not JSON.
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";
@@ -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
- /** @internal Maps a book market API payload to the SDK response shape. */
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. */
@@ -80,8 +80,42 @@ export function buildBookPath(params) {
80
80
  ].filter((segment) => segment !== undefined);
81
81
  return segments.map(encodeURIComponent).join("/");
82
82
  }
83
- /** @internal Maps a book market API payload to the SDK response shape. */
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 {
@@ -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.3.0";
2
+ export declare const MIDNIGHT_SDK_VERSION = "1.4.0-next.0";
@@ -1,3 +1,3 @@
1
1
  // Generated by scripts/release/generate-midnight-package-version.mjs. Do not edit.
2
2
  /** @internal Installed Midnight SDK package version. */
3
- export const MIDNIGHT_SDK_VERSION = "1.3.0";
3
+ export const MIDNIGHT_SDK_VERSION = "1.4.0-next.0";
@@ -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) => boolean | "force";
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): "0x270da1ebafc0f24637af3612fb8c3a1d828fcb56d3637c24e86dd006b12ca7f9" | "0x828b9cdf8326a1cf234328e4d5229546a98fb72ef73624f5b6b31538e555b96c" | "0xfcb7a3ca4094246b8185620c4cf025c93032b6f0384805aa3f22afe04290e982" | "0xcc97cb1955496a5269b5a7afca62ba694edcab26ba838a1adbd257931249de92" | "0xda3feb08db360ad9e09540132ff04d2b6a596fdaa4747892217aaa4c7c9bcc31" | "0x15bd6e2aa1a7a61614187ac16d2cbf8610c8f2f3c3d9eaa380ae7a501ee3cf06" | "0xb726cb7fab1a24c28213cbd482fa5a301f127fb25feb01da341919983a72711a" | "0xcea9cd557c6f821868ea287304199d0e0554af630bfa8fe36c64eb3bbacca418" | "0xf7dbde8234e8e345cec8fc0a8ac5909ee336b214882751ecd51e7b37df4f6cdd" | "0x5400a5d43d39e6bfe910af8cb84ac77bf501d310413769dffd62ccecda8b00c6" | "0x0754209b60d99d0822b3ecd5a970f9db09df9c8998a8441e24b81f06d6c76fee" | "0xf5d561d88647c3b38ed6636709d3166819fc66f8ed52a0daf4ae186387b4646c" | "0x5801c07a6c7df039ce00a7a2b8bd92aa1cf333c30b0bc3d78768590b6063d09e" | "0xc9da7190eaf4b14c7cb1c14f9898256c0adb6b1dc303afe79594dea64fe199c0" | "0xa47534c85ac57c583568465d40fd46683d2d558d8129fe1aca01e93023afca92" | "0xb1e841691fb54f4ef85e2ed9de45d610e57f49e1e6eb2510ceead16e447dd519" | "0x4fa4f16f09f0c36c7670449a4032073380d28a60071e12ee8874bb3e5a8318fc" | "0x817bbaac8bb863670f488b454cdd5d0990d9d81871a68e9df381c3c13d3f2ba2" | "0xc447f06079bddf4b011523c4bce119e9e90fdf937de4ee88f48010406560e9c1" | "0x1608d5eb56943c667c34b413f9f8a1c24a84ddfe1301a9c25487e638de1f5822" | "0x3a677100d2e855c24a62d1e9c365bff90d02287f066a07064843ca1ee70ea113";
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
- const items = [];
818
- for (const offer of tree.offers) {
819
- items.push({
820
- offer,
821
- ratifierData: ratifierData({
822
- tree,
823
- leafIndex: items.length,
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 normalized raw input exceeds the supported tree height.
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 normalized raw input exceeds the supported tree height.
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
- const items = [];
245
- for (const offer of tree.offers) {
246
- items.push({
247
- offer,
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 hasEmptyGroup = offer.group === zeroHash ||
43
- (options.allowDefaultGroup === true &&
44
- offer.group === EMPTY_OFFER_DEFAULT_GROUP);
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.3.0",
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.8.0",
51
- "typescript": "^6.0.3",
52
- "viem": "^2.54.2",
53
- "vitest": "^4.1.9",
54
- "@morpho-org/morpho-ts": "^2.8.0",
55
- "@morpho-org/test": "^2.8.3"
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
  }