@defisaver/positions-sdk 2.1.27 → 2.1.28

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.
Files changed (111) hide show
  1. package/.mocharc.json +4 -4
  2. package/.nvmrc +1 -1
  3. package/README.md +64 -64
  4. package/cjs/helpers/morphoBlueHelpers/index.js +66 -66
  5. package/cjs/savings/makerDSR/options.js +1 -0
  6. package/cjs/savings/morphoVaults/index.js +17 -17
  7. package/cjs/savings/morphoVaults/options.js +13 -0
  8. package/cjs/savings/yearnVaults/options.js +3 -0
  9. package/cjs/types/savings/makerDsr.d.ts +1 -0
  10. package/cjs/types/savings/morphoVaults.d.ts +1 -0
  11. package/cjs/types/savings/yearnVaults.d.ts +1 -0
  12. package/esm/helpers/morphoBlueHelpers/index.js +66 -66
  13. package/esm/savings/makerDSR/options.js +1 -0
  14. package/esm/savings/morphoVaults/index.js +17 -17
  15. package/esm/savings/morphoVaults/options.js +13 -0
  16. package/esm/savings/yearnVaults/options.js +3 -0
  17. package/esm/types/savings/makerDsr.d.ts +1 -0
  18. package/esm/types/savings/morphoVaults.d.ts +1 -0
  19. package/esm/types/savings/yearnVaults.d.ts +1 -0
  20. package/package.json +48 -48
  21. package/src/aaveV2/index.ts +240 -240
  22. package/src/aaveV3/index.ts +614 -614
  23. package/src/aaveV3/merit.ts +97 -97
  24. package/src/aaveV3/merkl.ts +74 -74
  25. package/src/claiming/aaveV3.ts +154 -154
  26. package/src/claiming/compV3.ts +22 -22
  27. package/src/claiming/ethena.ts +61 -61
  28. package/src/claiming/index.ts +12 -12
  29. package/src/claiming/king.ts +66 -66
  30. package/src/claiming/morphoBlue.ts +118 -118
  31. package/src/claiming/spark.ts +225 -225
  32. package/src/compoundV2/index.ts +244 -244
  33. package/src/compoundV3/index.ts +274 -274
  34. package/src/config/contracts.ts +1281 -1281
  35. package/src/constants/index.ts +10 -10
  36. package/src/contracts.ts +142 -142
  37. package/src/curveUsd/index.ts +254 -254
  38. package/src/eulerV2/index.ts +324 -324
  39. package/src/exchange/index.ts +25 -25
  40. package/src/fluid/index.ts +1800 -1800
  41. package/src/helpers/aaveHelpers/index.ts +187 -187
  42. package/src/helpers/compoundHelpers/index.ts +283 -283
  43. package/src/helpers/curveUsdHelpers/index.ts +40 -40
  44. package/src/helpers/eulerHelpers/index.ts +222 -222
  45. package/src/helpers/fluidHelpers/index.ts +326 -326
  46. package/src/helpers/index.ts +10 -10
  47. package/src/helpers/liquityV2Helpers/index.ts +82 -82
  48. package/src/helpers/llamaLendHelpers/index.ts +53 -53
  49. package/src/helpers/makerHelpers/index.ts +52 -52
  50. package/src/helpers/morphoBlueHelpers/index.ts +396 -396
  51. package/src/helpers/sparkHelpers/index.ts +158 -158
  52. package/src/index.ts +49 -49
  53. package/src/liquity/index.ts +159 -159
  54. package/src/liquityV2/index.ts +703 -703
  55. package/src/llamaLend/index.ts +305 -305
  56. package/src/maker/index.ts +223 -223
  57. package/src/markets/aave/index.ts +118 -118
  58. package/src/markets/aave/marketAssets.ts +54 -54
  59. package/src/markets/compound/index.ts +243 -243
  60. package/src/markets/compound/marketsAssets.ts +97 -97
  61. package/src/markets/curveUsd/index.ts +69 -69
  62. package/src/markets/euler/index.ts +26 -26
  63. package/src/markets/fluid/index.ts +2900 -2900
  64. package/src/markets/index.ts +25 -25
  65. package/src/markets/liquityV2/index.ts +102 -102
  66. package/src/markets/llamaLend/contractAddresses.ts +141 -141
  67. package/src/markets/llamaLend/index.ts +235 -235
  68. package/src/markets/morphoBlue/index.ts +971 -971
  69. package/src/markets/spark/index.ts +29 -29
  70. package/src/markets/spark/marketAssets.ts +12 -12
  71. package/src/moneymarket/moneymarketCommonService.ts +85 -85
  72. package/src/morphoBlue/index.ts +274 -274
  73. package/src/portfolio/index.ts +598 -598
  74. package/src/savings/index.ts +46 -46
  75. package/src/savings/makerDsr/index.ts +53 -53
  76. package/src/savings/makerDsr/options.ts +9 -8
  77. package/src/savings/morphoVaults/index.ts +80 -80
  78. package/src/savings/morphoVaults/options.ts +135 -122
  79. package/src/savings/yearnVaults/index.ts +73 -73
  80. package/src/savings/yearnVaults/options.ts +32 -29
  81. package/src/services/priceService.ts +278 -278
  82. package/src/services/utils.ts +115 -115
  83. package/src/services/viem.ts +34 -34
  84. package/src/setup.ts +8 -8
  85. package/src/spark/index.ts +456 -456
  86. package/src/staking/eligibility.ts +53 -53
  87. package/src/staking/index.ts +1 -1
  88. package/src/staking/staking.ts +186 -186
  89. package/src/types/aave.ts +196 -196
  90. package/src/types/claiming.ts +114 -114
  91. package/src/types/common.ts +107 -107
  92. package/src/types/compound.ts +144 -144
  93. package/src/types/curveUsd.ts +123 -123
  94. package/src/types/euler.ts +175 -175
  95. package/src/types/fluid.ts +483 -483
  96. package/src/types/index.ts +14 -14
  97. package/src/types/liquity.ts +30 -30
  98. package/src/types/liquityV2.ts +126 -126
  99. package/src/types/llamaLend.ts +159 -159
  100. package/src/types/maker.ts +63 -63
  101. package/src/types/merit.ts +1 -1
  102. package/src/types/merkl.ts +70 -70
  103. package/src/types/morphoBlue.ts +200 -200
  104. package/src/types/portfolio.ts +60 -60
  105. package/src/types/savings/index.ts +17 -17
  106. package/src/types/savings/makerDsr.ts +13 -12
  107. package/src/types/savings/morphoVaults.ts +25 -24
  108. package/src/types/savings/yearnVaults.ts +14 -13
  109. package/src/types/spark.ts +133 -133
  110. package/src/umbrella/index.ts +69 -69
  111. package/src/umbrella/umbrellaUtils.ts +29 -29
