@defisaver/positions-sdk 0.0.187 → 0.0.188-dev-markets

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