@morpho-org/midnight-sdk 1.6.0 → 1.7.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.
Files changed (43) hide show
  1. package/lib/cjs/api/MidnightApi.d.ts +16 -14
  2. package/lib/cjs/api/MidnightApi.js +26 -16
  3. package/lib/cjs/api/helpers.d.ts +14 -2
  4. package/lib/cjs/api/helpers.js +71 -11
  5. package/lib/cjs/api/index.d.ts +1 -1
  6. package/lib/cjs/api/index.js +2 -1
  7. package/lib/cjs/api/types.d.ts +1 -1
  8. package/lib/cjs/api/version.generated.d.ts +1 -1
  9. package/lib/cjs/api/version.generated.js +1 -1
  10. package/lib/cjs/errors.d.ts +13 -0
  11. package/lib/cjs/errors.js +18 -1
  12. package/lib/cjs/market/Market.d.ts +6 -30
  13. package/lib/cjs/market/Market.js +7 -31
  14. package/lib/cjs/market/MarketUtils.d.ts +14 -0
  15. package/lib/cjs/market/MarketUtils.js +17 -1
  16. package/lib/cjs/offers/Offer.d.ts +1 -1
  17. package/lib/cjs/offers/Offer.js +1 -1
  18. package/lib/cjs/offers/OfferChainUtils.d.ts +6 -2
  19. package/lib/cjs/offers/OfferChainUtils.js +6 -2
  20. package/lib/cjs/position/Position.d.ts +5 -25
  21. package/lib/cjs/position/Position.js +5 -25
  22. package/lib/esm/api/MidnightApi.d.ts +16 -14
  23. package/lib/esm/api/MidnightApi.js +28 -18
  24. package/lib/esm/api/helpers.d.ts +14 -2
  25. package/lib/esm/api/helpers.js +71 -12
  26. package/lib/esm/api/index.d.ts +1 -1
  27. package/lib/esm/api/index.js +1 -1
  28. package/lib/esm/api/types.d.ts +1 -1
  29. package/lib/esm/api/version.generated.d.ts +1 -1
  30. package/lib/esm/api/version.generated.js +1 -1
  31. package/lib/esm/errors.d.ts +13 -0
  32. package/lib/esm/errors.js +16 -0
  33. package/lib/esm/market/Market.d.ts +6 -30
  34. package/lib/esm/market/Market.js +7 -31
  35. package/lib/esm/market/MarketUtils.d.ts +14 -0
  36. package/lib/esm/market/MarketUtils.js +18 -2
  37. package/lib/esm/offers/Offer.d.ts +1 -1
  38. package/lib/esm/offers/Offer.js +1 -1
  39. package/lib/esm/offers/OfferChainUtils.d.ts +6 -2
  40. package/lib/esm/offers/OfferChainUtils.js +6 -2
  41. package/lib/esm/position/Position.d.ts +5 -25
  42. package/lib/esm/position/Position.js +5 -25
  43. package/package.json +3 -6
@@ -1,8 +1,8 @@
1
1
  import { assertNonNegative, MathLib } from "@morpho-org/morpho-ts";
2
- import { InvalidMidnightApiResponseError } from "../errors.js";
2
+ import { InvalidMidnightApiQuoteTargetError, InvalidMidnightApiResponseError, } from "../errors.js";
3
3
  import { TakeAmountsLib } from "../math/index.js";
4
4
  import { Payload } from "../signatures/Payload.js";
5
- import { buildBookPath, mapBoundBookMarket, mapBoundBooks, mapBoundTakeableOffers, mapPriceLevel, parseValidationResponse, requestMidnightApi, } from "./helpers.js";
5
+ import { buildBookPath, mapBoundBookMarket, mapBoundBooks, mapBoundTakeableOffers, mapPriceLevels, 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.
@@ -73,9 +73,9 @@ export class MidnightApi {
73
73
  * @param params.baseUrl - Optional Midnight API base URL override.
74
74
  * @param params.fetch - Optional fetch implementation override.
75
75
  * @param params.request - Optional fetch options forwarded to this request.
76
- * @returns Paginated books mapped to SDK camelCase fields.
76
+ * @returns Paginated books mapped to SDK camelCase fields, with each book's price levels sorted best first (asks ascending tick, bids descending tick).
77
77
  * @throws {MidnightApiError} when the API returns a non-2xx response.
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.
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`, `chainIds`, `loanTokens`, `collateralTokens`, or `maturities` filters.
79
79
  * @example
80
80
  * ```ts
81
81
  * import { MidnightApi } from "@morpho-org/midnight-sdk/api";
@@ -106,7 +106,13 @@ export class MidnightApi {
106
106
  });
107
107
  return {
108
108
  cursor: response.cursor,
109
- data: mapBoundBooks(response.data, input.marketIds),
109
+ data: mapBoundBooks(response.data, {
110
+ marketIds: input.marketIds,
111
+ chainIds: input.chainIds,
112
+ loanTokens: input.loanTokens,
113
+ collateralTokens: input.collateralTokens,
114
+ maturities: input.maturities,
115
+ }),
110
116
  };
111
117
  }
