@defisaver/positions-sdk 0.0.198 → 0.0.199-dev-1

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