@defisaver/positions-sdk 2.1.30 → 2.1.31-syrup-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 (105) hide show
  1. package/.mocharc.json +4 -4
  2. package/.nvmrc +1 -1
  3. package/CLAUDE.md +32 -0
  4. package/README.md +64 -64
  5. package/cjs/helpers/morphoBlueHelpers/index.js +66 -66
  6. package/cjs/markets/aave/marketAssets.js +2 -2
  7. package/cjs/savings/morphoVaults/index.js +17 -17
  8. package/esm/helpers/morphoBlueHelpers/index.js +66 -66
  9. package/esm/markets/aave/marketAssets.js +2 -2
  10. package/esm/savings/morphoVaults/index.js +17 -17
  11. package/package.json +48 -48
  12. package/src/aaveV2/index.ts +240 -240
  13. package/src/aaveV3/index.ts +614 -614
  14. package/src/aaveV3/merit.ts +97 -97
  15. package/src/aaveV3/merkl.ts +74 -74
  16. package/src/claiming/aaveV3.ts +154 -154
  17. package/src/claiming/compV3.ts +22 -22
  18. package/src/claiming/ethena.ts +61 -61
  19. package/src/claiming/index.ts +12 -12
  20. package/src/claiming/king.ts +66 -66
  21. package/src/claiming/morphoBlue.ts +118 -118
  22. package/src/claiming/spark.ts +225 -225
  23. package/src/compoundV2/index.ts +244 -244
  24. package/src/compoundV3/index.ts +274 -274
  25. package/src/config/contracts.ts +1284 -1284
  26. package/src/constants/index.ts +10 -10
  27. package/src/contracts.ts +160 -160
  28. package/src/curveUsd/index.ts +254 -254
  29. package/src/eulerV2/index.ts +324 -324
  30. package/src/exchange/index.ts +25 -25
  31. package/src/fluid/index.ts +1800 -1800
  32. package/src/helpers/aaveHelpers/index.ts +187 -187
  33. package/src/helpers/compoundHelpers/index.ts +283 -283
  34. package/src/helpers/curveUsdHelpers/index.ts +40 -40
  35. package/src/helpers/eulerHelpers/index.ts +222 -222
  36. package/src/helpers/fluidHelpers/index.ts +326 -326
  37. package/src/helpers/index.ts +10 -10
  38. package/src/helpers/liquityV2Helpers/index.ts +82 -82
  39. package/src/helpers/llamaLendHelpers/index.ts +53 -53
  40. package/src/helpers/makerHelpers/index.ts +52 -52
  41. package/src/helpers/morphoBlueHelpers/index.ts +396 -396
  42. package/src/helpers/sparkHelpers/index.ts +158 -158
  43. package/src/index.ts +49 -49
  44. package/src/liquity/index.ts +159 -159
  45. package/src/liquityV2/index.ts +703 -703
  46. package/src/llamaLend/index.ts +305 -305
  47. package/src/maker/index.ts +223 -223
  48. package/src/markets/aave/index.ts +118 -118
  49. package/src/markets/aave/marketAssets.ts +54 -54
  50. package/src/markets/compound/index.ts +243 -243
  51. package/src/markets/compound/marketsAssets.ts +97 -97
  52. package/src/markets/curveUsd/index.ts +69 -69
  53. package/src/markets/euler/index.ts +26 -26
  54. package/src/markets/fluid/index.ts +2900 -2900
  55. package/src/markets/index.ts +25 -25
  56. package/src/markets/liquityV2/index.ts +102 -102
  57. package/src/markets/llamaLend/contractAddresses.ts +141 -141
  58. package/src/markets/llamaLend/index.ts +235 -235
  59. package/src/markets/morphoBlue/index.ts +971 -971
  60. package/src/markets/spark/index.ts +29 -29
  61. package/src/markets/spark/marketAssets.ts +12 -12
  62. package/src/moneymarket/moneymarketCommonService.ts +85 -85
  63. package/src/morphoBlue/index.ts +274 -274
  64. package/src/portfolio/index.ts +598 -598
  65. package/src/savings/index.ts +55 -55
  66. package/src/savings/makerDsr/index.ts +53 -53
  67. package/src/savings/makerDsr/options.ts +9 -9
  68. package/src/savings/morphoVaults/index.ts +80 -80
  69. package/src/savings/morphoVaults/options.ts +203 -203
  70. package/src/savings/sparkSavingsVaults/index.ts +60 -60
  71. package/src/savings/sparkSavingsVaults/options.ts +35 -35
  72. package/src/savings/yearnVaults/index.ts +73 -73
  73. package/src/savings/yearnVaults/options.ts +32 -32
  74. package/src/services/priceService.ts +278 -278
  75. package/src/services/utils.ts +115 -115
  76. package/src/services/viem.ts +34 -34
  77. package/src/setup.ts +8 -8
  78. package/src/spark/index.ts +456 -456
  79. package/src/staking/eligibility.ts +53 -53
  80. package/src/staking/index.ts +1 -1
  81. package/src/staking/staking.ts +186 -186
  82. package/src/types/aave.ts +196 -196
  83. package/src/types/claiming.ts +114 -114
  84. package/src/types/common.ts +107 -107
  85. package/src/types/compound.ts +144 -144
  86. package/src/types/curveUsd.ts +123 -123
  87. package/src/types/euler.ts +175 -175
  88. package/src/types/fluid.ts +483 -483
  89. package/src/types/index.ts +14 -14
  90. package/src/types/liquity.ts +30 -30
  91. package/src/types/liquityV2.ts +126 -126
  92. package/src/types/llamaLend.ts +159 -159
  93. package/src/types/maker.ts +63 -63
  94. package/src/types/merit.ts +1 -1
  95. package/src/types/merkl.ts +70 -70
  96. package/src/types/morphoBlue.ts +200 -200
  97. package/src/types/portfolio.ts +60 -60
  98. package/src/types/savings/index.ts +19 -19
  99. package/src/types/savings/makerDsr.ts +13 -13
  100. package/src/types/savings/morphoVaults.ts +33 -33
  101. package/src/types/savings/sparkSavingsVaults.ts +15 -15
  102. package/src/types/savings/yearnVaults.ts +14 -14
  103. package/src/types/spark.ts +133 -133
  104. package/src/umbrella/index.ts +69 -69
  105. package/src/umbrella/umbrellaUtils.ts +29 -29
