@defisaver/positions-sdk 1.0.10 → 1.0.11-fluid-dev

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