112
118
  /**
@@ -119,7 +125,7 @@ export class MidnightApi {
119
125
  * @param params.baseUrl - Optional Midnight API base URL override.
120
126
  * @param params.fetch - Optional fetch implementation override.
121
127
  * @param params.request - Optional fetch options forwarded to this request.
122
- * @returns Book snapshot mapped to SDK camelCase fields.
128
+ * @returns Book snapshot mapped to SDK camelCase fields, with price levels sorted best first (asks ascending tick, bids descending tick).
123
129
  * @throws {MidnightApiError} when the API returns a non-2xx response.
124
130
  * @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
131
  * @example
@@ -157,7 +163,7 @@ export class MidnightApi {
157
163
  * @param params.baseUrl - Optional Midnight API base URL override.
158
164
  * @param params.fetch - Optional fetch implementation override.
159
165
  * @param params.request - Optional fetch options forwarded to this request.
160
- * @returns Price levels mapped to SDK camelCase fields.
166
+ * @returns Price levels mapped to SDK camelCase fields, sorted best first (asks ascending tick, bids descending tick).
161
167
  * @throws {MidnightApiError} when the API returns a non-2xx response.
162
168
  * @throws {InvalidMidnightApiResponseError} when the API success response is not JSON.
163
169
  * @example
@@ -182,7 +188,7 @@ export class MidnightApi {
182
188
  },
183
189
  });
184
190
  return {
185
- data: response.data.map(mapPriceLevel),
191
+ data: mapPriceLevels(response.data, input.side),
186
192
  };
187
193
  }
188
194
  /**
@@ -197,7 +203,7 @@ export class MidnightApi {
197
203
  * @param params.request - Optional fetch options forwarded to this request.
198
204
  * @returns ABI-ready take objects mapped from the API response.
199
205
  * @throws {MidnightApiError} when the API returns a non-2xx response.
200
- * @throws {InvalidMidnightApiResponseError} when the API success response is not JSON.
206
+ * @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when a returned offer cannot be mapped (malformed numeric or hex fields), when its `market_id` does not match, or cannot be derived from, its embedded market, when it falls outside the requested filters, when its `receiverIfMakerIsSeller` is not a well-formed address, or when its caps or buy-side receiver are not executable.
201
207
  * @example
202
208
  * ```ts
203
209
  * import { MidnightApi } from "@morpho-org/midnight-sdk/api";
@@ -243,9 +249,10 @@ export class MidnightApi {
243
249
  * @param params.fetch - Optional fetch implementation override.
244
250
  * @param params.request - Optional fetch options forwarded to this request.
245
251
  * @returns Quote and signed ABI-ready take caps mapped from the API response.
252
+ * @throws {InvalidMidnightApiQuoteTargetError} when the runtime input does not set exactly one of `units` or `assets`.
246
253
  * @throws {NegativeValueError} when `settlementFee` is negative.
247
254
  * @throws {MidnightApiError} when the API returns a non-2xx 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.
255
+ * @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when a returned offer cannot be mapped (malformed numeric or hex fields), when its `market_id` does not match, or cannot be derived from, its embedded market, when it falls outside the requested filters, when its `receiverIfMakerIsSeller` is not a well-formed address, when its caps or buy-side receiver are not executable, or when the returned offers imply a rounded aggregate settlement price outside the effective average-worst-price guard.
249
256
  * @throws {SettlementFeeExceedsPriceError} when a bid's settlement fee exceeds its offer price.
250
257
  * @example
251
258
  * ```ts
@@ -262,6 +269,8 @@ export class MidnightApi {
262
269
  */