@@ -1,396 +1,396 @@
1
- import Dec from 'decimal.js';
2
- import { assetAmountInWei, getAssetInfo, getAssetInfoByAddress } from '@defisaver/tokens';
3
- import {
4
- aprToApy, calcLeverageLiqPrice, getAssetsTotal, isLeveragedPos,
5
- } from '../../moneymarket';
6
- import { calculateNetApy } from '../../staking';
7
- import {
8
- EthereumProvider, MMAssetsData, MMUsedAssets, NetworkNumber,
9
- } from '../../types/common';
10
- import {
11
- MorphoBlueAggregatedPositionData, MorphoBlueAssetsData, MorphoBlueMarketData, MorphoBlueMarketInfo,
12
- MorphoBluePublicAllocatorItem,
13
- MorphoBlueRealloactionMarketData,
14
- } from '../../types';
15
- import { borrowOperations, SECONDS_PER_YEAR, WAD } from '../../constants';
16
- import { MorphoBlueViewContractViem } from '../../contracts';
17
- import { compareAddresses, DEFAULT_TIMEOUT, wethToEth } from '../../services/utils';
18
- import { getViemProvider } from '../../services/viem';
19
-
20
- export const getMorphoBlueAggregatedPositionData = ({ usedAssets, assetsData, marketInfo }: { usedAssets: MMUsedAssets, assetsData: MorphoBlueAssetsData, marketInfo: MorphoBlueMarketInfo }): MorphoBlueAggregatedPositionData => {
21
- const payload = {} as MorphoBlueAggregatedPositionData;
22
- payload.suppliedUsd = getAssetsTotal(usedAssets, ({ isSupplied }: { isSupplied: boolean }) => isSupplied, ({ suppliedUsd }: { suppliedUsd: string }) => suppliedUsd);
23
- payload.suppliedCollateralUsd = getAssetsTotal(usedAssets, ({ isSupplied, collateral }: { isSupplied: boolean, collateral: string }) => isSupplied && collateral, ({ suppliedUsd }: { suppliedUsd: string }) => suppliedUsd);
24
- payload.borrowedUsd = getAssetsTotal(usedAssets, ({ isBorrowed }: { isBorrowed: boolean }) => isBorrowed, ({ borrowedUsd }: { borrowedUsd: string }) => borrowedUsd);
25
-
26
- const {
27
- lltv, oracle, collateralToken, loanToken,
28
- } = marketInfo;
29
-
30
- payload.borrowLimitUsd = getAssetsTotal(
31
- usedAssets,
32
- ({ isSupplied, collateral }: { isSupplied: boolean, collateral: string }) => isSupplied && collateral,
33
- ({ symbol, suppliedUsd }: { symbol: string, suppliedUsd: string }) => {
34
- const suppliedUsdAmount = suppliedUsd;
35
-
36
- return new Dec(suppliedUsdAmount).mul(lltv);
37
- },
38
- );
39
- payload.liquidationLimitUsd = payload.borrowLimitUsd;
40
- const leftToBorrowUsd = new Dec(payload.borrowLimitUsd).sub(payload.borrowedUsd);
41
- payload.leftToBorrowUsd = leftToBorrowUsd.lte('0') ? '0' : leftToBorrowUsd.toString();
42
-
43
- payload.leftToBorrow = new Dec(usedAssets[collateralToken]?.supplied || 0).mul(oracle).mul(lltv).sub(usedAssets[loanToken]?.borrowed || 0)
44
- .toString();
45
-
46
- const { netApy, incentiveUsd, totalInterestUsd } = calculateNetApy({ usedAssets, assetsData: assetsData as unknown as MMAssetsData });
47
- payload.netApy = netApy;
48
- payload.incentiveUsd = incentiveUsd;
49
- payload.totalInterestUsd = totalInterestUsd;
50
-
51
- payload.ltv = new Dec(payload.borrowedUsd).div(payload.suppliedCollateralUsd).toString();
52
- payload.ltv = new Dec(usedAssets[loanToken]?.borrowed || 0).div(oracle).div(usedAssets[collateralToken]?.supplied || 1).toString(); // default to 1 because can't div 0
53
- payload.ratio = new Dec(usedAssets[collateralToken]?.supplied || 0).mul(oracle).div(usedAssets[loanToken]?.borrowed || 1).mul(100)
54
- .toString();
55
-
56
- const { leveragedType, leveragedAsset } = isLeveragedPos(usedAssets);
57
- payload.leveragedType = leveragedType;
58
- if (leveragedType !== '') {
59
- payload.leveragedAsset = leveragedAsset;
60
- let assetPrice = assetsData[leveragedAsset].price;
61
- if (leveragedType === 'lsd-leverage') {
62
- // Treat ETH like a stablecoin in a long stETH position
63
- payload.leveragedLsdAssetRatio = new Dec(assetsData[leveragedAsset].price).div(assetsData.ETH.price).toDP(18).toString();
64
- assetPrice = new Dec(assetPrice).div(assetsData.ETH.price).toString();
65
- }
66
- payload.liquidationPrice = calcLeverageLiqPrice(leveragedType, assetPrice, payload.borrowedUsd, payload.liquidationLimitUsd);
67
- }
68
- payload.minCollRatio = new Dec(payload.suppliedCollateralUsd).div(payload.borrowLimitUsd).mul(100).toString();
69
- payload.collLiquidationRatio = new Dec(payload.suppliedCollateralUsd).div(payload.liquidationLimitUsd).mul(100).toString();
70
-
71
- return payload;
72
- };
73
-
74
- const compound = (ratePerSeconds: string) => {
75
- const compounding = new Dec(ratePerSeconds).mul(SECONDS_PER_YEAR).toString();
76
- const apyNumber = Math.expm1(new Dec(compounding).div(WAD).toNumber());
77
- return new Dec(apyNumber).mul(WAD).floor().toString();
78
- };
79
-
80
- export const getSupplyRate = (totalSupplyAssets: string, totalBorrowAssets: string, borrowRate: string, fee: string) => {
81
- if (totalBorrowAssets === '0' || totalSupplyAssets === '0') {
82
- return '0';
83
- }
84
- const utillization = new Dec(totalBorrowAssets).mul(WAD).div(totalSupplyAssets).ceil()
85
- .toString();
86
- const supplyRate = new Dec(utillization).mul(borrowRate).div(WAD).ceil()
87
- .toString();
88
- const ratePerSecond = new Dec(supplyRate).mul(new Dec(WAD).minus(fee)).div(WAD).ceil()
89
- .toString();
90
- return new Dec(compound(ratePerSecond)).div(1e18).mul(100).toString();
91
- };
92
-
93
- export const getBorrowRate = (borrowRate: string, totalBorrowShares: string) => {
94
- if (totalBorrowShares === '0') {
95
- return '0';
96
- }
97
- return new Dec(compound(borrowRate)).div(1e18).mul(100).toString();
98
- };
99
-
100
- export const getApyAfterValuesEstimation = async (selectedMarket: MorphoBlueMarketData, actions: { action: string, amount: string, asset: string }[], provider: EthereumProvider, network: NetworkNumber) => {
101
- const client = getViemProvider(provider, network);
102
- const morphoBlueViewContract = MorphoBlueViewContractViem(client, network);
103
- const lltvInWei = assetAmountInWei(selectedMarket.lltv, 'ETH');
104
- const marketData = {
105
- loanToken: selectedMarket.loanToken,
106
- collateralToken: selectedMarket.collateralToken,
107
- oracle: selectedMarket.oracle,
108
- irm: selectedMarket.irm,
109
- lltv: BigInt(lltvInWei),
110
- };
111
-
112
- const params = actions.map(({ action, asset, amount }) => {
113
- const isBorrowOperation = borrowOperations.includes(action);
114
- const amountInWei = assetAmountInWei(amount, asset);
115
- let liquidityAdded;
116
- let liquidityRemoved;
117
- if (isBorrowOperation) {
118
- liquidityAdded = action === 'payback' ? amountInWei : '0';
119
- liquidityRemoved = action === 'borrow' ? amountInWei : '0';
120
- } else {
121
- liquidityAdded = action === 'collateral' ? amountInWei : '0';
122
- liquidityRemoved = action === 'withdraw' ? amountInWei : '0';
123
- }
124
- return {
125
- liquidityAdded: BigInt(liquidityAdded),
126
- liquidityRemoved: BigInt(liquidityRemoved),
127
- isBorrowOperation,
128
- };
129
- });
130
- const data = await morphoBlueViewContract.read.getApyAfterValuesEstimation([
131
- marketData,
132
- params,
133
- ]);
134
- const borrowRate = getBorrowRate(data[0].toString(), data[1].totalBorrowShares.toString());
135
- const supplyRate = getSupplyRate(data[1].totalSupplyAssets.toString(), data[1].totalBorrowAssets.toString(), data[0].toString(), data[1].fee.toString());
136
- return { borrowRate, supplyRate };
137
- };
138
-
139
- const API_URL = 'https://blue-api.morpho.org/graphql';
140
- const MARKET_QUERY = `
141
- query MarketByUniqueKey($uniqueKey: String!, $chainId: Int!) {
142
- marketByUniqueKey(uniqueKey: $uniqueKey, chainId: $chainId) {
143
- reallocatableLiquidityAssets
144
- targetBorrowUtilization
145
- loanAsset {
146
- address
147
- decimals
148
- priceUsd
149
- }
150
- state {
151
- liquidityAssets
152
- borrowAssets
153
- supplyAssets
154
- }
155
- publicAllocatorSharedLiquidity {
156
- assets
157
- vault {
158
- address
159
- name
160
- }
161
- allocationMarket {
162
- uniqueKey
163
- loanAsset {
164
- address
165
- }
166
- collateralAsset {
167
- address
168
- }
169
- irmAddress
170
- oracle {
171
- address
172
- }
173
- lltv
174
- }
175
- }
176
- loanAsset {
177
- address
178
- }
179
- collateralAsset {
180
- address
181
- }
182
- oracle {
183
- address
184
- }
185
- irmAddress
186
- lltv
187
- }
188
- }
189
- `;
190
-
191
- const REWARDS_QUERY = `
192
- query MarketByUniqueKey($uniqueKey: String!, $chainId: Int!) {
193
- marketByUniqueKey(uniqueKey: $uniqueKey, chainId: $chainId) {
194
- uniqueKey
195
- state {
196
- rewards {
197
- amountPerSuppliedToken
198
- supplyApr
199
- amountPerBorrowedToken
200
- borrowApr
201
- asset {
202
- address
203
- }
204
- }
205
- }
206
- }
207
- }
208
- `;
209
-
210
- /**
211
- * Get reallocatable liquidity to a given market and target borrow utilization
212
- * @param marketId - Unique key of the market liquidity is reallocated to
213
- * @param network - The network number
214
- * @returns The reallocatable liquidity and target borrow utilization
215
- */
216
- export const getReallocatableLiquidity = async (marketId: string, network: NetworkNumber = NetworkNumber.Eth): Promise<{ reallocatableLiquidity: string, targetBorrowUtilization: string }> => {
217
- try {
218
- const response = await fetch(API_URL, {
219
- method: 'POST',
220
- headers: { 'Content-Type': 'application/json' },
221
- body: JSON.stringify({
222
- query: MARKET_QUERY,
223
- variables: { uniqueKey: marketId, chainId: network },
224
- }),
225
- signal: AbortSignal.timeout(DEFAULT_TIMEOUT),
226
- });
227
-
228
- const data: { data: { marketByUniqueKey: MorphoBlueRealloactionMarketData } } = await response.json();
229
- const marketData: MorphoBlueRealloactionMarketData = data?.data?.marketByUniqueKey;
230
-
231
- if (!marketData) throw new Error('Market data not found');
232
-
233
- return {
234
- reallocatableLiquidity: marketData.reallocatableLiquidityAssets,
235
- targetBorrowUtilization: marketData.targetBorrowUtilization,
236
- };
237
- } catch (error) {
238
- console.error('External API Failure: Morpho blue reallocatable liquidity', error);
239
- throw new Error('Failed to fetch reallocatable liquidity');
240
- }
241
- };
242
-
243
- /**
244
- * Get liquidity to allocate for a given amount to borrow.
245
- * First, the function will try to calculate the amount of liquidity to allocate to be able to
246
- * hit the target utilization. If it is not possible to allocate enough liquidity to hit the
247
- * target utilization, the function will allocate the amount of liquidity needed to be able to
248
- * borrow the selected amount.
249
- * @param amountToBorrow - The amount to borrow
250
- * @param totalBorrow - The total amount borrowed from market
251
- * @param totalSupply - The total amount supplied to market
252
- * @param targetBorrowUtilization - The target borrow utilization of market
253
- * @param reallocatableLiquidityAssets - The amount of liquidity that can be reallocated from other markets
254
- * @returns The amount of liquidity to allocate
255
- */
256
- export const getLiquidityToAllocate = (amountToBorrow: string, totalBorrow: string, totalSupply: string, targetBorrowUtilization: string, reallocatableLiquidityAssets: string) => {
257
- const newTotalBorrowAssets = new Dec(totalBorrow).add(amountToBorrow).toString();
258
- const leftToBorrow = new Dec(totalSupply).sub(totalBorrow).toString();
259
- let liquidityToAllocate = new Dec(newTotalBorrowAssets).div(targetBorrowUtilization).mul(1e18).sub(totalSupply)
260
- .toFixed(0)
261
- .toString();
262
-
263
- if (new Dec(reallocatableLiquidityAssets).lt(liquidityToAllocate) || new Dec(liquidityToAllocate).lt('0')) {
264
- liquidityToAllocate = new Dec(amountToBorrow).lt(leftToBorrow) ? '0' : new Dec(amountToBorrow).sub(leftToBorrow).toString();
265
- if (new Dec(reallocatableLiquidityAssets).lt(liquidityToAllocate)) throw new Error('Not enough liquidity available to allocate');
266
- }
267
-
268
- return liquidityToAllocate;
269
- };
270
-
271
- /**
272
- * Get the vaults and withdrawals needed to reallocate liquidity for a given amount to borrow.
273
- * Amount to be reallocated is calculated in `getLiquidityToAllocate`
274
- * @param market - The market data
275
- * @param assetsData - The assets data
276
- * @param amountToBorrow - Amount being borrowed (not the amount being reallocated)
277
- * @param network - The network number
278
- * @returns The vaults and withdrawals needed to reallocate liquidity
279
- */
280
- export const getReallocation = async (market: MorphoBlueMarketData, assetsData: MorphoBlueAssetsData, amountToBorrow: string, network: NetworkNumber = NetworkNumber.Eth): Promise<{ vaults: string[], withdrawals: (string | string[])[][][] }> => {
281
- try {
282
- const { marketId, loanToken } = market;
283
- const response = await fetch(API_URL, {
284
- method: 'POST',
285
- headers: { 'Content-Type': 'application/json' },
286
- body: JSON.stringify({
287
- query: MARKET_QUERY,
288
- variables: { uniqueKey: marketId, chainId: network },
289
- }),
290
- signal: AbortSignal.timeout(DEFAULT_TIMEOUT),
291
- });
292
-
293
- const data: { data: { marketByUniqueKey: MorphoBlueRealloactionMarketData } } = await response.json();
294
- const marketData: MorphoBlueRealloactionMarketData = data?.data?.marketByUniqueKey;
295
-
296
- if (!marketData) throw new Error('Market data not found');
297
-
298
- const loanAssetInfo = getAssetInfoByAddress(loanToken, network);
299
- const { totalBorrow, totalSupply } = assetsData[loanAssetInfo.symbol] || { totalBorrow: '0', totalSupply: '0' };
300
- const totalBorrowWei = assetAmountInWei(totalBorrow!, loanAssetInfo.symbol);
301
- const totalSupplyWei = assetAmountInWei(totalSupply!, loanAssetInfo.symbol);
302
-
303
- const newTotalBorrowAssets = new Dec(totalBorrowWei).add(amountToBorrow).toString();
304
-
305
- const newUtil = new Dec(newTotalBorrowAssets).div(totalSupplyWei).toString();
306
- const newUtilScaled = new Dec(newUtil).mul(1e18).toString();
307
-
308
- if (new Dec(newUtilScaled).lt(marketData.targetBorrowUtilization)) return { vaults: [], withdrawals: [] };
309
-
310
- const liquidityToAllocate = getLiquidityToAllocate(amountToBorrow, totalBorrowWei, totalSupplyWei, marketData.targetBorrowUtilization, marketData.reallocatableLiquidityAssets);
311
-
312
- const vaultTotalAssets = marketData.publicAllocatorSharedLiquidity.reduce(
313
- (acc: Record<string, string>, item: MorphoBluePublicAllocatorItem) => {
314
- const vaultAddress = item.vault.address;
315
- acc[vaultAddress] = new Dec(acc[vaultAddress] || '0').add(item.assets).toString();
316
- return acc;
317
- },
318
- {},
319
- );
320
-
321
- const sortedVaults = Object.entries(vaultTotalAssets).sort(
322
- ([, a]: [string, string], [, b]: [string, string]) => new Dec(b || '0').sub(a || '0').toNumber(),
323
- );
324
-
325
- const withdrawalsPerVault: Record<string, [string[], string, string][]> = {};
326
- let totalReallocated = '0';
327
- for (const [vaultAddress] of sortedVaults) {
328
- if (new Dec(totalReallocated).gte(liquidityToAllocate)) break;
329
-
330
- const vaultAllocations = marketData.publicAllocatorSharedLiquidity.filter(
331
- (item: MorphoBluePublicAllocatorItem) => compareAddresses(item.vault.address, vaultAddress),
332
- );
333
- for (const item of vaultAllocations) {
334
- if (new Dec(totalReallocated).gte(liquidityToAllocate)) break;
335
- const itemAmount = item.assets;
336
- const leftToAllocate = new Dec(liquidityToAllocate).sub(totalReallocated).toString();
337
- const amountToTake = new Dec(itemAmount).lt(leftToAllocate) ? itemAmount : leftToAllocate;
338
- totalReallocated = new Dec(totalReallocated).add(amountToTake).toString();
339
- const withdrawal: [string[], string, string] = [
340
- [
341
- item.allocationMarket.loanAsset.address,
342
- item.allocationMarket.collateralAsset?.address,
343
- item.allocationMarket.oracle?.address,
344
- item.allocationMarket.irmAddress,
345
- item.allocationMarket.lltv,
346
- ],
347
- amountToTake.toString(),
348
- item.allocationMarket.uniqueKey,
349
- ];
350
- if (!withdrawalsPerVault[vaultAddress]) {
351
- withdrawalsPerVault[vaultAddress] = [];
352
- }
353
- withdrawalsPerVault[vaultAddress].push(withdrawal);
354
- }
355
- }
356
-
357
- const vaults = Object.keys(withdrawalsPerVault);
358
- const withdrawals = vaults.map(
359
- (vaultAddress) => withdrawalsPerVault[vaultAddress].sort(
360
- (a, b) => a[2].localeCompare(b[2]),
361
- ).map(w => [w[0], w[1]]),
362
- );
363
- return {
364
- vaults,
365
- withdrawals,
366
- };
367
- } catch (error) {
368
- console.error('External API Failure: Morpho blue reallocation', error);
369
- throw new Error('Failed to fetch reallocation data');
370
- }
371
- };
372
-
373
- export const getRewardsForMarket = async (marketId: string, network: NetworkNumber = NetworkNumber.Eth) => {
374
- const response = await fetch(API_URL, {
375
- method: 'POST',
376
- headers: { 'Content-Type': 'application/json' },
377
- body: JSON.stringify({
378
- query: REWARDS_QUERY,
379
- variables: { uniqueKey: marketId, chainId: network },
380
- }),
381
- });
382
-
383
- const data = await response.json();
384
- const marketData = data?.data?.marketByUniqueKey;
385
- if (!marketData) throw new Error('Market data not found');
386
- const morphoAssetInfo = getAssetInfo('MORPHO');
387
- const { supplyApr, borrowApr } = marketData.state.rewards.find((reward: any) => compareAddresses(reward.asset.address, morphoAssetInfo.addresses[network])) || { supplyApr: '0', borrowApr: '0' };
388
- const supplyAprPercent = new Dec(supplyApr).mul(100).toString();
389
- const borrowAprPercent = new Dec(borrowApr).mul(100).toString();
390
- return { supplyApy: aprToApy(supplyAprPercent), borrowApy: aprToApy(borrowAprPercent) };
391
- };
392
-
393
- export const getMorphoUnderlyingSymbol = (_symbol: string) => {
394
- if (_symbol === 'MORPHO Legacy') return 'MORPHO';
395
- return wethToEth(_symbol);
396
- };
1
+ import Dec from 'decimal.js';
2
+ import { assetAmountInWei, getAssetInfo, getAssetInfoByAddress } from '@defisaver/tokens';
3
+ import {
4
+ aprToApy, calcLeverageLiqPrice, getAssetsTotal, isLeveragedPos,
5
+ } from '../../moneymarket';
6
+ import { calculateNetApy } from '../../staking';
7
+ import {
8
+ EthereumProvider, MMAssetsData, MMUsedAssets, NetworkNumber,
9
+ } from '../../types/common';
10
+ import {
11
+ MorphoBlueAggregatedPositionData, MorphoBlueAssetsData, MorphoBlueMarketData, MorphoBlueMarketInfo,
12
+ MorphoBluePublicAllocatorItem,
13
+ MorphoBlueRealloactionMarketData,
14
+ } from '../../types';
15
+ import { borrowOperations, SECONDS_PER_YEAR, WAD } from '../../constants';
16
+ import { MorphoBlueViewContractViem } from '../../contracts';
17
+ import { compareAddresses, DEFAULT_TIMEOUT, wethToEth } from '../../services/utils';
18
+ import { getViemProvider } from '../../services/viem';
19
+
20
+ export const getMorphoBlueAggregatedPositionData = ({ usedAssets, assetsData, marketInfo }: { usedAssets: MMUsedAssets, assetsData: MorphoBlueAssetsData, marketInfo: MorphoBlueMarketInfo }): MorphoBlueAggregatedPositionData => {
21
+ const payload = {} as MorphoBlueAggregatedPositionData;
22
+ payload.suppliedUsd = getAssetsTotal(usedAssets, ({ isSupplied }: { isSupplied: boolean }) => isSupplied, ({ suppliedUsd }: { suppliedUsd: string }) => suppliedUsd);
23
+ payload.suppliedCollateralUsd = getAssetsTotal(usedAssets, ({ isSupplied, collateral }: { isSupplied: boolean, collateral: string }) => isSupplied && collateral, ({ suppliedUsd }: { suppliedUsd: string }) => suppliedUsd);
24
+ payload.borrowedUsd = getAssetsTotal(usedAssets, ({ isBorrowed }: { isBorrowed: boolean }) => isBorrowed, ({ borrowedUsd }: { borrowedUsd: string }) => borrowedUsd);
25
+
26
+ const {
27
+ lltv, oracle, collateralToken, loanToken,
28
+ } = marketInfo;
29
+
30
+ payload.borrowLimitUsd = getAssetsTotal(
31
+ usedAssets,
32
+ ({ isSupplied, collateral }: { isSupplied: boolean, collateral: string }) => isSupplied && collateral,
33
+ ({ symbol, suppliedUsd }: { symbol: string, suppliedUsd: string }) => {
34
+ const suppliedUsdAmount = suppliedUsd;
35
+
36
+ return new Dec(suppliedUsdAmount).mul(lltv);
37
+ },
38
+ );
39
+ payload.liquidationLimitUsd = payload.borrowLimitUsd;
40
+ const leftToBorrowUsd = new Dec(payload.borrowLimitUsd).sub(payload.borrowedUsd);
41
+ payload.leftToBorrowUsd = leftToBorrowUsd.lte('0') ? '0' : leftToBorrowUsd.toString();
42
+
43
+ payload.leftToBorrow = new Dec(usedAssets[collateralToken]?.supplied || 0).mul(oracle).mul(lltv).sub(usedAssets[loanToken]?.borrowed || 0)
44
+ .toString();
45
+
46
+ const { netApy, incentiveUsd, totalInterestUsd } = calculateNetApy({ usedAssets, assetsData: assetsData as unknown as MMAssetsData });
47
+ payload.netApy = netApy;
48
+ payload.incentiveUsd = incentiveUsd;
49
+ payload.totalInterestUsd = totalInterestUsd;
50
+
51
+ payload.ltv = new Dec(payload.borrowedUsd).div(payload.suppliedCollateralUsd).toString();
52
+ payload.ltv = new Dec(usedAssets[loanToken]?.borrowed || 0).div(oracle).div(usedAssets[collateralToken]?.supplied || 1).toString(); // default to 1 because can't div 0
53
+ payload.ratio = new Dec(usedAssets[collateralToken]?.supplied || 0).mul(oracle).div(usedAssets[loanToken]?.borrowed || 1).mul(100)
54
+ .toString();
55
+
56
+ const { leveragedType, leveragedAsset } = isLeveragedPos(usedAssets);
57
+ payload.leveragedType = leveragedType;
58
+ if (leveragedType !== '') {
59
+ payload.leveragedAsset = leveragedAsset;
60
+ let assetPrice = assetsData[leveragedAsset].price;
61
+ if (leveragedType === 'lsd-leverage') {
62
+ // Treat ETH like a stablecoin in a long stETH position
63
+ payload.leveragedLsdAssetRatio = new Dec(assetsData[leveragedAsset].price).div(assetsData.ETH.price).toDP(18).toString();
64
+ assetPrice = new Dec(assetPrice).div(assetsData.ETH.price).toString();
65
+ }
66
+ payload.liquidationPrice = calcLeverageLiqPrice(leveragedType, assetPrice, payload.borrowedUsd, payload.liquidationLimitUsd);
67
+ }
68
+ payload.minCollRatio = new Dec(payload.suppliedCollateralUsd).div(payload.borrowLimitUsd).mul(100).toString();
69
+ payload.collLiquidationRatio = new Dec(payload.suppliedCollateralUsd).div(payload.liquidationLimitUsd).mul(100).toString();
70
+
71
+ return payload;
72
+ };
73
+
74
+ const compound = (ratePerSeconds: string) => {
75
+ const compounding = new Dec(ratePerSeconds).mul(SECONDS_PER_YEAR).toString();
76
+ const apyNumber = Math.expm1(new Dec(compounding).div(WAD).toNumber());
77
+ return new Dec(apyNumber).mul(WAD).floor().toString();
78
+ };
79
+
80
+ export const getSupplyRate = (totalSupplyAssets: string, totalBorrowAssets: string, borrowRate: string, fee: string) => {
81
+ if (totalBorrowAssets === '0' || totalSupplyAssets === '0') {
82
+ return '0';
83
+ }
84
+ const utillization = new Dec(totalBorrowAssets).mul(WAD).div(totalSupplyAssets).ceil()
85
+ .toString();
86
+ const supplyRate = new Dec(utillization).mul(borrowRate).div(WAD).ceil()
87
+ .toString();
88
+ const ratePerSecond = new Dec(supplyRate).mul(new Dec(WAD).minus(fee)).div(WAD).ceil()
89
+ .toString();
90
+ return new Dec(compound(ratePerSecond)).div(1e18).mul(100).toString();
91
+ };
92
+
93
+ export const getBorrowRate = (borrowRate: string, totalBorrowShares: string) => {
94
+ if (totalBorrowShares === '0') {
95
+ return '0';
96
+ }
97
+ return new Dec(compound(borrowRate)).div(1e18).mul(100).toString();
98
+ };
99
+
100
+ export const getApyAfterValuesEstimation = async (selectedMarket: MorphoBlueMarketData, actions: { action: string, amount: string, asset: string }[], provider: EthereumProvider, network: NetworkNumber) => {
101
+ const client = getViemProvider(provider, network);
102
+ const morphoBlueViewContract = MorphoBlueViewContractViem(client, network);
103
+ const lltvInWei = assetAmountInWei(selectedMarket.lltv, 'ETH');
104
+ const marketData = {
105
+ loanToken: selectedMarket.loanToken,
106
+ collateralToken: selectedMarket.collateralToken,
107
+ oracle: selectedMarket.oracle,
108
+ irm: selectedMarket.irm,
109
+ lltv: BigInt(lltvInWei),
110
+ };
111
+
112
+ const params = actions.map(({ action, asset, amount }) => {
113
+ const isBorrowOperation = borrowOperations.includes(action);
114
+ const amountInWei = assetAmountInWei(amount, asset);
115
+ let liquidityAdded;
116
+ let liquidityRemoved;
117
+ if (isBorrowOperation) {
118
+ liquidityAdded = action === 'payback' ? amountInWei : '0';
119
+ liquidityRemoved = action === 'borrow' ? amountInWei : '0';
120
+ } else {
121
+ liquidityAdded = action === 'collateral' ? amountInWei : '0';
122
+ liquidityRemoved = action === 'withdraw' ? amountInWei : '0';
123
+ }
124
+ return {
125
+ liquidityAdded: BigInt(liquidityAdded),
126
+ liquidityRemoved: BigInt(liquidityRemoved),
127
+ isBorrowOperation,
128
+ };
129
+ });
130
+ const data = await morphoBlueViewContract.read.getApyAfterValuesEstimation([
131
+ marketData,
132
+ params,
133
+ ]);
134
+ const borrowRate = getBorrowRate(data[0].toString(), data[1].totalBorrowShares.toString());
135
+ const supplyRate = getSupplyRate(data[1].totalSupplyAssets.toString(), data[1].totalBorrowAssets.toString(), data[0].toString(), data[1].fee.toString());
136
+ return { borrowRate, supplyRate };
137
+ };
138
+
139
+ const API_URL = 'https://blue-api.morpho.org/graphql';
140
+ const MARKET_QUERY = `
141
+ query MarketByUniqueKey($uniqueKey: String!, $chainId: Int!) {
142
+ marketByUniqueKey(uniqueKey: $uniqueKey, chainId: $chainId) {
143
+ reallocatableLiquidityAssets
144
+ targetBorrowUtilization
145
+ loanAsset {
146
+ address
147
+ decimals
148
+ priceUsd
149
+ }
150
+ state {
151
+ liquidityAssets
152
+ borrowAssets
153
+ supplyAssets
154
+ }
155
+ publicAllocatorSharedLiquidity {
156
+ assets
157
+ vault {
158
+ address
159
+ name
160
+ }
161
+ allocationMarket {
162
+ uniqueKey
163
+ loanAsset {
164
+ address
165
+ }
166
+ collateralAsset {
167
+ address
168
+ }
169
+ irmAddress
170
+ oracle {
171
+ address
172
+ }
173
+ lltv
174
+ }
175
+ }
176
+ loanAsset {
177
+ address
178
+ }
179
+ collateralAsset {
180
+ address
181
+ }
182
+ oracle {
183
+ address
184
+ }
185
+ irmAddress
186
+ lltv
187
+ }
188
+ }
189
+ `;
190
+
191
+ const REWARDS_QUERY = `
192
+ query MarketByUniqueKey($uniqueKey: String!, $chainId: Int!) {
193
+ marketByUniqueKey(uniqueKey: $uniqueKey, chainId: $chainId) {
194
+ uniqueKey
195
+ state {
196
+ rewards {
197
+ amountPerSuppliedToken
198
+ supplyApr
199
+ amountPerBorrowedToken
200
+ borrowApr
201
+ asset {
202
+ address
203
+ }
204
+ }
205
+ }
206
+ }
207
+ }
208
+ `;
209
+
210
+ /**
211
+ * Get reallocatable liquidity to a given market and target borrow utilization
212
+ * @param marketId - Unique key of the market liquidity is reallocated to
213
+ * @param network - The network number
214
+ * @returns The reallocatable liquidity and target borrow utilization
215
+ */
216
+ export const getReallocatableLiquidity = async (marketId: string, network: NetworkNumber = NetworkNumber.Eth): Promise<{ reallocatableLiquidity: string, targetBorrowUtilization: string }> => {
217
+ try {
218
+ const response = await fetch(API_URL, {
219
+ method: 'POST',
220
+ headers: { 'Content-Type': 'application/json' },
221
+ body: JSON.stringify({
222
+ query: MARKET_QUERY,
223
+ variables: { uniqueKey: marketId, chainId: network },
224
+ }),
225
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT),
226
+ });
227
+
228
+ const data: { data: { marketByUniqueKey: MorphoBlueRealloactionMarketData } } = await response.json();
229
+ const marketData: MorphoBlueRealloactionMarketData = data?.data?.marketByUniqueKey;
230
+
231
+ if (!marketData) throw new Error('Market data not found');
232
+
233
+ return {
234
+ reallocatableLiquidity: marketData.reallocatableLiquidityAssets,
235
+ targetBorrowUtilization: marketData.targetBorrowUtilization,
236
+ };
237
+ } catch (error) {
238
+ console.error('External API Failure: Morpho blue reallocatable liquidity', error);
239
+ throw new Error('Failed to fetch reallocatable liquidity');
240
+ }
241
+ };
242
+
243
+ /**
244
+ * Get liquidity to allocate for a given amount to borrow.
245
+ * First, the function will try to calculate the amount of liquidity to allocate to be able to
246
+ * hit the target utilization. If it is not possible to allocate enough liquidity to hit the
247
+ * target utilization, the function will allocate the amount of liquidity needed to be able to
248
+ * borrow the selected amount.
249
+ * @param amountToBorrow - The amount to borrow
250
+ * @param totalBorrow - The total amount borrowed from market
251
+ * @param totalSupply - The total amount supplied to market
252
+ * @param targetBorrowUtilization - The target borrow utilization of market
253
+ * @param reallocatableLiquidityAssets - The amount of liquidity that can be reallocated from other markets
254
+ * @returns The amount of liquidity to allocate
255
+ */
256
+ export const getLiquidityToAllocate = (amountToBorrow: string, totalBorrow: string, totalSupply: string, targetBorrowUtilization: string, reallocatableLiquidityAssets: string) => {
257
+ const newTotalBorrowAssets = new Dec(totalBorrow).add(amountToBorrow).toString();
258
+ const leftToBorrow = new Dec(totalSupply).sub(totalBorrow).toString();
259
+ let liquidityToAllocate = new Dec(newTotalBorrowAssets).div(targetBorrowUtilization).mul(1e18).sub(totalSupply)
260
+ .toFixed(0)
261
+ .toString();
262
+
263
+ if (new Dec(reallocatableLiquidityAssets).lt(liquidityToAllocate) || new Dec(liquidityToAllocate).lt('0')) {
264
+ liquidityToAllocate = new Dec(amountToBorrow).lt(leftToBorrow) ? '0' : new Dec(amountToBorrow).sub(leftToBorrow).toString();
265
+ if (new Dec(reallocatableLiquidityAssets).lt(liquidityToAllocate)) throw new Error('Not enough liquidity available to allocate');
266
+ }
267
+
268
+ return liquidityToAllocate;
269
+ };
270
+
271
+ /**
272
+ * Get the vaults and withdrawals needed to reallocate liquidity for a given amount to borrow.
273
+ * Amount to be reallocated is calculated in `getLiquidityToAllocate`
274
+ * @param market - The market data
275
+ * @param assetsData - The assets data
276
+ * @param amountToBorrow - Amount being borrowed (not the amount being reallocated)
277
+ * @param network - The network number
278
+ * @returns The vaults and withdrawals needed to reallocate liquidity
279
+ */
280
+ export const getReallocation = async (market: MorphoBlueMarketData, assetsData: MorphoBlueAssetsData, amountToBorrow: string, network: NetworkNumber = NetworkNumber.Eth): Promise<{ vaults: string[], withdrawals: (string | string[])[][][] }> => {
281
+ try {
282
+ const { marketId, loanToken } = market;
283
+ const response = await fetch(API_URL, {
284
+ method: 'POST',
285
+ headers: { 'Content-Type': 'application/json' },
286
+ body: JSON.stringify({
287
+ query: MARKET_QUERY,
288
+ variables: { uniqueKey: marketId, chainId: network },
289
+ }),
290
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT),
291
+ });
292
+
293
+ const data: { data: { marketByUniqueKey: MorphoBlueRealloactionMarketData } } = await response.json();
294
+ const marketData: MorphoBlueRealloactionMarketData = data?.data?.marketByUniqueKey;
295
+
296
+ if (!marketData) throw new Error('Market data not found');
297
+
298
+ const loanAssetInfo = getAssetInfoByAddress(loanToken, network);
299
+ const { totalBorrow, totalSupply } = assetsData[loanAssetInfo.symbol] || { totalBorrow: '0', totalSupply: '0' };
300
+ const totalBorrowWei = assetAmountInWei(totalBorrow!, loanAssetInfo.symbol);
301
+ const totalSupplyWei = assetAmountInWei(totalSupply!, loanAssetInfo.symbol);
302
+
303
+ const newTotalBorrowAssets = new Dec(totalBorrowWei).add(amountToBorrow).toString();
304
+
305
+ const newUtil = new Dec(newTotalBorrowAssets).div(totalSupplyWei).toString();
306
+ const newUtilScaled = new Dec(newUtil).mul(1e18).toString();
307
+
308
+ if (new Dec(newUtilScaled).lt(marketData.targetBorrowUtilization)) return { vaults: [], withdrawals: [] };
309
+
310
+ const liquidityToAllocate = getLiquidityToAllocate(amountToBorrow, totalBorrowWei, totalSupplyWei, marketData.targetBorrowUtilization, marketData.reallocatableLiquidityAssets);
311
+
312
+ const vaultTotalAssets = marketData.publicAllocatorSharedLiquidity.reduce(
313
+ (acc: Record<string, string>, item: MorphoBluePublicAllocatorItem) => {
314
+ const vaultAddress = item.vault.address;
315
+ acc[vaultAddress] = new Dec(acc[vaultAddress] || '0').add(item.assets).toString();
316
+ return acc;
317
+ },
318
+ {},
319
+ );
320
+
321
+ const sortedVaults = Object.entries(vaultTotalAssets).sort(
322
+ ([, a]: [string, string], [, b]: [string, string]) => new Dec(b || '0').sub(a || '0').toNumber(),
323
+ );
324
+
325
+ const withdrawalsPerVault: Record<string, [string[], string, string][]> = {};
326
+ let totalReallocated = '0';
327
+ for (const [vaultAddress] of sortedVaults) {
328
+ if (new Dec(totalReallocated).gte(liquidityToAllocate)) break;
329
+
330
+ const vaultAllocations = marketData.publicAllocatorSharedLiquidity.filter(
331
+ (item: MorphoBluePublicAllocatorItem) => compareAddresses(item.vault.address, vaultAddress),
332
+ );
333
+ for (const item of vaultAllocations) {
334
+ if (new Dec(totalReallocated).gte(liquidityToAllocate)) break;
335
+ const itemAmount = item.assets;
336
+ const leftToAllocate = new Dec(liquidityToAllocate).sub(totalReallocated).toString();
337
+ const amountToTake = new Dec(itemAmount).lt(leftToAllocate) ? itemAmount : leftToAllocate;
338
+ totalReallocated = new Dec(totalReallocated).add(amountToTake).toString();
339
+ const withdrawal: [string[], string, string] = [
340
+ [
341
+ item.allocationMarket.loanAsset.address,
342
+ item.allocationMarket.collateralAsset?.address,
343
+ item.allocationMarket.oracle?.address,
344
+ item.allocationMarket.irmAddress,
345
+ item.allocationMarket.lltv,
346
+ ],
347
+ amountToTake.toString(),
348
+ item.allocationMarket.uniqueKey,
349
+ ];
350
+ if (!withdrawalsPerVault[vaultAddress]) {
351
+ withdrawalsPerVault[vaultAddress] = [];
352
+ }
353
+ withdrawalsPerVault[vaultAddress].push(withdrawal);
354
+ }
355
+ }
356
+
357
+ const vaults = Object.keys(withdrawalsPerVault);
358
+ const withdrawals = vaults.map(
359
+ (vaultAddress) => withdrawalsPerVault[vaultAddress].sort(
360
+ (a, b) => a[2].localeCompare(b[2]),
361
+ ).map(w => [w[0], w[1]]),
362
+ );
363
+ return {
364
+ vaults,
365
+ withdrawals,
366
+ };
367
+ } catch (error) {
368
+ console.error('External API Failure: Morpho blue reallocation', error);
369
+ throw new Error('Failed to fetch reallocation data');
370
+ }
371
+ };
372
+
373
+ export const getRewardsForMarket = async (marketId: string, network: NetworkNumber = NetworkNumber.Eth) => {
374
+ const response = await fetch(API_URL, {
375
+ method: 'POST',
376
+ headers: { 'Content-Type': 'application/json' },
377
+ body: JSON.stringify({
378
+ query: REWARDS_QUERY,
379
+ variables: { uniqueKey: marketId, chainId: network },
380
+ }),
381
+ });
382
+
383
+ const data = await response.json();
384
+ const marketData = data?.data?.marketByUniqueKey;
385
+ if (!marketData) throw new Error('Market data not found');
386
+ const morphoAssetInfo = getAssetInfo('MORPHO');
387
+ const { supplyApr, borrowApr } = marketData.state.rewards.find((reward: any) => compareAddresses(reward.asset.address, morphoAssetInfo.addresses[network])) || { supplyApr: '0', borrowApr: '0' };
388
+ const supplyAprPercent = new Dec(supplyApr).mul(100).toString();
389
+ const borrowAprPercent = new Dec(borrowApr).mul(100).toString();
390
+ return { supplyApy: aprToApy(supplyAprPercent), borrowApy: aprToApy(borrowAprPercent) };
391
+ };
392
+
393
+ export const getMorphoUnderlyingSymbol = (_symbol: string) => {
394
+ if (_symbol === 'MORPHO Legacy') return 'MORPHO';
395
+ return wethToEth(_symbol);
396
+ };