@augustdigital/sdk 8.3.2 → 8.5.0

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 (204) hide show
  1. package/lib/abis/ERC20_Bytes32.d.ts +4 -0
  2. package/lib/abis/ERC20_Bytes32.js +4 -0
  3. package/lib/abis/ERC4626.d.ts +1 -0
  4. package/lib/abis/ERC4626.js +1 -0
  5. package/lib/abis/ERC721.d.ts +1 -0
  6. package/lib/abis/ERC721.js +1 -0
  7. package/lib/abis/FeeOracle.js +1 -0
  8. package/lib/abis/LendingPool.js +1 -0
  9. package/lib/abis/LendingPoolV2.js +1 -0
  10. package/lib/abis/Multicall3.js +3 -0
  11. package/lib/abis/OFT.d.ts +20 -0
  12. package/lib/abis/OFT.js +20 -0
  13. package/lib/abis/SmartAccount.d.ts +1 -0
  14. package/lib/abis/SmartAccount.js +3 -0
  15. package/lib/abis/SwapRouter.d.ts +1 -0
  16. package/lib/abis/SwapRouter.js +1 -0
  17. package/lib/abis/UniversalSignatureValidator.js +3 -0
  18. package/lib/abis/index.d.ts +5 -0
  19. package/lib/abis/index.js +5 -0
  20. package/lib/adapters/evm/getters.d.ts +17 -2
  21. package/lib/adapters/evm/getters.js +35 -3
  22. package/lib/adapters/evm/index.d.ts +262 -0
  23. package/lib/adapters/evm/index.js +268 -1
  24. package/lib/adapters/evm/utils.d.ts +6 -0
  25. package/lib/adapters/evm/utils.js +7 -0
  26. package/lib/adapters/solana/constants.js +4 -1
  27. package/lib/adapters/solana/getters.d.ts +8 -0
  28. package/lib/adapters/solana/getters.js +21 -0
  29. package/lib/adapters/solana/idl/vault-idl.js +9 -0
  30. package/lib/adapters/solana/index.d.ts +55 -0
  31. package/lib/adapters/solana/index.js +57 -0
  32. package/lib/adapters/solana/utils.d.ts +28 -0
  33. package/lib/adapters/solana/utils.js +79 -4
  34. package/lib/adapters/solana/vault.actions.d.ts +19 -0
  35. package/lib/adapters/solana/vault.actions.js +47 -3
  36. package/lib/adapters/stellar/actions.d.ts +25 -0
  37. package/lib/adapters/stellar/actions.js +33 -0
  38. package/lib/adapters/stellar/constants.d.ts +26 -0
  39. package/lib/adapters/stellar/constants.js +29 -0
  40. package/lib/adapters/stellar/getters.d.ts +56 -0
  41. package/lib/adapters/stellar/getters.js +81 -0
  42. package/lib/adapters/stellar/index.d.ts +48 -0
  43. package/lib/adapters/stellar/index.js +48 -0
  44. package/lib/adapters/stellar/soroban.d.ts +20 -0
  45. package/lib/adapters/stellar/soroban.js +46 -0
  46. package/lib/adapters/stellar/submit.d.ts +12 -0
  47. package/lib/adapters/stellar/submit.js +19 -0
  48. package/lib/adapters/stellar/types.d.ts +27 -0
  49. package/lib/adapters/stellar/types.js +3 -0
  50. package/lib/adapters/stellar/utils.d.ts +10 -0
  51. package/lib/adapters/stellar/utils.js +10 -0
  52. package/lib/adapters/sui/getters.d.ts +6 -0
  53. package/lib/adapters/sui/getters.js +6 -0
  54. package/lib/adapters/sui/index.d.ts +15 -0
  55. package/lib/adapters/sui/index.js +15 -0
  56. package/lib/adapters/sui/transformer.d.ts +6 -0
  57. package/lib/adapters/sui/transformer.js +6 -0
  58. package/lib/adapters/sui/utils.d.ts +6 -0
  59. package/lib/adapters/sui/utils.js +6 -0
  60. package/lib/core/analytics/chain-name.d.ts +8 -0
  61. package/lib/core/analytics/chain-name.js +8 -0
  62. package/lib/core/analytics/constants.d.ts +4 -0
  63. package/lib/core/analytics/constants.js +4 -0
  64. package/lib/core/analytics/env.d.ts +25 -0
  65. package/lib/core/analytics/env.js +26 -0
  66. package/lib/core/analytics/index.d.ts +26 -0
  67. package/lib/core/analytics/index.js +35 -0
  68. package/lib/core/analytics/instrumentation.d.ts +26 -0
  69. package/lib/core/analytics/instrumentation.js +66 -2
  70. package/lib/core/analytics/method-taxonomy.d.ts +16 -0
  71. package/lib/core/analytics/method-taxonomy.js +18 -0
  72. package/lib/core/analytics/metrics.d.ts +23 -0
  73. package/lib/core/analytics/metrics.js +40 -0
  74. package/lib/core/analytics/sanitize.d.ts +38 -0
  75. package/lib/core/analytics/sanitize.js +46 -0
  76. package/lib/core/analytics/sentry-runtime.d.ts +11 -0
  77. package/lib/core/analytics/sentry-runtime.js +19 -0
  78. package/lib/core/analytics/sentry.d.ts +45 -0
  79. package/lib/core/analytics/sentry.js +115 -2
  80. package/lib/core/analytics/types.d.ts +27 -0
  81. package/lib/core/analytics/user-identity.d.ts +34 -0
  82. package/lib/core/analytics/user-identity.js +42 -0
  83. package/lib/core/analytics/version.d.ts +6 -1
  84. package/lib/core/analytics/version.js +6 -1
  85. package/lib/core/auth/verify.js +5 -0
  86. package/lib/core/base.class.d.ts +75 -0
  87. package/lib/core/base.class.js +56 -0
  88. package/lib/core/cache.d.ts +5 -0
  89. package/lib/core/cache.js +6 -0
  90. package/lib/core/constants/adapters.d.ts +15 -0
  91. package/lib/core/constants/adapters.js +28 -8
  92. package/lib/core/constants/core.d.ts +12 -1
  93. package/lib/core/constants/core.js +12 -0
  94. package/lib/core/constants/swap-router.d.ts +46 -0
  95. package/lib/core/constants/swap-router.js +50 -0
  96. package/lib/core/constants/vaults.d.ts +56 -0
  97. package/lib/core/constants/vaults.js +66 -1
  98. package/lib/core/constants/web3.d.ts +3 -0
  99. package/lib/core/constants/web3.js +18 -5
  100. package/lib/core/errors/index.d.ts +36 -0
  101. package/lib/core/errors/index.js +29 -0
  102. package/lib/core/fetcher.d.ts +134 -0
  103. package/lib/core/fetcher.js +191 -5
  104. package/lib/core/helpers/adapters.d.ts +9 -0
  105. package/lib/core/helpers/adapters.js +11 -0
  106. package/lib/core/helpers/chain-address.d.ts +10 -0
  107. package/lib/core/helpers/chain-address.js +11 -0
  108. package/lib/core/helpers/core.d.ts +42 -0
  109. package/lib/core/helpers/core.js +66 -1
  110. package/lib/core/helpers/explorer-link.d.ts +14 -0
  111. package/lib/core/helpers/explorer-link.js +14 -0
  112. package/lib/core/helpers/signer.d.ts +26 -0
  113. package/lib/core/helpers/signer.js +39 -0
  114. package/lib/core/helpers/swap-router.d.ts +32 -0
  115. package/lib/core/helpers/swap-router.js +32 -0
  116. package/lib/core/helpers/vault-version.d.ts +1 -0
  117. package/lib/core/helpers/vault-version.js +2 -0
  118. package/lib/core/helpers/vaults.d.ts +8 -0
  119. package/lib/core/helpers/vaults.js +22 -8
  120. package/lib/core/helpers/web3.d.ts +152 -0
  121. package/lib/core/helpers/web3.js +183 -6
  122. package/lib/core/logger/index.d.ts +55 -0
  123. package/lib/core/logger/index.js +19 -0
  124. package/lib/core/logger/slack.d.ts +3 -0
  125. package/lib/core/logger/slack.js +3 -0
  126. package/lib/core/vault-metadata.d.ts +6 -0
  127. package/lib/core/vault-metadata.js +6 -0
  128. package/lib/core/version-check.d.ts +52 -0
  129. package/lib/core/version-check.js +81 -0
  130. package/lib/evm/methods/crossChainVault.d.ts +90 -0
  131. package/lib/evm/methods/crossChainVault.js +186 -1
  132. package/lib/evm/methods/crossChainVaultRegistry.d.ts +93 -0
  133. package/lib/evm/methods/crossChainVaultRegistry.js +240 -0
  134. package/lib/evm/methods/index.d.ts +1 -0
  135. package/lib/evm/methods/index.js +1 -0
  136. package/lib/evm/types/crossChain.d.ts +202 -0
  137. package/lib/evm/types/crossChain.js +11 -0
  138. package/lib/index.d.ts +16 -0
  139. package/lib/index.js +19 -0
  140. package/lib/main.d.ts +288 -5
  141. package/lib/main.js +305 -0
  142. package/lib/modules/api/index.d.ts +1 -0
  143. package/lib/modules/api/index.js +6 -0
  144. package/lib/modules/api/main.d.ts +52 -0
  145. package/lib/modules/api/main.js +130 -0
  146. package/lib/modules/sub-accounts/fetcher.d.ts +15 -0
  147. package/lib/modules/sub-accounts/fetcher.js +15 -0
  148. package/lib/modules/sub-accounts/main.d.ts +33 -0
  149. package/lib/modules/sub-accounts/main.js +38 -0
  150. package/lib/modules/sub-accounts/utils.d.ts +3 -0
  151. package/lib/modules/sub-accounts/utils.js +3 -0
  152. package/lib/modules/vaults/adapter.helpers.d.ts +18 -0
  153. package/lib/modules/vaults/adapter.helpers.js +34 -0
  154. package/lib/modules/vaults/fetcher.d.ts +20 -0
  155. package/lib/modules/vaults/fetcher.js +40 -3
  156. package/lib/modules/vaults/getters.d.ts +295 -0
  157. package/lib/modules/vaults/getters.js +552 -12
  158. package/lib/modules/vaults/index.d.ts +12 -0
  159. package/lib/modules/vaults/index.js +12 -0
  160. package/lib/modules/vaults/main.d.ts +292 -4
  161. package/lib/modules/vaults/main.js +379 -7
  162. package/lib/modules/vaults/read.actions.d.ts +168 -0
  163. package/lib/modules/vaults/read.actions.js +143 -0
  164. package/lib/modules/vaults/types.d.ts +34 -0
  165. package/lib/modules/vaults/utils/call-data-decoder.d.ts +47 -0
  166. package/lib/modules/vaults/utils/call-data-decoder.js +56 -0
  167. package/lib/modules/vaults/utils/date-utils.d.ts +39 -0
  168. package/lib/modules/vaults/utils/date-utils.js +47 -1
  169. package/lib/modules/vaults/utils.d.ts +69 -0
  170. package/lib/modules/vaults/utils.js +104 -5
  171. package/lib/modules/vaults/write.actions.d.ts +363 -3
  172. package/lib/modules/vaults/write.actions.js +364 -2
  173. package/lib/polyfills.js +2 -0
  174. package/lib/sdk.d.ts +23705 -0
  175. package/lib/services/coingecko/fetcher.d.ts +13 -0
  176. package/lib/services/coingecko/fetcher.js +17 -0
  177. package/lib/services/debank/fetcher.d.ts +14 -0
  178. package/lib/services/debank/fetcher.js +12 -0
  179. package/lib/services/debank/utils.js +17 -0
  180. package/lib/services/layerzero/deposits.d.ts +11 -0
  181. package/lib/services/layerzero/deposits.js +34 -11
  182. package/lib/services/layerzero/redeems.d.ts +10 -0
  183. package/lib/services/layerzero/redeems.js +13 -0
  184. package/lib/services/layerzero/utils.d.ts +8 -0
  185. package/lib/services/layerzero/utils.js +11 -0
  186. package/lib/services/octavfi/fetcher.d.ts +7 -0
  187. package/lib/services/octavfi/fetcher.js +25 -0
  188. package/lib/services/octavfi/utils.d.ts +12 -0
  189. package/lib/services/octavfi/utils.js +44 -10
  190. package/lib/services/subgraph/fetcher.js +4 -2
  191. package/lib/services/subgraph/vaults.d.ts +12 -0
  192. package/lib/services/subgraph/vaults.js +43 -2
  193. package/lib/services/swap-quotes/index.d.ts +71 -0
  194. package/lib/services/swap-quotes/index.js +25 -0
  195. package/lib/services/swap-quotes/paraswap.d.ts +17 -0
  196. package/lib/services/swap-quotes/paraswap.js +14 -0
  197. package/lib/types/pools.d.ts +3 -0
  198. package/lib/types/typed-contract.d.ts +64 -0
  199. package/lib/types/vaults.d.ts +137 -2
  200. package/lib/types/vaults.js +10 -0
  201. package/lib/types/web3.d.ts +8 -0
  202. package/lib/types/web3.js +1 -0
  203. package/lib/types/webserver.d.ts +45 -0
  204. package/package.json +6 -5
@@ -84,6 +84,31 @@ const date_utils_1 = require("./utils/date-utils");
84
84
  const call_data_decoder_1 = require("./utils/call-data-decoder");
