@defisaver/positions-sdk 2.1.127-midnight-dev → 2.1.127-midnight-1-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 +34 -0
- package/cjs/helpers/morphoMidnightHelpers/index.js +103 -1
- package/cjs/morphoMidnight/index.js +25 -1
- package/cjs/types/morphoMidnight.d.ts +3 -0
- package/esm/helpers/morphoMidnightHelpers/index.d.ts +34 -0
- package/esm/helpers/morphoMidnightHelpers/index.js +98 -0
- package/esm/morphoMidnight/index.js +26 -2
- package/esm/types/morphoMidnight.d.ts +3 -0
- package/package.json +1 -1
- package/src/helpers/morphoMidnightHelpers/index.ts +133 -0
- package/src/morphoMidnight/index.ts +29 -2
- package/src/types/morphoMidnight.ts +6 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Dec from 'decimal.js';
|
|
1
2
|
import { MMUsedAssets } from '../../types/common';
|
|
2
3
|
import { MorphoMidnightAggregatedPositionData, MorphoMidnightAssetsData, MorphoMidnightMarketInfo } from '../../types';
|
|
3
4
|
/**
|
|
@@ -14,3 +15,36 @@ export declare const getMorphoMidnightAggregatedPositionData: ({ usedAssets, ass
|
|
|
14
15
|
assetsData: MorphoMidnightAssetsData;
|
|
15
16
|
marketInfo: MorphoMidnightMarketInfo;
|
|
16
17
|
}) => MorphoMidnightAggregatedPositionData;
|
|
18
|
+
export interface MorphoMidnightBorrowInfo {
|
|
19
|
+
borrowRate: string;
|
|
20
|
+
debtBase: string;
|
|
21
|
+
debtInterest: string;
|
|
22
|
+
debtTotal: string;
|
|
23
|
+
}
|
|
24
|
+
export interface MorphoMidnightBorrowQuote {
|
|
25
|
+
bestPrice: string;
|
|
26
|
+
worstPrice: string;
|
|
27
|
+
estBorrowRate: string;
|
|
28
|
+
maxRate: string;
|
|
29
|
+
newUnits: string;
|
|
30
|
+
maxUnits: string;
|
|
31
|
+
availableAssets: string;
|
|
32
|
+
availableUnits: string;
|
|
33
|
+
takeableOffers: any[];
|
|
34
|
+
}
|
|
35
|
+
export declare const midnightTimeToMaturityDays: (maturity: number, atSeconds?: number) => number;
|
|
36
|
+
export declare const midnightApyFromPrice: (price: Dec.Value, ttmDays: Dec.Value) => string;
|
|
37
|
+
/**
|
|
38
|
+
* Current borrower rate + debt breakdown from the Midnight transactions API. On-chain we can only read the
|
|
39
|
+
* total debt at maturity (`units`); the base-vs-interest split and the effective borrow rate require the
|
|
40
|
+
* fill history. Per fill the rate is (units / seller_assets)^(365 / ttmAtFill) − 1, weighted by base amount.
|
|
41
|
+
* The caller swallows errors — a missing rate must never block position rendering.
|
|
42
|
+
*/
|
|
43
|
+
export declare const getMorphoMidnightUserBorrowInfo: (account: string, marketId: string, maturity: number, loanTokenSymbol: string) => Promise<MorphoMidnightBorrowInfo>;
|
|
44
|
+
/**
|
|
45
|
+
* Estimate the borrow rate + slippage cap for a prospective borrow by quoting the Midnight order book.
|
|
46
|
+
* `assetsRaw` (and the returned `newUnits`/`maxUnits`) are raw loan-token base units — callers convert to/from
|
|
47
|
+
* human amounts. `maxUnits` (from the slippage-adjusted worst price) is the cap sent on-chain to protect the
|
|
48
|
+
* user if better offers get filled first. Throws if the book can't fill the amount (caller handles).
|
|
49
|
+
*/
|
|
50
|
+
export declare const getMorphoMidnightBorrowQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number) => Promise<MorphoMidnightBorrowQuote>;
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
2
11
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
13
|
};
|
|
5
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getMorphoMidnightAggregatedPositionData = void 0;
|
|
15
|
+
exports.getMorphoMidnightBorrowQuote = exports.getMorphoMidnightUserBorrowInfo = exports.midnightApyFromPrice = exports.midnightTimeToMaturityDays = exports.getMorphoMidnightAggregatedPositionData = void 0;
|
|
7
16
|
const decimal_js_1 = __importDefault(require("decimal.js"));
|
|
17
|
+
const tokens_1 = require("@defisaver/tokens");
|
|
8
18
|
const moneymarket_1 = require("../../moneymarket");
|
|
9
19
|
const staking_1 = require("../../staking");
|
|
10
20
|
const common_1 = require("../../types/common");
|
|
21
|
+
const constants_1 = require("../../constants");
|
|
22
|
+
const utils_1 = require("../../services/utils");
|
|
11
23
|
/**
|
|
12
24
|
* Aggregate a Morpho Midnight position. Midnight markets are multi-collateral, so the borrow limit is
|
|
13
25
|
* the sum of each collateral's USD value times its own lltv (Aave-v4 style), rather than a single pair.
|
|
@@ -66,3 +78,93 @@ const getMorphoMidnightAggregatedPositionData = ({ usedAssets, assetsData, marke
|
|
|
66
78
|
return payload;
|
|
67
79
|
};
|
|
68
80
|
exports.getMorphoMidnightAggregatedPositionData = getMorphoMidnightAggregatedPositionData;
|
|
81
|
+
// ── Off-chain order-book rate helpers ──────────────────────────────────────────────────────────────
|
|
82
|
+
// notion: https://app.notion.com/p/defisaver/Estimate-borrow-rate-and-slippage-before-execution-3a70be682adc80c783c8c11fdb761dd2
|
|
83
|
+
// the borrow rate is not exposed on-chain (MidnightView only stores total debt at maturity in `units`).
|
|
84
|
+
// derive the rate + interest from Morpho's public keyless Midnight API
|
|
85
|
+
// Quote prices are WAD-scaled
|
|
86
|
+
// loan-per-unit ratios (< 1 for a discounted fixed-term borrow); annualizing them yields the borrow APY.
|
|
87
|
+
const MIDNIGHT_API_BASE = 'https://api.morpho.org/v0/midnight';
|
|
88
|
+
const nowInSeconds = () => Math.floor(Date.now() / 1000);
|
|
89
|
+
// Days remaining until maturity, optionally measured at a past timestamp (for historical fills).
|
|
90
|
+
const midnightTimeToMaturityDays = (maturity, atSeconds = nowInSeconds()) => new decimal_js_1.default(maturity).sub(atSeconds).div(constants_1.SECONDS_PER_DAY).toNumber();
|
|
91
|
+
exports.midnightTimeToMaturityDays = midnightTimeToMaturityDays;
|
|
92
|
+
// Annualize a fixed-term discount price into an APY percent: (1 / price)^(365 / ttmDays) − 1.
|
|
93
|
+
// `price` is loan-per-unit (assets received / units owed), so 1/price ≥ 1.
|
|
94
|
+
const midnightApyFromPrice = (price, ttmDays) => {
|
|
95
|
+
const p = new decimal_js_1.default(price);
|
|
96
|
+
const ttm = new decimal_js_1.default(ttmDays);
|
|
97
|
+
if (p.lte(0) || ttm.lte(0))
|
|
98
|
+
return '0';
|
|
99
|
+
return new decimal_js_1.default(1).div(p).pow(new decimal_js_1.default(365).div(ttm)).sub(1)
|
|
100
|
+
.mul(100)
|
|
101
|
+
.toString();
|
|
102
|
+
};
|
|
103
|
+
exports.midnightApyFromPrice = midnightApyFromPrice;
|
|
104
|
+
/**
|
|
105
|
+
* Current borrower rate + debt breakdown from the Midnight transactions API. On-chain we can only read the
|
|
106
|
+
* total debt at maturity (`units`); the base-vs-interest split and the effective borrow rate require the
|
|
107
|
+
* fill history. Per fill the rate is (units / seller_assets)^(365 / ttmAtFill) − 1, weighted by base amount.
|
|
108
|
+
* The caller swallows errors — a missing rate must never block position rendering.
|
|
109
|
+
*/
|
|
110
|
+
const getMorphoMidnightUserBorrowInfo = (account, marketId, maturity, loanTokenSymbol) => __awaiter(void 0, void 0, void 0, function* () {
|
|
111
|
+
const res = yield fetch(`${MIDNIGHT_API_BASE}/users/${account}/transactions`, { signal: AbortSignal.timeout(utils_1.LONGER_TIMEOUT) });
|
|
112
|
+
const json = yield res.json();
|
|
113
|
+
const borrows = ((json === null || json === void 0 ? void 0 : json.data) || []).filter((t) => { var _a; return t.event_type === 'borrow' && ((_a = t.market_id) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === marketId.toLowerCase(); });
|
|
114
|
+
let sumSeller = new decimal_js_1.default(0); // Σ seller_assets (base), raw
|
|
115
|
+
let sumUnits = new decimal_js_1.default(0); // Σ units (debt at maturity), raw
|
|
116
|
+
let weightedApy = new decimal_js_1.default(0); // Σ seller_assets × APYᵢ
|
|
117
|
+
borrows.forEach((t) => {
|
|
118
|
+
var _a, _b;
|
|
119
|
+
const sellerAssets = new decimal_js_1.default(((_a = t.data) === null || _a === void 0 ? void 0 : _a.seller_assets) || 0);
|
|
120
|
+
const units = new decimal_js_1.default(((_b = t.data) === null || _b === void 0 ? void 0 : _b.units) || 0);
|
|
121
|
+
if (sellerAssets.lte(0) || units.lte(0))
|
|
122
|
+
return;
|
|
123
|
+
const ttmDays = (0, exports.midnightTimeToMaturityDays)(maturity, t.created_at);
|
|
124
|
+
const apy = (0, exports.midnightApyFromPrice)(sellerAssets.div(units), ttmDays); // price = seller_assets / units
|
|
125
|
+
sumSeller = sumSeller.add(sellerAssets);
|
|
126
|
+
sumUnits = sumUnits.add(units);
|
|
127
|
+
weightedApy = weightedApy.add(sellerAssets.mul(apy));
|
|
128
|
+
});
|
|
129
|
+
const borrowRate = sumSeller.lte(0) ? '0' : weightedApy.div(sumSeller).toString();
|
|
130
|
+
const debtBase = (0, tokens_1.assetAmountInEth)(sumSeller.toFixed(0), loanTokenSymbol);
|
|
131
|
+
const debtTotal = (0, tokens_1.assetAmountInEth)(sumUnits.toFixed(0), loanTokenSymbol);
|
|
132
|
+
const debtInterest = decimal_js_1.default.max(new decimal_js_1.default(debtTotal).sub(debtBase), 0).toString();
|
|
133
|
+
return {
|
|
134
|
+
borrowRate, debtBase, debtInterest, debtTotal,
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
exports.getMorphoMidnightUserBorrowInfo = getMorphoMidnightUserBorrowInfo;
|
|
138
|
+
/**
|
|
139
|
+
* Estimate the borrow rate + slippage cap for a prospective borrow by quoting the Midnight order book.
|
|
140
|
+
* `assetsRaw` (and the returned `newUnits`/`maxUnits`) are raw loan-token base units — callers convert to/from
|
|
141
|
+
* human amounts. `maxUnits` (from the slippage-adjusted worst price) is the cap sent on-chain to protect the
|
|
142
|
+
* user if better offers get filled first. Throws if the book can't fill the amount (caller handles).
|
|
143
|
+
*/
|
|
144
|
+
const getMorphoMidnightBorrowQuote = (marketId, assetsRaw, slippagePercent, maturity) => __awaiter(void 0, void 0, void 0, function* () {
|
|
145
|
+
const url = `${MIDNIGHT_API_BASE}/books/${marketId}/bids/quote?assets=${assetsRaw}&slippage=${slippagePercent}`;
|
|
146
|
+
const res = yield fetch(url, { signal: AbortSignal.timeout(utils_1.LONGER_TIMEOUT) });
|
|
147
|
+
const json = yield res.json();
|
|
148
|
+
const d = json === null || json === void 0 ? void 0 : json.data;
|
|
149
|
+
if (!(d === null || d === void 0 ? void 0 : d.average_best_price))
|
|
150
|
+
throw new Error('Morpho Midnight quote unavailable');
|
|
151
|
+
const bestPrice = new decimal_js_1.default(d.average_best_price).div(constants_1.WAD).toString();
|
|
152
|
+
const worstPrice = new decimal_js_1.default(d.average_worst_price).div(constants_1.WAD).toString();
|
|
153
|
+
const ttmDays = (0, exports.midnightTimeToMaturityDays)(maturity);
|
|
154
|
+
const estBorrowRate = (0, exports.midnightApyFromPrice)(bestPrice, ttmDays);
|
|
155
|
+
const maxRate = new decimal_js_1.default(estBorrowRate).add(slippagePercent).toString();
|
|
156
|
+
const newUnits = new decimal_js_1.default(bestPrice).lte(0) ? '0' : new decimal_js_1.default(assetsRaw).div(bestPrice).toFixed(0);
|
|
157
|
+
const maxUnits = new decimal_js_1.default(worstPrice).lte(0) ? '0' : new decimal_js_1.default(assetsRaw).div(worstPrice).toFixed(0);
|
|
158
|
+
return {
|
|
159
|
+
bestPrice,
|
|
160
|
+
worstPrice,
|
|
161
|
+
estBorrowRate,
|
|
162
|
+
maxRate,
|
|
163
|
+
newUnits,
|
|
164
|
+
maxUnits,
|
|
165
|
+
availableAssets: d.available_assets,
|
|
166
|
+
availableUnits: d.available_units,
|
|
167
|
+
takeableOffers: d.takeable_offers || [],
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
exports.getMorphoMidnightBorrowQuote = getMorphoMidnightBorrowQuote;
|
|
@@ -159,9 +159,33 @@ function _getMorphoMidnightAccountData(provider, network, account, selectedMarke
|
|
|
159
159
|
borrowedUsd: '0',
|
|
160
160
|
};
|
|
161
161
|
});
|
|
162
|
+
// Enrich borrower positions with the orderbook-derived rate + principal/interest split (off-chain).
|
|
163
|
+
// MidnightView only stores `debt` (= face value at maturity), so we default to full-debt-as-principal and
|
|
164
|
+
// override from the transactions API when available. Never throw here — the position must still render.
|
|
165
|
+
let borrowRate = '0';
|
|
166
|
+
let debtBase = debt; // fallback: treat the full on-chain debt as principal until fill history is known
|
|
167
|
+
let debtInterest = '0';
|
|
168
|
+
let assetsDataForApy = marketInfo.assetsData;
|
|
169
|
+
if (new decimal_js_1.default(positionInfo.debt.toString()).gt(0)) {
|
|
170
|
+
try {
|
|
171
|
+
const borrowInfo = yield (0, morphoMidnightHelpers_1.getMorphoMidnightUserBorrowInfo)(account, marketId, marketInfo.maturity, marketInfo.loanToken);
|
|
172
|
+
borrowRate = borrowInfo.borrowRate;
|
|
173
|
+
debtBase = borrowInfo.debtBase;
|
|
174
|
+
debtInterest = borrowInfo.debtInterest;
|
|
175
|
+
usedAssets[marketInfo.loanToken].borrowRate = borrowRate;
|
|
176
|
+
// Reflect the real borrow cost in netApy without mutating the shared marketInfo.assetsData.
|
|
177
|
+
assetsDataForApy = Object.assign(Object.assign({}, marketInfo.assetsData), { [marketInfo.loanToken]: Object.assign(Object.assign({}, loanTokenData), { borrowRate }) });
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
// Orderbook API unavailable — keep the on-chain-only fallback above.
|
|
181
|
+
}
|
|
182
|
+
}
|
|
162
183
|
return Object.assign({ usedAssets,
|
|
163
184
|
credit,
|
|
164
|
-
debt,
|
|
185
|
+
debt,
|
|
186
|
+
borrowRate,
|
|
187
|
+
debtBase,
|
|
188
|
+
debtInterest, maturity: marketInfo.maturity, isMatured: marketInfo.isMatured }, (0, morphoMidnightHelpers_1.getMorphoMidnightAggregatedPositionData)({ usedAssets, assetsData: assetsDataForApy, marketInfo }));
|
|
165
189
|
});
|
|
166
190
|
}
|
|
167
191
|
function getMorphoMidnightAccountData(provider, network, account, selectedMarket, marketInfo) {
|
|
@@ -86,6 +86,9 @@ export interface MorphoMidnightPositionData extends MorphoMidnightAggregatedPosi
|
|
|
86
86
|
usedAssets: MMUsedAssets;
|
|
87
87
|
credit: string;
|
|
88
88
|
debt: string;
|
|
89
|
+
borrowRate: string;
|
|
90
|
+
debtBase: string;
|
|
91
|
+
debtInterest: string;
|
|
89
92
|
maturity: number;
|
|
90
93
|
isMatured: boolean;
|
|
91
94
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Dec from 'decimal.js';
|
|
1
2
|
import { MMUsedAssets } from '../../types/common';
|
|
2
3
|
import { MorphoMidnightAggregatedPositionData, MorphoMidnightAssetsData, MorphoMidnightMarketInfo } from '../../types';
|
|
3
4
|
/**
|
|
@@ -14,3 +15,36 @@ export declare const getMorphoMidnightAggregatedPositionData: ({ usedAssets, ass
|
|
|
14
15
|
assetsData: MorphoMidnightAssetsData;
|
|
15
16
|
marketInfo: MorphoMidnightMarketInfo;
|
|
16
17
|
}) => MorphoMidnightAggregatedPositionData;
|
|
18
|
+
export interface MorphoMidnightBorrowInfo {
|
|
19
|
+
borrowRate: string;
|
|
20
|
+
debtBase: string;
|
|
21
|
+
debtInterest: string;
|
|
22
|
+
debtTotal: string;
|
|
23
|
+
}
|
|
24
|
+
export interface MorphoMidnightBorrowQuote {
|
|
25
|
+
bestPrice: string;
|
|
26
|
+
worstPrice: string;
|
|
27
|
+
estBorrowRate: string;
|
|
28
|
+
maxRate: string;
|
|
29
|
+
newUnits: string;
|
|
30
|
+
maxUnits: string;
|
|
31
|
+
availableAssets: string;
|
|
32
|
+
availableUnits: string;
|
|
33
|
+
takeableOffers: any[];
|
|
34
|
+
}
|
|
35
|
+
export declare const midnightTimeToMaturityDays: (maturity: number, atSeconds?: number) => number;
|
|
36
|
+
export declare const midnightApyFromPrice: (price: Dec.Value, ttmDays: Dec.Value) => string;
|
|
37
|
+
/**
|
|
38
|
+
* Current borrower rate + debt breakdown from the Midnight transactions API. On-chain we can only read the
|
|
39
|
+
* total debt at maturity (`units`); the base-vs-interest split and the effective borrow rate require the
|
|
40
|
+
* fill history. Per fill the rate is (units / seller_assets)^(365 / ttmAtFill) − 1, weighted by base amount.
|
|
41
|
+
* The caller swallows errors — a missing rate must never block position rendering.
|
|
42
|
+
*/
|
|
43
|
+
export declare const getMorphoMidnightUserBorrowInfo: (account: string, marketId: string, maturity: number, loanTokenSymbol: string) => Promise<MorphoMidnightBorrowInfo>;
|
|
44
|
+
/**
|
|
45
|
+
* Estimate the borrow rate + slippage cap for a prospective borrow by quoting the Midnight order book.
|
|
46
|
+
* `assetsRaw` (and the returned `newUnits`/`maxUnits`) are raw loan-token base units — callers convert to/from
|
|
47
|
+
* human amounts. `maxUnits` (from the slippage-adjusted worst price) is the cap sent on-chain to protect the
|
|
48
|
+
* user if better offers get filled first. Throws if the book can't fill the amount (caller handles).
|
|
49
|
+
*/
|
|
50
|
+
export declare const getMorphoMidnightBorrowQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number) => Promise<MorphoMidnightBorrowQuote>;
|
|
@@ -1,7 +1,19 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
1
10
|
import Dec from 'decimal.js';
|
|
11
|
+
import { assetAmountInEth } from '@defisaver/tokens';
|
|
2
12
|
import { calcLeverageLiqPrice, getAssetsTotal, getExposure, isLeveragedPos, } from '../../moneymarket';
|
|
3
13
|
import { calculateNetApy } from '../../staking';
|
|
4
14
|
import { LeverageType, } from '../../types/common';
|
|
15
|
+
import { SECONDS_PER_DAY, WAD } from '../../constants';
|
|
16
|
+
import { LONGER_TIMEOUT } from '../../services/utils';
|
|
5
17
|
/**
|
|
6
18
|
* Aggregate a Morpho Midnight position. Midnight markets are multi-collateral, so the borrow limit is
|
|
7
19
|
* the sum of each collateral's USD value times its own lltv (Aave-v4 style), rather than a single pair.
|
|
@@ -59,3 +71,89 @@ export const getMorphoMidnightAggregatedPositionData = ({ usedAssets, assetsData
|
|
|
59
71
|
payload.exposure = getExposure(payload.borrowedUsd, payload.suppliedUsd);
|
|
60
72
|
return payload;
|
|
61
73
|
};
|
|
74
|
+
// ── Off-chain order-book rate helpers ──────────────────────────────────────────────────────────────
|
|
75
|
+
// notion: https://app.notion.com/p/defisaver/Estimate-borrow-rate-and-slippage-before-execution-3a70be682adc80c783c8c11fdb761dd2
|
|
76
|
+
// the borrow rate is not exposed on-chain (MidnightView only stores total debt at maturity in `units`).
|
|
77
|
+
// derive the rate + interest from Morpho's public keyless Midnight API
|
|
78
|
+
// Quote prices are WAD-scaled
|
|
79
|
+
// loan-per-unit ratios (< 1 for a discounted fixed-term borrow); annualizing them yields the borrow APY.
|
|
80
|
+
const MIDNIGHT_API_BASE = 'https://api.morpho.org/v0/midnight';
|
|
81
|
+
const nowInSeconds = () => Math.floor(Date.now() / 1000);
|
|
82
|
+
// Days remaining until maturity, optionally measured at a past timestamp (for historical fills).
|
|
83
|
+
export const midnightTimeToMaturityDays = (maturity, atSeconds = nowInSeconds()) => new Dec(maturity).sub(atSeconds).div(SECONDS_PER_DAY).toNumber();
|
|
84
|
+
// Annualize a fixed-term discount price into an APY percent: (1 / price)^(365 / ttmDays) − 1.
|
|
85
|
+
// `price` is loan-per-unit (assets received / units owed), so 1/price ≥ 1.
|
|
86
|
+
export const midnightApyFromPrice = (price, ttmDays) => {
|
|
87
|
+
const p = new Dec(price);
|
|
88
|
+
const ttm = new Dec(ttmDays);
|
|
89
|
+
if (p.lte(0) || ttm.lte(0))
|
|
90
|
+
return '0';
|
|
91
|
+
return new Dec(1).div(p).pow(new Dec(365).div(ttm)).sub(1)
|
|
92
|
+
.mul(100)
|
|
93
|
+
.toString();
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Current borrower rate + debt breakdown from the Midnight transactions API. On-chain we can only read the
|
|
97
|
+
* total debt at maturity (`units`); the base-vs-interest split and the effective borrow rate require the
|
|
98
|
+
* fill history. Per fill the rate is (units / seller_assets)^(365 / ttmAtFill) − 1, weighted by base amount.
|
|
99
|
+
* The caller swallows errors — a missing rate must never block position rendering.
|
|
100
|
+
*/
|
|
101
|
+
export const getMorphoMidnightUserBorrowInfo = (account, marketId, maturity, loanTokenSymbol) => __awaiter(void 0, void 0, void 0, function* () {
|
|
102
|
+
const res = yield fetch(`${MIDNIGHT_API_BASE}/users/${account}/transactions`, { signal: AbortSignal.timeout(LONGER_TIMEOUT) });
|
|
103
|
+
const json = yield res.json();
|
|
104
|
+
const borrows = ((json === null || json === void 0 ? void 0 : json.data) || []).filter((t) => { var _a; return t.event_type === 'borrow' && ((_a = t.market_id) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === marketId.toLowerCase(); });
|
|
105
|
+
let sumSeller = new Dec(0); // Σ seller_assets (base), raw
|
|
106
|
+
let sumUnits = new Dec(0); // Σ units (debt at maturity), raw
|
|
107
|
+
let weightedApy = new Dec(0); // Σ seller_assets × APYᵢ
|
|
108
|
+
borrows.forEach((t) => {
|
|
109
|
+
var _a, _b;
|
|
110
|
+
const sellerAssets = new Dec(((_a = t.data) === null || _a === void 0 ? void 0 : _a.seller_assets) || 0);
|
|
111
|
+
const units = new Dec(((_b = t.data) === null || _b === void 0 ? void 0 : _b.units) || 0);
|
|
112
|
+
if (sellerAssets.lte(0) || units.lte(0))
|
|
113
|
+
return;
|
|
114
|
+
const ttmDays = midnightTimeToMaturityDays(maturity, t.created_at);
|
|
115
|
+
const apy = midnightApyFromPrice(sellerAssets.div(units), ttmDays); // price = seller_assets / units
|
|
116
|
+
sumSeller = sumSeller.add(sellerAssets);
|
|
117
|
+
sumUnits = sumUnits.add(units);
|
|
118
|
+
weightedApy = weightedApy.add(sellerAssets.mul(apy));
|
|
119
|
+
});
|
|
120
|
+
const borrowRate = sumSeller.lte(0) ? '0' : weightedApy.div(sumSeller).toString();
|
|
121
|
+
const debtBase = assetAmountInEth(sumSeller.toFixed(0), loanTokenSymbol);
|
|
122
|
+
const debtTotal = assetAmountInEth(sumUnits.toFixed(0), loanTokenSymbol);
|
|
123
|
+
const debtInterest = Dec.max(new Dec(debtTotal).sub(debtBase), 0).toString();
|
|
124
|
+
return {
|
|
125
|
+
borrowRate, debtBase, debtInterest, debtTotal,
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
/**
|
|
129
|
+
* Estimate the borrow rate + slippage cap for a prospective borrow by quoting the Midnight order book.
|
|
130
|
+
* `assetsRaw` (and the returned `newUnits`/`maxUnits`) are raw loan-token base units — callers convert to/from
|
|
131
|
+
* human amounts. `maxUnits` (from the slippage-adjusted worst price) is the cap sent on-chain to protect the
|
|
132
|
+
* user if better offers get filled first. Throws if the book can't fill the amount (caller handles).
|
|
133
|
+
*/
|
|
134
|
+
export const getMorphoMidnightBorrowQuote = (marketId, assetsRaw, slippagePercent, maturity) => __awaiter(void 0, void 0, void 0, function* () {
|
|
135
|
+
const url = `${MIDNIGHT_API_BASE}/books/${marketId}/bids/quote?assets=${assetsRaw}&slippage=${slippagePercent}`;
|
|
136
|
+
const res = yield fetch(url, { signal: AbortSignal.timeout(LONGER_TIMEOUT) });
|
|
137
|
+
const json = yield res.json();
|
|
138
|
+
const d = json === null || json === void 0 ? void 0 : json.data;
|
|
139
|
+
if (!(d === null || d === void 0 ? void 0 : d.average_best_price))
|
|
140
|
+
throw new Error('Morpho Midnight quote unavailable');
|
|
141
|
+
const bestPrice = new Dec(d.average_best_price).div(WAD).toString();
|
|
142
|
+
const worstPrice = new Dec(d.average_worst_price).div(WAD).toString();
|
|
143
|
+
const ttmDays = midnightTimeToMaturityDays(maturity);
|
|
144
|
+
const estBorrowRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
145
|
+
const maxRate = new Dec(estBorrowRate).add(slippagePercent).toString();
|
|
146
|
+
const newUnits = new Dec(bestPrice).lte(0) ? '0' : new Dec(assetsRaw).div(bestPrice).toFixed(0);
|
|
147
|
+
const maxUnits = new Dec(worstPrice).lte(0) ? '0' : new Dec(assetsRaw).div(worstPrice).toFixed(0);
|
|
148
|
+
return {
|
|
149
|
+
bestPrice,
|
|
150
|
+
worstPrice,
|
|
151
|
+
estBorrowRate,
|
|
152
|
+
maxRate,
|
|
153
|
+
newUnits,
|
|
154
|
+
maxUnits,
|
|
155
|
+
availableAssets: d.available_assets,
|
|
156
|
+
availableUnits: d.available_units,
|
|
157
|
+
takeableOffers: d.takeable_offers || [],
|
|
158
|
+
};
|
|
159
|
+
});
|
|
@@ -14,7 +14,7 @@ import { DFSFeedRegistryContractViem, FeedRegistryContractViem, MorphoMidnightVi
|
|
|
14
14
|
import { USD_QUOTE } from '../constants';
|
|
15
15
|
import { calculateNetApy } from '../staking';
|
|
16
16
|
import { isMainnetNetwork, wethToEth } from '../services/utils';
|
|
17
|
-
import { getMorphoMidnightAggregatedPositionData } from '../helpers/morphoMidnightHelpers';
|
|
17
|
+
import { getMorphoMidnightAggregatedPositionData, getMorphoMidnightUserBorrowInfo } from '../helpers/morphoMidnightHelpers';
|
|
18
18
|
import { getChainlinkAssetAddress } from '../services/priceService';
|
|
19
19
|
import { getViemProvider, setViemBlockNumber } from '../services/viem';
|
|
20
20
|
const HARDCODED_USD_STABLE_PRICE = '100000000'; // $1 with 8 decimals
|
|
@@ -148,9 +148,33 @@ export function _getMorphoMidnightAccountData(provider, network, account, select
|
|
|
148
148
|
borrowedUsd: '0',
|
|
149
149
|
};
|
|
150
150
|
});
|
|
151
|
+
// Enrich borrower positions with the orderbook-derived rate + principal/interest split (off-chain).
|
|
152
|
+
// MidnightView only stores `debt` (= face value at maturity), so we default to full-debt-as-principal and
|
|
153
|
+
// override from the transactions API when available. Never throw here — the position must still render.
|
|
154
|
+
let borrowRate = '0';
|
|
155
|
+
let debtBase = debt; // fallback: treat the full on-chain debt as principal until fill history is known
|
|
156
|
+
let debtInterest = '0';
|
|
157
|
+
let assetsDataForApy = marketInfo.assetsData;
|
|
158
|
+
if (new Dec(positionInfo.debt.toString()).gt(0)) {
|
|
159
|
+
try {
|
|
160
|
+
const borrowInfo = yield getMorphoMidnightUserBorrowInfo(account, marketId, marketInfo.maturity, marketInfo.loanToken);
|
|
161
|
+
borrowRate = borrowInfo.borrowRate;
|
|
162
|
+
debtBase = borrowInfo.debtBase;
|
|
163
|
+
debtInterest = borrowInfo.debtInterest;
|
|
164
|
+
usedAssets[marketInfo.loanToken].borrowRate = borrowRate;
|
|
165
|
+
// Reflect the real borrow cost in netApy without mutating the shared marketInfo.assetsData.
|
|
166
|
+
assetsDataForApy = Object.assign(Object.assign({}, marketInfo.assetsData), { [marketInfo.loanToken]: Object.assign(Object.assign({}, loanTokenData), { borrowRate }) });
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
// Orderbook API unavailable — keep the on-chain-only fallback above.
|
|
170
|
+
}
|
|
171
|
+
}
|
|
151
172
|
return Object.assign({ usedAssets,
|
|
152
173
|
credit,
|
|
153
|
-
debt,
|
|
174
|
+
debt,
|
|
175
|
+
borrowRate,
|
|
176
|
+
debtBase,
|
|
177
|
+
debtInterest, maturity: marketInfo.maturity, isMatured: marketInfo.isMatured }, getMorphoMidnightAggregatedPositionData({ usedAssets, assetsData: assetsDataForApy, marketInfo }));
|
|
154
178
|
});
|
|
155
179
|
}
|
|
156
180
|
export function getMorphoMidnightAccountData(provider, network, account, selectedMarket, marketInfo) {
|
|
@@ -86,6 +86,9 @@ export interface MorphoMidnightPositionData extends MorphoMidnightAggregatedPosi
|
|
|
86
86
|
usedAssets: MMUsedAssets;
|
|
87
87
|
credit: string;
|
|
88
88
|
debt: string;
|
|
89
|
+
borrowRate: string;
|
|
90
|
+
debtBase: string;
|
|
91
|
+
debtInterest: string;
|
|
89
92
|
maturity: number;
|
|
90
93
|
isMatured: boolean;
|
|
91
94
|
}
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Dec from 'decimal.js';
|
|
2
|
+
import { assetAmountInEth } from '@defisaver/tokens';
|
|
2
3
|
import {
|
|
3
4
|
calcLeverageLiqPrice, getAssetsTotal, getExposure, isLeveragedPos,
|
|
4
5
|
} from '../../moneymarket';
|
|
@@ -7,6 +8,8 @@ import {
|
|
|
7
8
|
LeverageType, MMAssetsData, MMUsedAsset, MMUsedAssets,
|
|
8
9
|
} from '../../types/common';
|
|
9
10
|
import { MorphoMidnightAggregatedPositionData, MorphoMidnightAssetsData, MorphoMidnightMarketInfo } from '../../types';
|
|
11
|
+
import { SECONDS_PER_DAY, WAD } from '../../constants';
|
|
12
|
+
import { LONGER_TIMEOUT } from '../../services/utils';
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* Aggregate a Morpho Midnight position. Midnight markets are multi-collateral, so the borrow limit is
|
|
@@ -84,3 +87,133 @@ export const getMorphoMidnightAggregatedPositionData = ({
|
|
|
84
87
|
|
|
85
88
|
return payload;
|
|
86
89
|
};
|
|
90
|
+
|
|
91
|
+
// ── Off-chain order-book rate helpers ──────────────────────────────────────────────────────────────
|
|
92
|
+
// notion: https://app.notion.com/p/defisaver/Estimate-borrow-rate-and-slippage-before-execution-3a70be682adc80c783c8c11fdb761dd2
|
|
93
|
+
// the borrow rate is not exposed on-chain (MidnightView only stores total debt at maturity in `units`).
|
|
94
|
+
// derive the rate + interest from Morpho's public keyless Midnight API
|
|
95
|
+
// Quote prices are WAD-scaled
|
|
96
|
+
// loan-per-unit ratios (< 1 for a discounted fixed-term borrow); annualizing them yields the borrow APY.
|
|
97
|
+
|
|
98
|
+
const MIDNIGHT_API_BASE = 'https://api.morpho.org/v0/midnight';
|
|
99
|
+
const nowInSeconds = () => Math.floor(Date.now() / 1000);
|
|
100
|
+
|
|
101
|
+
interface MidnightTransaction {
|
|
102
|
+
event_type: string,
|
|
103
|
+
market_id: string,
|
|
104
|
+
created_at: number,
|
|
105
|
+
data: { seller_assets?: string, units?: string },
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface MorphoMidnightBorrowInfo {
|
|
109
|
+
borrowRate: string, // weighted-average borrow APY as a percent
|
|
110
|
+
debtBase: string, // base borrowed (Σ seller_assets), loan-token units
|
|
111
|
+
debtInterest: string, // debtTotal − debtBase (interest owed at maturity), loan-token units
|
|
112
|
+
debtTotal: string, // Σ units = on-chain debt at maturity, loan-token units
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface MorphoMidnightBorrowQuote {
|
|
116
|
+
bestPrice: string, // average_best_price, loan-per-unit
|
|
117
|
+
worstPrice: string, // average_worst_price, slippage-adjusted
|
|
118
|
+
estBorrowRate: string, // estimated borrow APY as a percent
|
|
119
|
+
maxRate: string, // estBorrowRate + slippage (display only, not sent on-chain)
|
|
120
|
+
newUnits: string, // debt added at best price, raw loan-token base units
|
|
121
|
+
maxUnits: string, // slippage-capped debt (on-chain cap), raw loan-token base units
|
|
122
|
+
availableAssets: string,
|
|
123
|
+
availableUnits: string,
|
|
124
|
+
takeableOffers: any[], // opaque orderbook offers, forwarded verbatim to on-chain execution
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Days remaining until maturity, optionally measured at a past timestamp (for historical fills).
|
|
128
|
+
export const midnightTimeToMaturityDays = (maturity: number, atSeconds: number = nowInSeconds()): number => new Dec(maturity).sub(atSeconds).div(SECONDS_PER_DAY).toNumber();
|
|
129
|
+
|
|
130
|
+
// Annualize a fixed-term discount price into an APY percent: (1 / price)^(365 / ttmDays) − 1.
|
|
131
|
+
// `price` is loan-per-unit (assets received / units owed), so 1/price ≥ 1.
|
|
132
|
+
export const midnightApyFromPrice = (price: Dec.Value, ttmDays: Dec.Value): string => {
|
|
133
|
+
const p = new Dec(price);
|
|
134
|
+
const ttm = new Dec(ttmDays);
|
|
135
|
+
if (p.lte(0) || ttm.lte(0)) return '0';
|
|
136
|
+
return new Dec(1).div(p).pow(new Dec(365).div(ttm)).sub(1)
|
|
137
|
+
.mul(100)
|
|
138
|
+
.toString();
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Current borrower rate + debt breakdown from the Midnight transactions API. On-chain we can only read the
|
|
143
|
+
* total debt at maturity (`units`); the base-vs-interest split and the effective borrow rate require the
|
|
144
|
+
* fill history. Per fill the rate is (units / seller_assets)^(365 / ttmAtFill) − 1, weighted by base amount.
|
|
145
|
+
* The caller swallows errors — a missing rate must never block position rendering.
|
|
146
|
+
*/
|
|
147
|
+
export const getMorphoMidnightUserBorrowInfo = async (
|
|
148
|
+
account: string,
|
|
149
|
+
marketId: string,
|
|
150
|
+
maturity: number,
|
|
151
|
+
loanTokenSymbol: string,
|
|
152
|
+
): Promise<MorphoMidnightBorrowInfo> => {
|
|
153
|
+
const res = await fetch(`${MIDNIGHT_API_BASE}/users/${account}/transactions`, { signal: AbortSignal.timeout(LONGER_TIMEOUT) });
|
|
154
|
+
const json: { data?: MidnightTransaction[] } = await res.json();
|
|
155
|
+
const borrows = (json?.data || []).filter((t) => t.event_type === 'borrow' && t.market_id?.toLowerCase() === marketId.toLowerCase());
|
|
156
|
+
|
|
157
|
+
let sumSeller = new Dec(0); // Σ seller_assets (base), raw
|
|
158
|
+
let sumUnits = new Dec(0); // Σ units (debt at maturity), raw
|
|
159
|
+
let weightedApy = new Dec(0); // Σ seller_assets × APYᵢ
|
|
160
|
+
|
|
161
|
+
borrows.forEach((t) => {
|
|
162
|
+
const sellerAssets = new Dec(t.data?.seller_assets || 0);
|
|
163
|
+
const units = new Dec(t.data?.units || 0);
|
|
164
|
+
if (sellerAssets.lte(0) || units.lte(0)) return;
|
|
165
|
+
const ttmDays = midnightTimeToMaturityDays(maturity, t.created_at);
|
|
166
|
+
const apy = midnightApyFromPrice(sellerAssets.div(units), ttmDays); // price = seller_assets / units
|
|
167
|
+
sumSeller = sumSeller.add(sellerAssets);
|
|
168
|
+
sumUnits = sumUnits.add(units);
|
|
169
|
+
weightedApy = weightedApy.add(sellerAssets.mul(apy));
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const borrowRate = sumSeller.lte(0) ? '0' : weightedApy.div(sumSeller).toString();
|
|
173
|
+
const debtBase = assetAmountInEth(sumSeller.toFixed(0), loanTokenSymbol);
|
|
174
|
+
const debtTotal = assetAmountInEth(sumUnits.toFixed(0), loanTokenSymbol);
|
|
175
|
+
const debtInterest = Dec.max(new Dec(debtTotal).sub(debtBase), 0).toString();
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
borrowRate, debtBase, debtInterest, debtTotal,
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Estimate the borrow rate + slippage cap for a prospective borrow by quoting the Midnight order book.
|
|
184
|
+
* `assetsRaw` (and the returned `newUnits`/`maxUnits`) are raw loan-token base units — callers convert to/from
|
|
185
|
+
* human amounts. `maxUnits` (from the slippage-adjusted worst price) is the cap sent on-chain to protect the
|
|
186
|
+
* user if better offers get filled first. Throws if the book can't fill the amount (caller handles).
|
|
187
|
+
*/
|
|
188
|
+
export const getMorphoMidnightBorrowQuote = async (
|
|
189
|
+
marketId: string,
|
|
190
|
+
assetsRaw: string,
|
|
191
|
+
slippagePercent: Dec.Value,
|
|
192
|
+
maturity: number,
|
|
193
|
+
): Promise<MorphoMidnightBorrowQuote> => {
|
|
194
|
+
const url = `${MIDNIGHT_API_BASE}/books/${marketId}/bids/quote?assets=${assetsRaw}&slippage=${slippagePercent}`;
|
|
195
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(LONGER_TIMEOUT) });
|
|
196
|
+
const json: { data?: any } = await res.json();
|
|
197
|
+
const d = json?.data;
|
|
198
|
+
if (!d?.average_best_price) throw new Error('Morpho Midnight quote unavailable');
|
|
199
|
+
|
|
200
|
+
const bestPrice = new Dec(d.average_best_price).div(WAD).toString();
|
|
201
|
+
const worstPrice = new Dec(d.average_worst_price).div(WAD).toString();
|
|
202
|
+
const ttmDays = midnightTimeToMaturityDays(maturity);
|
|
203
|
+
const estBorrowRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
204
|
+
const maxRate = new Dec(estBorrowRate).add(slippagePercent).toString();
|
|
205
|
+
const newUnits = new Dec(bestPrice).lte(0) ? '0' : new Dec(assetsRaw).div(bestPrice).toFixed(0);
|
|
206
|
+
const maxUnits = new Dec(worstPrice).lte(0) ? '0' : new Dec(assetsRaw).div(worstPrice).toFixed(0);
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
bestPrice,
|
|
210
|
+
worstPrice,
|
|
211
|
+
estBorrowRate,
|
|
212
|
+
maxRate,
|
|
213
|
+
newUnits,
|
|
214
|
+
maxUnits,
|
|
215
|
+
availableAssets: d.available_assets,
|
|
216
|
+
availableUnits: d.available_units,
|
|
217
|
+
takeableOffers: d.takeable_offers || [],
|
|
218
|
+
};
|
|
219
|
+
};
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import { USD_QUOTE } from '../constants';
|
|
14
14
|
import { calculateNetApy } from '../staking';
|
|
15
15
|
import { isMainnetNetwork, wethToEth } from '../services/utils';
|
|
16
|
-
import { getMorphoMidnightAggregatedPositionData } from '../helpers/morphoMidnightHelpers';
|
|
16
|
+
import { getMorphoMidnightAggregatedPositionData, getMorphoMidnightUserBorrowInfo } from '../helpers/morphoMidnightHelpers';
|
|
17
17
|
import { getChainlinkAssetAddress } from '../services/priceService';
|
|
18
18
|
import { getViemProvider, setViemBlockNumber } from '../services/viem';
|
|
19
19
|
|
|
@@ -157,13 +157,40 @@ export async function _getMorphoMidnightAccountData(provider: Client, network: N
|
|
|
157
157
|
};
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
+
// Enrich borrower positions with the orderbook-derived rate + principal/interest split (off-chain).
|
|
161
|
+
// MidnightView only stores `debt` (= face value at maturity), so we default to full-debt-as-principal and
|
|
162
|
+
// override from the transactions API when available. Never throw here — the position must still render.
|
|
163
|
+
let borrowRate = '0';
|
|
164
|
+
let debtBase = debt; // fallback: treat the full on-chain debt as principal until fill history is known
|
|
165
|
+
let debtInterest = '0';
|
|
166
|
+
let assetsDataForApy = marketInfo.assetsData;
|
|
167
|
+
if (new Dec(positionInfo.debt.toString()).gt(0)) {
|
|
168
|
+
try {
|
|
169
|
+
const borrowInfo = await getMorphoMidnightUserBorrowInfo(account, marketId, marketInfo.maturity, marketInfo.loanToken);
|
|
170
|
+
borrowRate = borrowInfo.borrowRate;
|
|
171
|
+
debtBase = borrowInfo.debtBase;
|
|
172
|
+
debtInterest = borrowInfo.debtInterest;
|
|
173
|
+
usedAssets[marketInfo.loanToken].borrowRate = borrowRate;
|
|
174
|
+
// Reflect the real borrow cost in netApy without mutating the shared marketInfo.assetsData.
|
|
175
|
+
assetsDataForApy = {
|
|
176
|
+
...marketInfo.assetsData,
|
|
177
|
+
[marketInfo.loanToken]: { ...loanTokenData, borrowRate },
|
|
178
|
+
};
|
|
179
|
+
} catch (err) {
|
|
180
|
+
// Orderbook API unavailable — keep the on-chain-only fallback above.
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
160
184
|
return {
|
|
161
185
|
usedAssets,
|
|
162
186
|
credit,
|
|
163
187
|
debt,
|
|
188
|
+
borrowRate,
|
|
189
|
+
debtBase,
|
|
190
|
+
debtInterest,
|
|
164
191
|
maturity: marketInfo.maturity,
|
|
165
192
|
isMatured: marketInfo.isMatured,
|
|
166
|
-
...getMorphoMidnightAggregatedPositionData({ usedAssets, assetsData:
|
|
193
|
+
...getMorphoMidnightAggregatedPositionData({ usedAssets, assetsData: assetsDataForApy, marketInfo }),
|
|
167
194
|
};
|
|
168
195
|
}
|
|
169
196
|
|
|
@@ -99,6 +99,12 @@ export interface MorphoMidnightPositionData extends MorphoMidnightAggregatedPosi
|
|
|
99
99
|
usedAssets: MMUsedAssets,
|
|
100
100
|
credit: string, // lender credit units, face value at maturity (with interest); 0 for borrowers
|
|
101
101
|
debt: string, // borrower debt, face value at maturity (with interest); 0 for lenders
|
|
102
|
+
// Borrow rate + base/interest split are orderbook-derived off-chain (from the Midnight transactions API):
|
|
103
|
+
// MidnightView only stores `debt` (= face value at maturity), so principal-vs-interest and the effective
|
|
104
|
+
// rate are computed from the fill history. Default to '0'/`debt`/'0' for lenders or when the API is unavailable.
|
|
105
|
+
borrowRate: string, // weighted-average borrow APY as a percent
|
|
106
|
+
debtBase: string, // base borrowed (principal), loan-token units
|
|
107
|
+
debtInterest: string, // debt − debtBase (fixed interest owed at maturity), loan-token units
|
|
102
108
|
maturity: number,
|
|
103
109
|
isMatured: boolean,
|
|
104
110
|
}
|