@@ -1,1800 +1,1800 @@
1
- import Dec from 'decimal.js';
2
- import {
3
- assetAmountInEth, AssetData, getAssetInfo, getAssetInfoByAddress,
4
- } from '@defisaver/tokens';
5
- import { Client, PublicClient } from 'viem';
6
- import {
7
- EthAddress, EthereumProvider, IncentiveKind, NetworkNumber,
8
- } from '../types/common';
9
- import {
10
- FluidAggregatedVaultData,
11
- FluidAssetData,
12
- FluidAssetsData,
13
- FluidFTokenDataStructOutput,
14
- FluidMarketData,
15
- FluidMarketInfo,
16
- FluidUsedAsset,
17
- FluidUsedAssets,
18
- FluidUserEarnPositionStructOutput,
19
- FluidUserPositionStructOutputStruct,
20
- FluidVaultData,
21
- FluidVaultDataStructOutputStruct,
22
- FluidVaultType,
23
- InnerFluidMarketData,
24
- } from '../types';
25
- import {
26
- BTCPriceFeedContractViem,
27
- DFSFeedRegistryContractViem,
28
- ETHPriceFeedContractViem,
29
- FeedRegistryContractViem,
30
- FluidViewContractViem,
31
- } from '../contracts';
32
- import {
33
- compareAddresses,
34
- DEFAULT_TIMEOUT,
35
- getEthAmountForDecimals,
36
- getNativeAssetFromWrapped,
37
- isMainnetNetwork,
38
- } from '../services/utils';
39
- import {
40
- getFluidAggregatedData,
41
- mergeAssetData,
42
- mergeUsedAssets,
43
- parseDexBorrowData,
44
- parseDexSupplyData,
45
- } from '../helpers/fluidHelpers';
46
- import { getFluidMarketInfoById, getFluidVersionsDataForNetwork, getFTokenAddress } from '../markets';
47
- import { USD_QUOTE, ZERO_ADDRESS } from '../constants';
48
- import {
49
- getChainlinkAssetAddress,
50
- getSyrupUSDTChainLinkPriceCalls,
51
- getSyrupUSDTPrice,
52
- getWeETHChainLinkPriceCalls,
53
- getWeETHPrice,
54
- getWsrETHChainLinkPriceCalls,
55
- getWsrETHPrice,
56
- getWstETHChainLinkPriceCalls,
57
- getWstETHPrice,
58
- getWstETHPriceFluid,
59
- getWstUSRChainLinkPriceCalls,
60
- getWstUSRPrice,
61
- parseSyrupUSDTPriceCalls,
62
- parseWeETHPriceCalls,
63
- parseWrsETHPriceCalls,
64
- parseWstETHPriceCalls,
65
- parseWstUSRPriceCalls,
66
- } from '../services/priceService';
67
- import { getStakingApy, STAKING_ASSETS } from '../staking';
68
- import { getViemProvider } from '../services/viem';
69
-
70
- export const EMPTY_USED_ASSET = {
71
- isSupplied: false,
72
- isBorrowed: false,
73
- supplied: '0',
74
- suppliedUsd: '0',
75
- borrowed: '0',
76
- borrowedUsd: '0',
77
- symbol: '',
78
- collateral: false,
79
- };
80
-
81
- const parseVaultType = (vaultType: number) => {
82
- switch (vaultType) {
83
- case 10000: return FluidVaultType.T1;
84
- case 20000: return FluidVaultType.T2;
85
- case 30000: return FluidVaultType.T3;
86
- case 40000: return FluidVaultType.T4;
87
- default: return FluidVaultType.Unknown;
88
- }
89
- };
90
-
91
- const getChainLinkPricesForTokens = async (
92
- tokens: string[],
93
- network: NetworkNumber,
94
- client: PublicClient,
95
- ): Promise<{ [key: string]: string }> => {
96
- const isMainnet = isMainnetNetwork(network);
97
-
98
- const noDuplicateTokens = new Array(...new Set(tokens));
99
-
100
- const btcFeedContract = BTCPriceFeedContractViem(client, network);
101
- const ethFeedContract = ETHPriceFeedContractViem(client, network);
102
-
103
- const staticCalls = [
104
- {
105
- address: ethFeedContract.address,
106
- abi: ethFeedContract.abi,
107
- functionName: 'latestAnswer',
108
- args: [],
109
- },
110
- {
111
- address: btcFeedContract.address,
112
- abi: btcFeedContract.abi,
113
- functionName: 'latestAnswer',
114
- args: [],
115
- },
116
- ];
117
-
118
- // @ts-ignore
119
- const _calls = noDuplicateTokens.flatMap((address) => {
120
- const assetInfo = getAssetInfoByAddress(address, network);
121
- const isTokenUSDA = assetInfo.symbol === 'USDA';
122
- if (isTokenUSDA) return;
123
- const chainLinkFeedAddress = getChainlinkAssetAddress(assetInfo.symbol, network);
124
-
125
- if (assetInfo.symbol === 'wstETH') return getWstETHChainLinkPriceCalls(client, network);
126
- if (assetInfo.symbol === 'weETH' && network !== NetworkNumber.Plasma) return getWeETHChainLinkPriceCalls(client, network);
127
- if (assetInfo.symbol === 'wrsETH' && network === NetworkNumber.Plasma) return getWsrETHChainLinkPriceCalls(client, network);
128
- if (assetInfo.symbol === 'syrupUSDT') return getSyrupUSDTChainLinkPriceCalls(client, network);
129
- if (assetInfo.symbol === 'wstUSR') return getWstUSRChainLinkPriceCalls(client, network);
130
-
131
- if (isMainnet) {
132
- const feedRegistryContract = FeedRegistryContractViem(client, NetworkNumber.Eth);
133
- return ({
134
- address: feedRegistryContract.address,
135
- abi: feedRegistryContract.abi,
136
- functionName: 'latestAnswer',
137
- args: [chainLinkFeedAddress, USD_QUOTE],
138
- });
139
- }
140
-
141
- const feedRegistryContract = DFSFeedRegistryContractViem(client, network);
142
- return ({
143
- address: feedRegistryContract.address,
144
- abi: feedRegistryContract.abi,
145
- functionName: 'latestRoundData',
146
- args: [chainLinkFeedAddress, USD_QUOTE],
147
- });
148
- });
149
-
150
- const calls = [...staticCalls, ..._calls].filter((call) => call);
151
- // @ts-ignore
152
- const results = await client.multicall({ contracts: calls });
153
-
154
- const ethPriceChainlink = new Dec(results[0].result as string).div(1e8).toString();
155
- const btcPriceChainlink = new Dec(results[1].result as string).div(1e8).toString();
156
-
157
- let offset = 2; // wstETH and weETH has 3 calls, while others have only 1, so we need to keep track. First 2 are static calls for eth and btc prices
158
- return noDuplicateTokens.reduce((acc, token, i) => {
159
- const assetInfo = getAssetInfoByAddress(token, network);
160
- switch (assetInfo.symbol) {
161
- case 'USDA':
162
- acc[token] = '100000000';
163
- break;
164
-
165
- case 'wstETH': {
166
- const {
167
- ethPrice,
168
- wstETHRate,
169
- } = parseWstETHPriceCalls(
170
- results[i + offset].result!.toString(),
171
- // @ts-ignore
172
- results[i + offset + 1].result[1]!.toString(),
173
- results[i + offset + 2].result!.toString(),
174
- );
175
- offset += 2;
176
- acc[token] = new Dec(ethPrice).mul(wstETHRate).toString();
177
- break;
178
- }
179
-
180
- case 'wrsETH': {
181
- const {
182
- ethPrice,
183
- wrsETHRate,
184
- } = parseWrsETHPriceCalls(
185
- // @ts-ignore
186
- results[i + offset].result[1]!.toString(),
187
- // @ts-ignore
188
- results[i + offset + 1].result[1]!.toString(),
189
- );
190
- offset += 1;
191
- acc[token] = new Dec(ethPrice).mul(wrsETHRate).toString();
192
- break;
193
- }
194
-
195
- case 'syrupUSDT': {
196
- const {
197
- syrupUSDTRate,
198
- USDTRate,
199
- } = parseSyrupUSDTPriceCalls(
200
- // @ts-ignore
201
- results[i + offset].result[1]!.toString(),
202
- // @ts-ignore
203
- results[i + offset + 1].result[1]!.toString(),
204
- );
205
- offset += 1;
206
- acc[token] = new Dec(syrupUSDTRate).mul(USDTRate).toString();
207
- break;
208
- }
209
- case 'wstUSR': {
210
- const {
211
- wstUSRRate,
212
- USRRate,
213
- } = parseWstUSRPriceCalls(
214
- // @ts-ignore
215
- results[i + offset].result[1]!.toString(),
216
- // @ts-ignore
217
- results[i + offset + 1].result[1]!.toString(),
218
- );
219
- offset += 1;
220
- acc[token] = new Dec(wstUSRRate).mul(USRRate).toString();
221
- break;
222
- }
223
-
224
- // TODO: These addresses do not have chainlink feeds, so we need to handle them separately, this is hotfix
225
- case 'ezETH': {
226
- acc[token] = new Dec(ethPriceChainlink).mul(1.049).toString();
227
- break;
228
- }
229
- case 'rsETH': {
230
- acc[token] = new Dec(ethPriceChainlink).mul(1.0454).toString();
231
- break;
232
- }
233
- case 'weETHs': {
234
- acc[token] = new Dec(ethPriceChainlink).mul(1.026).toString();
235
- break;
236
- }
237
- case 'LBTC': {
238
- acc[token] = new Dec(btcPriceChainlink).toString();
239
- break;
240
- }
241
- case 'sUSDS': {
242
- acc[token] = new Dec('105276929').toString();
243
- break;
244
- }
245
-
246
- case 'weETH': {
247
- if (network !== NetworkNumber.Plasma) {
248
- const {
249
- ethPrice,
250
- weETHRate,
251
- } = parseWeETHPriceCalls(
252
- results[i + offset].result!.toString(),
253
- // @ts-ignore
254
- results[i + offset + 1].result[1]!.toString(),
255
- results[i + offset + 2].result!.toString(),
256
- );
257
- offset += 2;
258
- acc[token] = new Dec(ethPrice).mul(weETHRate).toString();
259
- // @ts-ignore
260
- } else if (results[i + offset].result?.[1]) {
261
- // For Plasma, use default chainlink feed (latestRoundData format)
262
- // @ts-ignore
263
- acc[token] = new Dec(results[i + offset].result[1]!.toString() as string).div(1e8).toString();
264
- } else if (results[i + offset].result) {
265
- // For Plasma, use default chainlink feed (latestAnswer format)
266
- acc[token] = new Dec(results[i + offset].result!.toString() as string).div(1e8).toString();
267
- } else {
268
- acc[token] = '0';
269
- }
270
- break;
271
- }
272
-
273
- default:
274
- // @ts-ignore
275
- if (results[i + offset].result?.[1]) {
276
- // @ts-ignore
277
- acc[token] = new Dec(results[i + offset].result[1]!.toString() as string).div(1e8).toString();
278
- } else if (results[i + offset].result) {
279
- acc[token] = new Dec(results[i + offset].result!.toString() as string).div(1e8).toString();
280
- } else acc[token] = '0';
281
- break;
282
- }
283
- return acc;
284
- }, {} as { [key: string]: string });
285
- };
286
-
287
-
288
- const getTokenPriceFromChainlink = async (asset: AssetData, network: NetworkNumber, provider: PublicClient) => {
289
- if (asset.symbol === 'sUSDS') {
290
- return new Dec('105276929').div(1e8).toString();
291
- }
292
- const isTokenUSDA = asset.symbol === 'USDA';
293
- const isMainnet = isMainnetNetwork(network);
294
- const loanTokenFeedAddress = getChainlinkAssetAddress(asset.symbol, network);
295
-
296
- let loanTokenPrice;
297
- if (asset.symbol === 'wstETH') {
298
- // need to handle wstETH for l2s inside getWstETHPrice
299
- loanTokenPrice = await getWstETHPriceFluid(provider, network);
300
- } else if (isMainnet) {
301
- const feedRegistryContract = FeedRegistryContractViem(provider, NetworkNumber.Eth);
302
- loanTokenPrice = isTokenUSDA ? '100000000' : await feedRegistryContract.read.latestAnswer([loanTokenFeedAddress, USD_QUOTE]);
303
- } else {
304
- // Currently only base network is supported
305
- const feedRegistryContract = DFSFeedRegistryContractViem(provider, network);
306
- try {
307
- const roundPriceData = isTokenUSDA ? [0, '100000000'] : await feedRegistryContract.read.latestRoundData([loanTokenFeedAddress, USD_QUOTE]);
308
- loanTokenPrice = roundPriceData[1].toString();
309
- } catch (err) {
310
- console.error(`Error fetching price for ${asset.symbol} on ${network}: ${err}`);
311
- loanTokenPrice = '0';
312
- }
313
- }
314
-
315
- return new Dec(loanTokenPrice).div(1e8).toString();
316
- };
317
-
318
- const getMarketRateForDex = (token1PerShare: string, token0PerShare: string, rate0: string, rate1: string, price0: string, price1: string) => {
319
- const token0PerShareUsd = new Dec(token0PerShare).mul(price0).toString();
320
- const token1PerShareUsd = new Dec(token1PerShare).mul(price1).toString();
321
- const sharesCombinedUsd = new Dec(token0PerShareUsd).plus(token1PerShareUsd);
322
-
323
- const rate0PerShare = new Dec(rate0).mul(token0PerShareUsd).div(sharesCombinedUsd).toString();
324
-
325
- const rate1PerShare = new Dec(rate1).mul(token1PerShareUsd).div(sharesCombinedUsd).toString();
326
-
327
- return new Dec(rate0PerShare).plus(rate1PerShare).toString();
328
- };
329
-
330
- const getAdditionalMarketRateForDex = (token1PerShare: string, token0PerShare: string, incentiveSupplyRate0: string, incentiveSupplyRate1: string, price0: string, price1: string) => {
331
- const token0PerShareUsd = new Dec(token0PerShare).mul(price0).toString();
332
- const token1PerShareUsd = new Dec(token1PerShare).mul(price1).toString();
333
- const sharesCombinedUsd = new Dec(token0PerShareUsd).plus(token1PerShareUsd);
334
-
335
- const rate0PerShare = incentiveSupplyRate0 ? new Dec(incentiveSupplyRate0).mul(token0PerShareUsd).div(sharesCombinedUsd).toString() : 0;
336
-
337
- const rate1PerShare = incentiveSupplyRate1 ? new Dec(incentiveSupplyRate1).mul(token1PerShareUsd).div(sharesCombinedUsd).toString() : 0;
338
-
339
- return new Dec(rate0PerShare).plus(rate1PerShare).toString();
340
- };
341
-
342
- const getTradingApy = async (poolAddress: EthAddress) => {
343
- let res;
344
- try {
345
- res = await fetch(`https://api.fluid.instadapp.io/v2/1/dexes/${poolAddress}/apy`,
346
- { signal: AbortSignal.timeout(DEFAULT_TIMEOUT) });
347
- } catch (e) {
348
- console.error('External API Failure: Fluid Trading Apy');
349
- return '0';
350
- }
351
- if (!res.ok) {
352
- return '0';
353
- }
354
- const data = await res.json();
355
- return new Dec(data.tradingApy).div(100).toString();
356
- };
357
-
358
- const parseT1MarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null) => {
359
- const collAssetContract = getAssetInfoByAddress(data.supplyToken0, network);
360
- const collAsset = getAssetInfo(getNativeAssetFromWrapped(collAssetContract.symbol), network);
361
- const debtAssetContract = getAssetInfoByAddress(data.borrowToken0, network);
362
- const debtAsset = getAssetInfo(getNativeAssetFromWrapped(debtAssetContract.symbol), network);
363
-
364
- const supplyRate = new Dec(data.supplyRateVault).div(100).toString();
365
- const borrowRate = new Dec(data.borrowRateVault).div(100).toString();
366
-
367
- const oracleScaleFactor = new Dec(27).add(debtAsset.decimals).sub(collAsset.decimals).toString();
368
- const oracleScale = new Dec(10).pow(oracleScaleFactor).toString();
369
- const oraclePrice = new Dec(data.oraclePriceOperate).div(oracleScale).toString();
370
- let debtPriceParsed = '0';
371
- if (tokenPrices) {
372
- debtPriceParsed = tokenPrices[debtAsset.symbol] || '0';
373
- } else {
374
- debtPriceParsed = await getTokenPriceFromChainlink(debtAssetContract, network, provider);
375
- }
376
-
377
- const collAssetData: FluidAssetData = {
378
- symbol: collAsset.symbol,
379
- address: collAsset.address,
380
- price: new Dec(debtPriceParsed).mul(oraclePrice).toString(),
381
- totalSupply: data.totalSupplyVault.toString(),
382
- totalBorrow: data.totalBorrowVault.toString(),
383
- canBeSupplied: true,
384
- canBeBorrowed: false,
385
- supplyRate,
386
- borrowRate: '0',
387
- supplyIncentives: [],
388
- borrowIncentives: [],
389
- };
390
-
391
- if (STAKING_ASSETS.includes(collAsset.symbol)) {
392
- collAssetData.supplyIncentives.push({
393
- apy: await getStakingApy(collAsset.symbol),
394
- token: collAssetData.symbol,
395
- incentiveKind: IncentiveKind.Staking,
396
- description: `Native ${collAssetData.symbol} yield.`,
397
- });
398
- }
399
-
400
- const debtAssetData: FluidAssetData = {
401
- symbol: debtAsset.symbol,
402
- address: debtAsset.address,
403
- price: debtPriceParsed,
404
- totalSupply: data.totalSupplyVault.toString(),
405
- totalBorrow: data.totalBorrowVault.toString(),
406
- canBeSupplied: false,
407
- canBeBorrowed: true,
408
- supplyRate: '0',
409
- borrowRate,
410
- supplyIncentives: [],
411
- borrowIncentives: [],
412
- };
413
- if (STAKING_ASSETS.includes(debtAssetData.symbol)) {
414
- debtAssetData.borrowIncentives.push({
415
- apy: new Dec(await getStakingApy(debtAsset.symbol)).mul(-1).toString(),
416
- token: debtAssetData.symbol,
417
- incentiveKind: IncentiveKind.Reward,
418
- description: `Due to the native yield of ${debtAssetData.symbol}, the value of the debt would increase over time.`,
419
- });
420
- }
421
-
422
- const assetsData = {
423
- [collAsset.symbol]: collAssetData,
424
- [debtAsset.symbol]: debtAssetData,
425
- };
426
- const marketInfo = getFluidMarketInfoById(+(data.vaultId.toString()), network);
427
- const totalSupplyVault = getEthAmountForDecimals(data.totalSupplyVault.toString(), collAsset.decimals);
428
- const totalBorrowVault = getEthAmountForDecimals(data.totalBorrowVault.toString(), debtAsset.decimals);
429
-
430
- const liqRatio = new Dec(data.liquidationThreshold).div(100).toString();
431
- const liquidationMaxLimit = new Dec(data.liquidationMaxLimit).div(100).toString();
432
- const liqFactor = new Dec(data.liquidationThreshold).div(10_000).toString();
433
-
434
- const marketData = {
435
- vaultId: +(data.vaultId.toString()),
436
- vaultValue: marketInfo?.value,
437
- isSmartColl: data.isSmartColl,
438
- isSmartDebt: data.isSmartDebt,
439
- marketAddress: data.vault,
440
- vaultType: parseVaultType(+(data.vaultType.toString())),
441
- oracle: data.oracle,
442
- liquidationPenaltyPercent: new Dec(data.liquidationPenalty).div(100).toString(),
443
- collFactor: new Dec(data.collateralFactor).div(10000).toString(), // we want actual factor, not in %, so we divide by 10000 instead of 100
444
- liquidationRatio: liqRatio,
445
- liqFactor,
446
- minRatio: new Dec(1).div(liqFactor).mul(100).toString(),
447
- collAsset0: collAsset.symbol,
448
- debtAsset0: debtAsset.symbol,
449
- totalPositions: data.totalPositions.toString(),
450
- totalSupplyVault,
451
- totalBorrowVault,
452
- totalSupplyVaultUsd: new Dec(totalSupplyVault).mul(collAssetData.price).toString(),
453
- totalBorrowVaultUsd: new Dec(totalBorrowVault).mul(debtAssetData.price).toString(),
454
- withdrawalLimit: getEthAmountForDecimals(data.withdrawalLimit.toString(), collAsset.decimals),
455
- withdrawableUntilLimit: getEthAmountForDecimals(data.withdrawableUntilLimit.toString(), collAsset.decimals),
456
- withdrawable: getEthAmountForDecimals(data.withdrawable.toString(), collAsset.decimals),
457
- borrowLimit: getEthAmountForDecimals(data.borrowLimit.toString(), debtAsset.decimals),
458
- borrowableUntilLimit: getEthAmountForDecimals(data.borrowableUntilLimit.toString(), debtAsset.decimals),
459
- borrowable: getEthAmountForDecimals(data.borrowable.toString(), debtAsset.decimals),
460
- borrowLimitUtilization: getEthAmountForDecimals(data.borrowLimitUtilization.toString(), debtAsset.decimals),
461
- maxBorrowLimit: getEthAmountForDecimals(data.maxBorrowLimit.toString(), debtAsset.decimals),
462
- baseBorrowLimit: getEthAmountForDecimals(data.baseBorrowLimit.toString(), debtAsset.decimals),
463
- minimumBorrowing: getEthAmountForDecimals(data.minimumBorrowing.toString(), debtAsset.decimals),
464
- liquidationMaxLimit,
465
- borrowRate,
466
- supplyRate,
467
- oraclePrice,
468
- incentiveSupplyRate: collAssetData.supplyIncentives[0]?.apy || '0',
469
- incentiveBorrowRate: debtAssetData.borrowIncentives[0]?.apy || '0',
470
- };
471
-
472
- return {
473
- assetsData,
474
- marketData,
475
- } as FluidMarketData;
476
- };
477
-
478
- const parseT2MarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null) => {
479
- const collAsset0Contract = getAssetInfoByAddress(data.supplyToken0, network);
480
- const collAsset0 = getAssetInfo(getNativeAssetFromWrapped(collAsset0Contract.symbol), network);
481
- const collAsset1Contract = getAssetInfoByAddress(data.supplyToken1, network);
482
- const collAsset1 = getAssetInfo(getNativeAssetFromWrapped(collAsset1Contract.symbol), network);
483
- const debtAssetContract = getAssetInfoByAddress(data.borrowToken0, network);
484
- const debtAsset = getAssetInfo(getNativeAssetFromWrapped(debtAssetContract.symbol), network);
485
-
486
- // 18 because collateral is represented in shares for which they use 18 decimals
487
- const oracleScaleFactor = new Dec(27).add(debtAsset.decimals).sub(18).toString();
488
- const oracleScale = new Dec(10).pow(oracleScaleFactor).toString();
489
- const oraclePrice = new Dec(data.oraclePriceOperate).div(oracleScale).toString();
490
-
491
- let prices: Record<string, string> = {};
492
- if (tokenPrices) {
493
- prices = tokenPrices;
494
- } else {
495
- prices = await getChainLinkPricesForTokens([collAsset0Contract.address, collAsset1Contract.address, debtAssetContract.address], network, provider);
496
- }
497
-
498
- const {
499
- supplyDexFee,
500
- totalSupplyShares,
501
- supplyRate1,
502
- totalSupplyToken1,
503
- token0PerSupplyShare,
504
- token1PerSupplyShare,
505
- totalSupplyToken0,
506
- maxSupplyShares,
507
- withdrawableToken0,
508
- withdrawable0,
509
- withdrawableToken1,
510
- withdrawable1,
511
- supplyRate0,
512
- utilizationSupply0,
513
- utilizationSupply1,
514
- withdrawableShares,
515
- reservesSupplyToken0,
516
- reservesSupplyToken1,
517
- } = parseDexSupplyData(data.dexSupplyData, collAsset0.symbol, collAsset1.symbol);
518
-
519
- const collFirstAssetData: Partial<FluidAssetData> = {
520
- symbol: collAsset0.symbol,
521
- address: collAsset0.address,
522
- price: prices[tokenPrices ? collAsset0.symbol : collAsset0Contract.address],
523
- totalSupply: new Dec(totalSupplyShares).mul(token0PerSupplyShare).toString(),
524
- canBeSupplied: true,
525
- supplyRate: supplyRate0,
526
- utilization: utilizationSupply0,
527
- withdrawable: withdrawable0,
528
- tokenPerSupplyShare: token0PerSupplyShare,
529
- supplyReserves: reservesSupplyToken0,
530
- supplyIncentives: [],
531
- borrowIncentives: [],
532
- };
533
- if (STAKING_ASSETS.includes(collFirstAssetData.symbol!)) {
534
- collFirstAssetData.supplyIncentives!.push({
535
- apy: await getStakingApy(collAsset0.symbol),
536
- token: collAsset0.symbol,
537
- incentiveKind: IncentiveKind.Staking,
538
- description: `Native ${collAsset0.symbol} yield.`,
539
- });
540
- }
541
-
542
- const collSecondAssetData: Partial<FluidAssetData> = {
543
- symbol: collAsset1.symbol,
544
- address: collAsset1.address,
545
- price: prices[tokenPrices ? collAsset1.symbol : collAsset1Contract.address],
546
- totalSupply: new Dec(totalSupplyShares).mul(token1PerSupplyShare).toString(),
547
- canBeSupplied: true,
548
- supplyRate: supplyRate1,
549
- withdrawable: withdrawable1,
550
- utilization: utilizationSupply1,
551
- tokenPerSupplyShare: token1PerSupplyShare,
552
- supplyReserves: reservesSupplyToken1,
553
- supplyIncentives: [],
554
- borrowIncentives: [],
555
- };
556
- if (STAKING_ASSETS.includes(collSecondAssetData.symbol!)) {
557
- collSecondAssetData.supplyIncentives!.push({
558
- apy: await getStakingApy(collAsset1.symbol),
559
- token: collAsset1.symbol,
560
- incentiveKind: IncentiveKind.Staking,
561
- description: `Native ${collAsset1.symbol} yield.`,
562
- });
563
- }
564
-
565
- const marketSupplyRate = getMarketRateForDex(token1PerSupplyShare, token0PerSupplyShare, supplyRate0, supplyRate1, collFirstAssetData.price!, collSecondAssetData.price!);
566
- const incentiveSupplyRate = getAdditionalMarketRateForDex(token1PerSupplyShare, token0PerSupplyShare, collFirstAssetData.supplyIncentives![0]?.apy || '0', collSecondAssetData.supplyIncentives![0]?.apy || '0', collFirstAssetData.price!, collSecondAssetData.price!);
567
- const tradingSupplyRate = await getTradingApy(data.dexSupplyData.dexPool as EthAddress);
568
-
569
- const borrowRate = new Dec(data.borrowRateVault).div(100).toString();
570
- const debtAssetData: Partial<FluidAssetData> = {
571
- symbol: debtAsset.symbol,
572
- price: prices[tokenPrices ? debtAsset.symbol : debtAssetContract.address],
573
- address: debtAsset.address,
574
- totalBorrow: data.totalBorrowVault.toString(),
575
- canBeBorrowed: true,
576
- borrowRate,
577
- supplyIncentives: [],
578
- borrowIncentives: [],
579
- };
580
- if (STAKING_ASSETS.includes(debtAssetData.symbol!)) {
581
- debtAssetData.borrowIncentives!.push({
582
- apy: new Dec(await getStakingApy(debtAsset.symbol)).mul(-1).toString(),
583
- token: debtAsset.symbol,
584
- incentiveKind: IncentiveKind.Reward,
585
- description: `Due to the native yield of ${debtAsset.symbol}, the value of the debt would increase over time.`,
586
- });
587
- }
588
-
589
- const incentiveBorrowRate = new Dec(debtAssetData.borrowIncentives![0]?.apy || '0').mul(-1).toString();
590
-
591
- const assetsData: FluidAssetsData = ([
592
- [collAsset0.symbol, collFirstAssetData],
593
- [collAsset1.symbol, collSecondAssetData],
594
- [debtAsset.symbol, debtAssetData],
595
- ] as [string, FluidAssetData][])
596
- .reduce((acc, [symbol, partialData]) => ({
597
- ...acc,
598
- [symbol]: mergeAssetData(acc[symbol], partialData),
599
- }), {} as Record<string, FluidAssetData>) as FluidAssetsData;
600
-
601
- const marketInfo = getFluidMarketInfoById(+(data.vaultId.toString()), network);
602
-
603
- const totalBorrowVault = getEthAmountForDecimals(data.totalBorrowVault.toString(), debtAsset.decimals);
604
-
605
- const liqRatio = new Dec(data.liquidationThreshold).div(100).toString();
606
- const liquidationMaxLimit = new Dec(data.liquidationMaxLimit).div(100).toString();
607
- const liqFactor = new Dec(data.liquidationThreshold).div(10_000).toString();
608
-
609
- const totalSupplySharesInVault = assetAmountInEth(data.totalSupplyVault.toString());
610
- const collSharePrice = new Dec(oraclePrice).mul(prices[tokenPrices ? debtAsset.symbol : debtAssetContract.address]).toString();
611
- const totalSupplyVaultUsd = new Dec(totalSupplySharesInVault).mul(collSharePrice).toString();
612
- const maxSupplySharesUsd = new Dec(maxSupplyShares).mul(collSharePrice).toString();
613
-
614
- const withdrawableUSD = new Dec(withdrawableShares).mul(collSharePrice).toString();
615
-
616
- const marketData = {
617
- vaultId: +(data.vaultId.toString()),
618
- vaultValue: marketInfo?.value,
619
- isSmartColl: data.isSmartColl,
620
- isSmartDebt: data.isSmartDebt,
621
- marketAddress: data.vault,
622
- vaultType: parseVaultType(+(data.vaultType.toString())),
623
- oracle: data.oracle,
624
- liquidationPenaltyPercent: new Dec(data.liquidationPenalty).div(100).toString(),
625
- collFactor: new Dec(data.collateralFactor).div(10000).toString(), // we want actual factor, not in %, so we divide by 10000 instead of 100
626
- liquidationRatio: liqRatio,
627
- liqFactor,
628
- minRatio: new Dec(1).div(liqFactor).mul(100).toString(),
629
- collAsset0: collAsset0.symbol,
630
- collAsset1: collAsset1.symbol,
631
- debtAsset0: debtAsset.symbol,
632
- totalPositions: data.totalPositions.toString(),
633
- totalSupplyVault: totalSupplyShares,
634
- totalBorrowVault,
635
- totalSupplyVaultUsd,
636
- collSharePrice,
637
- totalBorrowVaultUsd: new Dec(totalBorrowVault).mul(assetsData[debtAsset.symbol].price).toString(),
638
- borrowLimit: getEthAmountForDecimals(data.borrowLimit.toString(), debtAsset.decimals),
639
- borrowableUntilLimit: getEthAmountForDecimals(data.borrowableUntilLimit.toString(), debtAsset.decimals),
640
- borrowable: getEthAmountForDecimals(data.borrowable.toString(), debtAsset.decimals),
641
- borrowLimitUtilization: getEthAmountForDecimals(data.borrowLimitUtilization.toString(), debtAsset.decimals),
642
- maxBorrowLimit: getEthAmountForDecimals(data.maxBorrowLimit.toString(), debtAsset.decimals),
643
- baseBorrowLimit: getEthAmountForDecimals(data.baseBorrowLimit.toString(), debtAsset.decimals),
644
- minimumBorrowing: getEthAmountForDecimals(data.minimumBorrowing.toString(), debtAsset.decimals),
645
- liquidationMaxLimit,
646
- borrowRate,
647
- supplyRate: marketSupplyRate,
648
- incentiveSupplyRate,
649
- incentiveBorrowRate,
650
- totalSupplyToken0,
651
- totalSupplyToken1,
652
- withdrawableToken0,
653
- withdrawableToken1,
654
- withdrawableUSD,
655
- withdrawable: withdrawableShares,
656
- withdrawableDex: new Dec(maxSupplyShares).minus(totalSupplyShares).toString(),
657
- maxSupplyShares,
658
- maxSupplySharesUsd,
659
- collDexFee: supplyDexFee,
660
- oraclePrice,
661
- tradingSupplyRate,
662
- tradingBorrowRate: '0',
663
- };
664
-
665
- return {
666
- assetsData,
667
- marketData,
668
- } as FluidMarketData;
669
- };
670
-
671
- const parseT3MarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null) => {
672
- const collAssetContract = getAssetInfoByAddress(data.supplyToken0, network);
673
- const collAsset = getAssetInfo(getNativeAssetFromWrapped(collAssetContract.symbol), network);
674
- const debtAsset0Contract = getAssetInfoByAddress(data.borrowToken0, network);
675
- const debtAsset0 = getAssetInfo(getNativeAssetFromWrapped(debtAsset0Contract.symbol), network);
676
- const debtAsset1Contract = getAssetInfoByAddress(data.borrowToken1, network);
677
- const debtAsset1 = getAssetInfo(getNativeAssetFromWrapped(debtAsset1Contract.symbol), network);
678
-
679
- const {
680
- borrowableShares,
681
- maxBorrowShares,
682
- borrowDexFee,
683
- utilizationBorrow0,
684
- utilizationBorrow1,
685
- borrowable0,
686
- borrowable1,
687
- borrowRate0,
688
- borrowRate1,
689
- totalBorrowShares,
690
- token0PerBorrowShare,
691
- token1PerBorrowShare,
692
- borrowableToken0,
693
- borrowableToken1,
694
- totalBorrowToken0,
695
- totalBorrowToken1,
696
- reservesBorrowToken0,
697
- reservesBorrowToken1,
698
- } = parseDexBorrowData(data.dexBorrowData, debtAsset0.symbol, debtAsset1.symbol);
699
-
700
- // 18 because debt is represented in shares for which they use 18 decimals
701
- const oracleScaleFactor = new Dec(27).add(18).sub(collAsset.decimals).toString();
702
- const oracleScale = new Dec(10).pow(oracleScaleFactor).toString();
703
- const oraclePrice = new Dec(1).div(new Dec(data.oraclePriceOperate).div(oracleScale)).toString();
704
-
705
- let prices: Record<string, string> = {};
706
- if (tokenPrices) {
707
- prices = tokenPrices;
708
- } else {
709
- prices = await getChainLinkPricesForTokens([collAssetContract.address, debtAsset0Contract.address, debtAsset1Contract.address], network, provider);
710
- }
711
-
712
- const supplyRate = new Dec(data.supplyRateVault).div(100).toString();
713
- const collAssetData: Partial<FluidAssetData> = {
714
- symbol: collAsset.symbol,
715
- address: collAsset.address,
716
- price: prices[tokenPrices ? collAsset.symbol : collAssetContract.address],
717
- totalSupply: data.totalSupplyVault.toString(),
718
- canBeSupplied: true,
719
- supplyRate,
720
- supplyIncentives: [],
721
- borrowIncentives: [],
722
- };
723
- if (STAKING_ASSETS.includes(collAssetData.symbol!)) {
724
- collAssetData.supplyIncentives!.push({
725
- apy: await getStakingApy(collAsset.symbol),
726
- token: collAsset.symbol,
727
- incentiveKind: IncentiveKind.Staking,
728
- description: `Native ${collAsset.symbol} yield.`,
729
- });
730
- }
731
-
732
- const incentiveSupplyRate = collAssetData.supplyIncentives?.[0]?.apy || '0';
733
-
734
- const debtAsset0Data: Partial<FluidAssetData> = {
735
- symbol: debtAsset0.symbol,
736
- address: debtAsset0.address,
737
- price: prices[tokenPrices ? debtAsset0.symbol : debtAsset0Contract.address],
738
- totalBorrow: new Dec(totalBorrowShares).mul(token0PerBorrowShare).toString(),
739
- canBeBorrowed: true,
740
- borrowRate: borrowRate0,
741
- borrowable: borrowable0,
742
- utilization: utilizationBorrow0,
743
- tokenPerBorrowShare: token0PerBorrowShare,
744
- borrowReserves: reservesBorrowToken0,
745
- supplyIncentives: [],
746
- borrowIncentives: [],
747
- };
748
- if (STAKING_ASSETS.includes(debtAsset0Data.symbol!)) {
749
- debtAsset0Data.borrowIncentives!.push({
750
- apy: new Dec(await getStakingApy(debtAsset0.symbol)).mul(-1).toString(),
751
- token: debtAsset0.symbol,
752
- incentiveKind: IncentiveKind.Reward,
753
- description: `Due to the native yield of ${debtAsset0.symbol}, the value of the debt would increase over time.`,
754
- });
755
- }
756
-
757
- const debtAsset1Data: Partial<FluidAssetData> = {
758
- symbol: debtAsset1.symbol,
759
- address: debtAsset1.address,
760
- price: prices[tokenPrices ? debtAsset1.symbol : debtAsset1Contract.address],
761
- totalBorrow: new Dec(totalBorrowShares).mul(token1PerBorrowShare).toString(),
762
- canBeBorrowed: true,
763
- borrowRate: borrowRate1,
764
- borrowable: borrowable1,
765
- utilization: utilizationBorrow1,
766
- tokenPerBorrowShare: token1PerBorrowShare,
767
- borrowReserves: reservesBorrowToken1,
768
- supplyIncentives: [],
769
- borrowIncentives: [],
770
- };
771
- if (STAKING_ASSETS.includes(debtAsset1Data.symbol!)) {
772
- debtAsset1Data.borrowIncentives!.push({
773
- apy: new Dec(await getStakingApy(debtAsset1.symbol)).mul(-1).toString(),
774
- token: debtAsset1.symbol,
775
- incentiveKind: IncentiveKind.Reward,
776
- description: `Due to the native yield of ${debtAsset1.symbol}, the value of the debt would increase over time.`,
777
- });
778
- }
779
- const marketBorrowRate = getMarketRateForDex(token1PerBorrowShare, token0PerBorrowShare, borrowRate0, borrowRate1, debtAsset0Data.price!, debtAsset1Data.price!);
780
- const incentiveBorrowRate = getAdditionalMarketRateForDex(token1PerBorrowShare, token0PerBorrowShare, new Dec(debtAsset0Data.borrowIncentives![0]?.apy || '0').mul(-1).toString(), new Dec(debtAsset1Data.borrowIncentives![0]?.apy || '0').mul(-1).toString(), debtAsset0Data.price!, debtAsset1Data.price!);
781
- const tradingBorrowRate = await getTradingApy(data.dexBorrowData.dexPool as EthAddress);
782
-
783
- const assetsData: FluidAssetsData = ([
784
- [collAsset.symbol, collAssetData],
785
- [debtAsset0.symbol, debtAsset0Data],
786
- [debtAsset1.symbol, debtAsset1Data],
787
- ] as [string, FluidAssetData][])
788
- .reduce((acc, [symbol, partialData]) => ({
789
- ...acc,
790
- [symbol]: mergeAssetData(acc[symbol], partialData),
791
- }), {} as Record<string, FluidAssetData>) as FluidAssetsData;
792
-
793
- const marketInfo = getFluidMarketInfoById(+(data.vaultId.toString()), network);
794
-
795
- const totalSupplyVault = getEthAmountForDecimals(data.totalSupplyVault.toString(), collAsset.decimals);
796
-
797
- const liqRatio = new Dec(data.liquidationThreshold).div(100).toString();
798
- const liquidationMaxLimit = new Dec(data.liquidationMaxLimit).div(100).toString();
799
- const liqFactor = new Dec(data.liquidationThreshold).div(10_000).toString();
800
-
801
- const debtSharePrice = new Dec(oraclePrice).mul(prices[tokenPrices ? collAsset.symbol : collAsset.address]).toString();
802
-
803
- const totalBorrowSharesInVault = assetAmountInEth(data.totalBorrowVault.toString());
804
-
805
- const totalBorrowVaultUsd = new Dec(totalBorrowSharesInVault).mul(debtSharePrice).toString();
806
-
807
- const borrowableUSD = new Dec(borrowableShares).mul(debtSharePrice).toString();
808
- const maxBorrowSharesUsd = new Dec(maxBorrowShares).mul(debtSharePrice).toString();
809
-
810
- const marketData = {
811
- vaultId: +(data.vaultId.toString()),
812
- vaultValue: marketInfo?.value,
813
- isSmartColl: data.isSmartColl,
814
- isSmartDebt: data.isSmartDebt,
815
- marketAddress: data.vault,
816
- vaultType: parseVaultType(+(data.vaultType.toString())),
817
- oracle: data.oracle,
818
- liquidationPenaltyPercent: new Dec(data.liquidationPenalty).div(100).toString(),
819
- collFactor: new Dec(data.collateralFactor).div(10000).toString(), // we want actual factor, not in %, so we divide by 10000 instead of 100
820
- liquidationRatio: liqRatio,
821
- liqFactor,
822
- minRatio: new Dec(1).div(liqFactor).mul(100).toString(),
823
- collAsset0: collAsset.symbol,
824
- debtAsset0: debtAsset0.symbol,
825
- debtAsset1: debtAsset1.symbol,
826
- totalPositions: data.totalPositions.toString(),
827
- totalSupplyVault,
828
- totalBorrowVault: totalBorrowShares,
829
- totalSupplyVaultUsd: new Dec(totalSupplyVault).mul(assetsData[collAsset.symbol].price).toString(),
830
- totalBorrowVaultUsd,
831
- withdrawalLimit: getEthAmountForDecimals(data.withdrawalLimit.toString(), collAsset.decimals),
832
- withdrawableUntilLimit: getEthAmountForDecimals(data.withdrawableUntilLimit.toString(), collAsset.decimals),
833
- withdrawable: getEthAmountForDecimals(data.withdrawable.toString(), collAsset.decimals),
834
- liquidationMaxLimit,
835
- borrowRate: marketBorrowRate,
836
- supplyRate,
837
- incentiveBorrowRate,
838
- incentiveSupplyRate,
839
- tradingBorrowRate,
840
- tradingSupplyRate: '0',
841
- borrowableToken0,
842
- borrowableToken1,
843
- totalBorrowToken0,
844
- totalBorrowToken1,
845
- borrowableUSD,
846
- borrowable: borrowableShares,
847
- borrowableDex: new Dec(maxBorrowShares).minus(totalBorrowShares).toString(),
848
- maxBorrowShares,
849
- maxBorrowSharesUsd,
850
- borrowDexFee,
851
- debtSharePrice,
852
- oraclePrice,
853
- };
854
-
855
- return {
856
- assetsData,
857
- marketData,
858
- } as FluidMarketData;
859
- };
860
-
861
- const parseT4MarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null) => {
862
- const collAsset0Contract = getAssetInfoByAddress(data.supplyToken0, network);
863
- const collAsset0 = getAssetInfo(getNativeAssetFromWrapped(collAsset0Contract.symbol), network);
864
- const collAsset1Contract = getAssetInfoByAddress(data.supplyToken1, network);
865
- const collAsset1 = getAssetInfo(getNativeAssetFromWrapped(collAsset1Contract.symbol), network);
866
- const debtAsset0Contract = getAssetInfoByAddress(data.borrowToken0, network);
867
- const debtAsset0 = getAssetInfo(getNativeAssetFromWrapped(debtAsset0Contract.symbol), network);
868
- const debtAsset1Contract = getAssetInfoByAddress(data.borrowToken1, network);
869
- const debtAsset1 = getAssetInfo(getNativeAssetFromWrapped(debtAsset1Contract.symbol), network);
870
- const quoteToken = getAssetInfoByAddress(data.dexBorrowData.quoteToken, network);
871
-
872
- // 27 - 18 + 18
873
- const oracleScaleFactor = new Dec(27).toString();
874
- const oracleScale = new Dec(10).pow(oracleScaleFactor).toString();
875
- const oraclePrice = new Dec(data.oraclePriceOperate).div(oracleScale).toString();
876
-
877
- let prices: Record<string, string> = {};
878
- if (tokenPrices) {
879
- prices = tokenPrices;
880
- } else {
881
- prices = await getChainLinkPricesForTokens(
882
- [collAsset0Contract.address, collAsset1Contract.address, debtAsset0Contract.address, debtAsset1Contract.address],
883
- network, provider);
884
- }
885
-
886
- const {
887
- supplyDexFee,
888
- totalSupplyShares,
889
- supplyRate1,
890
- token0PerSupplyShare,
891
- token1PerSupplyShare,
892
- totalSupplyToken0,
893
- totalSupplyToken1,
894
- maxSupplyShares,
895
- withdrawableToken0,
896
- withdrawable0,
897
- withdrawableToken1,
898
- withdrawable1,
899
- supplyRate0,
900
- utilizationSupply0,
901
- utilizationSupply1,
902
- withdrawableShares,
903
- reservesSupplyToken0,
904
- reservesSupplyToken1,
905
- } = parseDexSupplyData(data.dexSupplyData, collAsset0.symbol, collAsset1.symbol);
906
-
907
- const {
908
- borrowableShares,
909
- maxBorrowShares,
910
- borrowDexFee,
911
- utilizationBorrow0,
912
- utilizationBorrow1,
913
- borrowable0,
914
- borrowable1,
915
- borrowRate0,
916
- borrowRate1,
917
- totalBorrowShares,
918
- token0PerBorrowShare,
919
- token1PerBorrowShare,
920
- borrowableToken0,
921
- borrowableToken1,
922
- totalBorrowToken0,
923
- totalBorrowToken1,
924
- quoteTokensPerShare,
925
- reservesBorrowToken0,
926
- reservesBorrowToken1,
927
- } = parseDexBorrowData(data.dexBorrowData, debtAsset0.symbol, debtAsset1.symbol);
928
-
929
- const collAsset0Data: Partial<FluidAssetData> = {
930
- symbol: collAsset0.symbol,
931
- address: collAsset0.address,
932
- price: prices[tokenPrices ? collAsset0.symbol : collAsset0Contract.address],
933
- totalSupply: new Dec(totalSupplyShares).mul(token0PerSupplyShare).toString(),
934
- canBeSupplied: true,
935
- supplyRate: supplyRate0,
936
- utilization: utilizationSupply0,
937
- withdrawable: withdrawable0,
938
- tokenPerSupplyShare: token0PerSupplyShare,
939
- supplyReserves: reservesSupplyToken0,
940
- supplyIncentives: [],
941
- borrowIncentives: [],
942
- };
943
- if (STAKING_ASSETS.includes(collAsset0Data.symbol!)) {
944
- collAsset0Data.supplyIncentives!.push({
945
- apy: await getStakingApy(collAsset0.symbol),
946
- token: collAsset0.symbol,
947
- incentiveKind: IncentiveKind.Staking,
948
- description: `Native ${collAsset0.symbol} yield.`,
949
- });
950
- }
951
-
952
- const collAsset1Data: Partial<FluidAssetData> = {
953
- symbol: collAsset1.symbol,
954
- address: collAsset1.address,
955
- price: prices[tokenPrices ? collAsset1.symbol : collAsset1Contract.address],
956
- totalSupply: new Dec(totalSupplyShares).mul(token1PerSupplyShare).toString(),
957
- canBeSupplied: true,
958
- supplyRate: supplyRate1,
959
- withdrawable: withdrawable1,
960
- utilization: utilizationSupply1,
961
- tokenPerSupplyShare: token1PerSupplyShare,
962
- supplyReserves: reservesSupplyToken1,
963
- supplyIncentives: [],
964
- borrowIncentives: [],
965
- };
966
- if (STAKING_ASSETS.includes(collAsset1Data.symbol!)) {
967
- collAsset1Data.supplyIncentives!.push({
968
- apy: await getStakingApy(collAsset1.symbol),
969
- token: collAsset1.symbol,
970
- incentiveKind: IncentiveKind.Staking,
971
- description: `Native ${collAsset1.symbol} yield.`,
972
- });
973
- }
974
-
975
- const debtAsset0Data: Partial<FluidAssetData> = {
976
- symbol: debtAsset0.symbol,
977
- address: debtAsset0.address,
978
- price: prices[tokenPrices ? debtAsset0.symbol : debtAsset0Contract.address],
979
- totalBorrow: new Dec(totalBorrowShares).mul(token0PerBorrowShare).toString(),
980
- canBeBorrowed: true,
981
- borrowRate: borrowRate0,
982
- borrowable: borrowable0,
983
- utilization: utilizationBorrow0,
984
- tokenPerBorrowShare: token0PerBorrowShare,
985
- borrowReserves: reservesBorrowToken0,
986
- supplyIncentives: [],
987
- borrowIncentives: [],
988
- };
989
- if (STAKING_ASSETS.includes(debtAsset0Data.symbol!)) {
990
- debtAsset0Data.borrowIncentives!.push({
991
- apy: new Dec(await getStakingApy(debtAsset0.symbol)).mul(-1).toString(),
992
- token: debtAsset0.symbol,
993
- incentiveKind: IncentiveKind.Reward,
994
- description: `Due to the native yield of ${debtAsset0.symbol}, the value of the debt would increase over time.`,
995
- });
996
- }
997
-
998
- const debtAsset1Data: Partial<FluidAssetData> = {
999
- symbol: debtAsset1.symbol,
1000
- address: debtAsset1.address,
1001
- price: prices[tokenPrices ? debtAsset1.symbol : debtAsset1Contract.address],
1002
- totalBorrow: new Dec(totalBorrowShares).mul(token1PerBorrowShare).toString(),
1003
- canBeBorrowed: true,
1004
- borrowRate: borrowRate1,
1005
- borrowable: borrowable1,
1006
- utilization: utilizationBorrow1,
1007
- tokenPerBorrowShare: token1PerBorrowShare,
1008
- borrowReserves: reservesBorrowToken1,
1009
- supplyIncentives: [],
1010
- borrowIncentives: [],
1011
- };
1012
- if (STAKING_ASSETS.includes(debtAsset1Data.symbol!)) {
1013
- debtAsset1Data.borrowIncentives!.push({
1014
- apy: new Dec(await getStakingApy(debtAsset1.symbol)).mul(-1).toString(),
1015
- token: debtAsset1.symbol,
1016
- incentiveKind: IncentiveKind.Reward,
1017
- description: `Due to the native yield of ${debtAsset1.symbol}, the value of the debt would increase over time.`,
1018
- });
1019
- }
1020
- const marketInfo = getFluidMarketInfoById(+(data.vaultId.toString()), network);
1021
-
1022
- const marketBorrowRate = getMarketRateForDex(token1PerBorrowShare, token0PerBorrowShare, borrowRate0, borrowRate1, debtAsset0Data.price!, debtAsset1Data.price!);
1023
- const incentiveBorrowRate = getAdditionalMarketRateForDex(token1PerBorrowShare, token0PerBorrowShare, new Dec(debtAsset0Data.borrowIncentives![0]?.apy || '0').mul(-1).toString(), new Dec(debtAsset1Data.borrowIncentives![0]?.apy || '0').mul(-1).toString(), debtAsset0Data.price!, debtAsset1Data.price!);
1024
- const tradingBorrowRate = await getTradingApy(data.dexBorrowData.dexPool as EthAddress);
1025
-
1026
- const marketSupplyRate = getMarketRateForDex(token1PerSupplyShare, token0PerSupplyShare, supplyRate0, supplyRate1, collAsset0Data.price!, collAsset1Data.price!);
1027
- const incentiveSupplyRate = getAdditionalMarketRateForDex(token1PerSupplyShare, token0PerSupplyShare, collAsset0Data.supplyIncentives![0]?.apy || '0', collAsset1Data.supplyIncentives![0]?.apy || '0', collAsset0Data.price!, collAsset1Data.price!);
1028
- const tradingSupplyRate = await getTradingApy(data.dexSupplyData.dexPool as EthAddress);
1029
-
1030
- const assetsData: FluidAssetsData = ([
1031
- [collAsset0.symbol, collAsset0Data],
1032
- [collAsset1.symbol, collAsset1Data],
1033
- [debtAsset0.symbol, debtAsset0Data],
1034
- [debtAsset1.symbol, debtAsset1Data],
1035
- ] as [string, FluidAssetData][])
1036
- .reduce((acc, [symbol, partialData]) => ({
1037
- ...acc,
1038
- [symbol]: mergeAssetData(acc[symbol], partialData),
1039
- }), {} as Record<string, FluidAssetData>) as FluidAssetsData;
1040
-
1041
-
1042
- const liqRatio = new Dec(data.liquidationThreshold).div(100).toString();
1043
- const liquidationMaxLimit = new Dec(data.liquidationMaxLimit).div(100).toString();
1044
- const liqFactor = new Dec(data.liquidationThreshold).div(10_000).toString();
1045
-
1046
- const totalBorrowSharesInVault = assetAmountInEth(data.totalBorrowVault.toString());
1047
- const debtSharePrice = new Dec(quoteTokensPerShare).mul(prices[tokenPrices ? quoteToken.symbol : quoteToken.address]).toString();
1048
- const totalBorrowVaultUsd = new Dec(totalBorrowSharesInVault).mul(debtSharePrice).toString();
1049
- const maxBorrowSharesUsd = new Dec(maxBorrowShares).mul(debtSharePrice).toString();
1050
- const borrowableUSD = new Dec(borrowableShares).mul(debtSharePrice).toString();
1051
-
1052
- const totalSupplySharesInVault = assetAmountInEth(data.totalSupplyVault.toString());
1053
- const collSharePrice = new Dec(oraclePrice).mul(debtSharePrice).toString();
1054
- const totalSupplyVaultUsd = new Dec(totalSupplySharesInVault).mul(collSharePrice).toString();
1055
- const maxSupplySharesUsd = new Dec(maxSupplyShares).mul(collSharePrice).toString();
1056
- const withdrawableUSD = new Dec(withdrawableShares).mul(collSharePrice).toString();
1057
-
1058
- const marketData = {
1059
- vaultId: +(data.vaultId.toString()),
1060
- vaultValue: marketInfo?.value,
1061
- isSmartColl: data.isSmartColl,
1062
- isSmartDebt: data.isSmartDebt,
1063
- marketAddress: data.vault,
1064
- vaultType: parseVaultType(+(data.vaultType.toString())),
1065
- oracle: data.oracle,
1066
- liquidationPenaltyPercent: new Dec(data.liquidationPenalty).div(100).toString(),
1067
- collFactor: new Dec(data.collateralFactor).div(10000).toString(), // we want actual factor, not in %, so we divide by 10000 instead of 100
1068
- liquidationRatio: liqRatio,
1069
- liqFactor,
1070
- minRatio: new Dec(1).div(liqFactor).mul(100).toString(),
1071
- collAsset0: collAsset0.symbol,
1072
- collAsset1: collAsset1.symbol,
1073
- debtAsset0: debtAsset0.symbol,
1074
- debtAsset1: debtAsset1.symbol,
1075
- totalPositions: data.totalPositions.toString(),
1076
- totalSupplyVault: totalSupplyShares,
1077
- totalBorrowVault: totalBorrowShares,
1078
- totalSupplyVaultUsd,
1079
- totalBorrowVaultUsd,
1080
- liquidationMaxLimit,
1081
- borrowRate: marketBorrowRate,
1082
- incentiveBorrowRate,
1083
- supplyRate: marketSupplyRate,
1084
- incentiveSupplyRate,
1085
- borrowableToken0,
1086
- borrowableToken1,
1087
- totalBorrowToken0,
1088
- totalBorrowToken1,
1089
- borrowableUSD,
1090
- borrowable: borrowableShares,
1091
- borrowableDex: new Dec(maxBorrowShares).minus(totalBorrowShares).toString(),
1092
- maxBorrowShares,
1093
- maxBorrowSharesUsd,
1094
- borrowDexFee,
1095
- totalSupplyToken0,
1096
- totalSupplyToken1,
1097
- withdrawableToken0,
1098
- withdrawableToken1,
1099
- withdrawableUSD,
1100
- withdrawable: withdrawableShares,
1101
- withdrawableDex: new Dec(maxSupplyShares).minus(totalSupplyShares).toString(),
1102
- maxSupplyShares,
1103
- maxSupplySharesUsd,
1104
- collDexFee: supplyDexFee,
1105
- collSharePrice,
1106
- debtSharePrice,
1107
- oraclePrice,
1108
- tradingBorrowRate,
1109
- tradingSupplyRate,
1110
- };
1111
-
1112
- return {
1113
- assetsData,
1114
- marketData,
1115
- } as FluidMarketData;
1116
- };
1117
-
1118
- const parseMarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null) => {
1119
- const marketInfo = getFluidMarketInfoById(+(data.vaultId.toString()), network);
1120
- if (!marketInfo) {
1121
- console.error(`Fluid market with address ${data.vault} not supported.`);
1122
- return; // skip unsupported market
1123
- }
1124
- const vaultType = parseVaultType(+(data.vaultType.toString()));
1125
- switch (vaultType) {
1126
- case FluidVaultType.T1:
1127
- return parseT1MarketData(provider, data, network, tokenPrices);
1128
- case FluidVaultType.T2:
1129
- return parseT2MarketData(provider, data, network, tokenPrices);
1130
- case FluidVaultType.T3:
1131
- return parseT3MarketData(provider, data, network, tokenPrices);
1132
- case FluidVaultType.T4:
1133
- return parseT4MarketData(provider, data, network, tokenPrices);
1134
- default:
1135
- throw new Error(`Unknown vault type: ${vaultType}`);
1136
- }
1137
- };
1138
-
1139
- export const EMPTY_FLUID_DATA = {
1140
- usedAssets: {},
1141
- suppliedUsd: '0',
1142
- borrowedUsd: '0',
1143
- borrowLimitUsd: '0',
1144
- leftToBorrowUsd: '0',
1145
- ratio: '0',
1146
- minRatio: '0',
1147
- netApy: '0',
1148
- incentiveUsd: '0',
1149
- totalInterestUsd: '0',
1150
- isSubscribedToAutomation: false,
1151
- automationResubscribeRequired: false,
1152
- lastUpdated: Date.now(),
1153
- };
1154
-
1155
- const parseT1UserData = (userPositionData: FluidUserPositionStructOutputStruct, vaultData: FluidMarketData): FluidVaultData => {
1156
- const {
1157
- assetsData,
1158
- marketData,
1159
- } = vaultData;
1160
-
1161
- const payload = {
1162
- owner: userPositionData.owner,
1163
- vaultId: marketData.vaultId,
1164
- ...EMPTY_FLUID_DATA,
1165
- lastUpdated: Date.now(),
1166
- };
1167
- const collAsset = getAssetInfo(marketData.collAsset0);
1168
- const debtAsset = getAssetInfo(marketData.debtAsset0);
1169
-
1170
- // for T2 and T4 - this is the number of shares
1171
- const supplied = getEthAmountForDecimals(userPositionData.supply.toString(), collAsset.decimals);
1172
- const borrowed = getEthAmountForDecimals(userPositionData.borrow.toString(), debtAsset.decimals);
1173
-
1174
- const collUsedAsset: FluidUsedAsset = {
1175
- ...EMPTY_USED_ASSET,
1176
- symbol: collAsset.symbol,
1177
- collateral: true,
1178
- supplied,
1179
- suppliedUsd: new Dec(supplied).mul(assetsData[collAsset.symbol].price).toString(),
1180
- isSupplied: new Dec(supplied).gt(0),
1181
- };
1182
-
1183
- const debtUsedAsset: FluidUsedAsset = {
1184
- ...EMPTY_USED_ASSET,
1185
- symbol: debtAsset.symbol,
1186
- collateral: false,
1187
- borrowed,
1188
- borrowedUsd: new Dec(borrowed).mul(assetsData[debtAsset.symbol].price).toString(),
1189
- isBorrowed: new Dec(borrowed).gt(0),
1190
- };
1191
-
1192
- const usedAssets: FluidUsedAssets = {
1193
- [collAsset.symbol]: collUsedAsset,
1194
- [debtAsset.symbol]: debtUsedAsset,
1195
- };
1196
-
1197
- return {
1198
- ...payload,
1199
- usedAssets,
1200
- nftId: userPositionData.nftId.toString(),
1201
- ...(getFluidAggregatedData({
1202
- usedAssets,
1203
- assetsData,
1204
- marketData,
1205
- }) as FluidAggregatedVaultData),
1206
- };
1207
- };
1208
-
1209
- const parseT2UserData = (userPositionData: FluidUserPositionStructOutputStruct, vaultData: FluidMarketData): FluidVaultData => {
1210
- const {
1211
- assetsData,
1212
- marketData,
1213
- } = vaultData;
1214
-
1215
- const payload = {
1216
- owner: userPositionData.owner,
1217
- vaultId: marketData.vaultId,
1218
- ...EMPTY_FLUID_DATA,
1219
- lastUpdated: Date.now(),
1220
- };
1221
-
1222
- const collAsset0 = getAssetInfo(marketData.collAsset0);
1223
- const collAsset1 = getAssetInfo(marketData.collAsset1);
1224
- const debtAsset = getAssetInfo(marketData.debtAsset0);
1225
-
1226
- const supplyShares = getEthAmountForDecimals(userPositionData.supply.toString(), 18); // this is supplied in coll shares
1227
- const borrowed = getEthAmountForDecimals(userPositionData.borrow.toString(), debtAsset.decimals); // this is actual token borrow
1228
-
1229
- const supplied0 = new Dec(supplyShares).mul(assetsData[collAsset0.symbol].tokenPerSupplyShare!).toString();
1230
- const supplied1 = new Dec(supplyShares).mul(assetsData[collAsset1.symbol].tokenPerSupplyShare!).toString();
1231
-
1232
- const collUsedAsset0: Partial<FluidUsedAsset> = {
1233
- symbol: collAsset0.symbol,
1234
- collateral: true,
1235
- supplied: supplied0,
1236
- suppliedUsd: new Dec(supplied0).mul(assetsData[collAsset0.symbol].price).toString(),
1237
- isSupplied: new Dec(supplied0).gt(0),
1238
- };
1239
-
1240
- const collUsedAsset1: Partial<FluidUsedAsset> = {
1241
- symbol: collAsset1.symbol,
1242
- collateral: true,
1243
- supplied: supplied1,
1244
- suppliedUsd: new Dec(supplied1).mul(assetsData[collAsset1.symbol].price).toString(),
1245
- isSupplied: new Dec(supplied1).gt(0),
1246
- };
1247
-
1248
- const debtUsedAsset: Partial<FluidUsedAsset> = {
1249
- symbol: debtAsset.symbol,
1250
- borrowed,
1251
- borrowedUsd: new Dec(borrowed).mul(assetsData[debtAsset.symbol].price).toString(),
1252
- isBorrowed: new Dec(borrowed).gt(0),
1253
- };
1254
-
1255
- const usedAssets: FluidUsedAssets = ([
1256
- [collAsset0.symbol, collUsedAsset0],
1257
- [collAsset1.symbol, collUsedAsset1],
1258
- [debtAsset.symbol, debtUsedAsset],
1259
- ] as [string, FluidUsedAsset][])
1260
- .reduce((acc, [symbol, partialData]) => {
1261
- acc[symbol] = mergeUsedAssets(acc[symbol], partialData);
1262
- return acc;
1263
- }, {} as Record<string, FluidUsedAsset>) as FluidUsedAssets;
1264
-
1265
- return {
1266
- ...payload,
1267
- usedAssets,
1268
- supplyShares,
1269
- nftId: userPositionData.nftId.toString(),
1270
- ...(getFluidAggregatedData({
1271
- usedAssets,
1272
- assetsData,
1273
- marketData,
1274
- }, supplyShares) as FluidAggregatedVaultData),
1275
- };
1276
- };
1277
-
1278
- const parseT3UserData = (userPositionData: FluidUserPositionStructOutputStruct, vaultData: FluidMarketData): FluidVaultData => {
1279
- const {
1280
- assetsData,
1281
- marketData,
1282
- } = vaultData;
1283
-
1284
- const payload = {
1285
- owner: userPositionData.owner,
1286
- vaultId: marketData.vaultId,
1287
- ...EMPTY_FLUID_DATA,
1288
- lastUpdated: Date.now(),
1289
- };
1290
-
1291
- const collAsset = getAssetInfo(marketData.collAsset0);
1292
- const debtAsset0 = getAssetInfo(marketData.debtAsset0);
1293
- const debtAsset1 = getAssetInfo(marketData.debtAsset1);
1294
-
1295
- const supplied = getEthAmountForDecimals(userPositionData.supply.toString(), collAsset.decimals); // this is actual token supply
1296
- const borrowShares = getEthAmountForDecimals(userPositionData.borrow.toString(), 18); // this is actual token borrow
1297
-
1298
- const borrowed0 = new Dec(borrowShares).mul(assetsData[debtAsset0.symbol].tokenPerBorrowShare!).toString();
1299
- const borrowed1 = new Dec(borrowShares).mul(assetsData[debtAsset1.symbol].tokenPerBorrowShare!).toString();
1300
-
1301
- const collUsedAsset: Partial<FluidUsedAsset> = {
1302
- symbol: collAsset.symbol,
1303
- collateral: true,
1304
- supplied,
1305
- suppliedUsd: new Dec(supplied).mul(assetsData[collAsset.symbol].price).toString(),
1306
- isSupplied: new Dec(supplied).gt(0),
1307
- };
1308
-
1309
- const debtUsedAsset0: Partial<FluidUsedAsset> = {
1310
- symbol: debtAsset0.symbol,
1311
- borrowed: borrowed0,
1312
- borrowedUsd: new Dec(borrowed0).mul(assetsData[debtAsset0.symbol].price).toString(),
1313
- isBorrowed: new Dec(borrowed0).gt(0),
1314
- };
1315
-
1316
- const debtUsedAsset1: Partial<FluidUsedAsset> = {
1317
- symbol: debtAsset1.symbol,
1318
- borrowed: borrowed1,
1319
- borrowedUsd: new Dec(borrowed1).mul(assetsData[debtAsset1.symbol].price).toString(),
1320
- isBorrowed: new Dec(borrowed1).gt(0),
1321
- };
1322
-
1323
- const usedAssets: FluidUsedAssets = ([
1324
- [collAsset.symbol, collUsedAsset],
1325
- [debtAsset0.symbol, debtUsedAsset0],
1326
- [debtAsset1.symbol, debtUsedAsset1],
1327
- ] as [string, FluidUsedAsset][])
1328
- .reduce((acc, [symbol, partialData]) => {
1329
- acc[symbol] = mergeUsedAssets(acc[symbol], partialData);
1330
- return acc;
1331
- }, {} as Record<string, FluidUsedAsset>) as FluidUsedAssets;
1332
-
1333
-
1334
- return {
1335
- ...payload,
1336
- usedAssets,
1337
- borrowShares,
1338
- nftId: userPositionData.nftId.toString(),
1339
- ...(getFluidAggregatedData({
1340
- usedAssets,
1341
- assetsData,
1342
- marketData,
1343
- }, '', borrowShares) as FluidAggregatedVaultData),
1344
- };
1345
- };
1346
-
1347
- const parseT4UserData = (userPositionData: FluidUserPositionStructOutputStruct, vaultData: FluidMarketData): FluidVaultData => {
1348
- const {
1349
- assetsData,
1350
- marketData,
1351
- } = vaultData;
1352
-
1353
- const payload = {
1354
- owner: userPositionData.owner,
1355
- vaultId: marketData.vaultId,
1356
- ...EMPTY_FLUID_DATA,
1357
- lastUpdated: Date.now(),
1358
- };
1359
- const collAsset0 = getAssetInfo(marketData.collAsset0);
1360
- const collAsset1 = getAssetInfo(marketData.collAsset1);
1361
- const debtAsset0 = getAssetInfo(marketData.debtAsset0);
1362
- const debtAsset1 = getAssetInfo(marketData.debtAsset1);
1363
-
1364
- const supplyShares = getEthAmountForDecimals(userPositionData.supply.toString(), 18); // this is actual token supply
1365
- const borrowShares = getEthAmountForDecimals(userPositionData.borrow.toString(), 18); // this is actual token borrow
1366
-
1367
- const supplied0 = new Dec(supplyShares).mul(assetsData[collAsset0.symbol].tokenPerSupplyShare!).toString();
1368
- const supplied1 = new Dec(supplyShares).mul(assetsData[collAsset1.symbol].tokenPerSupplyShare!).toString();
1369
-
1370
- const borrowed0 = new Dec(borrowShares).mul(assetsData[debtAsset0.symbol].tokenPerBorrowShare!).toString();
1371
- const borrowed1 = new Dec(borrowShares).mul(assetsData[debtAsset1.symbol].tokenPerBorrowShare!).toString();
1372
-
1373
- const collUsedAsset0: Partial<FluidUsedAsset> = {
1374
- symbol: collAsset0.symbol,
1375
- collateral: true,
1376
- supplied: supplied0,
1377
- suppliedUsd: new Dec(supplied0).mul(assetsData[collAsset0.symbol].price).toString(),
1378
- isSupplied: new Dec(supplied0).gt(0),
1379
- };
1380
- const collUsedAsset1: Partial<FluidUsedAsset> = {
1381
- symbol: collAsset1.symbol,
1382
- collateral: true,
1383
- supplied: supplied1,
1384
- suppliedUsd: new Dec(supplied1).mul(assetsData[collAsset1.symbol].price).toString(),
1385
- isSupplied: new Dec(supplied1).gt(0),
1386
- };
1387
-
1388
- const debtUsedAsset0: Partial<FluidUsedAsset> = {
1389
- symbol: debtAsset0.symbol,
1390
- borrowed: borrowed0,
1391
- borrowedUsd: new Dec(borrowed0).mul(assetsData[debtAsset0.symbol].price).toString(),
1392
- isBorrowed: new Dec(borrowed0).gt(0),
1393
- };
1394
- const debtUsedAsset1: Partial<FluidUsedAsset> = {
1395
- symbol: debtAsset1.symbol,
1396
- borrowed: borrowed1,
1397
- borrowedUsd: new Dec(borrowed1).mul(assetsData[debtAsset1.symbol].price).toString(),
1398
- isBorrowed: new Dec(borrowed1).gt(0),
1399
- };
1400
-
1401
- const usedAssets: FluidUsedAssets = ([
1402
- [collAsset0.symbol, collUsedAsset0],
1403
- [collAsset1.symbol, collUsedAsset1],
1404
- [debtAsset0.symbol, debtUsedAsset0],
1405
- [debtAsset1.symbol, debtUsedAsset1],
1406
- ] as [string, FluidUsedAsset][])
1407
- .reduce((acc, [symbol, partialData]) => {
1408
- acc[symbol] = mergeUsedAssets(acc[symbol], partialData);
1409
- return acc;
1410
- }, {} as Record<string, FluidUsedAsset>) as FluidUsedAssets;
1411
-
1412
- return {
1413
- ...payload,
1414
- usedAssets,
1415
- supplyShares,
1416
- borrowShares,
1417
- nftId: userPositionData.nftId.toString(),
1418
- ...(getFluidAggregatedData({
1419
- usedAssets,
1420
- assetsData,
1421
- marketData,
1422
- }, supplyShares, borrowShares) as FluidAggregatedVaultData),
1423
- };
1424
- };
1425
-
1426
- const parseUserData = (userPositionData: FluidUserPositionStructOutputStruct, vaultData: FluidMarketData): FluidVaultData => {
1427
- const vaultType = vaultData.marketData.vaultType;
1428
- switch (vaultType) {
1429
- case FluidVaultType.T1:
1430
- return parseT1UserData(userPositionData, vaultData);
1431
- case FluidVaultType.T2:
1432
- return parseT2UserData(userPositionData, vaultData);
1433
- case FluidVaultType.T3:
1434
- return parseT3UserData(userPositionData, vaultData);
1435
- case FluidVaultType.T4:
1436
- return parseT4UserData(userPositionData, vaultData);
1437
- default:
1438
- throw new Error(`Unknown vault type: ${vaultType}`);
1439
- }
1440
- };
1441
-
1442
- export const _getFluidMarketData = async (provider: PublicClient, network: NetworkNumber, market: FluidMarketInfo) => {
1443
- const view = FluidViewContractViem(provider, network);
1444
-
1445
- const data = await view.read.getVaultData([market.marketAddress]);
1446
-
1447
- return parseMarketData(provider, data, network);
1448
- };
1449
-
1450
- export const getFluidMarketData = async (
1451
- provider: EthereumProvider,
1452
- network: NetworkNumber,
1453
- market: FluidMarketInfo,
1454
- ) => _getFluidMarketData(getViemProvider(provider, network), network, market);
1455
-
1456
- export const _getFluidVaultIdsForUser = async (
1457
- provider: Client,
1458
- network: NetworkNumber,
1459
- user: EthAddress,
1460
- ): Promise<string[]> => {
1461
- const view = FluidViewContractViem(provider, network);
1462
-
1463
- return (await view.read.getUserNftIds([user])).map((item: bigint) => item.toString());
1464
- };
1465
-
1466
- export const getFluidVaultIdsForUser = async (
1467
- provider: EthereumProvider,
1468
- network: NetworkNumber,
1469
- user: EthAddress,
1470
- ): Promise<string[]> => _getFluidVaultIdsForUser(getViemProvider(provider, network), network, user);
1471
-
1472
-
1473
- export const _getFluidPosition = async (
1474
- provider: Client,
1475
- network: NetworkNumber,
1476
- vaultId: string,
1477
- extractedState: {
1478
- assetsData: FluidAssetsData
1479
- marketData: InnerFluidMarketData,
1480
- },
1481
- ): Promise<FluidVaultData> => {
1482
- const view = FluidViewContractViem(provider, network);
1483
-
1484
- const data = await view.read.getPositionByNftId([BigInt(vaultId)]);
1485
-
1486
- const userPositionData = data[0];
1487
-
1488
- return parseUserData(userPositionData, extractedState);
1489
- };
1490
-
1491
- export const getFluidPosition = async (
1492
- provider: EthereumProvider,
1493
- network: NetworkNumber,
1494
- vaultId: string,
1495
- extractedState: {
1496
- assetsData: FluidAssetsData
1497
- marketData: InnerFluidMarketData,
1498
- },
1499
- ): Promise<FluidVaultData> => _getFluidPosition(getViemProvider(provider, network), network, vaultId, extractedState);
1500
-
1501
- export const _getFluidPositionWithMarket = async (provider: PublicClient, network: NetworkNumber, vaultId: string) => {
1502
- const view = FluidViewContractViem(provider, network);
1503
- const data = await view.read.getPositionByNftId([BigInt(vaultId)]);
1504
- const marketData = await parseMarketData(provider, data[1], network);
1505
- if (!marketData) {
1506
- return;
1507
- }
1508
- const userData = parseUserData(data[0], marketData);
1509
-
1510
- return {
1511
- userData,
1512
- marketData,
1513
- };
1514
- };
1515
-
1516
- export const getFluidPositionWithMarket = async (
1517
- provider: EthereumProvider,
1518
- network: NetworkNumber,
1519
- vaultId: string,
1520
- ) => _getFluidPositionWithMarket(getViemProvider(provider, network), network, vaultId);
1521
-
1522
- export const _getAllFluidMarketDataChunked = async (network: NetworkNumber, provider: PublicClient) => {
1523
- const versions = getFluidVersionsDataForNetwork(network);
1524
- const view = FluidViewContractViem(provider, network);
1525
- const data = await Promise.all(versions.map((version) => view.read.getVaultData([version.marketAddress])));
1526
- return Promise.all(data.map(async (item, i) => parseMarketData(provider, item, network)));
1527
- };
1528
-
1529
- export const getAllFluidMarketDataChunked = async (
1530
- network: NetworkNumber,
1531
- provider: EthereumProvider,
1532
- ) => _getAllFluidMarketDataChunked(network, getViemProvider(provider, network, { batch: { multicall: true } }));
1533
-
1534
- export const _getFluidTokenData = async (provider: Client, network: NetworkNumber, token: string) => {
1535
- const view = FluidViewContractViem(provider, network);
1536
- const fTokenAddress = getFTokenAddress(token, network);
1537
- const [
1538
- data,
1539
- rewardsApiResponse,
1540
- ] = await Promise.all([
1541
- await view.read.getFTokenData([fTokenAddress]),
1542
- fetch(`https://api.fluid.instadapp.io/v2/lending/${network}/tokens/${fTokenAddress}`),
1543
- ]);
1544
- let rewardsData = { rewards: [] } as any;
1545
- if (!rewardsApiResponse.ok) {
1546
- console.log('External API Failure: Failed to fetch fluid rewards APY');
1547
- } else {
1548
- rewardsData = await rewardsApiResponse.json();
1549
- }
1550
- const supplyRate = new Dec(rewardsData?.supplyRate || '0').div(100).toString();
1551
- const rewardRates = rewardsData?.rewards?.reduce((acc: Dec, item: any) => acc.add(new Dec(item.rate || '0').div(100)), new Dec(0)) || '0';
1552
- const stakeRate = new Dec(rewardsData?.asset?.stakingApr || '0').div(100).toString();
1553
- const decimals = data.decimals.toString();
1554
-
1555
- const depositRate = new Dec(getEthAmountForDecimals(data.convertToShares.toString(), decimals)).toString();
1556
- const withdrawRate = new Dec(getEthAmountForDecimals(data.convertToAssets.toString(), decimals)).toString();
1557
-
1558
- return {
1559
- fTokenAddress,
1560
- fTokenSymbol: data.symbol,
1561
- decimals,
1562
- totalDeposited: getEthAmountForDecimals(data.totalAssets.toString(), decimals),
1563
- withdrawable: getEthAmountForDecimals(data.withdrawable.toString(), decimals),
1564
- apy: new Dec(supplyRate).plus(rewardRates).plus(stakeRate).toString(),
1565
- depositRate,
1566
- withdrawRate,
1567
- };
1568
- };
1569
-
1570
- export const getFluidTokenData = async (
1571
- provider: EthereumProvider,
1572
- network: NetworkNumber,
1573
- token: string,
1574
- ) => _getFluidTokenData(getViemProvider(provider, network), network, token);
1575
-
1576
- const parseFDepositTokenData = (fTokenData: FluidFTokenDataStructOutput, userPosition: FluidUserEarnPositionStructOutput, apiData: any, fTokenAddress?: string) => {
1577
- const decimals = fTokenData.decimals.toString();
1578
- const depositRate = new Dec(getEthAmountForDecimals(fTokenData.convertToShares.toString(), decimals)).toString();
1579
- const withdrawRate = new Dec(getEthAmountForDecimals(fTokenData.convertToAssets.toString(), decimals)).toString();
1580
- const supplyRate = new Dec(apiData?.supplyRate || '0').div(100).toString();
1581
- const rewardRates = apiData?.rewards?.reduce((acc: Dec, item: any) => acc.add(new Dec(item.rate || '0').div(100)), new Dec(0)) || '0';
1582
- const stakeRate = new Dec(apiData?.asset?.stakingApr || '0').div(100).toString();
1583
- return {
1584
- fTokenAddress,
1585
- fTokenSymbol: fTokenData.symbol,
1586
- decimals,
1587
- totalDeposited: getEthAmountForDecimals(fTokenData.totalAssets.toString(), decimals),
1588
- withdrawable: getEthAmountForDecimals(fTokenData.withdrawable.toString(), decimals),
1589
- apy: new Dec(supplyRate).plus(rewardRates).plus(stakeRate).toString(),
1590
- depositRate,
1591
- withdrawRate,
1592
- deposited: getEthAmountForDecimals(userPosition.underlyingAssets.toString(), decimals),
1593
- depositedShares: getEthAmountForDecimals(userPosition.fTokenShares.toString(), decimals),
1594
- };
1595
- };
1596
-
1597
- export const _getFluidDepositData = async (provider: Client, network: NetworkNumber, token: string, address: EthAddress) => {
1598
- const view = FluidViewContractViem(provider, network);
1599
- const fTokenAddress = getFTokenAddress(token, network);
1600
- const [
1601
- [userPosition, fTokenData],
1602
- rewardsApiResponse,
1603
- ] = await Promise.all([
1604
- view.read.getUserEarnPositionWithFToken([fTokenAddress, address]),
1605
- fetch(`https://api.fluid.instadapp.io/v2/lending/${network}/tokens/${fTokenAddress}`),
1606
- ]);
1607
- let rewardsData = { rewards: [] };
1608
- if (!rewardsApiResponse.ok) {
1609
- console.log('External API Failure: Failed to fetch fluid rewards APY');
1610
- } else {
1611
- rewardsData = await rewardsApiResponse.json();
1612
- }
1613
-
1614
- return parseFDepositTokenData(fTokenData, userPosition, rewardsData, fTokenAddress);
1615
- };
1616
-
1617
- export const getFluidDepositData = async (
1618
- provider: EthereumProvider,
1619
- network: NetworkNumber,
1620
- token: string,
1621
- address: EthAddress,
1622
- ) => _getFluidDepositData(getViemProvider(provider, network), network, token, address);
1623
-
1624
- export const _getAllUserEarnPositionsWithFTokens = async (provider: Client, network: NetworkNumber, user: EthAddress) => {
1625
- const view = FluidViewContractViem(provider, network);
1626
- const [
1627
- [userPositions, fTokensData],
1628
- rewardsApiResponse,
1629
- ] = await Promise.all([
1630
- view.read.getAllUserEarnPositionsWithFTokens([user]),
1631
- fetch(`https://api.fluid.instadapp.io/v2/lending/${network}/tokens`),
1632
- ]);
1633
-
1634
- let rewardsData = {
1635
- data: [{ address: ZERO_ADDRESS, rewards: [] }],
1636
- };
1637
- if (!rewardsApiResponse.ok) {
1638
- console.log('External API Failure: Failed to fetch fluid rewards APY');
1639
- } else {
1640
- rewardsData = await rewardsApiResponse.json();
1641
- }
1642
-
1643
- const parsedRes = fTokensData.reduce<ReturnType<typeof parseFDepositTokenData>[]>((acc, fTokenData, i) => {
1644
- const userPosition = userPositions[i];
1645
- const deposited = userPosition?.underlyingAssets;
1646
-
1647
- if (Number(deposited) > 0) {
1648
- const fTokenAddress = fTokenData.tokenAddress;
1649
- const apiData = rewardsData.data.find((item: any) => compareAddresses(item.address, fTokenAddress));
1650
- acc.push(parseFDepositTokenData(fTokenData, userPosition, apiData, fTokenAddress));
1651
- }
1652
-
1653
- return acc;
1654
- }, []);
1655
-
1656
- return parsedRes;
1657
- };
1658
-
1659
- export const getAllUserEarnPositionsWithFTokens = async (
1660
- provider: EthereumProvider,
1661
- network: NetworkNumber,
1662
- user: EthAddress,
1663
- ) => _getAllUserEarnPositionsWithFTokens(getViemProvider(provider, network), network, user);
1664
-
1665
- export const _getUserPositions = async (provider: PublicClient, network: NetworkNumber, user: EthAddress) => {
1666
- const view = FluidViewContractViem(provider, network);
1667
-
1668
- const data = await view.read.getUserPositions([user]);
1669
-
1670
- const parsedMarketData = (await Promise.all(data[1].map(async (vaultData) => parseMarketData(provider, vaultData, network))));
1671
-
1672
- const userData = data[0].map((position, i) => (parsedMarketData[i] && { ...parseUserData(position, parsedMarketData[i]) }));
1673
-
1674
- return parsedMarketData.map((market, i) => ({
1675
- marketData: market,
1676
- userData: userData[i],
1677
- })).filter(md => md.marketData !== undefined);
1678
- };
1679
-
1680
- export const getUserPositions = async (
1681
- provider: EthereumProvider,
1682
- network: NetworkNumber,
1683
- user: EthAddress,
1684
- ) => _getUserPositions(getViemProvider(provider, network), network, user);
1685
-
1686
- const getTokenPricePortfolio = async (token: string, provider: PublicClient, network: NetworkNumber) => {
1687
- if (token === 'ETH') {
1688
- const ethFeedContract = ETHPriceFeedContractViem(provider, network);
1689
- return ethFeedContract.read.latestAnswer();
1690
- }
1691
- if (token === 'WBTC') {
1692
- const wbtcFeedContract = BTCPriceFeedContractViem(provider, network);
1693
- return wbtcFeedContract.read.latestAnswer();
1694
- }
1695
- if (token === 'wstETH') {
1696
- return getWstETHPrice(provider, network);
1697
- }
1698
- if (token === 'weETH' && network !== NetworkNumber.Plasma) {
1699
- return getWeETHPrice(provider, network);
1700
- }
1701
-
1702
- if (token === 'wrsETH') {
1703
- return getWsrETHPrice(provider, network);
1704
- }
1705
-
1706
- if (token === 'syrupUSDT') {
1707
- return getSyrupUSDTPrice(provider, network);
1708
- }
1709
-
1710
- if (token === 'wstUSR') {
1711
- return getWstUSRPrice(provider, network);
1712
- }
1713
-
1714
- const isMainnet = isMainnetNetwork(network);
1715
- const chainLinkFeedAddress = getChainlinkAssetAddress(token, network);
1716
- if (isMainnet) {
1717
- const feedRegistryContract = FeedRegistryContractViem(provider, NetworkNumber.Eth);
1718
- return feedRegistryContract.read.latestAnswer([chainLinkFeedAddress, USD_QUOTE]);
1719
- }
1720
-
1721
- const feedRegistryContract = DFSFeedRegistryContractViem(provider, network);
1722
- return feedRegistryContract.read.latestRoundData([chainLinkFeedAddress, USD_QUOTE]);
1723
- };
1724
-
1725
- const tokensWithoutChainlinkPrices = ['sUSDS', 'USDA', 'ezETH', 'rsETH', 'weETHs', 'LBTC'];
1726
-
1727
- const handleTokenWithoutChainlinkPrice = (token: string, prices: Record<string, string>) => {
1728
- if (token === 'sUSDS') {
1729
- return new Dec('107057929').div(1e8).toString();
1730
- }
1731
- if (token === 'USDA') {
1732
- return new Dec('100000000').div(1e8).toString();
1733
- }
1734
- if (token === 'wstUSR') {
1735
- return new Dec('111280000').div(1e8).toString();
1736
- }
1737
- if (token === 'ezETH') {
1738
- return new Dec(prices.ETH).mul(1.06).toString();
1739
- }
1740
- if (token === 'rsETH') {
1741
- return new Dec(prices.wstETH).mul(1.0557).toString();
1742
- }
1743
- if (token === 'weETHs') {
1744
- return new Dec(prices.wstETH).mul(1.032).toString();
1745
- }
1746
- if (token === 'LBTC') {
1747
- return prices.WBTC;
1748
- }
1749
- return '0';
1750
- };
1751
-
1752
- const getTokensPricesForPortfolio = async (tokens: string[], provider: PublicClient, network: NetworkNumber) => {
1753
- const tokensWithChainlinkPrices = tokens.filter((token) => !tokensWithoutChainlinkPrices.includes(token));
1754
- const pricesFromChainlink: Record<string, string> = {};
1755
- await Promise.all(tokensWithChainlinkPrices.map(async (token) => {
1756
- try {
1757
- const price = await getTokenPricePortfolio(token, provider, network);
1758
- if (typeof price === 'string') pricesFromChainlink[token] = price;
1759
- else if (typeof price === 'bigint') pricesFromChainlink[token] = new Dec(price).div(1e8).toString();
1760
- else pricesFromChainlink[token] = new Dec(price[1]!.toString() as string).div(1e8).toString();
1761
- } catch (error: any) {
1762
- console.error(`Error occured while fetching price for ${token}: ${error.message}`);
1763
- }
1764
- }));
1765
- tokens.forEach((token) => {
1766
- if (tokensWithoutChainlinkPrices.includes(token)) {
1767
- pricesFromChainlink[token] = handleTokenWithoutChainlinkPrice(token, pricesFromChainlink);
1768
- }
1769
- });
1770
-
1771
- return pricesFromChainlink;
1772
- };
1773
-
1774
- export const _getUserPositionsPortfolio = async (provider: PublicClient, network: NetworkNumber, user: EthAddress) => {
1775
- const view = FluidViewContractViem(provider, network);
1776
-
1777
- const data = await view.read.getUserPositions([user]);
1778
- const tokens = Array.from(new Set(data[1].map((vaultData) => {
1779
- const vaultTokens = [getAssetInfoByAddress(vaultData.supplyToken0, network).symbol, getAssetInfoByAddress(vaultData.borrowToken0, network).symbol];
1780
- if (vaultData.supplyToken1 && !compareAddresses(ZERO_ADDRESS, vaultData.supplyToken1)) vaultTokens.push(getAssetInfoByAddress(vaultData.supplyToken1, network).symbol);
1781
- if (vaultData.borrowToken1 && !compareAddresses(ZERO_ADDRESS, vaultData.borrowToken1)) vaultTokens.push(getAssetInfoByAddress(vaultData.borrowToken1, network).symbol);
1782
- return vaultTokens;
1783
- }).flat()));
1784
-
1785
- if (tokens.length === 0) return [];
1786
- // ETH and WBTC needed for other tokens prices
1787
- if (!tokens.includes('ETH')) tokens.push('ETH');
1788
- if (!tokens.includes('WBTC')) tokens.push('WBTC');
1789
-
1790
- const tokenPrices = await getTokensPricesForPortfolio(tokens, provider, network);
1791
-
1792
- const parsedMarketData = (await Promise.all(data[1].map(async (vaultData) => parseMarketData(provider, vaultData, network, tokenPrices))));
1793
-
1794
- const userData = data[0].map((position, i) => (parsedMarketData[i] && { ...parseUserData(position, parsedMarketData[i]) }));
1795
-
1796
- return parsedMarketData.map((market, i) => ({
1797
- marketData: market,
1798
- userData: userData[i],
1799
- })).filter(md => md.marketData !== undefined);
1800
- };
1
+ import Dec from 'decimal.js';
2
+ import {
3
+ assetAmountInEth, AssetData, getAssetInfo, getAssetInfoByAddress,
4
+ } from '@defisaver/tokens';
5
+ import { Client, PublicClient } from 'viem';
6
+ import {
7
+ EthAddress, EthereumProvider, IncentiveKind, NetworkNumber,
8
+ } from '../types/common';
9
+ import {
10
+ FluidAggregatedVaultData,
11
+ FluidAssetData,
12
+ FluidAssetsData,
13
+ FluidFTokenDataStructOutput,
14
+ FluidMarketData,
15
+ FluidMarketInfo,
16
+ FluidUsedAsset,
17
+ FluidUsedAssets,
18
+ FluidUserEarnPositionStructOutput,
19
+ FluidUserPositionStructOutputStruct,
20
+ FluidVaultData,
21
+ FluidVaultDataStructOutputStruct,
22
+ FluidVaultType,
23
+ InnerFluidMarketData,
24
+ } from '../types';
25
+ import {
26
+ BTCPriceFeedContractViem,
27
+ DFSFeedRegistryContractViem,
28
+ ETHPriceFeedContractViem,
29
+ FeedRegistryContractViem,
30
+ FluidViewContractViem,
31
+ } from '../contracts';
32
+ import {
33
+ compareAddresses,
34
+ DEFAULT_TIMEOUT,
35
+ getEthAmountForDecimals,
36
+ getNativeAssetFromWrapped,
37
+ isMainnetNetwork,
38
+ } from '../services/utils';
39
+ import {
40
+ getFluidAggregatedData,
41
+ mergeAssetData,
42
+ mergeUsedAssets,
43
+ parseDexBorrowData,
44
+ parseDexSupplyData,
45
+ } from '../helpers/fluidHelpers';
46
+ import { getFluidMarketInfoById, getFluidVersionsDataForNetwork, getFTokenAddress } from '../markets';
47
+ import { USD_QUOTE, ZERO_ADDRESS } from '../constants';
48
+ import {
49
+ getChainlinkAssetAddress,
50
+ getSyrupUSDTChainLinkPriceCalls,
51
+ getSyrupUSDTPrice,
52
+ getWeETHChainLinkPriceCalls,
53
+ getWeETHPrice,
54
+ getWsrETHChainLinkPriceCalls,
55
+ getWsrETHPrice,
56
+ getWstETHChainLinkPriceCalls,
57
+ getWstETHPrice,
58
+ getWstETHPriceFluid,
59
+ getWstUSRChainLinkPriceCalls,
60
+ getWstUSRPrice,
61
+ parseSyrupUSDTPriceCalls,
62
+ parseWeETHPriceCalls,
63
+ parseWrsETHPriceCalls,
64
+ parseWstETHPriceCalls,
65
+ parseWstUSRPriceCalls,
66
+ } from '../services/priceService';
67
+ import { getStakingApy, STAKING_ASSETS } from '../staking';
68
+ import { getViemProvider } from '../services/viem';
69
+
70
+ export const EMPTY_USED_ASSET = {
71
+ isSupplied: false,
72
+ isBorrowed: false,
73
+ supplied: '0',
74
+ suppliedUsd: '0',
75
+ borrowed: '0',
76
+ borrowedUsd: '0',
77
+ symbol: '',
78
+ collateral: false,
79
+ };
80
+
81
+ const parseVaultType = (vaultType: number) => {
82
+ switch (vaultType) {
83
+ case 10000: return FluidVaultType.T1;
84
+ case 20000: return FluidVaultType.T2;
85
+ case 30000: return FluidVaultType.T3;
86
+ case 40000: return FluidVaultType.T4;
87
+ default: return FluidVaultType.Unknown;
88
+ }
89
+ };
90
+
91
+ const getChainLinkPricesForTokens = async (
92
+ tokens: string[],
93
+ network: NetworkNumber,
94
+ client: PublicClient,
95
+ ): Promise<{ [key: string]: string }> => {
96
+ const isMainnet = isMainnetNetwork(network);
97
+
98
+ const noDuplicateTokens = new Array(...new Set(tokens));
99
+
100
+ const btcFeedContract = BTCPriceFeedContractViem(client, network);
101
+ const ethFeedContract = ETHPriceFeedContractViem(client, network);
102
+
103
+ const staticCalls = [
104
+ {
105
+ address: ethFeedContract.address,
106
+ abi: ethFeedContract.abi,
107
+ functionName: 'latestAnswer',
108
+ args: [],
109
+ },
110
+ {
111
+ address: btcFeedContract.address,
112
+ abi: btcFeedContract.abi,
113
+ functionName: 'latestAnswer',
114
+ args: [],
115
+ },
116
+ ];
117
+
118
+ // @ts-ignore
119
+ const _calls = noDuplicateTokens.flatMap((address) => {
120
+ const assetInfo = getAssetInfoByAddress(address, network);
121
+ const isTokenUSDA = assetInfo.symbol === 'USDA';
122
+ if (isTokenUSDA) return;
123
+ const chainLinkFeedAddress = getChainlinkAssetAddress(assetInfo.symbol, network);
124
+
125
+ if (assetInfo.symbol === 'wstETH') return getWstETHChainLinkPriceCalls(client, network);
126
+ if (assetInfo.symbol === 'weETH' && network !== NetworkNumber.Plasma) return getWeETHChainLinkPriceCalls(client, network);
127
+ if (assetInfo.symbol === 'wrsETH' && network === NetworkNumber.Plasma) return getWsrETHChainLinkPriceCalls(client, network);
128
+ if (assetInfo.symbol === 'syrupUSDT') return getSyrupUSDTChainLinkPriceCalls(client, network);
129
+ if (assetInfo.symbol === 'wstUSR') return getWstUSRChainLinkPriceCalls(client, network);
130
+
131
+ if (isMainnet) {
132
+ const feedRegistryContract = FeedRegistryContractViem(client, NetworkNumber.Eth);
133
+ return ({
134
+ address: feedRegistryContract.address,
135
+ abi: feedRegistryContract.abi,
136
+ functionName: 'latestAnswer',
137
+ args: [chainLinkFeedAddress, USD_QUOTE],
138
+ });
139
+ }
140
+
141
+ const feedRegistryContract = DFSFeedRegistryContractViem(client, network);
142
+ return ({
143
+ address: feedRegistryContract.address,
144
+ abi: feedRegistryContract.abi,
145
+ functionName: 'latestRoundData',
146
+ args: [chainLinkFeedAddress, USD_QUOTE],
147
+ });
148
+ });
149
+
150
+ const calls = [...staticCalls, ..._calls].filter((call) => call);
151
+ // @ts-ignore
152
+ const results = await client.multicall({ contracts: calls });
153
+
154
+ const ethPriceChainlink = new Dec(results[0].result as string).div(1e8).toString();
155
+ const btcPriceChainlink = new Dec(results[1].result as string).div(1e8).toString();
156
+
157
+ let offset = 2; // wstETH and weETH has 3 calls, while others have only 1, so we need to keep track. First 2 are static calls for eth and btc prices
158
+ return noDuplicateTokens.reduce((acc, token, i) => {
159
+ const assetInfo = getAssetInfoByAddress(token, network);
160
+ switch (assetInfo.symbol) {
161
+ case 'USDA':
162
+ acc[token] = '100000000';
163
+ break;
164
+
165
+ case 'wstETH': {
166
+ const {
167
+ ethPrice,
168
+ wstETHRate,
169
+ } = parseWstETHPriceCalls(
170
+ results[i + offset].result!.toString(),
171
+ // @ts-ignore
172
+ results[i + offset + 1].result[1]!.toString(),
173
+ results[i + offset + 2].result!.toString(),
174
+ );
175
+ offset += 2;
176
+ acc[token] = new Dec(ethPrice).mul(wstETHRate).toString();
177
+ break;
178
+ }
179
+
180
+ case 'wrsETH': {
181
+ const {
182
+ ethPrice,
183
+ wrsETHRate,
184
+ } = parseWrsETHPriceCalls(
185
+ // @ts-ignore
186
+ results[i + offset].result[1]!.toString(),
187
+ // @ts-ignore
188
+ results[i + offset + 1].result[1]!.toString(),
189
+ );
190
+ offset += 1;
191
+ acc[token] = new Dec(ethPrice).mul(wrsETHRate).toString();
192
+ break;
193
+ }
194
+
195
+ case 'syrupUSDT': {
196
+ const {
197
+ syrupUSDTRate,
198
+ USDTRate,
199
+ } = parseSyrupUSDTPriceCalls(
200
+ // @ts-ignore
201
+ results[i + offset].result[1]!.toString(),
202
+ // @ts-ignore
203
+ results[i + offset + 1].result[1]!.toString(),
204
+ );
205
+ offset += 1;
206
+ acc[token] = new Dec(syrupUSDTRate).mul(USDTRate).toString();
207
+ break;
208
+ }
209
+ case 'wstUSR': {
210
+ const {
211
+ wstUSRRate,
212
+ USRRate,
213
+ } = parseWstUSRPriceCalls(
214
+ // @ts-ignore
215
+ results[i + offset].result[1]!.toString(),
216
+ // @ts-ignore
217
+ results[i + offset + 1].result[1]!.toString(),
218
+ );
219
+ offset += 1;
220
+ acc[token] = new Dec(wstUSRRate).mul(USRRate).toString();
221
+ break;
222
+ }
223
+
224
+ // TODO: These addresses do not have chainlink feeds, so we need to handle them separately, this is hotfix
225
+ case 'ezETH': {
226
+ acc[token] = new Dec(ethPriceChainlink).mul(1.049).toString();
227
+ break;
228
+ }
229
+ case 'rsETH': {
230
+ acc[token] = new Dec(ethPriceChainlink).mul(1.0454).toString();
231
+ break;
232
+ }
233
+ case 'weETHs': {
234
+ acc[token] = new Dec(ethPriceChainlink).mul(1.026).toString();
235
+ break;
236
+ }
237
+ case 'LBTC': {
238
+ acc[token] = new Dec(btcPriceChainlink).toString();
239
+ break;
240
+ }
241
+ case 'sUSDS': {
242
+ acc[token] = new Dec('105276929').toString();
243
+ break;
244
+ }
245
+
246
+ case 'weETH': {
247
+ if (network !== NetworkNumber.Plasma) {
248
+ const {
249
+ ethPrice,
250
+ weETHRate,
251
+ } = parseWeETHPriceCalls(
252
+ results[i + offset].result!.toString(),
253
+ // @ts-ignore
254
+ results[i + offset + 1].result[1]!.toString(),
255
+ results[i + offset + 2].result!.toString(),
256
+ );
257
+ offset += 2;
258
+ acc[token] = new Dec(ethPrice).mul(weETHRate).toString();
259
+ // @ts-ignore
260
+ } else if (results[i + offset].result?.[1]) {
261
+ // For Plasma, use default chainlink feed (latestRoundData format)
262
+ // @ts-ignore
263
+ acc[token] = new Dec(results[i + offset].result[1]!.toString() as string).div(1e8).toString();
264
+ } else if (results[i + offset].result) {
265
+ // For Plasma, use default chainlink feed (latestAnswer format)
266
+ acc[token] = new Dec(results[i + offset].result!.toString() as string).div(1e8).toString();
267
+ } else {
268
+ acc[token] = '0';
269
+ }
270
+ break;
271
+ }
272
+
273
+ default:
274
+ // @ts-ignore
275
+ if (results[i + offset].result?.[1]) {
276
+ // @ts-ignore
277
+ acc[token] = new Dec(results[i + offset].result[1]!.toString() as string).div(1e8).toString();
278
+ } else if (results[i + offset].result) {
279
+ acc[token] = new Dec(results[i + offset].result!.toString() as string).div(1e8).toString();
280
+ } else acc[token] = '0';
281
+ break;
282
+ }
283
+ return acc;
284
+ }, {} as { [key: string]: string });
285
+ };
286
+
287
+
288
+ const getTokenPriceFromChainlink = async (asset: AssetData, network: NetworkNumber, provider: PublicClient) => {
289
+ if (asset.symbol === 'sUSDS') {
290
+ return new Dec('105276929').div(1e8).toString();
291
+ }
292
+ const isTokenUSDA = asset.symbol === 'USDA';
293
+ const isMainnet = isMainnetNetwork(network);
294
+ const loanTokenFeedAddress = getChainlinkAssetAddress(asset.symbol, network);
295
+
296
+ let loanTokenPrice;
297
+ if (asset.symbol === 'wstETH') {
298
+ // need to handle wstETH for l2s inside getWstETHPrice
299
+ loanTokenPrice = await getWstETHPriceFluid(provider, network);
300
+ } else if (isMainnet) {
301
+ const feedRegistryContract = FeedRegistryContractViem(provider, NetworkNumber.Eth);
302
+ loanTokenPrice = isTokenUSDA ? '100000000' : await feedRegistryContract.read.latestAnswer([loanTokenFeedAddress, USD_QUOTE]);
303
+ } else {
304
+ // Currently only base network is supported
305
+ const feedRegistryContract = DFSFeedRegistryContractViem(provider, network);
306
+ try {
307
+ const roundPriceData = isTokenUSDA ? [0, '100000000'] : await feedRegistryContract.read.latestRoundData([loanTokenFeedAddress, USD_QUOTE]);
308
+ loanTokenPrice = roundPriceData[1].toString();
309
+ } catch (err) {
310
+ console.error(`Error fetching price for ${asset.symbol} on ${network}: ${err}`);
311
+ loanTokenPrice = '0';
312
+ }
313
+ }
314
+
315
+ return new Dec(loanTokenPrice).div(1e8).toString();
316
+ };
317
+
318
+ const getMarketRateForDex = (token1PerShare: string, token0PerShare: string, rate0: string, rate1: string, price0: string, price1: string) => {
319
+ const token0PerShareUsd = new Dec(token0PerShare).mul(price0).toString();
320
+ const token1PerShareUsd = new Dec(token1PerShare).mul(price1).toString();
321
+ const sharesCombinedUsd = new Dec(token0PerShareUsd).plus(token1PerShareUsd);
322
+
323
+ const rate0PerShare = new Dec(rate0).mul(token0PerShareUsd).div(sharesCombinedUsd).toString();
324
+
325
+ const rate1PerShare = new Dec(rate1).mul(token1PerShareUsd).div(sharesCombinedUsd).toString();
326
+
327
+ return new Dec(rate0PerShare).plus(rate1PerShare).toString();
328
+ };
329
+
330
+ const getAdditionalMarketRateForDex = (token1PerShare: string, token0PerShare: string, incentiveSupplyRate0: string, incentiveSupplyRate1: string, price0: string, price1: string) => {
331
+ const token0PerShareUsd = new Dec(token0PerShare).mul(price0).toString();
332
+ const token1PerShareUsd = new Dec(token1PerShare).mul(price1).toString();
333
+ const sharesCombinedUsd = new Dec(token0PerShareUsd).plus(token1PerShareUsd);
334
+
335
+ const rate0PerShare = incentiveSupplyRate0 ? new Dec(incentiveSupplyRate0).mul(token0PerShareUsd).div(sharesCombinedUsd).toString() : 0;
336
+
337
+ const rate1PerShare = incentiveSupplyRate1 ? new Dec(incentiveSupplyRate1).mul(token1PerShareUsd).div(sharesCombinedUsd).toString() : 0;
338
+
339
+ return new Dec(rate0PerShare).plus(rate1PerShare).toString();
340
+ };
341
+
342
+ const getTradingApy = async (poolAddress: EthAddress) => {
343
+ let res;
344
+ try {
345
+ res = await fetch(`https://api.fluid.instadapp.io/v2/1/dexes/${poolAddress}/apy`,
346
+ { signal: AbortSignal.timeout(DEFAULT_TIMEOUT) });
347
+ } catch (e) {
348
+ console.error('External API Failure: Fluid Trading Apy');
349
+ return '0';
350
+ }
351
+ if (!res.ok) {
352
+ return '0';
353
+ }
354
+ const data = await res.json();
355
+ return new Dec(data.tradingApy).div(100).toString();
356
+ };
357
+
358
+ const parseT1MarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null) => {
359
+ const collAssetContract = getAssetInfoByAddress(data.supplyToken0, network);
360
+ const collAsset = getAssetInfo(getNativeAssetFromWrapped(collAssetContract.symbol), network);
361
+ const debtAssetContract = getAssetInfoByAddress(data.borrowToken0, network);
362
+ const debtAsset = getAssetInfo(getNativeAssetFromWrapped(debtAssetContract.symbol), network);
363
+
364
+ const supplyRate = new Dec(data.supplyRateVault).div(100).toString();
365
+ const borrowRate = new Dec(data.borrowRateVault).div(100).toString();
366
+
367
+ const oracleScaleFactor = new Dec(27).add(debtAsset.decimals).sub(collAsset.decimals).toString();
368
+ const oracleScale = new Dec(10).pow(oracleScaleFactor).toString();
369
+ const oraclePrice = new Dec(data.oraclePriceOperate).div(oracleScale).toString();
370
+ let debtPriceParsed = '0';
371
+ if (tokenPrices) {
372
+ debtPriceParsed = tokenPrices[debtAsset.symbol] || '0';
373
+ } else {
374
+ debtPriceParsed = await getTokenPriceFromChainlink(debtAssetContract, network, provider);
375
+ }
376
+
377
+ const collAssetData: FluidAssetData = {
378
+ symbol: collAsset.symbol,
379
+ address: collAsset.address,
380
+ price: new Dec(debtPriceParsed).mul(oraclePrice).toString(),
381
+ totalSupply: data.totalSupplyVault.toString(),
382
+ totalBorrow: data.totalBorrowVault.toString(),
383
+ canBeSupplied: true,
384
+ canBeBorrowed: false,
385
+ supplyRate,
386
+ borrowRate: '0',
387
+ supplyIncentives: [],
388
+ borrowIncentives: [],
389
+ };
390
+
391
+ if (STAKING_ASSETS.includes(collAsset.symbol)) {
392
+ collAssetData.supplyIncentives.push({
393
+ apy: await getStakingApy(collAsset.symbol),
394
+ token: collAssetData.symbol,
395
+ incentiveKind: IncentiveKind.Staking,
396
+ description: `Native ${collAssetData.symbol} yield.`,
397
+ });
398
+ }
399
+
400
+ const debtAssetData: FluidAssetData = {
401
+ symbol: debtAsset.symbol,
402
+ address: debtAsset.address,
403
+ price: debtPriceParsed,
404
+ totalSupply: data.totalSupplyVault.toString(),
405
+ totalBorrow: data.totalBorrowVault.toString(),
406
+ canBeSupplied: false,
407
+ canBeBorrowed: true,
408
+ supplyRate: '0',
409
+ borrowRate,
410
+ supplyIncentives: [],
411
+ borrowIncentives: [],
412
+ };
413
+ if (STAKING_ASSETS.includes(debtAssetData.symbol)) {
414
+ debtAssetData.borrowIncentives.push({
415
+ apy: new Dec(await getStakingApy(debtAsset.symbol)).mul(-1).toString(),
416
+ token: debtAssetData.symbol,
417
+ incentiveKind: IncentiveKind.Reward,
418
+ description: `Due to the native yield of ${debtAssetData.symbol}, the value of the debt would increase over time.`,
419
+ });
420
+ }
421
+
422
+ const assetsData = {
423
+ [collAsset.symbol]: collAssetData,
424
+ [debtAsset.symbol]: debtAssetData,
425
+ };
426
+ const marketInfo = getFluidMarketInfoById(+(data.vaultId.toString()), network);
427
+ const totalSupplyVault = getEthAmountForDecimals(data.totalSupplyVault.toString(), collAsset.decimals);
428
+ const totalBorrowVault = getEthAmountForDecimals(data.totalBorrowVault.toString(), debtAsset.decimals);
429
+
430
+ const liqRatio = new Dec(data.liquidationThreshold).div(100).toString();
431
+ const liquidationMaxLimit = new Dec(data.liquidationMaxLimit).div(100).toString();
432
+ const liqFactor = new Dec(data.liquidationThreshold).div(10_000).toString();
433
+
434
+ const marketData = {
435
+ vaultId: +(data.vaultId.toString()),
436
+ vaultValue: marketInfo?.value,
437
+ isSmartColl: data.isSmartColl,
438
+ isSmartDebt: data.isSmartDebt,
439
+ marketAddress: data.vault,
440
+ vaultType: parseVaultType(+(data.vaultType.toString())),
441
+ oracle: data.oracle,
442
+ liquidationPenaltyPercent: new Dec(data.liquidationPenalty).div(100).toString(),
443
+ collFactor: new Dec(data.collateralFactor).div(10000).toString(), // we want actual factor, not in %, so we divide by 10000 instead of 100
444
+ liquidationRatio: liqRatio,
445
+ liqFactor,
446
+ minRatio: new Dec(1).div(liqFactor).mul(100).toString(),
447
+ collAsset0: collAsset.symbol,
448
+ debtAsset0: debtAsset.symbol,
449
+ totalPositions: data.totalPositions.toString(),
450
+ totalSupplyVault,
451
+ totalBorrowVault,
452
+ totalSupplyVaultUsd: new Dec(totalSupplyVault).mul(collAssetData.price).toString(),
453
+ totalBorrowVaultUsd: new Dec(totalBorrowVault).mul(debtAssetData.price).toString(),
454
+ withdrawalLimit: getEthAmountForDecimals(data.withdrawalLimit.toString(), collAsset.decimals),
455
+ withdrawableUntilLimit: getEthAmountForDecimals(data.withdrawableUntilLimit.toString(), collAsset.decimals),
456
+ withdrawable: getEthAmountForDecimals(data.withdrawable.toString(), collAsset.decimals),
457
+ borrowLimit: getEthAmountForDecimals(data.borrowLimit.toString(), debtAsset.decimals),
458
+ borrowableUntilLimit: getEthAmountForDecimals(data.borrowableUntilLimit.toString(), debtAsset.decimals),
459
+ borrowable: getEthAmountForDecimals(data.borrowable.toString(), debtAsset.decimals),
460
+ borrowLimitUtilization: getEthAmountForDecimals(data.borrowLimitUtilization.toString(), debtAsset.decimals),
461
+ maxBorrowLimit: getEthAmountForDecimals(data.maxBorrowLimit.toString(), debtAsset.decimals),
462
+ baseBorrowLimit: getEthAmountForDecimals(data.baseBorrowLimit.toString(), debtAsset.decimals),
463
+ minimumBorrowing: getEthAmountForDecimals(data.minimumBorrowing.toString(), debtAsset.decimals),
464
+ liquidationMaxLimit,
465
+ borrowRate,
466
+ supplyRate,
467
+ oraclePrice,
468
+ incentiveSupplyRate: collAssetData.supplyIncentives[0]?.apy || '0',
469
+ incentiveBorrowRate: debtAssetData.borrowIncentives[0]?.apy || '0',
470
+ };
471
+
472
+ return {
473
+ assetsData,
474
+ marketData,
475
+ } as FluidMarketData;
476
+ };
477
+
478
+ const parseT2MarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null) => {
479
+ const collAsset0Contract = getAssetInfoByAddress(data.supplyToken0, network);
480
+ const collAsset0 = getAssetInfo(getNativeAssetFromWrapped(collAsset0Contract.symbol), network);
481
+ const collAsset1Contract = getAssetInfoByAddress(data.supplyToken1, network);
482
+ const collAsset1 = getAssetInfo(getNativeAssetFromWrapped(collAsset1Contract.symbol), network);
483
+ const debtAssetContract = getAssetInfoByAddress(data.borrowToken0, network);
484
+ const debtAsset = getAssetInfo(getNativeAssetFromWrapped(debtAssetContract.symbol), network);
485
+
486
+ // 18 because collateral is represented in shares for which they use 18 decimals
487
+ const oracleScaleFactor = new Dec(27).add(debtAsset.decimals).sub(18).toString();
488
+ const oracleScale = new Dec(10).pow(oracleScaleFactor).toString();
489
+ const oraclePrice = new Dec(data.oraclePriceOperate).div(oracleScale).toString();
490
+
491
+ let prices: Record<string, string> = {};
492
+ if (tokenPrices) {
493
+ prices = tokenPrices;
494
+ } else {
495
+ prices = await getChainLinkPricesForTokens([collAsset0Contract.address, collAsset1Contract.address, debtAssetContract.address], network, provider);
496
+ }
497
+
498
+ const {
499
+ supplyDexFee,
500
+ totalSupplyShares,
501
+ supplyRate1,
502
+ totalSupplyToken1,
503
+ token0PerSupplyShare,
504
+ token1PerSupplyShare,
505
+ totalSupplyToken0,
506
+ maxSupplyShares,
507
+ withdrawableToken0,
508
+ withdrawable0,
509
+ withdrawableToken1,
510
+ withdrawable1,
511
+ supplyRate0,
512
+ utilizationSupply0,
513
+ utilizationSupply1,
514
+ withdrawableShares,
515
+ reservesSupplyToken0,
516
+ reservesSupplyToken1,
517
+ } = parseDexSupplyData(data.dexSupplyData, collAsset0.symbol, collAsset1.symbol);
518
+
519
+ const collFirstAssetData: Partial<FluidAssetData> = {
520
+ symbol: collAsset0.symbol,
521
+ address: collAsset0.address,
522
+ price: prices[tokenPrices ? collAsset0.symbol : collAsset0Contract.address],
523
+ totalSupply: new Dec(totalSupplyShares).mul(token0PerSupplyShare).toString(),
524
+ canBeSupplied: true,
525
+ supplyRate: supplyRate0,
526
+ utilization: utilizationSupply0,
527
+ withdrawable: withdrawable0,
528
+ tokenPerSupplyShare: token0PerSupplyShare,
529
+ supplyReserves: reservesSupplyToken0,
530
+ supplyIncentives: [],
531
+ borrowIncentives: [],
532
+ };
533
+ if (STAKING_ASSETS.includes(collFirstAssetData.symbol!)) {
534
+ collFirstAssetData.supplyIncentives!.push({
535
+ apy: await getStakingApy(collAsset0.symbol),
536
+ token: collAsset0.symbol,
537
+ incentiveKind: IncentiveKind.Staking,
538
+ description: `Native ${collAsset0.symbol} yield.`,
539
+ });
540
+ }
541
+
542
+ const collSecondAssetData: Partial<FluidAssetData> = {
543
+ symbol: collAsset1.symbol,
544
+ address: collAsset1.address,
545
+ price: prices[tokenPrices ? collAsset1.symbol : collAsset1Contract.address],
546
+ totalSupply: new Dec(totalSupplyShares).mul(token1PerSupplyShare).toString(),
547
+ canBeSupplied: true,
548
+ supplyRate: supplyRate1,
549
+ withdrawable: withdrawable1,
550
+ utilization: utilizationSupply1,
551
+ tokenPerSupplyShare: token1PerSupplyShare,
552
+ supplyReserves: reservesSupplyToken1,
553
+ supplyIncentives: [],
554
+ borrowIncentives: [],
555
+ };
556
+ if (STAKING_ASSETS.includes(collSecondAssetData.symbol!)) {
557
+ collSecondAssetData.supplyIncentives!.push({
558
+ apy: await getStakingApy(collAsset1.symbol),
559
+ token: collAsset1.symbol,
560
+ incentiveKind: IncentiveKind.Staking,
561
+ description: `Native ${collAsset1.symbol} yield.`,
562
+ });
563
+ }
564
+
565
+ const marketSupplyRate = getMarketRateForDex(token1PerSupplyShare, token0PerSupplyShare, supplyRate0, supplyRate1, collFirstAssetData.price!, collSecondAssetData.price!);
566
+ const incentiveSupplyRate = getAdditionalMarketRateForDex(token1PerSupplyShare, token0PerSupplyShare, collFirstAssetData.supplyIncentives![0]?.apy || '0', collSecondAssetData.supplyIncentives![0]?.apy || '0', collFirstAssetData.price!, collSecondAssetData.price!);
567
+ const tradingSupplyRate = await getTradingApy(data.dexSupplyData.dexPool as EthAddress);
568
+
569
+ const borrowRate = new Dec(data.borrowRateVault).div(100).toString();
570
+ const debtAssetData: Partial<FluidAssetData> = {
571
+ symbol: debtAsset.symbol,
572
+ price: prices[tokenPrices ? debtAsset.symbol : debtAssetContract.address],
573
+ address: debtAsset.address,
574
+ totalBorrow: data.totalBorrowVault.toString(),
575
+ canBeBorrowed: true,
576
+ borrowRate,
577
+ supplyIncentives: [],
578
+ borrowIncentives: [],
579
+ };
580
+ if (STAKING_ASSETS.includes(debtAssetData.symbol!)) {
581
+ debtAssetData.borrowIncentives!.push({
582
+ apy: new Dec(await getStakingApy(debtAsset.symbol)).mul(-1).toString(),
583
+ token: debtAsset.symbol,
584
+ incentiveKind: IncentiveKind.Reward,
585
+ description: `Due to the native yield of ${debtAsset.symbol}, the value of the debt would increase over time.`,
586
+ });
587
+ }
588
+
589
+ const incentiveBorrowRate = new Dec(debtAssetData.borrowIncentives![0]?.apy || '0').mul(-1).toString();
590
+
591
+ const assetsData: FluidAssetsData = ([
592
+ [collAsset0.symbol, collFirstAssetData],
593
+ [collAsset1.symbol, collSecondAssetData],
594
+ [debtAsset.symbol, debtAssetData],
595
+ ] as [string, FluidAssetData][])
596
+ .reduce((acc, [symbol, partialData]) => ({
597
+ ...acc,
598
+ [symbol]: mergeAssetData(acc[symbol], partialData),
599
+ }), {} as Record<string, FluidAssetData>) as FluidAssetsData;
600
+
601
+ const marketInfo = getFluidMarketInfoById(+(data.vaultId.toString()), network);
602
+
603
+ const totalBorrowVault = getEthAmountForDecimals(data.totalBorrowVault.toString(), debtAsset.decimals);
604
+
605
+ const liqRatio = new Dec(data.liquidationThreshold).div(100).toString();
606
+ const liquidationMaxLimit = new Dec(data.liquidationMaxLimit).div(100).toString();
607
+ const liqFactor = new Dec(data.liquidationThreshold).div(10_000).toString();
608
+
609
+ const totalSupplySharesInVault = assetAmountInEth(data.totalSupplyVault.toString());
610
+ const collSharePrice = new Dec(oraclePrice).mul(prices[tokenPrices ? debtAsset.symbol : debtAssetContract.address]).toString();
611
+ const totalSupplyVaultUsd = new Dec(totalSupplySharesInVault).mul(collSharePrice).toString();
612
+ const maxSupplySharesUsd = new Dec(maxSupplyShares).mul(collSharePrice).toString();
613
+
614
+ const withdrawableUSD = new Dec(withdrawableShares).mul(collSharePrice).toString();
615
+
616
+ const marketData = {
617
+ vaultId: +(data.vaultId.toString()),
618
+ vaultValue: marketInfo?.value,
619
+ isSmartColl: data.isSmartColl,
620
+ isSmartDebt: data.isSmartDebt,
621
+ marketAddress: data.vault,
622
+ vaultType: parseVaultType(+(data.vaultType.toString())),
623
+ oracle: data.oracle,
624
+ liquidationPenaltyPercent: new Dec(data.liquidationPenalty).div(100).toString(),
625
+ collFactor: new Dec(data.collateralFactor).div(10000).toString(), // we want actual factor, not in %, so we divide by 10000 instead of 100
626
+ liquidationRatio: liqRatio,
627
+ liqFactor,
628
+ minRatio: new Dec(1).div(liqFactor).mul(100).toString(),
629
+ collAsset0: collAsset0.symbol,
630
+ collAsset1: collAsset1.symbol,
631
+ debtAsset0: debtAsset.symbol,
632
+ totalPositions: data.totalPositions.toString(),
633
+ totalSupplyVault: totalSupplyShares,
634
+ totalBorrowVault,
635
+ totalSupplyVaultUsd,
636
+ collSharePrice,
637
+ totalBorrowVaultUsd: new Dec(totalBorrowVault).mul(assetsData[debtAsset.symbol].price).toString(),
638
+ borrowLimit: getEthAmountForDecimals(data.borrowLimit.toString(), debtAsset.decimals),
639
+ borrowableUntilLimit: getEthAmountForDecimals(data.borrowableUntilLimit.toString(), debtAsset.decimals),
640
+ borrowable: getEthAmountForDecimals(data.borrowable.toString(), debtAsset.decimals),
641
+ borrowLimitUtilization: getEthAmountForDecimals(data.borrowLimitUtilization.toString(), debtAsset.decimals),
642
+ maxBorrowLimit: getEthAmountForDecimals(data.maxBorrowLimit.toString(), debtAsset.decimals),
643
+ baseBorrowLimit: getEthAmountForDecimals(data.baseBorrowLimit.toString(), debtAsset.decimals),
644
+ minimumBorrowing: getEthAmountForDecimals(data.minimumBorrowing.toString(), debtAsset.decimals),
645
+ liquidationMaxLimit,
646
+ borrowRate,
647
+ supplyRate: marketSupplyRate,
648
+ incentiveSupplyRate,
649
+ incentiveBorrowRate,
650
+ totalSupplyToken0,
651
+ totalSupplyToken1,
652
+ withdrawableToken0,
653
+ withdrawableToken1,
654
+ withdrawableUSD,
655
+ withdrawable: withdrawableShares,
656
+ withdrawableDex: new Dec(maxSupplyShares).minus(totalSupplyShares).toString(),
657
+ maxSupplyShares,
658
+ maxSupplySharesUsd,
659
+ collDexFee: supplyDexFee,
660
+ oraclePrice,
661
+ tradingSupplyRate,
662
+ tradingBorrowRate: '0',
663
+ };
664
+
665
+ return {
666
+ assetsData,
667
+ marketData,
668
+ } as FluidMarketData;
669
+ };
670
+
671
+ const parseT3MarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null) => {
672
+ const collAssetContract = getAssetInfoByAddress(data.supplyToken0, network);
673
+ const collAsset = getAssetInfo(getNativeAssetFromWrapped(collAssetContract.symbol), network);
674
+ const debtAsset0Contract = getAssetInfoByAddress(data.borrowToken0, network);
675
+ const debtAsset0 = getAssetInfo(getNativeAssetFromWrapped(debtAsset0Contract.symbol), network);
676
+ const debtAsset1Contract = getAssetInfoByAddress(data.borrowToken1, network);
677
+ const debtAsset1 = getAssetInfo(getNativeAssetFromWrapped(debtAsset1Contract.symbol), network);
678
+
679
+ const {
680
+ borrowableShares,
681
+ maxBorrowShares,
682
+ borrowDexFee,
683
+ utilizationBorrow0,
684
+ utilizationBorrow1,
685
+ borrowable0,
686
+ borrowable1,
687
+ borrowRate0,
688
+ borrowRate1,
689
+ totalBorrowShares,
690
+ token0PerBorrowShare,
691
+ token1PerBorrowShare,
692
+ borrowableToken0,
693
+ borrowableToken1,
694
+ totalBorrowToken0,
695
+ totalBorrowToken1,
696
+ reservesBorrowToken0,
697
+ reservesBorrowToken1,
698
+ } = parseDexBorrowData(data.dexBorrowData, debtAsset0.symbol, debtAsset1.symbol);
699
+
700
+ // 18 because debt is represented in shares for which they use 18 decimals
701
+ const oracleScaleFactor = new Dec(27).add(18).sub(collAsset.decimals).toString();
702
+ const oracleScale = new Dec(10).pow(oracleScaleFactor).toString();
703
+ const oraclePrice = new Dec(1).div(new Dec(data.oraclePriceOperate).div(oracleScale)).toString();
704
+
705
+ let prices: Record<string, string> = {};
706
+ if (tokenPrices) {
707
+ prices = tokenPrices;
708
+ } else {
709
+ prices = await getChainLinkPricesForTokens([collAssetContract.address, debtAsset0Contract.address, debtAsset1Contract.address], network, provider);
710
+ }
711
+
712
+ const supplyRate = new Dec(data.supplyRateVault).div(100).toString();
713
+ const collAssetData: Partial<FluidAssetData> = {
714
+ symbol: collAsset.symbol,
715
+ address: collAsset.address,
716
+ price: prices[tokenPrices ? collAsset.symbol : collAssetContract.address],
717
+ totalSupply: data.totalSupplyVault.toString(),
718
+ canBeSupplied: true,
719
+ supplyRate,
720
+ supplyIncentives: [],
721
+ borrowIncentives: [],
722
+ };
723
+ if (STAKING_ASSETS.includes(collAssetData.symbol!)) {
724
+ collAssetData.supplyIncentives!.push({
725
+ apy: await getStakingApy(collAsset.symbol),
726
+ token: collAsset.symbol,
727
+ incentiveKind: IncentiveKind.Staking,
728
+ description: `Native ${collAsset.symbol} yield.`,
729
+ });
730
+ }
731
+
732
+ const incentiveSupplyRate = collAssetData.supplyIncentives?.[0]?.apy || '0';
733
+
734
+ const debtAsset0Data: Partial<FluidAssetData> = {
735
+ symbol: debtAsset0.symbol,
736
+ address: debtAsset0.address,
737
+ price: prices[tokenPrices ? debtAsset0.symbol : debtAsset0Contract.address],
738
+ totalBorrow: new Dec(totalBorrowShares).mul(token0PerBorrowShare).toString(),
739
+ canBeBorrowed: true,
740
+ borrowRate: borrowRate0,
741
+ borrowable: borrowable0,
742
+ utilization: utilizationBorrow0,
743
+ tokenPerBorrowShare: token0PerBorrowShare,
744
+ borrowReserves: reservesBorrowToken0,
745
+ supplyIncentives: [],
746
+ borrowIncentives: [],
747
+ };
748
+ if (STAKING_ASSETS.includes(debtAsset0Data.symbol!)) {
749
+ debtAsset0Data.borrowIncentives!.push({
750
+ apy: new Dec(await getStakingApy(debtAsset0.symbol)).mul(-1).toString(),
751
+ token: debtAsset0.symbol,
752
+ incentiveKind: IncentiveKind.Reward,
753
+ description: `Due to the native yield of ${debtAsset0.symbol}, the value of the debt would increase over time.`,
754
+ });
755
+ }
756
+
757
+ const debtAsset1Data: Partial<FluidAssetData> = {
758
+ symbol: debtAsset1.symbol,
759
+ address: debtAsset1.address,
760
+ price: prices[tokenPrices ? debtAsset1.symbol : debtAsset1Contract.address],
761
+ totalBorrow: new Dec(totalBorrowShares).mul(token1PerBorrowShare).toString(),
762
+ canBeBorrowed: true,
763
+ borrowRate: borrowRate1,
764
+ borrowable: borrowable1,
765
+ utilization: utilizationBorrow1,
766
+ tokenPerBorrowShare: token1PerBorrowShare,
767
+ borrowReserves: reservesBorrowToken1,
768
+ supplyIncentives: [],
769
+ borrowIncentives: [],
770
+ };
771
+ if (STAKING_ASSETS.includes(debtAsset1Data.symbol!)) {
772
+ debtAsset1Data.borrowIncentives!.push({
773
+ apy: new Dec(await getStakingApy(debtAsset1.symbol)).mul(-1).toString(),
774
+ token: debtAsset1.symbol,
775
+ incentiveKind: IncentiveKind.Reward,
776
+ description: `Due to the native yield of ${debtAsset1.symbol}, the value of the debt would increase over time.`,
777
+ });
778
+ }
779
+ const marketBorrowRate = getMarketRateForDex(token1PerBorrowShare, token0PerBorrowShare, borrowRate0, borrowRate1, debtAsset0Data.price!, debtAsset1Data.price!);
780
+ const incentiveBorrowRate = getAdditionalMarketRateForDex(token1PerBorrowShare, token0PerBorrowShare, new Dec(debtAsset0Data.borrowIncentives![0]?.apy || '0').mul(-1).toString(), new Dec(debtAsset1Data.borrowIncentives![0]?.apy || '0').mul(-1).toString(), debtAsset0Data.price!, debtAsset1Data.price!);
781
+ const tradingBorrowRate = await getTradingApy(data.dexBorrowData.dexPool as EthAddress);
782
+
783
+ const assetsData: FluidAssetsData = ([
784
+ [collAsset.symbol, collAssetData],
785
+ [debtAsset0.symbol, debtAsset0Data],
786
+ [debtAsset1.symbol, debtAsset1Data],
787
+ ] as [string, FluidAssetData][])
788
+ .reduce((acc, [symbol, partialData]) => ({
789
+ ...acc,
790
+ [symbol]: mergeAssetData(acc[symbol], partialData),
791
+ }), {} as Record<string, FluidAssetData>) as FluidAssetsData;
792
+
793
+ const marketInfo = getFluidMarketInfoById(+(data.vaultId.toString()), network);
794
+
795
+ const totalSupplyVault = getEthAmountForDecimals(data.totalSupplyVault.toString(), collAsset.decimals);
796
+
797
+ const liqRatio = new Dec(data.liquidationThreshold).div(100).toString();
798
+ const liquidationMaxLimit = new Dec(data.liquidationMaxLimit).div(100).toString();
799
+ const liqFactor = new Dec(data.liquidationThreshold).div(10_000).toString();
800
+
801
+ const debtSharePrice = new Dec(oraclePrice).mul(prices[tokenPrices ? collAsset.symbol : collAsset.address]).toString();
802
+
803
+ const totalBorrowSharesInVault = assetAmountInEth(data.totalBorrowVault.toString());
804
+
805
+ const totalBorrowVaultUsd = new Dec(totalBorrowSharesInVault).mul(debtSharePrice).toString();
806
+
807
+ const borrowableUSD = new Dec(borrowableShares).mul(debtSharePrice).toString();
808
+ const maxBorrowSharesUsd = new Dec(maxBorrowShares).mul(debtSharePrice).toString();
809
+
810
+ const marketData = {
811
+ vaultId: +(data.vaultId.toString()),
812
+ vaultValue: marketInfo?.value,
813
+ isSmartColl: data.isSmartColl,
814
+ isSmartDebt: data.isSmartDebt,
815
+ marketAddress: data.vault,
816
+ vaultType: parseVaultType(+(data.vaultType.toString())),
817
+ oracle: data.oracle,
818
+ liquidationPenaltyPercent: new Dec(data.liquidationPenalty).div(100).toString(),
819
+ collFactor: new Dec(data.collateralFactor).div(10000).toString(), // we want actual factor, not in %, so we divide by 10000 instead of 100
820
+ liquidationRatio: liqRatio,
821
+ liqFactor,
822
+ minRatio: new Dec(1).div(liqFactor).mul(100).toString(),
823
+ collAsset0: collAsset.symbol,
824
+ debtAsset0: debtAsset0.symbol,
825
+ debtAsset1: debtAsset1.symbol,
826
+ totalPositions: data.totalPositions.toString(),
827
+ totalSupplyVault,
828
+ totalBorrowVault: totalBorrowShares,
829
+ totalSupplyVaultUsd: new Dec(totalSupplyVault).mul(assetsData[collAsset.symbol].price).toString(),
830
+ totalBorrowVaultUsd,
831
+ withdrawalLimit: getEthAmountForDecimals(data.withdrawalLimit.toString(), collAsset.decimals),
832
+ withdrawableUntilLimit: getEthAmountForDecimals(data.withdrawableUntilLimit.toString(), collAsset.decimals),
833
+ withdrawable: getEthAmountForDecimals(data.withdrawable.toString(), collAsset.decimals),
834
+ liquidationMaxLimit,
835
+ borrowRate: marketBorrowRate,
836
+ supplyRate,
837
+ incentiveBorrowRate,
838
+ incentiveSupplyRate,
839
+ tradingBorrowRate,
840
+ tradingSupplyRate: '0',
841
+ borrowableToken0,
842
+ borrowableToken1,
843
+ totalBorrowToken0,
844
+ totalBorrowToken1,
845
+ borrowableUSD,
846
+ borrowable: borrowableShares,
847
+ borrowableDex: new Dec(maxBorrowShares).minus(totalBorrowShares).toString(),
848
+ maxBorrowShares,
849
+ maxBorrowSharesUsd,
850
+ borrowDexFee,
851
+ debtSharePrice,
852
+ oraclePrice,
853
+ };
854
+
855
+ return {
856
+ assetsData,
857
+ marketData,
858
+ } as FluidMarketData;
859
+ };
860
+
861
+ const parseT4MarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null) => {
862
+ const collAsset0Contract = getAssetInfoByAddress(data.supplyToken0, network);
863
+ const collAsset0 = getAssetInfo(getNativeAssetFromWrapped(collAsset0Contract.symbol), network);
864
+ const collAsset1Contract = getAssetInfoByAddress(data.supplyToken1, network);
865
+ const collAsset1 = getAssetInfo(getNativeAssetFromWrapped(collAsset1Contract.symbol), network);
866
+ const debtAsset0Contract = getAssetInfoByAddress(data.borrowToken0, network);
867
+ const debtAsset0 = getAssetInfo(getNativeAssetFromWrapped(debtAsset0Contract.symbol), network);
868
+ const debtAsset1Contract = getAssetInfoByAddress(data.borrowToken1, network);
869
+ const debtAsset1 = getAssetInfo(getNativeAssetFromWrapped(debtAsset1Contract.symbol), network);
870
+ const quoteToken = getAssetInfoByAddress(data.dexBorrowData.quoteToken, network);
871
+
872
+ // 27 - 18 + 18
873
+ const oracleScaleFactor = new Dec(27).toString();
874
+ const oracleScale = new Dec(10).pow(oracleScaleFactor).toString();
875
+ const oraclePrice = new Dec(data.oraclePriceOperate).div(oracleScale).toString();
876
+
877
+ let prices: Record<string, string> = {};
878
+ if (tokenPrices) {
879
+ prices = tokenPrices;
880
+ } else {
881
+ prices = await getChainLinkPricesForTokens(
882
+ [collAsset0Contract.address, collAsset1Contract.address, debtAsset0Contract.address, debtAsset1Contract.address],
883
+ network, provider);
884
+ }
885
+
886
+ const {
887
+ supplyDexFee,
888
+ totalSupplyShares,
889
+ supplyRate1,
890
+ token0PerSupplyShare,
891
+ token1PerSupplyShare,
892
+ totalSupplyToken0,
893
+ totalSupplyToken1,
894
+ maxSupplyShares,
895
+ withdrawableToken0,
896
+ withdrawable0,
897
+ withdrawableToken1,
898
+ withdrawable1,
899
+ supplyRate0,
900
+ utilizationSupply0,
901
+ utilizationSupply1,
902
+ withdrawableShares,
903
+ reservesSupplyToken0,
904
+ reservesSupplyToken1,
905
+ } = parseDexSupplyData(data.dexSupplyData, collAsset0.symbol, collAsset1.symbol);
906
+
907
+ const {
908
+ borrowableShares,
909
+ maxBorrowShares,
910
+ borrowDexFee,
911
+ utilizationBorrow0,
912
+ utilizationBorrow1,
913
+ borrowable0,
914
+ borrowable1,
915
+ borrowRate0,
916
+ borrowRate1,
917
+ totalBorrowShares,
918
+ token0PerBorrowShare,
919
+ token1PerBorrowShare,
920
+ borrowableToken0,
921
+ borrowableToken1,
922
+ totalBorrowToken0,
923
+ totalBorrowToken1,
924
+ quoteTokensPerShare,
925
+ reservesBorrowToken0,
926
+ reservesBorrowToken1,
927
+ } = parseDexBorrowData(data.dexBorrowData, debtAsset0.symbol, debtAsset1.symbol);
928
+
929
+ const collAsset0Data: Partial<FluidAssetData> = {
930
+ symbol: collAsset0.symbol,
931
+ address: collAsset0.address,
932
+ price: prices[tokenPrices ? collAsset0.symbol : collAsset0Contract.address],
933
+ totalSupply: new Dec(totalSupplyShares).mul(token0PerSupplyShare).toString(),
934
+ canBeSupplied: true,
935
+ supplyRate: supplyRate0,
936
+ utilization: utilizationSupply0,
937
+ withdrawable: withdrawable0,
938
+ tokenPerSupplyShare: token0PerSupplyShare,
939
+ supplyReserves: reservesSupplyToken0,
940
+ supplyIncentives: [],
941
+ borrowIncentives: [],
942
+ };
943
+ if (STAKING_ASSETS.includes(collAsset0Data.symbol!)) {
944
+ collAsset0Data.supplyIncentives!.push({
945
+ apy: await getStakingApy(collAsset0.symbol),
946
+ token: collAsset0.symbol,
947
+ incentiveKind: IncentiveKind.Staking,
948
+ description: `Native ${collAsset0.symbol} yield.`,
949
+ });
950
+ }
951
+
952
+ const collAsset1Data: Partial<FluidAssetData> = {
953
+ symbol: collAsset1.symbol,
954
+ address: collAsset1.address,
955
+ price: prices[tokenPrices ? collAsset1.symbol : collAsset1Contract.address],
956
+ totalSupply: new Dec(totalSupplyShares).mul(token1PerSupplyShare).toString(),
957
+ canBeSupplied: true,
958
+ supplyRate: supplyRate1,
959
+ withdrawable: withdrawable1,
960
+ utilization: utilizationSupply1,
961
+ tokenPerSupplyShare: token1PerSupplyShare,
962
+ supplyReserves: reservesSupplyToken1,
963
+ supplyIncentives: [],
964
+ borrowIncentives: [],
965
+ };
966
+ if (STAKING_ASSETS.includes(collAsset1Data.symbol!)) {
967
+ collAsset1Data.supplyIncentives!.push({
968
+ apy: await getStakingApy(collAsset1.symbol),
969
+ token: collAsset1.symbol,
970
+ incentiveKind: IncentiveKind.Staking,
971
+ description: `Native ${collAsset1.symbol} yield.`,
972
+ });
973
+ }
974
+
975
+ const debtAsset0Data: Partial<FluidAssetData> = {
976
+ symbol: debtAsset0.symbol,
977
+ address: debtAsset0.address,
978
+ price: prices[tokenPrices ? debtAsset0.symbol : debtAsset0Contract.address],
979
+ totalBorrow: new Dec(totalBorrowShares).mul(token0PerBorrowShare).toString(),
980
+ canBeBorrowed: true,
981
+ borrowRate: borrowRate0,
982
+ borrowable: borrowable0,
983
+ utilization: utilizationBorrow0,
984
+ tokenPerBorrowShare: token0PerBorrowShare,
985
+ borrowReserves: reservesBorrowToken0,
986
+ supplyIncentives: [],
987
+ borrowIncentives: [],
988
+ };
989
+ if (STAKING_ASSETS.includes(debtAsset0Data.symbol!)) {
990
+ debtAsset0Data.borrowIncentives!.push({
991
+ apy: new Dec(await getStakingApy(debtAsset0.symbol)).mul(-1).toString(),
992
+ token: debtAsset0.symbol,
993
+ incentiveKind: IncentiveKind.Reward,
994
+ description: `Due to the native yield of ${debtAsset0.symbol}, the value of the debt would increase over time.`,
995
+ });
996
+ }
997
+
998
+ const debtAsset1Data: Partial<FluidAssetData> = {
999
+ symbol: debtAsset1.symbol,
1000
+ address: debtAsset1.address,
1001
+ price: prices[tokenPrices ? debtAsset1.symbol : debtAsset1Contract.address],
1002
+ totalBorrow: new Dec(totalBorrowShares).mul(token1PerBorrowShare).toString(),
1003
+ canBeBorrowed: true,
1004
+ borrowRate: borrowRate1,
1005
+ borrowable: borrowable1,
1006
+ utilization: utilizationBorrow1,
1007
+ tokenPerBorrowShare: token1PerBorrowShare,
1008
+ borrowReserves: reservesBorrowToken1,
1009
+ supplyIncentives: [],
1010
+ borrowIncentives: [],
1011
+ };
1012
+ if (STAKING_ASSETS.includes(debtAsset1Data.symbol!)) {
1013
+ debtAsset1Data.borrowIncentives!.push({
1014
+ apy: new Dec(await getStakingApy(debtAsset1.symbol)).mul(-1).toString(),
1015
+ token: debtAsset1.symbol,
1016
+ incentiveKind: IncentiveKind.Reward,
1017
+ description: `Due to the native yield of ${debtAsset1.symbol}, the value of the debt would increase over time.`,
1018
+ });
1019
+ }
1020
+ const marketInfo = getFluidMarketInfoById(+(data.vaultId.toString()), network);
1021
+
1022
+ const marketBorrowRate = getMarketRateForDex(token1PerBorrowShare, token0PerBorrowShare, borrowRate0, borrowRate1, debtAsset0Data.price!, debtAsset1Data.price!);
1023
+ const incentiveBorrowRate = getAdditionalMarketRateForDex(token1PerBorrowShare, token0PerBorrowShare, new Dec(debtAsset0Data.borrowIncentives![0]?.apy || '0').mul(-1).toString(), new Dec(debtAsset1Data.borrowIncentives![0]?.apy || '0').mul(-1).toString(), debtAsset0Data.price!, debtAsset1Data.price!);
1024
+ const tradingBorrowRate = await getTradingApy(data.dexBorrowData.dexPool as EthAddress);
1025
+
1026
+ const marketSupplyRate = getMarketRateForDex(token1PerSupplyShare, token0PerSupplyShare, supplyRate0, supplyRate1, collAsset0Data.price!, collAsset1Data.price!);
1027
+ const incentiveSupplyRate = getAdditionalMarketRateForDex(token1PerSupplyShare, token0PerSupplyShare, collAsset0Data.supplyIncentives![0]?.apy || '0', collAsset1Data.supplyIncentives![0]?.apy || '0', collAsset0Data.price!, collAsset1Data.price!);
1028
+ const tradingSupplyRate = await getTradingApy(data.dexSupplyData.dexPool as EthAddress);
1029
+
1030
+ const assetsData: FluidAssetsData = ([
1031
+ [collAsset0.symbol, collAsset0Data],
1032
+ [collAsset1.symbol, collAsset1Data],
1033
+ [debtAsset0.symbol, debtAsset0Data],
1034
+ [debtAsset1.symbol, debtAsset1Data],
1035
+ ] as [string, FluidAssetData][])
1036
+ .reduce((acc, [symbol, partialData]) => ({
1037
+ ...acc,
1038
+ [symbol]: mergeAssetData(acc[symbol], partialData),
1039
+ }), {} as Record<string, FluidAssetData>) as FluidAssetsData;
1040
+
1041
+
1042
+ const liqRatio = new Dec(data.liquidationThreshold).div(100).toString();
1043
+ const liquidationMaxLimit = new Dec(data.liquidationMaxLimit).div(100).toString();
1044
+ const liqFactor = new Dec(data.liquidationThreshold).div(10_000).toString();
1045
+
1046
+ const totalBorrowSharesInVault = assetAmountInEth(data.totalBorrowVault.toString());
1047
+ const debtSharePrice = new Dec(quoteTokensPerShare).mul(prices[tokenPrices ? quoteToken.symbol : quoteToken.address]).toString();
1048
+ const totalBorrowVaultUsd = new Dec(totalBorrowSharesInVault).mul(debtSharePrice).toString();
1049
+ const maxBorrowSharesUsd = new Dec(maxBorrowShares).mul(debtSharePrice).toString();
1050
+ const borrowableUSD = new Dec(borrowableShares).mul(debtSharePrice).toString();
1051
+
1052
+ const totalSupplySharesInVault = assetAmountInEth(data.totalSupplyVault.toString());
1053
+ const collSharePrice = new Dec(oraclePrice).mul(debtSharePrice).toString();
1054
+ const totalSupplyVaultUsd = new Dec(totalSupplySharesInVault).mul(collSharePrice).toString();
1055
+ const maxSupplySharesUsd = new Dec(maxSupplyShares).mul(collSharePrice).toString();
1056
+ const withdrawableUSD = new Dec(withdrawableShares).mul(collSharePrice).toString();
1057
+
1058
+ const marketData = {
1059
+ vaultId: +(data.vaultId.toString()),
1060
+ vaultValue: marketInfo?.value,
1061
+ isSmartColl: data.isSmartColl,
1062
+ isSmartDebt: data.isSmartDebt,
1063
+ marketAddress: data.vault,
1064
+ vaultType: parseVaultType(+(data.vaultType.toString())),
1065
+ oracle: data.oracle,
1066
+ liquidationPenaltyPercent: new Dec(data.liquidationPenalty).div(100).toString(),
1067
+ collFactor: new Dec(data.collateralFactor).div(10000).toString(), // we want actual factor, not in %, so we divide by 10000 instead of 100
1068
+ liquidationRatio: liqRatio,
1069
+ liqFactor,
1070
+ minRatio: new Dec(1).div(liqFactor).mul(100).toString(),
1071
+ collAsset0: collAsset0.symbol,
1072
+ collAsset1: collAsset1.symbol,
1073
+ debtAsset0: debtAsset0.symbol,
1074
+ debtAsset1: debtAsset1.symbol,
1075
+ totalPositions: data.totalPositions.toString(),
1076
+ totalSupplyVault: totalSupplyShares,
1077
+ totalBorrowVault: totalBorrowShares,
1078
+ totalSupplyVaultUsd,
1079
+ totalBorrowVaultUsd,
1080
+ liquidationMaxLimit,
1081
+ borrowRate: marketBorrowRate,
1082
+ incentiveBorrowRate,
1083
+ supplyRate: marketSupplyRate,
1084
+ incentiveSupplyRate,
1085
+ borrowableToken0,
1086
+ borrowableToken1,
1087
+ totalBorrowToken0,
1088
+ totalBorrowToken1,
1089
+ borrowableUSD,
1090
+ borrowable: borrowableShares,
1091
+ borrowableDex: new Dec(maxBorrowShares).minus(totalBorrowShares).toString(),
1092
+ maxBorrowShares,
1093
+ maxBorrowSharesUsd,
1094
+ borrowDexFee,
1095
+ totalSupplyToken0,
1096
+ totalSupplyToken1,
1097
+ withdrawableToken0,
1098
+ withdrawableToken1,
1099
+ withdrawableUSD,
1100
+ withdrawable: withdrawableShares,
1101
+ withdrawableDex: new Dec(maxSupplyShares).minus(totalSupplyShares).toString(),
1102
+ maxSupplyShares,
1103
+ maxSupplySharesUsd,
1104
+ collDexFee: supplyDexFee,
1105
+ collSharePrice,
1106
+ debtSharePrice,
1107
+ oraclePrice,
1108
+ tradingBorrowRate,
1109
+ tradingSupplyRate,
1110
+ };
1111
+
1112
+ return {
1113
+ assetsData,
1114
+ marketData,
1115
+ } as FluidMarketData;
1116
+ };
1117
+
1118
+ const parseMarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null) => {
1119
+ const marketInfo = getFluidMarketInfoById(+(data.vaultId.toString()), network);
1120
+ if (!marketInfo) {
1121
+ console.error(`Fluid market with address ${data.vault} not supported.`);
1122
+ return; // skip unsupported market
1123
+ }
1124
+ const vaultType = parseVaultType(+(data.vaultType.toString()));
1125
+ switch (vaultType) {
1126
+ case FluidVaultType.T1:
1127
+ return parseT1MarketData(provider, data, network, tokenPrices);
1128
+ case FluidVaultType.T2:
1129
+ return parseT2MarketData(provider, data, network, tokenPrices);
1130
+ case FluidVaultType.T3:
1131
+ return parseT3MarketData(provider, data, network, tokenPrices);
1132
+ case FluidVaultType.T4:
1133
+ return parseT4MarketData(provider, data, network, tokenPrices);
1134
+ default:
1135
+ throw new Error(`Unknown vault type: ${vaultType}`);
1136
+ }
1137
+ };
1138
+
1139
+ export const EMPTY_FLUID_DATA = {
1140
+ usedAssets: {},
1141
+ suppliedUsd: '0',
1142
+ borrowedUsd: '0',
1143
+ borrowLimitUsd: '0',
1144
+ leftToBorrowUsd: '0',
1145
+ ratio: '0',
1146
+ minRatio: '0',
1147
+ netApy: '0',
1148
+ incentiveUsd: '0',
1149
+ totalInterestUsd: '0',
1150
+ isSubscribedToAutomation: false,
1151
+ automationResubscribeRequired: false,
1152
+ lastUpdated: Date.now(),
1153
+ };
1154
+
1155
+ const parseT1UserData = (userPositionData: FluidUserPositionStructOutputStruct, vaultData: FluidMarketData): FluidVaultData => {
1156
+ const {
1157
+ assetsData,
1158
+ marketData,
1159
+ } = vaultData;
1160
+
1161
+ const payload = {
1162
+ owner: userPositionData.owner,
1163
+ vaultId: marketData.vaultId,
1164
+ ...EMPTY_FLUID_DATA,
1165
+ lastUpdated: Date.now(),
1166
+ };
1167
+ const collAsset = getAssetInfo(marketData.collAsset0);
1168
+ const debtAsset = getAssetInfo(marketData.debtAsset0);
1169
+
1170
+ // for T2 and T4 - this is the number of shares
1171
+ const supplied = getEthAmountForDecimals(userPositionData.supply.toString(), collAsset.decimals);
1172
+ const borrowed = getEthAmountForDecimals(userPositionData.borrow.toString(), debtAsset.decimals);
1173
+
1174
+ const collUsedAsset: FluidUsedAsset = {
1175
+ ...EMPTY_USED_ASSET,
1176
+ symbol: collAsset.symbol,
1177
+ collateral: true,
1178
+ supplied,
1179
+ suppliedUsd: new Dec(supplied).mul(assetsData[collAsset.symbol].price).toString(),
1180
+ isSupplied: new Dec(supplied).gt(0),
1181
+ };
1182
+
1183
+ const debtUsedAsset: FluidUsedAsset = {
1184
+ ...EMPTY_USED_ASSET,
1185
+ symbol: debtAsset.symbol,
1186
+ collateral: false,
1187
+ borrowed,
1188
+ borrowedUsd: new Dec(borrowed).mul(assetsData[debtAsset.symbol].price).toString(),
1189
+ isBorrowed: new Dec(borrowed).gt(0),
1190
+ };
1191
+
1192
+ const usedAssets: FluidUsedAssets = {
1193
+ [collAsset.symbol]: collUsedAsset,
1194
+ [debtAsset.symbol]: debtUsedAsset,
1195
+ };
1196
+
1197
+ return {
1198
+ ...payload,
1199
+ usedAssets,
1200
+ nftId: userPositionData.nftId.toString(),
1201
+ ...(getFluidAggregatedData({
1202
+ usedAssets,
1203
+ assetsData,
1204
+ marketData,
1205
+ }) as FluidAggregatedVaultData),
1206
+ };
1207
+ };
1208
+
1209
+ const parseT2UserData = (userPositionData: FluidUserPositionStructOutputStruct, vaultData: FluidMarketData): FluidVaultData => {
1210
+ const {
1211
+ assetsData,
1212
+ marketData,
1213
+ } = vaultData;
1214
+
1215
+ const payload = {
1216
+ owner: userPositionData.owner,
1217
+ vaultId: marketData.vaultId,
1218
+ ...EMPTY_FLUID_DATA,
1219
+ lastUpdated: Date.now(),
1220
+ };
1221
+
1222
+ const collAsset0 = getAssetInfo(marketData.collAsset0);
1223
+ const collAsset1 = getAssetInfo(marketData.collAsset1);
1224
+ const debtAsset = getAssetInfo(marketData.debtAsset0);
1225
+
1226
+ const supplyShares = getEthAmountForDecimals(userPositionData.supply.toString(), 18); // this is supplied in coll shares
1227
+ const borrowed = getEthAmountForDecimals(userPositionData.borrow.toString(), debtAsset.decimals); // this is actual token borrow
1228
+
1229
+ const supplied0 = new Dec(supplyShares).mul(assetsData[collAsset0.symbol].tokenPerSupplyShare!).toString();
1230
+ const supplied1 = new Dec(supplyShares).mul(assetsData[collAsset1.symbol].tokenPerSupplyShare!).toString();
1231
+
1232
+ const collUsedAsset0: Partial<FluidUsedAsset> = {
1233
+ symbol: collAsset0.symbol,
1234
+ collateral: true,
1235
+ supplied: supplied0,
1236
+ suppliedUsd: new Dec(supplied0).mul(assetsData[collAsset0.symbol].price).toString(),
1237
+ isSupplied: new Dec(supplied0).gt(0),
1238
+ };
1239
+
1240
+ const collUsedAsset1: Partial<FluidUsedAsset> = {
1241
+ symbol: collAsset1.symbol,
1242
+ collateral: true,
1243
+ supplied: supplied1,
1244
+ suppliedUsd: new Dec(supplied1).mul(assetsData[collAsset1.symbol].price).toString(),
1245
+ isSupplied: new Dec(supplied1).gt(0),
1246
+ };
1247
+
1248
+ const debtUsedAsset: Partial<FluidUsedAsset> = {
1249
+ symbol: debtAsset.symbol,
1250
+ borrowed,
1251
+ borrowedUsd: new Dec(borrowed).mul(assetsData[debtAsset.symbol].price).toString(),
1252
+ isBorrowed: new Dec(borrowed).gt(0),
1253
+ };
1254
+
1255
+ const usedAssets: FluidUsedAssets = ([
1256
+ [collAsset0.symbol, collUsedAsset0],
1257
+ [collAsset1.symbol, collUsedAsset1],
1258
+ [debtAsset.symbol, debtUsedAsset],
1259
+ ] as [string, FluidUsedAsset][])
1260
+ .reduce((acc, [symbol, partialData]) => {
1261
+ acc[symbol] = mergeUsedAssets(acc[symbol], partialData);
1262
+ return acc;
1263
+ }, {} as Record<string, FluidUsedAsset>) as FluidUsedAssets;
1264
+
1265
+ return {
1266
+ ...payload,
1267
+ usedAssets,
1268
+ supplyShares,
1269
+ nftId: userPositionData.nftId.toString(),
1270
+ ...(getFluidAggregatedData({
1271
+ usedAssets,
1272
+ assetsData,
1273
+ marketData,
1274
+ }, supplyShares) as FluidAggregatedVaultData),
1275
+ };
1276
+ };
1277
+
1278
+ const parseT3UserData = (userPositionData: FluidUserPositionStructOutputStruct, vaultData: FluidMarketData): FluidVaultData => {
1279
+ const {
1280
+ assetsData,
1281
+ marketData,
1282
+ } = vaultData;
1283
+
1284
+ const payload = {
1285
+ owner: userPositionData.owner,
1286
+ vaultId: marketData.vaultId,
1287
+ ...EMPTY_FLUID_DATA,
1288
+ lastUpdated: Date.now(),
1289
+ };
1290
+
1291
+ const collAsset = getAssetInfo(marketData.collAsset0);
1292
+ const debtAsset0 = getAssetInfo(marketData.debtAsset0);
1293
+ const debtAsset1 = getAssetInfo(marketData.debtAsset1);
1294
+
1295
+ const supplied = getEthAmountForDecimals(userPositionData.supply.toString(), collAsset.decimals); // this is actual token supply
1296
+ const borrowShares = getEthAmountForDecimals(userPositionData.borrow.toString(), 18); // this is actual token borrow
1297
+
1298
+ const borrowed0 = new Dec(borrowShares).mul(assetsData[debtAsset0.symbol].tokenPerBorrowShare!).toString();
1299
+ const borrowed1 = new Dec(borrowShares).mul(assetsData[debtAsset1.symbol].tokenPerBorrowShare!).toString();
1300
+
1301
+ const collUsedAsset: Partial<FluidUsedAsset> = {
1302
+ symbol: collAsset.symbol,
1303
+ collateral: true,
1304
+ supplied,
1305
+ suppliedUsd: new Dec(supplied).mul(assetsData[collAsset.symbol].price).toString(),
1306
+ isSupplied: new Dec(supplied).gt(0),
1307
+ };
1308
+
1309
+ const debtUsedAsset0: Partial<FluidUsedAsset> = {
1310
+ symbol: debtAsset0.symbol,
1311
+ borrowed: borrowed0,
1312
+ borrowedUsd: new Dec(borrowed0).mul(assetsData[debtAsset0.symbol].price).toString(),
1313
+ isBorrowed: new Dec(borrowed0).gt(0),
1314
+ };
1315
+
1316
+ const debtUsedAsset1: Partial<FluidUsedAsset> = {
1317
+ symbol: debtAsset1.symbol,
1318
+ borrowed: borrowed1,
1319
+ borrowedUsd: new Dec(borrowed1).mul(assetsData[debtAsset1.symbol].price).toString(),
1320
+ isBorrowed: new Dec(borrowed1).gt(0),
1321
+ };
1322
+
1323
+ const usedAssets: FluidUsedAssets = ([
1324
+ [collAsset.symbol, collUsedAsset],
1325
+ [debtAsset0.symbol, debtUsedAsset0],
1326
+ [debtAsset1.symbol, debtUsedAsset1],
1327
+ ] as [string, FluidUsedAsset][])
1328
+ .reduce((acc, [symbol, partialData]) => {
1329
+ acc[symbol] = mergeUsedAssets(acc[symbol], partialData);
1330
+ return acc;
1331
+ }, {} as Record<string, FluidUsedAsset>) as FluidUsedAssets;
1332
+
1333
+
1334
+ return {
1335
+ ...payload,
1336
+ usedAssets,
1337
+ borrowShares,
1338
+ nftId: userPositionData.nftId.toString(),
1339
+ ...(getFluidAggregatedData({
1340
+ usedAssets,
1341
+ assetsData,
1342
+ marketData,
1343
+ }, '', borrowShares) as FluidAggregatedVaultData),
1344
+ };
1345
+ };
1346
+
1347
+ const parseT4UserData = (userPositionData: FluidUserPositionStructOutputStruct, vaultData: FluidMarketData): FluidVaultData => {
1348
+ const {
1349
+ assetsData,
1350
+ marketData,
1351
+ } = vaultData;
1352
+
1353
+ const payload = {
1354
+ owner: userPositionData.owner,
1355
+ vaultId: marketData.vaultId,
1356
+ ...EMPTY_FLUID_DATA,
1357
+ lastUpdated: Date.now(),
1358
+ };
1359
+ const collAsset0 = getAssetInfo(marketData.collAsset0);
1360
+ const collAsset1 = getAssetInfo(marketData.collAsset1);
1361
+ const debtAsset0 = getAssetInfo(marketData.debtAsset0);
1362
+ const debtAsset1 = getAssetInfo(marketData.debtAsset1);
1363
+
1364
+ const supplyShares = getEthAmountForDecimals(userPositionData.supply.toString(), 18); // this is actual token supply
1365
+ const borrowShares = getEthAmountForDecimals(userPositionData.borrow.toString(), 18); // this is actual token borrow
1366
+
1367
+ const supplied0 = new Dec(supplyShares).mul(assetsData[collAsset0.symbol].tokenPerSupplyShare!).toString();
1368
+ const supplied1 = new Dec(supplyShares).mul(assetsData[collAsset1.symbol].tokenPerSupplyShare!).toString();
1369
+
1370
+ const borrowed0 = new Dec(borrowShares).mul(assetsData[debtAsset0.symbol].tokenPerBorrowShare!).toString();
1371
+ const borrowed1 = new Dec(borrowShares).mul(assetsData[debtAsset1.symbol].tokenPerBorrowShare!).toString();
1372
+
1373
+ const collUsedAsset0: Partial<FluidUsedAsset> = {
1374
+ symbol: collAsset0.symbol,
1375
+ collateral: true,
1376
+ supplied: supplied0,
1377
+ suppliedUsd: new Dec(supplied0).mul(assetsData[collAsset0.symbol].price).toString(),
1378
+ isSupplied: new Dec(supplied0).gt(0),
1379
+ };
1380
+ const collUsedAsset1: Partial<FluidUsedAsset> = {
1381
+ symbol: collAsset1.symbol,
1382
+ collateral: true,
1383
+ supplied: supplied1,
1384
+ suppliedUsd: new Dec(supplied1).mul(assetsData[collAsset1.symbol].price).toString(),
1385
+ isSupplied: new Dec(supplied1).gt(0),
1386
+ };
1387
+
1388
+ const debtUsedAsset0: Partial<FluidUsedAsset> = {
1389
+ symbol: debtAsset0.symbol,
1390
+ borrowed: borrowed0,
1391
+ borrowedUsd: new Dec(borrowed0).mul(assetsData[debtAsset0.symbol].price).toString(),
1392
+ isBorrowed: new Dec(borrowed0).gt(0),
1393
+ };
1394
+ const debtUsedAsset1: Partial<FluidUsedAsset> = {
1395
+ symbol: debtAsset1.symbol,
1396
+ borrowed: borrowed1,
1397
+ borrowedUsd: new Dec(borrowed1).mul(assetsData[debtAsset1.symbol].price).toString(),
1398
+ isBorrowed: new Dec(borrowed1).gt(0),
1399
+ };
1400
+
1401
+ const usedAssets: FluidUsedAssets = ([
1402
+ [collAsset0.symbol, collUsedAsset0],
1403
+ [collAsset1.symbol, collUsedAsset1],
1404
+ [debtAsset0.symbol, debtUsedAsset0],
1405
+ [debtAsset1.symbol, debtUsedAsset1],
1406
+ ] as [string, FluidUsedAsset][])
1407
+ .reduce((acc, [symbol, partialData]) => {
1408
+ acc[symbol] = mergeUsedAssets(acc[symbol], partialData);
1409
+ return acc;
1410
+ }, {} as Record<string, FluidUsedAsset>) as FluidUsedAssets;
1411
+
1412
+ return {
1413
+ ...payload,
1414
+ usedAssets,
1415
+ supplyShares,
1416
+ borrowShares,
1417
+ nftId: userPositionData.nftId.toString(),
1418
+ ...(getFluidAggregatedData({
1419
+ usedAssets,
1420
+ assetsData,
1421
+ marketData,
1422
+ }, supplyShares, borrowShares) as FluidAggregatedVaultData),
1423
+ };
1424
+ };
1425
+
1426
+ const parseUserData = (userPositionData: FluidUserPositionStructOutputStruct, vaultData: FluidMarketData): FluidVaultData => {
1427
+ const vaultType = vaultData.marketData.vaultType;
1428
+ switch (vaultType) {
1429
+ case FluidVaultType.T1:
1430
+ return parseT1UserData(userPositionData, vaultData);
1431
+ case FluidVaultType.T2:
1432
+ return parseT2UserData(userPositionData, vaultData);
1433
+ case FluidVaultType.T3:
1434
+ return parseT3UserData(userPositionData, vaultData);
1435
+ case FluidVaultType.T4:
1436
+ return parseT4UserData(userPositionData, vaultData);
1437
+ default:
1438
+ throw new Error(`Unknown vault type: ${vaultType}`);
1439
+ }
1440
+ };
1441
+
1442
+ export const _getFluidMarketData = async (provider: PublicClient, network: NetworkNumber, market: FluidMarketInfo) => {
1443
+ const view = FluidViewContractViem(provider, network);
1444
+
1445
+ const data = await view.read.getVaultData([market.marketAddress]);
1446
+
1447
+ return parseMarketData(provider, data, network);
1448
+ };
1449
+
1450
+ export const getFluidMarketData = async (
1451
+ provider: EthereumProvider,
1452
+ network: NetworkNumber,
1453
+ market: FluidMarketInfo,
1454
+ ) => _getFluidMarketData(getViemProvider(provider, network), network, market);
1455
+
1456
+ export const _getFluidVaultIdsForUser = async (
1457
+ provider: Client,
1458
+ network: NetworkNumber,
1459
+ user: EthAddress,
1460
+ ): Promise<string[]> => {
1461
+ const view = FluidViewContractViem(provider, network);
1462
+
1463
+ return (await view.read.getUserNftIds([user])).map((item: bigint) => item.toString());
1464
+ };
1465
+
1466
+ export const getFluidVaultIdsForUser = async (
1467
+ provider: EthereumProvider,
1468
+ network: NetworkNumber,
1469
+ user: EthAddress,
1470
+ ): Promise<string[]> => _getFluidVaultIdsForUser(getViemProvider(provider, network), network, user);
1471
+
1472
+
1473
+ export const _getFluidPosition = async (
1474
+ provider: Client,
1475
+ network: NetworkNumber,
1476
+ vaultId: string,
1477
+ extractedState: {
1478
+ assetsData: FluidAssetsData
1479
+ marketData: InnerFluidMarketData,
1480
+ },
1481
+ ): Promise<FluidVaultData> => {
1482
+ const view = FluidViewContractViem(provider, network);
1483
+
1484
+ const data = await view.read.getPositionByNftId([BigInt(vaultId)]);
1485
+
1486
+ const userPositionData = data[0];
1487
+
1488
+ return parseUserData(userPositionData, extractedState);
1489
+ };
1490
+
1491
+ export const getFluidPosition = async (
1492
+ provider: EthereumProvider,
1493
+ network: NetworkNumber,
1494
+ vaultId: string,
1495
+ extractedState: {
1496
+ assetsData: FluidAssetsData
1497
+ marketData: InnerFluidMarketData,
1498
+ },
1499
+ ): Promise<FluidVaultData> => _getFluidPosition(getViemProvider(provider, network), network, vaultId, extractedState);
1500
+
1501
+ export const _getFluidPositionWithMarket = async (provider: PublicClient, network: NetworkNumber, vaultId: string) => {
1502
+ const view = FluidViewContractViem(provider, network);
1503
+ const data = await view.read.getPositionByNftId([BigInt(vaultId)]);
1504
+ const marketData = await parseMarketData(provider, data[1], network);
1505
+ if (!marketData) {
1506
+ return;
1507
+ }
1508
+ const userData = parseUserData(data[0], marketData);
1509
+
1510
+ return {
1511
+ userData,
1512
+ marketData,
1513
+ };
1514
+ };
1515
+
1516
+ export const getFluidPositionWithMarket = async (
1517
+ provider: EthereumProvider,
1518
+ network: NetworkNumber,
1519
+ vaultId: string,
1520
+ ) => _getFluidPositionWithMarket(getViemProvider(provider, network), network, vaultId);
1521
+
1522
+ export const _getAllFluidMarketDataChunked = async (network: NetworkNumber, provider: PublicClient) => {
1523
+ const versions = getFluidVersionsDataForNetwork(network);
1524
+ const view = FluidViewContractViem(provider, network);
1525
+ const data = await Promise.all(versions.map((version) => view.read.getVaultData([version.marketAddress])));
1526
+ return Promise.all(data.map(async (item, i) => parseMarketData(provider, item, network)));
1527
+ };
1528
+
1529
+ export const getAllFluidMarketDataChunked = async (
1530
+ network: NetworkNumber,
1531
+ provider: EthereumProvider,
1532
+ ) => _getAllFluidMarketDataChunked(network, getViemProvider(provider, network, { batch: { multicall: true } }));
1533
+
1534
+ export const _getFluidTokenData = async (provider: Client, network: NetworkNumber, token: string) => {
1535
+ const view = FluidViewContractViem(provider, network);
1536
+ const fTokenAddress = getFTokenAddress(token, network);
1537
+ const [
1538
+ data,
1539
+ rewardsApiResponse,
1540
+ ] = await Promise.all([
1541
+ await view.read.getFTokenData([fTokenAddress]),
1542
+ fetch(`https://api.fluid.instadapp.io/v2/lending/${network}/tokens/${fTokenAddress}`),
1543
+ ]);
1544
+ let rewardsData = { rewards: [] } as any;
1545
+ if (!rewardsApiResponse.ok) {
1546
+ console.log('External API Failure: Failed to fetch fluid rewards APY');
1547
+ } else {
1548
+ rewardsData = await rewardsApiResponse.json();
1549
+ }
1550
+ const supplyRate = new Dec(rewardsData?.supplyRate || '0').div(100).toString();
1551
+ const rewardRates = rewardsData?.rewards?.reduce((acc: Dec, item: any) => acc.add(new Dec(item.rate || '0').div(100)), new Dec(0)) || '0';
1552
+ const stakeRate = new Dec(rewardsData?.asset?.stakingApr || '0').div(100).toString();
1553
+ const decimals = data.decimals.toString();
1554
+
1555
+ const depositRate = new Dec(getEthAmountForDecimals(data.convertToShares.toString(), decimals)).toString();
1556
+ const withdrawRate = new Dec(getEthAmountForDecimals(data.convertToAssets.toString(), decimals)).toString();
1557
+
1558
+ return {
1559
+ fTokenAddress,
1560
+ fTokenSymbol: data.symbol,
1561
+ decimals,
1562
+ totalDeposited: getEthAmountForDecimals(data.totalAssets.toString(), decimals),
1563
+ withdrawable: getEthAmountForDecimals(data.withdrawable.toString(), decimals),
1564
+ apy: new Dec(supplyRate).plus(rewardRates).plus(stakeRate).toString(),
1565
+ depositRate,
1566
+ withdrawRate,
1567
+ };
1568
+ };
1569
+
1570
+ export const getFluidTokenData = async (
1571
+ provider: EthereumProvider,
1572
+ network: NetworkNumber,
1573
+ token: string,
1574
+ ) => _getFluidTokenData(getViemProvider(provider, network), network, token);
1575
+
1576
+ const parseFDepositTokenData = (fTokenData: FluidFTokenDataStructOutput, userPosition: FluidUserEarnPositionStructOutput, apiData: any, fTokenAddress?: string) => {
1577
+ const decimals = fTokenData.decimals.toString();
1578
+ const depositRate = new Dec(getEthAmountForDecimals(fTokenData.convertToShares.toString(), decimals)).toString();
1579
+ const withdrawRate = new Dec(getEthAmountForDecimals(fTokenData.convertToAssets.toString(), decimals)).toString();
1580
+ const supplyRate = new Dec(apiData?.supplyRate || '0').div(100).toString();
1581
+ const rewardRates = apiData?.rewards?.reduce((acc: Dec, item: any) => acc.add(new Dec(item.rate || '0').div(100)), new Dec(0)) || '0';
1582
+ const stakeRate = new Dec(apiData?.asset?.stakingApr || '0').div(100).toString();
1583
+ return {
1584
+ fTokenAddress,
1585
+ fTokenSymbol: fTokenData.symbol,
1586
+ decimals,
1587
+ totalDeposited: getEthAmountForDecimals(fTokenData.totalAssets.toString(), decimals),
1588
+ withdrawable: getEthAmountForDecimals(fTokenData.withdrawable.toString(), decimals),
1589
+ apy: new Dec(supplyRate).plus(rewardRates).plus(stakeRate).toString(),
1590
+ depositRate,
1591
+ withdrawRate,
1592
+ deposited: getEthAmountForDecimals(userPosition.underlyingAssets.toString(), decimals),
1593
+ depositedShares: getEthAmountForDecimals(userPosition.fTokenShares.toString(), decimals),
1594
+ };
1595
+ };
1596
+
1597
+ export const _getFluidDepositData = async (provider: Client, network: NetworkNumber, token: string, address: EthAddress) => {
1598
+ const view = FluidViewContractViem(provider, network);
1599
+ const fTokenAddress = getFTokenAddress(token, network);
1600
+ const [
1601
+ [userPosition, fTokenData],
1602
+ rewardsApiResponse,
1603
+ ] = await Promise.all([
1604
+ view.read.getUserEarnPositionWithFToken([fTokenAddress, address]),
1605
+ fetch(`https://api.fluid.instadapp.io/v2/lending/${network}/tokens/${fTokenAddress}`),
1606
+ ]);
1607
+ let rewardsData = { rewards: [] };
1608
+ if (!rewardsApiResponse.ok) {
1609
+ console.log('External API Failure: Failed to fetch fluid rewards APY');
1610
+ } else {
1611
+ rewardsData = await rewardsApiResponse.json();
1612
+ }
1613
+
1614
+ return parseFDepositTokenData(fTokenData, userPosition, rewardsData, fTokenAddress);
1615
+ };
1616
+
1617
+ export const getFluidDepositData = async (
1618
+ provider: EthereumProvider,
1619
+ network: NetworkNumber,
1620
+ token: string,
1621
+ address: EthAddress,
1622
+ ) => _getFluidDepositData(getViemProvider(provider, network), network, token, address);
1623
+
1624
+ export const _getAllUserEarnPositionsWithFTokens = async (provider: Client, network: NetworkNumber, user: EthAddress) => {
1625
+ const view = FluidViewContractViem(provider, network);
1626
+ const [
1627
+ [userPositions, fTokensData],
1628
+ rewardsApiResponse,
1629
+ ] = await Promise.all([
1630
+ view.read.getAllUserEarnPositionsWithFTokens([user]),
1631
+ fetch(`https://api.fluid.instadapp.io/v2/lending/${network}/tokens`),
1632
+ ]);
1633
+
1634
+ let rewardsData = {
1635
+ data: [{ address: ZERO_ADDRESS, rewards: [] }],
1636
+ };
1637
+ if (!rewardsApiResponse.ok) {
1638
+ console.log('External API Failure: Failed to fetch fluid rewards APY');
1639
+ } else {
1640
+ rewardsData = await rewardsApiResponse.json();
1641
+ }
1642
+
1643
+ const parsedRes = fTokensData.reduce<ReturnType<typeof parseFDepositTokenData>[]>((acc, fTokenData, i) => {
1644
+ const userPosition = userPositions[i];
1645
+ const deposited = userPosition?.underlyingAssets;
1646
+
1647
+ if (Number(deposited) > 0) {
1648
+ const fTokenAddress = fTokenData.tokenAddress;
1649
+ const apiData = rewardsData.data.find((item: any) => compareAddresses(item.address, fTokenAddress));
1650
+ acc.push(parseFDepositTokenData(fTokenData, userPosition, apiData, fTokenAddress));
1651
+ }
1652
+
1653
+ return acc;
1654
+ }, []);
1655
+
1656
+ return parsedRes;
1657
+ };
1658
+
1659
+ export const getAllUserEarnPositionsWithFTokens = async (
1660
+ provider: EthereumProvider,
1661
+ network: NetworkNumber,
1662
+ user: EthAddress,
1663
+ ) => _getAllUserEarnPositionsWithFTokens(getViemProvider(provider, network), network, user);
1664
+
1665
+ export const _getUserPositions = async (provider: PublicClient, network: NetworkNumber, user: EthAddress) => {
1666
+ const view = FluidViewContractViem(provider, network);
1667
+
1668
+ const data = await view.read.getUserPositions([user]);
1669
+
1670
+ const parsedMarketData = (await Promise.all(data[1].map(async (vaultData) => parseMarketData(provider, vaultData, network))));
1671
+
1672
+ const userData = data[0].map((position, i) => (parsedMarketData[i] && { ...parseUserData(position, parsedMarketData[i]) }));
1673
+
1674
+ return parsedMarketData.map((market, i) => ({
1675
+ marketData: market,
1676
+ userData: userData[i],
1677
+ })).filter(md => md.marketData !== undefined);
1678
+ };
1679
+
1680
+ export const getUserPositions = async (
1681
+ provider: EthereumProvider,
1682
+ network: NetworkNumber,
1683
+ user: EthAddress,
1684
+ ) => _getUserPositions(getViemProvider(provider, network), network, user);
1685
+
1686
+ const getTokenPricePortfolio = async (token: string, provider: PublicClient, network: NetworkNumber) => {
1687
+ if (token === 'ETH') {
1688
+ const ethFeedContract = ETHPriceFeedContractViem(provider, network);
1689
+ return ethFeedContract.read.latestAnswer();
1690
+ }
1691
+ if (token === 'WBTC') {
1692
+ const wbtcFeedContract = BTCPriceFeedContractViem(provider, network);
1693
+ return wbtcFeedContract.read.latestAnswer();
1694
+ }
1695
+ if (token === 'wstETH') {
1696
+ return getWstETHPrice(provider, network);
1697
+ }
1698
+ if (token === 'weETH' && network !== NetworkNumber.Plasma) {
1699
+ return getWeETHPrice(provider, network);
1700
+ }
1701
+
1702
+ if (token === 'wrsETH') {
1703
+ return getWsrETHPrice(provider, network);
1704
+ }
1705
+
1706
+ if (token === 'syrupUSDT') {
1707
+ return getSyrupUSDTPrice(provider, network);
1708
+ }
1709
+
1710
+ if (token === 'wstUSR') {
1711
+ return getWstUSRPrice(provider, network);
1712
+ }
1713
+
1714
+ const isMainnet = isMainnetNetwork(network);
1715
+ const chainLinkFeedAddress = getChainlinkAssetAddress(token, network);
1716
+ if (isMainnet) {
1717
+ const feedRegistryContract = FeedRegistryContractViem(provider, NetworkNumber.Eth);
1718
+ return feedRegistryContract.read.latestAnswer([chainLinkFeedAddress, USD_QUOTE]);
1719
+ }
1720
+
1721
+ const feedRegistryContract = DFSFeedRegistryContractViem(provider, network);
1722
+ return feedRegistryContract.read.latestRoundData([chainLinkFeedAddress, USD_QUOTE]);
1723
+ };
1724
+
1725
+ const tokensWithoutChainlinkPrices = ['sUSDS', 'USDA', 'ezETH', 'rsETH', 'weETHs', 'LBTC'];
1726
+
1727
+ const handleTokenWithoutChainlinkPrice = (token: string, prices: Record<string, string>) => {
1728
+ if (token === 'sUSDS') {
1729
+ return new Dec('107057929').div(1e8).toString();
1730
+ }
1731
+ if (token === 'USDA') {
1732
+ return new Dec('100000000').div(1e8).toString();
1733
+ }
1734
+ if (token === 'wstUSR') {
1735
+ return new Dec('111280000').div(1e8).toString();
1736
+ }
1737
+ if (token === 'ezETH') {
1738
+ return new Dec(prices.ETH).mul(1.06).toString();
1739
+ }
1740
+ if (token === 'rsETH') {
1741
+ return new Dec(prices.wstETH).mul(1.0557).toString();
1742
+ }
1743
+ if (token === 'weETHs') {
1744
+ return new Dec(prices.wstETH).mul(1.032).toString();
1745
+ }
1746
+ if (token === 'LBTC') {
1747
+ return prices.WBTC;
1748
+ }
1749
+ return '0';
1750
+ };
1751
+
1752
+ const getTokensPricesForPortfolio = async (tokens: string[], provider: PublicClient, network: NetworkNumber) => {
1753
+ const tokensWithChainlinkPrices = tokens.filter((token) => !tokensWithoutChainlinkPrices.includes(token));
1754
+ const pricesFromChainlink: Record<string, string> = {};
1755
+ await Promise.all(tokensWithChainlinkPrices.map(async (token) => {
1756
+ try {
1757
+ const price = await getTokenPricePortfolio(token, provider, network);
1758
+ if (typeof price === 'string') pricesFromChainlink[token] = price;
1759
+ else if (typeof price === 'bigint') pricesFromChainlink[token] = new Dec(price).div(1e8).toString();
1760
+ else pricesFromChainlink[token] = new Dec(price[1]!.toString() as string).div(1e8).toString();
1761
+ } catch (error: any) {
1762
+ console.error(`Error occured while fetching price for ${token}: ${error.message}`);
1763
+ }
1764
+ }));
1765
+ tokens.forEach((token) => {
1766
+ if (tokensWithoutChainlinkPrices.includes(token)) {
1767
+ pricesFromChainlink[token] = handleTokenWithoutChainlinkPrice(token, pricesFromChainlink);
1768
+ }
1769
+ });
1770
+
1771
+ return pricesFromChainlink;
1772
+ };
1773
+
1774
+ export const _getUserPositionsPortfolio = async (provider: PublicClient, network: NetworkNumber, user: EthAddress) => {
1775
+ const view = FluidViewContractViem(provider, network);
1776
+
1777
+ const data = await view.read.getUserPositions([user]);
1778
+ const tokens = Array.from(new Set(data[1].map((vaultData) => {
1779
+ const vaultTokens = [getAssetInfoByAddress(vaultData.supplyToken0, network).symbol, getAssetInfoByAddress(vaultData.borrowToken0, network).symbol];
1780
+ if (vaultData.supplyToken1 && !compareAddresses(ZERO_ADDRESS, vaultData.supplyToken1)) vaultTokens.push(getAssetInfoByAddress(vaultData.supplyToken1, network).symbol);
1781
+ if (vaultData.borrowToken1 && !compareAddresses(ZERO_ADDRESS, vaultData.borrowToken1)) vaultTokens.push(getAssetInfoByAddress(vaultData.borrowToken1, network).symbol);
1782
+ return vaultTokens;
1783
+ }).flat()));
1784
+
1785
+ if (tokens.length === 0) return [];
1786
+ // ETH and WBTC needed for other tokens prices
1787
+ if (!tokens.includes('ETH')) tokens.push('ETH');
1788
+ if (!tokens.includes('WBTC')) tokens.push('WBTC');
1789
+
1790
+ const tokenPrices = await getTokensPricesForPortfolio(tokens, provider, network);
1791
+
1792
+ const parsedMarketData = (await Promise.all(data[1].map(async (vaultData) => parseMarketData(provider, vaultData, network, tokenPrices))));
1793
+
1794
+ const userData = data[0].map((position, i) => (parsedMarketData[i] && { ...parseUserData(position, parsedMarketData[i]) }));
1795
+
1796
+ return parsedMarketData.map((market, i) => ({
1797
+ marketData: market,
1798
+ userData: userData[i],
1799
+ })).filter(md => md.marketData !== undefined);
1800
+ };