85
85
  const deposits_1 = require("../../services/layerzero/deposits");
86
86
  const redeems_1 = require("../../services/layerzero/redeems");
87
+ /**
88
+ * Vault Data Getters
89
+ *
90
+ * Main query functions for vault data across chains:
91
+ * - getVault: Comprehensive vault details with loans/allocations
92
+ * - getVaultLoans: Active loan data with on-chain enrichment
93
+ * - getVaultAllocations: DeFi/CeFi/OTC allocation breakdowns
94
+ * - getVaultAvailableRedemptions: Claimable redemption requests
95
+ * - getVaultRedemptionHistory: Historical redemption data
96
+ * - getVaultPositions: User positions across vaults
97
+ * - getVaultApy: @deprecated Current and historical APY data
98
+ * - getRewardsStakingPositions: Staking positions and rewards
99
+ * - getVaultTvl: Current and historical TVL
100
+ * - getVaultHistoricalTimeseries: Time series data for vault metrics
101
+ */
102
+ /**
103
+ * Fetch comprehensive vault data including on-chain state and backend metadata.
104
+ * Routes to appropriate chain adapter (EVM v1/v2, Solana, or Stellar) based on vault version.
105
+ * Optionally enriches with loan and allocation data.
106
+ * @param vault Vault contract address (EVM hex, Stellar C-address, or Solana program ID)
107
+ * @param loans Include active loan data
108
+ * @param allocations Include DeFi/CeFi allocation breakdowns
109
+ * @param options RPC and service configuration
110
+ * @returns Complete vault object with optional enrichments
111
+ */
87
112
  async function getVault({ vault, loans = false, allocations = false, options, loadSubaccounts, loadSnapshots, }) {
88
113
  let returnedVault;
89
114
  try {
@@ -116,6 +141,8 @@ async function getVault({ vault, loans = false, allocations = false, options, lo
116
141
  core_1.Logger.log.error('getVault', err, { vault });
117
142
  throw new Error(`#getVault::${vault}: ${err?.message}`);
118
143
  }
144
+ // Loans/allocations enrichment is only supported for EVM vaults.
145
+ // Non-EVM vaults already include empty defaults.
119
146
  const isEvmVault = returnedVault.version !== 'sol-0' &&
120
147
  returnedVault.version !== 'stellar-0' &&
121
148
  returnedVault.version !== 'sui-0';
@@ -161,19 +188,25 @@ async function getVault({ vault, loans = false, allocations = false, options, lo
161
188
  }
162
189
  return returnedVault;
163
190
  }
191
+ /**
192
+ * Vault Loans
193
+ */
164
194
  async function getVaultLoans(vault, options) {
195
+ // sanitize
165
196
  const vaultAddress = typeof vault === 'string' ? vault : vault.address;
166
197
  const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vaultAddress, options?.headers))?.[0];
167
198
  const vaultVersion = (0, core_1.getVaultVersionV2)(tokenizedVault);
168
199
  if (vaultVersion !== 'evm-0')
169
- return [];
200
+ return []; // only evm-0 uses subaccount loans
170
201
  try {
171
202
  let poolTotalSupply;
172
203
  let poolDecimals;
173
204
  const chainId = options?.chainId || tokenizedVault?.chain;
174
205
  const provider = (0, core_1.createProvider)(options.rpcUrl);
175
206
  const tokenizedVaultLoans = await (0, core_1.fetchTokenizedVaultLoans)(vaultAddress, chainId);
207
+ // if user passed IAddress or a full IVault
176
208
  if (typeof vault === 'string') {
209
+ // fetch necessary data
177
210
  const poolContract = (0, core_1.createContract)({
178
211
  provider,
179
212
  address: vaultAddress,
@@ -190,14 +223,21 @@ async function getVaultLoans(vault, options) {
190
223
  poolTotalSupply = vault.totalSupply.raw;
191
224
  poolDecimals = vault.decimals;
192
225
  }
226
+ // fetch and format all the loans
193
227
  const newLoans = (await Promise.all((Array.isArray(tokenizedVaultLoans) ? tokenizedVaultLoans : [])?.map(async (l) => {
228
+ // variables
194
229
  const borrower = l.borrower;
230
+ // calculate allocation
195
231
  const allocation = l.principal_amount /
196
232
  Number((0, ethers_1.formatUnits)(poolTotalSupply, poolDecimals));
233
+ // get august loan fee from oracle
197
234
  const loanFeeRate = await (0, core_1.getLoanOracleFeeRate)(provider, 'LOAN.REPAY.INTERESTS', l.address, chainId);
235
+ // calculate loan level apr
198
236
  const loanApr = Number(l.apr || 0) / 100;
199
237
  const loanAprAfterFees = loanApr * (1 - loanFeeRate / 100);
238
+ // determine if capital is deployed
200
239
  const isIdleCapital = core_1.IDLE_CAPITAL_BORROWER_ADDRESS.includes(borrower);
240
+ // build new obj
201
241
  const newLoanObj = {
202
242
  vault: vaultAddress,
203
243
  address: l.address,
@@ -228,7 +268,11 @@ async function getVaultLoans(vault, options) {
228
268
  throw new Error(`#getVaultLoans::${vault}:${e?.message}`);
229
269
  }
230
270
  }
271
+ /**
272
+ * Vault Subaccount Loans
273
+ */
231
274
  async function getVaultSubaccountLoans(vault, options) {
275
+ // sanitize
232
276
  const vaultAddress = typeof vault === 'string' ? vault : vault.address;
233
277
  const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vaultAddress, options?.headers))?.[0];
234
278
  const vaultVersion = (0, core_1.getVaultVersionV2)(tokenizedVault);
@@ -240,7 +284,9 @@ async function getVaultSubaccountLoans(vault, options) {
240
284
  const chainId = options?.chainId || tokenizedVault?.chain;
241
285
  const provider = (0, core_1.createProvider)(options.rpcUrl);
242
286
  const tokenizedVaultLoans = await (0, core_1.fetchTokenizedVaultSubaccountLoans)(vaultAddress, chainId);
287
+ // if user passed IAddress or a full IVault
243
288
  if (typeof vault === 'string') {
289
+ // fetch necessary data
244
290
  const poolContract = (0, core_1.createContract)({
245
291
  provider,
246
292
  address: vaultAddress,
@@ -257,14 +303,21 @@ async function getVaultSubaccountLoans(vault, options) {
257
303
  poolTotalSupply = vault.totalSupply.raw;
258
304
  poolDecimals = vault.decimals;
259
305
  }
306
+ // fetch and format all the loans
260
307
  const newLoans = (await Promise.all((Array.isArray(tokenizedVaultLoans) ? tokenizedVaultLoans : [])?.map(async (l) => {
308
+ // variables
261
309
  const borrower = l.borrower;
310
+ // calculate allocation
262
311
  const allocation = l.principal_amount /
263
312
  Number((0, ethers_1.formatUnits)(poolTotalSupply, poolDecimals));
313
+ // get august loan fee from oracle
264
314
  const loanFeeRate = await (0, core_1.getLoanOracleFeeRate)(provider, 'LOAN.REPAY.INTERESTS', l.address, chainId);
315
+ // calculate loan level apr
265
316
  const loanApr = Number(l.apr || 0) / 100;
266
317
  const loanAprAfterFees = loanApr * (1 - loanFeeRate / 100);
318
+ // determine if capital is deployed
267
319
  const isIdleCapital = core_1.IDLE_CAPITAL_BORROWER_ADDRESS.includes(borrower);
320
+ // build new obj
268
321
  const newLoanObj = {
269
322
  vault: vaultAddress,
270
323
  address: l.address,
@@ -295,7 +348,19 @@ async function getVaultSubaccountLoans(vault, options) {
295
348
  throw new Error(`#getVaultSubaccountLoans::${vault}:${e?.message}`);
296
349
  }
297
350
  }
351
+ /**
352
+ * Vault Allocations
353
+ */
354
+ /**
355
+ * Fetch comprehensive vault asset allocation breakdown.
356
+ * Includes DeFi protocols (via DeBank), CeFi balances, OTC positions, and loans.
357
+ * Categorizes exposures by borrowing, supplying, lending, and wallet holdings.
358
+ * @param vault Vault address
359
+ * @param options RPC configuration
360
+ * @returns Detailed allocation data with exposure categorization
361
+ */
298
362
  async function getVaultAllocations(vault, options) {
363
+ // to be used on upshift UI
299
364
  const protocolExposure = [];
300
365
  const tokenExposure = [];
301
366
  let cefiExposure = [];
@@ -322,8 +387,10 @@ async function getVaultAllocations(vault, options) {
322
387
  : null;
323
388
  },
324
389
  };
390
+ // Determine wallets to fetch allocation responses from
325
391
  let uniqueBorrowers = [];
326
392
  const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vault, options?.headers))?.[0];
393
+ // fetch all vault unique subaccounts/loan borrowers
327
394
  try {
328
395
  const vaultVersion = (0, core_1.getVaultVersionV2)(tokenizedVault);
329
396
  if (vaultVersion === 'evm-0') {
@@ -341,25 +408,30 @@ async function getVaultAllocations(vault, options) {
341
408
  catch (e) {
342
409
  core_1.Logger.log.error('getVaultAllocations:borrowers', e);
343
410
  }
411
+ // if hardcoded subaccount exists for vault, use that
344
412
  const hardcodedSubaccount = Object.values(core_1.VAULT_ALLOCATION_SUBACCOUNTS).find((entry) => entry.address.toLowerCase() === vault.toLowerCase() && !entry.useDebank);
345
413
  if (hardcodedSubaccount) {
346
414
  const debankRes = await (0, debank_1.fetchDebankResponse)(hardcodedSubaccount.subaccount);
347
415
  if (debankRes === false) {
348
416
  debankErr = true;
349
417
  }
418
+ // pull data from debank results
350
419
  (0, debank_1.parseVaultLevelDebank)(debankRes, protocolExposure, tokenExposure, hardcodedSubaccount.subaccount, exposurePerCategory, netValue);
351
420
  unfilteredTokens = debankRes?.subaccount?.tokens;
352
421
  const debankPerLoan = (0, debank_1.parseLoanLevelDebank)(debankRes);
353
422
  defiPerBorrower[hardcodedSubaccount.subaccount] = debankPerLoan;
354
423
  }
355
424
  else {
425
+ // Fetch DeBank data for all subaccounts in a single request (EVM only)
356
426
  if ((0, core_1.getAddressChainType)(vault) === 'evm') {
357
427
  const vaultDebankData = await (0, debank_1.fetchVaultDebankResponse)(vault, options?.headers);
358
428
  if (vaultDebankData === false) {
359
429
  debankErr = true;
360
430
  }
361
431
  else {
432
+ // Process DeBank data for each subaccount
362
433
  for (const [subaccountAddress, subaccountData] of Object.entries(vaultDebankData)) {
434
+ // Skip if subaccount has an error
363
435
  if ('error' in subaccountData) {
364
436
  core_1.Logger.log.error('getVaultAllocations:debank:subaccount', {
365
437
  subaccountAddress,
@@ -367,6 +439,7 @@ async function getVaultAllocations(vault, options) {
367
439
  });
368
440
  continue;
369
441
  }
442
+ // Transform to match existing parser format: { subaccount: { positions, app_positions, tokens } }
370
443
  const debankRes = {
371
444
  subaccount: {
372
445
  positions: subaccountData.positions || [],
@@ -381,6 +454,8 @@ async function getVaultAllocations(vault, options) {
381
454
  }
382
455
  }
383
456
  }
457
+ // Fetch portfolio data for non-EVM borrowers (EVM handled by fetchVaultDebankResponse above).
458
+ // CeFi and OTC fetches below run for ALL borrowers regardless of chain type.
384
459
  for (const borrower of uniqueBorrowers) {
385
460
  const chainType = (0, core_1.getAddressChainType)(borrower);
386
461
  if (chainType !== 'evm') {
@@ -414,6 +489,7 @@ async function getVaultAllocations(vault, options) {
414
489
  debankErr = true;
415
490
  }
416
491
  }
492
+ // fetch cefi_positions
417
493
  try {
418
494
  const cefiResponse = await (0, core_1.fetchAugustWithKey)(options.augustKey, core_1.WEBSERVER_ENDPOINTS.subaccount.cefi(borrower), { headers: options?.headers });
419
495
  if (cefiResponse.status !== 200) {
@@ -430,6 +506,7 @@ async function getVaultAllocations(vault, options) {
430
506
  catch (e) {
431
507
  core_1.Logger.log.error('getVaultAllocations:cefi', e, { borrower });
432
508
  }
509
+ // fetch otc_positions
433
510
  try {
434
511
  const otcResponse = await (0, core_1.fetchAugustWithKey)(options.augustKey, core_1.WEBSERVER_ENDPOINTS.subaccount.otc_positions(borrower), { headers: options?.headers });
435
512
  if (otcResponse.status !== 200) {
@@ -466,11 +543,14 @@ async function getVaultAllocations(vault, options) {
466
543
  defiPerBorrower,
467
544
  exposurePerCategory,
468
545
  netValue: netValue.value,
546
+ // this would be the debank token response field without any parsing. Different from
547
+ // tokens. Could use a better name tho
469
548
  unfilteredTokens: unfilteredTokens,
470
549
  };
471
550
  }
472
551
  async function getVaultAvailableRedemptions({ vault, wallet, options, }) {
473
552
  try {
553
+ // Stellar vaults don't support on-chain redemptions yet
474
554
  if ((0, utils_3.isStellarAddress)(vault)) {
475
555
  core_1.Logger.log.warn('getVaultAvailableRedemptions', `Available redemptions is not yet supported for Stellar vaults: ${vault}`);
476
556
  return {
@@ -482,6 +562,7 @@ async function getVaultAvailableRedemptions({ vault, wallet, options, }) {
482
562
  }),
483
563
  };
484
564
  }
565
+ // setup
485
566
  const provider = (0, core_1.createProvider)(options.rpcUrl);
486
567
  const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vault, options?.headers, false, false))?.[0];
487
568
  if (!tokenizedVault) {
@@ -496,8 +577,10 @@ async function getVaultAvailableRedemptions({ vault, wallet, options, }) {
496
577
  }),
497
578
  };
498
579
  }
580
+ // version split logic
499
581
  const version = (0, core_1.getVaultVersionV2)(tokenizedVault);
500
582
  let decimals;
583
+ // Use ABI_LENDING_POOL_V2 for both versions as it has the methods we need
501
584
  const vaultContract = (0, core_1.createContract)({
502
585
  address: vault,
503
586
  abi: abis_1.ABI_LENDING_POOL_V2,
@@ -512,9 +595,11 @@ async function getVaultAvailableRedemptions({ vault, wallet, options, }) {
512
595
  const ld = await vaultContract.lagDuration();
513
596
  const lagDuration = Number(ld);
514
597
  const { withdrawalRequesteds, withdrawalProcesseds } = await (0, subgraph_1.getSubgraphAllWithdrawals)(vault, provider);
598
+ // format
515
599
  const availableRedemptions = [];
516
600
  const pendingRedemptions = [];
517
601
  for (const ev of withdrawalRequesteds) {
602
+ // ensure event is defined
518
603
  if (!ev || typeof ev !== 'object') {
519
604
  core_1.Logger.log.warn('getVaultAvailableRedemptions', `Skipping invalid event: ${ev}`);
520
605
  continue;
@@ -523,11 +608,13 @@ async function getVaultAvailableRedemptions({ vault, wallet, options, }) {
523
608
  let day;
524
609
  let year;
525
610
  if (version === 'evm-2') {
611
+ // Use UTC-based computeClaimableDate to mirror contract logic exactly
526
612
  const claimable = (0, date_utils_1.computeClaimableDate)(Number(ev.timestamp_), lagDuration);
527
613
  year = claimable.year;
528
614
  month = claimable.month;
529
615
  day = claimable.day;
530
616
  }
617
+ // Validate required event properties
531
618
  if (!ev.year || !ev.month || !ev.day || !ev.receiverAddr) {
532
619
  if (version !== 'evm-2') {
533
620
  core_1.Logger.log.warn('getVaultAvailableRedemptions', `Skipping event with missing required properties: ${ev}`);
@@ -545,9 +632,12 @@ async function getVaultAvailableRedemptions({ vault, wallet, options, }) {
545
632
  const fullDate = version === 'evm-2'
546
633
  ? new Date(Date.UTC(year, month - 1, day))
547
634
  : new Date(Number(ev.year), Number(ev.month) - 1, Number(ev.day));
635
+ // Match request to processed event using deterministic UTC date key
548
636
  const requestDateKey = version === 'evm-2'
549
637
  ? (0, date_utils_1.formatDateKey)(year, month, day)
550
638
  : (0, date_utils_1.formatDateKey)(Number(ev.year), Number(ev.month), Number(ev.day));
639
+ // NOTE: evm-2 subgraph does not emit WithdrawalProcessed events, so matching
640
+ // relies on timestamp-based date comparison for evm-2 (deterministic for evm-1).
551
641
  const foundRedemptionAgainstClaim = withdrawalProcesseds.find((h) => {
552
642
  const proc = h;
553
643
  if (proc.receiverAddr !== ev.receiverAddr)
@@ -562,9 +652,11 @@ async function getVaultAvailableRedemptions({ vault, wallet, options, }) {
562
652
  return procKey === requestDateKey;
563
653
  });
564
654
  if (wallet && (0, ethers_1.isAddress)(wallet)) {
655
+ // check if connected users address
565
656
  if (ev?.receiverAddr?.toLowerCase() === wallet.toLowerCase()) {
566
657
  const alreadyRedeemed = version === 'evm-2'
567
658
  ? withdrawalProcesseds.find((red) => {
659
+ // Match by receiver + UTC date key (deterministic)
568
660
  if (red.receiverAddr.toLowerCase() !== wallet.toLowerCase()) {
569
661
  return false;
570
662
  }
@@ -576,9 +668,11 @@ async function getVaultAvailableRedemptions({ vault, wallet, options, }) {
576
668
  BigInt(red.month.raw) === BigInt(ev.month) &&
577
669
  BigInt(red.year.raw) === BigInt(ev.year));
578
670
  if (!(foundRedemptionAgainstClaim && alreadyRedeemed)) {
671
+ // double check if user has already claimed
579
672
  try {
580
673
  switch (version) {
581
674
  case 'evm-2': {
675
+ // this is to avoid edge case where old withdrawal A is already redeemed but we are now looking at withdrawal B that is not yet pending
582
676
  if (alreadyRedeemed) {
583
677
  const index = withdrawalProcesseds.findIndex((item) => item.id === alreadyRedeemed.id);
584
678
  if (index !== -1) {
@@ -586,12 +680,16 @@ async function getVaultAvailableRedemptions({ vault, wallet, options, }) {
586
680
  }
587
681
  }
588
682
  if (!alreadyRedeemed) {
683
+ // Recompute claimable date (includes lag + 300s manipulation window) to determine if claimable now
589
684
  const claimable = (0, date_utils_1.computeClaimableDate)(Number(ev.timestamp_), lagDuration);
590
685
  const pendingCondition = (0, date_utils_1.isClaimableNow)(claimable.epoch, Math.floor(Date.now() / 1000));
591
686
  if (pendingCondition) {
687
+ // Has passed lag duration - check if claim is not settled before adding to pending
592
688
  try {
593
689
  const burnableAmount = (await vaultContract?.getBurnableAmountByReceiver?.(BigInt(year), BigInt(month), BigInt(day), (0, ethers_1.getAddress)(ev.receiverAddr))) || BigInt(0);
594
690
  const claimAmount = BigInt(ev.shares) || BigInt(0);
691
+ // If burnableAmount >= claimAmount, claim hasn't been settled by processAllClaims
692
+ // Only add to pending if it's still unsettled
595
693
  if (burnableAmount >= claimAmount) {
596
694
  pendingRedemptions.push({
597
695
  id: ev.transactionHash_ || ev.id,
@@ -610,9 +708,11 @@ async function getVaultAvailableRedemptions({ vault, wallet, options, }) {
610
708
  }
611
709
  catch (burnableError) {
612
710
  core_1.Logger.log.warn('getVaultAvailableRedemptions', `Failed to check burnable amount for pending redemption: ${burnableError}`);
711
+ // If check fails, don't add to pending to be safe
613
712
  }
614
713
  }
615
714
  else {
715
+ // Hasn't passed lag duration - add to available redemptions only
616
716
  availableRedemptions.push({
617
717
  id: ev.transactionHash_ || ev.id,
618
718
  hash: ev.transactionHash_ || ev.id,
@@ -633,15 +733,19 @@ async function getVaultAvailableRedemptions({ vault, wallet, options, }) {
633
733
  default: {
634
734
  const trueClaimableAmount = await vaultContract?.getClaimableAmountByReceiver?.(BigInt(ev.year), BigInt(ev.month), BigInt(ev.day), (0, ethers_1.getAddress)(wallet));
635
735
  if (trueClaimableAmount > BigInt(0)) {
736
+ // Use computeClaimableDate for UTC-correct date with lag
636
737
  const v1Claimable = (0, date_utils_1.computeClaimableDate)(Number(ev.timestamp_), lagDuration);
637
738
  const pendingCondition = (0, date_utils_1.isClaimableNow)(v1Claimable.epoch, Math.floor(Date.now() / 1000));
638
739
  if (pendingCondition) {
740
+ // Has passed lag duration - check if claim is not settled before adding to pending
639
741
  try {
640
742
  year = v1Claimable.year;
641
743
  month = v1Claimable.month;
642
744
  day = v1Claimable.day;
643
745
  const burnableAmount = (await vaultContract?.getBurnableAmountByReceiver?.(BigInt(year), BigInt(month), BigInt(day), (0, ethers_1.getAddress)(ev.receiverAddr))) || BigInt(0);
644
746
  const claimAmount = trueClaimableAmount || BigInt(0);
747
+ // If burnableAmount >= claimAmount, claim hasn't been settled by processAllClaims
748
+ // Only add to pending if it's still unsettled
645
749
  if (burnableAmount >= claimAmount) {
646
750
  pendingRedemptions.push({
647
751
  id: ev.transactionHash_ || ev.id,
@@ -660,9 +764,11 @@ async function getVaultAvailableRedemptions({ vault, wallet, options, }) {
660
764
  }
661
765
  catch (burnableError) {
662
766
  core_1.Logger.log.warn('getVaultAvailableRedemptions', `Failed to check burnable amount for pending redemption: ${burnableError}`);
767
+ // If check fails, don't add to pending to be safe
663
768
  }
664
769
  }
665
770
  else {
771
+ // Hasn't passed lag duration - add to available redemptions only
666
772
  availableRedemptions.push({
667
773
  id: ev.transactionHash_ || ev.id,
668
774
  hash: ev.transactionHash_ || ev.id,
@@ -733,10 +839,12 @@ async function getVaultAvailableRedemptions({ vault, wallet, options, }) {
733
839
  }
734
840
  async function getVaultRedemptionHistory({ vault, wallet, lookbackBlocks, options, }) {
735
841
  try {
842
+ // Stellar vaults: no on-chain redemption history yet
736
843
  if ((0, utils_3.isStellarAddress)(vault)) {
737
844
  core_1.Logger.log.warn('getVaultRedemptionHistory', `Redemption history is not yet supported for Stellar vaults: ${vault}`);
738
845
  return [];
739
846
  }
847
+ // setup
740
848
  const provider = (0, core_1.createProvider)(options.rpcUrl);
741
849
  const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vault, options?.headers, false, false))?.[0];
742
850
  if (!tokenizedVault) {
@@ -746,16 +854,18 @@ async function getVaultRedemptionHistory({ vault, wallet, lookbackBlocks, option
746
854
  const chainId = tokenizedVault.chain;
747
855
  const version = (0, core_1.getVaultVersionV2)(tokenizedVault);
748
856
  const isV2 = version === 'evm-2';
857
+ // Use correct ABI based on vault version — different WithdrawalProcessed event signatures
749
858
  const vaultAbi = isV2 ? TokenizedVaultV2_1.ABI_TOKENIZED_VAULT_V2 : abis_1.ABI_LENDING_POOL_V2;
750
859
  const poolContract = (0, core_1.createContract)({
751
860
  address: vault,
752
861
  abi: vaultAbi,
753
862
  provider,
754
863
  });
864
+ // query getLogs
755
865
  const currentBlock = await provider.getBlockNumber();
756
866
  const blockSkip = (0, core_1.determineBlockSkipInternal)(chainId);
757
867
  const cutoffBlock = currentBlock - (lookbackBlocks ?? (0, core_1.determineBlockCutoff)(chainId));
758
- const BATCH_SIZE = 20;
868
+ const BATCH_SIZE = 20; // Max concurrent RPC requests per batch
759
869
  core_1.Logger.log.info('getVaultRedemptionHistory', {
760
870
  vault,
761
871
  version,
@@ -766,6 +876,7 @@ async function getVaultRedemptionHistory({ vault, wallet, lookbackBlocks, option
766
876
  blockSkip,
767
877
  totalRangeBlocks: currentBlock - cutoffBlock,
768
878
  });
879
+ // Build all query ranges
769
880
  const ranges = [];
770
881
  let endBlock = currentBlock;
771
882
  while (endBlock >= cutoffBlock) {
@@ -779,6 +890,11 @@ async function getVaultRedemptionHistory({ vault, wallet, lookbackBlocks, option
779
890
  firstRange: ranges[0],
780
891
  lastRange: ranges[ranges.length - 1],
781
892
  });
893
+ // Execute in batches to avoid RPC rate limits on high-block-count chains.
894
+ // Use allSettled + explicit throw so a chunk failure surfaces to the caller
895
+ // instead of silently returning an empty result (which used to corrupt
896
+ // `getWithdrawalRequestsWithStatus` into reporting `ready_to_claim` for
897
+ // requests that were already processed).
782
898
  const logs = [];
783
899
  for (let i = 0; i < ranges.length; i += BATCH_SIZE) {
784
900
  const batch = ranges.slice(i, i + BATCH_SIZE);
@@ -799,6 +915,8 @@ async function getVaultRedemptionHistory({ vault, wallet, lookbackBlocks, option
799
915
  const batchLogs = batchResults.flatMap((r) => r.status === 'fulfilled' ? r.value : []);
800
916
  logs.push(...batchLogs);
801
917
  }
918
+ // evm-1: WithdrawalProcessed(uint256 assetsAmount, uint256 processedOn, address receiverAddr, uint256 requestedOn)
919
+ // evm-2: WithdrawalProcessed(uint256 assetsAmount, address indexed receiverAddr)
802
920
  const iface = new ethers_1.ethers.Interface(isV2
803
921
  ? [
804
922
  'event WithdrawalProcessed(uint256 assetsAmount, address indexed receiverAddr)',
@@ -806,7 +924,9 @@ async function getVaultRedemptionHistory({ vault, wallet, lookbackBlocks, option
806
924
  : [
807
925
  'event WithdrawalProcessed(uint256 assetsAmount, uint256 processedOn, address receiverAddr, uint256 requestedOn)',
808
926
  ]);
927
+ // get pool decimals
809
928
  const decimals = await (0, core_1.getDecimals)(provider, vault);
929
+ // For evm-2 events (no timestamps in event), fetch block timestamps
810
930
  let blockTimestamps;
811
931
  if (isV2 && logs.length > 0) {
812
932
  const uniqueBlocks = [...new Set(logs.map((l) => l.blockNumber))];
@@ -817,6 +937,7 @@ async function getVaultRedemptionHistory({ vault, wallet, lookbackBlocks, option
817
937
  blockTimestamps.set(block.number, block.timestamp);
818
938
  });
819
939
  }
940
+ // format — iterate logs directly to preserve transactionHash
820
941
  const redemptions = [];
821
942
  logs.forEach((log) => {
822
943
  const ev = iface.parseLog(log);
@@ -831,7 +952,7 @@ async function getVaultRedemptionHistory({ vault, wallet, lookbackBlocks, option
831
952
  receiver: receiverAddr,
832
953
  amount: (0, core_1.toNormalizedBn)(assetsAmount, decimals),
833
954
  processed: blockDate,
834
- requested: new Date(0),
955
+ requested: new Date(0), // evm-2 event has no requestedOn field; epoch 0 is a sentinel for "unknown"
835
956
  vault,
836
957
  transactionHash: log.transactionHash,
837
958
  };
@@ -860,16 +981,25 @@ async function getVaultRedemptionHistory({ vault, wallet, lookbackBlocks, option
860
981
  return redemptions;
861
982
  }
862
983
  catch (e) {
984
+ // Log loudly and re-throw. Previously this caught all errors and returned
985
+ // [], which silently corrupted callers (notably getWithdrawalRequestsWithStatus,
986
+ // which would then report `ready_to_claim` for already-processed requests).
987
+ // Returning [] is reserved for the explicit early returns inside the try
988
+ // (unsupported chains, vault not in backend) — not for unexpected failures.
863
989
  core_1.Logger.log.error('getVaultRedemptionHistory', e, { vault, wallet });
864
990
  throw e;
865
991
  }
866
992
  }
993
+ /**
994
+ * Vault Positions
995
+ */
867
996
  async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, options, }) {
868
997
  try {
869
998
  const tokenizedVaults = await (0, core_1.fetchTokenizedVault)(vault ? vault : undefined, options?.headers, false, false);
870
999
  if (!tokenizedVaults || tokenizedVaults.length === 0) {
871
1000
  return [];
872
1001
  }
1002
+ // check if there are redemptions available or if you stake in this pool
873
1003
  function renderStatus(_redemptions, _balance) {
874
1004
  if (_redemptions?.length)
875
1005
  return 'REDEEM';
@@ -879,10 +1009,18 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
879
1009
  }
880
1010
  const promises = await Promise.all(tokenizedVaults.map(async (v) => {
881
1011
  try {
1012
+ // @solana: skip if not solana address
882
1013
  if (utils_2.SolanaUtils.isSolanaAddress(v.address)) {
1014
+ // Default decimals: backend metadata, then SDK fallback. The mint's
1015
+ // own decimals (read below) override this once we have a real
1016
+ // tokenAmount response.
883
1017
  const fallbackShareDecimals = v.solana_vault_metadata?.deposit_token_decimals ??
884
1018
  SolanaConstants.fallbackDecimals;
885
1019
  let balance = (0, core_1.toNormalizedBn)(0, fallbackShareDecimals);
1020
+ // Defensive: without a Solana adapter we can't fetch on-chain
1021
+ // state. The outer filter in modules/vaults/main.ts already gates
1022
+ // on `this.solanaService`, so this is a belt-and-suspenders guard
1023
+ // for any external caller that constructs `options` by hand.
886
1024
  if (utils_2.SolanaUtils.isSolanaAddress(solanaWallet) &&
887
1025
  options.solanaService) {
888
1026
  const vaultProgramId = utils_2.SolanaUtils.resolveProgramId(String(v.address), v.solana_vault_metadata);
@@ -890,12 +1028,23 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
890
1028
  const vaultState = vaultStateRes?.vaultState;
891
1029
  const { shareMint } = vaultState ?? { shareMint: null };
892
1030
  if (shareMint) {
1031
+ // Always use the raw helper — it returns the on-chain u64
1032
+ // string and the mint's true decimals. The legacy `uiAmount`
1033
+ // (JS number) path lost precision and silently encoded `.raw`
1034
+ // against 18 decimals when fed back through `toNormalizedBn`
1035
+ // without a decimals argument, which broke every downstream
1036
+ // BigInt consumer (redemptions, max actions, allowances).
893
1037
  const raw = await options.solanaService.fetchUserShareBalanceRaw(solanaWallet, shareMint);
894
1038
  const decimals = raw.decimals ?? fallbackShareDecimals;
895
1039
  balance = (0, core_1.toNormalizedBn)(BigInt(raw.amount), decimals);
896
1040
  }
897
1041
  }
898
1042
  return {
1043
+ // Mirror the Stellar branch — return the backend's `v.address`,
1044
+ // not the outer `vault` parameter (which can be undefined for
1045
+ // multi-vault calls). No `as IAddress` cast: Solana base58 is
1046
+ // not 0x-hex; the IAddress typing across non-EVM positions is a
1047
+ // known wart documented in IVaultPosition.
899
1048
  vault: v.address,
900
1049
  status: renderStatus([], balance),
901
1050
  availableRedemptions: [],
@@ -913,6 +1062,9 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
913
1062
  balance = (0, core_1.toNormalizedBn)(BigInt(position.shares), position.decimals);
914
1063
  }
915
1064
  else if (position) {
1065
+ // decimals() read failed: trusting the fallback (7) would
1066
+ // mis-scale the balance for ERC4626 offset vaults (AUGUST-6381).
1067
+ // Show zero rather than a confidently-wrong value.
916
1068
  core_1.Logger.log.warn('getVaultPositions', 'Stellar decimals() unresolved — refusing to scale balance with fallback decimals (showing zero)', { vault: v.address, wallet: stellarWallet });
917
1069
  }
918
1070
  else {
@@ -922,6 +1074,7 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
922
1074
  else if (stellarWallet) {
923
1075
  core_1.Logger.log.warn('getVaultPositions', 'Invalid stellarWallet address format', { vault: v.address, stellarWallet });
924
1076
  }
1077
+ // Stellar vaults have no on-chain redemption queue yet — redeemable is always 0
925
1078
  return {
926
1079
  vault: v.address,
927
1080
  status: renderStatus([], balance),
@@ -931,14 +1084,18 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
931
1084
  walletBalance: balance,
932
1085
  };
933
1086
  }
1087
+ // create provider
934
1088
  const provider = (0, core_1.createProvider)(options.rpcUrl);
1089
+ // version split logic
935
1090
  let decimals;
936
1091
  const version = (0, core_1.getVaultVersionV2)(v);
1092
+ // Use ABI_LENDING_POOL_V2 as the common ABI for vault operations
937
1093
  const vaultContract = (0, core_1.createContract)({
938
1094
  provider,
939
1095
  abi: abis_1.ABI_LENDING_POOL_V2,
940
1096
  address: v.address,
941
1097
  });
1098
+ // get vault metadata and balance based on version
942
1099
  let bal = BigInt(0);
943
1100
  if (version === 'evm-2') {
944
1101
  const receiptAddress = await (0, core_1.getReceiptTokenAddress)(provider, vault);
@@ -959,12 +1116,15 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
959
1116
  }
960
1117
  }
961
1118
  const balance = (0, core_1.toNormalizedBn)(bal, decimals);
1119
+ // check available redemptions
962
1120
  const { availableRedemptions, pendingRedemptions } = await getVaultAvailableRedemptions({
963
1121
  vault: v.address,
964
1122
  wallet,
965
1123
  options,
966
1124
  });
1125
+ // get aggregate redemption amount
967
1126
  const aggregateAvailableRedemptions = availableRedemptions.reduce((acc, curr) => acc + BigInt(curr.amount.raw), BigInt(0));
1127
+ // return obj
968
1128
  return {
969
1129
  vault,
970
1130
  status: renderStatus(availableRedemptions, balance),
@@ -998,10 +1158,14 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
998
1158
  throw new Error(`#getVaultPositions::${vault}:${e?.message}`);
999
1159
  }
1000
1160
  }
1161
+ /**
1162
+ * @deprecated use getVaultHistoricalTimeseries instead
1163
+ */
1001
1164
  async function getVaultApy({ vault, options, historical, }) {
1002
1165
  try {
1003
1166
  if (!vault)
1004
1167
  throw new Error('Vault input parameter is undefined.');
1168
+ // only looking for current apy
1005
1169
  if (!historical) {
1006
1170
  const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vault, options?.headers))?.[0];
1007
1171
  return [
@@ -1012,6 +1176,7 @@ async function getVaultApy({ vault, options, historical, }) {
1012
1176
  ];
1013
1177
  }
1014
1178
  else {
1179
+ // historical apy
1015
1180
  function paramBuilder(params, baseUrl) {
1016
1181
  if (!params)
1017
1182
  return '';
@@ -1051,15 +1216,16 @@ async function getVaultApy({ vault, options, historical, }) {
1051
1216
  }
1052
1217
  }
1053
1218
  async function getRewardsStakingPositions({ rpcUrl, wallet, coinGeckoKey, }) {
1054
- const REWARDS_CHAIN = 43114;
1055
- const REWARDS_SYMBOL = 'AVAX';
1056
- const REWARDS_DECIMALS = 18;
1057
- const REWARDS_NAME = 'Avalanche';
1219
+ const REWARDS_CHAIN = 43114; // avalanche
1220
+ const REWARDS_SYMBOL = 'AVAX'; // avalanche
1221
+ const REWARDS_DECIMALS = 18; // avalanche
1222
+ const REWARDS_NAME = 'Avalanche'; // avalanche
1058
1223
  const APR_MULTIPLIER = 31536000;
1059
1224
  const UP_AUSD_SYMBOL = 'upAUSD';
1060
1225
  try {
1061
1226
  const provider = (0, core_1.createProvider)(rpcUrl);
1062
1227
  const chainId = await (0, core_1.getChainId)(provider);
1228
+ // Validate chain has rewards distributor
1063
1229
  if (chainId !== REWARDS_CHAIN) {
1064
1230
  core_1.Logger.log.warn('getStakingPositions:unsupported_chain', chainId);
1065
1231
  return [];
@@ -1068,15 +1234,19 @@ async function getRewardsStakingPositions({ rpcUrl, wallet, coinGeckoKey, }) {
1068
1234
  core_1.Logger.log.warn('getStakingPositions:invalid_wallet', wallet);
1069
1235
  return [];
1070
1236
  }
1237
+ // get reward distributor contract addresses
1071
1238
  const rewardDistributorAddresses = (0, core_1.REWARD_DISTRIBUTOR_ADDRESS)(chainId);
1239
+ // loop through each reward distributor contract address
1072
1240
  const positions = await Promise.all(rewardDistributorAddresses.map(async (contract, i) => {
1073
1241
  const rewardContract = (0, core_1.createContract)({
1074
1242
  address: contract,
1075
1243
  provider: provider,
1076
1244
  abi: abis_1.ABI_REWARD_DISTRIBUTOR,
1077
1245
  });
1246
+ // get reward distributor metadata
1078
1247
  const totalStaked = await rewardContract.totalStaked();
1079
1248
  const rewardsPerSecond = await rewardContract.rewardsPerSecond();
1249
+ // get staking token metadata
1080
1250
  const stakingTokenCalls = ['decimals', 'symbol', 'name', 'totalSupply'];
1081
1251
  const stakingTokenAddress = (await rewardContract.stakingToken());
1082
1252
  const stakingTokenContract = (0, core_1.createContract)({
@@ -1085,6 +1255,7 @@ async function getRewardsStakingPositions({ rpcUrl, wallet, coinGeckoKey, }) {
1085
1255
  abi: abis_1.ABI_ERC20,
1086
1256
  });
1087
1257
  const [decimals, symbol, name] = await Promise.all(stakingTokenCalls.map((staking) => stakingTokenContract[staking]()));
1258
+ // if wallet address is passed, pull user balance of staked tokens
1088
1259
  let balance;
1089
1260
  let earned;
1090
1261
  if (wallet) {
@@ -1092,12 +1263,15 @@ async function getRewardsStakingPositions({ rpcUrl, wallet, coinGeckoKey, }) {
1092
1263
  balance = await rewardContract.balanceOf(formattedWallet);
1093
1264
  earned = await rewardContract.earned(formattedWallet);
1094
1265
  }
1266
+ // get appropriate token price
1095
1267
  const rewardTokenPriceInUsd = await (0, core_1.fetchTokenPrice)(REWARDS_SYMBOL, null, coinGeckoKey);
1096
1268
  const normalizedRewardTokenPrice = (0, core_1.toNormalizedBn)(rewardTokenPriceInUsd);
1269
+ // normalize numbers
1097
1270
  const normalizedTotalStakedInPool = (0, core_1.toNormalizedBn)(totalStaked, decimals);
1098
1271
  const normalizedRedeemable = (0, core_1.toNormalizedBn)(earned, REWARDS_DECIMALS);
1099
1272
  const normalizedRewardsPerSecond = (0, core_1.toNormalizedBn)(rewardsPerSecond, REWARDS_DECIMALS);
1100
1273
  const normalizedTotalStakedBalance = (0, core_1.toNormalizedBn)(balance, decimals);
1274
+ // calculate APR's
1101
1275
  const rewardTokenPriceToMultiply = symbol === UP_AUSD_SYMBOL
1102
1276
  ? Number(normalizedRewardTokenPrice?.normalized)
1103
1277
  : 1;
@@ -1151,12 +1325,15 @@ async function getVaultTvl({ vault, options, historical, }) {
1151
1325
  try {
1152
1326
  if (!vault)
1153
1327
  throw new Error('Vault input parameter is undefined.');
1328
+ // Verify vault exists
1154
1329
  const _vaultExists = (await (0, core_1.fetchTokenizedVault)(vault))?.[0];
1155
1330
  if (!_vaultExists) {
1156
1331
  throw new Error(`Vault ${vault} not found in backend`);
1157
1332
  }
1333
+ // only wanting current APR
1158
1334
  if (!historical) {
1159
1335
  const version = (0, core_1.getVaultVersionV2)(_vaultExists);
1336
+ // Non-EVM early returns (no provider needed)
1160
1337
  if (version === 'sol-0') {
1161
1338
  return [
1162
1339
  {
@@ -1209,17 +1386,33 @@ async function getVaultTvl({ vault, options, historical, }) {
1209
1386
  ];
1210
1387
  }
1211
1388
  }
1389
+ // const vaultRes = await getVault({
1390
+ // vault,
1391
+ // options,
1392
+ // loans: false,
1393
+ // allocations: false,
1394
+ // });
1395
+ // const returnObj = [
1396
+ // { value: vaultRes.totalSupply, timestamp: new Date().toISOString() },
1397
+ // ];
1398
+ // Logger.log.info('getVaultTvl:current', returnObj);
1399
+ // return returnObj;
1212
1400
  }
1213
1401
  else {
1402
+ // historical tvl
1403
+ // check vault version early to guard non-EVM chains
1214
1404
  const version = (0, core_1.getVaultVersionV2)(_vaultExists);
1215
1405
  if (version === 'stellar-0' || version === 'sol-0') {
1216
1406
  core_1.Logger.log.warn('getVaultTvl:historical', 'Historical TVL not supported for non-EVM vaults', { vault, version });
1217
1407
  return [];
1218
1408
  }
1409
+ // if no order is passed but inputParams are passed
1219
1410
  if (typeof historical !== 'undefined' && !historical.order)
1220
1411
  historical.order = 'desc';
1412
+ // days is default interval
1221
1413
  if (typeof historical !== 'undefined' && !historical.interval)
1222
1414
  historical.interval = 'days';
1415
+ // setup
1223
1416
  const provider = (0, core_1.createProvider)(options.rpcUrl);
1224
1417
  const vaultContract = (0, core_1.createContract)({
1225
1418
  address: vault,
@@ -1244,6 +1437,8 @@ async function getVaultTvl({ vault, options, historical, }) {
1244
1437
  break;
1245
1438
  }
1246
1439
  }
1440
+ // block trackback setup
1441
+ // Get the latest finalized block to ensure all queried blocks are confirmed
1247
1442
  const finalizedBlock = await provider.getBlock('finalized');
1248
1443
  const finalizedTimestamp = finalizedBlock
1249
1444
  ? finalizedBlock.timestamp * 1000
@@ -1253,19 +1448,26 @@ async function getVaultTvl({ vault, options, historical, }) {
1253
1448
  const startDate = historical.daysAgo
1254
1449
  ? new Date(finalizedTimestamp - daysAgo)
1255
1450
  : new Date(finalizedTimestamp);
1256
- const blocks = await dater.getEvery(historical.interval, startDate.toUTCString(), new Date(finalizedTimestamp).toUTCString());
1451
+ const blocks = await dater.getEvery(historical.interval, // Period
1452
+ startDate.toUTCString(), // Start date
1453
+ new Date(finalizedTimestamp).toUTCString());
1454
+ // order blocks array
1257
1455
  const orderedBlocks = historical.order === 'desc' ? (0, core_1.orderObjArrByDate)(blocks) : blocks;
1456
+ // minimal ABI
1258
1457
  const totalAssetsHistorical = await (0, core_1.promiseSettle)(orderedBlocks?.map(async ({ block, date }) => {
1458
+ // get totalAssets at said block
1259
1459
  const totalAssetsAtBlock = await provider.call({
1260
1460
  to: vaultContract,
1261
1461
  data: new ethers_1.Interface(minAbi).encodeFunctionData(functionName, []),
1262
1462
  blockTag: block,
1263
1463
  });
1464
+ // cover case when data returned is 0
1264
1465
  if (totalAssetsAtBlock === '0x')
1265
1466
  return {
1266
1467
  timestamp: (0, core_1.dateToUnix)(new Date(date)),
1267
1468
  value: (0, core_1.toNormalizedBn)(0, 0),
1268
1469
  };
1470
+ // decode back to readable
1269
1471
  const readableTotalAssetsAtBlock = new ethers_1.Interface(minAbi).decodeFunctionResult(functionName, totalAssetsAtBlock);
1270
1472
  return {
1271
1473
  timestamp: (0, core_1.dateToUnix)(new Date(date)),
@@ -1281,9 +1483,27 @@ async function getVaultTvl({ vault, options, historical, }) {
1281
1483
  throw new Error(`Failed to fetch TVL for ${vault}: ${e instanceof Error ? e.message : 'Unknown error'}`);
1282
1484
  }
1283
1485
  }
1486
+ /**
1487
+ * Build the per-loan borrower / health-factor list for a single vault.
1488
+ *
1489
+ * Resilience: individual loan reads (`loanState()`, `borrower()`) and the
1490
+ * per-borrower August backend call are isolated with `Promise.allSettled` and
1491
+ * try/catch so one bad loan — a test loan whose address isn't a real contract,
1492
+ * a borrower the backend doesn't recognize, an intermittent RPC error — drops
1493
+ * that single row from the result instead of rejecting the whole batch. Failed
1494
+ * rows are logged via `Logger.log.warn`; the loan is still returned with
1495
+ * `health_factor: undefined` so callers can render an explicit empty state
1496
+ * rather than a perpetual loading skeleton.
1497
+ *
1498
+ * @param vault Vault address to scope the fetch to.
1499
+ * @param options Standard vault options — `rpcUrl` must point at `vault`'s chain.
1500
+ * @returns Array of `IVaultBorrowerHealthFactor`, one entry per active loan.
1501
+ */
1284
1502
  async function getVaultBorrowerHealthFactor({ vault, options, }) {
1285
1503
  const provider = (0, core_1.createProvider)(options.rpcUrl);
1286
1504
  const loans = await getVaultLoans(vault, options);
1505
+ // Read each loan's on-chain state; drop loans whose RPC call fails so a
1506
+ // single bad contract (e.g. a test loan address) can't sink the batch.
1287
1507
  const activeLoanResults = await Promise.allSettled(loans.map(async (l) => {
1288
1508
  const loanContract = (0, core_1.createContract)({
1289
1509
  provider,
@@ -1306,6 +1526,7 @@ async function getVaultBorrowerHealthFactor({ vault, options, }) {
1306
1526
  return undefined;
1307
1527
  })
1308
1528
  .filter((l) => l !== undefined);
1529
+ // Same fault-tolerance for the per-loan borrower() read.
1309
1530
  const formattedSettled = await Promise.allSettled(activeLoans.map(async (l) => {
1310
1531
  const loanContract = (0, core_1.createContract)({
1311
1532
  provider,
@@ -1351,6 +1572,26 @@ async function getVaultBorrowerHealthFactor({ vault, options, }) {
1351
1572
  });
1352
1573
  return formattedLoansArray;
1353
1574
  }
1575
+ /**
1576
+ * Aggregate borrower-health-factor data, optionally scoped to a single vault.
1577
+ *
1578
+ * - When `vault` is provided, only that vault's tokenized record is fetched
1579
+ * and only its loans are read. This is the path callers should use when
1580
+ * they already know which vault they care about — it skips the cross-chain
1581
+ * fanout entirely.
1582
+ * - When `vault` is omitted, the function fetches every tokenized vault and
1583
+ * builds health factors for each, preserving the legacy behavior. Per-vault
1584
+ * failures are isolated with `Promise.allSettled` so one bad vault returns
1585
+ * an empty array instead of rejecting the whole map.
1586
+ *
1587
+ * The returned map is keyed by **lowercased** vault address so callers can
1588
+ * look up entries without worrying about EIP-55 checksum casing in either the
1589
+ * backend response or the caller's input.
1590
+ *
1591
+ * @param options Standard vault options — `rpcUrl` should match `vault`'s chain.
1592
+ * @param vault Optional vault address to scope the fetch to.
1593
+ * @returns Map of lowercased vault address → array of borrower-health-factor rows.
1594
+ */
1354
1595
  async function getHealthFactorOfBorrowersByVault({ options, vault, }) {
1355
1596
  const vaults = vault
1356
1597
  ? ((await (0, core_1.fetchTokenizedVault)(vault, options?.headers)) ?? [])
@@ -1376,6 +1617,13 @@ async function getHealthFactorOfBorrowersByVault({ options, vault, }) {
1376
1617
  });
1377
1618
  return healthFactorsByPool;
1378
1619
  }
1620
+ /**
1621
+ * Fetch user points from the backend API endpoint.
1622
+ * This replaces client-side points calculation with server-side processing.
1623
+ * @param userAddress User wallet address (EVM or Solana)
1624
+ * @param options Request options including headers
1625
+ * @returns Points data from the backend API
1626
+ */
1379
1627
  async function getUserPoints({ userAddress, options, }) {
1380
1628
  try {
1381
1629
  if (!userAddress) {
@@ -1397,6 +1645,62 @@ async function getUserPoints({ userAddress, options, }) {
1397
1645
  throw new Error(`#getUserPoints::${userAddress}:${errorMessage}`);
1398
1646
  }
1399
1647
  }
1648
+ /**
1649
+ * Register a user for the points program, authenticated by a wallet signature.
1650
+ *
1651
+ * The caller must obtain a personal_sign (EIP-191) signature over the
1652
+ * canonical message template below — the backend reconstructs the same
1653
+ * string from these fields and verifies the signature recovers to
1654
+ * `userAddress`. Building the message client-side and server-side from the
1655
+ * same primitives means a hostile client cannot show one message in the
1656
+ * wallet prompt while submitting a different one for validation.
1657
+ *
1658
+ * Canonical message (`\n`-separated, no trailing newline):
1659
+ *
1660
+ * ```
1661
+ * Upshift: register {userAddress.toLowerCase()}
1662
+ * referrer: {referrerAddress.toLowerCase() || "none"}
1663
+ * chain: {chainId}
1664
+ * nonce: {nonce}
1665
+ * expires: {expiry}
1666
+ * ```
1667
+ *
1668
+ * @param userAddress EVM wallet address being registered. Must match the
1669
+ * signer that produced `signature`. Lowercased before being embedded in
1670
+ * the canonical message.
1671
+ * @param referrerAddress Optional EVM address that referred this user.
1672
+ * Lowercased when embedded; the literal string `"none"` is used when
1673
+ * absent. Tampering with this field after signing produces 401.
1674
+ * @param chainId Chain on which the wallet signed. For smart-contract wallets
1675
+ * (Safe etc.) this is the chain whose RPC the backend will use to run the
1676
+ * EIP-1271 `isValidSignature` check; for EOAs it still pins the signature
1677
+ * to a chain so it can't be replayed cross-chain. Must be one of the chains
1678
+ * Upshift supports (see backend `SUPPORTED_REGISTRATION_CHAINS`).
1679
+ * @param signature `0x`-prefixed hex of the personal_sign signature over
1680
+ * the canonical message. Single-use: replays within ~10 min are rejected.
1681
+ * @param nonce Caller-generated random string (8–128 chars). Pair with
1682
+ * `userAddress` to form the single-use key in the backend's nonce store.
1683
+ * @param expiry Unix timestamp (seconds). Must be in the future and within
1684
+ * the backend's TTL window (currently 10 min).
1685
+ * @param options Request options (headers).
1686
+ * @returns Raw `Response` from the backend. 200 on success, 401 for
1687
+ * expired / replayed / mismatched-signer / tampered-referrer, 422 for an
1688
+ * unsupported `chainId`.
1689
+ *
1690
+ * @example
1691
+ * ```ts
1692
+ * const nonce = crypto.randomUUID().replace(/-/g, '');
1693
+ * const expiry = Math.floor(Date.now() / 1000) + 300;
1694
+ * const message =
1695
+ * `Upshift: register ${address.toLowerCase()}\n` +
1696
+ * `referrer: ${referrer?.toLowerCase() ?? 'none'}\n` +
1697
+ * `chain: ${chainId}\n` +
1698
+ * `nonce: ${nonce}\n` +
1699
+ * `expires: ${expiry}`;
1700
+ * const signature = await walletClient.signMessage({ account: address, message });
1701
+ * await registerUserForPoints({ userAddress: address, referrerAddress: referrer, chainId, signature, nonce, expiry });
1702
+ * ```
1703
+ */
1400
1704
  async function registerUserForPoints({ userAddress, referrerAddress, chainId, signature, nonce, expiry, options, }) {
1401
1705
  try {
1402
1706
  if (!userAddress) {
@@ -1429,6 +1733,12 @@ async function registerUserForPoints({ userAddress, referrerAddress, chainId, si
1429
1733
  throw new Error(`#registerUserForPoints::${userAddress}:${errorMessage}`);
1430
1734
  }
1431
1735
  }
1736
+ /**
1737
+ * Fetch the points leaderboard data.
1738
+ * @param params Optional parameters for pagination and sorting
1739
+ * @param options Request options including headers
1740
+ * @returns Leaderboard response data
1741
+ */
1432
1742
  async function fetchPointsLeaderboard({ params, options, }) {
1433
1743
  try {
1434
1744
  const { page = 1, perPage = 10, sortBy = 'totalPoints' } = params || {};
@@ -1451,6 +1761,13 @@ async function fetchPointsLeaderboard({ params, options, }) {
1451
1761
  throw new Error(`#fetchPointsLeaderboard::${errorMessage}`);
1452
1762
  }
1453
1763
  }
1764
+ /**
1765
+ * Fetch the timestamp when yield was last realized for a vault.
1766
+ * Returns the assetsUpdatedOn timestamp from the vault contract.
1767
+ * @param vault Vault contract address
1768
+ * @param options RPC configuration
1769
+ * @returns Timestamp (Unix timestamp in seconds) when yield was last realized
1770
+ */
1454
1771
  async function getYieldLastRealizedOn({ vault, options, }) {
1455
1772
  try {
1456
1773
  const provider = (0, core_1.createProvider)(options.rpcUrl);
@@ -1484,6 +1801,8 @@ async function getYieldLastRealizedOn({ vault, options, }) {
1484
1801
  return 0;
1485
1802
  }
1486
1803
  default: {
1804
+ // Old lending pools don't have assetsUpdatedOn function
1805
+ // Return 0 for legacy vaults
1487
1806
  return 0;
1488
1807
  }
1489
1808
  }
@@ -1495,6 +1814,19 @@ async function getYieldLastRealizedOn({ vault, options, }) {
1495
1814
  throw new Error(`#getYieldLastRealizedOn::${vault}:${errorMessage}`);
1496
1815
  }
1497
1816
  }
1817
+ /**
1818
+ * Calculate lifetime PnL for a user in a specific vault.
1819
+ *
1820
+ * Basic logic:
1821
+ * 1. Get user's list of deposits and withdrawals from subgraph
1822
+ * 2. Fetch user's current assets (balanceOf * sharePrice via convertToAssets)
1823
+ * 3. Calculate PnL = assets withdrawn + current assets - assets deposited
1824
+ *
1825
+ * @param vault Vault contract address
1826
+ * @param wallet User wallet address
1827
+ * @param options RPC configuration and service options
1828
+ * @returns Lifetime PnL data in both native token and USD
1829
+ */
1498
1830
  async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1499
1831
  try {
1500
1832
  if (!vault)
@@ -1507,6 +1839,7 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1507
1839
  if (!(0, ethers_1.isAddress)(wallet))
1508
1840
  throw new Error(`Wallet input parameter is not an address: ${String(wallet)}`);
1509
1841
  const provider = (0, core_1.createProvider)(options.rpcUrl);
1842
+ // Parallelize independent operations
1510
1843
  const [decimals, userHistory, positions, tokenizedVaultResult] = await Promise.all([
1511
1844
  (0, core_1.getDecimals)(provider, vault),
1512
1845
  (0, vaults_1.getSubgraphUserHistory)(wallet, provider, vault),
@@ -1521,6 +1854,7 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1521
1854
  if (!tokenizedVault) {
1522
1855
  throw new Error(`Vault ${vault} not found`);
1523
1856
  }
1857
+ // Fetch LayerZero deposits/redeems for supported vaults
1524
1858
  const lzVaultKey = (0, deposits_1.isLayerZeroVault)(vault);
1525
1859
  let lzDeposits = [];
1526
1860
  let lzRedeems = [];
@@ -1534,6 +1868,7 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1534
1868
  core_1.Logger.log.warn('getVaultUserLifetimePnl:lzDeposits', `Failed to fetch LZ deposits for ${lzVaultKey}: ${e}`);
1535
1869
  }),
1536
1870
  ];
1871
+ // Only earnAUSD has redeem events currently
1537
1872
  if (lzVaultKey === 'earnAUSD') {
1538
1873
  lzPromises.push((0, redeems_1.queryLayerZeroRedeems)(wallet)
1539
1874
  .then((r) => {
@@ -1547,10 +1882,12 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1547
1882
  }
1548
1883
  const version = (0, core_1.getVaultVersionV2)(tokenizedVault);
1549
1884
  const currentPosition = positions.find((pos) => pos.vault?.toLowerCase() === vault.toLowerCase());
1885
+ // Start underlyingSymbol fetch (runs in parallel with processHistory)
1550
1886
  const underlyingSymbolPromise = (async () => {
1551
1887
  let underlyingTokenSymbol = 'UNKNOWN';
1552
1888
  let underlyingAssetAddress;
1553
1889
  try {
1890
+ // Use ABI_LENDING_POOL_V2 which has the asset() method
1554
1891
  const vaultContract = (0, core_1.createContract)({
1555
1892
  provider,
1556
1893
  address: vault,
@@ -1578,15 +1915,22 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1578
1915
  }
1579
1916
  return { underlyingTokenSymbol, underlyingAssetAddress };
1580
1917
  })();
1918
+ // Process history - count all withdrawal requests (both processed and pending)
1919
+ // We don't count processed withdrawals separately to avoid double counting
1920
+ // Group deposit amounts by asset address to handle different decimals
1581
1921
  const depositsByAsset = new Map();
1922
+ // Separate withdrawal tracking: shares need conversion, assets don't
1582
1923
  let totalWithdrawalsInShares = BigInt(0);
1583
1924
  let totalWithdrawalsInAssets = BigInt(0);
1584
1925
  for (const historyItem of userHistory) {
1585
1926
  const amount = BigInt(historyItem.amount);
1927
+ // ignore bad transactions when calculating pnl
1586
1928
  if ((0, core_1.isBadTransaction)(historyItem.transactionHash_, tokenizedVault.chain)) {
1587
1929
  continue;
1588
1930
  }
1589
1931
  if (historyItem.type === 'deposit') {
1932
+ // Use assetIn if available (V2 vaults), otherwise use a default key
1933
+ // Use proper type guard instead of type assertion
1590
1934
  const assetKey = 'assetIn' in historyItem && typeof historyItem.assetIn === 'string'
1591
1935
  ? historyItem.assetIn.toLowerCase()
1592
1936
  : 'default';
@@ -1594,15 +1938,21 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1594
1938
  depositsByAsset.set(assetKey, current + amount);
1595
1939
  }
1596
1940
  else if (historyItem.type === 'withdraw-request') {
1941
+ // Withdraw requests are in shares, need conversion to assets
1597
1942
  totalWithdrawalsInShares += amount;
1598
1943
  }
1599
1944
  else if (historyItem.type === 'withdraw-processed' &&
1600
1945
  historyItem.isInstant) {
1946
+ // Instant withdrawals are already in assets, no conversion needed
1601
1947
  totalWithdrawalsInAssets += amount;
1602
1948
  }
1603
1949
  }
1950
+ // Include LayerZero cross-chain deposits and redeems
1604
1951
  if (lzVaultKey && lzDeposits.length > 0) {
1605
1952
  for (const lzDeposit of lzDeposits) {
1953
+ // LZ deposits assetAmt is the underlying asset amount deposited in hub-chain decimals.
1954
+ // This assumes the LZ subgraph indexes amounts in the same decimals as the hub vault
1955
+ // (e.g. 6 for USDC). If source/hub decimals diverge, normalization would be needed here.
1606
1956
  const assetKey = 'lz-default';
1607
1957
  const amount = BigInt(lzDeposit.assetAmt);
1608
1958
  const current = depositsByAsset.get(assetKey) ?? BigInt(0);
@@ -1611,9 +1961,11 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1611
1961
  }
1612
1962
  if (lzVaultKey && lzRedeems.length > 0) {
1613
1963
  for (const lzRedeem of lzRedeems) {
1964
+ // LZ redeems shareAmt is in shares, needs conversion via share price
1614
1965
  totalWithdrawalsInShares += BigInt(lzRedeem.shareAmt);
1615
1966
  }
1616
1967
  }
1968
+ // Fetch decimals for each unique asset in parallel
1617
1969
  const assetDecimals = new Map();
1618
1970
  await Promise.all(Array.from(depositsByAsset.keys()).map(async (assetAddress) => {
1619
1971
  if (assetAddress === 'default' || assetAddress === 'lz-default') {
@@ -1624,15 +1976,18 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1624
1976
  assetDecimals.set(assetAddress, dec);
1625
1977
  }
1626
1978
  }));
1979
+ // Store deposits as a map: assetAddress -> INormalizedNumber (with correct decimals)
1627
1980
  const totalDepositedRaw = new Map();
1628
1981
  for (const [assetAddress, rawAmount] of depositsByAsset) {
1629
1982
  const assetDec = assetDecimals.get(assetAddress) ?? decimals;
1630
1983
  totalDepositedRaw.set(assetAddress, (0, core_1.toNormalizedBn)(rawAmount, assetDec));
1631
1984
  }
1985
+ // Fetch USD prices for each unique deposit asset in parallel
1632
1986
  const assetPrices = new Map();
1633
1987
  if (tokenizedVault.chain) {
1634
1988
  await Promise.all(Array.from(depositsByAsset.keys()).map(async (assetAddress) => {
1635
1989
  if (assetAddress === 'default' || assetAddress === 'lz-default') {
1990
+ // Will use underlying asset price later
1636
1991
  assetPrices.set(assetAddress, 0);
1637
1992
  }
1638
1993
  else {
@@ -1648,6 +2003,7 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1648
2003
  }));
1649
2004
  }
1650
2005
  const { underlyingTokenSymbol, underlyingAssetAddress } = await underlyingSymbolPromise;
2006
+ // Calculate current position value and fetch price in parallel
1651
2007
  let currentPositionValue = (0, core_1.toNormalizedBn)(0, decimals);
1652
2008
  const pricePromise = underlyingAssetAddress && tokenizedVault.chain
1653
2009
  ? (0, core_1.fetchTokenPriceByAddress)(underlyingAssetAddress, tokenizedVault.chain, options?.headers).catch(() => 0)
@@ -1674,7 +2030,10 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1674
2030
  else {
1675
2031
  currentShares = await vaultContract.balanceOf(wallet);
1676
2032
  }
2033
+ // For LayerZero vaults, include receipt token balances on remote chains
1677
2034
  if (lzVaultKey) {
2035
+ // Build list of remote chains to check: prefer receipt_token_integrations from API,
2036
+ // fall back to LAYERZERO_VAULTS spoke chain config (OVault shareOFT addresses)
1678
2037
  const remoteChains = [];
1679
2038
  if (tokenizedVault.receipt_token_integrations?.length) {
1680
2039
  for (const rti of tokenizedVault.receipt_token_integrations) {
@@ -1689,6 +2048,7 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1689
2048
  }
1690
2049
  }
1691
2050
  else {
2051
+ // Fallback: use spoke chain addresses from LAYERZERO_VAULTS config
1692
2052
  const lzVaultConfig = deposits_1.LAYERZERO_VAULTS[lzVaultKey];
1693
2053
  if (lzVaultConfig.spokeChains) {
1694
2054
  for (const [chainIdStr, address] of Object.entries(lzVaultConfig.spokeChains)) {
@@ -1721,6 +2081,7 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1721
2081
  remoteTokenContract.decimals(),
1722
2082
  ]);
1723
2083
  const remDec = Number(remoteDecimals) || decimals;
2084
+ // Normalize remote balance to hub chain decimals
1724
2085
  if (remDec > decimals) {
1725
2086
  return rawBalance / BigInt(10 ** (remDec - decimals));
1726
2087
  }
@@ -1748,7 +2109,7 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1748
2109
  vaultContract.totalSupply(),
1749
2110
  ]);
1750
2111
  if (totalSupply === BigInt(0)) {
1751
- sharePriceRaw = BigInt(10 ** decimals);
2112
+ sharePriceRaw = BigInt(10 ** decimals); // 1:1 if no supply
1752
2113
  }
1753
2114
  else {
1754
2115
  sharePriceRaw = (totalAssets * BigInt(10 ** decimals)) / totalSupply;
@@ -1763,22 +2124,28 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1763
2124
  currentPositionValue = currentPosition.walletBalance;
1764
2125
  }
1765
2126
  }
2127
+ // Calculate totalWithdrawn combining shares (converted) and assets (as-is)
1766
2128
  let totalWithdrawnRaw = totalWithdrawalsInAssets;
1767
2129
  if (version === 'evm-2' && sharePriceRaw > BigInt(0)) {
2130
+ // Only convert shares to assets, instant withdrawals are already in assets
1768
2131
  totalWithdrawnRaw +=
1769
2132
  (totalWithdrawalsInShares * sharePriceRaw) / BigInt(10 ** decimals);
1770
2133
  }
1771
2134
  else {
2135
+ // For non-evm-2 or when share price is 0, treat shares as assets
1772
2136
  totalWithdrawnRaw += totalWithdrawalsInShares;
1773
2137
  }
1774
2138
  const totalWithdrawn = (0, core_1.toNormalizedBn)(totalWithdrawnRaw, decimals);
1775
2139
  const tokenPriceUsd = await pricePromise;
2140
+ // Validate token price and log warning if invalid
1776
2141
  const effectiveTokenPrice = tokenPriceUsd > 0 ? tokenPriceUsd : 1;
1777
2142
  if (tokenPriceUsd <= 0) {
1778
2143
  core_1.Logger.log.warn('getVaultUserLifetimePnl:tokenPrice', `Invalid underlying token price: ${tokenPriceUsd}, using fallback of 1`);
1779
2144
  }
2145
+ // Sum all deposited amounts in USD using each asset's own price
1780
2146
  let totalDepositedUsd = 0;
1781
2147
  for (const [assetAddress, normalizedAmount] of totalDepositedRaw) {
2148
+ // Use the asset-specific price, or underlying price for 'default'
1782
2149
  const assetPrice = assetAddress === 'default' || assetAddress === 'lz-default'
1783
2150
  ? effectiveTokenPrice
1784
2151
  : assetPrices.get(assetAddress) || effectiveTokenPrice;
@@ -1786,14 +2153,18 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1786
2153
  }
1787
2154
  const totalWithdrawnUsd = Number(totalWithdrawn.normalized) * effectiveTokenPrice;
1788
2155
  const currentPositionValueUsd = Number(currentPositionValue.normalized) * effectiveTokenPrice;
2156
+ // Calculate lifetimePnl in USD first (since deposits may be in different tokens with different prices)
1789
2157
  const lifetimePnlUsd = currentPositionValueUsd + totalWithdrawnUsd - totalDepositedUsd;
1790
- const scaleFactor = BigInt(10 ** 18);
2158
+ // Use BigInt-scaled arithmetic to minimize precision loss when converting USD to native
2159
+ const scaleFactor = BigInt(10 ** 18); // High precision scale
1791
2160
  const tokenPriceScaled = BigInt(Math.round(effectiveTokenPrice * Number(scaleFactor)));
2161
+ // Convert totalDepositedUsd to native units for backwards compatibility
1792
2162
  const totalDepositedUsdScaled = BigInt(Math.round(totalDepositedUsd * Number(scaleFactor)));
1793
2163
  const totalDepositedNativeRaw = tokenPriceScaled > BigInt(0)
1794
2164
  ? (totalDepositedUsdScaled * BigInt(10 ** decimals)) / tokenPriceScaled
1795
2165
  : BigInt(0);
1796
2166
  const totalDeposited = (0, core_1.toNormalizedBn)(totalDepositedNativeRaw, decimals);
2167
+ // Convert lifetimePnlUsd to native units
1797
2168
  const lifetimePnlUsdScaled = BigInt(Math.round(lifetimePnlUsd * Number(scaleFactor)));
1798
2169
  const lifetimePnlRaw = tokenPriceScaled > BigInt(0)
1799
2170
  ? (lifetimePnlUsdScaled * BigInt(10 ** decimals)) / tokenPriceScaled
@@ -1821,6 +2192,14 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1821
2192
  throw new Error(`#getVaultUserLifetimePnl::${vault}::${wallet}:${errorMessage}`);
1822
2193
  }
1823
2194
  }
2195
+ /**
2196
+ * Calculate PnL for a vault (vault-level, not user-specific).
2197
+ * Returns the vault's overall profit and loss across all users.
2198
+ *
2199
+ * @param vault Vault contract address
2200
+ * @param options RPC configuration and service options
2201
+ * @returns Vault PnL in USD and notional value
2202
+ */
1824
2203
  async function getVaultPnl({ vault, options, }) {
1825
2204
  try {
1826
2205
  if (!vault)
@@ -1828,6 +2207,7 @@ async function getVaultPnl({ vault, options, }) {
1828
2207
  (0, utils_3.assertNotStellar)(vault, 'Vault PnL');
1829
2208
  if (!(0, ethers_1.isAddress)(vault))
1830
2209
  throw new Error(`Vault input parameter is not an address: ${String(vault)}`);
2210
+ // Get vault data
1831
2211
  const vaultData = await getVault({
1832
2212
  vault,
1833
2213
  loans: false,
@@ -1837,6 +2217,7 @@ async function getVaultPnl({ vault, options, }) {
1837
2217
  if (!vaultData) {
1838
2218
  throw new Error(`Vault ${vault} not found`);
1839
2219
  }
2220
+ // Check if vault has daily_pnl_per_share and historical_snapshots
1840
2221
  if (!vaultData.dailyPnlPerShare ||
1841
2222
  !Array.isArray(vaultData.dailyPnlPerShare) ||
1842
2223
  vaultData.dailyPnlPerShare.length === 0) {
@@ -1854,38 +2235,49 @@ async function getVaultPnl({ vault, options, }) {
1854
2235
  };
1855
2236
  }
1856
2237
  const decimals = vaultData.decimals;
2238
+ // Filter out dates with zero PnL
1857
2239
  const validDailyPnl = vaultData.dailyPnlPerShare.filter((dayData) => dayData.value && dayData.value !== 0);
2240
+ // Create a map of historical_snapshots by date (YYYY-MM-DD) for quick lookup
1858
2241
  const snapshotsByDate = new Map();
1859
2242
  vaultData.historical_snapshots.forEach((snapshot) => {
1860
2243
  const snapshotDate = new Date(snapshot.snapshot_datetime);
1861
- const dateKey = snapshotDate.toISOString().split('T')[0];
2244
+ const dateKey = snapshotDate.toISOString().split('T')[0]; // YYYY-MM-DD
1862
2245
  snapshotsByDate.set(dateKey, snapshot);
1863
2246
  });
2247
+ // Calculate PnL for each day using historical_snapshots
1864
2248
  let totalPnlRaw = BigInt(0);
1865
2249
  validDailyPnl.forEach((dayData) => {
1866
2250
  const dayTimestamp = dayData.timestamp;
1867
2251
  const dailyPnlPerShare = dayData.value;
2252
+ // Get date key (YYYY-MM-DD) from timestamp
1868
2253
  const dayDate = new Date(dayTimestamp);
1869
2254
  const dateKey = dayDate.toISOString().split('T')[0];
2255
+ // Find matching snapshot for this date
1870
2256
  const snapshot = snapshotsByDate.get(dateKey);
1871
2257
  if (!snapshot || !snapshot.total_shares) {
1872
2258
  core_1.Logger.log.warn('getVaultPnl:snapshotNotFound', `No snapshot found for date ${dateKey}`);
1873
2259
  return;
1874
2260
  }
2261
+ // Convert normalized numbers to BigInt using toNormalizedBn's normalized string
1875
2262
  const dailyPnlNorm = (0, core_1.toNormalizedBn)(dailyPnlPerShare, decimals);
1876
2263
  const totalSharesNorm = (0, core_1.toNormalizedBn)(snapshot.total_shares, decimals);
2264
+ // Convert normalized strings to BigInt (split, pad fraction, combine) - all inline
1877
2265
  const [dailyWhole = '0', dailyFrac = ''] = dailyPnlNorm.normalized.split('.');
1878
2266
  const dailyPnlPerShareScaled = BigInt(dailyWhole + (dailyFrac || '').padEnd(decimals, '0').slice(0, decimals));
1879
2267
  const [sharesWhole = '0', sharesFrac = ''] = totalSharesNorm.normalized.split('.');
1880
2268
  const totalSharesScaled = BigInt(sharesWhole +
1881
2269
  (sharesFrac || '').padEnd(decimals, '0').slice(0, decimals));
2270
+ // Calculate: daily_pnl_per_share[day] * total_shares[day] (both in raw units)
1882
2271
  const dayPnlRaw = (dailyPnlPerShareScaled * totalSharesScaled) / BigInt(10 ** decimals);
1883
2272
  totalPnlRaw += dayPnlRaw;
1884
2273
  });
2274
+ // Convert totalPnlRaw (BigInt) to normalized number using toNormalizedBn
1885
2275
  const totalPnl = (0, core_1.toNormalizedBn)(totalPnlRaw, decimals);
1886
2276
  const totalPnlNormalized = Number(totalPnl.normalized);
1887
2277
  const chainId = vaultData.chainId;
2278
+ // Get receipt token address (HLPe) from vaultData - no need for on-chain lookup
1888
2279
  const lpTokenAddress = vaultData.receipt.address;
2280
+ // Fetch HLPe (lpTokenAddress) price in USD
1889
2281
  let tokenPriceUsd = 0;
1890
2282
  try {
1891
2283
  tokenPriceUsd = await (0, core_1.fetchTokenPriceByAddress)(lpTokenAddress, chainId, options?.headers);
@@ -1893,6 +2285,7 @@ async function getVaultPnl({ vault, options, }) {
1893
2285
  catch (error) {
1894
2286
  core_1.Logger.log.warn('getVaultPnl:priceFetch', `Failed to fetch HLPe token price: ${error}`);
1895
2287
  }
2288
+ // Convert to USD and format to 6 decimal places
1896
2289
  const pnlUsd = Number((totalPnlNormalized * tokenPriceUsd).toFixed(6));
1897
2290
  return {
1898
2291
  totalPnl,
@@ -1905,6 +2298,14 @@ async function getVaultPnl({ vault, options, }) {
1905
2298
  throw new Error(`#getVaultPnl::${vault}:${errorMessage}`);
1906
2299
  }
1907
2300
  }
2301
+ /*
2302
+ * Fetch historical timeseries data for a vault.
2303
+ * Returns TVL, APY, PnL, share price, and other metrics over time.
2304
+ * @param vault Vault contract address
2305
+ * @param nDays Number of days of historical data (default 30, min 1)
2306
+ * @param options Request options including headers
2307
+ * @returns Historical timeseries data with date string keys
2308
+ */
1908
2309
  async function getVaultHistoricalTimeseries({ vault, nDays, options, }) {
1909
2310
  try {
1910
2311
  if (!vault)
@@ -1918,6 +2319,19 @@ async function getVaultHistoricalTimeseries({ vault, nDays, options, }) {
1918
2319
  throw new Error(`#getVaultHistoricalTimeseries::${vault}:${errorMessage}`);
1919
2320
  }
1920
2321
  }
2322
+ /**
2323
+ * Fetch annualized APY metrics for a vault.
2324
+ *
2325
+ * Supported Vaults: cUSDO, tETH, wstETH, rsETH
2326
+ *
2327
+ * @deprecated The `hgETH30dLiquidAPY` and `hgETH7dLiquidAPY` response fields are deprecated.
2328
+ * These fields will be removed on 2026-01-01.
2329
+ * Use `liquidAPY30Day` and `liquidAPY7Day` fields instead.
2330
+ *
2331
+ * @param vault Vault contract address
2332
+ * @param options Request options including headers
2333
+ * @returns Annualized APY data including liquidity APY
2334
+ */
1921
2335
  async function getVaultAnnualizedApy({ vault, options, }) {
1922
2336
  try {
1923
2337
  if (!vault) {
@@ -1942,6 +2356,12 @@ async function getVaultAnnualizedApy({ vault, options, }) {
1942
2356
  throw new Error(`#getVaultAnnualizedApy::${vault}:${errorMessage}`);
1943
2357
  }
1944
2358
  }
2359
+ /**
2360
+ * Fetch summary data for a vault (name, type, chain, recent returns).
2361
+ * @param vault Vault contract address
2362
+ * @param options Request options including headers
2363
+ * @returns Vault summary data
2364
+ */
1945
2365
  async function getVaultSummary({ vault, options, }) {
1946
2366
  try {
1947
2367
  if (!vault) {
@@ -1967,6 +2387,13 @@ async function getVaultSummary({ vault, options, }) {
1967
2387
  throw new Error(`#getVaultSummary::${vault}:${errorMessage}`);
1968
2388
  }
1969
2389
  }
2390
+ /**
2391
+ * Fetch withdrawal summary and pending queue for a vault.
2392
+ * @param vault Vault contract address
2393
+ * @param chain Chain identifier (e.g., chain ID as string)
2394
+ * @param options Request options including headers
2395
+ * @returns Withdrawal summary and pending queue
2396
+ */
1970
2397
  async function getVaultWithdrawals({ vault, chain, options, }) {
1971
2398
  try {
1972
2399
  if (!vault) {
@@ -1995,6 +2422,14 @@ async function getVaultWithdrawals({ vault, chain, options, }) {
1995
2422
  throw new Error(`#getVaultWithdrawals::${vault}::${chain}:${errorMessage}`);
1996
2423
  }
1997
2424
  }
2425
+ /**
2426
+ * Fetch pending redemptions for a vault with liquidity analysis.
2427
+ * @param vault Vault contract address
2428
+ * @param pastDays Number of past days to include (default 7, min 1, max 30)
2429
+ * @param futureDays Number of future days to include (default 14, min 1, max 30)
2430
+ * @param options Request options including headers
2431
+ * @returns Pending redemptions grouped by date with liquidity summary
2432
+ */
1998
2433
  async function getVaultPendingRedemptions({ vault, pastDays, futureDays, options, }) {
1999
2434
  try {
2000
2435
  if (!vault)
@@ -2062,6 +2497,13 @@ async function getVaultPendingRedemptions({ vault, pastDays, futureDays, options
2062
2497
  throw new Error(`#getVaultPendingRedemptions::${vault}:${errorMessage}`);
2063
2498
  }
2064
2499
  }
2500
+ /**
2501
+ * Preview the amount of assets that would be received for redeeming shares (queued redemption).
2502
+ * @param vault Vault contract address
2503
+ * @param sharesAmount Amount of shares to redeem (human-readable, raw bigint, or string)
2504
+ * @param options RPC configuration
2505
+ * @returns The amount of assets as INormalizedNumber { normalized, raw }
2506
+ */
2065
2507
  async function getPreviewRedemption({ vault, sharesAmount, options, }) {
2066
2508
  try {
2067
2509
  if (!vault)
@@ -2109,6 +2551,13 @@ async function getPreviewRedemption({ vault, sharesAmount, options, }) {
2109
2551
  throw new Error(`Failed to preview redemption for ${vault}: ${e instanceof Error ? e.message : 'Unknown error'}`);
2110
2552
  }
2111
2553
  }
2554
+ /**
2555
+ * Helper: Decode a processing transaction to extract (year, month, day).
2556
+ *
2557
+ * Note: Multicall-wrapped transactions cannot be decoded from calldata or logs
2558
+ * (the WithdrawalProcessed event doesn't include the date). When decoding fails,
2559
+ * the caller falls back to timestamp-based date estimation.
2560
+ */
2112
2561
  async function decodeProcessingTransaction(txHash, provider, iface) {
2113
2562
  const tx = await provider.getTransaction(txHash);
2114
2563
  if (!tx) {
@@ -2122,14 +2571,74 @@ async function decodeProcessingTransaction(txHash, provider, iface) {
2122
2571
  }
2123
2572
  return (0, call_data_decoder_1.decodeWithdrawalProcessing)(tx.data, iface);
2124
2573
  }
2574
+ /**
2575
+ * Returns all withdrawal requests for a vault, enriched with computed claimable dates
2576
+ * and current processing status.
2577
+ *
2578
+ * The unique identifier for matching requests to processed events is:
2579
+ * (receiver, claimableDate.year, claimableDate.month, claimableDate.day)
2580
+ *
2581
+ * This is the primary method for integrators to reliably track withdrawal status
2582
+ * when multiple requests are submitted by the same receiver.
2583
+ *
2584
+ * ## Data sources
2585
+ * - **Withdrawal requests** come from the subgraph (all historical requests).
2586
+ * - **Processed events** come from on-chain `WithdrawalProcessed` logs, which are
2587
+ * limited to a finite lookback window determined by the chain's block parameters.
2588
+ *
2589
+ * ## Lookback window & filtering
2590
+ * On-chain logs only cover a limited number of recent blocks (e.g. ~20 days on Monad,
2591
+ * ~21 days on Ethereum). The `lookbackBlocks` parameter controls how far back to scan
2592
+ * and is applied uniformly to both data sources: it bounds the `WithdrawalProcessed`
2593
+ * log query *and* drops any withdrawal request whose submission timestamp falls outside
2594
+ * the window — regardless of whether a matching processed event was found. Callers who
2595
+ * need deeper history should widen `lookbackBlocks`.
2596
+ *
2597
+ * ## Status determination
2598
+ * - `'processed'` — A matching `WithdrawalProcessed` event was found (by receiver + claimable date).
2599
+ * - `'ready_to_claim'` — Claimable date has passed (within the lookback window) but no processed event found.
2600
+ * - `'pending'` — Claimable date is still in the future.
2601
+ *
2602
+ * @param params - Query parameters
2603
+ * @param params.vault - Vault address to query
2604
+ * @param params.receiver - (Optional) Filter by receiver address
2605
+ * @param params.lookbackBlocks - (Optional) On-chain log lookback window in blocks.
2606
+ * Defaults to a chain-specific value (e.g. 150,000 for Ethereum, 3,456,000 for Monad).
2607
+ * Increasing this scans more history but requires more RPC calls.
2608
+ * @param params.options - Standard vault query options (provider, RPC URL, etc.)
2609
+ * @returns Array of withdrawal requests with status and claimable dates
2610
+ *
2611
+ * @example
2612
+ * ```typescript
2613
+ * const requests = await getWithdrawalRequestsWithStatus({
2614
+ * vault: '0xVault...',
2615
+ * receiver: '0xReceiver...'
2616
+ * });
2617
+ *
2618
+ * // Results show exact status + claimable date
2619
+ * requests.forEach(req => {
2620
+ * console.log(
2621
+ * `${req.claimableDate.year}-${req.claimableDate.month}-${req.claimableDate.day}: ${req.status}`
2622
+ * );
2623
+ * });
2624
+ *
2625
+ * // With custom lookback window (e.g. 30 days on Ethereum ≈ 216,000 blocks)
2626
+ * const moreResults = await getWithdrawalRequestsWithStatus({
2627
+ * vault: '0xVault...',
2628
+ * lookbackBlocks: 216_000,
2629
+ * });
2630
+ * ```
2631
+ */
2125
2632
  async function getWithdrawalRequestsWithStatus(params) {
2126
2633
  try {
2127
2634
  const { vault, receiver, lookbackBlocks, options } = params;
2635
+ // Input validation
2128
2636
  if (!vault || !(0, ethers_1.isAddress)(vault)) {
2129
2637
  throw new Error('Invalid vault address');
2130
2638
  }
2131
2639
  const vaultAddress = (0, ethers_1.getAddress)(vault);
2132
2640
  const provider = (0, core_1.createProvider)(options.rpcUrl);
2641
+ // Fetch vault metadata and lag duration
2133
2642
  const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vaultAddress, options?.headers, false, false))?.[0];
2134
2643
  if (!tokenizedVault) {
2135
2644
  core_1.Logger.log.warn('getWithdrawalRequestsWithStatus', `Vault not found in backend: ${vaultAddress}`);
@@ -2143,7 +2652,7 @@ async function getWithdrawalRequestsWithStatus(params) {
2143
2652
  abi: vaultAbi,
2144
2653
  provider,
2145
2654
  });
2146
- let lagDuration = 86400;
2655
+ let lagDuration = 86400; // Default: 1 day
2147
2656
  try {
2148
2657
  const lagValue = await vaultContract.lagDuration?.();
2149
2658
  if (lagValue !== undefined && lagValue !== null) {
@@ -2153,6 +2662,9 @@ async function getWithdrawalRequestsWithStatus(params) {
2153
2662
  catch (e) {
2154
2663
  core_1.Logger.log.warn('getWithdrawalRequestsWithStatus', `Failed to fetch lagDuration from vault ${vaultAddress}, using default 1 day — status determinations may be inaccurate: ${e instanceof Error ? e.message : 'Unknown error'}`);
2155
2664
  }
2665
+ // Fetch subgraph requests, on-chain processed events, and the current block
2666
+ // in parallel. Current block is needed to derive the block-based lookback
2667
+ // cutoff applied uniformly to both data sources.
2156
2668
  const [withdrawalRequests, processedEvents, currentBlock] = await Promise.all([
2157
2669
  (0, vaults_1.getSubgraphWithdrawRequests)(vaultAddress, provider),
2158
2670
  getVaultRedemptionHistory({
@@ -2170,6 +2682,10 @@ async function getWithdrawalRequestsWithStatus(params) {
2170
2682
  const effectiveLookbackBlocks = lookbackBlocks ?? (0, core_1.determineBlockCutoff)(chainId);
2171
2683
  const blockCutoff = currentBlock - effectiveLookbackBlocks;
2172
2684
  const now = Math.floor(Date.now() / 1000);
2685
+ // Filter by receiver (if provided) and drop any request whose submission
2686
+ // block is outside the lookback window — uniformly, regardless of whether
2687
+ // a matching processed event exists. Using block_number (not timestamp)
2688
+ // keeps this consistent with the on-chain log scan's block-based cutoff.
2173
2689
  const filteredRequests = withdrawalRequests.filter((req) => {
2174
2690
  if (receiver &&
2175
2691
  req.receiverAddr?.toLowerCase() !== receiver.toLowerCase()) {
@@ -2177,8 +2693,10 @@ async function getWithdrawalRequestsWithStatus(params) {
2177
2693
  }
2178
2694
  return Number(req.block_number) >= blockCutoff;
2179
2695
  });
2696
+ // Pre-decode all processed transactions in parallel for performance
2180
2697
  const iface = new ethers_1.Interface(vaultAbi);
2181
2698
  const decodedProcessed = await Promise.all((processedEvents || []).map(async (processed) => {
2699
+ // Try calldata decoding first (deterministic)
2182
2700
  if (processed.transactionHash) {
2183
2701
  try {
2184
2702
  const decoded = await decodeProcessingTransaction(processed.transactionHash, provider, iface);
@@ -2197,8 +2715,12 @@ async function getWithdrawalRequestsWithStatus(params) {
2197
2715
  core_1.Logger.log.warn('getWithdrawalRequestsWithStatus', `Failed to decode processed transaction ${processed.transactionHash}: ${e instanceof Error ? e.message : 'Unknown error'}`);
2198
2716
  }
2199
2717
  }
2718
+ // Fallback: use original request timestamp to compute claimable date,
2719
+ // or if unavailable (evm-2: requested=epoch 0), use the processed block
2720
+ // timestamp as the date directly (processing happens on the claimable date).
2200
2721
  const requestedEpoch = Math.floor(processed.requested.getTime() / 1000);
2201
2722
  if (requestedEpoch > 0) {
2723
+ // evm-1: we have the original request timestamp → compute claimable date
2202
2724
  const fallbackDate = (0, date_utils_1.computeClaimableDate)(requestedEpoch, lagDuration);
2203
2725
  return {
2204
2726
  ...processed,
@@ -2209,6 +2731,8 @@ async function getWithdrawalRequestsWithStatus(params) {
2209
2731
  },
2210
2732
  };
2211
2733
  }
2734
+ // evm-2: no request timestamp — use processed block date directly
2735
+ // (processing occurs on the claimable date, so the block date IS the date)
2212
2736
  const processedDate = processed.processed;
2213
2737
  return {
2214
2738
  ...processed,
@@ -2219,6 +2743,11 @@ async function getWithdrawalRequestsWithStatus(params) {
2219
2743
  },
2220
2744
  };
2221
2745
  }));
2746
+ // Build deterministic lookup map: (receiver + year + month + day) → processed events
2747
+ // A single WithdrawalProcessed event covers the entire day's batch for a
2748
+ // receiver — all requests whose claimable date falls on that day should
2749
+ // match it. We keep an array per key only to preserve every event we saw
2750
+ // for visibility; matching is a non-destructive peek at the first entry.
2222
2751
  const processedMap = new Map();
2223
2752
  for (const processed of decodedProcessed) {
2224
2753
  if (!processed.decodedDate) {
@@ -2229,15 +2758,24 @@ async function getWithdrawalRequestsWithStatus(params) {
2229
2758
  const existing = processedMap.get(key) ?? [];
2230
2759
  processedMap.set(key, [...existing, processed]);
2231
2760
  }
2761
+ // Compute status for each withdrawal request. The lookback window was
2762
+ // already applied up-front when pruning filteredRequests, so every request
2763
+ // reaching this loop is within the window by construction.
2232
2764
  const results = [];
2233
2765
  for (const req of filteredRequests) {
2766
+ // timestamp_ is a Unix epoch string (seconds)
2234
2767
  const requestTimestamp = Number(req.timestamp_);
2768
+ // Compute claimable date from timestamp + lagDuration (UTC)
2235
2769
  const claimableDate = (0, date_utils_1.computeClaimableDate)(requestTimestamp, lagDuration);
2770
+ // Determine status: check processed first, then claimability
2236
2771
  let status = 'pending';
2237
2772
  let processedTxHash;
2238
2773
  let assetsReceived;
2774
+ // Match deterministically by (receiver, year, month, day)
2239
2775
  const dateKey = (0, date_utils_1.formatDateKey)(claimableDate.year, claimableDate.month, claimableDate.day);
2240
2776
  const lookupKey = `${req.receiverAddr.toLowerCase()}:${dateKey}`;
2777
+ // Peek — do not consume. The processing event covers the whole day for
2778
+ // this receiver, so every request on that day matches the same event.
2241
2779
  const processedEvent = processedMap.get(lookupKey)?.[0];
2242
2780
  if (processedEvent) {
2243
2781
  status = 'processed';
@@ -2250,6 +2788,7 @@ async function getWithdrawalRequestsWithStatus(params) {
2250
2788
  }
2251
2789
  }
2252
2790
  else if ((0, date_utils_1.isClaimableNow)(claimableDate.epoch, now)) {
2791
+ // Claimable date is recent (within lookback) and not yet processed
2253
2792
  status = 'ready_to_claim';
2254
2793
  }
2255
2794
  results.push({
@@ -2260,6 +2799,7 @@ async function getWithdrawalRequestsWithStatus(params) {
2260
2799
  return BigInt(req.shares);
2261
2800
  }
2262
2801
  catch {
2802
+ // Handle decimal strings from subgraph (e.g. "1234.0")
2263
2803
  const truncated = Math.trunc(Number(req.shares));
2264
2804
  if (!Number.isFinite(truncated)) {
2265
2805
  core_1.Logger.log.warn('getWithdrawalRequestsWithStatus', `Cannot parse shares value "${req.shares}" — defaulting to 0`);