@defisaver/positions-sdk 2.1.169-custom-fetching-dev → 2.1.169-custom-fetching-3-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 (145) hide show
  1. package/cjs/aaveV2/index.d.ts +4 -4
  2. package/cjs/aaveV2/index.js +7 -7
  3. package/cjs/aaveV3/index.d.ts +4 -3
  4. package/cjs/aaveV3/index.js +14 -10
  5. package/cjs/aaveV3/merkl.d.ts +5 -1
  6. package/cjs/aaveV3/merkl.js +8 -3
  7. package/cjs/aaveV4/index.d.ts +4 -3
  8. package/cjs/aaveV4/index.js +19 -16
  9. package/cjs/aaveV4/merkl.d.ts +10 -2
  10. package/cjs/aaveV4/merkl.js +15 -6
  11. package/cjs/claiming/aaveV3.d.ts +2 -2
  12. package/cjs/claiming/aaveV3.js +5 -4
  13. package/cjs/claiming/king.d.ts +2 -2
  14. package/cjs/claiming/king.js +3 -2
  15. package/cjs/claiming/spark.d.ts +3 -3
  16. package/cjs/claiming/spark.js +8 -7
  17. package/cjs/claiming/uniswap.d.ts +2 -2
  18. package/cjs/claiming/uniswap.js +3 -2
  19. package/cjs/compoundV2/index.d.ts +4 -4
  20. package/cjs/compoundV2/index.js +9 -9
  21. package/cjs/compoundV3/index.d.ts +5 -4
  22. package/cjs/compoundV3/index.js +17 -13
  23. package/cjs/compoundV3/merkl.d.ts +1 -1
  24. package/cjs/compoundV3/merkl.js +2 -5
  25. package/cjs/curveUsd/index.d.ts +5 -5
  26. package/cjs/curveUsd/index.js +17 -17
  27. package/cjs/fluid/index.d.ts +23 -22
  28. package/cjs/fluid/index.js +75 -72
  29. package/cjs/fluid/merkl.d.ts +5 -3
  30. package/cjs/fluid/merkl.js +11 -6
  31. package/cjs/liquity/index.d.ts +4 -4
  32. package/cjs/liquity/index.js +23 -23
  33. package/cjs/liquityV2/index.d.ts +10 -10
  34. package/cjs/liquityV2/index.js +59 -59
  35. package/cjs/llamaLend/index.d.ts +5 -5
  36. package/cjs/llamaLend/index.js +13 -13
  37. package/cjs/maker/index.d.ts +6 -6
  38. package/cjs/maker/index.js +16 -16
  39. package/cjs/morphoBlue/index.d.ts +7 -7
  40. package/cjs/morphoBlue/index.js +28 -26
  41. package/cjs/morphoBlue/merkl.d.ts +5 -3
  42. package/cjs/morphoBlue/merkl.js +6 -8
  43. package/cjs/morphoMidnight/index.d.ts +5 -5
  44. package/cjs/morphoMidnight/index.js +18 -18
  45. package/cjs/portfolio/customDeFiData.js +45 -40
  46. package/cjs/services/merkl.d.ts +36 -2
  47. package/cjs/services/merkl.js +142 -19
  48. package/cjs/services/priceService.d.ts +12 -12
  49. package/cjs/services/priceService.js +30 -29
  50. package/cjs/services/viem.d.ts +10 -2
  51. package/cjs/services/viem.js +27 -1
  52. package/cjs/spark/index.d.ts +4 -4
  53. package/cjs/spark/index.js +9 -9
  54. package/cjs/types/merkl.d.ts +18 -0
  55. package/cjs/types/portfolio.d.ts +2 -0
  56. package/cjs/umbrella/index.d.ts +2 -2
  57. package/cjs/umbrella/index.js +5 -4
  58. package/esm/aaveV2/index.d.ts +4 -4
  59. package/esm/aaveV2/index.js +8 -8
  60. package/esm/aaveV3/index.d.ts +4 -3
  61. package/esm/aaveV3/index.js +15 -11
  62. package/esm/aaveV3/merkl.d.ts +5 -1
  63. package/esm/aaveV3/merkl.js +9 -4
  64. package/esm/aaveV4/index.d.ts +4 -3
  65. package/esm/aaveV4/index.js +20 -17
  66. package/esm/aaveV4/merkl.d.ts +10 -2
  67. package/esm/aaveV4/merkl.js +16 -7
  68. package/esm/claiming/aaveV3.d.ts +2 -2
  69. package/esm/claiming/aaveV3.js +5 -4
  70. package/esm/claiming/king.d.ts +2 -2
  71. package/esm/claiming/king.js +3 -2
  72. package/esm/claiming/spark.d.ts +3 -3
  73. package/esm/claiming/spark.js +8 -7
  74. package/esm/claiming/uniswap.d.ts +2 -2
  75. package/esm/claiming/uniswap.js +3 -2
  76. package/esm/compoundV2/index.d.ts +4 -4
  77. package/esm/compoundV2/index.js +9 -9
  78. package/esm/compoundV3/index.d.ts +5 -4
  79. package/esm/compoundV3/index.js +19 -15
  80. package/esm/compoundV3/merkl.d.ts +1 -1
  81. package/esm/compoundV3/merkl.js +3 -6
  82. package/esm/curveUsd/index.d.ts +5 -5
  83. package/esm/curveUsd/index.js +17 -17
  84. package/esm/fluid/index.d.ts +23 -22
  85. package/esm/fluid/index.js +76 -73
  86. package/esm/fluid/merkl.d.ts +5 -3
  87. package/esm/fluid/merkl.js +12 -7
  88. package/esm/liquity/index.d.ts +4 -4
  89. package/esm/liquity/index.js +23 -23
  90. package/esm/liquityV2/index.d.ts +10 -10
  91. package/esm/liquityV2/index.js +60 -60
  92. package/esm/llamaLend/index.d.ts +5 -5
  93. package/esm/llamaLend/index.js +13 -13
  94. package/esm/maker/index.d.ts +6 -6
  95. package/esm/maker/index.js +16 -16
  96. package/esm/morphoBlue/index.d.ts +7 -7
  97. package/esm/morphoBlue/index.js +29 -27
  98. package/esm/morphoBlue/merkl.d.ts +5 -3
  99. package/esm/morphoBlue/merkl.js +7 -9
  100. package/esm/morphoMidnight/index.d.ts +5 -5
  101. package/esm/morphoMidnight/index.js +18 -18
  102. package/esm/portfolio/customDeFiData.js +47 -42
  103. package/esm/services/merkl.d.ts +36 -2
  104. package/esm/services/merkl.js +136 -18
  105. package/esm/services/priceService.d.ts +12 -12
  106. package/esm/services/priceService.js +31 -30
  107. package/esm/services/viem.d.ts +10 -2
  108. package/esm/services/viem.js +25 -0
  109. package/esm/spark/index.d.ts +4 -4
  110. package/esm/spark/index.js +10 -10
  111. package/esm/types/merkl.d.ts +18 -0
  112. package/esm/types/portfolio.d.ts +2 -0
  113. package/esm/umbrella/index.d.ts +2 -2
  114. package/esm/umbrella/index.js +5 -4
  115. package/package.json +1 -1
  116. package/src/aaveV2/index.ts +7 -7
  117. package/src/aaveV3/index.ts +15 -11
  118. package/src/aaveV3/merkl.ts +9 -4
  119. package/src/aaveV4/index.ts +19 -16
  120. package/src/aaveV4/merkl.ts +16 -7
  121. package/src/claiming/aaveV3.ts +5 -3
  122. package/src/claiming/king.ts +6 -3
  123. package/src/claiming/spark.ts +9 -6
  124. package/src/claiming/uniswap.ts +4 -3
  125. package/src/compoundV2/index.ts +10 -9
  126. package/src/compoundV3/index.ts +20 -14
  127. package/src/compoundV3/merkl.ts +5 -4
  128. package/src/curveUsd/index.ts +19 -17
  129. package/src/fluid/index.ts +88 -70
  130. package/src/fluid/merkl.ts +12 -7
  131. package/src/liquity/index.ts +23 -23
  132. package/src/liquityV2/index.ts +64 -56
  133. package/src/llamaLend/index.ts +15 -13
  134. package/src/maker/index.ts +19 -16
  135. package/src/morphoBlue/index.ts +23 -20
  136. package/src/morphoBlue/merkl.ts +9 -6
  137. package/src/morphoMidnight/index.ts +13 -13
  138. package/src/portfolio/customDeFiData.ts +48 -41
  139. package/src/services/merkl.ts +152 -19
  140. package/src/services/priceService.ts +33 -30
  141. package/src/services/viem.ts +16 -0
  142. package/src/spark/index.ts +11 -8
  143. package/src/types/merkl.ts +20 -0
  144. package/src/types/portfolio.ts +2 -0
  145. package/src/umbrella/index.ts +6 -5