263
270
  static async fetchBookQuote(params) {
264
271
  const input = params;
272
+ if ((input.units == null) === (input.assets == null))
273
+ throw new InvalidMidnightApiQuoteTargetError();
265
274
  const settlementFee = BigInt(input.settlementFee ?? 0n);
266
275
  assertNonNegative("settlementFee", settlementFee);
267
276
  const response = await requestMidnightApi({
@@ -368,7 +377,7 @@ export class MidnightApi {
368
377
  * @param params.request - Optional fetch options forwarded to this request.
369
378
  * @returns Paginated ABI-ready take objects mapped from the API response.
370
379
  * @throws {MidnightApiError} when the API returns a non-2xx response.
371
- * @throws {InvalidMidnightApiResponseError} when the API success response is not JSON.
380
+ * @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when a returned offer cannot be mapped (malformed numeric or hex fields), when its `market_id` does not match, or cannot be derived from, its embedded market, when it falls outside the requested filters, when its `receiverIfMakerIsSeller` is not a well-formed address, or when its caps or buy-side receiver are not executable.
372
381
  * @example
373
382
  * ```ts
374
383
  * import { MidnightApi } from "@morpho-org/midnight-sdk/api";
@@ -533,9 +542,9 @@ export class MidnightApi {
533
542
  * @param params.marketIds - Optional market id filters.
534
543
  * @param params.limit - Optional maximum number of books to return.
535
544
  * @param params.cursor - Optional opaque pagination cursor from a previous response.
536
- * @returns Paginated books mapped to SDK camelCase fields.
545
+ * @returns Paginated books mapped to SDK camelCase fields, with each book's price levels sorted best first (asks ascending tick, bids descending tick).
537
546
  * @throws {MidnightApiError} when the API returns a non-2xx response.
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.
547
+ * @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`, `chainIds`, `loanTokens`, `collateralTokens`, or `maturities` filters.
539
548
  * @example
540
549
  * ```ts
541
550
  * import { MidnightApi } from "@morpho-org/midnight-sdk/api";
@@ -558,7 +567,7 @@ export class MidnightApi {
558
567
  *
559
568
  * @param params.marketId - Market id whose book to read.
560
569
  * @param params.depth - Optional maximum levels returned per side.
561
- * @returns Book snapshot mapped to SDK camelCase fields.
570
+ * @returns Book snapshot mapped to SDK camelCase fields, with price levels sorted best first (asks ascending tick, bids descending tick).
562
571
  * @throws {MidnightApiError} when the API returns a non-2xx response.
563
572
  * @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`.
564
573
  * @example
@@ -586,7 +595,7 @@ export class MidnightApi {
586
595
  * @param params.marketId - Market id whose book side to read.
587
596
  * @param params.side - Book side to query.
588
597
  * @param params.depth - Optional maximum levels returned.
589
- * @returns Price levels mapped to SDK camelCase fields.
598
+ * @returns Price levels mapped to SDK camelCase fields, sorted best first (asks ascending tick, bids descending tick).
590
599
  * @throws {MidnightApiError} when the API returns a non-2xx response.
591
600
  * @throws {InvalidMidnightApiResponseError} when the API success response is not JSON.
592
601
  * @example
@@ -616,7 +625,7 @@ export class MidnightApi {
616
625
  * @param params.side - Book side to query.
617
626
  * @returns ABI-ready take objects mapped from the API response.
618
627
  * @throws {MidnightApiError} when the API returns a non-2xx response.
619
- * @throws {InvalidMidnightApiResponseError} when the API success response is not JSON.
628
+ * @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when a returned offer cannot be mapped (malformed numeric or hex fields), when its `market_id` does not match, or cannot be derived from, its embedded market, when it falls outside the requested filters, when its `receiverIfMakerIsSeller` is not a well-formed address, or when its caps or buy-side receiver are not executable.
620
629
  * @example
621
630
  * ```ts
622
631
  * import { MidnightApi } from "@morpho-org/midnight-sdk/api";
@@ -648,9 +657,10 @@ export class MidnightApi {
648
657
  * @param params.slippage - Optional slippage percentage used to derive the guard. Mutually exclusive with `params.averageWorstPrice`.
649
658
  * @param params.settlementFee - Optional current WAD-scaled settlement fee used for local guard validation. Defaults to zero.
650
659
  * @returns Quote and signed ABI-ready take caps mapped from the API response.
660
+ * @throws {InvalidMidnightApiQuoteTargetError} when the runtime input does not set exactly one of `units` or `assets`.
651
661
  * @throws {NegativeValueError} when `settlementFee` is negative.
652
662
  * @throws {MidnightApiError} when the API returns a non-2xx 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.
663
+ * @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when a returned offer cannot be mapped (malformed numeric or hex fields), when its `market_id` does not match, or cannot be derived from, its embedded market, when it falls outside the requested filters, when its `receiverIfMakerIsSeller` is not a well-formed address, when its caps or buy-side receiver are not executable, or when the returned offers imply a rounded aggregate settlement price outside the effective average-worst-price guard.
654
664
  * @throws {SettlementFeeExceedsPriceError} when a bid's settlement fee exceeds its offer price.
655
665
  * @example
656
666
  * ```ts
@@ -683,7 +693,7 @@ export class MidnightApi {
683
693
  * @param params.cursor - Optional opaque pagination cursor from a previous response.
684
694
  * @returns Paginated ABI-ready take objects mapped from the API response.
685
695
  * @throws {MidnightApiError} when the API returns a non-2xx response.
686
- * @throws {InvalidMidnightApiResponseError} when the API success response is not JSON.
696
+ * @throws {InvalidMidnightApiResponseError} when the API success response is not JSON, when a returned offer cannot be mapped (malformed numeric or hex fields), when its `market_id` does not match, or cannot be derived from, its embedded market, when it falls outside the requested filters, when its `receiverIfMakerIsSeller` is not a well-formed address, or when its caps or buy-side receiver are not executable.
687
697
  * @example
688
698
  * ```ts
689
699
  * import { MidnightApi } from "@morpho-org/midnight-sdk/api";
@@ -14,16 +14,28 @@ export declare function buildBookPath(params: {
14
14
  * its own market params. Recomputing the id with `MarketUtils.toId` (mirroring the
15
15
  * takeable-offers path) stops a hostile API from pairing a trusted id with foreign
16
16
  * metadata; every book flows through here, so both bound paths reject the same substitution.
17
+ * Collaterals are returned in the protocol's canonical order so their array index matches
18
+ * the onchain `collateralIndex`.
17
19
  */
18
20
  export declare function mapBookMarket(book: ApiBookMarketResponse): MidnightApiBookMarket;
19
21
  /** @internal Maps a book market, binding its verified id to the requested market. */
20
22
  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[];
23
+ /** @internal Requested `fetchBooks` filters that every returned book must satisfy. */
24
+ export interface BookFilterContext {
25
+ readonly marketIds?: readonly Hash[];
26
+ readonly chainIds?: readonly number[];
27
+ readonly loanTokens?: readonly Address[];
28
+ readonly collateralTokens?: readonly Address[];
29
+ readonly maturities?: readonly number[];
30
+ }
31
+ /** @internal Maps listed book markets, binding each verified id to requested filters when present. */
32
+ export declare function mapBoundBooks(books: readonly ApiBookMarketResponse[], context?: BookFilterContext): MidnightApiBookMarket[];
23
33
  /** @internal Maps a collateral API payload to the SDK response shape. */
24
34
  export declare function mapCollateral(collateral: ApiCollateralResponse): MidnightApiCollateral;
25
35
  /** @internal Maps a price-level API payload to the SDK response shape. */
26
36
  export declare function mapPriceLevel(level: ApiPriceLevelResponse): MidnightApiPriceLevel;
37
+ /** @internal Maps API price levels and sorts them best first: asks by ascending tick, bids by descending tick. */
38
+ export declare function mapPriceLevels(levels: readonly ApiPriceLevelResponse[], side: MidnightApiBookSide): MidnightApiPriceLevel[];
27
39
  /** @internal Maps a takeable-offer API payload to the SDK response shape. */
28
40
  export declare function mapTakeableOffer(takeableOffer: ApiTakeableOfferResponse): MidnightApiTake;
29
41
  /** @internal API response context required for a takeable offer to be executable for the caller's request. */
@@ -1,7 +1,8 @@
1
1
  import { BLUE_API_BASE_URL, isHexEqual } from "@morpho-org/morpho-ts";
2
2
  import { isAddress, isAddressEqual, maxUint256, } from "viem";
3
- import { InvalidMidnightApiResponseError, MidnightApiError, } from "../errors.js";
3
+ import { InvalidMidnightApiResponseError, InvalidOfferParameterError, MidnightApiError, } from "../errors.js";
4
4
  import { MarketUtils } from "../market/index.js";
5
+ import { OfferUtils } from "../offers/index.js";
5
6
  import { MempoolPayloadValidationRule, } from "./types.js";
6
7
  import { MIDNIGHT_SDK_VERSION } from "./version.generated.js";
7
8
  const DEFAULT_MIDNIGHT_API_URL = new URL("/v0/midnight", BLUE_API_BASE_URL);
@@ -86,6 +87,8 @@ export function buildBookPath(params) {
86
87
  * its own market params. Recomputing the id with `MarketUtils.toId` (mirroring the
87
88
  * takeable-offers path) stops a hostile API from pairing a trusted id with foreign
88
89
  * metadata; every book flows through here, so both bound paths reject the same substitution.
90
+ * Collaterals are returned in the protocol's canonical order so their array index matches
91
+ * the onchain `collateralIndex`.
89
92
  */
90
93
  export function mapBookMarket(book) {
91
94
  let derivedMarketId;
@@ -121,13 +124,15 @@ export function mapBookMarket(book) {
121
124
  chainId: book.chain_id,
122
125
  midnight: book.midnight,
123
126
  loanToken: book.loan_token,
124
- collaterals: book.collaterals.map(mapCollateral),
127
+ collaterals: [...book.collaterals]
128
+ .sort(MarketUtils.compareCollateralTokens)
129
+ .map(mapCollateral),
125
130
  maturity: book.maturity,
126
131
  rcfThreshold: book.rcf_threshold,
127
132
  enterGate: book.enter_gate,
128
133
  liquidatorGate: book.liquidator_gate,
129
- asks: book.asks.map(mapPriceLevel),
130
- bids: book.bids.map(mapPriceLevel),
134
+ asks: mapPriceLevels(book.asks, "asks"),
135
+ bids: mapPriceLevels(book.bids, "bids"),
131
136
  };
132
137
  }
133
138
  /** @internal Maps a book market, binding its verified id to the requested market. */
@@ -138,15 +143,36 @@ export function mapBoundBookMarket(book, requestedMarketId) {
138
143
  }
139
144
  return market;
140
145
  }
141
- /** @internal Maps listed book markets, binding each verified id to the requested market_ids filter when present. */
142
- export function mapBoundBooks(books, marketIds) {
146
+ /** @internal Maps listed book markets, binding each verified id to requested filters when present. */
147
+ export function mapBoundBooks(books, context = {}) {
143
148
  return books.map((book) => {
144
149
  const market = mapBookMarket(book);
145
- if (marketIds != null &&
146
- marketIds.length > 0 &&
147
- !marketIds.some((marketId) => isHexEqual(marketId, market.marketId))) {
150
+ if (context.marketIds != null &&
151
+ context.marketIds.length > 0 &&
152
+ !context.marketIds.some((marketId) => isHexEqual(marketId, market.marketId))) {
148
153
  throw new InvalidMidnightApiResponseError(`Midnight API book market_id "${market.marketId}" is outside the requested market_ids filter.`);
149
154
  }
155
+ if (context.chainIds != null &&
156
+ context.chainIds.length > 0 &&
157
+ !context.chainIds.includes(market.chainId)) {
158
+ throw new InvalidMidnightApiResponseError(`Midnight API book market_id "${market.marketId}" is outside the requested chain_ids filter.`);
159
+ }
160
+ if (context.loanTokens != null &&
161
+ context.loanTokens.length > 0 &&
162
+ !context.loanTokens.some((token) => isAddressEqual(token, market.loanToken))) {
163
+ throw new InvalidMidnightApiResponseError(`Midnight API book market_id "${market.marketId}" is outside the requested loan_tokens filter.`);
164
+ }
165
+ const { collateralTokens } = context;
166
+ if (collateralTokens != null &&
167
+ collateralTokens.length > 0 &&
168
+ !market.collaterals.some((collateral) => collateralTokens.some((token) => isAddressEqual(token, collateral.token)))) {
169
+ throw new InvalidMidnightApiResponseError(`Midnight API book market_id "${market.marketId}" is outside the requested collateral_tokens filter.`);
170
+ }
171
+ if (context.maturities != null &&
172
+ context.maturities.length > 0 &&
173
+ !context.maturities.includes(market.maturity)) {
174
+ throw new InvalidMidnightApiResponseError(`Midnight API book market_id "${market.marketId}" is outside the requested maturities filter.`);
175
+ }
150
176
  return market;
151
177
  });
152
178
  }
@@ -169,6 +195,12 @@ export function mapPriceLevel(level) {
169
195
  count: level.count,
170
196
  };
171
197
  }
198
+ /** @internal Maps API price levels and sorts them best first: asks by ascending tick, bids by descending tick. */
199
+ export function mapPriceLevels(levels, side) {
200
+ return levels
201
+ .map(mapPriceLevel)
202
+ .sort((a, b) => (side === "asks" ? a.tick - b.tick : b.tick - a.tick));
203
+ }
172
204
  /** @internal Maps a takeable-offer API payload to the SDK response shape. */
173
205
  export function mapTakeableOffer(takeableOffer) {
174
206
  const offer = takeableOffer.offer;
@@ -212,11 +244,38 @@ export function mapTakeableOffer(takeableOffer) {
212
244
  /** @internal Maps, validates, and best-price-sorts API takeable offers. */
213
245
  export function mapBoundTakeableOffers(takeableOffers, context) {
214
246
  const mapped = takeableOffers.map((takeableOffer) => {
215
- const take = mapTakeableOffer(takeableOffer);
216
- const embeddedMarketId = MarketUtils.toId(take.offer.market);
217
- if (!isHexEqual(embeddedMarketId, take.marketId)) {
247
+ let take;
248
+ let matchesAdvertisedId;
249
+ let embeddedMarketId;
250
+ try {
251
+ take = mapTakeableOffer(takeableOffer);
252
+ embeddedMarketId = MarketUtils.toId(take.offer.market);
253
+ matchesAdvertisedId = isHexEqual(embeddedMarketId, take.marketId);
254
+ }
255
+ catch (cause) {
256
+ throw new InvalidMidnightApiResponseError(`Midnight API takeable offer market_id "${takeableOffer.market_id}" could not be mapped or validated against its embedded offer market.`, { cause });
257
+ }
258
+ if (!matchesAdvertisedId) {
218
259
  throw new InvalidMidnightApiResponseError(`Midnight API takeable offer market_id "${take.marketId}" does not match embedded offer market "${embeddedMarketId}".`);
219
260
  }
261
+ const { maxUnits, maxAssets, buy, maker, receiverIfMakerIsSeller } = take.offer;
262
+ if (!isAddress(receiverIfMakerIsSeller)) {
263
+ throw new InvalidMidnightApiResponseError(`Midnight API takeable offer receiverIfMakerIsSeller "${receiverIfMakerIsSeller}" is not an address.`);
264
+ }
265
+ try {
266
+ // Return values unused: called only to enforce the cap-shape and buy-receiver invariants.
267
+ OfferUtils.validateOfferCaps({ maxUnits, maxAssets });
268
+ OfferUtils.resolveReceiverIfMakerIsSeller({
269
+ buy,
270
+ maker,
271
+ receiverIfMakerIsSeller,
272
+ });
273
+ }
274
+ catch (cause) {
275
+ if (!(cause instanceof InvalidOfferParameterError))
276
+ throw cause;
277
+ throw new InvalidMidnightApiResponseError(`Midnight API takeable offer for market_id "${take.marketId}" is not executable: ${cause.message}`, { cause });
278
+ }
220
279
  if (context.marketId != null &&
221
280
  !isHexEqual(take.marketId, context.marketId)) {
222
281
  throw new InvalidMidnightApiResponseError(`Midnight API takeable offer market_id "${take.marketId}" does not match requested market "${context.marketId}".`);
@@ -1,2 +1,2 @@
1
- export { InvalidMidnightApiResponseError, MidnightApiError, } from "../errors.js";
1
+ export { InvalidMidnightApiQuoteTargetError, InvalidMidnightApiResponseError, MidnightApiError, } from "../errors.js";
2
2
  export * from "./MidnightApi.js";
@@ -1,2 +1,2 @@
1
- export { InvalidMidnightApiResponseError, MidnightApiError, } from "../errors.js";
1
+ export { InvalidMidnightApiQuoteTargetError, InvalidMidnightApiResponseError, MidnightApiError, } from "../errors.js";
2
2
  export * from "./MidnightApi.js";
@@ -341,7 +341,7 @@ export interface MidnightApiBookMarket {
341
341
  readonly midnight: Address;
342
342
  /** Loan token address. */
343
343
  readonly loanToken: Address;
344
- /** Collateral definitions for the market. */
344
+ /** Collateral definitions for the market, in canonical `collateralIndex` order. */
345
345
  readonly collaterals: readonly MidnightApiCollateral[];
346
346
  /** Market maturity timestamp in unix seconds. */
347
347
  readonly maturity: number;
@@ -1,2 +1,2 @@
1
1
  /** @internal Installed Midnight SDK package version. */
2
- export declare const MIDNIGHT_SDK_VERSION = "1.6.0";
2
+ export declare const MIDNIGHT_SDK_VERSION = "1.7.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.6.0";
3
+ export const MIDNIGHT_SDK_VERSION = "1.7.0";
@@ -339,3 +339,16 @@ export declare class MidnightApiError extends Error {
339
339
  export declare class InvalidMidnightApiResponseError extends Error {
340
340
  constructor(message: string, options?: ErrorOptions);
341
341
  }
342
+ /**
343
+ * Thrown when a Midnight API quote request does not target exactly one of `units` or `assets`.
344
+ *
345
+ * @example
346
+ * ```ts
347
+ * import { InvalidMidnightApiQuoteTargetError } from "@morpho-org/midnight-sdk/api";
348
+ *
349
+ * throw new InvalidMidnightApiQuoteTargetError();
350
+ * ```
351
+ */
352
+ export declare class InvalidMidnightApiQuoteTargetError extends Error {
353
+ constructor(options?: ErrorOptions);
354
+ }
package/lib/esm/errors.js CHANGED
@@ -395,3 +395,19 @@ export class InvalidMidnightApiResponseError extends Error {
395
395
  this.name = "InvalidMidnightApiResponseError";
396
396
  }
397
397
  }
398
+ /**
399
+ * Thrown when a Midnight API quote request does not target exactly one of `units` or `assets`.
400
+ *
401
+ * @example
402
+ * ```ts
403
+ * import { InvalidMidnightApiQuoteTargetError } from "@morpho-org/midnight-sdk/api";
404
+ *
405
+ * throw new InvalidMidnightApiQuoteTargetError();
406
+ * ```
407
+ */
408
+ export class InvalidMidnightApiQuoteTargetError extends Error {
409
+ constructor(options) {
410
+ super("Midnight API quote requires exactly one of `units` or `assets`. Provide a single target amount.", options);
411
+ this.name = "InvalidMidnightApiQuoteTargetError";
412
+ }
413
+ }
@@ -292,11 +292,7 @@ export type MarketInput = IMarketParams | IMarket;
292
292
  * registerCustomAddresses({
293
293
  * addresses: {
294
294
  * 31337: {
295
- * morpho: "0x0000000000000000000000000000000000000001",
296
- * bundler3: {
297
- * bundler3: "0x0000000000000000000000000000000000000002",
298
- * generalAdapter1: "0x0000000000000000000000000000000000000003",
299
- * },
295
+ * blue: "0x0000000000000000000000000000000000000001",
300
296
  * adaptiveCurveIrm: "0x0000000000000000000000000000000000000004",
301
297
  * midnight: "0x0000000000000000000000000000000000001000",
302
298
  * },
@@ -412,11 +408,7 @@ export declare class Market {
412
408
  * registerCustomAddresses({
413
409
  * addresses: {
414
410
  * 31337: {
415
- * morpho: "0x0000000000000000000000000000000000000001",
416
- * bundler3: {
417
- * bundler3: "0x0000000000000000000000000000000000000002",
418
- * generalAdapter1: "0x0000000000000000000000000000000000000003",
419
- * },
411
+ * blue: "0x0000000000000000000000000000000000000001",
420
412
  * adaptiveCurveIrm: "0x0000000000000000000000000000000000000004",
421
413
  * midnight: "0x0000000000000000000000000000000000001000",
422
414
  * },
@@ -467,11 +459,7 @@ export declare class Market {
467
459
  * registerCustomAddresses({
468
460
  * addresses: {
469
461
  * 31337: {
470
- * morpho: "0x0000000000000000000000000000000000000001",
471
- * bundler3: {
472
- * bundler3: "0x0000000000000000000000000000000000000002",
473
- * generalAdapter1: "0x0000000000000000000000000000000000000003",
474
- * },
462
+ * blue: "0x0000000000000000000000000000000000000001",
475
463
  * adaptiveCurveIrm: "0x0000000000000000000000000000000000000004",
476
464
  * midnight: "0x0000000000000000000000000000000000001000",
477
465
  * },
@@ -522,11 +510,7 @@ export declare class Market {
522
510
  * registerCustomAddresses({
523
511
  * addresses: {
524
512
  * 31337: {
525
- * morpho: "0x0000000000000000000000000000000000000001",
526
- * bundler3: {
527
- * bundler3: "0x0000000000000000000000000000000000000002",
528
- * generalAdapter1: "0x0000000000000000000000000000000000000003",
529
- * },
513
+ * blue: "0x0000000000000000000000000000000000000001",
530
514
  * adaptiveCurveIrm: "0x0000000000000000000000000000000000000004",
531
515
  * midnight: "0x0000000000000000000000000000000000001000",
532
516
  * },
@@ -577,11 +561,7 @@ export declare class Market {
577
561
  * registerCustomAddresses({
578
562
  * addresses: {
579
563
  * 31337: {
580
- * morpho: "0x0000000000000000000000000000000000000001",
581
- * bundler3: {
582
- * bundler3: "0x0000000000000000000000000000000000000002",
583
- * generalAdapter1: "0x0000000000000000000000000000000000000003",
584
- * },
564
+ * blue: "0x0000000000000000000000000000000000000001",
585
565
  * adaptiveCurveIrm: "0x0000000000000000000000000000000000000004",
586
566
  * midnight: "0x0000000000000000000000000000000000001000",
587
567
  * },
@@ -633,11 +613,7 @@ export declare class Market {
633
613
  * registerCustomAddresses({
634
614
  * addresses: {
635
615
  * 31337: {
636
- * morpho: "0x0000000000000000000000000000000000000001",
637
- * bundler3: {
638
- * bundler3: "0x0000000000000000000000000000000000000002",
639
- * generalAdapter1: "0x0000000000000000000000000000000000000003",
640
- * },
616
+ * blue: "0x0000000000000000000000000000000000000001",
641
617
  * adaptiveCurveIrm: "0x0000000000000000000000000000000000000004",
642
618
  * midnight: "0x0000000000000000000000000000000000001000",
643
619
  * },
@@ -132,7 +132,7 @@ export class MarketParams {
132
132
  }
133
133
  seenCollateralTokens.add(token);
134
134
  }
135
- this.collateralParams = collateralParams.sort((a, b) => a.token.toLowerCase() < b.token.toLowerCase() ? -1 : 1);
135
+ this.collateralParams = collateralParams.sort(MarketUtils.compareCollateralTokens);
136
136
  this.maturity = BigInt(params.maturity);
137
137
  this.rcfThreshold = BigInt(params.rcfThreshold);
138
138
  this.enterGate = params.enterGate;
@@ -187,11 +187,7 @@ export class MarketParams {
187
187
  * registerCustomAddresses({
188
188
  * addresses: {
189
189
  * 31337: {
190
- * morpho: "0x0000000000000000000000000000000000000001",
191
- * bundler3: {
192
- * bundler3: "0x0000000000000000000000000000000000000002",
193
- * generalAdapter1: "0x0000000000000000000000000000000000000003",
194
- * },
190
+ * blue: "0x0000000000000000000000000000000000000001",
195
191
  * adaptiveCurveIrm: "0x0000000000000000000000000000000000000004",
196
192
  * midnight: "0x0000000000000000000000000000000000001000",
197
193
  * },
@@ -320,11 +316,7 @@ export class Market {
320
316
  * registerCustomAddresses({
321
317
  * addresses: {
322
318
  * 31337: {
323
- * morpho: "0x0000000000000000000000000000000000000001",
324
- * bundler3: {
325
- * bundler3: "0x0000000000000000000000000000000000000002",
326
- * generalAdapter1: "0x0000000000000000000000000000000000000003",
327
- * },
319
+ * blue: "0x0000000000000000000000000000000000000001",
328
320
  * adaptiveCurveIrm: "0x0000000000000000000000000000000000000004",
329
321
  * midnight: "0x0000000000000000000000000000000000001000",
330
322
  * },
@@ -381,11 +373,7 @@ export class Market {
381
373
  * registerCustomAddresses({
382
374
  * addresses: {
383
375
  * 31337: {
384
- * morpho: "0x0000000000000000000000000000000000000001",
385
- * bundler3: {
386
- * bundler3: "0x0000000000000000000000000000000000000002",
387
- * generalAdapter1: "0x0000000000000000000000000000000000000003",
388
- * },
376
+ * blue: "0x0000000000000000000000000000000000000001",
389
377
  * adaptiveCurveIrm: "0x0000000000000000000000000000000000000004",
390
378
  * midnight: "0x0000000000000000000000000000000000001000",
391
379
  * },
@@ -441,11 +429,7 @@ export class Market {
441
429
  * registerCustomAddresses({
442
430
  * addresses: {
443
431
  * 31337: {
444
- * morpho: "0x0000000000000000000000000000000000000001",
445
- * bundler3: {
446
- * bundler3: "0x0000000000000000000000000000000000000002",
447
- * generalAdapter1: "0x0000000000000000000000000000000000000003",
448
- * },
432
+ * blue: "0x0000000000000000000000000000000000000001",
449
433
  * adaptiveCurveIrm: "0x0000000000000000000000000000000000000004",
450
434
  * midnight: "0x0000000000000000000000000000000000001000",
451
435
  * },
@@ -498,11 +482,7 @@ export class Market {
498
482
  * registerCustomAddresses({
499
483
  * addresses: {
500
484
  * 31337: {
501
- * morpho: "0x0000000000000000000000000000000000000001",
502
- * bundler3: {
503
- * bundler3: "0x0000000000000000000000000000000000000002",
504
- * generalAdapter1: "0x0000000000000000000000000000000000000003",
505
- * },
485
+ * blue: "0x0000000000000000000000000000000000000001",
506
486
  * adaptiveCurveIrm: "0x0000000000000000000000000000000000000004",
507
487
  * midnight: "0x0000000000000000000000000000000000001000",
508
488
  * },
@@ -558,11 +538,7 @@ export class Market {
558
538
  * registerCustomAddresses({
559
539
  * addresses: {
560
540
  * 31337: {
561
- * morpho: "0x0000000000000000000000000000000000000001",
562
- * bundler3: {
563
- * bundler3: "0x0000000000000000000000000000000000000002",
564
- * generalAdapter1: "0x0000000000000000000000000000000000000003",
565
- * },
541
+ * blue: "0x0000000000000000000000000000000000000001",
566
542
  * adaptiveCurveIrm: "0x0000000000000000000000000000000000000004",
567
543
  * midnight: "0x0000000000000000000000000000000000001000",
568
544
  * },
@@ -1,4 +1,5 @@
1
1
  import { type BigIntish } from "@morpho-org/morpho-ts";
2
+ import { type Address } from "viem";
2
3
  import { type CollateralParams, type ICollateralParams, type MarketInput, MarketParams, type SettlementFeeCbps } from "./Market.js";
3
4
  type CollateralParamsInput = ICollateralParams | CollateralParams;
4
5
  /**
@@ -12,6 +13,19 @@ type CollateralParamsInput = ICollateralParams | CollateralParams;
12
13
  * ```
13
14
  */
14
15
  export declare namespace MarketUtils {
16
+ /**
17
+ * @internal Orders collaterals by token address, case-insensitively, matching the protocol's
18
+ * canonical `collateralIndex` order used by `MarketParams` and `MarketUtils.hash`.
19
+ *
20
+ * @param a - First collateral.
21
+ * @param b - Second collateral.
22
+ * @returns Negative when `a` sorts before `b`, positive when after, `0` when tokens are equal.
23
+ */
24
+ function compareCollateralTokens(a: {
25
+ readonly token: Address;
26
+ }, b: {
27
+ readonly token: Address;
28
+ }): -1 | 0 | 1;
15
29
  /**
16
30
  * Converts collateral params from a plain input or ABI tuple.
17
31
  *
@@ -1,5 +1,5 @@
1
1
  import { assertNonNegative, MathLib, } from "@morpho-org/morpho-ts";
2
- import { concat, encodeAbiParameters, encodePacked, keccak256 } from "viem";
2
+ import { concat, encodeAbiParameters, encodePacked, keccak256, } from "viem";
3
3
  import { CBP, COLLATERAL_PARAMS_TYPEHASH, MARKET_TYPEHASH, SETTLEMENT_FEE_BREAKPOINTS, } from "../constants.js";
4
4
  import { InvalidMarketParameterError, UnknownCollateralIndexError, } from "../errors.js";
5
5
  import { MarketParams, } from "./Market.js";
@@ -56,6 +56,22 @@ const marketParamsAbiParameter = {
56
56
  */
57
57
  export var MarketUtils;
58
58
  (function (MarketUtils) {
59
+ /**
60
+ * @internal Orders collaterals by token address, case-insensitively, matching the protocol's
61
+ * canonical `collateralIndex` order used by `MarketParams` and `MarketUtils.hash`.
62
+ *
63
+ * @param a - First collateral.
64
+ * @param b - Second collateral.
65
+ * @returns Negative when `a` sorts before `b`, positive when after, `0` when tokens are equal.
66
+ */
67
+ function compareCollateralTokens(a, b) {
68
+ const tokenA = a.token.toLowerCase();
69
+ const tokenB = b.token.toLowerCase();
70
+ if (tokenA === tokenB)
71
+ return 0;
72
+ return tokenA < tokenB ? -1 : 1;
73
+ }
74
+ MarketUtils.compareCollateralTokens = compareCollateralTokens;
59
75
  /**
60
76
  * Converts collateral params from a plain input or ABI tuple.
61
77
  *
@@ -299,7 +315,7 @@ export var MarketUtils;
299
315
  const marketParams = "params" in market ? market.params : market;
300
316
  const collateralParams = marketParams.collateralParams.length === 0
301
317
  ? marketParams.collateralParams
302
- : [...marketParams.collateralParams].sort((a, b) => a.token.toLowerCase() < b.token.toLowerCase() ? -1 : 1);
318
+ : [...marketParams.collateralParams].sort(compareCollateralTokens);
303
319
  const collateralParamHashes = collateralParams.map((params) => keccak256(encodeAbiParameters(collateralParamsHashParams, [
304
320
  COLLATERAL_PARAMS_TYPEHASH,
305
321
  params.token,
@@ -526,7 +526,7 @@ export declare class Offer {
526
526
  * @param params.tickSpacing - Optional market tick spacing; defaults to `DEFAULT_TICK_SPACING`.
527
527
  * @param params.maxUnits - Optional unit cap; defaults to zero.
528
528
  * @param params.maxAssets - Optional buyer or seller asset cap; defaults to zero.
529
- * @param params.continuousFeeCap - Optional maximum market continuous fee accepted by this offer; defaults to `MAX_CONTINUOUS_FEE`.
529
+ * @param params.continuousFeeCap - Optional maximum market continuous fee accepted by this offer; defaults to `0n` (fail-closed: no market continuous fee accepted unless set explicitly).
530
530
  * @param params.start - Optional offer start timestamp; defaults to zero.
531
531
  * @param params.expiry - Offer expiry timestamp.
532
532
  * @param params.callback - Optional callback address; defaults to the zero address.