@defisaver/positions-sdk 2.1.127-midnight-4-dev → 2.1.127-midnight-5-dev

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.
@@ -43,6 +43,17 @@ export interface MorphoMidnightPaybackQuote {
43
43
  availableUnits: string;
44
44
  takeableOffers: any[];
45
45
  }
46
+ export interface MorphoMidnightPaybackUnitsQuote {
47
+ bestPrice: string;
48
+ worstPrice: string;
49
+ estPaybackRate: string;
50
+ minRate: string;
51
+ newAssets: string;
52
+ maxAssets: string;
53
+ availableAssets: string;
54
+ availableUnits: string;
55
+ takeableOffers: any[];
56
+ }
46
57
  export declare const midnightTimeToMaturityDays: (maturity: number, atSeconds?: number) => number;
47
58
  export declare const midnightApyFromPrice: (price: Dec.Value, ttmDays: Dec.Value) => string;
48
59
  /**
@@ -123,3 +134,19 @@ export declare const getMorphoMidnightBorrowQuote: (marketId: string, assetsRaw:
123
134
  * on-chain. Compare the two before submitting and tell the user their floor is over the market rate.
124
135
  */
125
136
  export declare const getMorphoMidnightPaybackQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value) => Promise<MorphoMidnightPaybackQuote>;
137
+ /**
138
+ * Payback quoted against a **units** target instead of a spend: "retire exactly these debt units, and tell
139
+ * me what that costs". The sibling of `getMorphoMidnightPaybackQuote` in every other respect — same ask
140
+ * side, same rate floor, same offer list — but with the two amounts swapped, so the on-chain guard becomes
141
+ * a ceiling on assets spent rather than a floor on units bought.
142
+ *
143
+ * This is what a **full close** must be sized with. Retiring N units costs less than N loan tokens before
144
+ * maturity, so quoting the close as a spend of the debt's face value asks the book for more depth than the
145
+ * close needs (and can be refused for liquidity that is in fact there), and caps the taker's spend at more
146
+ * than the position is worth. Callers that cannot refund an overspend — a taker calling the bundler
147
+ * directly, rather than through an action contract that sweeps the remainder back — need this quote.
148
+ *
149
+ * A `minPaybackRate` above `estPaybackRate` yields `maxAssets < newAssets`: the ceiling is under what the
150
+ * buy costs and it would revert on-chain, the mirror of the assets-target quote's `minUnits > newUnits`.
151
+ */
152
+ export declare const getMorphoMidnightPaybackUnitsQuote: (marketId: string, unitsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value) => Promise<MorphoMidnightPaybackUnitsQuote>;
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.getMorphoMidnightPaybackQuote = exports.getMorphoMidnightBorrowQuote = exports.getMorphoMidnightMarketBook = exports.getMorphoMidnightUserBorrowInfo = exports.midnightSlippageParam = exports.midnightPriceFromApy = exports.midnightApyFromPrice = exports.midnightTimeToMaturityDays = exports.getMorphoMidnightAggregatedPositionData = void 0;
15
+ exports.getMorphoMidnightPaybackUnitsQuote = exports.getMorphoMidnightPaybackQuote = exports.getMorphoMidnightBorrowQuote = exports.getMorphoMidnightMarketBook = exports.getMorphoMidnightUserBorrowInfo = exports.midnightSlippageParam = exports.midnightPriceFromApy = exports.midnightApyFromPrice = exports.midnightTimeToMaturityDays = exports.getMorphoMidnightAggregatedPositionData = void 0;
16
16
  const decimal_js_1 = __importDefault(require("decimal.js"));
17
17
  const tokens_1 = require("@defisaver/tokens");
18
18
  const moneymarket_1 = require("../../moneymarket");
@@ -202,8 +202,9 @@ const midnightQuoteError = (error) => {
202
202
  return reason ? `Morpho Midnight quote unavailable: ${reason}` : 'Morpho Midnight quote unavailable';
203
203
  };
