@defisaver/positions-sdk 2.1.127-midnight-9-dev → 2.1.127-midnight-11-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.
- package/cjs/helpers/morphoMidnightHelpers/index.d.ts +4 -57
- package/cjs/helpers/morphoMidnightHelpers/index.js +15 -72
- package/cjs/helpers/morphoMidnightHelpers/rate.d.ts +4 -0
- package/cjs/helpers/morphoMidnightHelpers/rate.js +14 -1
- package/cjs/helpers/morphoMidnightHelpers/tenor.d.ts +8 -4
- package/cjs/helpers/morphoMidnightHelpers/tenor.js +12 -14
- package/cjs/morphoMidnight/index.js +8 -6
- package/esm/helpers/morphoMidnightHelpers/index.d.ts +4 -57
- package/esm/helpers/morphoMidnightHelpers/index.js +14 -73
- package/esm/helpers/morphoMidnightHelpers/rate.d.ts +4 -0
- package/esm/helpers/morphoMidnightHelpers/rate.js +12 -0
- package/esm/helpers/morphoMidnightHelpers/tenor.d.ts +8 -4
- package/esm/helpers/morphoMidnightHelpers/tenor.js +12 -13
- package/esm/morphoMidnight/index.js +8 -6
- package/package.json +1 -1
- package/src/helpers/morphoMidnightHelpers/index.ts +16 -69
- package/src/helpers/morphoMidnightHelpers/rate.ts +25 -0
- package/src/helpers/morphoMidnightHelpers/tenor.ts +12 -16
- package/src/morphoMidnight/index.ts +11 -9
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Dec from 'decimal.js';
|
|
2
2
|
import { MMUsedAssets, NetworkNumber } from '../../types/common';
|
|
3
3
|
import { MorphoMidnightAggregatedPositionData, MorphoMidnightAssetsData, MorphoMidnightBookSide, MorphoMidnightMarketData, MorphoMidnightMarketInfo, MorphoMidnightParsedBook } from '../../types';
|
|
4
|
-
export { buildMidnightParsedBook, midnightApyFromPrice, midnightBookBestFirst, midnightPriceFromApy, midnightTimeToMaturityDays, } from './rate';
|
|
4
|
+
export { buildMidnightParsedBook, midnightApyFromPrice, midnightBookBestFirst, midnightBoundPrice, midnightPriceFromApy, midnightTimeToMaturityDays, MIDNIGHT_DEFAULT_RATE_SLIPPAGE, } from './rate';
|
|
5
5
|
export { tenorBookKeyFor, tenorBookRateToApyPercent, tenorOfferFillToApiFill, tenorOfferToApiOffer, } from './tenor';
|
|
6
6
|
/**
|
|
7
7
|
* Aggregate a Morpho Midnight position. Midnight markets are multi-collateral, so the borrow limit is
|
|
@@ -88,59 +88,6 @@ export declare const getMorphoMidnightUserBorrowInfo: (account: string, marketId
|
|
|
88
88
|
* JSON, so without the `res.ok` check it parses as an empty book and the market silently vanishes.
|
|
89
89
|
*/
|
|
90
90
|
export declare const getMorphoMidnightMarketBook: (market: MorphoMidnightMarketData, network: NetworkNumber, side?: MorphoMidnightBookSide) => Promise<MorphoMidnightParsedBook | null>;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
* (and the returned `newUnits`/`maxUnits`) are raw loan-token base units — callers convert to/from human
|
|
95
|
-
* amounts. Throws if the book can't fill the amount (caller handles).
|
|
96
|
-
*
|
|
97
|
-
* Two ways to set the cap:
|
|
98
|
-
* - `maxBorrowRate` — an absolute APY ceiling, honoured **exactly**: the cap price is derived locally via
|
|
99
|
-
* `midnightPriceFromApy`. Prefer this when a user pins a max rate.
|
|
100
|
-
* - otherwise `slippagePercent`, the API's own knob. Note it is a **price**-level slippage, not APY points:
|
|
101
|
-
* near maturity the annualisation factor (365 / ttmDays) multiplies it heavily, so on a 22-day market a
|
|
102
|
-
* slippage of 0.5 permitted an APY ~9pp above the estimate, not 0.5pp. It also saturates at the book's
|
|
103
|
-
* cheapest bid. `maxRate` therefore reports what the cap actually permits, derived from the cap price.
|
|
104
|
-
*
|
|
105
|
-
* A `maxBorrowRate` below `estBorrowRate` yields `maxUnits < newUnits` — the borrow would revert on-chain.
|
|
106
|
-
* Compare the two before submitting and tell the user their ceiling is under the market rate.
|
|
107
|
-
*
|
|
108
|
-
* Tenor-hosted markets (see `isTenorMidnightMarket`) are quoted against Tenor's router instead of Morpho's
|
|
109
|
-
* book API. Slippage is applied locally to `maxUnits`; `taker` is the position owner (the smart wallet).
|
|
110
|
-
*/
|
|
111
|
-
export declare const getMorphoMidnightBorrowQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, maxBorrowRate?: Dec.Value, taker?: string) => Promise<MorphoMidnightBorrowQuote>;
|
|
112
|
-
/**
|
|
113
|
-
* Quote a prospective payback against the ask side of the Midnight order book: the rate the repayment
|
|
114
|
-
* retires debt at, the debt units it buys, and the `minUnits` floor sent on-chain to protect the user if
|
|
115
|
-
* the cheap offers get taken first. `assetsRaw` is the amount **spent** (raw loan-token base units) —
|
|
116
|
-
* matching `MidnightPaybackFromOrders`, whose `amount` is what leaves the wallet; the units bought, and
|
|
117
|
-
* therefore the debt retired, exceed it because a unit costs less than one loan token before maturity.
|
|
118
|
-
* Throws if the book can't fill the amount (caller handles).
|
|
119
|
-
*
|
|
120
|
-
* The mirror image of the borrow quote in every respect. A repayer wants a *high* rate, i.e. cheap units,
|
|
121
|
-
* so the guard is a floor rather than a ceiling:
|
|
122
|
-
* - `minPaybackRate` — an absolute APY floor, honoured **exactly** via `midnightPriceFromApy`. Prefer
|
|
123
|
-
* this when a user pins a min rate.
|
|
124
|
-
* - otherwise `slippagePercent`, the API's own price-level knob, whose APY effect is amplified by the
|
|
125
|
-
* annualisation factor near maturity. `minRate` reports what the floor actually permits.
|
|
126
|
-
*
|
|
127
|
-
* A `minPaybackRate` above `estPaybackRate` yields `minUnits > newUnits` — the payback would revert
|
|
128
|
-
* on-chain. Compare the two before submitting and tell the user their floor is over the market rate.
|
|
129
|
-
*/
|
|
130
|
-
export declare const getMorphoMidnightPaybackQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value, taker?: string) => Promise<MorphoMidnightPaybackQuote>;
|
|
131
|
-
/**
|
|
132
|
-
* Payback quoted against a **units** target instead of a spend: "retire exactly these debt units, and tell
|
|
133
|
-
* me what that costs". The sibling of `getMorphoMidnightPaybackQuote` in every other respect — same ask
|
|
134
|
-
* side, same rate floor, same offer list — but with the two amounts swapped, so the on-chain guard becomes
|
|
135
|
-
* a ceiling on assets spent rather than a floor on units bought.
|
|
136
|
-
*
|
|
137
|
-
* This is what a **full close** must be sized with. Retiring N units costs less than N loan tokens before
|
|
138
|
-
* maturity, so quoting the close as a spend of the debt's face value asks the book for more depth than the
|
|
139
|
-
* close needs (and can be refused for liquidity that is in fact there), and caps the taker's spend at more
|
|
140
|
-
* than the position is worth. Callers that cannot refund an overspend — a taker calling the bundler
|
|
141
|
-
* directly, rather than through an action contract that sweeps the remainder back — need this quote.
|
|
142
|
-
*
|
|
143
|
-
* A `minPaybackRate` above `estPaybackRate` yields `maxAssets < newAssets`: the ceiling is under what the
|
|
144
|
-
* buy costs and it would revert on-chain, the mirror of the assets-target quote's `minUnits > newUnits`.
|
|
145
|
-
*/
|
|
146
|
-
export declare const getMorphoMidnightPaybackUnitsQuote: (marketId: string, unitsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value, taker?: string) => Promise<MorphoMidnightPaybackUnitsQuote>;
|
|
91
|
+
export declare const getMorphoMidnightBorrowQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, maxBorrowRate?: Dec.Value, taker?: string, rateSlippagePercent?: Dec.Value) => Promise<MorphoMidnightBorrowQuote>;
|
|
92
|
+
export declare const getMorphoMidnightPaybackQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value, taker?: string, rateSlippagePercent?: Dec.Value) => Promise<MorphoMidnightPaybackQuote>;
|
|
93
|
+
export declare const getMorphoMidnightPaybackUnitsQuote: (marketId: string, unitsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value, taker?: string, rateSlippagePercent?: 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.getMorphoMidnightPaybackUnitsQuote = exports.getMorphoMidnightPaybackQuote = exports.getMorphoMidnightBorrowQuote = exports.getMorphoMidnightMarketBook = exports.getMorphoMidnightUserBorrowInfo = exports.midnightSlippageParam = exports.getMorphoMidnightAggregatedPositionData = exports.tenorOfferToApiOffer = exports.tenorOfferFillToApiFill = exports.tenorBookRateToApyPercent = exports.tenorBookKeyFor = exports.midnightTimeToMaturityDays = exports.midnightPriceFromApy = exports.midnightBookBestFirst = exports.midnightApyFromPrice = exports.buildMidnightParsedBook = void 0;
|
|
15
|
+
exports.getMorphoMidnightPaybackUnitsQuote = exports.getMorphoMidnightPaybackQuote = exports.getMorphoMidnightBorrowQuote = exports.getMorphoMidnightMarketBook = exports.getMorphoMidnightUserBorrowInfo = exports.midnightSlippageParam = exports.getMorphoMidnightAggregatedPositionData = exports.tenorOfferToApiOffer = exports.tenorOfferFillToApiFill = exports.tenorBookRateToApyPercent = exports.tenorBookKeyFor = exports.MIDNIGHT_DEFAULT_RATE_SLIPPAGE = exports.midnightTimeToMaturityDays = exports.midnightPriceFromApy = exports.midnightBoundPrice = exports.midnightBookBestFirst = exports.midnightApyFromPrice = exports.buildMidnightParsedBook = 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");
|
|
@@ -27,8 +27,10 @@ var rate_2 = require("./rate");
|
|
|
27
27
|
Object.defineProperty(exports, "buildMidnightParsedBook", { enumerable: true, get: function () { return rate_2.buildMidnightParsedBook; } });
|
|
28
28
|
Object.defineProperty(exports, "midnightApyFromPrice", { enumerable: true, get: function () { return rate_2.midnightApyFromPrice; } });
|
|
29
29
|
Object.defineProperty(exports, "midnightBookBestFirst", { enumerable: true, get: function () { return rate_2.midnightBookBestFirst; } });
|
|
30
|
+
Object.defineProperty(exports, "midnightBoundPrice", { enumerable: true, get: function () { return rate_2.midnightBoundPrice; } });
|
|
30
31
|
Object.defineProperty(exports, "midnightPriceFromApy", { enumerable: true, get: function () { return rate_2.midnightPriceFromApy; } });
|
|
31
32
|
Object.defineProperty(exports, "midnightTimeToMaturityDays", { enumerable: true, get: function () { return rate_2.midnightTimeToMaturityDays; } });
|
|
33
|
+
Object.defineProperty(exports, "MIDNIGHT_DEFAULT_RATE_SLIPPAGE", { enumerable: true, get: function () { return rate_2.MIDNIGHT_DEFAULT_RATE_SLIPPAGE; } });
|
|
32
34
|
var tenor_2 = require("./tenor");
|
|
33
35
|
Object.defineProperty(exports, "tenorBookKeyFor", { enumerable: true, get: function () { return tenor_2.tenorBookKeyFor; } });
|
|
34
36
|
Object.defineProperty(exports, "tenorBookRateToApyPercent", { enumerable: true, get: function () { return tenor_2.tenorBookRateToApyPercent; } });
|
|
@@ -194,39 +196,17 @@ const fetchMorphoMidnightQuote = (marketId, side, size, slippagePercent) => __aw
|
|
|
194
196
|
takeableOffers: d.takeable_offers || [],
|
|
195
197
|
};
|
|
196
198
|
});
|
|
197
|
-
|
|
198
|
-
* Quote a prospective borrow against the Midnight order book: the estimated rate, the debt units it adds,
|
|
199
|
-
* and the `maxUnits` cap sent on-chain to protect the user if better offers get filled first. `assetsRaw`
|
|
200
|
-
* (and the returned `newUnits`/`maxUnits`) are raw loan-token base units — callers convert to/from human
|
|
201
|
-
* amounts. Throws if the book can't fill the amount (caller handles).
|
|
202
|
-
*
|
|
203
|
-
* Two ways to set the cap:
|
|
204
|
-
* - `maxBorrowRate` — an absolute APY ceiling, honoured **exactly**: the cap price is derived locally via
|
|
205
|
-
* `midnightPriceFromApy`. Prefer this when a user pins a max rate.
|
|
206
|
-
* - otherwise `slippagePercent`, the API's own knob. Note it is a **price**-level slippage, not APY points:
|
|
207
|
-
* near maturity the annualisation factor (365 / ttmDays) multiplies it heavily, so on a 22-day market a
|
|
208
|
-
* slippage of 0.5 permitted an APY ~9pp above the estimate, not 0.5pp. It also saturates at the book's
|
|
209
|
-
* cheapest bid. `maxRate` therefore reports what the cap actually permits, derived from the cap price.
|
|
210
|
-
*
|
|
211
|
-
* A `maxBorrowRate` below `estBorrowRate` yields `maxUnits < newUnits` — the borrow would revert on-chain.
|
|
212
|
-
* Compare the two before submitting and tell the user their ceiling is under the market rate.
|
|
213
|
-
*
|
|
214
|
-
* Tenor-hosted markets (see `isTenorMidnightMarket`) are quoted against Tenor's router instead of Morpho's
|
|
215
|
-
* book API. Slippage is applied locally to `maxUnits`; `taker` is the position owner (the smart wallet).
|
|
216
|
-
*/
|
|
217
|
-
const getMorphoMidnightBorrowQuote = (marketId, assetsRaw, slippagePercent, maturity, maxBorrowRate, taker) => __awaiter(void 0, void 0, void 0, function* () {
|
|
199
|
+
const getMorphoMidnightBorrowQuote = (marketId, assetsRaw, slippagePercent, maturity, maxBorrowRate, taker, rateSlippagePercent) => __awaiter(void 0, void 0, void 0, function* () {
|
|
218
200
|
if ((0, morphoMidnight_1.isTenorMidnightMarket)(marketId)) {
|
|
219
|
-
return (0, tenor_1.getTenorBorrowQuote)(marketId, assetsRaw, slippagePercent, maturity, maxBorrowRate, taker);
|
|
201
|
+
return (0, tenor_1.getTenorBorrowQuote)(marketId, assetsRaw, slippagePercent, maturity, maxBorrowRate, taker, rateSlippagePercent);
|
|
220
202
|
}
|
|
221
203
|
const quote = yield fetchMorphoMidnightQuote(marketId, 'bids', { assets: assetsRaw }, slippagePercent);
|
|
222
|
-
const { bestPrice
|
|
204
|
+
const { bestPrice } = quote;
|
|
223
205
|
const ttmDays = (0, rate_1.midnightTimeToMaturityDays)(maturity);
|
|
224
206
|
const estBorrowRate = (0, rate_1.midnightApyFromPrice)(bestPrice, ttmDays);
|
|
225
207
|
// Price the cap sits at, and the rate that price represents — one derivation, so `maxRate` and
|
|
226
208
|
// `maxUnits` can never disagree about what the user is protected at.
|
|
227
|
-
const capPrice =
|
|
228
|
-
? (0, rate_1.midnightPriceFromApy)(maxBorrowRate, ttmDays)
|
|
229
|
-
: worstPrice;
|
|
209
|
+
const capPrice = (0, rate_1.midnightBoundPrice)(estBorrowRate, ttmDays, 'ceiling', maxBorrowRate, rateSlippagePercent);
|
|
230
210
|
const maxRate = (0, rate_1.midnightApyFromPrice)(capPrice, ttmDays);
|
|
231
211
|
const newUnits = new decimal_js_1.default(bestPrice).lte(0) ? '0' : new decimal_js_1.default(assetsRaw).div(bestPrice).toFixed(0);
|
|
232
212
|
const maxUnits = new decimal_js_1.default(capPrice).lte(0) ? '0' : new decimal_js_1.default(assetsRaw).div(capPrice).toFixed(0);
|
|
@@ -236,35 +216,15 @@ const getMorphoMidnightBorrowQuote = (marketId, assetsRaw, slippagePercent, matu
|
|
|
236
216
|
maxUnits });
|
|
237
217
|
});
|
|
238
218
|
exports.getMorphoMidnightBorrowQuote = getMorphoMidnightBorrowQuote;
|
|
239
|
-
|
|
240
|
-
* Quote a prospective payback against the ask side of the Midnight order book: the rate the repayment
|
|
241
|
-
* retires debt at, the debt units it buys, and the `minUnits` floor sent on-chain to protect the user if
|
|
242
|
-
* the cheap offers get taken first. `assetsRaw` is the amount **spent** (raw loan-token base units) —
|
|
243
|
-
* matching `MidnightPaybackFromOrders`, whose `amount` is what leaves the wallet; the units bought, and
|
|
244
|
-
* therefore the debt retired, exceed it because a unit costs less than one loan token before maturity.
|
|
245
|
-
* Throws if the book can't fill the amount (caller handles).
|
|
246
|
-
*
|
|
247
|
-
* The mirror image of the borrow quote in every respect. A repayer wants a *high* rate, i.e. cheap units,
|
|
248
|
-
* so the guard is a floor rather than a ceiling:
|
|
249
|
-
* - `minPaybackRate` — an absolute APY floor, honoured **exactly** via `midnightPriceFromApy`. Prefer
|
|
250
|
-
* this when a user pins a min rate.
|
|
251
|
-
* - otherwise `slippagePercent`, the API's own price-level knob, whose APY effect is amplified by the
|
|
252
|
-
* annualisation factor near maturity. `minRate` reports what the floor actually permits.
|
|
253
|
-
*
|
|
254
|
-
* A `minPaybackRate` above `estPaybackRate` yields `minUnits > newUnits` — the payback would revert
|
|
255
|
-
* on-chain. Compare the two before submitting and tell the user their floor is over the market rate.
|
|
256
|
-
*/
|
|
257
|
-
const getMorphoMidnightPaybackQuote = (marketId, assetsRaw, slippagePercent, maturity, minPaybackRate, taker) => __awaiter(void 0, void 0, void 0, function* () {
|
|
219
|
+
const getMorphoMidnightPaybackQuote = (marketId, assetsRaw, slippagePercent, maturity, minPaybackRate, taker, rateSlippagePercent) => __awaiter(void 0, void 0, void 0, function* () {
|
|
258
220
|
if ((0, morphoMidnight_1.isTenorMidnightMarket)(marketId)) {
|
|
259
|
-
return (0, tenor_1.getTenorPaybackQuote)(marketId, assetsRaw, slippagePercent, maturity, minPaybackRate, taker);
|
|
221
|
+
return (0, tenor_1.getTenorPaybackQuote)(marketId, assetsRaw, slippagePercent, maturity, minPaybackRate, taker, rateSlippagePercent);
|
|
260
222
|
}
|
|
261
223
|
const quote = yield fetchMorphoMidnightQuote(marketId, 'asks', { assets: assetsRaw }, slippagePercent);
|
|
262
|
-
const { bestPrice
|
|
224
|
+
const { bestPrice } = quote;
|
|
263
225
|
const ttmDays = (0, rate_1.midnightTimeToMaturityDays)(maturity);
|
|
264
226
|
const estPaybackRate = (0, rate_1.midnightApyFromPrice)(bestPrice, ttmDays);
|
|
265
|
-
const capPrice =
|
|
266
|
-
? (0, rate_1.midnightPriceFromApy)(minPaybackRate, ttmDays)
|
|
267
|
-
: worstPrice;
|
|
227
|
+
const capPrice = (0, rate_1.midnightBoundPrice)(estPaybackRate, ttmDays, 'floor', minPaybackRate, rateSlippagePercent);
|
|
268
228
|
const minRate = (0, rate_1.midnightApyFromPrice)(capPrice, ttmDays);
|
|
269
229
|
// Rounded down on both counts: `newUnits` must not overstate the debt the user sees retired, and a
|
|
270
230
|
// `minUnits` rounded up would be a stricter floor than asked for and revert a payback that was fine.
|
|
@@ -276,32 +236,15 @@ const getMorphoMidnightPaybackQuote = (marketId, assetsRaw, slippagePercent, mat
|
|
|
276
236
|
minUnits });
|
|
277
237
|
});
|
|
278
238
|
exports.getMorphoMidnightPaybackQuote = getMorphoMidnightPaybackQuote;
|
|
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
|
-
const getMorphoMidnightPaybackUnitsQuote = (marketId, unitsRaw, slippagePercent, maturity, minPaybackRate, taker) => __awaiter(void 0, void 0, void 0, function* () {
|
|
239
|
+
const getMorphoMidnightPaybackUnitsQuote = (marketId, unitsRaw, slippagePercent, maturity, minPaybackRate, taker, rateSlippagePercent) => __awaiter(void 0, void 0, void 0, function* () {
|
|
295
240
|
if ((0, morphoMidnight_1.isTenorMidnightMarket)(marketId)) {
|
|
296
|
-
return (0, tenor_1.getTenorPaybackUnitsQuote)(marketId, unitsRaw, slippagePercent, maturity, minPaybackRate, taker);
|
|
241
|
+
return (0, tenor_1.getTenorPaybackUnitsQuote)(marketId, unitsRaw, slippagePercent, maturity, minPaybackRate, taker, rateSlippagePercent);
|
|
297
242
|
}
|
|
298
243
|
const quote = yield fetchMorphoMidnightQuote(marketId, 'asks', { units: unitsRaw }, slippagePercent);
|
|
299
|
-
const { bestPrice
|
|
244
|
+
const { bestPrice } = quote;
|
|
300
245
|
const ttmDays = (0, rate_1.midnightTimeToMaturityDays)(maturity);
|
|
301
246
|
const estPaybackRate = (0, rate_1.midnightApyFromPrice)(bestPrice, ttmDays);
|
|
302
|
-
const capPrice =
|
|
303
|
-
? (0, rate_1.midnightPriceFromApy)(minPaybackRate, ttmDays)
|
|
304
|
-
: worstPrice;
|
|
247
|
+
const capPrice = (0, rate_1.midnightBoundPrice)(estPaybackRate, ttmDays, 'floor', minPaybackRate, rateSlippagePercent);
|
|
305
248
|
const minRate = (0, rate_1.midnightApyFromPrice)(capPrice, ttmDays);
|
|
306
249
|
// Rounded UP on both counts — the mirror of the assets-target quote's ROUND_DOWN. Here the figures are
|
|
307
250
|
// what the taker SPENDS, so a value rounded down understates the cost by a base unit and would leave the
|
|
@@ -7,5 +7,9 @@ export declare const midnightApyFromPrice: (price: Dec.Value, ttmDays: Dec.Value
|
|
|
7
7
|
* price = (1 + rate)^(−ttmDays / 365).
|
|
8
8
|
*/
|
|
9
9
|
export declare const midnightPriceFromApy: (ratePercent: Dec.Value, ttmDays: Dec.Value) => string;
|
|
10
|
+
export declare const MIDNIGHT_DEFAULT_RATE_SLIPPAGE = 0.1;
|
|
11
|
+
/** Which side of the estimate a guard sits on: a ceiling for borrows, a floor for paybacks. */
|
|
12
|
+
export type MidnightRateBoundKind = 'ceiling' | 'floor';
|
|
13
|
+
export declare const midnightBoundPrice: (estRate: Dec.Value, ttmDays: Dec.Value, kind: MidnightRateBoundKind, boundRate?: Dec.Value, rateSlippagePercent?: Dec.Value) => string;
|
|
10
14
|
export declare const midnightBookBestFirst: (side: MorphoMidnightBookSide) => 1 | -1;
|
|
11
15
|
export declare const buildMidnightParsedBook: (offers: MorphoMidnightBookOffer[], side: MorphoMidnightBookSide) => MorphoMidnightParsedBook | null;
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.buildMidnightParsedBook = exports.midnightBookBestFirst = exports.midnightPriceFromApy = exports.midnightApyFromPrice = exports.midnightTimeToMaturityDays = void 0;
|
|
6
|
+
exports.buildMidnightParsedBook = exports.midnightBookBestFirst = exports.midnightBoundPrice = exports.MIDNIGHT_DEFAULT_RATE_SLIPPAGE = exports.midnightPriceFromApy = exports.midnightApyFromPrice = exports.midnightTimeToMaturityDays = void 0;
|
|
7
7
|
const decimal_js_1 = __importDefault(require("decimal.js"));
|
|
8
8
|
const constants_1 = require("../../constants");
|
|
9
9
|
const nowInSeconds = () => Math.floor(Date.now() / 1000);
|
|
@@ -34,6 +34,19 @@ const midnightPriceFromApy = (ratePercent, ttmDays) => {
|
|
|
34
34
|
return new decimal_js_1.default(1).div(new decimal_js_1.default(1).add(rate.div(100)).pow(ttm.div(365))).toString();
|
|
35
35
|
};
|
|
36
36
|
exports.midnightPriceFromApy = midnightPriceFromApy;
|
|
37
|
+
exports.MIDNIGHT_DEFAULT_RATE_SLIPPAGE = 0.1;
|
|
38
|
+
const MIDNIGHT_MIN_FLOOR_RATIO = 0.5;
|
|
39
|
+
const midnightBoundPrice = (estRate, ttmDays, kind, boundRate, rateSlippagePercent = exports.MIDNIGHT_DEFAULT_RATE_SLIPPAGE) => {
|
|
40
|
+
if (boundRate !== undefined && new decimal_js_1.default(boundRate).gt(0))
|
|
41
|
+
return (0, exports.midnightPriceFromApy)(boundRate, ttmDays);
|
|
42
|
+
const est = new decimal_js_1.default(estRate);
|
|
43
|
+
const slippage = new decimal_js_1.default(rateSlippagePercent);
|
|
44
|
+
const bound = kind === 'ceiling'
|
|
45
|
+
? est.add(slippage)
|
|
46
|
+
: decimal_js_1.default.max(est.sub(slippage), est.mul(MIDNIGHT_MIN_FLOOR_RATIO));
|
|
47
|
+
return (0, exports.midnightPriceFromApy)(bound, ttmDays);
|
|
48
|
+
};
|
|
49
|
+
exports.midnightBoundPrice = midnightBoundPrice;
|
|
37
50
|
const midnightBookBestFirst = (side) => (side === 'asks' ? -1 : 1);
|
|
38
51
|
exports.midnightBookBestFirst = midnightBookBestFirst;
|
|
39
52
|
const buildMidnightParsedBook = (offers, side) => {
|
|
@@ -4,7 +4,6 @@ import type { MorphoMidnightBorrowQuote, MorphoMidnightPaybackQuote, MorphoMidni
|
|
|
4
4
|
type TenorBookKey = 'asks' | 'bids';
|
|
5
5
|
export declare const tenorBookKeyFor: (side: MorphoMidnightBookSide) => TenorBookKey;
|
|
6
6
|
export declare const tenorBookRateToApyPercent: (rate: Dec.Value) => string;
|
|
7
|
-
export declare const tenorCapPrice: (bestPrice: string, slippagePercent: Dec.Value, direction: 1 | -1, ttmDays: Dec.Value, boundRate?: Dec.Value) => string;
|
|
8
7
|
interface TenorOfferCollateral {
|
|
9
8
|
token: string;
|
|
10
9
|
lltv: string | number;
|
|
@@ -119,7 +118,12 @@ export declare const parseTenorBookSide: (json: TenorBookResponse, side: MorphoM
|
|
|
119
118
|
}[];
|
|
120
119
|
export declare const parseTenorOrderBook: (json: TenorBookResponse, side: MorphoMidnightBookSide, loanSymbol: string) => MorphoMidnightParsedBook | null;
|
|
121
120
|
export declare const getTenorMarketBook: (market: MorphoMidnightMarketData, network: NetworkNumber, side?: MorphoMidnightBookSide) => Promise<MorphoMidnightParsedBook | null>;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Tenor's router takes no slippage of its own — it prices the fill and hands back the offers. `slippagePercent`
|
|
123
|
+
* is therefore accepted only to keep the signature aligned with the Morpho-hosted quote that `index.ts`
|
|
124
|
+
* forwards to positionally; the guard comes from `rateSlippagePercent` via `midnightBoundPrice`.
|
|
125
|
+
*/
|
|
126
|
+
export declare const getTenorBorrowQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, maxBorrowRate?: Dec.Value, taker?: string, rateSlippagePercent?: Dec.Value, network?: NetworkNumber) => Promise<MorphoMidnightBorrowQuote>;
|
|
127
|
+
export declare const getTenorPaybackQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value, taker?: string, rateSlippagePercent?: Dec.Value, network?: NetworkNumber) => Promise<MorphoMidnightPaybackQuote>;
|
|
128
|
+
export declare const getTenorPaybackUnitsQuote: (marketId: string, unitsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value, taker?: string, rateSlippagePercent?: Dec.Value, network?: NetworkNumber) => Promise<MorphoMidnightPaybackUnitsQuote>;
|
|
125
129
|
export {};
|
|
@@ -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.getTenorPaybackUnitsQuote = exports.getTenorPaybackQuote = exports.getTenorBorrowQuote = exports.getTenorMarketBook = exports.parseTenorOrderBook = exports.parseTenorBookSide = exports.tenorOfferFillToApiFill = exports.tenorOfferToApiOffer = exports.
|
|
15
|
+
exports.getTenorPaybackUnitsQuote = exports.getTenorPaybackQuote = exports.getTenorBorrowQuote = exports.getTenorMarketBook = exports.parseTenorOrderBook = exports.parseTenorBookSide = exports.tenorOfferFillToApiFill = exports.tenorOfferToApiOffer = exports.tenorBookRateToApyPercent = exports.tenorBookKeyFor = void 0;
|
|
16
16
|
const decimal_js_1 = __importDefault(require("decimal.js"));
|
|
17
17
|
const tokens_1 = require("@defisaver/tokens");
|
|
18
18
|
const constants_1 = require("../../constants");
|
|
@@ -31,13 +31,6 @@ exports.tenorBookKeyFor = tenorBookKeyFor;
|
|
|
31
31
|
const tenorBookRateToApyPercent = (rate) => new decimal_js_1.default(rate || 0).div(100).toString();
|
|
32
32
|
exports.tenorBookRateToApyPercent = tenorBookRateToApyPercent;
|
|
33
33
|
const tenorFillPrice = (assets, units) => (new decimal_js_1.default(units).lte(0) ? '0' : new decimal_js_1.default(assets).div(units).toString());
|
|
34
|
-
const tenorCapPrice = (bestPrice, slippagePercent, direction, ttmDays, boundRate) => {
|
|
35
|
-
if (boundRate !== undefined && new decimal_js_1.default(boundRate).gt(0))
|
|
36
|
-
return (0, rate_1.midnightPriceFromApy)(boundRate, ttmDays);
|
|
37
|
-
const band = new decimal_js_1.default(1).add(new decimal_js_1.default(slippagePercent || 0).div(100).mul(direction));
|
|
38
|
-
return decimal_js_1.default.max(0, new decimal_js_1.default(bestPrice).mul(band)).toString();
|
|
39
|
-
};
|
|
40
|
-
exports.tenorCapPrice = tenorCapPrice;
|
|
41
34
|
/**
|
|
42
35
|
* Tenor's offer JSON is flat (market fields live on the offer). Morpho's is nested, and that nested
|
|
43
36
|
* shape is what the app encodes for `Midnight.take`. Map Tenor into that shape so recipes stay on one encoder.
|
|
@@ -166,7 +159,12 @@ const fetchTenorQuote = (_a) => __awaiter(void 0, [_a], void 0, function* ({ mar
|
|
|
166
159
|
};
|
|
167
160
|
});
|
|
168
161
|
const TENOR_NO_AVAILABLE_UNITS = '0';
|
|
169
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Tenor's router takes no slippage of its own — it prices the fill and hands back the offers. `slippagePercent`
|
|
164
|
+
* is therefore accepted only to keep the signature aligned with the Morpho-hosted quote that `index.ts`
|
|
165
|
+
* forwards to positionally; the guard comes from `rateSlippagePercent` via `midnightBoundPrice`.
|
|
166
|
+
*/
|
|
167
|
+
const getTenorBorrowQuote = (marketId_1, assetsRaw_1, slippagePercent_1, maturity_1, maxBorrowRate_1, taker_1, rateSlippagePercent_1, ...args_1) => __awaiter(void 0, [marketId_1, assetsRaw_1, slippagePercent_1, maturity_1, maxBorrowRate_1, taker_1, rateSlippagePercent_1, ...args_1], void 0, function* (marketId, assetsRaw, slippagePercent, maturity, maxBorrowRate, taker, rateSlippagePercent, network = types_1.NetworkNumber.Base) {
|
|
170
168
|
if (!(0, morphoMidnight_1.isTenorMidnightMarket)(marketId)) {
|
|
171
169
|
throw new Error(tenorQuoteError(`Not a Tenor Midnight market: ${marketId}`));
|
|
172
170
|
}
|
|
@@ -179,7 +177,7 @@ const getTenorBorrowQuote = (marketId_1, assetsRaw_1, slippagePercent_1, maturit
|
|
|
179
177
|
const ttmDays = (0, rate_1.midnightTimeToMaturityDays)(maturity);
|
|
180
178
|
const bestPrice = tenorFillPrice(assetsRaw, quote.units);
|
|
181
179
|
const estBorrowRate = (0, rate_1.midnightApyFromPrice)(bestPrice, ttmDays);
|
|
182
|
-
const capPrice = (0,
|
|
180
|
+
const capPrice = (0, rate_1.midnightBoundPrice)(estBorrowRate, ttmDays, 'ceiling', maxBorrowRate, rateSlippagePercent);
|
|
183
181
|
const maxRate = (0, rate_1.midnightApyFromPrice)(capPrice, ttmDays);
|
|
184
182
|
const maxUnits = new decimal_js_1.default(capPrice).lte(0) ? '0' : new decimal_js_1.default(assetsRaw).div(capPrice).toFixed(0);
|
|
185
183
|
return {
|
|
@@ -195,7 +193,7 @@ const getTenorBorrowQuote = (marketId_1, assetsRaw_1, slippagePercent_1, maturit
|
|
|
195
193
|
};
|
|
196
194
|
});
|
|
197
195
|
exports.getTenorBorrowQuote = getTenorBorrowQuote;
|
|
198
|
-
const getTenorPaybackQuote = (marketId_1, assetsRaw_1, slippagePercent_1, maturity_1, minPaybackRate_1, taker_1, ...args_1) => __awaiter(void 0, [marketId_1, assetsRaw_1, slippagePercent_1, maturity_1, minPaybackRate_1, taker_1, ...args_1], void 0, function* (marketId, assetsRaw, slippagePercent, maturity, minPaybackRate, taker, network = types_1.NetworkNumber.Base) {
|
|
196
|
+
const getTenorPaybackQuote = (marketId_1, assetsRaw_1, slippagePercent_1, maturity_1, minPaybackRate_1, taker_1, rateSlippagePercent_1, ...args_1) => __awaiter(void 0, [marketId_1, assetsRaw_1, slippagePercent_1, maturity_1, minPaybackRate_1, taker_1, rateSlippagePercent_1, ...args_1], void 0, function* (marketId, assetsRaw, slippagePercent, maturity, minPaybackRate, taker, rateSlippagePercent, network = types_1.NetworkNumber.Base) {
|
|
199
197
|
if (!(0, morphoMidnight_1.isTenorMidnightMarket)(marketId)) {
|
|
200
198
|
throw new Error(tenorQuoteError(`Not a Tenor Midnight market: ${marketId}`));
|
|
201
199
|
}
|
|
@@ -208,7 +206,7 @@ const getTenorPaybackQuote = (marketId_1, assetsRaw_1, slippagePercent_1, maturi
|
|
|
208
206
|
const ttmDays = (0, rate_1.midnightTimeToMaturityDays)(maturity);
|
|
209
207
|
const bestPrice = tenorFillPrice(assetsRaw, quote.units);
|
|
210
208
|
const estPaybackRate = (0, rate_1.midnightApyFromPrice)(bestPrice, ttmDays);
|
|
211
|
-
const capPrice = (0,
|
|
209
|
+
const capPrice = (0, rate_1.midnightBoundPrice)(estPaybackRate, ttmDays, 'floor', minPaybackRate, rateSlippagePercent);
|
|
212
210
|
const minRate = (0, rate_1.midnightApyFromPrice)(capPrice, ttmDays);
|
|
213
211
|
const minUnits = new decimal_js_1.default(capPrice).lte(0) ? '0' : new decimal_js_1.default(assetsRaw).div(capPrice).toFixed(0, decimal_js_1.default.ROUND_DOWN);
|
|
214
212
|
return {
|
|
@@ -224,7 +222,7 @@ const getTenorPaybackQuote = (marketId_1, assetsRaw_1, slippagePercent_1, maturi
|
|
|
224
222
|
};
|
|
225
223
|
});
|
|
226
224
|
exports.getTenorPaybackQuote = getTenorPaybackQuote;
|
|
227
|
-
const getTenorPaybackUnitsQuote = (marketId_1, unitsRaw_1, slippagePercent_1, maturity_1, minPaybackRate_1, taker_1, ...args_1) => __awaiter(void 0, [marketId_1, unitsRaw_1, slippagePercent_1, maturity_1, minPaybackRate_1, taker_1, ...args_1], void 0, function* (marketId, unitsRaw, slippagePercent, maturity, minPaybackRate, taker, network = types_1.NetworkNumber.Base) {
|
|
225
|
+
const getTenorPaybackUnitsQuote = (marketId_1, unitsRaw_1, slippagePercent_1, maturity_1, minPaybackRate_1, taker_1, rateSlippagePercent_1, ...args_1) => __awaiter(void 0, [marketId_1, unitsRaw_1, slippagePercent_1, maturity_1, minPaybackRate_1, taker_1, rateSlippagePercent_1, ...args_1], void 0, function* (marketId, unitsRaw, slippagePercent, maturity, minPaybackRate, taker, rateSlippagePercent, network = types_1.NetworkNumber.Base) {
|
|
228
226
|
if (!(0, morphoMidnight_1.isTenorMidnightMarket)(marketId)) {
|
|
229
227
|
throw new Error(tenorQuoteError(`Not a Tenor Midnight market: ${marketId}`));
|
|
230
228
|
}
|
|
@@ -238,7 +236,7 @@ const getTenorPaybackUnitsQuote = (marketId_1, unitsRaw_1, slippagePercent_1, ma
|
|
|
238
236
|
const newAssets = quote.buyerAssets;
|
|
239
237
|
const bestPrice = tenorFillPrice(newAssets, unitsRaw);
|
|
240
238
|
const estPaybackRate = (0, rate_1.midnightApyFromPrice)(bestPrice, ttmDays);
|
|
241
|
-
const capPrice = (0,
|
|
239
|
+
const capPrice = (0, rate_1.midnightBoundPrice)(estPaybackRate, ttmDays, 'floor', minPaybackRate, rateSlippagePercent);
|
|
242
240
|
const minRate = (0, rate_1.midnightApyFromPrice)(capPrice, ttmDays);
|
|
243
241
|
const maxAssets = new decimal_js_1.default(unitsRaw).mul(capPrice).toFixed(0, decimal_js_1.default.ROUND_UP);
|
|
244
242
|
return {
|
|
@@ -169,12 +169,14 @@ function _getMorphoMidnightAccountData(provider, network, account, selectedMarke
|
|
|
169
169
|
if (new decimal_js_1.default(positionInfo.debt.toString()).gt(0)) {
|
|
170
170
|
try {
|
|
171
171
|
const borrowInfo = yield (0, morphoMidnightHelpers_1.getMorphoMidnightUserBorrowInfo)(account, marketId, marketInfo.loanToken);
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
172
|
+
if (new decimal_js_1.default(borrowInfo.debtTotal).gt(0)) {
|
|
173
|
+
borrowRate = borrowInfo.borrowRate;
|
|
174
|
+
debtBase = borrowInfo.debtBase;
|
|
175
|
+
debtInterest = borrowInfo.debtInterest;
|
|
176
|
+
usedAssets[marketInfo.loanToken].borrowRate = borrowRate;
|
|
177
|
+
// Reflect the real borrow cost in netApy without mutating the shared marketInfo.assetsData.
|
|
178
|
+
assetsDataForApy = Object.assign(Object.assign({}, marketInfo.assetsData), { [marketInfo.loanToken]: Object.assign(Object.assign({}, loanTokenData), { borrowRate }) });
|
|
179
|
+
}
|
|
178
180
|
}
|
|
179
181
|
catch (err) {
|
|
180
182
|
// Orderbook API unavailable — keep the on-chain-only fallback above.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Dec from 'decimal.js';
|
|
2
2
|
import { MMUsedAssets, NetworkNumber } from '../../types/common';
|
|
3
3
|
import { MorphoMidnightAggregatedPositionData, MorphoMidnightAssetsData, MorphoMidnightBookSide, MorphoMidnightMarketData, MorphoMidnightMarketInfo, MorphoMidnightParsedBook } from '../../types';
|
|
4
|
-
export { buildMidnightParsedBook, midnightApyFromPrice, midnightBookBestFirst, midnightPriceFromApy, midnightTimeToMaturityDays, } from './rate';
|
|
4
|
+
export { buildMidnightParsedBook, midnightApyFromPrice, midnightBookBestFirst, midnightBoundPrice, midnightPriceFromApy, midnightTimeToMaturityDays, MIDNIGHT_DEFAULT_RATE_SLIPPAGE, } from './rate';
|
|
5
5
|
export { tenorBookKeyFor, tenorBookRateToApyPercent, tenorOfferFillToApiFill, tenorOfferToApiOffer, } from './tenor';
|
|
6
6
|
/**
|
|
7
7
|
* Aggregate a Morpho Midnight position. Midnight markets are multi-collateral, so the borrow limit is
|
|
@@ -88,59 +88,6 @@ export declare const getMorphoMidnightUserBorrowInfo: (account: string, marketId
|
|
|
88
88
|
* JSON, so without the `res.ok` check it parses as an empty book and the market silently vanishes.
|
|
89
89
|
*/
|
|
90
90
|
export declare const getMorphoMidnightMarketBook: (market: MorphoMidnightMarketData, network: NetworkNumber, side?: MorphoMidnightBookSide) => Promise<MorphoMidnightParsedBook | null>;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
* (and the returned `newUnits`/`maxUnits`) are raw loan-token base units — callers convert to/from human
|
|
95
|
-
* amounts. Throws if the book can't fill the amount (caller handles).
|
|
96
|
-
*
|
|
97
|
-
* Two ways to set the cap:
|
|
98
|
-
* - `maxBorrowRate` — an absolute APY ceiling, honoured **exactly**: the cap price is derived locally via
|
|
99
|
-
* `midnightPriceFromApy`. Prefer this when a user pins a max rate.
|
|
100
|
-
* - otherwise `slippagePercent`, the API's own knob. Note it is a **price**-level slippage, not APY points:
|
|
101
|
-
* near maturity the annualisation factor (365 / ttmDays) multiplies it heavily, so on a 22-day market a
|
|
102
|
-
* slippage of 0.5 permitted an APY ~9pp above the estimate, not 0.5pp. It also saturates at the book's
|
|
103
|
-
* cheapest bid. `maxRate` therefore reports what the cap actually permits, derived from the cap price.
|
|
104
|
-
*
|
|
105
|
-
* A `maxBorrowRate` below `estBorrowRate` yields `maxUnits < newUnits` — the borrow would revert on-chain.
|
|
106
|
-
* Compare the two before submitting and tell the user their ceiling is under the market rate.
|
|
107
|
-
*
|
|
108
|
-
* Tenor-hosted markets (see `isTenorMidnightMarket`) are quoted against Tenor's router instead of Morpho's
|
|
109
|
-
* book API. Slippage is applied locally to `maxUnits`; `taker` is the position owner (the smart wallet).
|
|
110
|
-
*/
|
|
111
|
-
export declare const getMorphoMidnightBorrowQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, maxBorrowRate?: Dec.Value, taker?: string) => Promise<MorphoMidnightBorrowQuote>;
|
|
112
|
-
/**
|
|
113
|
-
* Quote a prospective payback against the ask side of the Midnight order book: the rate the repayment
|
|
114
|
-
* retires debt at, the debt units it buys, and the `minUnits` floor sent on-chain to protect the user if
|
|
115
|
-
* the cheap offers get taken first. `assetsRaw` is the amount **spent** (raw loan-token base units) —
|
|
116
|
-
* matching `MidnightPaybackFromOrders`, whose `amount` is what leaves the wallet; the units bought, and
|
|
117
|
-
* therefore the debt retired, exceed it because a unit costs less than one loan token before maturity.
|
|
118
|
-
* Throws if the book can't fill the amount (caller handles).
|
|
119
|
-
*
|
|
120
|
-
* The mirror image of the borrow quote in every respect. A repayer wants a *high* rate, i.e. cheap units,
|
|
121
|
-
* so the guard is a floor rather than a ceiling:
|
|
122
|
-
* - `minPaybackRate` — an absolute APY floor, honoured **exactly** via `midnightPriceFromApy`. Prefer
|
|
123
|
-
* this when a user pins a min rate.
|
|
124
|
-
* - otherwise `slippagePercent`, the API's own price-level knob, whose APY effect is amplified by the
|
|
125
|
-
* annualisation factor near maturity. `minRate` reports what the floor actually permits.
|
|
126
|
-
*
|
|
127
|
-
* A `minPaybackRate` above `estPaybackRate` yields `minUnits > newUnits` — the payback would revert
|
|
128
|
-
* on-chain. Compare the two before submitting and tell the user their floor is over the market rate.
|
|
129
|
-
*/
|
|
130
|
-
export declare const getMorphoMidnightPaybackQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value, taker?: string) => Promise<MorphoMidnightPaybackQuote>;
|
|
131
|
-
/**
|
|
132
|
-
* Payback quoted against a **units** target instead of a spend: "retire exactly these debt units, and tell
|
|
133
|
-
* me what that costs". The sibling of `getMorphoMidnightPaybackQuote` in every other respect — same ask
|
|
134
|
-
* side, same rate floor, same offer list — but with the two amounts swapped, so the on-chain guard becomes
|
|
135
|
-
* a ceiling on assets spent rather than a floor on units bought.
|
|
136
|
-
*
|
|
137
|
-
* This is what a **full close** must be sized with. Retiring N units costs less than N loan tokens before
|
|
138
|
-
* maturity, so quoting the close as a spend of the debt's face value asks the book for more depth than the
|
|
139
|
-
* close needs (and can be refused for liquidity that is in fact there), and caps the taker's spend at more
|
|
140
|
-
* than the position is worth. Callers that cannot refund an overspend — a taker calling the bundler
|
|
141
|
-
* directly, rather than through an action contract that sweeps the remainder back — need this quote.
|
|
142
|
-
*
|
|
143
|
-
* A `minPaybackRate` above `estPaybackRate` yields `maxAssets < newAssets`: the ceiling is under what the
|
|
144
|
-
* buy costs and it would revert on-chain, the mirror of the assets-target quote's `minUnits > newUnits`.
|
|
145
|
-
*/
|
|
146
|
-
export declare const getMorphoMidnightPaybackUnitsQuote: (marketId: string, unitsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value, taker?: string) => Promise<MorphoMidnightPaybackUnitsQuote>;
|
|
91
|
+
export declare const getMorphoMidnightBorrowQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, maxBorrowRate?: Dec.Value, taker?: string, rateSlippagePercent?: Dec.Value) => Promise<MorphoMidnightBorrowQuote>;
|
|
92
|
+
export declare const getMorphoMidnightPaybackQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value, taker?: string, rateSlippagePercent?: Dec.Value) => Promise<MorphoMidnightPaybackQuote>;
|
|
93
|
+
export declare const getMorphoMidnightPaybackUnitsQuote: (marketId: string, unitsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value, taker?: string, rateSlippagePercent?: Dec.Value) => Promise<MorphoMidnightPaybackUnitsQuote>;
|
|
@@ -15,9 +15,9 @@ import { LeverageType, } from '../../types/common';
|
|
|
15
15
|
import { WAD } from '../../constants';
|
|
16
16
|
import { LONGER_TIMEOUT } from '../../services/utils';
|
|
17
17
|
import { isTenorMidnightMarket } from '../../markets/morphoMidnight';
|
|
18
|
-
import { buildMidnightParsedBook, midnightApyFromPrice,
|
|
18
|
+
import { buildMidnightParsedBook, midnightApyFromPrice, midnightBoundPrice, midnightTimeToMaturityDays, } from './rate';
|
|
19
19
|
import { getTenorBorrowQuote, getTenorMarketBook, getTenorPaybackQuote, getTenorPaybackUnitsQuote, } from './tenor';
|
|
20
|
-
export { buildMidnightParsedBook, midnightApyFromPrice, midnightBookBestFirst, midnightPriceFromApy, midnightTimeToMaturityDays, } from './rate';
|
|
20
|
+
export { buildMidnightParsedBook, midnightApyFromPrice, midnightBookBestFirst, midnightBoundPrice, midnightPriceFromApy, midnightTimeToMaturityDays, MIDNIGHT_DEFAULT_RATE_SLIPPAGE, } from './rate';
|
|
21
21
|
export { tenorBookKeyFor, tenorBookRateToApyPercent, tenorOfferFillToApiFill, tenorOfferToApiOffer, } from './tenor';
|
|
22
22
|
/**
|
|
23
23
|
* Aggregate a Morpho Midnight position. Midnight markets are multi-collateral, so the borrow limit is
|
|
@@ -175,39 +175,17 @@ const fetchMorphoMidnightQuote = (marketId, side, size, slippagePercent) => __aw
|
|
|
175
175
|
takeableOffers: d.takeable_offers || [],
|
|
176
176
|
};
|
|
177
177
|
});
|
|
178
|
-
|
|
179
|
-
* Quote a prospective borrow against the Midnight order book: the estimated rate, the debt units it adds,
|
|
180
|
-
* and the `maxUnits` cap sent on-chain to protect the user if better offers get filled first. `assetsRaw`
|
|
181
|
-
* (and the returned `newUnits`/`maxUnits`) are raw loan-token base units — callers convert to/from human
|
|
182
|
-
* amounts. Throws if the book can't fill the amount (caller handles).
|
|
183
|
-
*
|
|
184
|
-
* Two ways to set the cap:
|
|
185
|
-
* - `maxBorrowRate` — an absolute APY ceiling, honoured **exactly**: the cap price is derived locally via
|
|
186
|
-
* `midnightPriceFromApy`. Prefer this when a user pins a max rate.
|
|
187
|
-
* - otherwise `slippagePercent`, the API's own knob. Note it is a **price**-level slippage, not APY points:
|
|
188
|
-
* near maturity the annualisation factor (365 / ttmDays) multiplies it heavily, so on a 22-day market a
|
|
189
|
-
* slippage of 0.5 permitted an APY ~9pp above the estimate, not 0.5pp. It also saturates at the book's
|
|
190
|
-
* cheapest bid. `maxRate` therefore reports what the cap actually permits, derived from the cap price.
|
|
191
|
-
*
|
|
192
|
-
* A `maxBorrowRate` below `estBorrowRate` yields `maxUnits < newUnits` — the borrow would revert on-chain.
|
|
193
|
-
* Compare the two before submitting and tell the user their ceiling is under the market rate.
|
|
194
|
-
*
|
|
195
|
-
* Tenor-hosted markets (see `isTenorMidnightMarket`) are quoted against Tenor's router instead of Morpho's
|
|
196
|
-
* book API. Slippage is applied locally to `maxUnits`; `taker` is the position owner (the smart wallet).
|
|
197
|
-
*/
|
|
198
|
-
export const getMorphoMidnightBorrowQuote = (marketId, assetsRaw, slippagePercent, maturity, maxBorrowRate, taker) => __awaiter(void 0, void 0, void 0, function* () {
|
|
178
|
+
export const getMorphoMidnightBorrowQuote = (marketId, assetsRaw, slippagePercent, maturity, maxBorrowRate, taker, rateSlippagePercent) => __awaiter(void 0, void 0, void 0, function* () {
|
|
199
179
|
if (isTenorMidnightMarket(marketId)) {
|
|
200
|
-
return getTenorBorrowQuote(marketId, assetsRaw, slippagePercent, maturity, maxBorrowRate, taker);
|
|
180
|
+
return getTenorBorrowQuote(marketId, assetsRaw, slippagePercent, maturity, maxBorrowRate, taker, rateSlippagePercent);
|
|
201
181
|
}
|
|
202
182
|
const quote = yield fetchMorphoMidnightQuote(marketId, 'bids', { assets: assetsRaw }, slippagePercent);
|
|
203
|
-
const { bestPrice
|
|
183
|
+
const { bestPrice } = quote;
|
|
204
184
|
const ttmDays = midnightTimeToMaturityDays(maturity);
|
|
205
185
|
const estBorrowRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
206
186
|
// Price the cap sits at, and the rate that price represents — one derivation, so `maxRate` and
|
|
207
187
|
// `maxUnits` can never disagree about what the user is protected at.
|
|
208
|
-
const capPrice =
|
|
209
|
-
? midnightPriceFromApy(maxBorrowRate, ttmDays)
|
|
210
|
-
: worstPrice;
|
|
188
|
+
const capPrice = midnightBoundPrice(estBorrowRate, ttmDays, 'ceiling', maxBorrowRate, rateSlippagePercent);
|
|
211
189
|
const maxRate = midnightApyFromPrice(capPrice, ttmDays);
|
|
212
190
|
const newUnits = new Dec(bestPrice).lte(0) ? '0' : new Dec(assetsRaw).div(bestPrice).toFixed(0);
|
|
213
191
|
const maxUnits = new Dec(capPrice).lte(0) ? '0' : new Dec(assetsRaw).div(capPrice).toFixed(0);
|
|
@@ -216,35 +194,15 @@ export const getMorphoMidnightBorrowQuote = (marketId, assetsRaw, slippagePercen
|
|
|
216
194
|
newUnits,
|
|
217
195
|
maxUnits });
|
|
218
196
|
});
|
|
219
|
-
|
|
220
|
-
* Quote a prospective payback against the ask side of the Midnight order book: the rate the repayment
|
|
221
|
-
* retires debt at, the debt units it buys, and the `minUnits` floor sent on-chain to protect the user if
|
|
222
|
-
* the cheap offers get taken first. `assetsRaw` is the amount **spent** (raw loan-token base units) —
|
|
223
|
-
* matching `MidnightPaybackFromOrders`, whose `amount` is what leaves the wallet; the units bought, and
|
|
224
|
-
* therefore the debt retired, exceed it because a unit costs less than one loan token before maturity.
|
|
225
|
-
* Throws if the book can't fill the amount (caller handles).
|
|
226
|
-
*
|
|
227
|
-
* The mirror image of the borrow quote in every respect. A repayer wants a *high* rate, i.e. cheap units,
|
|
228
|
-
* so the guard is a floor rather than a ceiling:
|
|
229
|
-
* - `minPaybackRate` — an absolute APY floor, honoured **exactly** via `midnightPriceFromApy`. Prefer
|
|
230
|
-
* this when a user pins a min rate.
|
|
231
|
-
* - otherwise `slippagePercent`, the API's own price-level knob, whose APY effect is amplified by the
|
|
232
|
-
* annualisation factor near maturity. `minRate` reports what the floor actually permits.
|
|
233
|
-
*
|
|
234
|
-
* A `minPaybackRate` above `estPaybackRate` yields `minUnits > newUnits` — the payback would revert
|
|
235
|
-
* on-chain. Compare the two before submitting and tell the user their floor is over the market rate.
|
|
236
|
-
*/
|
|
237
|
-
export const getMorphoMidnightPaybackQuote = (marketId, assetsRaw, slippagePercent, maturity, minPaybackRate, taker) => __awaiter(void 0, void 0, void 0, function* () {
|
|
197
|
+
export const getMorphoMidnightPaybackQuote = (marketId, assetsRaw, slippagePercent, maturity, minPaybackRate, taker, rateSlippagePercent) => __awaiter(void 0, void 0, void 0, function* () {
|
|
238
198
|
if (isTenorMidnightMarket(marketId)) {
|
|
239
|
-
return getTenorPaybackQuote(marketId, assetsRaw, slippagePercent, maturity, minPaybackRate, taker);
|
|
199
|
+
return getTenorPaybackQuote(marketId, assetsRaw, slippagePercent, maturity, minPaybackRate, taker, rateSlippagePercent);
|
|
240
200
|
}
|
|
241
201
|
const quote = yield fetchMorphoMidnightQuote(marketId, 'asks', { assets: assetsRaw }, slippagePercent);
|
|
242
|
-
const { bestPrice
|
|
202
|
+
const { bestPrice } = quote;
|
|
243
203
|
const ttmDays = midnightTimeToMaturityDays(maturity);
|
|
244
204
|
const estPaybackRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
245
|
-
const capPrice =
|
|
246
|
-
? midnightPriceFromApy(minPaybackRate, ttmDays)
|
|
247
|
-
: worstPrice;
|
|
205
|
+
const capPrice = midnightBoundPrice(estPaybackRate, ttmDays, 'floor', minPaybackRate, rateSlippagePercent);
|
|
248
206
|
const minRate = midnightApyFromPrice(capPrice, ttmDays);
|
|
249
207
|
// Rounded down on both counts: `newUnits` must not overstate the debt the user sees retired, and a
|
|
250
208
|
// `minUnits` rounded up would be a stricter floor than asked for and revert a payback that was fine.
|
|
@@ -255,32 +213,15 @@ export const getMorphoMidnightPaybackQuote = (marketId, assetsRaw, slippagePerce
|
|
|
255
213
|
newUnits,
|
|
256
214
|
minUnits });
|
|
257
215
|
});
|
|
258
|
-
|
|
259
|
-
* Payback quoted against a **units** target instead of a spend: "retire exactly these debt units, and tell
|
|
260
|
-
* me what that costs". The sibling of `getMorphoMidnightPaybackQuote` in every other respect — same ask
|
|
261
|
-
* side, same rate floor, same offer list — but with the two amounts swapped, so the on-chain guard becomes
|
|
262
|
-
* a ceiling on assets spent rather than a floor on units bought.
|
|
263
|
-
*
|
|
264
|
-
* This is what a **full close** must be sized with. Retiring N units costs less than N loan tokens before
|
|
265
|
-
* maturity, so quoting the close as a spend of the debt's face value asks the book for more depth than the
|
|
266
|
-
* close needs (and can be refused for liquidity that is in fact there), and caps the taker's spend at more
|
|
267
|
-
* than the position is worth. Callers that cannot refund an overspend — a taker calling the bundler
|
|
268
|
-
* directly, rather than through an action contract that sweeps the remainder back — need this quote.
|
|
269
|
-
*
|
|
270
|
-
* A `minPaybackRate` above `estPaybackRate` yields `maxAssets < newAssets`: the ceiling is under what the
|
|
271
|
-
* buy costs and it would revert on-chain, the mirror of the assets-target quote's `minUnits > newUnits`.
|
|
272
|
-
*/
|
|
273
|
-
export const getMorphoMidnightPaybackUnitsQuote = (marketId, unitsRaw, slippagePercent, maturity, minPaybackRate, taker) => __awaiter(void 0, void 0, void 0, function* () {
|
|
216
|
+
export const getMorphoMidnightPaybackUnitsQuote = (marketId, unitsRaw, slippagePercent, maturity, minPaybackRate, taker, rateSlippagePercent) => __awaiter(void 0, void 0, void 0, function* () {
|
|
274
217
|
if (isTenorMidnightMarket(marketId)) {
|
|
275
|
-
return getTenorPaybackUnitsQuote(marketId, unitsRaw, slippagePercent, maturity, minPaybackRate, taker);
|
|
218
|
+
return getTenorPaybackUnitsQuote(marketId, unitsRaw, slippagePercent, maturity, minPaybackRate, taker, rateSlippagePercent);
|
|
276
219
|
}
|
|
277
220
|
const quote = yield fetchMorphoMidnightQuote(marketId, 'asks', { units: unitsRaw }, slippagePercent);
|
|
278
|
-
const { bestPrice
|
|
221
|
+
const { bestPrice } = quote;
|
|
279
222
|
const ttmDays = midnightTimeToMaturityDays(maturity);
|
|
280
223
|
const estPaybackRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
281
|
-
const capPrice =
|
|
282
|
-
? midnightPriceFromApy(minPaybackRate, ttmDays)
|
|
283
|
-
: worstPrice;
|
|
224
|
+
const capPrice = midnightBoundPrice(estPaybackRate, ttmDays, 'floor', minPaybackRate, rateSlippagePercent);
|
|
284
225
|
const minRate = midnightApyFromPrice(capPrice, ttmDays);
|
|
285
226
|
// Rounded UP on both counts — the mirror of the assets-target quote's ROUND_DOWN. Here the figures are
|
|
286
227
|
// what the taker SPENDS, so a value rounded down understates the cost by a base unit and would leave the
|
|
@@ -7,5 +7,9 @@ export declare const midnightApyFromPrice: (price: Dec.Value, ttmDays: Dec.Value
|
|
|
7
7
|
* price = (1 + rate)^(−ttmDays / 365).
|
|
8
8
|
*/
|
|
9
9
|
export declare const midnightPriceFromApy: (ratePercent: Dec.Value, ttmDays: Dec.Value) => string;
|
|
10
|
+
export declare const MIDNIGHT_DEFAULT_RATE_SLIPPAGE = 0.1;
|
|
11
|
+
/** Which side of the estimate a guard sits on: a ceiling for borrows, a floor for paybacks. */
|
|
12
|
+
export type MidnightRateBoundKind = 'ceiling' | 'floor';
|
|
13
|
+
export declare const midnightBoundPrice: (estRate: Dec.Value, ttmDays: Dec.Value, kind: MidnightRateBoundKind, boundRate?: Dec.Value, rateSlippagePercent?: Dec.Value) => string;
|
|
10
14
|
export declare const midnightBookBestFirst: (side: MorphoMidnightBookSide) => 1 | -1;
|
|
11
15
|
export declare const buildMidnightParsedBook: (offers: MorphoMidnightBookOffer[], side: MorphoMidnightBookSide) => MorphoMidnightParsedBook | null;
|
|
@@ -25,6 +25,18 @@ export const midnightPriceFromApy = (ratePercent, ttmDays) => {
|
|
|
25
25
|
return '1';
|
|
26
26
|
return new Dec(1).div(new Dec(1).add(rate.div(100)).pow(ttm.div(365))).toString();
|
|
27
27
|
};
|
|
28
|
+
export const MIDNIGHT_DEFAULT_RATE_SLIPPAGE = 0.1;
|
|
29
|
+
const MIDNIGHT_MIN_FLOOR_RATIO = 0.5;
|
|
30
|
+
export const midnightBoundPrice = (estRate, ttmDays, kind, boundRate, rateSlippagePercent = MIDNIGHT_DEFAULT_RATE_SLIPPAGE) => {
|
|
31
|
+
if (boundRate !== undefined && new Dec(boundRate).gt(0))
|
|
32
|
+
return midnightPriceFromApy(boundRate, ttmDays);
|
|
33
|
+
const est = new Dec(estRate);
|
|
34
|
+
const slippage = new Dec(rateSlippagePercent);
|
|
35
|
+
const bound = kind === 'ceiling'
|
|
36
|
+
? est.add(slippage)
|
|
37
|
+
: Dec.max(est.sub(slippage), est.mul(MIDNIGHT_MIN_FLOOR_RATIO));
|
|
38
|
+
return midnightPriceFromApy(bound, ttmDays);
|
|
39
|
+
};
|
|
28
40
|
export const midnightBookBestFirst = (side) => (side === 'asks' ? -1 : 1);
|
|
29
41
|
export const buildMidnightParsedBook = (offers, side) => {
|
|
30
42
|
const bestFirst = [...offers].sort((a, b) => new Dec(a.rate).minus(b.rate).mul(midnightBookBestFirst(side)).toNumber());
|
|
@@ -4,7 +4,6 @@ import type { MorphoMidnightBorrowQuote, MorphoMidnightPaybackQuote, MorphoMidni
|
|
|
4
4
|
type TenorBookKey = 'asks' | 'bids';
|
|
5
5
|
export declare const tenorBookKeyFor: (side: MorphoMidnightBookSide) => TenorBookKey;
|
|
6
6
|
export declare const tenorBookRateToApyPercent: (rate: Dec.Value) => string;
|
|
7
|
-
export declare const tenorCapPrice: (bestPrice: string, slippagePercent: Dec.Value, direction: 1 | -1, ttmDays: Dec.Value, boundRate?: Dec.Value) => string;
|
|
8
7
|
interface TenorOfferCollateral {
|
|
9
8
|
token: string;
|
|
10
9
|
lltv: string | number;
|
|
@@ -119,7 +118,12 @@ export declare const parseTenorBookSide: (json: TenorBookResponse, side: MorphoM
|
|
|
119
118
|
}[];
|
|
120
119
|
export declare const parseTenorOrderBook: (json: TenorBookResponse, side: MorphoMidnightBookSide, loanSymbol: string) => MorphoMidnightParsedBook | null;
|
|
121
120
|
export declare const getTenorMarketBook: (market: MorphoMidnightMarketData, network: NetworkNumber, side?: MorphoMidnightBookSide) => Promise<MorphoMidnightParsedBook | null>;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Tenor's router takes no slippage of its own — it prices the fill and hands back the offers. `slippagePercent`
|
|
123
|
+
* is therefore accepted only to keep the signature aligned with the Morpho-hosted quote that `index.ts`
|
|
124
|
+
* forwards to positionally; the guard comes from `rateSlippagePercent` via `midnightBoundPrice`.
|
|
125
|
+
*/
|
|
126
|
+
export declare const getTenorBorrowQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, maxBorrowRate?: Dec.Value, taker?: string, rateSlippagePercent?: Dec.Value, network?: NetworkNumber) => Promise<MorphoMidnightBorrowQuote>;
|
|
127
|
+
export declare const getTenorPaybackQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value, taker?: string, rateSlippagePercent?: Dec.Value, network?: NetworkNumber) => Promise<MorphoMidnightPaybackQuote>;
|
|
128
|
+
export declare const getTenorPaybackUnitsQuote: (marketId: string, unitsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value, taker?: string, rateSlippagePercent?: Dec.Value, network?: NetworkNumber) => Promise<MorphoMidnightPaybackUnitsQuote>;
|
|
125
129
|
export {};
|
|
@@ -12,7 +12,7 @@ import { assetAmountInEth, getAssetInfoByAddress } from '@defisaver/tokens';
|
|
|
12
12
|
import { ZERO_ADDRESS, ZERO_BYTES32 } from '../../constants';
|
|
13
13
|
import { NetworkNumber, } from '../../types';
|
|
14
14
|
import { isTenorMidnightMarket, MIDNIGHT_BASE } from '../../markets/morphoMidnight';
|
|
15
|
-
import { buildMidnightParsedBook, midnightApyFromPrice,
|
|
15
|
+
import { buildMidnightParsedBook, midnightApyFromPrice, midnightBoundPrice, midnightTimeToMaturityDays, } from './rate';
|
|
16
16
|
// Notion doc by Rajko: https://app.notion.com/p/defisaver/Tenor-API-3ba0be682adc80dfad35c81a9a4cb442
|
|
17
17
|
const TENOR_QUOTES_URL = 'https://router.tenor.finance/v1/quotes';
|
|
18
18
|
const TENOR_ORDERBOOK_URL = 'https://router.tenor.finance/v1/orderbook';
|
|
@@ -23,12 +23,6 @@ export const tenorBookKeyFor = (side) => (side === 'bids' ? 'asks' : 'bids');
|
|
|
23
23
|
// Tenor order-book levels are annualized APR in basis points: 25 → 0.25%, 700 → 7%.
|
|
24
24
|
export const tenorBookRateToApyPercent = (rate) => new Dec(rate || 0).div(100).toString();
|
|
25
25
|
const tenorFillPrice = (assets, units) => (new Dec(units).lte(0) ? '0' : new Dec(assets).div(units).toString());
|
|
26
|
-
export const tenorCapPrice = (bestPrice, slippagePercent, direction, ttmDays, boundRate) => {
|
|
27
|
-
if (boundRate !== undefined && new Dec(boundRate).gt(0))
|
|
28
|
-
return midnightPriceFromApy(boundRate, ttmDays);
|
|
29
|
-
const band = new Dec(1).add(new Dec(slippagePercent || 0).div(100).mul(direction));
|
|
30
|
-
return Dec.max(0, new Dec(bestPrice).mul(band)).toString();
|
|
31
|
-
};
|
|
32
26
|
/**
|
|
33
27
|
* Tenor's offer JSON is flat (market fields live on the offer). Morpho's is nested, and that nested
|
|
34
28
|
* shape is what the app encodes for `Midnight.take`. Map Tenor into that shape so recipes stay on one encoder.
|
|
@@ -152,7 +146,12 @@ const fetchTenorQuote = (_a) => __awaiter(void 0, [_a], void 0, function* ({ mar
|
|
|
152
146
|
};
|
|
153
147
|
});
|
|
154
148
|
const TENOR_NO_AVAILABLE_UNITS = '0';
|
|
155
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Tenor's router takes no slippage of its own — it prices the fill and hands back the offers. `slippagePercent`
|
|
151
|
+
* is therefore accepted only to keep the signature aligned with the Morpho-hosted quote that `index.ts`
|
|
152
|
+
* forwards to positionally; the guard comes from `rateSlippagePercent` via `midnightBoundPrice`.
|
|
153
|
+
*/
|
|
154
|
+
export const getTenorBorrowQuote = (marketId_1, assetsRaw_1, slippagePercent_1, maturity_1, maxBorrowRate_1, taker_1, rateSlippagePercent_1, ...args_1) => __awaiter(void 0, [marketId_1, assetsRaw_1, slippagePercent_1, maturity_1, maxBorrowRate_1, taker_1, rateSlippagePercent_1, ...args_1], void 0, function* (marketId, assetsRaw, slippagePercent, maturity, maxBorrowRate, taker, rateSlippagePercent, network = NetworkNumber.Base) {
|
|
156
155
|
if (!isTenorMidnightMarket(marketId)) {
|
|
157
156
|
throw new Error(tenorQuoteError(`Not a Tenor Midnight market: ${marketId}`));
|
|
158
157
|
}
|
|
@@ -165,7 +164,7 @@ export const getTenorBorrowQuote = (marketId_1, assetsRaw_1, slippagePercent_1,
|
|
|
165
164
|
const ttmDays = midnightTimeToMaturityDays(maturity);
|
|
166
165
|
const bestPrice = tenorFillPrice(assetsRaw, quote.units);
|
|
167
166
|
const estBorrowRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
168
|
-
const capPrice =
|
|
167
|
+
const capPrice = midnightBoundPrice(estBorrowRate, ttmDays, 'ceiling', maxBorrowRate, rateSlippagePercent);
|
|
169
168
|
const maxRate = midnightApyFromPrice(capPrice, ttmDays);
|
|
170
169
|
const maxUnits = new Dec(capPrice).lte(0) ? '0' : new Dec(assetsRaw).div(capPrice).toFixed(0);
|
|
171
170
|
return {
|
|
@@ -180,7 +179,7 @@ export const getTenorBorrowQuote = (marketId_1, assetsRaw_1, slippagePercent_1,
|
|
|
180
179
|
takeableOffers: quote.offerFills,
|
|
181
180
|
};
|
|
182
181
|
});
|
|
183
|
-
export const getTenorPaybackQuote = (marketId_1, assetsRaw_1, slippagePercent_1, maturity_1, minPaybackRate_1, taker_1, ...args_1) => __awaiter(void 0, [marketId_1, assetsRaw_1, slippagePercent_1, maturity_1, minPaybackRate_1, taker_1, ...args_1], void 0, function* (marketId, assetsRaw, slippagePercent, maturity, minPaybackRate, taker, network = NetworkNumber.Base) {
|
|
182
|
+
export const getTenorPaybackQuote = (marketId_1, assetsRaw_1, slippagePercent_1, maturity_1, minPaybackRate_1, taker_1, rateSlippagePercent_1, ...args_1) => __awaiter(void 0, [marketId_1, assetsRaw_1, slippagePercent_1, maturity_1, minPaybackRate_1, taker_1, rateSlippagePercent_1, ...args_1], void 0, function* (marketId, assetsRaw, slippagePercent, maturity, minPaybackRate, taker, rateSlippagePercent, network = NetworkNumber.Base) {
|
|
184
183
|
if (!isTenorMidnightMarket(marketId)) {
|
|
185
184
|
throw new Error(tenorQuoteError(`Not a Tenor Midnight market: ${marketId}`));
|
|
186
185
|
}
|
|
@@ -193,7 +192,7 @@ export const getTenorPaybackQuote = (marketId_1, assetsRaw_1, slippagePercent_1,
|
|
|
193
192
|
const ttmDays = midnightTimeToMaturityDays(maturity);
|
|
194
193
|
const bestPrice = tenorFillPrice(assetsRaw, quote.units);
|
|
195
194
|
const estPaybackRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
196
|
-
const capPrice =
|
|
195
|
+
const capPrice = midnightBoundPrice(estPaybackRate, ttmDays, 'floor', minPaybackRate, rateSlippagePercent);
|
|
197
196
|
const minRate = midnightApyFromPrice(capPrice, ttmDays);
|
|
198
197
|
const minUnits = new Dec(capPrice).lte(0) ? '0' : new Dec(assetsRaw).div(capPrice).toFixed(0, Dec.ROUND_DOWN);
|
|
199
198
|
return {
|
|
@@ -208,7 +207,7 @@ export const getTenorPaybackQuote = (marketId_1, assetsRaw_1, slippagePercent_1,
|
|
|
208
207
|
takeableOffers: quote.offerFills,
|
|
209
208
|
};
|
|
210
209
|
});
|
|
211
|
-
export const getTenorPaybackUnitsQuote = (marketId_1, unitsRaw_1, slippagePercent_1, maturity_1, minPaybackRate_1, taker_1, ...args_1) => __awaiter(void 0, [marketId_1, unitsRaw_1, slippagePercent_1, maturity_1, minPaybackRate_1, taker_1, ...args_1], void 0, function* (marketId, unitsRaw, slippagePercent, maturity, minPaybackRate, taker, network = NetworkNumber.Base) {
|
|
210
|
+
export const getTenorPaybackUnitsQuote = (marketId_1, unitsRaw_1, slippagePercent_1, maturity_1, minPaybackRate_1, taker_1, rateSlippagePercent_1, ...args_1) => __awaiter(void 0, [marketId_1, unitsRaw_1, slippagePercent_1, maturity_1, minPaybackRate_1, taker_1, rateSlippagePercent_1, ...args_1], void 0, function* (marketId, unitsRaw, slippagePercent, maturity, minPaybackRate, taker, rateSlippagePercent, network = NetworkNumber.Base) {
|
|
212
211
|
if (!isTenorMidnightMarket(marketId)) {
|
|
213
212
|
throw new Error(tenorQuoteError(`Not a Tenor Midnight market: ${marketId}`));
|
|
214
213
|
}
|
|
@@ -222,7 +221,7 @@ export const getTenorPaybackUnitsQuote = (marketId_1, unitsRaw_1, slippagePercen
|
|
|
222
221
|
const newAssets = quote.buyerAssets;
|
|
223
222
|
const bestPrice = tenorFillPrice(newAssets, unitsRaw);
|
|
224
223
|
const estPaybackRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
225
|
-
const capPrice =
|
|
224
|
+
const capPrice = midnightBoundPrice(estPaybackRate, ttmDays, 'floor', minPaybackRate, rateSlippagePercent);
|
|
226
225
|
const minRate = midnightApyFromPrice(capPrice, ttmDays);
|
|
227
226
|
const maxAssets = new Dec(unitsRaw).mul(capPrice).toFixed(0, Dec.ROUND_UP);
|
|
228
227
|
return {
|
|
@@ -158,12 +158,14 @@ export function _getMorphoMidnightAccountData(provider, network, account, select
|
|
|
158
158
|
if (new Dec(positionInfo.debt.toString()).gt(0)) {
|
|
159
159
|
try {
|
|
160
160
|
const borrowInfo = yield getMorphoMidnightUserBorrowInfo(account, marketId, marketInfo.loanToken);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
161
|
+
if (new Dec(borrowInfo.debtTotal).gt(0)) {
|
|
162
|
+
borrowRate = borrowInfo.borrowRate;
|
|
163
|
+
debtBase = borrowInfo.debtBase;
|
|
164
|
+
debtInterest = borrowInfo.debtInterest;
|
|
165
|
+
usedAssets[marketInfo.loanToken].borrowRate = borrowRate;
|
|
166
|
+
// Reflect the real borrow cost in netApy without mutating the shared marketInfo.assetsData.
|
|
167
|
+
assetsDataForApy = Object.assign(Object.assign({}, marketInfo.assetsData), { [marketInfo.loanToken]: Object.assign(Object.assign({}, loanTokenData), { borrowRate }) });
|
|
168
|
+
}
|
|
167
169
|
}
|
|
168
170
|
catch (err) {
|
|
169
171
|
// Orderbook API unavailable — keep the on-chain-only fallback above.
|
package/package.json
CHANGED
|
@@ -20,7 +20,7 @@ import { WAD } from '../../constants';
|
|
|
20
20
|
import { LONGER_TIMEOUT } from '../../services/utils';
|
|
21
21
|
import { isTenorMidnightMarket } from '../../markets/morphoMidnight';
|
|
22
22
|
import {
|
|
23
|
-
buildMidnightParsedBook, midnightApyFromPrice,
|
|
23
|
+
buildMidnightParsedBook, midnightApyFromPrice, midnightBoundPrice, midnightTimeToMaturityDays,
|
|
24
24
|
} from './rate';
|
|
25
25
|
import {
|
|
26
26
|
getTenorBorrowQuote, getTenorMarketBook, getTenorPaybackQuote, getTenorPaybackUnitsQuote,
|
|
@@ -30,8 +30,10 @@ export {
|
|
|
30
30
|
buildMidnightParsedBook,
|
|
31
31
|
midnightApyFromPrice,
|
|
32
32
|
midnightBookBestFirst,
|
|
33
|
+
midnightBoundPrice,
|
|
33
34
|
midnightPriceFromApy,
|
|
34
35
|
midnightTimeToMaturityDays,
|
|
36
|
+
MIDNIGHT_DEFAULT_RATE_SLIPPAGE,
|
|
35
37
|
} from './rate';
|
|
36
38
|
export {
|
|
37
39
|
tenorBookKeyFor,
|
|
@@ -332,26 +334,6 @@ const fetchMorphoMidnightQuote = async (
|
|
|
332
334
|
};
|
|
333
335
|
};
|
|
334
336
|
|
|
335
|
-
/**
|
|
336
|
-
* Quote a prospective borrow against the Midnight order book: the estimated rate, the debt units it adds,
|
|
337
|
-
* and the `maxUnits` cap sent on-chain to protect the user if better offers get filled first. `assetsRaw`
|
|
338
|
-
* (and the returned `newUnits`/`maxUnits`) are raw loan-token base units — callers convert to/from human
|
|
339
|
-
* amounts. Throws if the book can't fill the amount (caller handles).
|
|
340
|
-
*
|
|
341
|
-
* Two ways to set the cap:
|
|
342
|
-
* - `maxBorrowRate` — an absolute APY ceiling, honoured **exactly**: the cap price is derived locally via
|
|
343
|
-
* `midnightPriceFromApy`. Prefer this when a user pins a max rate.
|
|
344
|
-
* - otherwise `slippagePercent`, the API's own knob. Note it is a **price**-level slippage, not APY points:
|
|
345
|
-
* near maturity the annualisation factor (365 / ttmDays) multiplies it heavily, so on a 22-day market a
|
|
346
|
-
* slippage of 0.5 permitted an APY ~9pp above the estimate, not 0.5pp. It also saturates at the book's
|
|
347
|
-
* cheapest bid. `maxRate` therefore reports what the cap actually permits, derived from the cap price.
|
|
348
|
-
*
|
|
349
|
-
* A `maxBorrowRate` below `estBorrowRate` yields `maxUnits < newUnits` — the borrow would revert on-chain.
|
|
350
|
-
* Compare the two before submitting and tell the user their ceiling is under the market rate.
|
|
351
|
-
*
|
|
352
|
-
* Tenor-hosted markets (see `isTenorMidnightMarket`) are quoted against Tenor's router instead of Morpho's
|
|
353
|
-
* book API. Slippage is applied locally to `maxUnits`; `taker` is the position owner (the smart wallet).
|
|
354
|
-
*/
|
|
355
337
|
export const getMorphoMidnightBorrowQuote = async (
|
|
356
338
|
marketId: string,
|
|
357
339
|
assetsRaw: string,
|
|
@@ -359,21 +341,20 @@ export const getMorphoMidnightBorrowQuote = async (
|
|
|
359
341
|
maturity: number,
|
|
360
342
|
maxBorrowRate?: Dec.Value,
|
|
361
343
|
taker?: string,
|
|
344
|
+
rateSlippagePercent?: Dec.Value,
|
|
362
345
|
): Promise<MorphoMidnightBorrowQuote> => {
|
|
363
346
|
if (isTenorMidnightMarket(marketId)) {
|
|
364
|
-
return getTenorBorrowQuote(marketId, assetsRaw, slippagePercent, maturity, maxBorrowRate, taker);
|
|
347
|
+
return getTenorBorrowQuote(marketId, assetsRaw, slippagePercent, maturity, maxBorrowRate, taker, rateSlippagePercent);
|
|
365
348
|
}
|
|
366
349
|
|
|
367
350
|
const quote = await fetchMorphoMidnightQuote(marketId, 'bids', { assets: assetsRaw }, slippagePercent);
|
|
368
|
-
const { bestPrice
|
|
351
|
+
const { bestPrice } = quote;
|
|
369
352
|
const ttmDays = midnightTimeToMaturityDays(maturity);
|
|
370
353
|
const estBorrowRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
371
354
|
|
|
372
355
|
// Price the cap sits at, and the rate that price represents — one derivation, so `maxRate` and
|
|
373
356
|
// `maxUnits` can never disagree about what the user is protected at.
|
|
374
|
-
const capPrice =
|
|
375
|
-
? midnightPriceFromApy(maxBorrowRate, ttmDays)
|
|
376
|
-
: worstPrice;
|
|
357
|
+
const capPrice = midnightBoundPrice(estBorrowRate, ttmDays, 'ceiling', maxBorrowRate, rateSlippagePercent);
|
|
377
358
|
const maxRate = midnightApyFromPrice(capPrice, ttmDays);
|
|
378
359
|
const newUnits = new Dec(bestPrice).lte(0) ? '0' : new Dec(assetsRaw).div(bestPrice).toFixed(0);
|
|
379
360
|
const maxUnits = new Dec(capPrice).lte(0) ? '0' : new Dec(assetsRaw).div(capPrice).toFixed(0);
|
|
@@ -387,24 +368,6 @@ export const getMorphoMidnightBorrowQuote = async (
|
|
|
387
368
|
};
|
|
388
369
|
};
|
|
389
370
|
|
|
390
|
-
/**
|
|
391
|
-
* Quote a prospective payback against the ask side of the Midnight order book: the rate the repayment
|
|
392
|
-
* retires debt at, the debt units it buys, and the `minUnits` floor sent on-chain to protect the user if
|
|
393
|
-
* the cheap offers get taken first. `assetsRaw` is the amount **spent** (raw loan-token base units) —
|
|
394
|
-
* matching `MidnightPaybackFromOrders`, whose `amount` is what leaves the wallet; the units bought, and
|
|
395
|
-
* therefore the debt retired, exceed it because a unit costs less than one loan token before maturity.
|
|
396
|
-
* Throws if the book can't fill the amount (caller handles).
|
|
397
|
-
*
|
|
398
|
-
* The mirror image of the borrow quote in every respect. A repayer wants a *high* rate, i.e. cheap units,
|
|
399
|
-
* so the guard is a floor rather than a ceiling:
|
|
400
|
-
* - `minPaybackRate` — an absolute APY floor, honoured **exactly** via `midnightPriceFromApy`. Prefer
|
|
401
|
-
* this when a user pins a min rate.
|
|
402
|
-
* - otherwise `slippagePercent`, the API's own price-level knob, whose APY effect is amplified by the
|
|
403
|
-
* annualisation factor near maturity. `minRate` reports what the floor actually permits.
|
|
404
|
-
*
|
|
405
|
-
* A `minPaybackRate` above `estPaybackRate` yields `minUnits > newUnits` — the payback would revert
|
|
406
|
-
* on-chain. Compare the two before submitting and tell the user their floor is over the market rate.
|
|
407
|
-
*/
|
|
408
371
|
export const getMorphoMidnightPaybackQuote = async (
|
|
409
372
|
marketId: string,
|
|
410
373
|
assetsRaw: string,
|
|
@@ -412,19 +375,18 @@ export const getMorphoMidnightPaybackQuote = async (
|
|
|
412
375
|
maturity: number,
|
|
413
376
|
minPaybackRate?: Dec.Value,
|
|
414
377
|
taker?: string,
|
|
378
|
+
rateSlippagePercent?: Dec.Value,
|
|
415
379
|
): Promise<MorphoMidnightPaybackQuote> => {
|
|
416
380
|
if (isTenorMidnightMarket(marketId)) {
|
|
417
|
-
return getTenorPaybackQuote(marketId, assetsRaw, slippagePercent, maturity, minPaybackRate, taker);
|
|
381
|
+
return getTenorPaybackQuote(marketId, assetsRaw, slippagePercent, maturity, minPaybackRate, taker, rateSlippagePercent);
|
|
418
382
|
}
|
|
419
383
|
|
|
420
384
|
const quote = await fetchMorphoMidnightQuote(marketId, 'asks', { assets: assetsRaw }, slippagePercent);
|
|
421
|
-
const { bestPrice
|
|
385
|
+
const { bestPrice } = quote;
|
|
422
386
|
const ttmDays = midnightTimeToMaturityDays(maturity);
|
|
423
387
|
const estPaybackRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
424
388
|
|
|
425
|
-
const capPrice =
|
|
426
|
-
? midnightPriceFromApy(minPaybackRate, ttmDays)
|
|
427
|
-
: worstPrice;
|
|
389
|
+
const capPrice = midnightBoundPrice(estPaybackRate, ttmDays, 'floor', minPaybackRate, rateSlippagePercent);
|
|
428
390
|
const minRate = midnightApyFromPrice(capPrice, ttmDays);
|
|
429
391
|
// Rounded down on both counts: `newUnits` must not overstate the debt the user sees retired, and a
|
|
430
392
|
// `minUnits` rounded up would be a stricter floor than asked for and revert a payback that was fine.
|
|
@@ -440,21 +402,7 @@ export const getMorphoMidnightPaybackQuote = async (
|
|
|
440
402
|
};
|
|
441
403
|
};
|
|
442
404
|
|
|
443
|
-
|
|
444
|
-
* Payback quoted against a **units** target instead of a spend: "retire exactly these debt units, and tell
|
|
445
|
-
* me what that costs". The sibling of `getMorphoMidnightPaybackQuote` in every other respect — same ask
|
|
446
|
-
* side, same rate floor, same offer list — but with the two amounts swapped, so the on-chain guard becomes
|
|
447
|
-
* a ceiling on assets spent rather than a floor on units bought.
|
|
448
|
-
*
|
|
449
|
-
* This is what a **full close** must be sized with. Retiring N units costs less than N loan tokens before
|
|
450
|
-
* maturity, so quoting the close as a spend of the debt's face value asks the book for more depth than the
|
|
451
|
-
* close needs (and can be refused for liquidity that is in fact there), and caps the taker's spend at more
|
|
452
|
-
* than the position is worth. Callers that cannot refund an overspend — a taker calling the bundler
|
|
453
|
-
* directly, rather than through an action contract that sweeps the remainder back — need this quote.
|
|
454
|
-
*
|
|
455
|
-
* A `minPaybackRate` above `estPaybackRate` yields `maxAssets < newAssets`: the ceiling is under what the
|
|
456
|
-
* buy costs and it would revert on-chain, the mirror of the assets-target quote's `minUnits > newUnits`.
|
|
457
|
-
*/
|
|
405
|
+
|
|
458
406
|
export const getMorphoMidnightPaybackUnitsQuote = async (
|
|
459
407
|
marketId: string,
|
|
460
408
|
unitsRaw: string,
|
|
@@ -462,19 +410,18 @@ export const getMorphoMidnightPaybackUnitsQuote = async (
|
|
|
462
410
|
maturity: number,
|
|
463
411
|
minPaybackRate?: Dec.Value,
|
|
464
412
|
taker?: string,
|
|
413
|
+
rateSlippagePercent?: Dec.Value,
|
|
465
414
|
): Promise<MorphoMidnightPaybackUnitsQuote> => {
|
|
466
415
|
if (isTenorMidnightMarket(marketId)) {
|
|
467
|
-
return getTenorPaybackUnitsQuote(marketId, unitsRaw, slippagePercent, maturity, minPaybackRate, taker);
|
|
416
|
+
return getTenorPaybackUnitsQuote(marketId, unitsRaw, slippagePercent, maturity, minPaybackRate, taker, rateSlippagePercent);
|
|
468
417
|
}
|
|
469
418
|
|
|
470
419
|
const quote = await fetchMorphoMidnightQuote(marketId, 'asks', { units: unitsRaw }, slippagePercent);
|
|
471
|
-
const { bestPrice
|
|
420
|
+
const { bestPrice } = quote;
|
|
472
421
|
const ttmDays = midnightTimeToMaturityDays(maturity);
|
|
473
422
|
const estPaybackRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
474
423
|
|
|
475
|
-
const capPrice =
|
|
476
|
-
? midnightPriceFromApy(minPaybackRate, ttmDays)
|
|
477
|
-
: worstPrice;
|
|
424
|
+
const capPrice = midnightBoundPrice(estPaybackRate, ttmDays, 'floor', minPaybackRate, rateSlippagePercent);
|
|
478
425
|
const minRate = midnightApyFromPrice(capPrice, ttmDays);
|
|
479
426
|
// Rounded UP on both counts — the mirror of the assets-target quote's ROUND_DOWN. Here the figures are
|
|
480
427
|
// what the taker SPENDS, so a value rounded down understates the cost by a base unit and would leave the
|
|
@@ -29,6 +29,31 @@ export const midnightPriceFromApy = (ratePercent: Dec.Value, ttmDays: Dec.Value)
|
|
|
29
29
|
return new Dec(1).div(new Dec(1).add(rate.div(100)).pow(ttm.div(365))).toString();
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
+
export const MIDNIGHT_DEFAULT_RATE_SLIPPAGE = 0.1;
|
|
33
|
+
|
|
34
|
+
const MIDNIGHT_MIN_FLOOR_RATIO = 0.5;
|
|
35
|
+
|
|
36
|
+
/** Which side of the estimate a guard sits on: a ceiling for borrows, a floor for paybacks. */
|
|
37
|
+
export type MidnightRateBoundKind = 'ceiling' | 'floor';
|
|
38
|
+
|
|
39
|
+
export const midnightBoundPrice = (
|
|
40
|
+
estRate: Dec.Value,
|
|
41
|
+
ttmDays: Dec.Value,
|
|
42
|
+
kind: MidnightRateBoundKind,
|
|
43
|
+
boundRate?: Dec.Value,
|
|
44
|
+
rateSlippagePercent: Dec.Value = MIDNIGHT_DEFAULT_RATE_SLIPPAGE,
|
|
45
|
+
): string => {
|
|
46
|
+
if (boundRate !== undefined && new Dec(boundRate).gt(0)) return midnightPriceFromApy(boundRate, ttmDays);
|
|
47
|
+
|
|
48
|
+
const est = new Dec(estRate);
|
|
49
|
+
const slippage = new Dec(rateSlippagePercent);
|
|
50
|
+
const bound = kind === 'ceiling'
|
|
51
|
+
? est.add(slippage)
|
|
52
|
+
: Dec.max(est.sub(slippage), est.mul(MIDNIGHT_MIN_FLOOR_RATIO));
|
|
53
|
+
|
|
54
|
+
return midnightPriceFromApy(bound, ttmDays);
|
|
55
|
+
};
|
|
56
|
+
|
|
32
57
|
export const midnightBookBestFirst = (side: MorphoMidnightBookSide): 1 | -1 => (side === 'asks' ? -1 : 1);
|
|
33
58
|
|
|
34
59
|
export const buildMidnightParsedBook = (
|
|
@@ -17,7 +17,7 @@ import type {
|
|
|
17
17
|
import {
|
|
18
18
|
buildMidnightParsedBook,
|
|
19
19
|
midnightApyFromPrice,
|
|
20
|
-
|
|
20
|
+
midnightBoundPrice,
|
|
21
21
|
midnightTimeToMaturityDays,
|
|
22
22
|
} from './rate';
|
|
23
23
|
|
|
@@ -43,18 +43,6 @@ const tenorFillPrice = (assets: Dec.Value, units: Dec.Value): string => (
|
|
|
43
43
|
new Dec(units).lte(0) ? '0' : new Dec(assets).div(units).toString()
|
|
44
44
|
);
|
|
45
45
|
|
|
46
|
-
export const tenorCapPrice = (
|
|
47
|
-
bestPrice: string,
|
|
48
|
-
slippagePercent: Dec.Value,
|
|
49
|
-
direction: 1 | -1,
|
|
50
|
-
ttmDays: Dec.Value,
|
|
51
|
-
boundRate?: Dec.Value,
|
|
52
|
-
): string => {
|
|
53
|
-
if (boundRate !== undefined && new Dec(boundRate).gt(0)) return midnightPriceFromApy(boundRate, ttmDays);
|
|
54
|
-
const band = new Dec(1).add(new Dec(slippagePercent || 0).div(100).mul(direction));
|
|
55
|
-
return Dec.max(0, new Dec(bestPrice).mul(band)).toString();
|
|
56
|
-
};
|
|
57
|
-
|
|
58
46
|
interface TenorOfferCollateral {
|
|
59
47
|
token: string;
|
|
60
48
|
lltv: string | number;
|
|
@@ -286,6 +274,11 @@ const fetchTenorQuote = async ({
|
|
|
286
274
|
|
|
287
275
|
const TENOR_NO_AVAILABLE_UNITS = '0';
|
|
288
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Tenor's router takes no slippage of its own — it prices the fill and hands back the offers. `slippagePercent`
|
|
279
|
+
* is therefore accepted only to keep the signature aligned with the Morpho-hosted quote that `index.ts`
|
|
280
|
+
* forwards to positionally; the guard comes from `rateSlippagePercent` via `midnightBoundPrice`.
|
|
281
|
+
*/
|
|
289
282
|
export const getTenorBorrowQuote = async (
|
|
290
283
|
marketId: string,
|
|
291
284
|
assetsRaw: string,
|
|
@@ -293,6 +286,7 @@ export const getTenorBorrowQuote = async (
|
|
|
293
286
|
maturity: number,
|
|
294
287
|
maxBorrowRate?: Dec.Value,
|
|
295
288
|
taker?: string,
|
|
289
|
+
rateSlippagePercent?: Dec.Value,
|
|
296
290
|
network: NetworkNumber = NetworkNumber.Base,
|
|
297
291
|
): Promise<MorphoMidnightBorrowQuote> => {
|
|
298
292
|
if (!isTenorMidnightMarket(marketId)) {
|
|
@@ -310,7 +304,7 @@ export const getTenorBorrowQuote = async (
|
|
|
310
304
|
const bestPrice = tenorFillPrice(assetsRaw, quote.units);
|
|
311
305
|
const estBorrowRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
312
306
|
|
|
313
|
-
const capPrice =
|
|
307
|
+
const capPrice = midnightBoundPrice(estBorrowRate, ttmDays, 'ceiling', maxBorrowRate, rateSlippagePercent);
|
|
314
308
|
const maxRate = midnightApyFromPrice(capPrice, ttmDays);
|
|
315
309
|
const maxUnits = new Dec(capPrice).lte(0) ? '0' : new Dec(assetsRaw).div(capPrice).toFixed(0);
|
|
316
310
|
|
|
@@ -334,6 +328,7 @@ export const getTenorPaybackQuote = async (
|
|
|
334
328
|
maturity: number,
|
|
335
329
|
minPaybackRate?: Dec.Value,
|
|
336
330
|
taker?: string,
|
|
331
|
+
rateSlippagePercent?: Dec.Value,
|
|
337
332
|
network: NetworkNumber = NetworkNumber.Base,
|
|
338
333
|
): Promise<MorphoMidnightPaybackQuote> => {
|
|
339
334
|
if (!isTenorMidnightMarket(marketId)) {
|
|
@@ -351,7 +346,7 @@ export const getTenorPaybackQuote = async (
|
|
|
351
346
|
const bestPrice = tenorFillPrice(assetsRaw, quote.units);
|
|
352
347
|
const estPaybackRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
353
348
|
|
|
354
|
-
const capPrice =
|
|
349
|
+
const capPrice = midnightBoundPrice(estPaybackRate, ttmDays, 'floor', minPaybackRate, rateSlippagePercent);
|
|
355
350
|
const minRate = midnightApyFromPrice(capPrice, ttmDays);
|
|
356
351
|
const minUnits = new Dec(capPrice).lte(0) ? '0' : new Dec(assetsRaw).div(capPrice).toFixed(0, Dec.ROUND_DOWN);
|
|
357
352
|
|
|
@@ -375,6 +370,7 @@ export const getTenorPaybackUnitsQuote = async (
|
|
|
375
370
|
maturity: number,
|
|
376
371
|
minPaybackRate?: Dec.Value,
|
|
377
372
|
taker?: string,
|
|
373
|
+
rateSlippagePercent?: Dec.Value,
|
|
378
374
|
network: NetworkNumber = NetworkNumber.Base,
|
|
379
375
|
): Promise<MorphoMidnightPaybackUnitsQuote> => {
|
|
380
376
|
if (!isTenorMidnightMarket(marketId)) {
|
|
@@ -393,7 +389,7 @@ export const getTenorPaybackUnitsQuote = async (
|
|
|
393
389
|
const bestPrice = tenorFillPrice(newAssets, unitsRaw);
|
|
394
390
|
const estPaybackRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
395
391
|
|
|
396
|
-
const capPrice =
|
|
392
|
+
const capPrice = midnightBoundPrice(estPaybackRate, ttmDays, 'floor', minPaybackRate, rateSlippagePercent);
|
|
397
393
|
const minRate = midnightApyFromPrice(capPrice, ttmDays);
|
|
398
394
|
const maxAssets = new Dec(unitsRaw).mul(capPrice).toFixed(0, Dec.ROUND_UP);
|
|
399
395
|
|
|
@@ -167,15 +167,17 @@ export async function _getMorphoMidnightAccountData(provider: Client, network: N
|
|
|
167
167
|
if (new Dec(positionInfo.debt.toString()).gt(0)) {
|
|
168
168
|
try {
|
|
169
169
|
const borrowInfo = await getMorphoMidnightUserBorrowInfo(account, marketId, marketInfo.loanToken);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
170
|
+
if (new Dec(borrowInfo.debtTotal).gt(0)) {
|
|
171
|
+
borrowRate = borrowInfo.borrowRate;
|
|
172
|
+
debtBase = borrowInfo.debtBase;
|
|
173
|
+
debtInterest = borrowInfo.debtInterest;
|
|
174
|
+
usedAssets[marketInfo.loanToken].borrowRate = borrowRate;
|
|
175
|
+
// Reflect the real borrow cost in netApy without mutating the shared marketInfo.assetsData.
|
|
176
|
+
assetsDataForApy = {
|
|
177
|
+
...marketInfo.assetsData,
|
|
178
|
+
[marketInfo.loanToken]: { ...loanTokenData, borrowRate },
|
|
179
|
+
};
|
|
180
|
+
}
|
|
179
181
|
} catch (err) {
|
|
180
182
|
// Orderbook API unavailable — keep the on-chain-only fallback above.
|
|
181
183
|
}
|