@defisaver/positions-sdk 2.1.144-dev → 2.1.145
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/config/contracts.d.ts +249 -0
- package/cjs/config/contracts.js +11 -1
- package/cjs/constants/index.d.ts +1 -0
- package/cjs/constants/index.js +2 -1
- package/cjs/contracts.d.ts +1510 -0
- package/cjs/contracts.js +3 -2
- package/cjs/fluid/index.js +6 -2
- package/cjs/helpers/fluidHelpers/index.js +5 -4
- package/cjs/helpers/index.d.ts +1 -0
- package/cjs/helpers/index.js +2 -1
- package/cjs/helpers/morphoMidnightHelpers/headroom.d.ts +48 -0
- package/cjs/helpers/morphoMidnightHelpers/headroom.js +52 -0
- package/cjs/helpers/morphoMidnightHelpers/index.d.ts +113 -0
- package/cjs/helpers/morphoMidnightHelpers/index.js +298 -0
- package/cjs/helpers/morphoMidnightHelpers/rate.d.ts +15 -0
- package/cjs/helpers/morphoMidnightHelpers/rate.js +62 -0
- package/cjs/helpers/morphoMidnightHelpers/tenor.d.ts +129 -0
- package/cjs/helpers/morphoMidnightHelpers/tenor.js +254 -0
- package/cjs/index.d.ts +2 -1
- package/cjs/index.js +3 -1
- package/cjs/markets/index.d.ts +1 -0
- package/cjs/markets/index.js +6 -1
- package/cjs/markets/morphoMidnight/index.d.ts +60 -0
- package/cjs/markets/morphoMidnight/index.js +659 -0
- package/cjs/morphoMidnight/index.d.ts +14 -0
- package/cjs/morphoMidnight/index.js +261 -0
- package/cjs/portfolio/discovery.js +4 -0
- package/cjs/portfolio/index.js +44 -0
- package/cjs/services/viem.d.ts +11 -11
- package/cjs/staking/eligibility.d.ts +15 -1
- package/cjs/staking/eligibility.js +12 -1
- package/cjs/staking/staking.js +18 -22
- package/cjs/types/index.d.ts +1 -0
- package/cjs/types/index.js +1 -0
- package/cjs/types/morphoMidnight.d.ts +141 -0
- package/cjs/types/morphoMidnight.js +47 -0
- package/cjs/types/portfolio.d.ts +4 -0
- package/esm/config/contracts.d.ts +249 -0
- package/esm/config/contracts.js +9 -0
- package/esm/constants/index.d.ts +1 -0
- package/esm/constants/index.js +1 -0
- package/esm/contracts.d.ts +1510 -0
- package/esm/contracts.js +1 -0
- package/esm/fluid/index.js +6 -2
- package/esm/helpers/fluidHelpers/index.js +6 -5
- package/esm/helpers/index.d.ts +1 -0
- package/esm/helpers/index.js +1 -0
- package/esm/helpers/morphoMidnightHelpers/headroom.d.ts +48 -0
- package/esm/helpers/morphoMidnightHelpers/headroom.js +45 -0
- package/esm/helpers/morphoMidnightHelpers/index.d.ts +113 -0
- package/esm/helpers/morphoMidnightHelpers/index.js +271 -0
- package/esm/helpers/morphoMidnightHelpers/rate.d.ts +15 -0
- package/esm/helpers/morphoMidnightHelpers/rate.js +50 -0
- package/esm/helpers/morphoMidnightHelpers/tenor.d.ts +129 -0
- package/esm/helpers/morphoMidnightHelpers/tenor.js +238 -0
- package/esm/index.d.ts +2 -1
- package/esm/index.js +2 -1
- package/esm/markets/index.d.ts +1 -0
- package/esm/markets/index.js +1 -0
- package/esm/markets/morphoMidnight/index.d.ts +60 -0
- package/esm/markets/morphoMidnight/index.js +631 -0
- package/esm/morphoMidnight/index.d.ts +14 -0
- package/esm/morphoMidnight/index.js +248 -0
- package/esm/portfolio/discovery.js +5 -1
- package/esm/portfolio/index.js +45 -1
- package/esm/services/viem.d.ts +11 -11
- package/esm/staking/eligibility.d.ts +15 -1
- package/esm/staking/eligibility.js +10 -0
- package/esm/staking/staking.js +19 -23
- package/esm/types/index.d.ts +1 -0
- package/esm/types/index.js +1 -0
- package/esm/types/morphoMidnight.d.ts +141 -0
- package/esm/types/morphoMidnight.js +44 -0
- package/esm/types/portfolio.d.ts +4 -0
- package/package.json +1 -1
- package/src/config/contracts.ts +9 -0
- package/src/constants/index.ts +1 -0
- package/src/contracts.ts +1 -0
- package/src/fluid/index.ts +6 -2
- package/src/helpers/fluidHelpers/index.ts +6 -5
- package/src/helpers/index.ts +1 -0
- package/src/helpers/morphoMidnightHelpers/headroom.ts +82 -0
- package/src/helpers/morphoMidnightHelpers/index.ts +485 -0
- package/src/helpers/morphoMidnightHelpers/rate.ts +71 -0
- package/src/helpers/morphoMidnightHelpers/tenor.ts +407 -0
- package/src/index.ts +2 -0
- package/src/markets/index.ts +3 -0
- package/src/markets/morphoMidnight/index.ts +675 -0
- package/src/morphoMidnight/index.ts +276 -0
- package/src/portfolio/discovery.ts +6 -0
- package/src/portfolio/index.ts +44 -0
- package/src/staking/eligibility.ts +29 -2
- package/src/staking/staking.ts +20 -22
- package/src/types/index.ts +1 -0
- package/src/types/morphoMidnight.ts +163 -0
- package/src/types/portfolio.ts +4 -0
package/cjs/contracts.js
CHANGED
|
@@ -33,8 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.
|
|
37
|
-
exports.UniswapTokenDistributorViem = exports.AaveV4ViewContractViem = exports.SkySavingsContractView = exports.MakerDsrContractViem = exports.YearnViewContractViem = exports.StkAAVEViem = exports.LiquityV2sBoldVaultViem = void 0;
|
|
36
|
+
exports.SparkRewardsControllerViem = exports.UUPSViem = exports.LiquityStabilityPoolViem = exports.LiquityLQTYStakingViem = exports.AaveUmbrellaViewViem = exports.AaveIncentivesControllerViem = exports.FluidViewContractViem = exports.LiquityV2LegacyViewContractViem = exports.LiquityV2ViewContractViem = exports.LiquityActivePoolContractViem = exports.LiquityPriceFeedContractViem = exports.LiquityTroveManagerContractViem = exports.LiquityCollSurplusPoolContractViem = exports.LiquityViewContractViem = exports.BTCPriceFeedContractViem = exports.WeETHPriceFeedContractViem = exports.ComptrollerContractViem = exports.CompoundLoanInfoContractViem = exports.McdJugContractViem = exports.McdDogContractViem = exports.McdSpotterContractViem = exports.McdVatContractViem = exports.McdViewContractViem = exports.McdGetCdpsContractViem = exports.LlamaLendViewContractViem = exports.CrvUSDFactoryContractViem = exports.CrvUSDViewContractViem = exports.SparkIncentiveDataProviderContractViem = exports.SparkViewContractViem = exports.CompV3ViewContractViem = exports.WstETHPriceFeedContractViem = exports.USDCPriceFeedContractViem = exports.ETHPriceFeedContractViem = exports.COMPPriceFeedContractViem = exports.DFSFeedRegistryContractViem = exports.FeedRegistryContractViem = exports.AaveIncentiveDataProviderV3ContractViem = exports.AaveV3ViewContractViem = exports.AaveLoanInfoV2ContractViem = exports.MorphoMidnightViewContractViem = exports.MorphoBlueViewContractViem = exports.getYearnV3VaultContractViem = exports.getErc4626ContractViem = exports.getErc20ContractViem = exports.getSparkSavingsVaultContractViem = exports.getYearnVaultContractViem = exports.getMorphoVaultContractViem = exports.createViemContractFromConfigFunc = exports.getConfigContractAbi = exports.getConfigContractAddress = void 0;
|
|
37
|
+
exports.UniswapTokenDistributorViem = exports.AaveV4ViewContractViem = exports.SkySavingsContractView = exports.MakerDsrContractViem = exports.YearnViewContractViem = exports.StkAAVEViem = exports.LiquityV2sBoldVaultViem = exports.AaveRewardsControllerViem = void 0;
|
|
38
38
|
const viem_1 = require("viem");
|
|
39
39
|
const configRaw = __importStar(require("./config/contracts"));
|
|
40
40
|
// @ts-ignore
|
|
@@ -140,6 +140,7 @@ const getYearnV3VaultContractViem = (client, address) => {
|
|
|
140
140
|
};
|
|
141
141
|
exports.getYearnV3VaultContractViem = getYearnV3VaultContractViem;
|
|
142
142
|
exports.MorphoBlueViewContractViem = (0, exports.createViemContractFromConfigFunc)('MorphoBlueView');
|
|
143
|
+
exports.MorphoMidnightViewContractViem = (0, exports.createViemContractFromConfigFunc)('MidnightView');
|
|
143
144
|
exports.AaveLoanInfoV2ContractViem = (0, exports.createViemContractFromConfigFunc)('AaveLoanInfoV2');
|
|
144
145
|
exports.AaveV3ViewContractViem = (0, exports.createViemContractFromConfigFunc)('AaveV3View');
|
|
145
146
|
exports.AaveIncentiveDataProviderV3ContractViem = (0, exports.createViemContractFromConfigFunc)('AaveUiIncentiveDataProviderV3');
|
package/cjs/fluid/index.js
CHANGED
|
@@ -1282,8 +1282,12 @@ const getAllUserEarnPositionsWithFTokens = (provider, network, user) => __awaite
|
|
|
1282
1282
|
exports.getAllUserEarnPositionsWithFTokens = getAllUserEarnPositionsWithFTokens;
|
|
1283
1283
|
const _getUserPositions = (provider, network, user) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1284
1284
|
const view = (0, contracts_1.FluidViewContractViem)(provider, network);
|
|
1285
|
-
const data = yield
|
|
1286
|
-
|
|
1285
|
+
const [data, merklCampaigns] = yield Promise.all([
|
|
1286
|
+
view.read.getUserPositions([user]),
|
|
1287
|
+
(0, merkl_1.getFluidMerklCampaigns)(network),
|
|
1288
|
+
]);
|
|
1289
|
+
const parsedMarketData = (yield Promise.all(data[1].map((vaultData) => __awaiter(void 0, void 0, void 0, function* () { return parseMarketData(provider, vaultData, network); }))))
|
|
1290
|
+
.map(marketData => (marketData ? (0, merkl_1.attachFluidMerklIncentives)(marketData, merklCampaigns) : marketData));
|
|
1287
1291
|
const userData = data[0].map((position, i) => (parsedMarketData[i] && Object.assign({}, parseUserData(position, parsedMarketData[i]))));
|
|
1288
1292
|
return parsedMarketData.map((market, i) => ({
|
|
1289
1293
|
marketData: market,
|
|
@@ -23,10 +23,11 @@ const getUniqueFluidMerklIncentives = (assetsData, side) => {
|
|
|
23
23
|
};
|
|
24
24
|
exports.getUniqueFluidMerklIncentives = getUniqueFluidMerklIncentives;
|
|
25
25
|
const getMerklIncentiveInterest = (incentives, principal, usedAssets) => incentives.reduce((total, { apy, eligibilityId }) => {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
:
|
|
26
|
+
const { isEligible, eligibleUSDAmount } = (0, staking_1.getIncentiveEligibility)({
|
|
27
|
+
incentive: { eligibilityId },
|
|
28
|
+
usedAssets,
|
|
29
|
+
defaultEligibleUSDAmount: principal,
|
|
30
|
+
});
|
|
30
31
|
const incentiveInterest = isEligible ? (0, staking_1.calculateInterestEarned)(eligibleUSDAmount, apy, 'year', true) : 0;
|
|
31
32
|
return new decimal_js_1.default(total).add(incentiveInterest).toString();
|
|
32
33
|
}, '0');
|
package/cjs/helpers/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * as sparkHelpers from './sparkHelpers';
|
|
|
4
4
|
export * as curveUsdHelpers from './curveUsdHelpers';
|
|
5
5
|
export * as makerHelpers from './makerHelpers';
|
|
6
6
|
export * as morphoBlueHelpers from './morphoBlueHelpers';
|
|
7
|
+
export * as morphoMidnightHelpers from './morphoMidnightHelpers';
|
|
7
8
|
export * as llamaLendHelpers from './llamaLendHelpers';
|
|
8
9
|
export * as liquityV2Helpers from './liquityV2Helpers';
|
|
9
10
|
export * as fluidHelpers from './fluidHelpers';
|
package/cjs/helpers/index.js
CHANGED
|
@@ -33,13 +33,14 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.aaveV4Helpers = exports.fluidHelpers = exports.liquityV2Helpers = exports.llamaLendHelpers = exports.morphoBlueHelpers = exports.makerHelpers = exports.curveUsdHelpers = exports.sparkHelpers = exports.compoundHelpers = exports.aaveHelpers = void 0;
|
|
36
|
+
exports.aaveV4Helpers = exports.fluidHelpers = exports.liquityV2Helpers = exports.llamaLendHelpers = exports.morphoMidnightHelpers = exports.morphoBlueHelpers = exports.makerHelpers = exports.curveUsdHelpers = exports.sparkHelpers = exports.compoundHelpers = exports.aaveHelpers = void 0;
|
|
37
37
|
exports.aaveHelpers = __importStar(require("./aaveHelpers"));
|
|
38
38
|
exports.compoundHelpers = __importStar(require("./compoundHelpers"));
|
|
39
39
|
exports.sparkHelpers = __importStar(require("./sparkHelpers"));
|
|
40
40
|
exports.curveUsdHelpers = __importStar(require("./curveUsdHelpers"));
|
|
41
41
|
exports.makerHelpers = __importStar(require("./makerHelpers"));
|
|
42
42
|
exports.morphoBlueHelpers = __importStar(require("./morphoBlueHelpers"));
|
|
43
|
+
exports.morphoMidnightHelpers = __importStar(require("./morphoMidnightHelpers"));
|
|
43
44
|
exports.llamaLendHelpers = __importStar(require("./llamaLendHelpers"));
|
|
44
45
|
exports.liquityV2Helpers = __importStar(require("./liquityV2Helpers"));
|
|
45
46
|
exports.fluidHelpers = __importStar(require("./fluidHelpers"));
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import Dec from 'decimal.js';
|
|
2
|
+
/**
|
|
3
|
+
* Safety margin every Midnight position is held at. On Midnight the lltv is *both* the borrow cap and the
|
|
4
|
+
* liquidation threshold (`liquidationLimitUsd === borrowLimitUsd` in the aggregate), so a position borrowed
|
|
5
|
+
* to its full limit opens at a health ratio of exactly 1 — immediately liquidatable. This is what holds it
|
|
6
|
+
* off that line, and what absorbs swap price impact on a boost.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MIDNIGHT_MIN_HEALTH_RATIO = 1.025;
|
|
9
|
+
export interface MidnightBorrowHeadroomParams {
|
|
10
|
+
borrowLimitUsd: Dec.Value;
|
|
11
|
+
borrowedUsd: Dec.Value;
|
|
12
|
+
/** Principal a unit of borrow power is worth (loan-per-unit price, ≤ 1), off a book rate. */
|
|
13
|
+
principalPerUnit?: Dec.Value;
|
|
14
|
+
/** Depth the book can fill, in loan tokens, from the same quote. Omitted leaves the answer uncapped. */
|
|
15
|
+
availableAssets?: Dec.Value;
|
|
16
|
+
loanTokenPrice?: Dec.Value;
|
|
17
|
+
/** Loan token decimals. Omitted skips the quantisation, which only a caller ignoring `leftToBorrow` wants. */
|
|
18
|
+
loanTokenDecimals?: number;
|
|
19
|
+
isMatured?: boolean;
|
|
20
|
+
minHealthRatio?: Dec.Value;
|
|
21
|
+
}
|
|
22
|
+
export interface MidnightBorrowHeadroom {
|
|
23
|
+
/** Face-value headroom, before the order-book discount. What a *boost* solve scales itself. */
|
|
24
|
+
headroomUsd: string;
|
|
25
|
+
/** The borrowable amount in loan tokens — the Borrow action's Max, quantised exactly as it is typed in. */
|
|
26
|
+
leftToBorrow: string;
|
|
27
|
+
/** `leftToBorrow · price`. Derived FROM the token amount, never computed alongside it — see below. */
|
|
28
|
+
leftToBorrowUsd: string;
|
|
29
|
+
borrowPowerUsed: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The one answer to "how much more can this position borrow". Every surface that sizes a Midnight borrow
|
|
33
|
+
* derives from here — the max-borrow getter, the position overview's headroom rows, and the max-boost
|
|
34
|
+
* solve, which scales `headroomUsd` by its own leverage factors — so a max and the figure shown next to it
|
|
35
|
+
* cannot drift into two different formulas. They did: the overview used to price
|
|
36
|
+
* the whole headroom off the top-of-book offer (often dust) with no rate pad and no depth cap, while the
|
|
37
|
+
* max walked the book for its real size, which is what put the two 1–2% apart.
|
|
38
|
+
*
|
|
39
|
+
* Three corrections separate this from a plain `limit − debt`, and dropping any one overstates the answer:
|
|
40
|
+
* `minHealthRatio` reserves the margin, `principalPerUnit` turns face-value headroom into the principal a
|
|
41
|
+
* borrow actually receives (a correction that grows with the term, past 7% on a one-year market), and
|
|
42
|
+
* `availableAssets` caps it at what the book can currently fill.
|
|
43
|
+
*
|
|
44
|
+
* `leftToBorrowUsd` is deliberately `leftToBorrow · price` rather than the USD figure the token amount was
|
|
45
|
+
* derived from: the two differ by the round-down to token decimals, and a UI showing one next to an input
|
|
46
|
+
* holding the other would be reporting a difference the user cannot spend. Everything quantises once, here.
|
|
47
|
+
*/
|
|
48
|
+
export declare const getMorphoMidnightBorrowHeadroom: ({ borrowLimitUsd, borrowedUsd, principalPerUnit, availableAssets, loanTokenPrice, loanTokenDecimals, isMatured, minHealthRatio, }: MidnightBorrowHeadroomParams) => MidnightBorrowHeadroom;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getMorphoMidnightBorrowHeadroom = exports.MIDNIGHT_MIN_HEALTH_RATIO = void 0;
|
|
7
|
+
const decimal_js_1 = __importDefault(require("decimal.js"));
|
|
8
|
+
/**
|
|
9
|
+
* Safety margin every Midnight position is held at. On Midnight the lltv is *both* the borrow cap and the
|
|
10
|
+
* liquidation threshold (`liquidationLimitUsd === borrowLimitUsd` in the aggregate), so a position borrowed
|
|
11
|
+
* to its full limit opens at a health ratio of exactly 1 — immediately liquidatable. This is what holds it
|
|
12
|
+
* off that line, and what absorbs swap price impact on a boost.
|
|
13
|
+
*/
|
|
14
|
+
exports.MIDNIGHT_MIN_HEALTH_RATIO = 1.025;
|
|
15
|
+
/**
|
|
16
|
+
* The one answer to "how much more can this position borrow". Every surface that sizes a Midnight borrow
|
|
17
|
+
* derives from here — the max-borrow getter, the position overview's headroom rows, and the max-boost
|
|
18
|
+
* solve, which scales `headroomUsd` by its own leverage factors — so a max and the figure shown next to it
|
|
19
|
+
* cannot drift into two different formulas. They did: the overview used to price
|
|
20
|
+
* the whole headroom off the top-of-book offer (often dust) with no rate pad and no depth cap, while the
|
|
21
|
+
* max walked the book for its real size, which is what put the two 1–2% apart.
|
|
22
|
+
*
|
|
23
|
+
* Three corrections separate this from a plain `limit − debt`, and dropping any one overstates the answer:
|
|
24
|
+
* `minHealthRatio` reserves the margin, `principalPerUnit` turns face-value headroom into the principal a
|
|
25
|
+
* borrow actually receives (a correction that grows with the term, past 7% on a one-year market), and
|
|
26
|
+
* `availableAssets` caps it at what the book can currently fill.
|
|
27
|
+
*
|
|
28
|
+
* `leftToBorrowUsd` is deliberately `leftToBorrow · price` rather than the USD figure the token amount was
|
|
29
|
+
* derived from: the two differ by the round-down to token decimals, and a UI showing one next to an input
|
|
30
|
+
* holding the other would be reporting a difference the user cannot spend. Everything quantises once, here.
|
|
31
|
+
*/
|
|
32
|
+
const getMorphoMidnightBorrowHeadroom = ({ borrowLimitUsd, borrowedUsd, principalPerUnit = 1, availableAssets, loanTokenPrice = 0, loanTokenDecimals, isMatured = false, minHealthRatio = exports.MIDNIGHT_MIN_HEALTH_RATIO, }) => {
|
|
33
|
+
const usableLimitUsd = new decimal_js_1.default(borrowLimitUsd).div(minHealthRatio);
|
|
34
|
+
const headroomUsd = isMatured ? new decimal_js_1.default(0) : decimal_js_1.default.max(0, usableLimitUsd.sub(borrowedUsd));
|
|
35
|
+
const price = new decimal_js_1.default(loanTokenPrice);
|
|
36
|
+
// Capped in loan tokens rather than in USD: `availableAssets` is quoted in tokens, and converting it to
|
|
37
|
+
// USD only to divide the result back out again would re-round the one number that has to land exactly.
|
|
38
|
+
const payout = price.lte(0) ? new decimal_js_1.default(0) : headroomUsd.mul(principalPerUnit).div(price);
|
|
39
|
+
const capped = availableAssets === undefined ? payout : decimal_js_1.default.min(payout, availableAssets);
|
|
40
|
+
// Round down: rounding the last decimal up would eat into the margin we just reserved.
|
|
41
|
+
const leftToBorrow = loanTokenDecimals === undefined ? capped : capped.toDP(loanTokenDecimals, decimal_js_1.default.ROUND_DOWN);
|
|
42
|
+
return {
|
|
43
|
+
headroomUsd: headroomUsd.toString(),
|
|
44
|
+
leftToBorrow: leftToBorrow.toString(),
|
|
45
|
+
leftToBorrowUsd: leftToBorrow.mul(price).toString(),
|
|
46
|
+
// Measured against the margined limit rather than the raw one, so it reads 100% at max borrow instead
|
|
47
|
+
// of 97.56%, and can exceed 100% inside the reserved band. Deliberately left uncapped by book depth:
|
|
48
|
+
// how much a position has borrowed is a property of the position, not of what the book can fill today.
|
|
49
|
+
borrowPowerUsed: usableLimitUsd.lte(0) ? '0' : new decimal_js_1.default(borrowedUsd).div(usableLimitUsd).mul(100).toString(),
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
exports.getMorphoMidnightBorrowHeadroom = getMorphoMidnightBorrowHeadroom;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import Dec from 'decimal.js';
|
|
2
|
+
import { MMUsedAssets, NetworkNumber } from '../../types/common';
|
|
3
|
+
import { MorphoMidnightAggregatedPositionData, MorphoMidnightAssetsData, MorphoMidnightBookSide, MorphoMidnightMarketData, MorphoMidnightMarketInfo, MorphoMidnightParsedBook } from '../../types';
|
|
4
|
+
export { buildMidnightParsedBook, midnightApyFromPrice, midnightBookBestFirst, midnightBoundPrice, midnightPriceFromApy, midnightTimeToMaturityDays, MIDNIGHT_DEFAULT_RATE_SLIPPAGE, } from './rate';
|
|
5
|
+
export { getMorphoMidnightBorrowHeadroom, MIDNIGHT_MIN_HEALTH_RATIO, } from './headroom';
|
|
6
|
+
export type { MidnightBorrowHeadroom, MidnightBorrowHeadroomParams } from './headroom';
|
|
7
|
+
export { tenorBookKeyFor, tenorBookRateToApyPercent, tenorOfferFillToApiFill, tenorOfferToApiOffer, } from './tenor';
|
|
8
|
+
/**
|
|
9
|
+
* Aggregate a Morpho Midnight position. Midnight markets are multi-collateral, so the borrow limit is
|
|
10
|
+
* the sum of each collateral's USD value times its own lltv (Aave-v4 style), rather than a single pair.
|
|
11
|
+
*
|
|
12
|
+
* Note on amounts: `borrowedUsd` is derived from the position's `debt`, which is the face value owed at
|
|
13
|
+
* maturity (principal + fixed interest). Health is therefore measured against the full maturity debt,
|
|
14
|
+
* matching how MidnightView computes `ratio`. Fixed-rate APY is not derived on-chain in MVP, so
|
|
15
|
+
* `netApy` reflects the `'0'` rates in `assetsData` (see the module getter).
|
|
16
|
+
*/
|
|
17
|
+
export declare const getMorphoMidnightAggregatedPositionData: ({ usedAssets, assetsData, marketInfo, }: {
|
|
18
|
+
usedAssets: MMUsedAssets;
|
|
19
|
+
assetsData: MorphoMidnightAssetsData;
|
|
20
|
+
marketInfo: MorphoMidnightMarketInfo;
|
|
21
|
+
}) => MorphoMidnightAggregatedPositionData;
|
|
22
|
+
export interface MorphoMidnightBorrowInfo {
|
|
23
|
+
borrowRate: string;
|
|
24
|
+
debtBase: string;
|
|
25
|
+
debtInterest: string;
|
|
26
|
+
debtTotal: string;
|
|
27
|
+
}
|
|
28
|
+
export interface MorphoMidnightBorrowQuote {
|
|
29
|
+
bestPrice: string;
|
|
30
|
+
worstPrice: string;
|
|
31
|
+
estBorrowRate: string;
|
|
32
|
+
maxRate: string;
|
|
33
|
+
newUnits: string;
|
|
34
|
+
maxUnits: string;
|
|
35
|
+
availableAssets: string;
|
|
36
|
+
availableUnits: string;
|
|
37
|
+
takeableOffers: any[];
|
|
38
|
+
}
|
|
39
|
+
export interface MorphoMidnightPaybackQuote {
|
|
40
|
+
bestPrice: string;
|
|
41
|
+
worstPrice: string;
|
|
42
|
+
estPaybackRate: string;
|
|
43
|
+
minRate: string;
|
|
44
|
+
newUnits: string;
|
|
45
|
+
minUnits: string;
|
|
46
|
+
availableAssets: string;
|
|
47
|
+
availableUnits: string;
|
|
48
|
+
takeableOffers: any[];
|
|
49
|
+
}
|
|
50
|
+
export interface MorphoMidnightPaybackUnitsQuote {
|
|
51
|
+
bestPrice: string;
|
|
52
|
+
worstPrice: string;
|
|
53
|
+
estPaybackRate: string;
|
|
54
|
+
minRate: string;
|
|
55
|
+
newAssets: string;
|
|
56
|
+
maxAssets: string;
|
|
57
|
+
availableAssets: string;
|
|
58
|
+
availableUnits: string;
|
|
59
|
+
takeableOffers: any[];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Coerce a slippage into what the quote endpoint accepts: 0.1–100 with at most one decimal place. The
|
|
63
|
+
* validation is lexical, so a computed value (`4.15066671050631467`) is rejected outright — without this
|
|
64
|
+
* the request 400s and the quote looks unavailable.
|
|
65
|
+
*
|
|
66
|
+
* Rounded **down**, since a wider slippage is a looser cap than the caller asked for.
|
|
67
|
+
*/
|
|
68
|
+
export declare const midnightSlippageParam: (slippagePercent: Dec.Value) => string;
|
|
69
|
+
/**
|
|
70
|
+
* Current borrower rate + debt breakdown from the Midnight positions API. Reconstructing this from the raw
|
|
71
|
+
* `/transactions` fill history only sums `borrow` fills, so it overstates debt for any position with an
|
|
72
|
+
* early exit or partial liquidation (`exit_borrow_primary`, `partial_liquidation`, ... — the fill history
|
|
73
|
+
* has no exhaustive list of debt-reducing event types). `/positions` instead reports the already-netted
|
|
74
|
+
* `debt`, matching `MidnightView.getPositionInfo` exactly, plus `cost_basis` (outstanding principal,
|
|
75
|
+
* WAD-scaled raw base units) and `effective_rate_wad` (borrow APY, WAD-scaled) for the base/interest split.
|
|
76
|
+
* The caller swallows errors — a missing rate must never block position rendering.
|
|
77
|
+
*/
|
|
78
|
+
export declare const getMorphoMidnightUserBorrowInfo: (account: string, marketId: string, loanTokenSymbol: string) => Promise<MorphoMidnightBorrowInfo>;
|
|
79
|
+
/**
|
|
80
|
+
* The inverse of the split above: where `getMorphoMidnightUserBorrowInfo` reads a position's principal and
|
|
81
|
+
* interest off the indexer, this carries that same split forward through a payback, from a debt of
|
|
82
|
+
* `borrowedBefore` down to one of `borrowedAfter`. Retiring units retires principal and interest pro rata,
|
|
83
|
+
* so both scale by the same fraction.
|
|
84
|
+
*
|
|
85
|
+
* Deriving the interest as `borrowedAfter − debtBase` instead reads it off two different sources —
|
|
86
|
+
* `borrowed` is the chain's debt, `debtBase` the indexer's principal — so the entire gap between them lands
|
|
87
|
+
* in the interest whenever they disagree, which they do until the indexer catches up with a fresh borrow or
|
|
88
|
+
* payback: behind a borrow it inflates the interest by the debt the indexer has not seen yet, behind a
|
|
89
|
+
* payback it goes negative on a position that still owes. Callers detect that window as
|
|
90
|
+
* `debtBase + debtInterest !== debt`.
|
|
91
|
+
*
|
|
92
|
+
* Pro rata is also what keeps the answer sane when a payback retires debt at a different rate than the one
|
|
93
|
+
* it was opened at. Mirroring a borrow instead — principal growing by the assets received, interest by the
|
|
94
|
+
* rest — hands back a negative interest as soon as the book sells units back cheaper than they were bought.
|
|
95
|
+
*/
|
|
96
|
+
export declare const scaleMorphoMidnightDebtSplit: ({ debtBase, debtInterest }: Pick<MorphoMidnightBorrowInfo, "debtBase" | "debtInterest">, borrowedBefore: Dec.Value, borrowedAfter: Dec.Value) => Pick<MorphoMidnightBorrowInfo, "debtBase" | "debtInterest">;
|
|
97
|
+
/**
|
|
98
|
+
* One side of a market's resting order book, as rates rather than the API's WAD-scaled loan-per-unit
|
|
99
|
+
* prices. Annualizing each price against time-to-maturity gives the rate a taker filling that offer gets
|
|
100
|
+
* — verified against Morpho's fixed-market UI, where per-offer rates match to the cent.
|
|
101
|
+
*
|
|
102
|
+
* `bids` are the lend offers a borrower fills, so the best of them is the *lowest* rate; `asks` are the
|
|
103
|
+
* sell offers a repayer buys debt units from, where a lower price buys more units, so the best is the
|
|
104
|
+
* *highest* rate. Either way `offers` comes back best-first and `bestRate` is `offers[0].rate`.
|
|
105
|
+
*
|
|
106
|
+
* Returns `null` for an empty side: there is nothing to take, so a market listing should skip the market
|
|
107
|
+
* rather than advertise it at a 0% rate. Throws when the request fails — an error response is rarely
|
|
108
|
+
* JSON, so without the `res.ok` check it parses as an empty book and the market silently vanishes.
|
|
109
|
+
*/
|
|
110
|
+
export declare const getMorphoMidnightMarketBook: (market: MorphoMidnightMarketData, network: NetworkNumber, side?: MorphoMidnightBookSide) => Promise<MorphoMidnightParsedBook | null>;
|
|
111
|
+
export declare const getMorphoMidnightBorrowQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, maxBorrowRate?: Dec.Value, taker?: string, rateSlippagePercent?: Dec.Value) => Promise<MorphoMidnightBorrowQuote>;
|
|
112
|
+
export declare const getMorphoMidnightPaybackQuote: (marketId: string, assetsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value, taker?: string, rateSlippagePercent?: Dec.Value) => Promise<MorphoMidnightPaybackQuote>;
|
|
113
|
+
export declare const getMorphoMidnightPaybackUnitsQuote: (marketId: string, unitsRaw: string, slippagePercent: Dec.Value, maturity: number, minPaybackRate?: Dec.Value, taker?: string, rateSlippagePercent?: Dec.Value) => Promise<MorphoMidnightPaybackUnitsQuote>;
|
|
@@ -0,0 +1,298 @@
|
|
|
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
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.getMorphoMidnightPaybackUnitsQuote = exports.getMorphoMidnightPaybackQuote = exports.getMorphoMidnightBorrowQuote = exports.getMorphoMidnightMarketBook = exports.scaleMorphoMidnightDebtSplit = exports.getMorphoMidnightUserBorrowInfo = exports.midnightSlippageParam = exports.getMorphoMidnightAggregatedPositionData = exports.tenorOfferToApiOffer = exports.tenorOfferFillToApiFill = exports.tenorBookRateToApyPercent = exports.tenorBookKeyFor = exports.MIDNIGHT_MIN_HEALTH_RATIO = exports.getMorphoMidnightBorrowHeadroom = exports.MIDNIGHT_DEFAULT_RATE_SLIPPAGE = exports.midnightTimeToMaturityDays = exports.midnightPriceFromApy = exports.midnightBoundPrice = exports.midnightBookBestFirst = exports.midnightApyFromPrice = exports.buildMidnightParsedBook = void 0;
|
|
16
|
+
const decimal_js_1 = __importDefault(require("decimal.js"));
|
|
17
|
+
const tokens_1 = require("@defisaver/tokens");
|
|
18
|
+
const moneymarket_1 = require("../../moneymarket");
|
|
19
|
+
const staking_1 = require("../../staking");
|
|
20
|
+
const common_1 = require("../../types/common");
|
|
21
|
+
const constants_1 = require("../../constants");
|
|
22
|
+
const utils_1 = require("../../services/utils");
|
|
23
|
+
const morphoMidnight_1 = require("../../markets/morphoMidnight");
|
|
24
|
+
const rate_1 = require("./rate");
|
|
25
|
+
const tenor_1 = require("./tenor");
|
|
26
|
+
var rate_2 = require("./rate");
|
|
27
|
+
Object.defineProperty(exports, "buildMidnightParsedBook", { enumerable: true, get: function () { return rate_2.buildMidnightParsedBook; } });
|
|
28
|
+
Object.defineProperty(exports, "midnightApyFromPrice", { enumerable: true, get: function () { return rate_2.midnightApyFromPrice; } });
|
|
29
|
+
Object.defineProperty(exports, "midnightBookBestFirst", { enumerable: true, get: function () { return rate_2.midnightBookBestFirst; } });
|
|
30
|
+
Object.defineProperty(exports, "midnightBoundPrice", { enumerable: true, get: function () { return rate_2.midnightBoundPrice; } });
|
|
31
|
+
Object.defineProperty(exports, "midnightPriceFromApy", { enumerable: true, get: function () { return rate_2.midnightPriceFromApy; } });
|
|
32
|
+
Object.defineProperty(exports, "midnightTimeToMaturityDays", { enumerable: true, get: function () { return rate_2.midnightTimeToMaturityDays; } });
|
|
33
|
+
Object.defineProperty(exports, "MIDNIGHT_DEFAULT_RATE_SLIPPAGE", { enumerable: true, get: function () { return rate_2.MIDNIGHT_DEFAULT_RATE_SLIPPAGE; } });
|
|
34
|
+
var headroom_1 = require("./headroom");
|
|
35
|
+
Object.defineProperty(exports, "getMorphoMidnightBorrowHeadroom", { enumerable: true, get: function () { return headroom_1.getMorphoMidnightBorrowHeadroom; } });
|
|
36
|
+
Object.defineProperty(exports, "MIDNIGHT_MIN_HEALTH_RATIO", { enumerable: true, get: function () { return headroom_1.MIDNIGHT_MIN_HEALTH_RATIO; } });
|
|
37
|
+
var tenor_2 = require("./tenor");
|
|
38
|
+
Object.defineProperty(exports, "tenorBookKeyFor", { enumerable: true, get: function () { return tenor_2.tenorBookKeyFor; } });
|
|
39
|
+
Object.defineProperty(exports, "tenorBookRateToApyPercent", { enumerable: true, get: function () { return tenor_2.tenorBookRateToApyPercent; } });
|
|
40
|
+
Object.defineProperty(exports, "tenorOfferFillToApiFill", { enumerable: true, get: function () { return tenor_2.tenorOfferFillToApiFill; } });
|
|
41
|
+
Object.defineProperty(exports, "tenorOfferToApiOffer", { enumerable: true, get: function () { return tenor_2.tenorOfferToApiOffer; } });
|
|
42
|
+
/**
|
|
43
|
+
* Aggregate a Morpho Midnight position. Midnight markets are multi-collateral, so the borrow limit is
|
|
44
|
+
* the sum of each collateral's USD value times its own lltv (Aave-v4 style), rather than a single pair.
|
|
45
|
+
*
|
|
46
|
+
* Note on amounts: `borrowedUsd` is derived from the position's `debt`, which is the face value owed at
|
|
47
|
+
* maturity (principal + fixed interest). Health is therefore measured against the full maturity debt,
|
|
48
|
+
* matching how MidnightView computes `ratio`. Fixed-rate APY is not derived on-chain in MVP, so
|
|
49
|
+
* `netApy` reflects the `'0'` rates in `assetsData` (see the module getter).
|
|
50
|
+
*/
|
|
51
|
+
const getMorphoMidnightAggregatedPositionData = ({ usedAssets, assetsData, marketInfo, }) => {
|
|
52
|
+
var _a;
|
|
53
|
+
const payload = {};
|
|
54
|
+
payload.suppliedUsd = (0, moneymarket_1.getAssetsTotal)(usedAssets, ({ isSupplied }) => isSupplied, ({ suppliedUsd }) => suppliedUsd);
|
|
55
|
+
payload.suppliedCollateralUsd = (0, moneymarket_1.getAssetsTotal)(usedAssets, ({ isSupplied, collateral }) => isSupplied && collateral, ({ suppliedUsd }) => suppliedUsd);
|
|
56
|
+
payload.borrowedUsd = (0, moneymarket_1.getAssetsTotal)(usedAssets, ({ isBorrowed }) => isBorrowed, ({ borrowedUsd }) => borrowedUsd);
|
|
57
|
+
// borrowLimit = Σ collateralUsd_i * lltv_i (per-collateral lltv carried on assetsData)
|
|
58
|
+
payload.borrowLimitUsd = (0, moneymarket_1.getAssetsTotal)(usedAssets, ({ isSupplied, collateral }) => isSupplied && collateral, ({ symbol, suppliedUsd }) => { var _a; return new decimal_js_1.default(suppliedUsd).mul(((_a = assetsData[symbol]) === null || _a === void 0 ? void 0 : _a.lltv) || 0); });
|
|
59
|
+
payload.liquidationLimitUsd = payload.borrowLimitUsd;
|
|
60
|
+
// Same subtraction every other money market uses, but it does NOT mean the same thing here. Elsewhere
|
|
61
|
+
// `borrowedUsd` is debt at present value, so the remainder is what a borrow would pay out. Midnight
|
|
62
|
+
// records debt at its face value at maturity, so this is face-value headroom: borrowing it would add
|
|
63
|
+
// more debt than the number says, by the market's discount. It also reserves no safety margin, and this
|
|
64
|
+
// aggregate is synchronous so it has no book rate to discount by.
|
|
65
|
+
//
|
|
66
|
+
// DO NOT SURFACE THIS AS "what you can borrow" — use `getMorphoMidnightBorrowHeadroom` instead, which
|
|
67
|
+
// applies both corrections (the margin, and the loan-per-unit price off a book rate) and caps on the
|
|
68
|
+
// depth the book can fill. The discount alone grows past 7% on a one-year market.
|
|
69
|
+
const leftToBorrowUsd = new decimal_js_1.default(payload.borrowLimitUsd).sub(payload.borrowedUsd);
|
|
70
|
+
payload.leftToBorrowUsd = leftToBorrowUsd.lte('0') ? '0' : leftToBorrowUsd.toString();
|
|
71
|
+
const loanTokenPrice = ((_a = assetsData[marketInfo.loanToken]) === null || _a === void 0 ? void 0 : _a.price) || '0';
|
|
72
|
+
payload.leftToBorrow = new decimal_js_1.default(loanTokenPrice).eq(0) ? '0' : new decimal_js_1.default(payload.leftToBorrowUsd).div(loanTokenPrice).toString();
|
|
73
|
+
const { netApy, incentiveUsd, totalInterestUsd } = (0, staking_1.calculateNetApy)({ usedAssets, assetsData: assetsData });
|
|
74
|
+
payload.netApy = netApy;
|
|
75
|
+
payload.incentiveUsd = incentiveUsd;
|
|
76
|
+
payload.totalInterestUsd = totalInterestUsd;
|
|
77
|
+
payload.ltv = new decimal_js_1.default(payload.suppliedCollateralUsd).eq(0) ? '0' : new decimal_js_1.default(payload.borrowedUsd).div(payload.suppliedCollateralUsd).toString();
|
|
78
|
+
payload.ratio = new decimal_js_1.default(payload.borrowedUsd).eq(0) ? '0' : new decimal_js_1.default(payload.suppliedCollateralUsd).div(payload.borrowedUsd).mul(100).toString();
|
|
79
|
+
payload.healthRatio = new decimal_js_1.default(payload.borrowedUsd).eq(0) ? 'Infinity' : new decimal_js_1.default(payload.liquidationLimitUsd).div(payload.borrowedUsd).toDP(4).toString();
|
|
80
|
+
const { leveragedType, leveragedAsset } = (0, moneymarket_1.isLeveragedPos)(usedAssets);
|
|
81
|
+
payload.leveragedType = leveragedType;
|
|
82
|
+
payload.liquidationPrice = '';
|
|
83
|
+
if (leveragedType !== '') {
|
|
84
|
+
payload.leveragedAsset = leveragedAsset;
|
|
85
|
+
let assetPrice = assetsData[leveragedAsset].price;
|
|
86
|
+
if (leveragedType === common_1.LeverageType.VolatilePair) {
|
|
87
|
+
const borrowedAsset = Object.values(usedAssets).find(({ borrowedUsd }) => +borrowedUsd > 0);
|
|
88
|
+
const borrowedAssetPrice = assetsData[borrowedAsset.symbol].price;
|
|
89
|
+
const leveragedAssetPrice = assetsData[leveragedAsset].price;
|
|
90
|
+
const isReverse = new decimal_js_1.default(leveragedAssetPrice).lt(borrowedAssetPrice);
|
|
91
|
+
if (isReverse) {
|
|
92
|
+
payload.leveragedType = common_1.LeverageType.VolatilePairReverse;
|
|
93
|
+
payload.currentVolatilePairRatio = new decimal_js_1.default(borrowedAssetPrice).div(leveragedAssetPrice).toDP(18).toString();
|
|
94
|
+
assetPrice = new decimal_js_1.default(borrowedAssetPrice).div(assetPrice).toString();
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
assetPrice = new decimal_js_1.default(assetPrice).div(borrowedAssetPrice).toString();
|
|
98
|
+
payload.currentVolatilePairRatio = new decimal_js_1.default(leveragedAssetPrice).div(borrowedAssetPrice).toDP(18).toString();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
payload.liquidationPrice = (0, moneymarket_1.calcLeverageLiqPrice)(payload.leveragedType, assetPrice, payload.borrowedUsd, payload.liquidationLimitUsd);
|
|
102
|
+
}
|
|
103
|
+
payload.minCollRatio = new decimal_js_1.default(payload.borrowLimitUsd).eq(0) ? '0' : new decimal_js_1.default(payload.suppliedCollateralUsd).div(payload.borrowLimitUsd).mul(100).toString();
|
|
104
|
+
payload.collLiquidationRatio = new decimal_js_1.default(payload.liquidationLimitUsd).eq(0) ? '0' : new decimal_js_1.default(payload.suppliedCollateralUsd).div(payload.liquidationLimitUsd).mul(100).toString();
|
|
105
|
+
payload.exposure = (0, moneymarket_1.getExposure)(payload.borrowedUsd, payload.suppliedUsd);
|
|
106
|
+
return payload;
|
|
107
|
+
};
|
|
108
|
+
exports.getMorphoMidnightAggregatedPositionData = getMorphoMidnightAggregatedPositionData;
|
|
109
|
+
// ── Off-chain order-book rate helpers ──────────────────────────────────────────────────────────────
|
|
110
|
+
// notion: https://app.notion.com/p/defisaver/Estimate-borrow-rate-and-slippage-before-execution-3a70be682adc80c783c8c11fdb761dd2
|
|
111
|
+
// the borrow rate is not exposed on-chain (MidnightView only stores total debt at maturity in `units`).
|
|
112
|
+
// derive the rate + interest from Morpho's public keyless Midnight API
|
|
113
|
+
// Quote prices are WAD-scaled
|
|
114
|
+
// loan-per-unit ratios (< 1 for a discounted fixed-term borrow); annualizing them yields the borrow APY.
|
|
115
|
+
const MIDNIGHT_API_BASE = 'https://api.morpho.org/v0/midnight';
|
|
116
|
+
// The book endpoint is markedly slower than the rest of the API — `LONGER_TIMEOUT` (5s) aborts it often
|
|
117
|
+
// enough that markets drop out of the list for no reason.
|
|
118
|
+
const MIDNIGHT_BOOK_TIMEOUT = 30000;
|
|
119
|
+
// The quote endpoint's `slippage` query param is validated as a string: 0.1–100, at most one decimal
|
|
120
|
+
// place (`0.50` is rejected even though `0.5` passes). See `midnightSlippageParam`.
|
|
121
|
+
const MIDNIGHT_SLIPPAGE_MIN = 0.1;
|
|
122
|
+
const MIDNIGHT_SLIPPAGE_MAX = 100;
|
|
123
|
+
/**
|
|
124
|
+
* Coerce a slippage into what the quote endpoint accepts: 0.1–100 with at most one decimal place. The
|
|
125
|
+
* validation is lexical, so a computed value (`4.15066671050631467`) is rejected outright — without this
|
|
126
|
+
* the request 400s and the quote looks unavailable.
|
|
127
|
+
*
|
|
128
|
+
* Rounded **down**, since a wider slippage is a looser cap than the caller asked for.
|
|
129
|
+
*/
|
|
130
|
+
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();
|
|
131
|
+
exports.midnightSlippageParam = midnightSlippageParam;
|
|
132
|
+
/**
|
|
133
|
+
* Current borrower rate + debt breakdown from the Midnight positions API. Reconstructing this from the raw
|
|
134
|
+
* `/transactions` fill history only sums `borrow` fills, so it overstates debt for any position with an
|
|
135
|
+
* early exit or partial liquidation (`exit_borrow_primary`, `partial_liquidation`, ... — the fill history
|
|
136
|
+
* has no exhaustive list of debt-reducing event types). `/positions` instead reports the already-netted
|
|
137
|
+
* `debt`, matching `MidnightView.getPositionInfo` exactly, plus `cost_basis` (outstanding principal,
|
|
138
|
+
* WAD-scaled raw base units) and `effective_rate_wad` (borrow APY, WAD-scaled) for the base/interest split.
|
|
139
|
+
* The caller swallows errors — a missing rate must never block position rendering.
|
|
140
|
+
*/
|
|
141
|
+
const getMorphoMidnightUserBorrowInfo = (account, marketId, loanTokenSymbol) => __awaiter(void 0, void 0, void 0, function* () {
|
|
142
|
+
const res = yield fetch(`${MIDNIGHT_API_BASE}/users/${account}/positions`, { signal: AbortSignal.timeout(utils_1.LONGER_TIMEOUT) });
|
|
143
|
+
const json = yield res.json();
|
|
144
|
+
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(); });
|
|
145
|
+
const debtTotal = (0, tokens_1.assetAmountInEth)((position === null || position === void 0 ? void 0 : position.debt) || '0', loanTokenSymbol);
|
|
146
|
+
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
|
|
147
|
+
const debtBase = decimal_js_1.default.min((0, tokens_1.assetAmountInEth)(costBasisRaw.toString(), loanTokenSymbol), debtTotal).toString();
|
|
148
|
+
const debtInterest = decimal_js_1.default.max(new decimal_js_1.default(debtTotal).sub(debtBase), 0).toString();
|
|
149
|
+
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();
|
|
150
|
+
return {
|
|
151
|
+
borrowRate, debtBase, debtInterest, debtTotal,
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
exports.getMorphoMidnightUserBorrowInfo = getMorphoMidnightUserBorrowInfo;
|
|
155
|
+
/**
|
|
156
|
+
* The inverse of the split above: where `getMorphoMidnightUserBorrowInfo` reads a position's principal and
|
|
157
|
+
* interest off the indexer, this carries that same split forward through a payback, from a debt of
|
|
158
|
+
* `borrowedBefore` down to one of `borrowedAfter`. Retiring units retires principal and interest pro rata,
|
|
159
|
+
* so both scale by the same fraction.
|
|
160
|
+
*
|
|
161
|
+
* Deriving the interest as `borrowedAfter − debtBase` instead reads it off two different sources —
|
|
162
|
+
* `borrowed` is the chain's debt, `debtBase` the indexer's principal — so the entire gap between them lands
|
|
163
|
+
* in the interest whenever they disagree, which they do until the indexer catches up with a fresh borrow or
|
|
164
|
+
* payback: behind a borrow it inflates the interest by the debt the indexer has not seen yet, behind a
|
|
165
|
+
* payback it goes negative on a position that still owes. Callers detect that window as
|
|
166
|
+
* `debtBase + debtInterest !== debt`.
|
|
167
|
+
*
|
|
168
|
+
* Pro rata is also what keeps the answer sane when a payback retires debt at a different rate than the one
|
|
169
|
+
* it was opened at. Mirroring a borrow instead — principal growing by the assets received, interest by the
|
|
170
|
+
* rest — hands back a negative interest as soon as the book sells units back cheaper than they were bought.
|
|
171
|
+
*/
|
|
172
|
+
const scaleMorphoMidnightDebtSplit = ({ debtBase, debtInterest }, borrowedBefore, borrowedAfter) => {
|
|
173
|
+
const fractionRemaining = new decimal_js_1.default(borrowedBefore).lte(0)
|
|
174
|
+
? new decimal_js_1.default(0)
|
|
175
|
+
: decimal_js_1.default.max(0, new decimal_js_1.default(borrowedAfter)).div(borrowedBefore);
|
|
176
|
+
return {
|
|
177
|
+
debtBase: new decimal_js_1.default(debtBase).mul(fractionRemaining).toString(),
|
|
178
|
+
debtInterest: new decimal_js_1.default(debtInterest).mul(fractionRemaining).toString(),
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
exports.scaleMorphoMidnightDebtSplit = scaleMorphoMidnightDebtSplit;
|
|
182
|
+
/**
|
|
183
|
+
* One side of a market's resting order book, as rates rather than the API's WAD-scaled loan-per-unit
|
|
184
|
+
* prices. Annualizing each price against time-to-maturity gives the rate a taker filling that offer gets
|
|
185
|
+
* — verified against Morpho's fixed-market UI, where per-offer rates match to the cent.
|
|
186
|
+
*
|
|
187
|
+
* `bids` are the lend offers a borrower fills, so the best of them is the *lowest* rate; `asks` are the
|
|
188
|
+
* sell offers a repayer buys debt units from, where a lower price buys more units, so the best is the
|
|
189
|
+
* *highest* rate. Either way `offers` comes back best-first and `bestRate` is `offers[0].rate`.
|
|
190
|
+
*
|
|
191
|
+
* Returns `null` for an empty side: there is nothing to take, so a market listing should skip the market
|
|
192
|
+
* rather than advertise it at a 0% rate. Throws when the request fails — an error response is rarely
|
|
193
|
+
* JSON, so without the `res.ok` check it parses as an empty book and the market silently vanishes.
|
|
194
|
+
*/
|
|
195
|
+
const getMorphoMidnightMarketBook = (market_1, network_1, ...args_1) => __awaiter(void 0, [market_1, network_1, ...args_1], void 0, function* (market, network, side = 'bids') {
|
|
196
|
+
var _a;
|
|
197
|
+
if ((0, morphoMidnight_1.isTenorMidnightMarket)(market)) {
|
|
198
|
+
return (0, tenor_1.getTenorMarketBook)(market, network, side);
|
|
199
|
+
}
|
|
200
|
+
const loanSymbol = (0, tokens_1.getAssetInfoByAddress)(market.loanToken, network).symbol;
|
|
201
|
+
const res = yield fetch(`${MIDNIGHT_API_BASE}/books/${market.marketId}`, { signal: AbortSignal.timeout(MIDNIGHT_BOOK_TIMEOUT) });
|
|
202
|
+
if (!res.ok)
|
|
203
|
+
throw new Error(`Midnight book request failed for ${market.value} (${res.status})`);
|
|
204
|
+
const json = yield res.json();
|
|
205
|
+
const ttmDays = (0, rate_1.midnightTimeToMaturityDays)(market.maturity);
|
|
206
|
+
const offers = (((_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a[side]) || []).map((offer) => ({
|
|
207
|
+
rate: (0, rate_1.midnightApyFromPrice)(new decimal_js_1.default(offer.price).div(constants_1.WAD), ttmDays),
|
|
208
|
+
liquidity: (0, tokens_1.assetAmountInEth)(offer.assets, loanSymbol),
|
|
209
|
+
}));
|
|
210
|
+
return (0, rate_1.buildMidnightParsedBook)(offers, side);
|
|
211
|
+
});
|
|
212
|
+
exports.getMorphoMidnightMarketBook = getMorphoMidnightMarketBook;
|
|
213
|
+
// The API says why a quote failed — NOT_FOUND (market matured or not open yet), INSUFFICIENT_LIQUIDITY
|
|
214
|
+
// (book can't fill the size), VALIDATION_ERROR (bad param, with the offending field in `details`).
|
|
215
|
+
// Callers surface this to the user, so keep the reason rather than collapsing everything into one string.
|
|
216
|
+
const midnightQuoteError = (error) => {
|
|
217
|
+
const detail = ((error === null || error === void 0 ? void 0 : error.details) || []).map(({ issue }) => issue).filter(Boolean).join('; ');
|
|
218
|
+
const reason = detail || (error === null || error === void 0 ? void 0 : error.message) || (error === null || error === void 0 ? void 0 : error.code);
|
|
219
|
+
return reason ? `Morpho Midnight quote unavailable: ${reason}` : 'Morpho Midnight quote unavailable';
|
|
220
|
+
};
|
|
221
|
+
// The raw quote both sides share: prices descaled from WAD, everything else forwarded verbatim.
|
|
222
|
+
const fetchMorphoMidnightQuote = (marketId, side, size, slippagePercent) => __awaiter(void 0, void 0, void 0, function* () {
|
|
223
|
+
const [sizeParam, sizeValue] = 'assets' in size ? ['assets', size.assets] : ['units', size.units];
|
|
224
|
+
const url = `${MIDNIGHT_API_BASE}/books/${marketId}/${side}/quote?${sizeParam}=${sizeValue}&slippage=${(0, exports.midnightSlippageParam)(slippagePercent)}`;
|
|
225
|
+
const res = yield fetch(url, { signal: AbortSignal.timeout(utils_1.LONGER_TIMEOUT) });
|
|
226
|
+
const json = yield res.json();
|
|
227
|
+
const d = json === null || json === void 0 ? void 0 : json.data;
|
|
228
|
+
if (!(d === null || d === void 0 ? void 0 : d.average_best_price))
|
|
229
|
+
throw new Error(midnightQuoteError(json === null || json === void 0 ? void 0 : json.error));
|
|
230
|
+
return {
|
|
231
|
+
bestPrice: new decimal_js_1.default(d.average_best_price).div(constants_1.WAD).toString(),
|
|
232
|
+
worstPrice: new decimal_js_1.default(d.average_worst_price || 0).div(constants_1.WAD).toString(),
|
|
233
|
+
availableAssets: d.available_assets || '0',
|
|
234
|
+
availableUnits: d.available_units || '0',
|
|
235
|
+
takeableOffers: d.takeable_offers || [],
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
const getMorphoMidnightBorrowQuote = (marketId, assetsRaw, slippagePercent, maturity, maxBorrowRate, taker, rateSlippagePercent) => __awaiter(void 0, void 0, void 0, function* () {
|
|
239
|
+
if ((0, morphoMidnight_1.isTenorMidnightMarket)(marketId)) {
|
|
240
|
+
return (0, tenor_1.getTenorBorrowQuote)(marketId, assetsRaw, slippagePercent, maturity, maxBorrowRate, taker, rateSlippagePercent);
|
|
241
|
+
}
|
|
242
|
+
const quote = yield fetchMorphoMidnightQuote(marketId, 'bids', { assets: assetsRaw }, slippagePercent);
|
|
243
|
+
const { bestPrice } = quote;
|
|
244
|
+
const ttmDays = (0, rate_1.midnightTimeToMaturityDays)(maturity);
|
|
245
|
+
const estBorrowRate = (0, rate_1.midnightApyFromPrice)(bestPrice, ttmDays);
|
|
246
|
+
// Price the cap sits at, and the rate that price represents — one derivation, so `maxRate` and
|
|
247
|
+
// `maxUnits` can never disagree about what the user is protected at.
|
|
248
|
+
const capPrice = (0, rate_1.midnightBoundPrice)(estBorrowRate, ttmDays, 'ceiling', maxBorrowRate, rateSlippagePercent);
|
|
249
|
+
const maxRate = (0, rate_1.midnightApyFromPrice)(capPrice, ttmDays);
|
|
250
|
+
const newUnits = new decimal_js_1.default(bestPrice).lte(0) ? '0' : new decimal_js_1.default(assetsRaw).div(bestPrice).toFixed(0);
|
|
251
|
+
const maxUnits = new decimal_js_1.default(capPrice).lte(0) ? '0' : new decimal_js_1.default(assetsRaw).div(capPrice).toFixed(0);
|
|
252
|
+
return Object.assign(Object.assign({}, quote), { estBorrowRate,
|
|
253
|
+
maxRate,
|
|
254
|
+
newUnits,
|
|
255
|
+
maxUnits });
|
|
256
|
+
});
|
|
257
|
+
exports.getMorphoMidnightBorrowQuote = getMorphoMidnightBorrowQuote;
|
|
258
|
+
const getMorphoMidnightPaybackQuote = (marketId, assetsRaw, slippagePercent, maturity, minPaybackRate, taker, rateSlippagePercent) => __awaiter(void 0, void 0, void 0, function* () {
|
|
259
|
+
if ((0, morphoMidnight_1.isTenorMidnightMarket)(marketId)) {
|
|
260
|
+
return (0, tenor_1.getTenorPaybackQuote)(marketId, assetsRaw, slippagePercent, maturity, minPaybackRate, taker, rateSlippagePercent);
|
|
261
|
+
}
|
|
262
|
+
const quote = yield fetchMorphoMidnightQuote(marketId, 'asks', { assets: assetsRaw }, slippagePercent);
|
|
263
|
+
const { bestPrice } = quote;
|
|
264
|
+
const ttmDays = (0, rate_1.midnightTimeToMaturityDays)(maturity);
|
|
265
|
+
const estPaybackRate = (0, rate_1.midnightApyFromPrice)(bestPrice, ttmDays);
|
|
266
|
+
const capPrice = (0, rate_1.midnightBoundPrice)(estPaybackRate, ttmDays, 'floor', minPaybackRate, rateSlippagePercent);
|
|
267
|
+
const minRate = (0, rate_1.midnightApyFromPrice)(capPrice, ttmDays);
|
|
268
|
+
// Rounded down on both counts: `newUnits` must not overstate the debt the user sees retired, and a
|
|
269
|
+
// `minUnits` rounded up would be a stricter floor than asked for and revert a payback that was fine.
|
|
270
|
+
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);
|
|
271
|
+
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);
|
|
272
|
+
return Object.assign(Object.assign({}, quote), { estPaybackRate,
|
|
273
|
+
minRate,
|
|
274
|
+
newUnits,
|
|
275
|
+
minUnits });
|
|
276
|
+
});
|
|
277
|
+
exports.getMorphoMidnightPaybackQuote = getMorphoMidnightPaybackQuote;
|
|
278
|
+
const getMorphoMidnightPaybackUnitsQuote = (marketId, unitsRaw, slippagePercent, maturity, minPaybackRate, taker, rateSlippagePercent) => __awaiter(void 0, void 0, void 0, function* () {
|
|
279
|
+
if ((0, morphoMidnight_1.isTenorMidnightMarket)(marketId)) {
|
|
280
|
+
return (0, tenor_1.getTenorPaybackUnitsQuote)(marketId, unitsRaw, slippagePercent, maturity, minPaybackRate, taker, rateSlippagePercent);
|
|
281
|
+
}
|
|
282
|
+
const quote = yield fetchMorphoMidnightQuote(marketId, 'asks', { units: unitsRaw }, slippagePercent);
|
|
283
|
+
const { bestPrice } = quote;
|
|
284
|
+
const ttmDays = (0, rate_1.midnightTimeToMaturityDays)(maturity);
|
|
285
|
+
const estPaybackRate = (0, rate_1.midnightApyFromPrice)(bestPrice, ttmDays);
|
|
286
|
+
const capPrice = (0, rate_1.midnightBoundPrice)(estPaybackRate, ttmDays, 'floor', minPaybackRate, rateSlippagePercent);
|
|
287
|
+
const minRate = (0, rate_1.midnightApyFromPrice)(capPrice, ttmDays);
|
|
288
|
+
// Rounded UP on both counts — the mirror of the assets-target quote's ROUND_DOWN. Here the figures are
|
|
289
|
+
// what the taker SPENDS, so a value rounded down understates the cost by a base unit and would leave the
|
|
290
|
+
// buy short of the units it was asked for.
|
|
291
|
+
const newAssets = new decimal_js_1.default(unitsRaw).mul(bestPrice).toFixed(0, decimal_js_1.default.ROUND_UP);
|
|
292
|
+
const maxAssets = new decimal_js_1.default(unitsRaw).mul(capPrice).toFixed(0, decimal_js_1.default.ROUND_UP);
|
|
293
|
+
return Object.assign(Object.assign({}, quote), { estPaybackRate,
|
|
294
|
+
minRate,
|
|
295
|
+
newAssets,
|
|
296
|
+
maxAssets });
|
|
297
|
+
});
|
|
298
|
+
exports.getMorphoMidnightPaybackUnitsQuote = getMorphoMidnightPaybackUnitsQuote;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Dec from 'decimal.js';
|
|
2
|
+
import { MorphoMidnightBookOffer, MorphoMidnightBookSide, MorphoMidnightParsedBook } from '../../types';
|
|
3
|
+
export declare const midnightTimeToMaturityDays: (maturity: number, atSeconds?: number) => number;
|
|
4
|
+
export declare const midnightApyFromPrice: (price: Dec.Value, ttmDays: Dec.Value) => string;
|
|
5
|
+
/**
|
|
6
|
+
* Inverse of `midnightApyFromPrice`: the loan-per-unit price a borrow APY implies,
|
|
7
|
+
* price = (1 + rate)^(−ttmDays / 365).
|
|
8
|
+
*/
|
|
9
|
+
export declare const midnightPriceFromApy: (ratePercent: Dec.Value, ttmDays: Dec.Value) => string;
|
|
10
|
+
export declare const MIDNIGHT_DEFAULT_RATE_SLIPPAGE = 0.1;
|
|
11
|
+
/** Which side of the estimate a guard sits on: a ceiling for borrows, a floor for paybacks. */
|
|
12
|
+
export type MidnightRateBoundKind = 'ceiling' | 'floor';
|
|
13
|
+
export declare const midnightBoundPrice: (estRate: Dec.Value, ttmDays: Dec.Value, kind: MidnightRateBoundKind, boundRate?: Dec.Value, rateSlippagePercent?: Dec.Value) => string;
|
|
14
|
+
export declare const midnightBookBestFirst: (side: MorphoMidnightBookSide) => 1 | -1;
|
|
15
|
+
export declare const buildMidnightParsedBook: (offers: MorphoMidnightBookOffer[], side: MorphoMidnightBookSide) => MorphoMidnightParsedBook | null;
|