204
204
  // The raw quote both sides share: prices descaled from WAD, everything else forwarded verbatim.
205
- const fetchMorphoMidnightQuote = (marketId, side, assetsRaw, slippagePercent) => __awaiter(void 0, void 0, void 0, function* () {
206
- const url = `${MIDNIGHT_API_BASE}/books/${marketId}/${side}/quote?assets=${assetsRaw}&slippage=${(0, exports.midnightSlippageParam)(slippagePercent)}`;
205
+ const fetchMorphoMidnightQuote = (marketId, side, size, slippagePercent) => __awaiter(void 0, void 0, void 0, function* () {
206
+ const [sizeParam, sizeValue] = 'assets' in size ? ['assets', size.assets] : ['units', size.units];
207
+ const url = `${MIDNIGHT_API_BASE}/books/${marketId}/${side}/quote?${sizeParam}=${sizeValue}&slippage=${(0, exports.midnightSlippageParam)(slippagePercent)}`;
207
208
  const res = yield fetch(url, { signal: AbortSignal.timeout(utils_1.LONGER_TIMEOUT) });
208
209
  const json = yield res.json();
209
210
  const d = json === null || json === void 0 ? void 0 : json.data;
@@ -235,7 +236,7 @@ const fetchMorphoMidnightQuote = (marketId, side, assetsRaw, slippagePercent) =>
235
236
  * Compare the two before submitting and tell the user their ceiling is under the market rate.
236
237
  */
237
238
  const getMorphoMidnightBorrowQuote = (marketId, assetsRaw, slippagePercent, maturity, maxBorrowRate) => __awaiter(void 0, void 0, void 0, function* () {
238
- const quote = yield fetchMorphoMidnightQuote(marketId, 'bids', assetsRaw, slippagePercent);
239
+ const quote = yield fetchMorphoMidnightQuote(marketId, 'bids', { assets: assetsRaw }, slippagePercent);
239
240
  const { bestPrice, worstPrice } = quote;
240
241
  const ttmDays = (0, exports.midnightTimeToMaturityDays)(maturity);
241
242
  const estBorrowRate = (0, exports.midnightApyFromPrice)(bestPrice, ttmDays);
@@ -272,7 +273,7 @@ exports.getMorphoMidnightBorrowQuote = getMorphoMidnightBorrowQuote;
272
273
  * on-chain. Compare the two before submitting and tell the user their floor is over the market rate.
273
274
  */
274
275
  const getMorphoMidnightPaybackQuote = (marketId, assetsRaw, slippagePercent, maturity, minPaybackRate) => __awaiter(void 0, void 0, void 0, function* () {
275
- const quote = yield fetchMorphoMidnightQuote(marketId, 'asks', assetsRaw, slippagePercent);
276
+ const quote = yield fetchMorphoMidnightQuote(marketId, 'asks', { assets: assetsRaw }, slippagePercent);
276
277
  const { bestPrice, worstPrice } = quote;
277
278
  const ttmDays = (0, exports.midnightTimeToMaturityDays)(maturity);
278
279
  const estPaybackRate = (0, exports.midnightApyFromPrice)(bestPrice, ttmDays);
@@ -290,3 +291,38 @@ const getMorphoMidnightPaybackQuote = (marketId, assetsRaw, slippagePercent, mat
290
291
  minUnits });
291
292
  });
292
293
  exports.getMorphoMidnightPaybackQuote = getMorphoMidnightPaybackQuote;
294
+ /**
295
+ * Payback quoted against a **units** target instead of a spend: "retire exactly these debt units, and tell
296
+ * me what that costs". The sibling of `getMorphoMidnightPaybackQuote` in every other respect — same ask
297
+ * side, same rate floor, same offer list — but with the two amounts swapped, so the on-chain guard becomes
298
+ * a ceiling on assets spent rather than a floor on units bought.
299
+ *
300
+ * This is what a **full close** must be sized with. Retiring N units costs less than N loan tokens before
301
+ * maturity, so quoting the close as a spend of the debt's face value asks the book for more depth than the
302
+ * close needs (and can be refused for liquidity that is in fact there), and caps the taker's spend at more
303
+ * than the position is worth. Callers that cannot refund an overspend — a taker calling the bundler
304
+ * directly, rather than through an action contract that sweeps the remainder back — need this quote.
305
+ *
306
+ * A `minPaybackRate` above `estPaybackRate` yields `maxAssets < newAssets`: the ceiling is under what the
307
+ * buy costs and it would revert on-chain, the mirror of the assets-target quote's `minUnits > newUnits`.
308
+ */
309
+ const getMorphoMidnightPaybackUnitsQuote = (marketId, unitsRaw, slippagePercent, maturity, minPaybackRate) => __awaiter(void 0, void 0, void 0, function* () {
310
+ const quote = yield fetchMorphoMidnightQuote(marketId, 'asks', { units: unitsRaw }, slippagePercent);
311
+ const { bestPrice, worstPrice } = quote;
312
+ const ttmDays = (0, exports.midnightTimeToMaturityDays)(maturity);
313
+ const estPaybackRate = (0, exports.midnightApyFromPrice)(bestPrice, ttmDays);
314
+ const capPrice = minPaybackRate !== undefined && new decimal_js_1.default(minPaybackRate).gt(0)
315
+ ? (0, exports.midnightPriceFromApy)(minPaybackRate, ttmDays)
316
+ : worstPrice;
317
+ const minRate = (0, exports.midnightApyFromPrice)(capPrice, ttmDays);
318
+ // Rounded UP on both counts — the mirror of the assets-target quote's ROUND_DOWN. Here the figures are
319
+ // what the taker SPENDS, so a value rounded down understates the cost by a base unit and would leave the
320
+ // buy short of the units it was asked for.
321
+ const newAssets = new decimal_js_1.default(unitsRaw).mul(bestPrice).toFixed(0, decimal_js_1.default.ROUND_UP);
322
+ const maxAssets = new decimal_js_1.default(unitsRaw).mul(capPrice).toFixed(0, decimal_js_1.default.ROUND_UP);
323
+ return Object.assign(Object.assign({}, quote), { estPaybackRate,
324
+ minRate,
325
+ newAssets,
326
+ maxAssets });
327
+ });
328
+ exports.getMorphoMidnightPaybackUnitsQuote = getMorphoMidnightPaybackUnitsQuote;
@@ -43,6 +43,17 @@ export interface MorphoMidnightPaybackQuote {
43
43
  availableUnits: string;
44
44
  takeableOffers: any[];
45
45
  }
46
+ export interface MorphoMidnightPaybackUnitsQuote {
47
+ bestPrice: string;
48
+ worstPrice: string;
49
+ estPaybackRate: string;
50
+ minRate: string;
51
+ newAssets: string;
52
+ maxAssets: string;
53
+ availableAssets: string;
54
+ availableUnits: string;
55
+ takeableOffers: any[];
56
+ }
46
57
  export declare const midnightTimeToMaturityDays: (maturity: number, atSeconds?: number) => number;
47
58
  export declare const midnightApyFromPrice: (price: Dec.Value, ttmDays: Dec.Value) => string;
48
59
  /**
@@ -123,3 +134,19 @@ export declare const getMorphoMidnightBorrowQuote: (marketId: string, assetsRaw:
123
134
  * on-chain. Compare the two before submitting and tell the user their floor is over the market rate.
124
135
  */
125
136
  export declare const getMorphoMidnightPaybackQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value) => Promise<MorphoMidnightPaybackQuote>;
137
+ /**
138
+ * Payback quoted against a **units** target instead of a spend: "retire exactly these debt units, and tell
139
+ * me what that costs". The sibling of `getMorphoMidnightPaybackQuote` in every other respect — same ask
140
+ * side, same rate floor, same offer list — but with the two amounts swapped, so the on-chain guard becomes
141
+ * a ceiling on assets spent rather than a floor on units bought.
142
+ *
143
+ * This is what a **full close** must be sized with. Retiring N units costs less than N loan tokens before
144
+ * maturity, so quoting the close as a spend of the debt's face value asks the book for more depth than the
145
+ * close needs (and can be refused for liquidity that is in fact there), and caps the taker's spend at more
146
+ * than the position is worth. Callers that cannot refund an overspend — a taker calling the bundler
147
+ * directly, rather than through an action contract that sweeps the remainder back — need this quote.
148
+ *
149
+ * A `minPaybackRate` above `estPaybackRate` yields `maxAssets < newAssets`: the ceiling is under what the
150
+ * buy costs and it would revert on-chain, the mirror of the assets-target quote's `minUnits > newUnits`.
151
+ */
152
+ export declare const getMorphoMidnightPaybackUnitsQuote: (marketId: string, unitsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value) => Promise<MorphoMidnightPaybackUnitsQuote>;
@@ -189,8 +189,9 @@ const midnightQuoteError = (error) => {
189
189
  return reason ? `Morpho Midnight quote unavailable: ${reason}` : 'Morpho Midnight quote unavailable';
190
190
  };
191
191
  // The raw quote both sides share: prices descaled from WAD, everything else forwarded verbatim.
192
- const fetchMorphoMidnightQuote = (marketId, side, assetsRaw, slippagePercent) => __awaiter(void 0, void 0, void 0, function* () {
193
- const url = `${MIDNIGHT_API_BASE}/books/${marketId}/${side}/quote?assets=${assetsRaw}&slippage=${midnightSlippageParam(slippagePercent)}`;
192
+ const fetchMorphoMidnightQuote = (marketId, side, size, slippagePercent) => __awaiter(void 0, void 0, void 0, function* () {
193
+ const [sizeParam, sizeValue] = 'assets' in size ? ['assets', size.assets] : ['units', size.units];
194
+ const url = `${MIDNIGHT_API_BASE}/books/${marketId}/${side}/quote?${sizeParam}=${sizeValue}&slippage=${midnightSlippageParam(slippagePercent)}`;
194
195
  const res = yield fetch(url, { signal: AbortSignal.timeout(LONGER_TIMEOUT) });
195
196
  const json = yield res.json();
196
197
  const d = json === null || json === void 0 ? void 0 : json.data;
@@ -222,7 +223,7 @@ const fetchMorphoMidnightQuote = (marketId, side, assetsRaw, slippagePercent) =>
222
223
  * Compare the two before submitting and tell the user their ceiling is under the market rate.
223
224
  */
224
225
  export const getMorphoMidnightBorrowQuote = (marketId, assetsRaw, slippagePercent, maturity, maxBorrowRate) => __awaiter(void 0, void 0, void 0, function* () {
225
- const quote = yield fetchMorphoMidnightQuote(marketId, 'bids', assetsRaw, slippagePercent);
226
+ const quote = yield fetchMorphoMidnightQuote(marketId, 'bids', { assets: assetsRaw }, slippagePercent);
226
227
  const { bestPrice, worstPrice } = quote;
227
228
  const ttmDays = midnightTimeToMaturityDays(maturity);
228
229
  const estBorrowRate = midnightApyFromPrice(bestPrice, ttmDays);
@@ -258,7 +259,7 @@ export const getMorphoMidnightBorrowQuote = (marketId, assetsRaw, slippagePercen
258
259
  * on-chain. Compare the two before submitting and tell the user their floor is over the market rate.
259
260
  */
260
261
  export const getMorphoMidnightPaybackQuote = (marketId, assetsRaw, slippagePercent, maturity, minPaybackRate) => __awaiter(void 0, void 0, void 0, function* () {
261
- const quote = yield fetchMorphoMidnightQuote(marketId, 'asks', assetsRaw, slippagePercent);
262
+ const quote = yield fetchMorphoMidnightQuote(marketId, 'asks', { assets: assetsRaw }, slippagePercent);
262
263
  const { bestPrice, worstPrice } = quote;
263
264
  const ttmDays = midnightTimeToMaturityDays(maturity);
264
265
  const estPaybackRate = midnightApyFromPrice(bestPrice, ttmDays);
@@ -275,3 +276,37 @@ export const getMorphoMidnightPaybackQuote = (marketId, assetsRaw, slippagePerce
275
276
  newUnits,
276
277
  minUnits });
277
278
  });
279
+ /**
280
+ * Payback quoted against a **units** target instead of a spend: "retire exactly these debt units, and tell
281
+ * me what that costs". The sibling of `getMorphoMidnightPaybackQuote` in every other respect — same ask
282
+ * side, same rate floor, same offer list — but with the two amounts swapped, so the on-chain guard becomes
283
+ * a ceiling on assets spent rather than a floor on units bought.
284
+ *
285
+ * This is what a **full close** must be sized with. Retiring N units costs less than N loan tokens before
286
+ * maturity, so quoting the close as a spend of the debt's face value asks the book for more depth than the
287
+ * close needs (and can be refused for liquidity that is in fact there), and caps the taker's spend at more
288
+ * than the position is worth. Callers that cannot refund an overspend — a taker calling the bundler
289
+ * directly, rather than through an action contract that sweeps the remainder back — need this quote.
290
+ *
291
+ * A `minPaybackRate` above `estPaybackRate` yields `maxAssets < newAssets`: the ceiling is under what the
292
+ * buy costs and it would revert on-chain, the mirror of the assets-target quote's `minUnits > newUnits`.
293
+ */
294
+ export const getMorphoMidnightPaybackUnitsQuote = (marketId, unitsRaw, slippagePercent, maturity, minPaybackRate) => __awaiter(void 0, void 0, void 0, function* () {
295
+ const quote = yield fetchMorphoMidnightQuote(marketId, 'asks', { units: unitsRaw }, slippagePercent);
296
+ const { bestPrice, worstPrice } = quote;
297
+ const ttmDays = midnightTimeToMaturityDays(maturity);
298
+ const estPaybackRate = midnightApyFromPrice(bestPrice, ttmDays);
299
+ const capPrice = minPaybackRate !== undefined && new Dec(minPaybackRate).gt(0)
300
+ ? midnightPriceFromApy(minPaybackRate, ttmDays)
301
+ : worstPrice;
302
+ const minRate = midnightApyFromPrice(capPrice, ttmDays);
303
+ // Rounded UP on both counts — the mirror of the assets-target quote's ROUND_DOWN. Here the figures are
304
+ // what the taker SPENDS, so a value rounded down understates the cost by a base unit and would leave the
305
+ // buy short of the units it was asked for.
306
+ const newAssets = new Dec(unitsRaw).mul(bestPrice).toFixed(0, Dec.ROUND_UP);
307
+ const maxAssets = new Dec(unitsRaw).mul(capPrice).toFixed(0, Dec.ROUND_UP);
308
+ return Object.assign(Object.assign({}, quote), { estPaybackRate,
309
+ minRate,
310
+ newAssets,
311
+ maxAssets });
312
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defisaver/positions-sdk",
3
- "version": "2.1.127-midnight-4-dev",
3
+ "version": "2.1.127-midnight-5-dev",
4
4
  "description": "",
5
5
  "main": "./cjs/index.js",
6
6
  "module": "./esm/index.js",
@@ -173,6 +173,20 @@ export interface MorphoMidnightPaybackQuote {
173
173
  takeableOffers: any[], // opaque orderbook offers, forwarded verbatim to on-chain execution
174
174
  }
175
175
 
176
+ // Payback quoted the other way round: the caller names the debt units to retire, and the quote prices
177
+ // what buying them costs. Same rate guard as the assets-target quote, expressed as a spend ceiling.
178
+ export interface MorphoMidnightPaybackUnitsQuote {
179
+ bestPrice: string, // average_best_price, loan-per-unit — the cheapest units on the ask side
180
+ worstPrice: string, // average_worst_price, slippage-adjusted (ABOVE best: a dearer unit)
181
+ estPaybackRate: string, // APY the repayment retires debt at, as a percent
182
+ minRate: string, // APY the on-chain ceiling permits, i.e. `maxAssets` annualized (display only)
183
+ newAssets: string, // assets the target units cost at best price, raw loan-token base units
184
+ maxAssets: string, // ceiling on assets spent (on-chain guard), raw loan-token base units
185
+ availableAssets: string,
186
+ availableUnits: string,
187
+ takeableOffers: any[], // opaque orderbook offers, forwarded verbatim to on-chain execution
188
+ }
189
+
176
190
  // Days remaining until maturity, optionally measured at a past timestamp (for historical fills).
177
191
  export const midnightTimeToMaturityDays = (maturity: number, atSeconds: number = nowInSeconds()): number => new Dec(maturity).sub(atSeconds).div(SECONDS_PER_DAY).toNumber();
178
192
 
@@ -302,14 +316,22 @@ interface MidnightParsedQuote {
302
316
  takeableOffers: any[],
303
317
  }
304
318
 
319
+ /**
320
+ * How much of the book to quote. The endpoint takes exactly one of the two — it rejects a request with
321
+ * neither ("Either assets or units must be provided") — and answers the same `takeable_offers` list either
322
+ * way, since that list is the whole in-band depth rather than the slice this size consumes.
323
+ */
324
+ type MidnightQuoteSize = { assets: string } | { units: string };
325
+
305
326
  // The raw quote both sides share: prices descaled from WAD, everything else forwarded verbatim.
306
327
  const fetchMorphoMidnightQuote = async (
307
328
  marketId: string,
308
329
  side: MorphoMidnightBookSide,
309
- assetsRaw: string,
330
+ size: MidnightQuoteSize,
310
331
  slippagePercent: Dec.Value,
311
332
  ): Promise<MidnightParsedQuote> => {
312
- const url = `${MIDNIGHT_API_BASE}/books/${marketId}/${side}/quote?assets=${assetsRaw}&slippage=${midnightSlippageParam(slippagePercent)}`;
333
+ const [sizeParam, sizeValue] = 'assets' in size ? ['assets', size.assets] : ['units', size.units];
334
+ const url = `${MIDNIGHT_API_BASE}/books/${marketId}/${side}/quote?${sizeParam}=${sizeValue}&slippage=${midnightSlippageParam(slippagePercent)}`;
313
335
  const res = await fetch(url, { signal: AbortSignal.timeout(LONGER_TIMEOUT) });
314
336
  const json: { data?: MidnightQuoteResponse, error?: MidnightApiError } = await res.json();
315
337
  const d = json?.data;
@@ -348,7 +370,7 @@ export const getMorphoMidnightBorrowQuote = async (
348
370
  maturity: number,
349
371
  maxBorrowRate?: Dec.Value,
350
372
  ): Promise<MorphoMidnightBorrowQuote> => {
351
- const quote = await fetchMorphoMidnightQuote(marketId, 'bids', assetsRaw, slippagePercent);
373
+ const quote = await fetchMorphoMidnightQuote(marketId, 'bids', { assets: assetsRaw }, slippagePercent);
352
374
  const { bestPrice, worstPrice } = quote;
353
375
  const ttmDays = midnightTimeToMaturityDays(maturity);
354
376
  const estBorrowRate = midnightApyFromPrice(bestPrice, ttmDays);
@@ -396,7 +418,7 @@ export const getMorphoMidnightPaybackQuote = async (
396
418
  maturity: number,
397
419
  minPaybackRate?: Dec.Value,
398
420
  ): Promise<MorphoMidnightPaybackQuote> => {
399
- const quote = await fetchMorphoMidnightQuote(marketId, 'asks', assetsRaw, slippagePercent);
421
+ const quote = await fetchMorphoMidnightQuote(marketId, 'asks', { assets: assetsRaw }, slippagePercent);
400
422
  const { bestPrice, worstPrice } = quote;
401
423
  const ttmDays = midnightTimeToMaturityDays(maturity);
402
424
  const estPaybackRate = midnightApyFromPrice(bestPrice, ttmDays);
@@ -418,3 +440,49 @@ export const getMorphoMidnightPaybackQuote = async (
418
440
  minUnits,
419
441
  };
420
442
  };
443
+
444
+ /**
445
+ * Payback quoted against a **units** target instead of a spend: "retire exactly these debt units, and tell
446
+ * me what that costs". The sibling of `getMorphoMidnightPaybackQuote` in every other respect — same ask
447
+ * side, same rate floor, same offer list — but with the two amounts swapped, so the on-chain guard becomes
448
+ * a ceiling on assets spent rather than a floor on units bought.
449
+ *
450
+ * This is what a **full close** must be sized with. Retiring N units costs less than N loan tokens before
451
+ * maturity, so quoting the close as a spend of the debt's face value asks the book for more depth than the
452
+ * close needs (and can be refused for liquidity that is in fact there), and caps the taker's spend at more
453
+ * than the position is worth. Callers that cannot refund an overspend — a taker calling the bundler
454
+ * directly, rather than through an action contract that sweeps the remainder back — need this quote.
455
+ *
456
+ * A `minPaybackRate` above `estPaybackRate` yields `maxAssets < newAssets`: the ceiling is under what the
457
+ * buy costs and it would revert on-chain, the mirror of the assets-target quote's `minUnits > newUnits`.
458
+ */
459
+ export const getMorphoMidnightPaybackUnitsQuote = async (
460
+ marketId: string,
461
+ unitsRaw: string,
462
+ slippagePercent: Dec.Value,
463
+ maturity: number,
464
+ minPaybackRate?: Dec.Value,
465
+ ): Promise<MorphoMidnightPaybackUnitsQuote> => {
466
+ const quote = await fetchMorphoMidnightQuote(marketId, 'asks', { units: unitsRaw }, slippagePercent);
467
+ const { bestPrice, worstPrice } = quote;
468
+ const ttmDays = midnightTimeToMaturityDays(maturity);
469
+ const estPaybackRate = midnightApyFromPrice(bestPrice, ttmDays);
470
+
471
+ const capPrice = minPaybackRate !== undefined && new Dec(minPaybackRate).gt(0)
472
+ ? midnightPriceFromApy(minPaybackRate, ttmDays)
473
+ : worstPrice;
474
+ const minRate = midnightApyFromPrice(capPrice, ttmDays);
475
+ // Rounded UP on both counts — the mirror of the assets-target quote's ROUND_DOWN. Here the figures are
476
+ // what the taker SPENDS, so a value rounded down understates the cost by a base unit and would leave the
477
+ // buy short of the units it was asked for.
478
+ const newAssets = new Dec(unitsRaw).mul(bestPrice).toFixed(0, Dec.ROUND_UP);
479
+ const maxAssets = new Dec(unitsRaw).mul(capPrice).toFixed(0, Dec.ROUND_UP);
480
+
481
+ return {
482
+ ...quote,
483
+ estPaybackRate,
484
+ minRate,
485
+ newAssets,
486
+ maxAssets,
487
+ };
488
+ };