@@ -1,6 +1,6 @@
1
1
  import { getAssetInfoByAddress } from '@defisaver/tokens';
2
2
  import { aprToApy } from '../moneymarket';
3
- import { fetchAllMerklOpportunities } from '../services/merkl';
3
+ import { fetchMerklOpportunities } from '../services/merkl';
4
4
  import { compareAddresses, wethToEth } from '../services/utils';
5
5
  import {
6
6
  CompoundV3AssetData,
@@ -21,12 +21,13 @@ import {
21
21
  * be gated and sized by a curated check in EligibilityMapping; unknown ids apply
22
22
  * unconditionally, mirroring Aave V3.
23
23
  */
24
- export const getCompoundV3MerklOpportunities = async (): Promise<MerklOpportunity[]> => {
24
+ export const getCompoundV3MerklOpportunities = async (asOfTimestamp?: number, chainId?: NetworkNumber): Promise<MerklOpportunity[]> => {
25
25
  try {
26
- return await fetchAllMerklOpportunities({
26
+ return await fetchMerklOpportunities({
27
27
  mainProtocolId: 'compound-v3',
28
28
  status: OpportunityStatus.LIVE,
29
- });
29
+ ...(chainId ? { chainId: chainId.toString() } : {}),
30
+ }, asOfTimestamp);
30
31
  } catch (e) {
31
32
  console.error('Failed to fetch Compound V3 Merkl campaigns', e);
32
33
  return [];
@@ -22,7 +22,7 @@ const ammRateToBorrowApy = (rateWei: string) => {
22
22
  return new Dec(new Dec(2.718281828459).pow(exponent).minus(1)).mul(100).toString();
23
23
  };
24
24
 
25
- const getAndFormatBands = async (provider: Client, network: NetworkNumber, selectedMarket: CrvUSDMarketData, _minBand: string, _maxBand: string) => {
25
+ const getAndFormatBands = async (provider: Client, network: NetworkNumber, selectedMarket: CrvUSDMarketData, _minBand: string, _maxBand: string, blockNumber: Blockish = 'latest') => {
26
26
  const contract = CrvUSDViewContractViem(provider, network);
27
27
  const minBand = parseInt(_minBand, 10);
28
28
  const maxBand = parseInt(_maxBand, 10);
@@ -46,7 +46,7 @@ const getAndFormatBands = async (provider: Client, network: NetworkNumber, selec
46
46
  } else {
47
47
  start = pivots[index - 1] + 1;
48
48
  }
49
- const pivotedBandsData = await contract.read.getBandsData([selectedMarket.controllerAddress, BigInt(start), BigInt(pivot)]);
49
+ const pivotedBandsData = await contract.read.getBandsData([selectedMarket.controllerAddress, BigInt(start), BigInt(pivot)], setViemBlockNumber(blockNumber));
50
50
  return pivotedBandsData;
51
51
  }))).flat();
52
52
 
@@ -59,17 +59,17 @@ const getAndFormatBands = async (provider: Client, network: NetworkNumber, selec
59
59
  }));
60
60
  };
61
61
 
