@defisaver/positions-sdk 2.1.127-midnight-3-dev → 2.1.127-midnight-4-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 +49 -12
- package/cjs/helpers/morphoMidnightHelpers/index.js +88 -54
- package/cjs/morphoMidnight/index.js +1 -1
- package/cjs/types/morphoMidnight.d.ts +1 -0
- package/esm/helpers/morphoMidnightHelpers/index.d.ts +49 -12
- package/esm/helpers/morphoMidnightHelpers/index.js +86 -53
- package/esm/morphoMidnight/index.js +1 -1
- package/esm/types/morphoMidnight.d.ts +1 -0
- package/package.json +1 -1
- package/src/helpers/morphoMidnightHelpers/index.ts +135 -59
- package/src/morphoMidnight/index.ts +1 -1
- package/src/types/morphoMidnight.ts +8 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Dec from 'decimal.js';
|
|
2
2
|
import { MMUsedAssets, NetworkNumber } from '../../types/common';
|
|
3
|
-
import { MorphoMidnightAggregatedPositionData, MorphoMidnightAssetsData, MorphoMidnightMarketData, MorphoMidnightMarketInfo, MorphoMidnightParsedBook } from '../../types';
|
|
3
|
+
import { MorphoMidnightAggregatedPositionData, MorphoMidnightAssetsData, MorphoMidnightBookSide, MorphoMidnightMarketData, MorphoMidnightMarketInfo, MorphoMidnightParsedBook } from '../../types';
|
|
4
4
|
/**
|
|
5
5
|
* Aggregate a Morpho Midnight position. Midnight markets are multi-collateral, so the borrow limit is
|
|
6
6
|
* the sum of each collateral's USD value times its own lltv (Aave-v4 style), rather than a single pair.
|
|
@@ -32,6 +32,17 @@ export interface MorphoMidnightBorrowQuote {
|
|
|
32
32
|
availableUnits: string;
|
|
33
33
|
takeableOffers: any[];
|
|
34
34
|
}
|
|
35
|
+
export interface MorphoMidnightPaybackQuote {
|
|
36
|
+
bestPrice: string;
|
|
37
|
+
worstPrice: string;
|
|
38
|
+
estPaybackRate: string;
|
|
39
|
+
minRate: string;
|
|
40
|
+
newUnits: string;
|
|
41
|
+
minUnits: string;
|
|
42
|
+
availableAssets: string;
|
|
43
|
+
availableUnits: string;
|
|
44
|
+
takeableOffers: any[];
|
|
45
|
+
}
|
|
35
46
|
export declare const midnightTimeToMaturityDays: (maturity: number, atSeconds?: number) => number;
|
|
36
47
|
export declare const midnightApyFromPrice: (price: Dec.Value, ttmDays: Dec.Value) => string;
|
|
37
48
|
/**
|
|
@@ -52,22 +63,29 @@ export declare const midnightPriceFromApy: (ratePercent: Dec.Value, ttmDays: Dec
|
|
|
52
63
|
*/
|
|
53
64
|
export declare const midnightSlippageParam: (slippagePercent: Dec.Value) => string;
|
|
54
65
|
/**
|
|
55
|
-
* Current borrower rate + debt breakdown from the Midnight
|
|
56
|
-
*
|
|
57
|
-
*
|
|
66
|
+
* Current borrower rate + debt breakdown from the Midnight positions API. Reconstructing this from the raw
|
|
67
|
+
* `/transactions` fill history only sums `borrow` fills, so it overstates debt for any position with an
|
|
68
|
+
* early exit or partial liquidation (`exit_borrow_primary`, `partial_liquidation`, ... — the fill history
|
|
69
|
+
* has no exhaustive list of debt-reducing event types). `/positions` instead reports the already-netted
|
|
70
|
+
* `debt`, matching `MidnightView.getPositionInfo` exactly, plus `cost_basis` (outstanding principal,
|
|
71
|
+
* WAD-scaled raw base units) and `effective_rate_wad` (borrow APY, WAD-scaled) for the base/interest split.
|
|
58
72
|
* The caller swallows errors — a missing rate must never block position rendering.
|
|
59
73
|
*/
|
|
60
|
-
export declare const getMorphoMidnightUserBorrowInfo: (account: string, marketId: string,
|
|
74
|
+
export declare const getMorphoMidnightUserBorrowInfo: (account: string, marketId: string, loanTokenSymbol: string) => Promise<MorphoMidnightBorrowInfo>;
|
|
61
75
|
/**
|
|
62
|
-
*
|
|
63
|
-
* each price against time-to-maturity gives the rate a
|
|
64
|
-
* against Morpho's fixed-market UI, where per-offer rates match to the cent.
|
|
76
|
+
* One side of a market's resting order book, as rates rather than the API's WAD-scaled loan-per-unit
|
|
77
|
+
* prices. Annualizing each price against time-to-maturity gives the rate a taker filling that offer gets
|
|
78
|
+
* — verified against Morpho's fixed-market UI, where per-offer rates match to the cent.
|
|
79
|
+
*
|
|
80
|
+
* `bids` are the lend offers a borrower fills, so the best of them is the *lowest* rate; `asks` are the
|
|
81
|
+
* sell offers a repayer buys debt units from, where a lower price buys more units, so the best is the
|
|
82
|
+
* *highest* rate. Either way `offers` comes back best-first and `bestRate` is `offers[0].rate`.
|
|
65
83
|
*
|
|
66
|
-
* Returns `null` for an empty
|
|
67
|
-
*
|
|
68
|
-
*
|
|
84
|
+
* Returns `null` for an empty side: there is nothing to take, so a market listing should skip the market
|
|
85
|
+
* rather than advertise it at a 0% rate. Throws when the request fails — an error response is rarely
|
|
86
|
+
* JSON, so without the `res.ok` check it parses as an empty book and the market silently vanishes.
|
|
69
87
|
*/
|
|
70
|
-
export declare const getMorphoMidnightMarketBook: (market: MorphoMidnightMarketData, network: NetworkNumber) => Promise<MorphoMidnightParsedBook | null>;
|
|
88
|
+
export declare const getMorphoMidnightMarketBook: (market: MorphoMidnightMarketData, network: NetworkNumber, side?: MorphoMidnightBookSide) => Promise<MorphoMidnightParsedBook | null>;
|
|
71
89
|
/**
|
|
72
90
|
* Quote a prospective borrow against the Midnight order book: the estimated rate, the debt units it adds,
|
|
73
91
|
* and the `maxUnits` cap sent on-chain to protect the user if better offers get filled first. `assetsRaw`
|
|
@@ -86,3 +104,22 @@ export declare const getMorphoMidnightMarketBook: (market: MorphoMidnightMarketD
|
|
|
86
104
|
* Compare the two before submitting and tell the user their ceiling is under the market rate.
|
|
87
105
|
*/
|
|
88
106
|
export declare const getMorphoMidnightBorrowQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, maxBorrowRate?: Dec.Value) => Promise<MorphoMidnightBorrowQuote>;
|
|
107
|
+
/**
|
|
108
|
+
* Quote a prospective payback against the ask side of the Midnight order book: the rate the repayment
|
|
109
|
+
* retires debt at, the debt units it buys, and the `minUnits` floor sent on-chain to protect the user if
|
|
110
|
+
* the cheap offers get taken first. `assetsRaw` is the amount **spent** (raw loan-token base units) —
|
|
111
|
+
* matching `MidnightPaybackFromOrders`, whose `amount` is what leaves the wallet; the units bought, and
|
|
112
|
+
* therefore the debt retired, exceed it because a unit costs less than one loan token before maturity.
|
|
113
|
+
* Throws if the book can't fill the amount (caller handles).
|
|
114
|
+
*
|
|
115
|
+
* The mirror image of the borrow quote in every respect. A repayer wants a *high* rate, i.e. cheap units,
|
|
116
|
+
* so the guard is a floor rather than a ceiling:
|
|
117
|
+
* - `minPaybackRate` — an absolute APY floor, honoured **exactly** via `midnightPriceFromApy`. Prefer
|
|
118
|
+
* this when a user pins a min rate.
|
|
119
|
+
* - otherwise `slippagePercent`, the API's own price-level knob, whose APY effect is amplified by the
|
|
120
|
+
* annualisation factor near maturity. `minRate` reports what the floor actually permits.
|
|
121
|
+
*
|
|
122
|
+
* A `minPaybackRate` above `estPaybackRate` yields `minUnits > newUnits` — the payback would revert
|
|
123
|
+
* on-chain. Compare the two before submitting and tell the user their floor is over the market rate.
|
|
124
|
+
*/
|
|
125
|
+
export declare const getMorphoMidnightPaybackQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value) => Promise<MorphoMidnightPaybackQuote>;
|
|
@@ -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.getMorphoMidnightBorrowQuote = exports.getMorphoMidnightMarketBook = exports.getMorphoMidnightUserBorrowInfo = exports.midnightSlippageParam = exports.midnightPriceFromApy = exports.midnightApyFromPrice = exports.midnightTimeToMaturityDays = exports.getMorphoMidnightAggregatedPositionData = void 0;
|
|
15
|
+
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");
|
|
@@ -134,49 +134,42 @@ exports.midnightPriceFromApy = midnightPriceFromApy;
|
|
|
134
134
|
const midnightSlippageParam = (slippagePercent) => decimal_js_1.default.min(decimal_js_1.default.max(new decimal_js_1.default(slippagePercent), MIDNIGHT_SLIPPAGE_MIN), MIDNIGHT_SLIPPAGE_MAX).toDP(1, decimal_js_1.default.ROUND_DOWN).toString();
|
|
135
135
|
exports.midnightSlippageParam = midnightSlippageParam;
|
|
136
136
|
/**
|
|
137
|
-
* Current borrower rate + debt breakdown from the Midnight
|
|
138
|
-
*
|
|
139
|
-
*
|
|
137
|
+
* Current borrower rate + debt breakdown from the Midnight positions API. Reconstructing this from the raw
|
|
138
|
+
* `/transactions` fill history only sums `borrow` fills, so it overstates debt for any position with an
|
|
139
|
+
* early exit or partial liquidation (`exit_borrow_primary`, `partial_liquidation`, ... — the fill history
|
|
140
|
+
* has no exhaustive list of debt-reducing event types). `/positions` instead reports the already-netted
|
|
141
|
+
* `debt`, matching `MidnightView.getPositionInfo` exactly, plus `cost_basis` (outstanding principal,
|
|
142
|
+
* WAD-scaled raw base units) and `effective_rate_wad` (borrow APY, WAD-scaled) for the base/interest split.
|
|
140
143
|
* The caller swallows errors — a missing rate must never block position rendering.
|
|
141
144
|
*/
|
|
142
|
-
const getMorphoMidnightUserBorrowInfo = (account, marketId,
|
|
143
|
-
const res = yield fetch(`${MIDNIGHT_API_BASE}/users/${account}/
|
|
145
|
+
const getMorphoMidnightUserBorrowInfo = (account, marketId, loanTokenSymbol) => __awaiter(void 0, void 0, void 0, function* () {
|
|
146
|
+
const res = yield fetch(`${MIDNIGHT_API_BASE}/users/${account}/positions`, { signal: AbortSignal.timeout(utils_1.LONGER_TIMEOUT) });
|
|
144
147
|
const json = yield res.json();
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
borrows.forEach((t) => {
|
|
150
|
-
var _a, _b;
|
|
151
|
-
const sellerAssets = new decimal_js_1.default(((_a = t.data) === null || _a === void 0 ? void 0 : _a.seller_assets) || 0);
|
|
152
|
-
const units = new decimal_js_1.default(((_b = t.data) === null || _b === void 0 ? void 0 : _b.units) || 0);
|
|
153
|
-
if (sellerAssets.lte(0) || units.lte(0))
|
|
154
|
-
return;
|
|
155
|
-
const ttmDays = (0, exports.midnightTimeToMaturityDays)(maturity, t.created_at);
|
|
156
|
-
const apy = (0, exports.midnightApyFromPrice)(sellerAssets.div(units), ttmDays); // price = seller_assets / units
|
|
157
|
-
sumSeller = sumSeller.add(sellerAssets);
|
|
158
|
-
sumUnits = sumUnits.add(units);
|
|
159
|
-
weightedApy = weightedApy.add(sellerAssets.mul(apy));
|
|
160
|
-
});
|
|
161
|
-
const borrowRate = sumSeller.lte(0) ? '0' : weightedApy.div(sumSeller).toString();
|
|
162
|
-
const debtBase = (0, tokens_1.assetAmountInEth)(sumSeller.toFixed(0), loanTokenSymbol);
|
|
163
|
-
const debtTotal = (0, tokens_1.assetAmountInEth)(sumUnits.toFixed(0), loanTokenSymbol);
|
|
148
|
+
const position = ((json === null || json === void 0 ? void 0 : json.data) || []).find((p) => { var _a; return p.type === 'borrow' && ((_a = p.market_id) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === marketId.toLowerCase(); });
|
|
149
|
+
const debtTotal = (0, tokens_1.assetAmountInEth)((position === null || position === void 0 ? void 0 : position.debt) || '0', loanTokenSymbol);
|
|
150
|
+
const costBasisRaw = new decimal_js_1.default((position === null || position === void 0 ? void 0 : position.cost_basis) || 0).div(constants_1.WAD); // WAD-scaled → raw base units
|
|
151
|
+
const debtBase = decimal_js_1.default.min((0, tokens_1.assetAmountInEth)(costBasisRaw.toString(), loanTokenSymbol), debtTotal).toString();
|
|
164
152
|
const debtInterest = decimal_js_1.default.max(new decimal_js_1.default(debtTotal).sub(debtBase), 0).toString();
|
|
153
|
+
const borrowRate = new decimal_js_1.default((position === null || position === void 0 ? void 0 : position.effective_rate_wad) || 0).div(constants_1.WAD).mul(100).toString();
|
|
165
154
|
return {
|
|
166
155
|
borrowRate, debtBase, debtInterest, debtTotal,
|
|
167
156
|
};
|
|
168
157
|
});
|
|
169
158
|
exports.getMorphoMidnightUserBorrowInfo = getMorphoMidnightUserBorrowInfo;
|
|
170
159
|
/**
|
|
171
|
-
*
|
|
172
|
-
* each price against time-to-maturity gives the rate a
|
|
173
|
-
* against Morpho's fixed-market UI, where per-offer rates match to the cent.
|
|
160
|
+
* One side of a market's resting order book, as rates rather than the API's WAD-scaled loan-per-unit
|
|
161
|
+
* prices. Annualizing each price against time-to-maturity gives the rate a taker filling that offer gets
|
|
162
|
+
* — verified against Morpho's fixed-market UI, where per-offer rates match to the cent.
|
|
163
|
+
*
|
|
164
|
+
* `bids` are the lend offers a borrower fills, so the best of them is the *lowest* rate; `asks` are the
|
|
165
|
+
* sell offers a repayer buys debt units from, where a lower price buys more units, so the best is the
|
|
166
|
+
* *highest* rate. Either way `offers` comes back best-first and `bestRate` is `offers[0].rate`.
|
|
174
167
|
*
|
|
175
|
-
* Returns `null` for an empty
|
|
176
|
-
*
|
|
177
|
-
*
|
|
168
|
+
* Returns `null` for an empty side: there is nothing to take, so a market listing should skip the market
|
|
169
|
+
* rather than advertise it at a 0% rate. Throws when the request fails — an error response is rarely
|
|
170
|
+
* JSON, so without the `res.ok` check it parses as an empty book and the market silently vanishes.
|
|
178
171
|
*/
|
|
179
|
-
const getMorphoMidnightMarketBook = (
|
|
172
|
+
const getMorphoMidnightMarketBook = (market_1, network_1, ...args_1) => __awaiter(void 0, [market_1, network_1, ...args_1], void 0, function* (market, network, side = 'bids') {
|
|
180
173
|
var _a;
|
|
181
174
|
const loanSymbol = (0, tokens_1.getAssetInfoByAddress)(market.loanToken, network).symbol;
|
|
182
175
|
const res = yield fetch(`${MIDNIGHT_API_BASE}/books/${market.marketId}`, { signal: AbortSignal.timeout(MIDNIGHT_BOOK_TIMEOUT) });
|
|
@@ -184,12 +177,13 @@ const getMorphoMidnightMarketBook = (market, network) => __awaiter(void 0, void
|
|
|
184
177
|
throw new Error(`Midnight book request failed for ${market.value} (${res.status})`);
|
|
185
178
|
const json = yield res.json();
|
|
186
179
|
const ttmDays = (0, exports.midnightTimeToMaturityDays)(market.maturity);
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
180
|
+
const bestFirst = side === 'asks' ? -1 : 1;
|
|
181
|
+
const offers = (((_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a[side]) || [])
|
|
182
|
+
.map((offer) => ({
|
|
183
|
+
rate: (0, exports.midnightApyFromPrice)(new decimal_js_1.default(offer.price).div(constants_1.WAD), ttmDays),
|
|
184
|
+
liquidity: (0, tokens_1.assetAmountInEth)(offer.assets, loanSymbol),
|
|
191
185
|
}))
|
|
192
|
-
.sort((a, b) => new decimal_js_1.default(a.rate).minus(b.rate).toNumber());
|
|
186
|
+
.sort((a, b) => new decimal_js_1.default(a.rate).minus(b.rate).mul(bestFirst).toNumber());
|
|
193
187
|
if (offers.length === 0)
|
|
194
188
|
return null;
|
|
195
189
|
return {
|
|
@@ -207,6 +201,22 @@ const midnightQuoteError = (error) => {
|
|
|
207
201
|
const reason = detail || (error === null || error === void 0 ? void 0 : error.message) || (error === null || error === void 0 ? void 0 : error.code);
|
|
208
202
|
return reason ? `Morpho Midnight quote unavailable: ${reason}` : 'Morpho Midnight quote unavailable';
|
|
209
203
|
};
|
|
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)}`;
|
|
207
|
+
const res = yield fetch(url, { signal: AbortSignal.timeout(utils_1.LONGER_TIMEOUT) });
|
|
208
|
+
const json = yield res.json();
|
|
209
|
+
const d = json === null || json === void 0 ? void 0 : json.data;
|
|
210
|
+
if (!(d === null || d === void 0 ? void 0 : d.average_best_price))
|
|
211
|
+
throw new Error(midnightQuoteError(json === null || json === void 0 ? void 0 : json.error));
|
|
212
|
+
return {
|
|
213
|
+
bestPrice: new decimal_js_1.default(d.average_best_price).div(constants_1.WAD).toString(),
|
|
214
|
+
worstPrice: new decimal_js_1.default(d.average_worst_price || 0).div(constants_1.WAD).toString(),
|
|
215
|
+
availableAssets: d.available_assets || '0',
|
|
216
|
+
availableUnits: d.available_units || '0',
|
|
217
|
+
takeableOffers: d.takeable_offers || [],
|
|
218
|
+
};
|
|
219
|
+
});
|
|
210
220
|
/**
|
|
211
221
|
* Quote a prospective borrow against the Midnight order book: the estimated rate, the debt units it adds,
|
|
212
222
|
* and the `maxUnits` cap sent on-chain to protect the user if better offers get filled first. `assetsRaw`
|
|
@@ -225,14 +235,8 @@ const midnightQuoteError = (error) => {
|
|
|
225
235
|
* Compare the two before submitting and tell the user their ceiling is under the market rate.
|
|
226
236
|
*/
|
|
227
237
|
const getMorphoMidnightBorrowQuote = (marketId, assetsRaw, slippagePercent, maturity, maxBorrowRate) => __awaiter(void 0, void 0, void 0, function* () {
|
|
228
|
-
const
|
|
229
|
-
const
|
|
230
|
-
const json = yield res.json();
|
|
231
|
-
const d = json === null || json === void 0 ? void 0 : json.data;
|
|
232
|
-
if (!(d === null || d === void 0 ? void 0 : d.average_best_price))
|
|
233
|
-
throw new Error(midnightQuoteError(json === null || json === void 0 ? void 0 : json.error));
|
|
234
|
-
const bestPrice = new decimal_js_1.default(d.average_best_price).div(constants_1.WAD).toString();
|
|
235
|
-
const worstPrice = new decimal_js_1.default(d.average_worst_price || 0).div(constants_1.WAD).toString();
|
|
238
|
+
const quote = yield fetchMorphoMidnightQuote(marketId, 'bids', assetsRaw, slippagePercent);
|
|
239
|
+
const { bestPrice, worstPrice } = quote;
|
|
236
240
|
const ttmDays = (0, exports.midnightTimeToMaturityDays)(maturity);
|
|
237
241
|
const estBorrowRate = (0, exports.midnightApyFromPrice)(bestPrice, ttmDays);
|
|
238
242
|
// Price the cap sits at, and the rate that price represents — one derivation, so `maxRate` and
|
|
@@ -243,16 +247,46 @@ const getMorphoMidnightBorrowQuote = (marketId, assetsRaw, slippagePercent, matu
|
|
|
243
247
|
const maxRate = (0, exports.midnightApyFromPrice)(capPrice, ttmDays);
|
|
244
248
|
const newUnits = new decimal_js_1.default(bestPrice).lte(0) ? '0' : new decimal_js_1.default(assetsRaw).div(bestPrice).toFixed(0);
|
|
245
249
|
const maxUnits = new decimal_js_1.default(capPrice).lte(0) ? '0' : new decimal_js_1.default(assetsRaw).div(capPrice).toFixed(0);
|
|
246
|
-
return {
|
|
247
|
-
bestPrice,
|
|
248
|
-
worstPrice,
|
|
249
|
-
estBorrowRate,
|
|
250
|
+
return Object.assign(Object.assign({}, quote), { estBorrowRate,
|
|
250
251
|
maxRate,
|
|
251
252
|
newUnits,
|
|
252
|
-
maxUnits
|
|
253
|
-
availableAssets: d.available_assets || '0',
|
|
254
|
-
availableUnits: d.available_units || '0',
|
|
255
|
-
takeableOffers: d.takeable_offers || [],
|
|
256
|
-
};
|
|
253
|
+
maxUnits });
|
|
257
254
|
});
|
|
258
255
|
exports.getMorphoMidnightBorrowQuote = getMorphoMidnightBorrowQuote;
|
|
256
|
+
/**
|
|
257
|
+
* Quote a prospective payback against the ask side of the Midnight order book: the rate the repayment
|
|
258
|
+
* retires debt at, the debt units it buys, and the `minUnits` floor sent on-chain to protect the user if
|
|
259
|
+
* the cheap offers get taken first. `assetsRaw` is the amount **spent** (raw loan-token base units) —
|
|
260
|
+
* matching `MidnightPaybackFromOrders`, whose `amount` is what leaves the wallet; the units bought, and
|
|
261
|
+
* therefore the debt retired, exceed it because a unit costs less than one loan token before maturity.
|
|
262
|
+
* Throws if the book can't fill the amount (caller handles).
|
|
263
|
+
*
|
|
264
|
+
* The mirror image of the borrow quote in every respect. A repayer wants a *high* rate, i.e. cheap units,
|
|
265
|
+
* so the guard is a floor rather than a ceiling:
|
|
266
|
+
* - `minPaybackRate` — an absolute APY floor, honoured **exactly** via `midnightPriceFromApy`. Prefer
|
|
267
|
+
* this when a user pins a min rate.
|
|
268
|
+
* - otherwise `slippagePercent`, the API's own price-level knob, whose APY effect is amplified by the
|
|
269
|
+
* annualisation factor near maturity. `minRate` reports what the floor actually permits.
|
|
270
|
+
*
|
|
271
|
+
* A `minPaybackRate` above `estPaybackRate` yields `minUnits > newUnits` — the payback would revert
|
|
272
|
+
* on-chain. Compare the two before submitting and tell the user their floor is over the market rate.
|
|
273
|
+
*/
|
|
274
|
+
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 { bestPrice, worstPrice } = quote;
|
|
277
|
+
const ttmDays = (0, exports.midnightTimeToMaturityDays)(maturity);
|
|
278
|
+
const estPaybackRate = (0, exports.midnightApyFromPrice)(bestPrice, ttmDays);
|
|
279
|
+
const capPrice = minPaybackRate !== undefined && new decimal_js_1.default(minPaybackRate).gt(0)
|
|
280
|
+
? (0, exports.midnightPriceFromApy)(minPaybackRate, ttmDays)
|
|
281
|
+
: worstPrice;
|
|
282
|
+
const minRate = (0, exports.midnightApyFromPrice)(capPrice, ttmDays);
|
|
283
|
+
// Rounded down on both counts: `newUnits` must not overstate the debt the user sees retired, and a
|
|
284
|
+
// `minUnits` rounded up would be a stricter floor than asked for and revert a payback that was fine.
|
|
285
|
+
const newUnits = new decimal_js_1.default(bestPrice).lte(0) ? '0' : new decimal_js_1.default(assetsRaw).div(bestPrice).toFixed(0, decimal_js_1.default.ROUND_DOWN);
|
|
286
|
+
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);
|
|
287
|
+
return Object.assign(Object.assign({}, quote), { estPaybackRate,
|
|
288
|
+
minRate,
|
|
289
|
+
newUnits,
|
|
290
|
+
minUnits });
|
|
291
|
+
});
|
|
292
|
+
exports.getMorphoMidnightPaybackQuote = getMorphoMidnightPaybackQuote;
|
|
@@ -168,7 +168,7 @@ function _getMorphoMidnightAccountData(provider, network, account, selectedMarke
|
|
|
168
168
|
let assetsDataForApy = marketInfo.assetsData;
|
|
169
169
|
if (new decimal_js_1.default(positionInfo.debt.toString()).gt(0)) {
|
|
170
170
|
try {
|
|
171
|
-
const borrowInfo = yield (0, morphoMidnightHelpers_1.getMorphoMidnightUserBorrowInfo)(account, marketId, marketInfo.
|
|
171
|
+
const borrowInfo = yield (0, morphoMidnightHelpers_1.getMorphoMidnightUserBorrowInfo)(account, marketId, marketInfo.loanToken);
|
|
172
172
|
borrowRate = borrowInfo.borrowRate;
|
|
173
173
|
debtBase = borrowInfo.debtBase;
|
|
174
174
|
debtInterest = borrowInfo.debtInterest;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Dec from 'decimal.js';
|
|
2
2
|
import { MMUsedAssets, NetworkNumber } from '../../types/common';
|
|
3
|
-
import { MorphoMidnightAggregatedPositionData, MorphoMidnightAssetsData, MorphoMidnightMarketData, MorphoMidnightMarketInfo, MorphoMidnightParsedBook } from '../../types';
|
|
3
|
+
import { MorphoMidnightAggregatedPositionData, MorphoMidnightAssetsData, MorphoMidnightBookSide, MorphoMidnightMarketData, MorphoMidnightMarketInfo, MorphoMidnightParsedBook } from '../../types';
|
|
4
4
|
/**
|
|
5
5
|
* Aggregate a Morpho Midnight position. Midnight markets are multi-collateral, so the borrow limit is
|
|
6
6
|
* the sum of each collateral's USD value times its own lltv (Aave-v4 style), rather than a single pair.
|
|
@@ -32,6 +32,17 @@ export interface MorphoMidnightBorrowQuote {
|
|
|
32
32
|
availableUnits: string;
|
|
33
33
|
takeableOffers: any[];
|
|
34
34
|
}
|
|
35
|
+
export interface MorphoMidnightPaybackQuote {
|
|
36
|
+
bestPrice: string;
|
|
37
|
+
worstPrice: string;
|
|
38
|
+
estPaybackRate: string;
|
|
39
|
+
minRate: string;
|
|
40
|
+
newUnits: string;
|
|
41
|
+
minUnits: string;
|
|
42
|
+
availableAssets: string;
|
|
43
|
+
availableUnits: string;
|
|
44
|
+
takeableOffers: any[];
|
|
45
|
+
}
|
|
35
46
|
export declare const midnightTimeToMaturityDays: (maturity: number, atSeconds?: number) => number;
|
|
36
47
|
export declare const midnightApyFromPrice: (price: Dec.Value, ttmDays: Dec.Value) => string;
|
|
37
48
|
/**
|
|
@@ -52,22 +63,29 @@ export declare const midnightPriceFromApy: (ratePercent: Dec.Value, ttmDays: Dec
|
|
|
52
63
|
*/
|
|
53
64
|
export declare const midnightSlippageParam: (slippagePercent: Dec.Value) => string;
|
|
54
65
|
/**
|
|
55
|
-
* Current borrower rate + debt breakdown from the Midnight
|
|
56
|
-
*
|
|
57
|
-
*
|
|
66
|
+
* Current borrower rate + debt breakdown from the Midnight positions API. Reconstructing this from the raw
|
|
67
|
+
* `/transactions` fill history only sums `borrow` fills, so it overstates debt for any position with an
|
|
68
|
+
* early exit or partial liquidation (`exit_borrow_primary`, `partial_liquidation`, ... — the fill history
|
|
69
|
+
* has no exhaustive list of debt-reducing event types). `/positions` instead reports the already-netted
|
|
70
|
+
* `debt`, matching `MidnightView.getPositionInfo` exactly, plus `cost_basis` (outstanding principal,
|
|
71
|
+
* WAD-scaled raw base units) and `effective_rate_wad` (borrow APY, WAD-scaled) for the base/interest split.
|
|
58
72
|
* The caller swallows errors — a missing rate must never block position rendering.
|
|
59
73
|
*/
|
|
60
|
-
export declare const getMorphoMidnightUserBorrowInfo: (account: string, marketId: string,
|
|
74
|
+
export declare const getMorphoMidnightUserBorrowInfo: (account: string, marketId: string, loanTokenSymbol: string) => Promise<MorphoMidnightBorrowInfo>;
|
|
61
75
|
/**
|
|
62
|
-
*
|
|
63
|
-
* each price against time-to-maturity gives the rate a
|
|
64
|
-
* against Morpho's fixed-market UI, where per-offer rates match to the cent.
|
|
76
|
+
* One side of a market's resting order book, as rates rather than the API's WAD-scaled loan-per-unit
|
|
77
|
+
* prices. Annualizing each price against time-to-maturity gives the rate a taker filling that offer gets
|
|
78
|
+
* — verified against Morpho's fixed-market UI, where per-offer rates match to the cent.
|
|
79
|
+
*
|
|
80
|
+
* `bids` are the lend offers a borrower fills, so the best of them is the *lowest* rate; `asks` are the
|
|
81
|
+
* sell offers a repayer buys debt units from, where a lower price buys more units, so the best is the
|
|
82
|
+
* *highest* rate. Either way `offers` comes back best-first and `bestRate` is `offers[0].rate`.
|
|
65
83
|
*
|
|
66
|
-
* Returns `null` for an empty
|
|
67
|
-
*
|
|
68
|
-
*
|
|
84
|
+
* Returns `null` for an empty side: there is nothing to take, so a market listing should skip the market
|
|
85
|
+
* rather than advertise it at a 0% rate. Throws when the request fails — an error response is rarely
|
|
86
|
+
* JSON, so without the `res.ok` check it parses as an empty book and the market silently vanishes.
|
|
69
87
|
*/
|
|
70
|
-
export declare const getMorphoMidnightMarketBook: (market: MorphoMidnightMarketData, network: NetworkNumber) => Promise<MorphoMidnightParsedBook | null>;
|
|
88
|
+
export declare const getMorphoMidnightMarketBook: (market: MorphoMidnightMarketData, network: NetworkNumber, side?: MorphoMidnightBookSide) => Promise<MorphoMidnightParsedBook | null>;
|
|
71
89
|
/**
|
|
72
90
|
* Quote a prospective borrow against the Midnight order book: the estimated rate, the debt units it adds,
|
|
73
91
|
* and the `maxUnits` cap sent on-chain to protect the user if better offers get filled first. `assetsRaw`
|
|
@@ -86,3 +104,22 @@ export declare const getMorphoMidnightMarketBook: (market: MorphoMidnightMarketD
|
|
|
86
104
|
* Compare the two before submitting and tell the user their ceiling is under the market rate.
|
|
87
105
|
*/
|
|
88
106
|
export declare const getMorphoMidnightBorrowQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, maxBorrowRate?: Dec.Value) => Promise<MorphoMidnightBorrowQuote>;
|
|
107
|
+
/**
|
|
108
|
+
* Quote a prospective payback against the ask side of the Midnight order book: the rate the repayment
|
|
109
|
+
* retires debt at, the debt units it buys, and the `minUnits` floor sent on-chain to protect the user if
|
|
110
|
+
* the cheap offers get taken first. `assetsRaw` is the amount **spent** (raw loan-token base units) —
|
|
111
|
+
* matching `MidnightPaybackFromOrders`, whose `amount` is what leaves the wallet; the units bought, and
|
|
112
|
+
* therefore the debt retired, exceed it because a unit costs less than one loan token before maturity.
|
|
113
|
+
* Throws if the book can't fill the amount (caller handles).
|
|
114
|
+
*
|
|
115
|
+
* The mirror image of the borrow quote in every respect. A repayer wants a *high* rate, i.e. cheap units,
|
|
116
|
+
* so the guard is a floor rather than a ceiling:
|
|
117
|
+
* - `minPaybackRate` — an absolute APY floor, honoured **exactly** via `midnightPriceFromApy`. Prefer
|
|
118
|
+
* this when a user pins a min rate.
|
|
119
|
+
* - otherwise `slippagePercent`, the API's own price-level knob, whose APY effect is amplified by the
|
|
120
|
+
* annualisation factor near maturity. `minRate` reports what the floor actually permits.
|
|
121
|
+
*
|
|
122
|
+
* A `minPaybackRate` above `estPaybackRate` yields `minUnits > newUnits` — the payback would revert
|
|
123
|
+
* on-chain. Compare the two before submitting and tell the user their floor is over the market rate.
|
|
124
|
+
*/
|
|
125
|
+
export declare const getMorphoMidnightPaybackQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value) => Promise<MorphoMidnightPaybackQuote>;
|
|
@@ -123,48 +123,41 @@ export const midnightPriceFromApy = (ratePercent, ttmDays) => {
|
|
|
123
123
|
*/
|
|
124
124
|
export const midnightSlippageParam = (slippagePercent) => Dec.min(Dec.max(new Dec(slippagePercent), MIDNIGHT_SLIPPAGE_MIN), MIDNIGHT_SLIPPAGE_MAX).toDP(1, Dec.ROUND_DOWN).toString();
|
|
125
125
|
/**
|
|
126
|
-
* Current borrower rate + debt breakdown from the Midnight
|
|
127
|
-
*
|
|
128
|
-
*
|
|
126
|
+
* Current borrower rate + debt breakdown from the Midnight positions API. Reconstructing this from the raw
|
|
127
|
+
* `/transactions` fill history only sums `borrow` fills, so it overstates debt for any position with an
|
|
128
|
+
* early exit or partial liquidation (`exit_borrow_primary`, `partial_liquidation`, ... — the fill history
|
|
129
|
+
* has no exhaustive list of debt-reducing event types). `/positions` instead reports the already-netted
|
|
130
|
+
* `debt`, matching `MidnightView.getPositionInfo` exactly, plus `cost_basis` (outstanding principal,
|
|
131
|
+
* WAD-scaled raw base units) and `effective_rate_wad` (borrow APY, WAD-scaled) for the base/interest split.
|
|
129
132
|
* The caller swallows errors — a missing rate must never block position rendering.
|
|
130
133
|
*/
|
|
131
|
-
export const getMorphoMidnightUserBorrowInfo = (account, marketId,
|
|
132
|
-
const res = yield fetch(`${MIDNIGHT_API_BASE}/users/${account}/
|
|
134
|
+
export const getMorphoMidnightUserBorrowInfo = (account, marketId, loanTokenSymbol) => __awaiter(void 0, void 0, void 0, function* () {
|
|
135
|
+
const res = yield fetch(`${MIDNIGHT_API_BASE}/users/${account}/positions`, { signal: AbortSignal.timeout(LONGER_TIMEOUT) });
|
|
133
136
|
const json = yield res.json();
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
borrows.forEach((t) => {
|
|
139
|
-
var _a, _b;
|
|
140
|
-
const sellerAssets = new Dec(((_a = t.data) === null || _a === void 0 ? void 0 : _a.seller_assets) || 0);
|
|
141
|
-
const units = new Dec(((_b = t.data) === null || _b === void 0 ? void 0 : _b.units) || 0);
|
|
142
|
-
if (sellerAssets.lte(0) || units.lte(0))
|
|
143
|
-
return;
|
|
144
|
-
const ttmDays = midnightTimeToMaturityDays(maturity, t.created_at);
|
|
145
|
-
const apy = midnightApyFromPrice(sellerAssets.div(units), ttmDays); // price = seller_assets / units
|
|
146
|
-
sumSeller = sumSeller.add(sellerAssets);
|
|
147
|
-
sumUnits = sumUnits.add(units);
|
|
148
|
-
weightedApy = weightedApy.add(sellerAssets.mul(apy));
|
|
149
|
-
});
|
|
150
|
-
const borrowRate = sumSeller.lte(0) ? '0' : weightedApy.div(sumSeller).toString();
|
|
151
|
-
const debtBase = assetAmountInEth(sumSeller.toFixed(0), loanTokenSymbol);
|
|
152
|
-
const debtTotal = assetAmountInEth(sumUnits.toFixed(0), loanTokenSymbol);
|
|
137
|
+
const position = ((json === null || json === void 0 ? void 0 : json.data) || []).find((p) => { var _a; return p.type === 'borrow' && ((_a = p.market_id) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === marketId.toLowerCase(); });
|
|
138
|
+
const debtTotal = assetAmountInEth((position === null || position === void 0 ? void 0 : position.debt) || '0', loanTokenSymbol);
|
|
139
|
+
const costBasisRaw = new Dec((position === null || position === void 0 ? void 0 : position.cost_basis) || 0).div(WAD); // WAD-scaled → raw base units
|
|
140
|
+
const debtBase = Dec.min(assetAmountInEth(costBasisRaw.toString(), loanTokenSymbol), debtTotal).toString();
|
|
153
141
|
const debtInterest = Dec.max(new Dec(debtTotal).sub(debtBase), 0).toString();
|
|
142
|
+
const borrowRate = new Dec((position === null || position === void 0 ? void 0 : position.effective_rate_wad) || 0).div(WAD).mul(100).toString();
|
|
154
143
|
return {
|
|
155
144
|
borrowRate, debtBase, debtInterest, debtTotal,
|
|
156
145
|
};
|
|
157
146
|
});
|
|
158
147
|
/**
|
|
159
|
-
*
|
|
160
|
-
* each price against time-to-maturity gives the rate a
|
|
161
|
-
* against Morpho's fixed-market UI, where per-offer rates match to the cent.
|
|
148
|
+
* One side of a market's resting order book, as rates rather than the API's WAD-scaled loan-per-unit
|
|
149
|
+
* prices. Annualizing each price against time-to-maturity gives the rate a taker filling that offer gets
|
|
150
|
+
* — verified against Morpho's fixed-market UI, where per-offer rates match to the cent.
|
|
151
|
+
*
|
|
152
|
+
* `bids` are the lend offers a borrower fills, so the best of them is the *lowest* rate; `asks` are the
|
|
153
|
+
* sell offers a repayer buys debt units from, where a lower price buys more units, so the best is the
|
|
154
|
+
* *highest* rate. Either way `offers` comes back best-first and `bestRate` is `offers[0].rate`.
|
|
162
155
|
*
|
|
163
|
-
* Returns `null` for an empty
|
|
164
|
-
*
|
|
165
|
-
*
|
|
156
|
+
* Returns `null` for an empty side: there is nothing to take, so a market listing should skip the market
|
|
157
|
+
* rather than advertise it at a 0% rate. Throws when the request fails — an error response is rarely
|
|
158
|
+
* JSON, so without the `res.ok` check it parses as an empty book and the market silently vanishes.
|
|
166
159
|
*/
|
|
167
|
-
export const getMorphoMidnightMarketBook = (
|
|
160
|
+
export const getMorphoMidnightMarketBook = (market_1, network_1, ...args_1) => __awaiter(void 0, [market_1, network_1, ...args_1], void 0, function* (market, network, side = 'bids') {
|
|
168
161
|
var _a;
|
|
169
162
|
const loanSymbol = getAssetInfoByAddress(market.loanToken, network).symbol;
|
|
170
163
|
const res = yield fetch(`${MIDNIGHT_API_BASE}/books/${market.marketId}`, { signal: AbortSignal.timeout(MIDNIGHT_BOOK_TIMEOUT) });
|
|
@@ -172,12 +165,13 @@ export const getMorphoMidnightMarketBook = (market, network) => __awaiter(void 0
|
|
|
172
165
|
throw new Error(`Midnight book request failed for ${market.value} (${res.status})`);
|
|
173
166
|
const json = yield res.json();
|
|
174
167
|
const ttmDays = midnightTimeToMaturityDays(market.maturity);
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
168
|
+
const bestFirst = side === 'asks' ? -1 : 1;
|
|
169
|
+
const offers = (((_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a[side]) || [])
|
|
170
|
+
.map((offer) => ({
|
|
171
|
+
rate: midnightApyFromPrice(new Dec(offer.price).div(WAD), ttmDays),
|
|
172
|
+
liquidity: assetAmountInEth(offer.assets, loanSymbol),
|
|
179
173
|
}))
|
|
180
|
-
.sort((a, b) => new Dec(a.rate).minus(b.rate).toNumber());
|
|
174
|
+
.sort((a, b) => new Dec(a.rate).minus(b.rate).mul(bestFirst).toNumber());
|
|
181
175
|
if (offers.length === 0)
|
|
182
176
|
return null;
|
|
183
177
|
return {
|
|
@@ -194,6 +188,22 @@ const midnightQuoteError = (error) => {
|
|
|
194
188
|
const reason = detail || (error === null || error === void 0 ? void 0 : error.message) || (error === null || error === void 0 ? void 0 : error.code);
|
|
195
189
|
return reason ? `Morpho Midnight quote unavailable: ${reason}` : 'Morpho Midnight quote unavailable';
|
|
196
190
|
};
|
|
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)}`;
|
|
194
|
+
const res = yield fetch(url, { signal: AbortSignal.timeout(LONGER_TIMEOUT) });
|
|
195
|
+
const json = yield res.json();
|
|
196
|
+
const d = json === null || json === void 0 ? void 0 : json.data;
|
|
197
|
+
if (!(d === null || d === void 0 ? void 0 : d.average_best_price))
|
|
198
|
+
throw new Error(midnightQuoteError(json === null || json === void 0 ? void 0 : json.error));
|
|
199
|
+
return {
|
|
200
|
+
bestPrice: new Dec(d.average_best_price).div(WAD).toString(),
|
|
201
|
+
worstPrice: new Dec(d.average_worst_price || 0).div(WAD).toString(),
|
|
202
|
+
availableAssets: d.available_assets || '0',
|
|
203
|
+
availableUnits: d.available_units || '0',
|
|
204
|
+
takeableOffers: d.takeable_offers || [],
|
|
205
|
+
};
|
|
206
|
+
});
|
|
197
207
|
/**
|
|
198
208
|
* Quote a prospective borrow against the Midnight order book: the estimated rate, the debt units it adds,
|
|
199
209
|
* and the `maxUnits` cap sent on-chain to protect the user if better offers get filled first. `assetsRaw`
|
|
@@ -212,14 +222,8 @@ const midnightQuoteError = (error) => {
|
|
|
212
222
|
* Compare the two before submitting and tell the user their ceiling is under the market rate.
|
|
213
223
|
*/
|
|
214
224
|
export const getMorphoMidnightBorrowQuote = (marketId, assetsRaw, slippagePercent, maturity, maxBorrowRate) => __awaiter(void 0, void 0, void 0, function* () {
|
|
215
|
-
const
|
|
216
|
-
const
|
|
217
|
-
const json = yield res.json();
|
|
218
|
-
const d = json === null || json === void 0 ? void 0 : json.data;
|
|
219
|
-
if (!(d === null || d === void 0 ? void 0 : d.average_best_price))
|
|
220
|
-
throw new Error(midnightQuoteError(json === null || json === void 0 ? void 0 : json.error));
|
|
221
|
-
const bestPrice = new Dec(d.average_best_price).div(WAD).toString();
|
|
222
|
-
const worstPrice = new Dec(d.average_worst_price || 0).div(WAD).toString();
|
|
225
|
+
const quote = yield fetchMorphoMidnightQuote(marketId, 'bids', assetsRaw, slippagePercent);
|
|
226
|
+
const { bestPrice, worstPrice } = quote;
|
|
223
227
|
const ttmDays = midnightTimeToMaturityDays(maturity);
|
|
224
228
|
const estBorrowRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
225
229
|
// Price the cap sits at, and the rate that price represents — one derivation, so `maxRate` and
|
|
@@ -230,15 +234,44 @@ export const getMorphoMidnightBorrowQuote = (marketId, assetsRaw, slippagePercen
|
|
|
230
234
|
const maxRate = midnightApyFromPrice(capPrice, ttmDays);
|
|
231
235
|
const newUnits = new Dec(bestPrice).lte(0) ? '0' : new Dec(assetsRaw).div(bestPrice).toFixed(0);
|
|
232
236
|
const maxUnits = new Dec(capPrice).lte(0) ? '0' : new Dec(assetsRaw).div(capPrice).toFixed(0);
|
|
233
|
-
return {
|
|
234
|
-
bestPrice,
|
|
235
|
-
worstPrice,
|
|
236
|
-
estBorrowRate,
|
|
237
|
+
return Object.assign(Object.assign({}, quote), { estBorrowRate,
|
|
237
238
|
maxRate,
|
|
238
239
|
newUnits,
|
|
239
|
-
maxUnits
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
240
|
+
maxUnits });
|
|
241
|
+
});
|
|
242
|
+
/**
|
|
243
|
+
* Quote a prospective payback against the ask side of the Midnight order book: the rate the repayment
|
|
244
|
+
* retires debt at, the debt units it buys, and the `minUnits` floor sent on-chain to protect the user if
|
|
245
|
+
* the cheap offers get taken first. `assetsRaw` is the amount **spent** (raw loan-token base units) —
|
|
246
|
+
* matching `MidnightPaybackFromOrders`, whose `amount` is what leaves the wallet; the units bought, and
|
|
247
|
+
* therefore the debt retired, exceed it because a unit costs less than one loan token before maturity.
|
|
248
|
+
* Throws if the book can't fill the amount (caller handles).
|
|
249
|
+
*
|
|
250
|
+
* The mirror image of the borrow quote in every respect. A repayer wants a *high* rate, i.e. cheap units,
|
|
251
|
+
* so the guard is a floor rather than a ceiling:
|
|
252
|
+
* - `minPaybackRate` — an absolute APY floor, honoured **exactly** via `midnightPriceFromApy`. Prefer
|
|
253
|
+
* this when a user pins a min rate.
|
|
254
|
+
* - otherwise `slippagePercent`, the API's own price-level knob, whose APY effect is amplified by the
|
|
255
|
+
* annualisation factor near maturity. `minRate` reports what the floor actually permits.
|
|
256
|
+
*
|
|
257
|
+
* A `minPaybackRate` above `estPaybackRate` yields `minUnits > newUnits` — the payback would revert
|
|
258
|
+
* on-chain. Compare the two before submitting and tell the user their floor is over the market rate.
|
|
259
|
+
*/
|
|
260
|
+
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 { bestPrice, worstPrice } = quote;
|
|
263
|
+
const ttmDays = midnightTimeToMaturityDays(maturity);
|
|
264
|
+
const estPaybackRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
265
|
+
const capPrice = minPaybackRate !== undefined && new Dec(minPaybackRate).gt(0)
|
|
266
|
+
? midnightPriceFromApy(minPaybackRate, ttmDays)
|
|
267
|
+
: worstPrice;
|
|
268
|
+
const minRate = midnightApyFromPrice(capPrice, ttmDays);
|
|
269
|
+
// Rounded down on both counts: `newUnits` must not overstate the debt the user sees retired, and a
|
|
270
|
+
// `minUnits` rounded up would be a stricter floor than asked for and revert a payback that was fine.
|
|
271
|
+
const newUnits = new Dec(bestPrice).lte(0) ? '0' : new Dec(assetsRaw).div(bestPrice).toFixed(0, Dec.ROUND_DOWN);
|
|
272
|
+
const minUnits = new Dec(capPrice).lte(0) ? '0' : new Dec(assetsRaw).div(capPrice).toFixed(0, Dec.ROUND_DOWN);
|
|
273
|
+
return Object.assign(Object.assign({}, quote), { estPaybackRate,
|
|
274
|
+
minRate,
|
|
275
|
+
newUnits,
|
|
276
|
+
minUnits });
|
|
244
277
|
});
|
|
@@ -157,7 +157,7 @@ export function _getMorphoMidnightAccountData(provider, network, account, select
|
|
|
157
157
|
let assetsDataForApy = marketInfo.assetsData;
|
|
158
158
|
if (new Dec(positionInfo.debt.toString()).gt(0)) {
|
|
159
159
|
try {
|
|
160
|
-
const borrowInfo = yield getMorphoMidnightUserBorrowInfo(account, marketId, marketInfo.
|
|
160
|
+
const borrowInfo = yield getMorphoMidnightUserBorrowInfo(account, marketId, marketInfo.loanToken);
|
|
161
161
|
borrowRate = borrowInfo.borrowRate;
|
|
162
162
|
debtBase = borrowInfo.debtBase;
|
|
163
163
|
debtInterest = borrowInfo.debtInterest;
|
package/package.json
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
MorphoMidnightAggregatedPositionData,
|
|
12
12
|
MorphoMidnightAssetsData,
|
|
13
13
|
MorphoMidnightBookOffer,
|
|
14
|
+
MorphoMidnightBookSide,
|
|
14
15
|
MorphoMidnightMarketData,
|
|
15
16
|
MorphoMidnightMarketInfo,
|
|
16
17
|
MorphoMidnightParsedBook,
|
|
@@ -114,11 +115,12 @@ const MIDNIGHT_BOOK_TIMEOUT = 30000;
|
|
|
114
115
|
const MIDNIGHT_SLIPPAGE_MIN = 0.1;
|
|
115
116
|
const MIDNIGHT_SLIPPAGE_MAX = 100;
|
|
116
117
|
|
|
117
|
-
interface
|
|
118
|
-
event_type: string,
|
|
118
|
+
interface MidnightPosition {
|
|
119
119
|
market_id: string,
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
type: string, // 'borrow' | 'lend'
|
|
121
|
+
debt: string, // raw loan-token base units — matches MidnightView.getPositionInfo exactly
|
|
122
|
+
cost_basis: string, // WAD-scaled raw base units — outstanding principal, net of exits/liquidations
|
|
123
|
+
effective_rate_wad: string, // WAD-scaled borrow APY, e.g. 0.05e18 = 5%
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
interface MidnightApiError {
|
|
@@ -127,7 +129,7 @@ interface MidnightApiError {
|
|
|
127
129
|
details?: ({ field?: string, issue?: string })[] | null,
|
|
128
130
|
}
|
|
129
131
|
|
|
130
|
-
interface
|
|
132
|
+
interface MidnightRawOffer {
|
|
131
133
|
price: string, // WAD-scaled loan-per-unit
|
|
132
134
|
assets: string, // loan-token base units available at this offer
|
|
133
135
|
}
|
|
@@ -141,10 +143,10 @@ interface MidnightQuoteResponse {
|
|
|
141
143
|
}
|
|
142
144
|
|
|
143
145
|
export interface MorphoMidnightBorrowInfo {
|
|
144
|
-
borrowRate: string, //
|
|
145
|
-
debtBase: string, //
|
|
146
|
+
borrowRate: string, // effective borrow APY as a percent
|
|
147
|
+
debtBase: string, // outstanding principal (cost_basis), loan-token units
|
|
146
148
|
debtInterest: string, // debtTotal − debtBase (interest owed at maturity), loan-token units
|
|
147
|
-
debtTotal: string, //
|
|
149
|
+
debtTotal: string, // on-chain debt at maturity, loan-token units
|
|
148
150
|
}
|
|
149
151
|
|
|
150
152
|
export interface MorphoMidnightBorrowQuote {
|
|
@@ -159,6 +161,18 @@ export interface MorphoMidnightBorrowQuote {
|
|
|
159
161
|
takeableOffers: any[], // opaque orderbook offers, forwarded verbatim to on-chain execution
|
|
160
162
|
}
|
|
161
163
|
|
|
164
|
+
export interface MorphoMidnightPaybackQuote {
|
|
165
|
+
bestPrice: string, // average_best_price, loan-per-unit — the cheapest units on the ask side
|
|
166
|
+
worstPrice: string, // average_worst_price, slippage-adjusted (ABOVE best: a dearer unit)
|
|
167
|
+
estPaybackRate: string, // APY the repayment retires debt at, as a percent
|
|
168
|
+
minRate: string, // APY the on-chain floor permits, i.e. `minUnits` annualized (display only)
|
|
169
|
+
newUnits: string, // debt retired at best price, raw loan-token base units
|
|
170
|
+
minUnits: string, // floor on debt retired (on-chain guard), raw loan-token base units
|
|
171
|
+
availableAssets: string,
|
|
172
|
+
availableUnits: string,
|
|
173
|
+
takeableOffers: any[], // opaque orderbook offers, forwarded verbatim to on-chain execution
|
|
174
|
+
}
|
|
175
|
+
|
|
162
176
|
// Days remaining until maturity, optionally measured at a past timestamp (for historical fills).
|
|
163
177
|
export const midnightTimeToMaturityDays = (maturity: number, atSeconds: number = nowInSeconds()): number => new Dec(maturity).sub(atSeconds).div(SECONDS_PER_DAY).toNumber();
|
|
164
178
|
|
|
@@ -201,40 +215,28 @@ export const midnightSlippageParam = (slippagePercent: Dec.Value): string => Dec
|
|
|
201
215
|
).toDP(1, Dec.ROUND_DOWN).toString();
|
|
202
216
|
|
|
203
217
|
/**
|
|
204
|
-
* Current borrower rate + debt breakdown from the Midnight
|
|
205
|
-
*
|
|
206
|
-
*
|
|
218
|
+
* Current borrower rate + debt breakdown from the Midnight positions API. Reconstructing this from the raw
|
|
219
|
+
* `/transactions` fill history only sums `borrow` fills, so it overstates debt for any position with an
|
|
220
|
+
* early exit or partial liquidation (`exit_borrow_primary`, `partial_liquidation`, ... — the fill history
|
|
221
|
+
* has no exhaustive list of debt-reducing event types). `/positions` instead reports the already-netted
|
|
222
|
+
* `debt`, matching `MidnightView.getPositionInfo` exactly, plus `cost_basis` (outstanding principal,
|
|
223
|
+
* WAD-scaled raw base units) and `effective_rate_wad` (borrow APY, WAD-scaled) for the base/interest split.
|
|
207
224
|
* The caller swallows errors — a missing rate must never block position rendering.
|
|
208
225
|
*/
|
|
209
226
|
export const getMorphoMidnightUserBorrowInfo = async (
|
|
210
227
|
account: string,
|
|
211
228
|
marketId: string,
|
|
212
|
-
maturity: number,
|
|
213
229
|
loanTokenSymbol: string,
|
|
214
230
|
): Promise<MorphoMidnightBorrowInfo> => {
|
|
215
|
-
const res = await fetch(`${MIDNIGHT_API_BASE}/users/${account}/
|
|
216
|
-
const json: { data?:
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
borrows.forEach((t) => {
|
|
224
|
-
const sellerAssets = new Dec(t.data?.seller_assets || 0);
|
|
225
|
-
const units = new Dec(t.data?.units || 0);
|
|
226
|
-
if (sellerAssets.lte(0) || units.lte(0)) return;
|
|
227
|
-
const ttmDays = midnightTimeToMaturityDays(maturity, t.created_at);
|
|
228
|
-
const apy = midnightApyFromPrice(sellerAssets.div(units), ttmDays); // price = seller_assets / units
|
|
229
|
-
sumSeller = sumSeller.add(sellerAssets);
|
|
230
|
-
sumUnits = sumUnits.add(units);
|
|
231
|
-
weightedApy = weightedApy.add(sellerAssets.mul(apy));
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
const borrowRate = sumSeller.lte(0) ? '0' : weightedApy.div(sumSeller).toString();
|
|
235
|
-
const debtBase = assetAmountInEth(sumSeller.toFixed(0), loanTokenSymbol);
|
|
236
|
-
const debtTotal = assetAmountInEth(sumUnits.toFixed(0), loanTokenSymbol);
|
|
231
|
+
const res = await fetch(`${MIDNIGHT_API_BASE}/users/${account}/positions`, { signal: AbortSignal.timeout(LONGER_TIMEOUT) });
|
|
232
|
+
const json: { data?: MidnightPosition[] } = await res.json();
|
|
233
|
+
const position = (json?.data || []).find((p) => p.type === 'borrow' && p.market_id?.toLowerCase() === marketId.toLowerCase());
|
|
234
|
+
|
|
235
|
+
const debtTotal = assetAmountInEth(position?.debt || '0', loanTokenSymbol);
|
|
236
|
+
const costBasisRaw = new Dec(position?.cost_basis || 0).div(WAD); // WAD-scaled → raw base units
|
|
237
|
+
const debtBase = Dec.min(assetAmountInEth(costBasisRaw.toString(), loanTokenSymbol), debtTotal).toString();
|
|
237
238
|
const debtInterest = Dec.max(new Dec(debtTotal).sub(debtBase), 0).toString();
|
|
239
|
+
const borrowRate = new Dec(position?.effective_rate_wad || 0).div(WAD).mul(100).toString();
|
|
238
240
|
|
|
239
241
|
return {
|
|
240
242
|
borrowRate, debtBase, debtInterest, debtTotal,
|
|
@@ -242,31 +244,37 @@ export const getMorphoMidnightUserBorrowInfo = async (
|
|
|
242
244
|
};
|
|
243
245
|
|
|
244
246
|
/**
|
|
245
|
-
*
|
|
246
|
-
* each price against time-to-maturity gives the rate a
|
|
247
|
-
* against Morpho's fixed-market UI, where per-offer rates match to the cent.
|
|
247
|
+
* One side of a market's resting order book, as rates rather than the API's WAD-scaled loan-per-unit
|
|
248
|
+
* prices. Annualizing each price against time-to-maturity gives the rate a taker filling that offer gets
|
|
249
|
+
* — verified against Morpho's fixed-market UI, where per-offer rates match to the cent.
|
|
248
250
|
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
251
|
+
* `bids` are the lend offers a borrower fills, so the best of them is the *lowest* rate; `asks` are the
|
|
252
|
+
* sell offers a repayer buys debt units from, where a lower price buys more units, so the best is the
|
|
253
|
+
* *highest* rate. Either way `offers` comes back best-first and `bestRate` is `offers[0].rate`.
|
|
254
|
+
*
|
|
255
|
+
* Returns `null` for an empty side: there is nothing to take, so a market listing should skip the market
|
|
256
|
+
* rather than advertise it at a 0% rate. Throws when the request fails — an error response is rarely
|
|
257
|
+
* JSON, so without the `res.ok` check it parses as an empty book and the market silently vanishes.
|
|
252
258
|
*/
|
|
253
259
|
export const getMorphoMidnightMarketBook = async (
|
|
254
260
|
market: MorphoMidnightMarketData,
|
|
255
261
|
network: NetworkNumber,
|
|
262
|
+
side: MorphoMidnightBookSide = 'bids',
|
|
256
263
|
): Promise<MorphoMidnightParsedBook | null> => {
|
|
257
264
|
const loanSymbol = getAssetInfoByAddress(market.loanToken, network).symbol;
|
|
258
265
|
const res = await fetch(`${MIDNIGHT_API_BASE}/books/${market.marketId}`, { signal: AbortSignal.timeout(MIDNIGHT_BOOK_TIMEOUT) });
|
|
259
266
|
if (!res.ok) throw new Error(`Midnight book request failed for ${market.value} (${res.status})`);
|
|
260
267
|
|
|
261
|
-
const json: { data?:
|
|
268
|
+
const json: { data?: Partial<Record<MorphoMidnightBookSide, MidnightRawOffer[]>> } = await res.json();
|
|
262
269
|
const ttmDays = midnightTimeToMaturityDays(market.maturity);
|
|
270
|
+
const bestFirst = side === 'asks' ? -1 : 1;
|
|
263
271
|
|
|
264
|
-
const offers: MorphoMidnightBookOffer[] = (json?.data?.
|
|
265
|
-
.map((
|
|
266
|
-
rate: midnightApyFromPrice(new Dec(
|
|
267
|
-
liquidity: assetAmountInEth(
|
|
272
|
+
const offers: MorphoMidnightBookOffer[] = (json?.data?.[side] || [])
|
|
273
|
+
.map((offer) => ({
|
|
274
|
+
rate: midnightApyFromPrice(new Dec(offer.price).div(WAD), ttmDays),
|
|
275
|
+
liquidity: assetAmountInEth(offer.assets, loanSymbol),
|
|
268
276
|
}))
|
|
269
|
-
.sort((a, b) => new Dec(a.rate).minus(b.rate).toNumber());
|
|
277
|
+
.sort((a, b) => new Dec(a.rate).minus(b.rate).mul(bestFirst).toNumber());
|
|
270
278
|
|
|
271
279
|
if (offers.length === 0) return null;
|
|
272
280
|
|
|
@@ -286,6 +294,36 @@ const midnightQuoteError = (error?: MidnightApiError): string => {
|
|
|
286
294
|
return reason ? `Morpho Midnight quote unavailable: ${reason}` : 'Morpho Midnight quote unavailable';
|
|
287
295
|
};
|
|
288
296
|
|
|
297
|
+
interface MidnightParsedQuote {
|
|
298
|
+
bestPrice: string, // loan-per-unit
|
|
299
|
+
worstPrice: string, // slippage-adjusted; below best on `bids`, above it on `asks`
|
|
300
|
+
availableAssets: string,
|
|
301
|
+
availableUnits: string,
|
|
302
|
+
takeableOffers: any[],
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// The raw quote both sides share: prices descaled from WAD, everything else forwarded verbatim.
|
|
306
|
+
const fetchMorphoMidnightQuote = async (
|
|
307
|
+
marketId: string,
|
|
308
|
+
side: MorphoMidnightBookSide,
|
|
309
|
+
assetsRaw: string,
|
|
310
|
+
slippagePercent: Dec.Value,
|
|
311
|
+
): Promise<MidnightParsedQuote> => {
|
|
312
|
+
const url = `${MIDNIGHT_API_BASE}/books/${marketId}/${side}/quote?assets=${assetsRaw}&slippage=${midnightSlippageParam(slippagePercent)}`;
|
|
313
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(LONGER_TIMEOUT) });
|
|
314
|
+
const json: { data?: MidnightQuoteResponse, error?: MidnightApiError } = await res.json();
|
|
315
|
+
const d = json?.data;
|
|
316
|
+
if (!d?.average_best_price) throw new Error(midnightQuoteError(json?.error));
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
bestPrice: new Dec(d.average_best_price).div(WAD).toString(),
|
|
320
|
+
worstPrice: new Dec(d.average_worst_price || 0).div(WAD).toString(),
|
|
321
|
+
availableAssets: d.available_assets || '0',
|
|
322
|
+
availableUnits: d.available_units || '0',
|
|
323
|
+
takeableOffers: d.takeable_offers || [],
|
|
324
|
+
};
|
|
325
|
+
};
|
|
326
|
+
|
|
289
327
|
/**
|
|
290
328
|
* Quote a prospective borrow against the Midnight order book: the estimated rate, the debt units it adds,
|
|
291
329
|
* and the `maxUnits` cap sent on-chain to protect the user if better offers get filled first. `assetsRaw`
|
|
@@ -310,14 +348,8 @@ export const getMorphoMidnightBorrowQuote = async (
|
|
|
310
348
|
maturity: number,
|
|
311
349
|
maxBorrowRate?: Dec.Value,
|
|
312
350
|
): Promise<MorphoMidnightBorrowQuote> => {
|
|
313
|
-
const
|
|
314
|
-
const
|
|
315
|
-
const json: { data?: MidnightQuoteResponse, error?: MidnightApiError } = await res.json();
|
|
316
|
-
const d = json?.data;
|
|
317
|
-
if (!d?.average_best_price) throw new Error(midnightQuoteError(json?.error));
|
|
318
|
-
|
|
319
|
-
const bestPrice = new Dec(d.average_best_price).div(WAD).toString();
|
|
320
|
-
const worstPrice = new Dec(d.average_worst_price || 0).div(WAD).toString();
|
|
351
|
+
const quote = await fetchMorphoMidnightQuote(marketId, 'bids', assetsRaw, slippagePercent);
|
|
352
|
+
const { bestPrice, worstPrice } = quote;
|
|
321
353
|
const ttmDays = midnightTimeToMaturityDays(maturity);
|
|
322
354
|
const estBorrowRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
323
355
|
|
|
@@ -331,14 +363,58 @@ export const getMorphoMidnightBorrowQuote = async (
|
|
|
331
363
|
const maxUnits = new Dec(capPrice).lte(0) ? '0' : new Dec(assetsRaw).div(capPrice).toFixed(0);
|
|
332
364
|
|
|
333
365
|
return {
|
|
334
|
-
|
|
335
|
-
worstPrice,
|
|
366
|
+
...quote,
|
|
336
367
|
estBorrowRate,
|
|
337
368
|
maxRate,
|
|
338
369
|
newUnits,
|
|
339
370
|
maxUnits,
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
371
|
+
};
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Quote a prospective payback against the ask side of the Midnight order book: the rate the repayment
|
|
376
|
+
* retires debt at, the debt units it buys, and the `minUnits` floor sent on-chain to protect the user if
|
|
377
|
+
* the cheap offers get taken first. `assetsRaw` is the amount **spent** (raw loan-token base units) —
|
|
378
|
+
* matching `MidnightPaybackFromOrders`, whose `amount` is what leaves the wallet; the units bought, and
|
|
379
|
+
* therefore the debt retired, exceed it because a unit costs less than one loan token before maturity.
|
|
380
|
+
* Throws if the book can't fill the amount (caller handles).
|
|
381
|
+
*
|
|
382
|
+
* The mirror image of the borrow quote in every respect. A repayer wants a *high* rate, i.e. cheap units,
|
|
383
|
+
* so the guard is a floor rather than a ceiling:
|
|
384
|
+
* - `minPaybackRate` — an absolute APY floor, honoured **exactly** via `midnightPriceFromApy`. Prefer
|
|
385
|
+
* this when a user pins a min rate.
|
|
386
|
+
* - otherwise `slippagePercent`, the API's own price-level knob, whose APY effect is amplified by the
|
|
387
|
+
* annualisation factor near maturity. `minRate` reports what the floor actually permits.
|
|
388
|
+
*
|
|
389
|
+
* A `minPaybackRate` above `estPaybackRate` yields `minUnits > newUnits` — the payback would revert
|
|
390
|
+
* on-chain. Compare the two before submitting and tell the user their floor is over the market rate.
|
|
391
|
+
*/
|
|
392
|
+
export const getMorphoMidnightPaybackQuote = async (
|
|
393
|
+
marketId: string,
|
|
394
|
+
assetsRaw: string,
|
|
395
|
+
slippagePercent: Dec.Value,
|
|
396
|
+
maturity: number,
|
|
397
|
+
minPaybackRate?: Dec.Value,
|
|
398
|
+
): Promise<MorphoMidnightPaybackQuote> => {
|
|
399
|
+
const quote = await fetchMorphoMidnightQuote(marketId, 'asks', assetsRaw, slippagePercent);
|
|
400
|
+
const { bestPrice, worstPrice } = quote;
|
|
401
|
+
const ttmDays = midnightTimeToMaturityDays(maturity);
|
|
402
|
+
const estPaybackRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
403
|
+
|
|
404
|
+
const capPrice = minPaybackRate !== undefined && new Dec(minPaybackRate).gt(0)
|
|
405
|
+
? midnightPriceFromApy(minPaybackRate, ttmDays)
|
|
406
|
+
: worstPrice;
|
|
407
|
+
const minRate = midnightApyFromPrice(capPrice, ttmDays);
|
|
408
|
+
// Rounded down on both counts: `newUnits` must not overstate the debt the user sees retired, and a
|
|
409
|
+
// `minUnits` rounded up would be a stricter floor than asked for and revert a payback that was fine.
|
|
410
|
+
const newUnits = new Dec(bestPrice).lte(0) ? '0' : new Dec(assetsRaw).div(bestPrice).toFixed(0, Dec.ROUND_DOWN);
|
|
411
|
+
const minUnits = new Dec(capPrice).lte(0) ? '0' : new Dec(assetsRaw).div(capPrice).toFixed(0, Dec.ROUND_DOWN);
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
...quote,
|
|
415
|
+
estPaybackRate,
|
|
416
|
+
minRate,
|
|
417
|
+
newUnits,
|
|
418
|
+
minUnits,
|
|
343
419
|
};
|
|
344
420
|
};
|
|
@@ -166,7 +166,7 @@ export async function _getMorphoMidnightAccountData(provider: Client, network: N
|
|
|
166
166
|
let assetsDataForApy = marketInfo.assetsData;
|
|
167
167
|
if (new Dec(positionInfo.debt.toString()).gt(0)) {
|
|
168
168
|
try {
|
|
169
|
-
const borrowInfo = await getMorphoMidnightUserBorrowInfo(account, marketId, marketInfo.
|
|
169
|
+
const borrowInfo = await getMorphoMidnightUserBorrowInfo(account, marketId, marketInfo.loanToken);
|
|
170
170
|
borrowRate = borrowInfo.borrowRate;
|
|
171
171
|
debtBase = borrowInfo.debtBase;
|
|
172
172
|
debtInterest = borrowInfo.debtInterest;
|
|
@@ -70,16 +70,20 @@ export interface MorphoMidnightMarketInfo {
|
|
|
70
70
|
assetsData: MorphoMidnightAssetsData,
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
//
|
|
73
|
+
// Which half of the order book a caller is taking from: `bids` are the lend offers a borrower fills,
|
|
74
|
+
// `asks` the sell offers a repayer buys debt units from.
|
|
75
|
+
export type MorphoMidnightBookSide = 'bids' | 'asks';
|
|
76
|
+
|
|
77
|
+
// One resting offer on a market's order book, as an annualized rate rather than the API's raw WAD price.
|
|
74
78
|
export interface MorphoMidnightBookOffer {
|
|
75
|
-
rate: string, // fixed
|
|
79
|
+
rate: string, // fixed APY, percent
|
|
76
80
|
liquidity: string, // loan-token amount available at this rate
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
export interface MorphoMidnightParsedBook {
|
|
80
|
-
bestRate: string, //
|
|
84
|
+
bestRate: string, // best rate for the taker of this side (= offers[0].rate)
|
|
81
85
|
totalLiquidity: string, // Σ offers[].liquidity, loan-token units
|
|
82
|
-
offers: MorphoMidnightBookOffer[], // ascending by rate
|
|
86
|
+
offers: MorphoMidnightBookOffer[], // best-first: bids ascending by rate, asks descending
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
export interface MorphoMidnightAggregatedPositionData {
|