62
- export const _getCurveUsdGlobalData = async (provider: Client, network: NetworkNumber, selectedMarket: CrvUSDMarketData): Promise<CrvUSDGlobalMarketData> => {
62
+ export const _getCurveUsdGlobalData = async (provider: Client, network: NetworkNumber, selectedMarket: CrvUSDMarketData, blockNumber: Blockish = 'latest'): Promise<CrvUSDGlobalMarketData> => {
63
63
  const contract = CrvUSDViewContractViem(provider, network);
64
64
  const factoryContract = CrvUSDFactoryContractViem(provider, network);
65
65
  const cntrollerContract = createViemContractFromConfigFunc('crvUSDwstETHController', selectedMarket.controllerAddress)(provider, network);
66
66
  const debtAsset = selectedMarket.baseAsset;
67
67
 
68
68
  const [debtCeiling, _, data, loanDiscountWei] = await Promise.all([
69
- factoryContract.read.debt_ceiling([selectedMarket.controllerAddress]),
70
- factoryContract.read.total_debt(),
71
- contract.read.globalData([selectedMarket.controllerAddress]),
72
- cntrollerContract.read.loan_discount(),
69
+ factoryContract.read.debt_ceiling([selectedMarket.controllerAddress], setViemBlockNumber(blockNumber)),
70
+ factoryContract.read.total_debt(setViemBlockNumber(blockNumber)),
71
+ contract.read.globalData([selectedMarket.controllerAddress], setViemBlockNumber(blockNumber)),
72
+ cntrollerContract.read.loan_discount(setViemBlockNumber(blockNumber)),
73
73
  ]);
74
74
 
75
75
  // all prices are in 18 decimals
@@ -79,7 +79,7 @@ export const _getCurveUsdGlobalData = async (provider: Client, network: NetworkN
79
79
  const borrowRate = ammRateToBorrowApy(data.ammRate.toString());
80
80
  const futureBorrowRate = ammRateToBorrowApy(data.monetaryPolicyRate.toString());
81
81
 
82
- const bandsData = await getAndFormatBands(provider, network, selectedMarket, data.minBand.toString(), data.maxBand.toString());
82
+ const bandsData = await getAndFormatBands(provider, network, selectedMarket, data.minBand.toString(), data.maxBand.toString(), blockNumber);
83
83
 
84
84
  const leftToBorrow = new Dec(debtCeiling.toString()).minus(totalDebt).toString();
85
85
 
@@ -112,7 +112,8 @@ export const getCurveUsdGlobalData = async (
112
112
  provider: EthereumProvider,
113
113
  network: NetworkNumber,
114
114
  selectedMarket: CrvUSDMarketData,
115
- ): Promise<CrvUSDGlobalMarketData> => _getCurveUsdGlobalData(getViemProvider(provider, network), network, selectedMarket);
115
+ blockNumber: Blockish = 'latest',
116
+ ): Promise<CrvUSDGlobalMarketData> => _getCurveUsdGlobalData(getViemProvider(provider, network), network, selectedMarket, blockNumber);
116
117
 
117
118
  const getStatusForUser = (bandRange: string[], activeBand: string, crvUSDSupplied: string, collSupplied: string, healthPercent: string) => {
118
119
  // if bands are equal, that can only be [0,0] which means user doesn't have loan (min number of bands is 4)
@@ -172,17 +173,17 @@ export const getCrvUsdAccountBalances = async (
172
173
  /**
173
174
  * @param borrowRate market borrow APY in % (`CrvUSDGlobalMarketData.borrowRate`); when omitted it is read from the view contract
174
175
  */
175
- export const _getCurveUsdUserData = async (provider: Client, network: NetworkNumber, address: EthAddress, selectedMarket: CrvUSDMarketData, activeBand: string, borrowRate?: string): Promise<CrvUSDUserData> => {
176
+ export const _getCurveUsdUserData = async (provider: Client, network: NetworkNumber, address: EthAddress, selectedMarket: CrvUSDMarketData, activeBand: string, borrowRate?: string, blockNumber: Blockish = 'latest'): Promise<CrvUSDUserData> => {
176
177
  const contract = CrvUSDViewContractViem(provider, network);
177
178
  const collAsset = selectedMarket.collAsset;
178
179
  const debtAsset = selectedMarket.baseAsset;
179
180
 
180
181
  const [data, collStakingApy, marketBorrowRate] = await Promise.all([
181
- contract.read.userData([selectedMarket.controllerAddress, address]),
182
+ contract.read.userData([selectedMarket.controllerAddress, address], setViemBlockNumber(blockNumber)),
182
183
  STAKING_ASSETS.includes(collAsset) ? getStakingApy(collAsset) : Promise.resolve('0'),
183
184
  borrowRate !== undefined
184
185
  ? Promise.resolve(borrowRate)
185
- : contract.read.globalData([selectedMarket.controllerAddress]).then((globalData) => ammRateToBorrowApy(globalData.ammRate.toString())),
186
+ : contract.read.globalData([selectedMarket.controllerAddress], setViemBlockNumber(blockNumber)).then((globalData) => ammRateToBorrowApy(globalData.ammRate.toString())),
186
187
  ]);
187
188
 
188
189
  const health = assetAmountInEth(data.health.toString());
@@ -223,7 +224,7 @@ export const _getCurveUsdUserData = async (provider: Client, network: NetworkNum
223
224
  const priceHigh = assetAmountInEth(data.priceHigh.toString());
224
225
  const priceLow = assetAmountInEth(data.priceLow.toString());
225
226
 
226
- const _userBands = data.loanExists ? (await getAndFormatBands(provider, network, selectedMarket, data.bandRange[0].toString(), data.bandRange[1].toString())) : [];
227
+ const _userBands = data.loanExists ? (await getAndFormatBands(provider, network, selectedMarket, data.bandRange[0].toString(), data.bandRange[1].toString(), blockNumber)) : [];
227
228
 
228
229
  const status = data.loanExists ? getStatusForUser(data.bandRange.map(b => b.toString()), activeBand, crvUSDSupplied, collSupplied, healthPercent) : CrvUSDStatus.Nonexistant;
229
230
 
@@ -267,10 +268,11 @@ export const getCurveUsdUserData = async (
267
268
  selectedMarket: CrvUSDMarketData,
268
269
  activeBand: string,
269
270
  borrowRate?: string,
270
- ): Promise<CrvUSDUserData> => _getCurveUsdUserData(getViemProvider(provider, network), network, address, selectedMarket, activeBand, borrowRate);
271
+ blockNumber: Blockish = 'latest',
272
+ ): Promise<CrvUSDUserData> => _getCurveUsdUserData(getViemProvider(provider, network), network, address, selectedMarket, activeBand, borrowRate, blockNumber);
271
273
 
272
- export const getCurveUsdFullPositionData = async (provider: EthereumProvider, network: NetworkNumber, address: EthAddress, selectedMarket: CrvUSDMarketData): Promise<CrvUSDUserData> => {
273
- const marketData = await getCurveUsdGlobalData(provider, network, selectedMarket);
274
- const positionData = await getCurveUsdUserData(provider, network, address, selectedMarket, marketData.activeBand, marketData.borrowRate);
274
+ export const getCurveUsdFullPositionData = async (provider: EthereumProvider, network: NetworkNumber, address: EthAddress, selectedMarket: CrvUSDMarketData, blockNumber: Blockish = 'latest'): Promise<CrvUSDUserData> => {
275
+ const marketData = await getCurveUsdGlobalData(provider, network, selectedMarket, blockNumber);
276
+ const positionData = await getCurveUsdUserData(provider, network, address, selectedMarket, marketData.activeBand, marketData.borrowRate, blockNumber);
275
277
  return positionData;
276
278
  };
@@ -22,6 +22,7 @@ import {
22
22
  FluidVaultDataStructOutputStruct,
23
23
  FluidVaultType,
24
24
  InnerFluidMarketData,
25
+ Blockish,
25
26
  } from '../types';
26
27
  import {
27
28
  BTCPriceFeedContractViem,
@@ -66,7 +67,7 @@ import {
66
67
  parseWstUSRPriceCalls,
67
68
  } from '../services/priceService';
68
69
  import { getStakingApy, STAKING_ASSETS } from '../staking';
69
- import { getViemProvider } from '../services/viem';
70
+ import { getBlockTimestamp, getViemProvider, setViemBlockNumber } from '../services/viem';
70
71
  import { attachFluidMerklIncentives, getFluidMerklCampaigns } from './merkl';
71
72
 
72
73
  export {
@@ -101,6 +102,7 @@ const getChainLinkPricesForTokens = async (
101
102
  tokens: string[],
102
103
  network: NetworkNumber,
103
104
  client: PublicClient,
105
+ blockNumber: Blockish = 'latest',
104
106
  ): Promise<{ [key: string]: string }> => {
105
107
  const isMainnet = isMainnetNetwork(network);
106
108
 
@@ -158,7 +160,7 @@ const getChainLinkPricesForTokens = async (
158
160
 
159
161
  const calls = [...staticCalls, ..._calls].filter((call) => call);
160
162
  // @ts-ignore
161
- const results = await client.multicall({ contracts: calls });
163
+ const results = await client.multicall({ contracts: calls, ...setViemBlockNumber(blockNumber) });
162
164
 
163
165
  const ethPriceChainlink = new Dec(results[0].result as string).div(1e8).toString();
164
166
  const btcPriceChainlink = new Dec(results[1].result as string).div(1e8).toString();
@@ -294,7 +296,7 @@ const getChainLinkPricesForTokens = async (
294
296
  };
295
297
 
296
298
 
297
- const getTokenPriceFromChainlink = async (asset: AssetData, network: NetworkNumber, provider: PublicClient) => {
299
+ const getTokenPriceFromChainlink = async (asset: AssetData, network: NetworkNumber, provider: PublicClient, blockNumber: Blockish = 'latest') => {
298
300
  if (asset.symbol === 'sUSDS') {
299
301
  return new Dec('105276929').div(1e8).toString();
300
302
  }
@@ -305,15 +307,15 @@ const getTokenPriceFromChainlink = async (asset: AssetData, network: NetworkNumb
305
307
  let loanTokenPrice;
306
308
  if (asset.symbol === 'wstETH') {
307
309
  // need to handle wstETH for l2s inside getWstETHPrice
308
- loanTokenPrice = await getWstETHPriceFluid(provider, network);
310
+ loanTokenPrice = await getWstETHPriceFluid(provider, network, blockNumber);
309
311
  } else if (isMainnet) {
310
312
  const feedRegistryContract = FeedRegistryContractViem(provider, NetworkNumber.Eth);
311
- loanTokenPrice = isTokenUSDA ? '100000000' : await feedRegistryContract.read.latestAnswer([loanTokenFeedAddress, USD_QUOTE]);
313
+ loanTokenPrice = isTokenUSDA ? '100000000' : await feedRegistryContract.read.latestAnswer([loanTokenFeedAddress, USD_QUOTE], setViemBlockNumber(blockNumber));
312
314
  } else {
313
315
  // Currently only base network is supported
314
316
  const feedRegistryContract = DFSFeedRegistryContractViem(provider, network);
315
317
  try {
316
- const roundPriceData = isTokenUSDA ? [0, '100000000'] : await feedRegistryContract.read.latestRoundData([loanTokenFeedAddress, USD_QUOTE]);
318
+ const roundPriceData = isTokenUSDA ? [0, '100000000'] : await feedRegistryContract.read.latestRoundData([loanTokenFeedAddress, USD_QUOTE], setViemBlockNumber(blockNumber));
317
319
  loanTokenPrice = roundPriceData[1].toString();
318
320
  } catch (err) {
319
321
  console.error(`Error fetching price for ${asset.symbol} on ${network}: ${err}`);
@@ -364,7 +366,7 @@ const getTradingApy = async (poolAddress: EthAddress) => {
364
366
  return new Dec(data.tradingApy).div(100).toString();
365
367
  };
366
368
 
367
- const parseT1MarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null) => {
369
+ const parseT1MarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null, blockNumber: Blockish = 'latest') => {
368
370
  const collAssetContract = getAssetInfoByAddress(data.supplyToken0, network);
369
371
  const collAsset = getAssetInfo(getNativeAssetFromWrapped(collAssetContract.symbol), network);
370
372
  const debtAssetContract = getAssetInfoByAddress(data.borrowToken0, network);
@@ -380,7 +382,7 @@ const parseT1MarketData = async (provider: PublicClient, data: FluidVaultDataStr
380
382
  if (tokenPrices) {
381
383
  debtPriceParsed = tokenPrices[debtAsset.symbol] || '0';
382
384
  } else {
383
- debtPriceParsed = await getTokenPriceFromChainlink(debtAssetContract, network, provider);
385
+ debtPriceParsed = await getTokenPriceFromChainlink(debtAssetContract, network, provider, blockNumber);
384
386
  }
385
387
 
386
388
  const collAssetData: FluidAssetData = {
@@ -484,7 +486,7 @@ const parseT1MarketData = async (provider: PublicClient, data: FluidVaultDataStr
484
486
  } as FluidMarketData;
485
487
  };
486
488
 
487
- const parseT2MarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null) => {
489
+ const parseT2MarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null, blockNumber: Blockish = 'latest') => {
488
490
  const collAsset0Contract = getAssetInfoByAddress(data.supplyToken0, network);
489
491
  const collAsset0 = getAssetInfo(getNativeAssetFromWrapped(collAsset0Contract.symbol), network);
490
492
  const collAsset1Contract = getAssetInfoByAddress(data.supplyToken1, network);
@@ -501,7 +503,7 @@ const parseT2MarketData = async (provider: PublicClient, data: FluidVaultDataStr
501
503
  if (tokenPrices) {
502
504
  prices = tokenPrices;
503
505
  } else {
504
- prices = await getChainLinkPricesForTokens([collAsset0Contract.address, collAsset1Contract.address, debtAssetContract.address], network, provider);
506
+ prices = await getChainLinkPricesForTokens([collAsset0Contract.address, collAsset1Contract.address, debtAssetContract.address], network, provider, blockNumber);
505
507
  }
506
508
 
507
509
  const {
@@ -677,7 +679,7 @@ const parseT2MarketData = async (provider: PublicClient, data: FluidVaultDataStr
677
679
  } as FluidMarketData;
678
680
  };
679
681
 
680
- const parseT3MarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null) => {
682
+ const parseT3MarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null, blockNumber: Blockish = 'latest') => {
681
683
  const collAssetContract = getAssetInfoByAddress(data.supplyToken0, network);
682
684
  const collAsset = getAssetInfo(getNativeAssetFromWrapped(collAssetContract.symbol), network);
683
685
  const debtAsset0Contract = getAssetInfoByAddress(data.borrowToken0, network);
@@ -715,7 +717,7 @@ const parseT3MarketData = async (provider: PublicClient, data: FluidVaultDataStr
715
717
  if (tokenPrices) {
716
718
  prices = tokenPrices;
717
719
  } else {
718
- prices = await getChainLinkPricesForTokens([collAssetContract.address, debtAsset0Contract.address, debtAsset1Contract.address], network, provider);
720
+ prices = await getChainLinkPricesForTokens([collAssetContract.address, debtAsset0Contract.address, debtAsset1Contract.address], network, provider, blockNumber);
719
721
  }
720
722
 
721
723
  const supplyRate = new Dec(data.supplyRateVault).div(100).toString();
@@ -867,7 +869,7 @@ const parseT3MarketData = async (provider: PublicClient, data: FluidVaultDataStr
867
869
  } as FluidMarketData;
868
870
  };
869
871
 
870
- const parseT4MarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null) => {
872
+ const parseT4MarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null, blockNumber: Blockish = 'latest') => {
871
873
  const collAsset0Contract = getAssetInfoByAddress(data.supplyToken0, network);
872
874
  const collAsset0 = getAssetInfo(getNativeAssetFromWrapped(collAsset0Contract.symbol), network);
873
875
  const collAsset1Contract = getAssetInfoByAddress(data.supplyToken1, network);
@@ -889,7 +891,7 @@ const parseT4MarketData = async (provider: PublicClient, data: FluidVaultDataStr
889
891
  } else {
890
892
  prices = await getChainLinkPricesForTokens(
891
893
  [collAsset0Contract.address, collAsset1Contract.address, debtAsset0Contract.address, debtAsset1Contract.address],
892
- network, provider);
894
+ network, provider, blockNumber);
893
895
  }
894
896
 
895
897
  const {
@@ -1124,7 +1126,7 @@ const parseT4MarketData = async (provider: PublicClient, data: FluidVaultDataStr
1124
1126
  } as FluidMarketData;
1125
1127
  };
1126
1128
 
1127
- const parseMarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null) => {
1129
+ const parseMarketData = async (provider: PublicClient, data: FluidVaultDataStructOutputStruct, network: NetworkNumber, tokenPrices: Record<string, string> | null = null, blockNumber: Blockish = 'latest') => {
1128
1130
  const marketInfo = getFluidMarketInfoById(+(data.vaultId.toString()), network);
1129
1131
  if (!marketInfo) {
1130
1132
  console.error(`Fluid market with address ${data.vault} not supported.`);
@@ -1133,13 +1135,13 @@ const parseMarketData = async (provider: PublicClient, data: FluidVaultDataStruc
1133
1135
  const vaultType = parseVaultType(+(data.vaultType.toString()));
1134
1136
  switch (vaultType) {
1135
1137
  case FluidVaultType.T1:
1136
- return parseT1MarketData(provider, data, network, tokenPrices);
1138
+ return parseT1MarketData(provider, data, network, tokenPrices, blockNumber);
1137
1139
  case FluidVaultType.T2:
1138
- return parseT2MarketData(provider, data, network, tokenPrices);
1140
+ return parseT2MarketData(provider, data, network, tokenPrices, blockNumber);
1139
1141
  case FluidVaultType.T3:
1140
- return parseT3MarketData(provider, data, network, tokenPrices);
1142
+ return parseT3MarketData(provider, data, network, tokenPrices, blockNumber);
1141
1143
  case FluidVaultType.T4:
1142
- return parseT4MarketData(provider, data, network, tokenPrices);
1144
+ return parseT4MarketData(provider, data, network, tokenPrices, blockNumber);
1143
1145
  default:
1144
1146
  throw new Error(`Unknown vault type: ${vaultType}`);
1145
1147
  }
@@ -1450,15 +1452,15 @@ const parseUserData = (userPositionData: FluidUserPositionStructOutputStruct, va
1450
1452
  }
1451
1453
  };
1452
1454
 
1453
- export const _getFluidMarketData = async (provider: PublicClient, network: NetworkNumber, market: FluidMarketInfo) => {
1455
+ export const _getFluidMarketData = async (provider: PublicClient, network: NetworkNumber, market: FluidMarketInfo, blockNumber: Blockish = 'latest') => {
1454
1456
  const view = FluidViewContractViem(provider, network);
1455
1457
 
1456
1458
  const [data, merklCampaigns] = await Promise.all([
1457
- view.read.getVaultData([market.marketAddress]),
1459
+ view.read.getVaultData([market.marketAddress], setViemBlockNumber(blockNumber)),
1458
1460
  getFluidMerklCampaigns(network),
1459
1461
  ]);
1460
1462
 
1461
- const marketData = await parseMarketData(provider, data, network);
1463
+ const marketData = await parseMarketData(provider, data, network, null, blockNumber);
1462
1464
  return marketData ? attachFluidMerklIncentives(marketData, merklCampaigns) : marketData;
1463
1465
  };
1464
1466
 
@@ -1466,23 +1468,26 @@ export const getFluidMarketData = async (
1466
1468
  provider: EthereumProvider,
1467
1469
  network: NetworkNumber,
1468
1470
  market: FluidMarketInfo,
1469
- ) => _getFluidMarketData(getViemProvider(provider, network), network, market);
1471
+ blockNumber: Blockish = 'latest',
1472
+ ) => _getFluidMarketData(getViemProvider(provider, network), network, market, blockNumber);
1470
1473
 
1471
1474
  export const _getFluidVaultIdsForUser = async (
1472
1475
  provider: Client,
1473
1476
  network: NetworkNumber,
1474
1477
  user: EthAddress,
1478
+ blockNumber: Blockish = 'latest',
1475
1479
  ): Promise<string[]> => {
1476
1480
  const view = FluidViewContractViem(provider, network);
1477
1481
 
1478
- return (await view.read.getUserNftIds([user])).map((item: bigint) => item.toString());
1482
+ return (await view.read.getUserNftIds([user], setViemBlockNumber(blockNumber))).map((item: bigint) => item.toString());
1479
1483
  };
1480
1484
 
1481
1485
  export const getFluidVaultIdsForUser = async (
1482
1486
  provider: EthereumProvider,
1483
1487
  network: NetworkNumber,
1484
1488
  user: EthAddress,
1485
- ): Promise<string[]> => _getFluidVaultIdsForUser(getViemProvider(provider, network), network, user);
1489
+ blockNumber: Blockish = 'latest',
1490
+ ): Promise<string[]> => _getFluidVaultIdsForUser(getViemProvider(provider, network), network, user, blockNumber);
1486
1491
 
1487
1492
 
1488
1493
  export const _getFluidPosition = async (
@@ -1493,10 +1498,11 @@ export const _getFluidPosition = async (
1493
1498
  assetsData: FluidAssetsData
1494
1499
  marketData: InnerFluidMarketData,
1495
1500
  },
1501
+ blockNumber: Blockish = 'latest',
1496
1502
  ): Promise<FluidVaultData> => {
1497
1503
  const view = FluidViewContractViem(provider, network);
1498
1504
 
1499
- const data = await view.read.getPositionByNftId([BigInt(vaultId)]);
1505
+ const data = await view.read.getPositionByNftId([BigInt(vaultId)], setViemBlockNumber(blockNumber));
1500
1506
 
1501
1507
  const userPositionData = data[0];
1502
1508
 
@@ -1511,15 +1517,16 @@ export const getFluidPosition = async (
1511
1517
  assetsData: FluidAssetsData
1512
1518
  marketData: InnerFluidMarketData,
1513
1519
  },
1514
- ): Promise<FluidVaultData> => _getFluidPosition(getViemProvider(provider, network), network, vaultId, extractedState);
1520
+ blockNumber: Blockish = 'latest',
1521
+ ): Promise<FluidVaultData> => _getFluidPosition(getViemProvider(provider, network), network, vaultId, extractedState, blockNumber);
1515
1522
 
1516
- export const _getFluidPositionWithMarket = async (provider: PublicClient, network: NetworkNumber, vaultId: string) => {
1523
+ export const _getFluidPositionWithMarket = async (provider: PublicClient, network: NetworkNumber, vaultId: string, blockNumber: Blockish = 'latest') => {
1517
1524
  const view = FluidViewContractViem(provider, network);
1518
1525
  const [data, merklCampaigns] = await Promise.all([
1519
- view.read.getPositionByNftId([BigInt(vaultId)]),
1526
+ view.read.getPositionByNftId([BigInt(vaultId)], setViemBlockNumber(blockNumber)),
1520
1527
  getFluidMerklCampaigns(network),
1521
1528
  ]);
1522
- const parsedMarketData = await parseMarketData(provider, data[1], network);
1529
+ const parsedMarketData = await parseMarketData(provider, data[1], network, null, blockNumber);
1523
1530
  if (!parsedMarketData) {
1524
1531
  return;
1525
1532
  }
@@ -1536,17 +1543,18 @@ export const getFluidPositionWithMarket = async (
1536
1543
  provider: EthereumProvider,
1537
1544
  network: NetworkNumber,
1538
1545
  vaultId: string,
1539
- ) => _getFluidPositionWithMarket(getViemProvider(provider, network), network, vaultId);
1546
+ blockNumber: Blockish = 'latest',
1547
+ ) => _getFluidPositionWithMarket(getViemProvider(provider, network), network, vaultId, blockNumber);
1540
1548
 
1541
- export const _getAllFluidMarketDataChunked = async (network: NetworkNumber, provider: PublicClient) => {
1549
+ export const _getAllFluidMarketDataChunked = async (network: NetworkNumber, provider: PublicClient, blockNumber: Blockish = 'latest') => {
1542
1550
  const versions = getFluidVersionsDataForNetwork(network);
1543
1551
  const view = FluidViewContractViem(provider, network);
1544
1552
  const [data, merklCampaigns] = await Promise.all([
1545
- Promise.all(versions.map((version) => view.read.getVaultData([version.marketAddress]))),
1553
+ Promise.all(versions.map((version) => view.read.getVaultData([version.marketAddress], setViemBlockNumber(blockNumber)))),
1546
1554
  getFluidMerklCampaigns(network),
1547
1555
  ]);
1548
1556
  return Promise.all(data.map(async (item) => {
1549
- const marketData = await parseMarketData(provider, item, network);
1557
+ const marketData = await parseMarketData(provider, item, network, null, blockNumber);
1550
1558
  return marketData ? attachFluidMerklIncentives(marketData, merklCampaigns) : marketData;
1551
1559
  }));
1552
1560
  };
@@ -1554,16 +1562,17 @@ export const _getAllFluidMarketDataChunked = async (network: NetworkNumber, prov
1554
1562
  export const getAllFluidMarketDataChunked = async (
1555
1563
  network: NetworkNumber,
1556
1564
  provider: EthereumProvider,
1557
- ) => _getAllFluidMarketDataChunked(network, getViemProvider(provider, network, { batch: { multicall: true } }));
1565
+ blockNumber: Blockish = 'latest',
1566
+ ) => _getAllFluidMarketDataChunked(network, getViemProvider(provider, network, { batch: { multicall: true } }), blockNumber);
1558
1567
 
1559
- export const _getFluidTokenData = async (provider: Client, network: NetworkNumber, token: string) => {
1568
+ export const _getFluidTokenData = async (provider: Client, network: NetworkNumber, token: string, blockNumber: Blockish = 'latest') => {
1560
1569
  const view = FluidViewContractViem(provider, network);
1561
1570
  const fTokenAddress = getFTokenAddress(token, network);
1562
1571
  const [
1563
1572
  data,
1564
1573
  rewardsApiResponse,
1565
1574
  ] = await Promise.all([
1566
- await view.read.getFTokenData([fTokenAddress]),
1575
+ await view.read.getFTokenData([fTokenAddress], setViemBlockNumber(blockNumber)),
1567
1576
  fetch(`https://api.fluid.instadapp.io/v2/lending/${network}/tokens/${fTokenAddress}`),
1568
1577
  ]);
1569
1578
  let rewardsData = { rewards: [] } as any;
@@ -1596,7 +1605,8 @@ export const getFluidTokenData = async (
1596
1605
  provider: EthereumProvider,
1597
1606
  network: NetworkNumber,
1598
1607
  token: string,
1599
- ) => _getFluidTokenData(getViemProvider(provider, network), network, token);
1608
+ blockNumber: Blockish = 'latest',
1609
+ ) => _getFluidTokenData(getViemProvider(provider, network), network, token, blockNumber);
1600
1610
 
1601
1611
  const parseFDepositTokenData = (fTokenData: FluidFTokenDataStructOutput, userPosition: FluidUserEarnPositionStructOutput, apiData: any, fTokenAddress?: string) => {
1602
1612
  const decimals = fTokenData.decimals.toString();
@@ -1619,14 +1629,14 @@ const parseFDepositTokenData = (fTokenData: FluidFTokenDataStructOutput, userPos
1619
1629
  };
1620
1630
  };
1621
1631
 
1622
- export const _getFluidDepositData = async (provider: Client, network: NetworkNumber, token: string, address: EthAddress) => {
1632
+ export const _getFluidDepositData = async (provider: Client, network: NetworkNumber, token: string, address: EthAddress, blockNumber: Blockish = 'latest') => {
1623
1633
  const view = FluidViewContractViem(provider, network);
1624
1634
  const fTokenAddress = getFTokenAddress(token, network);
1625
1635
  const [
1626
1636
  [userPosition, fTokenData],
1627
1637
  rewardsApiResponse,
1628
1638
  ] = await Promise.all([
1629
- view.read.getUserEarnPositionWithFToken([fTokenAddress, address]),
1639
+ view.read.getUserEarnPositionWithFToken([fTokenAddress, address], setViemBlockNumber(blockNumber)),
1630
1640
  fetch(`https://api.fluid.instadapp.io/v2/lending/${network}/tokens/${fTokenAddress}`),
1631
1641
  ]);
1632
1642
  let rewardsData = { rewards: [] };
@@ -1644,15 +1654,16 @@ export const getFluidDepositData = async (
1644
1654
  network: NetworkNumber,
1645
1655
  token: string,
1646
1656
  address: EthAddress,
1647
- ) => _getFluidDepositData(getViemProvider(provider, network), network, token, address);
1657
+ blockNumber: Blockish = 'latest',
1658
+ ) => _getFluidDepositData(getViemProvider(provider, network), network, token, address, blockNumber);
1648
1659
 
1649
- export const _getAllUserEarnPositionsWithFTokens = async (provider: Client, network: NetworkNumber, user: EthAddress) => {
1660
+ export const _getAllUserEarnPositionsWithFTokens = async (provider: Client, network: NetworkNumber, user: EthAddress, blockNumber: Blockish = 'latest') => {
1650
1661
  const view = FluidViewContractViem(provider, network);
1651
1662
  const [
1652
1663
  [userPositions, fTokensData],
1653
1664
  rewardsApiResponse,
1654
1665
  ] = await Promise.all([
1655
- view.read.getAllUserEarnPositionsWithFTokens([user]),
1666
+ view.read.getAllUserEarnPositionsWithFTokens([user], setViemBlockNumber(blockNumber)),
1656
1667
  fetch(`https://api.fluid.instadapp.io/v2/lending/${network}/tokens`),
1657
1668
  ]);
1658
1669
 
@@ -1685,17 +1696,18 @@ export const getAllUserEarnPositionsWithFTokens = async (
1685
1696
  provider: EthereumProvider,
1686
1697
  network: NetworkNumber,
1687
1698
  user: EthAddress,
1688
- ) => _getAllUserEarnPositionsWithFTokens(getViemProvider(provider, network), network, user);
1699
+ blockNumber: Blockish = 'latest',
1700
+ ) => _getAllUserEarnPositionsWithFTokens(getViemProvider(provider, network), network, user, blockNumber);
1689
1701
 
1690
- export const _getUserPositions = async (provider: PublicClient, network: NetworkNumber, user: EthAddress) => {
1702
+ export const _getUserPositions = async (provider: PublicClient, network: NetworkNumber, user: EthAddress, blockNumber: Blockish = 'latest') => {
1691
1703
  const view = FluidViewContractViem(provider, network);
1692
1704
 
1693
1705
  const [data, merklCampaigns] = await Promise.all([
1694
- view.read.getUserPositions([user]),
1706
+ view.read.getUserPositions([user], setViemBlockNumber(blockNumber)),
1695
1707
  getFluidMerklCampaigns(network),
1696
1708
  ]);
1697
1709
 
1698
- const parsedMarketData = (await Promise.all(data[1].map(async (vaultData) => parseMarketData(provider, vaultData, network))))
1710
+ const parsedMarketData = (await Promise.all(data[1].map(async (vaultData) => parseMarketData(provider, vaultData, network, null, blockNumber))))
1699
1711
  .map(marketData => (marketData ? attachFluidMerklIncentives(marketData, merklCampaigns) : marketData));
1700
1712
 
1701
1713
  const userData = data[0].map((position, i) => (parsedMarketData[i] && { ...parseUserData(position, parsedMarketData[i]) }));
@@ -1710,45 +1722,46 @@ export const getUserPositions = async (
1710
1722
  provider: EthereumProvider,
1711
1723
  network: NetworkNumber,
1712
1724
  user: EthAddress,
1713
- ) => _getUserPositions(getViemProvider(provider, network), network, user);
1725
+ blockNumber: Blockish = 'latest',
1726
+ ) => _getUserPositions(getViemProvider(provider, network), network, user, blockNumber);
1714
1727
 
1715
- const getTokenPricePortfolio = async (token: string, provider: PublicClient, network: NetworkNumber) => {
1728
+ const getTokenPricePortfolio = async (token: string, provider: PublicClient, network: NetworkNumber, blockNumber: Blockish = 'latest') => {
1716
1729
  if (token === 'ETH') {
1717
1730
  const ethFeedContract = ETHPriceFeedContractViem(provider, network);
1718
- return ethFeedContract.read.latestAnswer();
1731
+ return ethFeedContract.read.latestAnswer(setViemBlockNumber(blockNumber));
1719
1732
  }
1720
1733
  if (token === 'WBTC') {
1721
1734
  const wbtcFeedContract = BTCPriceFeedContractViem(provider, network);
1722
- return wbtcFeedContract.read.latestAnswer();
1735
+ return wbtcFeedContract.read.latestAnswer(setViemBlockNumber(blockNumber));
1723
1736
  }
1724
1737
  if (token === 'wstETH') {
1725
- return getWstETHPrice(provider, network);
1738
+ return getWstETHPrice(provider, network, blockNumber);
1726
1739
  }
1727
1740
  if (token === 'weETH' && network !== NetworkNumber.Plasma) {
1728
- return getWeETHPrice(provider, network);
1741
+ return getWeETHPrice(provider, network, blockNumber);
1729
1742
  }
1730
1743
 
1731
1744
  if (token === 'wrsETH') {
1732
- return getWsrETHPrice(provider, network);
1745
+ return getWsrETHPrice(provider, network, blockNumber);
1733
1746
  }
1734
1747
 
1735
1748
  if (token === 'syrupUSDT') {
1736
- return getSyrupUSDTPrice(provider, network);
1749
+ return getSyrupUSDTPrice(provider, network, blockNumber);
1737
1750
  }
1738
1751
 
1739
1752
  if (token === 'wstUSR') {
1740
- return getWstUSRPrice(provider, network);
1753
+ return getWstUSRPrice(provider, network, blockNumber);
1741
1754
  }
1742
1755
 
1743
1756
  const isMainnet = isMainnetNetwork(network);
1744
1757
  const chainLinkFeedAddress = getChainlinkAssetAddress(token, network);
1745
1758
  if (isMainnet) {
1746
1759
  const feedRegistryContract = FeedRegistryContractViem(provider, NetworkNumber.Eth);
1747
- return feedRegistryContract.read.latestAnswer([chainLinkFeedAddress, USD_QUOTE]);
1760
+ return feedRegistryContract.read.latestAnswer([chainLinkFeedAddress, USD_QUOTE], setViemBlockNumber(blockNumber));
1748
1761
  }
1749
1762
 
1750
1763
  const feedRegistryContract = DFSFeedRegistryContractViem(provider, network);
1751
- return feedRegistryContract.read.latestRoundData([chainLinkFeedAddress, USD_QUOTE]);
1764
+ return feedRegistryContract.read.latestRoundData([chainLinkFeedAddress, USD_QUOTE], setViemBlockNumber(blockNumber));
1752
1765
  };
1753
1766
 
1754
1767
  const tokensWithoutChainlinkPrices = ['sUSDS', 'USDA', 'ezETH', 'rsETH', 'weETHs', 'LBTC'];
@@ -1778,12 +1791,12 @@ const handleTokenWithoutChainlinkPrice = (token: string, prices: Record<string,
1778
1791
  return '0';
1779
1792
  };
1780
1793
 
1781
- const getTokensPricesForPortfolio = async (tokens: string[], provider: PublicClient, network: NetworkNumber) => {
1794
+ const getTokensPricesForPortfolio = async (tokens: string[], provider: PublicClient, network: NetworkNumber, blockNumber: Blockish = 'latest') => {
1782
1795
  const tokensWithChainlinkPrices = tokens.filter((token) => !tokensWithoutChainlinkPrices.includes(token));
1783
1796
  const pricesFromChainlink: Record<string, string> = {};
1784
1797
  await Promise.all(tokensWithChainlinkPrices.map(async (token) => {
1785
1798
  try {
1786
- const price = await getTokenPricePortfolio(token, provider, network);
1799
+ const price = await getTokenPricePortfolio(token, provider, network, blockNumber);
1787
1800
  if (typeof price === 'string') pricesFromChainlink[token] = price;
1788
1801
  else if (typeof price === 'bigint') pricesFromChainlink[token] = new Dec(price).div(1e8).toString();
1789
1802
  else pricesFromChainlink[token] = new Dec(price[1]!.toString() as string).div(1e8).toString();
@@ -1800,10 +1813,10 @@ const getTokensPricesForPortfolio = async (tokens: string[], provider: PublicCli
1800
1813
  return pricesFromChainlink;
1801
1814
  };
1802
1815
 
1803
- export const _getUserPositionsPortfolio = async (provider: PublicClient, network: NetworkNumber, user: EthAddress) => {
1816
+ export const _getUserPositionsPortfolio = async (provider: PublicClient, network: NetworkNumber, user: EthAddress, blockNumber: Blockish = 'latest') => {
1804
1817
  const view = FluidViewContractViem(provider, network);
1805
1818
 
1806
- const data = await view.read.getUserPositions([user]);
1819
+ const data = await view.read.getUserPositions([user], setViemBlockNumber(blockNumber));
1807
1820
  const tokens = Array.from(new Set(data[1].map((vaultData) => {
1808
1821
  const vaultTokens = [getAssetInfoByAddress(vaultData.supplyToken0, network).symbol, getAssetInfoByAddress(vaultData.borrowToken0, network).symbol];
1809
1822
  if (vaultData.supplyToken1 && !compareAddresses(ZERO_ADDRESS, vaultData.supplyToken1)) vaultTokens.push(getAssetInfoByAddress(vaultData.supplyToken1, network).symbol);
@@ -1817,11 +1830,11 @@ export const _getUserPositionsPortfolio = async (provider: PublicClient, network
1817
1830
  if (!tokens.includes('WBTC')) tokens.push('WBTC');
1818
1831
 
1819
1832
  const [tokenPrices, merklCampaigns] = await Promise.all([
1820
- getTokensPricesForPortfolio(tokens, provider, network),
1833
+ getTokensPricesForPortfolio(tokens, provider, network, blockNumber),
1821
1834
  getFluidMerklCampaigns(network),
1822
1835
  ]);
1823
1836
 
1824
- const parsedMarketData = (await Promise.all(data[1].map(async (vaultData) => parseMarketData(provider, vaultData, network, tokenPrices))))
1837
+ const parsedMarketData = (await Promise.all(data[1].map(async (vaultData) => parseMarketData(provider, vaultData, network, tokenPrices, blockNumber))))
1825
1838
  .map(marketData => (marketData ? attachFluidMerklIncentives(marketData, merklCampaigns) : marketData));
1826
1839
 
1827
1840
  const userData = data[0].map((position, i) => (parsedMarketData[i] && { ...parseUserData(position, parsedMarketData[i]) }));
@@ -1833,12 +1846,13 @@ export const _getUserPositionsPortfolio = async (provider: PublicClient, network
1833
1846
  };
1834
1847
 
1835
1848
 
1836
- export const _getAllFluidMarketDataPortfolio = async (provider: PublicClient, network: NetworkNumber): Promise<Record<string, FluidMarketData>> => {
1849
+ /** `blockTimestamp` (unix seconds) is the historical block's timestamp when already known; otherwise it is read from the block. */
1850
+ export const _getAllFluidMarketDataPortfolio = async (provider: PublicClient, network: NetworkNumber, blockNumber: Blockish = 'latest', blockTimestamp?: number): Promise<Record<string, FluidMarketData>> => {
1837
1851
  const versions = getFluidVersionsDataForNetwork(network);
1838
1852
  if (versions.length === 0) return {};
1839
1853
 
1840
1854
  const view = FluidViewContractViem(provider, network);
1841
- const vaultsData = await Promise.all(versions.map((version) => view.read.getVaultData([version.marketAddress])));
1855
+ const vaultsData = await Promise.all(versions.map((version) => view.read.getVaultData([version.marketAddress], setViemBlockNumber(blockNumber))));
1842
1856
 
1843
1857
  const tokens = Array.from(new Set(vaultsData.map((vaultData) => {
1844
1858
  const vaultTokens = [getAssetInfoByAddress(vaultData.supplyToken0, network).symbol, getAssetInfoByAddress(vaultData.borrowToken0, network).symbol];
@@ -1852,11 +1866,13 @@ export const _getAllFluidMarketDataPortfolio = async (provider: PublicClient, ne
1852
1866
  if (!tokens.includes('WBTC')) tokens.push('WBTC');
1853
1867
 
1854
1868
  const [tokenPrices, merklCampaigns] = await Promise.all([
1855
- getTokensPricesForPortfolio(tokens, provider, network),
1856
- getFluidMerklCampaigns(network),
1869
+ getTokensPricesForPortfolio(tokens, provider, network, blockNumber),
1870
+ // Merkl rewards are keyed by time: a historical block resolves to its timestamp first (one RPC
1871
+ // call unless given, none for 'latest') so the rewards in force at that moment are attached.
1872
+ getBlockTimestamp(provider, blockNumber, blockTimestamp).then((asOfTimestamp) => getFluidMerklCampaigns(network, asOfTimestamp)),
1857
1873
  ]);
1858
1874
 
1859
- const parsedMarketsData = await Promise.all(vaultsData.map(async (vaultData) => parseMarketData(provider, vaultData, network, tokenPrices)));
1875
+ const parsedMarketsData = await Promise.all(vaultsData.map(async (vaultData) => parseMarketData(provider, vaultData, network, tokenPrices, blockNumber)));
1860
1876
 
1861
1877
  const marketsData: Record<string, FluidMarketData> = {};
1862
1878
  parsedMarketsData.forEach((marketData, i) => {
@@ -1870,4 +1886,6 @@ export const _getAllFluidMarketDataPortfolio = async (provider: PublicClient, ne
1870
1886
  export const getAllFluidMarketDataPortfolio = async (
1871
1887
  provider: EthereumProvider,
1872
1888
  network: NetworkNumber,
1873
- ): Promise<Record<string, FluidMarketData>> => _getAllFluidMarketDataPortfolio(getViemProvider(provider, network, { batch: { multicall: true } }), network);
1889
+ blockNumber: Blockish = 'latest',
1890
+ blockTimestamp?: number,
1891
+ ): Promise<Record<string, FluidMarketData>> => _getAllFluidMarketDataPortfolio(getViemProvider(provider, network, { batch: { multicall: true } }), network, blockNumber, blockTimestamp);