@augustdigital/sdk 8.3.2 → 8.6.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 (213) hide show
  1. package/README.md +207 -117
  2. package/lib/abis/ERC20_Bytes32.d.ts +4 -0
  3. package/lib/abis/ERC20_Bytes32.js +4 -0
  4. package/lib/abis/ERC4626.d.ts +1 -0
  5. package/lib/abis/ERC4626.js +1 -0
  6. package/lib/abis/ERC721.d.ts +1 -0
  7. package/lib/abis/ERC721.js +1 -0
  8. package/lib/abis/FeeOracle.js +1 -0
  9. package/lib/abis/LendingPool.js +1 -0
  10. package/lib/abis/LendingPoolV2.js +1 -0
  11. package/lib/abis/Multicall3.js +3 -0
  12. package/lib/abis/OFT.d.ts +20 -0
  13. package/lib/abis/OFT.js +20 -0
  14. package/lib/abis/SmartAccount.d.ts +1 -0
  15. package/lib/abis/SmartAccount.js +3 -0
  16. package/lib/abis/SwapRouter.d.ts +1 -0
  17. package/lib/abis/SwapRouter.js +1 -0
  18. package/lib/abis/UniversalSignatureValidator.js +3 -0
  19. package/lib/abis/index.d.ts +5 -0
  20. package/lib/abis/index.js +5 -0
  21. package/lib/adapters/evm/getters.d.ts +17 -2
  22. package/lib/adapters/evm/getters.js +35 -3
  23. package/lib/adapters/evm/index.d.ts +266 -4
  24. package/lib/adapters/evm/index.js +270 -1
  25. package/lib/adapters/evm/utils.d.ts +6 -0
  26. package/lib/adapters/evm/utils.js +7 -0
  27. package/lib/adapters/solana/constants.d.ts +1 -1
  28. package/lib/adapters/solana/constants.js +4 -1
  29. package/lib/adapters/solana/getters.d.ts +9 -1
  30. package/lib/adapters/solana/getters.js +21 -0
  31. package/lib/adapters/solana/idl/vault-idl.js +9 -0
  32. package/lib/adapters/solana/index.d.ts +59 -4
  33. package/lib/adapters/solana/index.js +61 -0
  34. package/lib/adapters/solana/types.d.ts +1 -1
  35. package/lib/adapters/solana/utils.d.ts +31 -3
  36. package/lib/adapters/solana/utils.js +79 -4
  37. package/lib/adapters/solana/vault.actions.d.ts +23 -4
  38. package/lib/adapters/solana/vault.actions.js +47 -3
  39. package/lib/adapters/stellar/actions.d.ts +26 -1
  40. package/lib/adapters/stellar/actions.js +33 -0
  41. package/lib/adapters/stellar/constants.d.ts +26 -0
  42. package/lib/adapters/stellar/constants.js +29 -0
  43. package/lib/adapters/stellar/getters.d.ts +56 -0
  44. package/lib/adapters/stellar/getters.js +81 -0
  45. package/lib/adapters/stellar/index.d.ts +70 -0
  46. package/lib/adapters/stellar/index.js +71 -0
  47. package/lib/adapters/stellar/soroban.d.ts +21 -1
  48. package/lib/adapters/stellar/soroban.js +47 -1
  49. package/lib/adapters/stellar/submit.d.ts +29 -0
  50. package/lib/adapters/stellar/submit.js +90 -5
  51. package/lib/adapters/stellar/types.d.ts +27 -0
  52. package/lib/adapters/stellar/types.js +3 -0
  53. package/lib/adapters/stellar/utils.d.ts +10 -0
  54. package/lib/adapters/stellar/utils.js +10 -0
  55. package/lib/adapters/sui/getters.d.ts +7 -1
  56. package/lib/adapters/sui/getters.js +6 -0
  57. package/lib/adapters/sui/index.d.ts +16 -1
  58. package/lib/adapters/sui/index.js +17 -0
  59. package/lib/adapters/sui/transformer.d.ts +8 -2
  60. package/lib/adapters/sui/transformer.js +7 -0
  61. package/lib/adapters/sui/types.d.ts +1 -1
  62. package/lib/adapters/sui/utils.d.ts +6 -0
  63. package/lib/adapters/sui/utils.js +6 -0
  64. package/lib/core/analytics/chain-name.d.ts +8 -0
  65. package/lib/core/analytics/chain-name.js +8 -0
  66. package/lib/core/analytics/constants.d.ts +4 -0
  67. package/lib/core/analytics/constants.js +4 -0
  68. package/lib/core/analytics/env.d.ts +25 -0
  69. package/lib/core/analytics/env.js +26 -0
  70. package/lib/core/analytics/index.d.ts +26 -0
  71. package/lib/core/analytics/index.js +35 -0
  72. package/lib/core/analytics/instrumentation.d.ts +26 -0
  73. package/lib/core/analytics/instrumentation.js +66 -2
  74. package/lib/core/analytics/method-taxonomy.d.ts +16 -0
  75. package/lib/core/analytics/method-taxonomy.js +18 -0
  76. package/lib/core/analytics/metrics.d.ts +23 -0
  77. package/lib/core/analytics/metrics.js +40 -0
  78. package/lib/core/analytics/sanitize.d.ts +38 -0
  79. package/lib/core/analytics/sanitize.js +46 -0
  80. package/lib/core/analytics/sentry-runtime.d.ts +11 -0
  81. package/lib/core/analytics/sentry-runtime.js +20 -1
  82. package/lib/core/analytics/sentry.d.ts +47 -2
  83. package/lib/core/analytics/sentry.js +115 -2
  84. package/lib/core/analytics/types.d.ts +28 -1
  85. package/lib/core/analytics/user-identity.d.ts +36 -2
  86. package/lib/core/analytics/user-identity.js +43 -1
  87. package/lib/core/analytics/version.d.ts +6 -1
  88. package/lib/core/analytics/version.js +6 -1
  89. package/lib/core/auth/verify.js +5 -0
  90. package/lib/core/base.class.d.ts +78 -3
  91. package/lib/core/base.class.js +66 -4
  92. package/lib/core/cache.d.ts +5 -0
  93. package/lib/core/cache.js +6 -0
  94. package/lib/core/constants/adapters.d.ts +16 -1
  95. package/lib/core/constants/adapters.js +28 -8
  96. package/lib/core/constants/core.d.ts +14 -1
  97. package/lib/core/constants/core.js +18 -0
  98. package/lib/core/constants/swap-router.d.ts +83 -1
  99. package/lib/core/constants/swap-router.js +91 -1
  100. package/lib/core/constants/vaults.d.ts +57 -1
  101. package/lib/core/constants/vaults.js +66 -1
  102. package/lib/core/constants/web3.d.ts +4 -1
  103. package/lib/core/constants/web3.js +15 -5
  104. package/lib/core/errors/index.d.ts +38 -2
  105. package/lib/core/errors/index.js +38 -0
  106. package/lib/core/fetcher.d.ts +136 -2
  107. package/lib/core/fetcher.js +229 -25
  108. package/lib/core/helpers/adapters.d.ts +10 -1
  109. package/lib/core/helpers/adapters.js +11 -0
  110. package/lib/core/helpers/chain-address.d.ts +10 -0
  111. package/lib/core/helpers/chain-address.js +11 -0
  112. package/lib/core/helpers/core.d.ts +49 -4
  113. package/lib/core/helpers/core.js +74 -7
  114. package/lib/core/helpers/explorer-link.d.ts +15 -1
  115. package/lib/core/helpers/explorer-link.js +14 -0
  116. package/lib/core/helpers/signer.d.ts +27 -1
  117. package/lib/core/helpers/signer.js +39 -0
  118. package/lib/core/helpers/swap-router.d.ts +33 -1
  119. package/lib/core/helpers/swap-router.js +32 -0
  120. package/lib/core/helpers/vault-version.d.ts +2 -1
  121. package/lib/core/helpers/vault-version.js +2 -0
  122. package/lib/core/helpers/vaults.d.ts +9 -1
  123. package/lib/core/helpers/vaults.js +22 -8
  124. package/lib/core/helpers/web3.d.ts +154 -2
  125. package/lib/core/helpers/web3.js +192 -15
  126. package/lib/core/logger/index.d.ts +55 -0
  127. package/lib/core/logger/index.js +19 -0
  128. package/lib/core/logger/slack.d.ts +3 -0
  129. package/lib/core/logger/slack.js +3 -0
  130. package/lib/core/vault-metadata.d.ts +7 -1
  131. package/lib/core/vault-metadata.js +6 -0
  132. package/lib/core/version-check.d.ts +52 -0
  133. package/lib/core/version-check.js +82 -1
  134. package/lib/evm/methods/crossChainVault.d.ts +90 -0
  135. package/lib/evm/methods/crossChainVault.js +187 -2
  136. package/lib/evm/methods/crossChainVaultRegistry.d.ts +93 -0
  137. package/lib/evm/methods/crossChainVaultRegistry.js +240 -0
  138. package/lib/evm/methods/index.d.ts +1 -0
  139. package/lib/evm/methods/index.js +1 -0
  140. package/lib/evm/types/crossChain.d.ts +202 -0
  141. package/lib/evm/types/crossChain.js +12 -1
  142. package/lib/index.d.ts +16 -0
  143. package/lib/index.js +19 -0
  144. package/lib/main.d.ts +289 -6
  145. package/lib/main.js +312 -0
  146. package/lib/modules/api/index.d.ts +1 -0
  147. package/lib/modules/api/index.js +6 -0
  148. package/lib/modules/api/main.d.ts +52 -0
  149. package/lib/modules/api/main.js +130 -0
  150. package/lib/modules/sub-accounts/fetcher.d.ts +17 -2
  151. package/lib/modules/sub-accounts/fetcher.js +15 -0
  152. package/lib/modules/sub-accounts/main.d.ts +35 -2
  153. package/lib/modules/sub-accounts/main.js +39 -1
  154. package/lib/modules/sub-accounts/utils.d.ts +4 -1
  155. package/lib/modules/sub-accounts/utils.js +3 -0
  156. package/lib/modules/vaults/adapter.helpers.d.ts +21 -3
  157. package/lib/modules/vaults/adapter.helpers.js +34 -0
  158. package/lib/modules/vaults/fetcher.d.ts +45 -0
  159. package/lib/modules/vaults/fetcher.js +91 -4
  160. package/lib/modules/vaults/getters.d.ts +295 -0
  161. package/lib/modules/vaults/getters.js +567 -26
  162. package/lib/modules/vaults/index.d.ts +12 -0
  163. package/lib/modules/vaults/index.js +12 -0
  164. package/lib/modules/vaults/main.d.ts +292 -4
  165. package/lib/modules/vaults/main.js +382 -8
  166. package/lib/modules/vaults/read.actions.d.ts +170 -2
  167. package/lib/modules/vaults/read.actions.js +143 -0
  168. package/lib/modules/vaults/types.d.ts +34 -0
  169. package/lib/modules/vaults/utils/call-data-decoder.d.ts +48 -1
  170. package/lib/modules/vaults/utils/call-data-decoder.js +56 -0
  171. package/lib/modules/vaults/utils/date-utils.d.ts +39 -0
  172. package/lib/modules/vaults/utils/date-utils.js +47 -1
  173. package/lib/modules/vaults/utils.d.ts +85 -0
  174. package/lib/modules/vaults/utils.js +139 -7
  175. package/lib/modules/vaults/write.actions.d.ts +363 -3
  176. package/lib/modules/vaults/write.actions.js +399 -3
  177. package/lib/polyfills.js +4 -2
  178. package/lib/sdk.d.ts +23847 -0
  179. package/lib/services/coingecko/fetcher.d.ts +15 -1
  180. package/lib/services/coingecko/fetcher.js +30 -9
  181. package/lib/services/debank/fetcher.d.ts +15 -1
  182. package/lib/services/debank/fetcher.js +12 -0
  183. package/lib/services/debank/utils.d.ts +1 -1
  184. package/lib/services/debank/utils.js +18 -1
  185. package/lib/services/layerzero/deposits.d.ts +12 -1
  186. package/lib/services/layerzero/deposits.js +34 -11
  187. package/lib/services/layerzero/redeems.d.ts +11 -1
  188. package/lib/services/layerzero/redeems.js +13 -0
  189. package/lib/services/layerzero/utils.d.ts +8 -0
  190. package/lib/services/layerzero/utils.js +11 -0
  191. package/lib/services/octavfi/fetcher.d.ts +8 -1
  192. package/lib/services/octavfi/fetcher.js +25 -0
  193. package/lib/services/octavfi/types.d.ts +1 -1
  194. package/lib/services/octavfi/utils.d.ts +14 -2
  195. package/lib/services/octavfi/utils.js +44 -10
  196. package/lib/services/subgraph/fetcher.js +4 -2
  197. package/lib/services/subgraph/vaults.d.ts +12 -0
  198. package/lib/services/subgraph/vaults.js +119 -2
  199. package/lib/services/swap-quotes/index.d.ts +80 -0
  200. package/lib/services/swap-quotes/index.js +27 -1
  201. package/lib/services/swap-quotes/paraswap.d.ts +25 -0
  202. package/lib/services/swap-quotes/paraswap.js +22 -0
  203. package/lib/types/pools.d.ts +5 -2
  204. package/lib/types/staking.d.ts +1 -1
  205. package/lib/types/sub-accounts.d.ts +1 -1
  206. package/lib/types/subgraph.d.ts +10 -1
  207. package/lib/types/typed-contract.d.ts +64 -0
  208. package/lib/types/vaults.d.ts +163 -2
  209. package/lib/types/vaults.js +10 -0
  210. package/lib/types/web3.d.ts +9 -1
  211. package/lib/types/web3.js +1 -0
  212. package/lib/types/webserver.d.ts +47 -2
  213. 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,22 +595,26 @@ 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;
521
606
  }
522
- let month;
523
- let day;
524
- let year;
607
+ let month = 0;
608
+ let day = 0;
609
+ let year = 0;
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,17 +924,21 @@ 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))];
813
933
  const blockResults = await Promise.all(uniqueBlocks.map((bn) => provider.getBlock(bn)));
814
- blockTimestamps = new Map();
934
+ const timestamps = new Map();
815
935
  blockResults.forEach((block) => {
816
936
  if (block)
817
- blockTimestamps.set(block.number, block.timestamp);
937
+ timestamps.set(block.number, block.timestamp);
818
938
  });
939
+ blockTimestamps = timestamps;
819
940
  }
941
+ // format — iterate logs directly to preserve transactionHash
820
942
  const redemptions = [];
821
943
  logs.forEach((log) => {
822
944
  const ev = iface.parseLog(log);
@@ -831,7 +953,7 @@ async function getVaultRedemptionHistory({ vault, wallet, lookbackBlocks, option
831
953
  receiver: receiverAddr,
832
954
  amount: (0, core_1.toNormalizedBn)(assetsAmount, decimals),
833
955
  processed: blockDate,
834
- requested: new Date(0),
956
+ requested: new Date(0), // evm-2 event has no requestedOn field; epoch 0 is a sentinel for "unknown"
835
957
  vault,
836
958
  transactionHash: log.transactionHash,
837
959
  };
@@ -860,16 +982,25 @@ async function getVaultRedemptionHistory({ vault, wallet, lookbackBlocks, option
860
982
  return redemptions;
861
983
  }
862
984
  catch (e) {
985
+ // Log loudly and re-throw. Previously this caught all errors and returned
986
+ // [], which silently corrupted callers (notably getWithdrawalRequestsWithStatus,
987
+ // which would then report `ready_to_claim` for already-processed requests).
988
+ // Returning [] is reserved for the explicit early returns inside the try
989
+ // (unsupported chains, vault not in backend) — not for unexpected failures.
863
990
  core_1.Logger.log.error('getVaultRedemptionHistory', e, { vault, wallet });
864
991
  throw e;
865
992
  }
866
993
  }
994
+ /**
995
+ * Vault Positions
996
+ */
867
997
  async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, options, }) {
868
998
  try {
869
999
  const tokenizedVaults = await (0, core_1.fetchTokenizedVault)(vault ? vault : undefined, options?.headers, false, false);
870
1000
  if (!tokenizedVaults || tokenizedVaults.length === 0) {
871
1001
  return [];
872
1002
  }
1003
+ // check if there are redemptions available or if you stake in this pool
873
1004
  function renderStatus(_redemptions, _balance) {
874
1005
  if (_redemptions?.length)
875
1006
  return 'REDEEM';
@@ -879,10 +1010,18 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
879
1010
  }
880
1011
  const promises = await Promise.all(tokenizedVaults.map(async (v) => {
881
1012
  try {
1013
+ // @solana: skip if not solana address
882
1014
  if (utils_2.SolanaUtils.isSolanaAddress(v.address)) {
1015
+ // Default decimals: backend metadata, then SDK fallback. The mint's
1016
+ // own decimals (read below) override this once we have a real
1017
+ // tokenAmount response.
883
1018
  const fallbackShareDecimals = v.solana_vault_metadata?.deposit_token_decimals ??
884
1019
  SolanaConstants.fallbackDecimals;
885
1020
  let balance = (0, core_1.toNormalizedBn)(0, fallbackShareDecimals);
1021
+ // Defensive: without a Solana adapter we can't fetch on-chain
1022
+ // state. The outer filter in modules/vaults/main.ts already gates
1023
+ // on `this.solanaService`, so this is a belt-and-suspenders guard
1024
+ // for any external caller that constructs `options` by hand.
886
1025
  if (utils_2.SolanaUtils.isSolanaAddress(solanaWallet) &&
887
1026
  options.solanaService) {
888
1027
  const vaultProgramId = utils_2.SolanaUtils.resolveProgramId(String(v.address), v.solana_vault_metadata);
@@ -890,12 +1029,23 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
890
1029
  const vaultState = vaultStateRes?.vaultState;
891
1030
  const { shareMint } = vaultState ?? { shareMint: null };
892
1031
  if (shareMint) {
1032
+ // Always use the raw helper — it returns the on-chain u64
1033
+ // string and the mint's true decimals. The legacy `uiAmount`
1034
+ // (JS number) path lost precision and silently encoded `.raw`
1035
+ // against 18 decimals when fed back through `toNormalizedBn`
1036
+ // without a decimals argument, which broke every downstream
1037
+ // BigInt consumer (redemptions, max actions, allowances).
893
1038
  const raw = await options.solanaService.fetchUserShareBalanceRaw(solanaWallet, shareMint);
894
1039
  const decimals = raw.decimals ?? fallbackShareDecimals;
895
1040
  balance = (0, core_1.toNormalizedBn)(BigInt(raw.amount), decimals);
896
1041
  }
897
1042
  }
898
1043
  return {
1044
+ // Mirror the Stellar branch — return the backend's `v.address`,
1045
+ // not the outer `vault` parameter (which can be undefined for
1046
+ // multi-vault calls). No `as IAddress` cast: Solana base58 is
1047
+ // not 0x-hex; the IAddress typing across non-EVM positions is a
1048
+ // known wart documented in IVaultPosition.
899
1049
  vault: v.address,
900
1050
  status: renderStatus([], balance),
901
1051
  availableRedemptions: [],
@@ -913,6 +1063,9 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
913
1063
  balance = (0, core_1.toNormalizedBn)(BigInt(position.shares), position.decimals);
914
1064
  }
915
1065
  else if (position) {
1066
+ // decimals() read failed: trusting the fallback (7) would
1067
+ // mis-scale the balance for ERC4626 offset vaults (AUGUST-6381).
1068
+ // Show zero rather than a confidently-wrong value.
916
1069
  core_1.Logger.log.warn('getVaultPositions', 'Stellar decimals() unresolved — refusing to scale balance with fallback decimals (showing zero)', { vault: v.address, wallet: stellarWallet });
917
1070
  }
918
1071
  else {
@@ -922,6 +1075,7 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
922
1075
  else if (stellarWallet) {
923
1076
  core_1.Logger.log.warn('getVaultPositions', 'Invalid stellarWallet address format', { vault: v.address, stellarWallet });
924
1077
  }
1078
+ // Stellar vaults have no on-chain redemption queue yet — redeemable is always 0
925
1079
  return {
926
1080
  vault: v.address,
927
1081
  status: renderStatus([], balance),
@@ -931,14 +1085,18 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
931
1085
  walletBalance: balance,
932
1086
  };
933
1087
  }
1088
+ // create provider
934
1089
  const provider = (0, core_1.createProvider)(options.rpcUrl);
1090
+ // version split logic
935
1091
  let decimals;
936
1092
  const version = (0, core_1.getVaultVersionV2)(v);
1093
+ // Use ABI_LENDING_POOL_V2 as the common ABI for vault operations
937
1094
  const vaultContract = (0, core_1.createContract)({
938
1095
  provider,
939
1096
  abi: abis_1.ABI_LENDING_POOL_V2,
940
1097
  address: v.address,
941
1098
  });
1099
+ // get vault metadata and balance based on version
942
1100
  let bal = BigInt(0);
943
1101
  if (version === 'evm-2') {
944
1102
  const receiptAddress = await (0, core_1.getReceiptTokenAddress)(provider, vault);
@@ -959,12 +1117,15 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
959
1117
  }
960
1118
  }
961
1119
  const balance = (0, core_1.toNormalizedBn)(bal, decimals);
1120
+ // check available redemptions
962
1121
  const { availableRedemptions, pendingRedemptions } = await getVaultAvailableRedemptions({
963
1122
  vault: v.address,
964
1123
  wallet,
965
1124
  options,
966
1125
  });
1126
+ // get aggregate redemption amount
967
1127
  const aggregateAvailableRedemptions = availableRedemptions.reduce((acc, curr) => acc + BigInt(curr.amount.raw), BigInt(0));
1128
+ // return obj
968
1129
  return {
969
1130
  vault,
970
1131
  status: renderStatus(availableRedemptions, balance),
@@ -998,10 +1159,14 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
998
1159
  throw new Error(`#getVaultPositions::${vault}:${e?.message}`);
999
1160
  }
1000
1161
  }
1162
+ /**
1163
+ * @deprecated use getVaultHistoricalTimeseries instead
1164
+ */
1001
1165
  async function getVaultApy({ vault, options, historical, }) {
1002
1166
  try {
1003
1167
  if (!vault)
1004
1168
  throw new Error('Vault input parameter is undefined.');
1169
+ // only looking for current apy
1005
1170
  if (!historical) {
1006
1171
  const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vault, options?.headers))?.[0];
1007
1172
  return [
@@ -1012,6 +1177,7 @@ async function getVaultApy({ vault, options, historical, }) {
1012
1177
  ];
1013
1178
  }
1014
1179
  else {
1180
+ // historical apy
1015
1181
  function paramBuilder(params, baseUrl) {
1016
1182
  if (!params)
1017
1183
  return '';
@@ -1025,7 +1191,7 @@ async function getVaultApy({ vault, options, historical, }) {
1025
1191
  const query = urlParams.toString();
1026
1192
  if (!query)
1027
1193
  return '';
1028
- return baseUrl && baseUrl.includes('?') ? `&${query}` : `?${query}`;
1194
+ return baseUrl?.includes('?') ? `&${query}` : `?${query}`;
1029
1195
  }
1030
1196
  const baseUrl = core_1.WEBSERVER_ENDPOINTS.public.tokenizedVault.historicalApy(vault);
1031
1197
  const historicalApy = await (0, core_1.fetchAugustPublic)(`${baseUrl}${paramBuilder(historical, baseUrl)}`, {
@@ -1051,15 +1217,16 @@ async function getVaultApy({ vault, options, historical, }) {
1051
1217
  }
1052
1218
  }
1053
1219
  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';
1220
+ const REWARDS_CHAIN = 43114; // avalanche
1221
+ const REWARDS_SYMBOL = 'AVAX'; // avalanche
1222
+ const REWARDS_DECIMALS = 18; // avalanche
1223
+ const REWARDS_NAME = 'Avalanche'; // avalanche
1058
1224
  const APR_MULTIPLIER = 31536000;
1059
1225
  const UP_AUSD_SYMBOL = 'upAUSD';
1060
1226
  try {
1061
1227
  const provider = (0, core_1.createProvider)(rpcUrl);
1062
1228
  const chainId = await (0, core_1.getChainId)(provider);
1229
+ // Validate chain has rewards distributor
1063
1230
  if (chainId !== REWARDS_CHAIN) {
1064
1231
  core_1.Logger.log.warn('getStakingPositions:unsupported_chain', chainId);
1065
1232
  return [];
@@ -1068,15 +1235,19 @@ async function getRewardsStakingPositions({ rpcUrl, wallet, coinGeckoKey, }) {
1068
1235
  core_1.Logger.log.warn('getStakingPositions:invalid_wallet', wallet);
1069
1236
  return [];
1070
1237
  }
1238
+ // get reward distributor contract addresses
1071
1239
  const rewardDistributorAddresses = (0, core_1.REWARD_DISTRIBUTOR_ADDRESS)(chainId);
1240
+ // loop through each reward distributor contract address
1072
1241
  const positions = await Promise.all(rewardDistributorAddresses.map(async (contract, i) => {
1073
1242
  const rewardContract = (0, core_1.createContract)({
1074
1243
  address: contract,
1075
1244
  provider: provider,
1076
1245
  abi: abis_1.ABI_REWARD_DISTRIBUTOR,
1077
1246
  });
1247
+ // get reward distributor metadata
1078
1248
  const totalStaked = await rewardContract.totalStaked();
1079
1249
  const rewardsPerSecond = await rewardContract.rewardsPerSecond();
1250
+ // get staking token metadata
1080
1251
  const stakingTokenCalls = ['decimals', 'symbol', 'name', 'totalSupply'];
1081
1252
  const stakingTokenAddress = (await rewardContract.stakingToken());
1082
1253
  const stakingTokenContract = (0, core_1.createContract)({
@@ -1085,6 +1256,7 @@ async function getRewardsStakingPositions({ rpcUrl, wallet, coinGeckoKey, }) {
1085
1256
  abi: abis_1.ABI_ERC20,
1086
1257
  });
1087
1258
  const [decimals, symbol, name] = await Promise.all(stakingTokenCalls.map((staking) => stakingTokenContract[staking]()));
1259
+ // if wallet address is passed, pull user balance of staked tokens
1088
1260
  let balance;
1089
1261
  let earned;
1090
1262
  if (wallet) {
@@ -1092,12 +1264,15 @@ async function getRewardsStakingPositions({ rpcUrl, wallet, coinGeckoKey, }) {
1092
1264
  balance = await rewardContract.balanceOf(formattedWallet);
1093
1265
  earned = await rewardContract.earned(formattedWallet);
1094
1266
  }
1267
+ // get appropriate token price
1095
1268
  const rewardTokenPriceInUsd = await (0, core_1.fetchTokenPrice)(REWARDS_SYMBOL, null, coinGeckoKey);
1096
1269
  const normalizedRewardTokenPrice = (0, core_1.toNormalizedBn)(rewardTokenPriceInUsd);
1270
+ // normalize numbers
1097
1271
  const normalizedTotalStakedInPool = (0, core_1.toNormalizedBn)(totalStaked, decimals);
1098
1272
  const normalizedRedeemable = (0, core_1.toNormalizedBn)(earned, REWARDS_DECIMALS);
1099
1273
  const normalizedRewardsPerSecond = (0, core_1.toNormalizedBn)(rewardsPerSecond, REWARDS_DECIMALS);
1100
1274
  const normalizedTotalStakedBalance = (0, core_1.toNormalizedBn)(balance, decimals);
1275
+ // calculate APR's
1101
1276
  const rewardTokenPriceToMultiply = symbol === UP_AUSD_SYMBOL
1102
1277
  ? Number(normalizedRewardTokenPrice?.normalized)
1103
1278
  : 1;
@@ -1151,12 +1326,15 @@ async function getVaultTvl({ vault, options, historical, }) {
1151
1326
  try {
1152
1327
  if (!vault)
1153
1328
  throw new Error('Vault input parameter is undefined.');
1329
+ // Verify vault exists
1154
1330
  const _vaultExists = (await (0, core_1.fetchTokenizedVault)(vault))?.[0];
1155
1331
  if (!_vaultExists) {
1156
1332
  throw new Error(`Vault ${vault} not found in backend`);
1157
1333
  }
1334
+ // only wanting current APR
1158
1335
  if (!historical) {
1159
1336
  const version = (0, core_1.getVaultVersionV2)(_vaultExists);
1337
+ // Non-EVM early returns (no provider needed)
1160
1338
  if (version === 'sol-0') {
1161
1339
  return [
1162
1340
  {
@@ -1180,15 +1358,15 @@ async function getVaultTvl({ vault, options, historical, }) {
1180
1358
  case 'evm-2': {
1181
1359
  const receiptTokenAddress = await (0, core_1.getReceiptTokenAddress)(provider, vault);
1182
1360
  const receiptTokenDecimals = await (0, core_1.getDecimals)(provider, receiptTokenAddress, false);
1183
- const receiptTokenContract = (0, core_1.createContract)({
1184
- address: receiptTokenAddress,
1185
- abi: TokenizedVaultV2Receipt_1.ABI_TOKENIZED_VAULT_V2_RECEIPT,
1361
+ const vaultContract = (0, core_1.createContract)({
1362
+ address: vault,
1363
+ abi: TokenizedVaultV2_1.ABI_TOKENIZED_VAULT_V2,
1186
1364
  provider,
1187
1365
  });
1188
- const totalSupply = await receiptTokenContract.totalSupply();
1366
+ const totalAssets = await vaultContract.getTotalAssets();
1189
1367
  return [
1190
1368
  {
1191
- value: (0, core_1.toNormalizedBn)(totalSupply, receiptTokenDecimals),
1369
+ value: (0, core_1.toNormalizedBn)(totalAssets, receiptTokenDecimals),
1192
1370
  timestamp: new Date().toISOString(),
1193
1371
  },
1194
1372
  ];
@@ -1200,26 +1378,42 @@ async function getVaultTvl({ vault, options, historical, }) {
1200
1378
  provider,
1201
1379
  });
1202
1380
  const decimals = await (0, core_1.getDecimals)(provider, vault);
1203
- const totalSupply = await vaultContract.totalSupply();
1381
+ const totalAssets = await vaultContract.totalAssets();
1204
1382
  return [
1205
1383
  {
1206
- value: (0, core_1.toNormalizedBn)(totalSupply, decimals),
1384
+ value: (0, core_1.toNormalizedBn)(totalAssets, decimals),
1207
1385
  timestamp: new Date().toISOString(),
1208
1386
  },
1209
1387
  ];
1210
1388
  }
1211
1389
  }
1390
+ // const vaultRes = await getVault({
1391
+ // vault,
1392
+ // options,
1393
+ // loans: false,
1394
+ // allocations: false,
1395
+ // });
1396
+ // const returnObj = [
1397
+ // { value: vaultRes.totalSupply, timestamp: new Date().toISOString() },
1398
+ // ];
1399
+ // Logger.log.info('getVaultTvl:current', returnObj);
1400
+ // return returnObj;
1212
1401
  }
1213
1402
  else {
1403
+ // historical tvl
1404
+ // check vault version early to guard non-EVM chains
1214
1405
  const version = (0, core_1.getVaultVersionV2)(_vaultExists);
1215
1406
  if (version === 'stellar-0' || version === 'sol-0') {
1216
1407
  core_1.Logger.log.warn('getVaultTvl:historical', 'Historical TVL not supported for non-EVM vaults', { vault, version });
1217
1408
  return [];
1218
1409
  }
1410
+ // if no order is passed but inputParams are passed
1219
1411
  if (typeof historical !== 'undefined' && !historical.order)
1220
1412
  historical.order = 'desc';
1413
+ // days is default interval
1221
1414
  if (typeof historical !== 'undefined' && !historical.interval)
1222
1415
  historical.interval = 'days';
1416
+ // setup
1223
1417
  const provider = (0, core_1.createProvider)(options.rpcUrl);
1224
1418
  const vaultContract = (0, core_1.createContract)({
1225
1419
  address: vault,
@@ -1244,6 +1438,8 @@ async function getVaultTvl({ vault, options, historical, }) {
1244
1438
  break;
1245
1439
  }
1246
1440
  }
1441
+ // block trackback setup
1442
+ // Get the latest finalized block to ensure all queried blocks are confirmed
1247
1443
  const finalizedBlock = await provider.getBlock('finalized');
1248
1444
  const finalizedTimestamp = finalizedBlock
1249
1445
  ? finalizedBlock.timestamp * 1000
@@ -1253,19 +1449,26 @@ async function getVaultTvl({ vault, options, historical, }) {
1253
1449
  const startDate = historical.daysAgo
1254
1450
  ? new Date(finalizedTimestamp - daysAgo)
1255
1451
  : new Date(finalizedTimestamp);
1256
- const blocks = await dater.getEvery(historical.interval, startDate.toUTCString(), new Date(finalizedTimestamp).toUTCString());
1452
+ const blocks = await dater.getEvery(historical.interval, // Period
1453
+ startDate.toUTCString(), // Start date
1454
+ new Date(finalizedTimestamp).toUTCString());
1455
+ // order blocks array
1257
1456
  const orderedBlocks = historical.order === 'desc' ? (0, core_1.orderObjArrByDate)(blocks) : blocks;
1457
+ // minimal ABI
1258
1458
  const totalAssetsHistorical = await (0, core_1.promiseSettle)(orderedBlocks?.map(async ({ block, date }) => {
1459
+ // get totalAssets at said block
1259
1460
  const totalAssetsAtBlock = await provider.call({
1260
1461
  to: vaultContract,
1261
1462
  data: new ethers_1.Interface(minAbi).encodeFunctionData(functionName, []),
1262
1463
  blockTag: block,
1263
1464
  });
1465
+ // cover case when data returned is 0
1264
1466
  if (totalAssetsAtBlock === '0x')
1265
1467
  return {
1266
1468
  timestamp: (0, core_1.dateToUnix)(new Date(date)),
1267
1469
  value: (0, core_1.toNormalizedBn)(0, 0),
1268
1470
  };
1471
+ // decode back to readable
1269
1472
  const readableTotalAssetsAtBlock = new ethers_1.Interface(minAbi).decodeFunctionResult(functionName, totalAssetsAtBlock);
1270
1473
  return {
1271
1474
  timestamp: (0, core_1.dateToUnix)(new Date(date)),
@@ -1281,9 +1484,27 @@ async function getVaultTvl({ vault, options, historical, }) {
1281
1484
  throw new Error(`Failed to fetch TVL for ${vault}: ${e instanceof Error ? e.message : 'Unknown error'}`);
1282
1485
  }
1283
1486
  }
1487
+ /**
1488
+ * Build the per-loan borrower / health-factor list for a single vault.
1489
+ *
1490
+ * Resilience: individual loan reads (`loanState()`, `borrower()`) and the
1491
+ * per-borrower August backend call are isolated with `Promise.allSettled` and
1492
+ * try/catch so one bad loan — a test loan whose address isn't a real contract,
1493
+ * a borrower the backend doesn't recognize, an intermittent RPC error — drops
1494
+ * that single row from the result instead of rejecting the whole batch. Failed
1495
+ * rows are logged via `Logger.log.warn`; the loan is still returned with
1496
+ * `health_factor: undefined` so callers can render an explicit empty state
1497
+ * rather than a perpetual loading skeleton.
1498
+ *
1499
+ * @param vault - Vault address to scope the fetch to.
1500
+ * @param options - Standard vault options — `rpcUrl` must point at `vault`'s chain.
1501
+ * @returns Array of `IVaultBorrowerHealthFactor`, one entry per active loan.
1502
+ */
1284
1503
  async function getVaultBorrowerHealthFactor({ vault, options, }) {
1285
1504
  const provider = (0, core_1.createProvider)(options.rpcUrl);
1286
1505
  const loans = await getVaultLoans(vault, options);
1506
+ // Read each loan's on-chain state; drop loans whose RPC call fails so a
1507
+ // single bad contract (e.g. a test loan address) can't sink the batch.
1287
1508
  const activeLoanResults = await Promise.allSettled(loans.map(async (l) => {
1288
1509
  const loanContract = (0, core_1.createContract)({
1289
1510
  provider,
@@ -1306,6 +1527,7 @@ async function getVaultBorrowerHealthFactor({ vault, options, }) {
1306
1527
  return undefined;
1307
1528
  })
1308
1529
  .filter((l) => l !== undefined);
1530
+ // Same fault-tolerance for the per-loan borrower() read.
1309
1531
  const formattedSettled = await Promise.allSettled(activeLoans.map(async (l) => {
1310
1532
  const loanContract = (0, core_1.createContract)({
1311
1533
  provider,
@@ -1351,6 +1573,26 @@ async function getVaultBorrowerHealthFactor({ vault, options, }) {
1351
1573
  });
1352
1574
  return formattedLoansArray;
1353
1575
  }
1576
+ /**
1577
+ * Aggregate borrower-health-factor data, optionally scoped to a single vault.
1578
+ *
1579
+ * - When `vault` is provided, only that vault's tokenized record is fetched
1580
+ * and only its loans are read. This is the path callers should use when
1581
+ * they already know which vault they care about — it skips the cross-chain
1582
+ * fanout entirely.
1583
+ * - When `vault` is omitted, the function fetches every tokenized vault and
1584
+ * builds health factors for each, preserving the legacy behavior. Per-vault
1585
+ * failures are isolated with `Promise.allSettled` so one bad vault returns
1586
+ * an empty array instead of rejecting the whole map.
1587
+ *
1588
+ * The returned map is keyed by **lowercased** vault address so callers can
1589
+ * look up entries without worrying about EIP-55 checksum casing in either the
1590
+ * backend response or the caller's input.
1591
+ *
1592
+ * @param options - Standard vault options — `rpcUrl` should match `vault`'s chain.
1593
+ * @param vault - Optional vault address to scope the fetch to.
1594
+ * @returns Map of lowercased vault address → array of borrower-health-factor rows.
1595
+ */
1354
1596
  async function getHealthFactorOfBorrowersByVault({ options, vault, }) {
1355
1597
  const vaults = vault
1356
1598
  ? ((await (0, core_1.fetchTokenizedVault)(vault, options?.headers)) ?? [])
@@ -1376,6 +1618,13 @@ async function getHealthFactorOfBorrowersByVault({ options, vault, }) {
1376
1618
  });
1377
1619
  return healthFactorsByPool;
1378
1620
  }
1621
+ /**
1622
+ * Fetch user points from the backend API endpoint.
1623
+ * This replaces client-side points calculation with server-side processing.
1624
+ * @param userAddress - User wallet address (EVM or Solana)
1625
+ * @param options - Request options including headers
1626
+ * @returns Points data from the backend API
1627
+ */
1379
1628
  async function getUserPoints({ userAddress, options, }) {
1380
1629
  try {
1381
1630
  if (!userAddress) {
@@ -1397,6 +1646,62 @@ async function getUserPoints({ userAddress, options, }) {
1397
1646
  throw new Error(`#getUserPoints::${userAddress}:${errorMessage}`);
1398
1647
  }
1399
1648
  }
1649
+ /**
1650
+ * Register a user for the points program, authenticated by a wallet signature.
1651
+ *
1652
+ * The caller must obtain a personal_sign (EIP-191) signature over the
1653
+ * canonical message template below — the backend reconstructs the same
1654
+ * string from these fields and verifies the signature recovers to
1655
+ * `userAddress`. Building the message client-side and server-side from the
1656
+ * same primitives means a hostile client cannot show one message in the
1657
+ * wallet prompt while submitting a different one for validation.
1658
+ *
1659
+ * Canonical message (`\n`-separated, no trailing newline):
1660
+ *
1661
+ * ```
1662
+ * Upshift: register {userAddress.toLowerCase()}
1663
+ * referrer: {referrerAddress.toLowerCase() || "none"}
1664
+ * chain: {chainId}
1665
+ * nonce: {nonce}
1666
+ * expires: {expiry}
1667
+ * ```
1668
+ *
1669
+ * @param userAddress - EVM wallet address being registered. Must match the
1670
+ * signer that produced `signature`. Lowercased before being embedded in
1671
+ * the canonical message.
1672
+ * @param referrerAddress - Optional EVM address that referred this user.
1673
+ * Lowercased when embedded; the literal string `"none"` is used when
1674
+ * absent. Tampering with this field after signing produces 401.
1675
+ * @param chainId - Chain on which the wallet signed. For smart-contract wallets
1676
+ * (Safe etc.) this is the chain whose RPC the backend will use to run the
1677
+ * EIP-1271 `isValidSignature` check; for EOAs it still pins the signature
1678
+ * to a chain so it can't be replayed cross-chain. Must be one of the chains
1679
+ * Upshift supports (see backend `SUPPORTED_REGISTRATION_CHAINS`).
1680
+ * @param signature - `0x`-prefixed hex of the personal_sign signature over
1681
+ * the canonical message. Single-use: replays within ~10 min are rejected.
1682
+ * @param nonce - Caller-generated random string (8–128 chars). Pair with
1683
+ * `userAddress` to form the single-use key in the backend's nonce store.
1684
+ * @param expiry - Unix timestamp (seconds). Must be in the future and within
1685
+ * the backend's TTL window (currently 10 min).
1686
+ * @param options - Request options (headers).
1687
+ * @returns Raw `Response` from the backend. 200 on success, 401 for
1688
+ * expired / replayed / mismatched-signer / tampered-referrer, 422 for an
1689
+ * unsupported `chainId`.
1690
+ *
1691
+ * @example
1692
+ * ```ts
1693
+ * const nonce = crypto.randomUUID().replace(/-/g, '');
1694
+ * const expiry = Math.floor(Date.now() / 1000) + 300;
1695
+ * const message =
1696
+ * `Upshift: register ${address.toLowerCase()}\n` +
1697
+ * `referrer: ${referrer?.toLowerCase() ?? 'none'}\n` +
1698
+ * `chain: ${chainId}\n` +
1699
+ * `nonce: ${nonce}\n` +
1700
+ * `expires: ${expiry}`;
1701
+ * const signature = await walletClient.signMessage({ account: address, message });
1702
+ * await registerUserForPoints({ userAddress: address, referrerAddress: referrer, chainId, signature, nonce, expiry });
1703
+ * ```
1704
+ */
1400
1705
  async function registerUserForPoints({ userAddress, referrerAddress, chainId, signature, nonce, expiry, options, }) {
1401
1706
  try {
1402
1707
  if (!userAddress) {
@@ -1429,6 +1734,12 @@ async function registerUserForPoints({ userAddress, referrerAddress, chainId, si
1429
1734
  throw new Error(`#registerUserForPoints::${userAddress}:${errorMessage}`);
1430
1735
  }
1431
1736
  }
1737
+ /**
1738
+ * Fetch the points leaderboard data.
1739
+ * @param params - Optional parameters for pagination and sorting
1740
+ * @param options - Request options including headers
1741
+ * @returns Leaderboard response data
1742
+ */
1432
1743
  async function fetchPointsLeaderboard({ params, options, }) {
1433
1744
  try {
1434
1745
  const { page = 1, perPage = 10, sortBy = 'totalPoints' } = params || {};
@@ -1451,6 +1762,13 @@ async function fetchPointsLeaderboard({ params, options, }) {
1451
1762
  throw new Error(`#fetchPointsLeaderboard::${errorMessage}`);
1452
1763
  }
1453
1764
  }
1765
+ /**
1766
+ * Fetch the timestamp when yield was last realized for a vault.
1767
+ * Returns the assetsUpdatedOn timestamp from the vault contract.
1768
+ * @param vault - Vault contract address
1769
+ * @param options - RPC configuration
1770
+ * @returns Timestamp (Unix timestamp in seconds) when yield was last realized
1771
+ */
1454
1772
  async function getYieldLastRealizedOn({ vault, options, }) {
1455
1773
  try {
1456
1774
  const provider = (0, core_1.createProvider)(options.rpcUrl);
@@ -1484,6 +1802,8 @@ async function getYieldLastRealizedOn({ vault, options, }) {
1484
1802
  return 0;
1485
1803
  }
1486
1804
  default: {
1805
+ // Old lending pools don't have assetsUpdatedOn function
1806
+ // Return 0 for legacy vaults
1487
1807
  return 0;
1488
1808
  }
1489
1809
  }
@@ -1495,6 +1815,19 @@ async function getYieldLastRealizedOn({ vault, options, }) {
1495
1815
  throw new Error(`#getYieldLastRealizedOn::${vault}:${errorMessage}`);
1496
1816
  }
1497
1817
  }
1818
+ /**
1819
+ * Calculate lifetime PnL for a user in a specific vault.
1820
+ *
1821
+ * Basic logic:
1822
+ * 1. Get user's list of deposits and withdrawals from subgraph
1823
+ * 2. Fetch user's current assets (balanceOf * sharePrice via convertToAssets)
1824
+ * 3. Calculate PnL = assets withdrawn + current assets - assets deposited
1825
+ *
1826
+ * @param vault - Vault contract address
1827
+ * @param wallet - User wallet address
1828
+ * @param options - RPC configuration and service options
1829
+ * @returns Lifetime PnL data in both native token and USD
1830
+ */
1498
1831
  async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1499
1832
  try {
1500
1833
  if (!vault)
@@ -1507,6 +1840,7 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1507
1840
  if (!(0, ethers_1.isAddress)(wallet))
1508
1841
  throw new Error(`Wallet input parameter is not an address: ${String(wallet)}`);
1509
1842
  const provider = (0, core_1.createProvider)(options.rpcUrl);
1843
+ // Parallelize independent operations
1510
1844
  const [decimals, userHistory, positions, tokenizedVaultResult] = await Promise.all([
1511
1845
  (0, core_1.getDecimals)(provider, vault),
1512
1846
  (0, vaults_1.getSubgraphUserHistory)(wallet, provider, vault),
@@ -1521,6 +1855,7 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1521
1855
  if (!tokenizedVault) {
1522
1856
  throw new Error(`Vault ${vault} not found`);
1523
1857
  }
1858
+ // Fetch LayerZero deposits/redeems for supported vaults
1524
1859
  const lzVaultKey = (0, deposits_1.isLayerZeroVault)(vault);
1525
1860
  let lzDeposits = [];
1526
1861
  let lzRedeems = [];
@@ -1534,6 +1869,7 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1534
1869
  core_1.Logger.log.warn('getVaultUserLifetimePnl:lzDeposits', `Failed to fetch LZ deposits for ${lzVaultKey}: ${e}`);
1535
1870
  }),
1536
1871
  ];
1872
+ // Only earnAUSD has redeem events currently
1537
1873
  if (lzVaultKey === 'earnAUSD') {
1538
1874
  lzPromises.push((0, redeems_1.queryLayerZeroRedeems)(wallet)
1539
1875
  .then((r) => {
@@ -1547,10 +1883,12 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1547
1883
  }
1548
1884
  const version = (0, core_1.getVaultVersionV2)(tokenizedVault);
1549
1885
  const currentPosition = positions.find((pos) => pos.vault?.toLowerCase() === vault.toLowerCase());
1886
+ // Start underlyingSymbol fetch (runs in parallel with processHistory)
1550
1887
  const underlyingSymbolPromise = (async () => {
1551
1888
  let underlyingTokenSymbol = 'UNKNOWN';
1552
1889
  let underlyingAssetAddress;
1553
1890
  try {
1891
+ // Use ABI_LENDING_POOL_V2 which has the asset() method
1554
1892
  const vaultContract = (0, core_1.createContract)({
1555
1893
  provider,
1556
1894
  address: vault,
@@ -1578,15 +1916,22 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1578
1916
  }
1579
1917
  return { underlyingTokenSymbol, underlyingAssetAddress };
1580
1918
  })();
1919
+ // Process history - count all withdrawal requests (both processed and pending)
1920
+ // We don't count processed withdrawals separately to avoid double counting
1921
+ // Group deposit amounts by asset address to handle different decimals
1581
1922
  const depositsByAsset = new Map();
1923
+ // Separate withdrawal tracking: shares need conversion, assets don't
1582
1924
  let totalWithdrawalsInShares = BigInt(0);
1583
1925
  let totalWithdrawalsInAssets = BigInt(0);
1584
1926
  for (const historyItem of userHistory) {
1585
1927
  const amount = BigInt(historyItem.amount);
1928
+ // ignore bad transactions when calculating pnl
1586
1929
  if ((0, core_1.isBadTransaction)(historyItem.transactionHash_, tokenizedVault.chain)) {
1587
1930
  continue;
1588
1931
  }
1589
1932
  if (historyItem.type === 'deposit') {
1933
+ // Use assetIn if available (V2 vaults), otherwise use a default key
1934
+ // Use proper type guard instead of type assertion
1590
1935
  const assetKey = 'assetIn' in historyItem && typeof historyItem.assetIn === 'string'
1591
1936
  ? historyItem.assetIn.toLowerCase()
1592
1937
  : 'default';
@@ -1594,15 +1939,21 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1594
1939
  depositsByAsset.set(assetKey, current + amount);
1595
1940
  }
1596
1941
  else if (historyItem.type === 'withdraw-request') {
1942
+ // Withdraw requests are in shares, need conversion to assets
1597
1943
  totalWithdrawalsInShares += amount;
1598
1944
  }
1599
1945
  else if (historyItem.type === 'withdraw-processed' &&
1600
1946
  historyItem.isInstant) {
1947
+ // Instant withdrawals are already in assets, no conversion needed
1601
1948
  totalWithdrawalsInAssets += amount;
1602
1949
  }
1603
1950
  }
1951
+ // Include LayerZero cross-chain deposits and redeems
1604
1952
  if (lzVaultKey && lzDeposits.length > 0) {
1605
1953
  for (const lzDeposit of lzDeposits) {
1954
+ // LZ deposits assetAmt is the underlying asset amount deposited in hub-chain decimals.
1955
+ // This assumes the LZ subgraph indexes amounts in the same decimals as the hub vault
1956
+ // (e.g. 6 for USDC). If source/hub decimals diverge, normalization would be needed here.
1606
1957
  const assetKey = 'lz-default';
1607
1958
  const amount = BigInt(lzDeposit.assetAmt);
1608
1959
  const current = depositsByAsset.get(assetKey) ?? BigInt(0);
@@ -1611,9 +1962,11 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1611
1962
  }
1612
1963
  if (lzVaultKey && lzRedeems.length > 0) {
1613
1964
  for (const lzRedeem of lzRedeems) {
1965
+ // LZ redeems shareAmt is in shares, needs conversion via share price
1614
1966
  totalWithdrawalsInShares += BigInt(lzRedeem.shareAmt);
1615
1967
  }
1616
1968
  }
1969
+ // Fetch decimals for each unique asset in parallel
1617
1970
  const assetDecimals = new Map();
1618
1971
  await Promise.all(Array.from(depositsByAsset.keys()).map(async (assetAddress) => {
1619
1972
  if (assetAddress === 'default' || assetAddress === 'lz-default') {
@@ -1624,15 +1977,18 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1624
1977
  assetDecimals.set(assetAddress, dec);
1625
1978
  }
1626
1979
  }));
1980
+ // Store deposits as a map: assetAddress -> INormalizedNumber (with correct decimals)
1627
1981
  const totalDepositedRaw = new Map();
1628
1982
  for (const [assetAddress, rawAmount] of depositsByAsset) {
1629
1983
  const assetDec = assetDecimals.get(assetAddress) ?? decimals;
1630
1984
  totalDepositedRaw.set(assetAddress, (0, core_1.toNormalizedBn)(rawAmount, assetDec));
1631
1985
  }
1986
+ // Fetch USD prices for each unique deposit asset in parallel
1632
1987
  const assetPrices = new Map();
1633
1988
  if (tokenizedVault.chain) {
1634
1989
  await Promise.all(Array.from(depositsByAsset.keys()).map(async (assetAddress) => {
1635
1990
  if (assetAddress === 'default' || assetAddress === 'lz-default') {
1991
+ // Will use underlying asset price later
1636
1992
  assetPrices.set(assetAddress, 0);
1637
1993
  }
1638
1994
  else {
@@ -1648,6 +2004,7 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1648
2004
  }));
1649
2005
  }
1650
2006
  const { underlyingTokenSymbol, underlyingAssetAddress } = await underlyingSymbolPromise;
2007
+ // Calculate current position value and fetch price in parallel
1651
2008
  let currentPositionValue = (0, core_1.toNormalizedBn)(0, decimals);
1652
2009
  const pricePromise = underlyingAssetAddress && tokenizedVault.chain
1653
2010
  ? (0, core_1.fetchTokenPriceByAddress)(underlyingAssetAddress, tokenizedVault.chain, options?.headers).catch(() => 0)
@@ -1674,7 +2031,10 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1674
2031
  else {
1675
2032
  currentShares = await vaultContract.balanceOf(wallet);
1676
2033
  }
2034
+ // For LayerZero vaults, include receipt token balances on remote chains
1677
2035
  if (lzVaultKey) {
2036
+ // Build list of remote chains to check: prefer receipt_token_integrations from API,
2037
+ // fall back to LAYERZERO_VAULTS spoke chain config (OVault shareOFT addresses)
1678
2038
  const remoteChains = [];
1679
2039
  if (tokenizedVault.receipt_token_integrations?.length) {
1680
2040
  for (const rti of tokenizedVault.receipt_token_integrations) {
@@ -1689,6 +2049,7 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1689
2049
  }
1690
2050
  }
1691
2051
  else {
2052
+ // Fallback: use spoke chain addresses from LAYERZERO_VAULTS config
1692
2053
  const lzVaultConfig = deposits_1.LAYERZERO_VAULTS[lzVaultKey];
1693
2054
  if (lzVaultConfig.spokeChains) {
1694
2055
  for (const [chainIdStr, address] of Object.entries(lzVaultConfig.spokeChains)) {
@@ -1721,6 +2082,7 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1721
2082
  remoteTokenContract.decimals(),
1722
2083
  ]);
1723
2084
  const remDec = Number(remoteDecimals) || decimals;
2085
+ // Normalize remote balance to hub chain decimals
1724
2086
  if (remDec > decimals) {
1725
2087
  return rawBalance / BigInt(10 ** (remDec - decimals));
1726
2088
  }
@@ -1748,7 +2110,7 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1748
2110
  vaultContract.totalSupply(),
1749
2111
  ]);
1750
2112
  if (totalSupply === BigInt(0)) {
1751
- sharePriceRaw = BigInt(10 ** decimals);
2113
+ sharePriceRaw = BigInt(10 ** decimals); // 1:1 if no supply
1752
2114
  }
1753
2115
  else {
1754
2116
  sharePriceRaw = (totalAssets * BigInt(10 ** decimals)) / totalSupply;
@@ -1763,22 +2125,28 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1763
2125
  currentPositionValue = currentPosition.walletBalance;
1764
2126
  }
1765
2127
  }
2128
+ // Calculate totalWithdrawn combining shares (converted) and assets (as-is)
1766
2129
  let totalWithdrawnRaw = totalWithdrawalsInAssets;
1767
2130
  if (version === 'evm-2' && sharePriceRaw > BigInt(0)) {
2131
+ // Only convert shares to assets, instant withdrawals are already in assets
1768
2132
  totalWithdrawnRaw +=
1769
2133
  (totalWithdrawalsInShares * sharePriceRaw) / BigInt(10 ** decimals);
1770
2134
  }
1771
2135
  else {
2136
+ // For non-evm-2 or when share price is 0, treat shares as assets
1772
2137
  totalWithdrawnRaw += totalWithdrawalsInShares;
1773
2138
  }
1774
2139
  const totalWithdrawn = (0, core_1.toNormalizedBn)(totalWithdrawnRaw, decimals);
1775
2140
  const tokenPriceUsd = await pricePromise;
2141
+ // Validate token price and log warning if invalid
1776
2142
  const effectiveTokenPrice = tokenPriceUsd > 0 ? tokenPriceUsd : 1;
1777
2143
  if (tokenPriceUsd <= 0) {
1778
2144
  core_1.Logger.log.warn('getVaultUserLifetimePnl:tokenPrice', `Invalid underlying token price: ${tokenPriceUsd}, using fallback of 1`);
1779
2145
  }
2146
+ // Sum all deposited amounts in USD using each asset's own price
1780
2147
  let totalDepositedUsd = 0;
1781
2148
  for (const [assetAddress, normalizedAmount] of totalDepositedRaw) {
2149
+ // Use the asset-specific price, or underlying price for 'default'
1782
2150
  const assetPrice = assetAddress === 'default' || assetAddress === 'lz-default'
1783
2151
  ? effectiveTokenPrice
1784
2152
  : assetPrices.get(assetAddress) || effectiveTokenPrice;
@@ -1786,14 +2154,18 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1786
2154
  }
1787
2155
  const totalWithdrawnUsd = Number(totalWithdrawn.normalized) * effectiveTokenPrice;
1788
2156
  const currentPositionValueUsd = Number(currentPositionValue.normalized) * effectiveTokenPrice;
2157
+ // Calculate lifetimePnl in USD first (since deposits may be in different tokens with different prices)
1789
2158
  const lifetimePnlUsd = currentPositionValueUsd + totalWithdrawnUsd - totalDepositedUsd;
1790
- const scaleFactor = BigInt(10 ** 18);
2159
+ // Use BigInt-scaled arithmetic to minimize precision loss when converting USD to native
2160
+ const scaleFactor = BigInt(10 ** 18); // High precision scale
1791
2161
  const tokenPriceScaled = BigInt(Math.round(effectiveTokenPrice * Number(scaleFactor)));
2162
+ // Convert totalDepositedUsd to native units for backwards compatibility
1792
2163
  const totalDepositedUsdScaled = BigInt(Math.round(totalDepositedUsd * Number(scaleFactor)));
1793
2164
  const totalDepositedNativeRaw = tokenPriceScaled > BigInt(0)
1794
2165
  ? (totalDepositedUsdScaled * BigInt(10 ** decimals)) / tokenPriceScaled
1795
2166
  : BigInt(0);
1796
2167
  const totalDeposited = (0, core_1.toNormalizedBn)(totalDepositedNativeRaw, decimals);
2168
+ // Convert lifetimePnlUsd to native units
1797
2169
  const lifetimePnlUsdScaled = BigInt(Math.round(lifetimePnlUsd * Number(scaleFactor)));
1798
2170
  const lifetimePnlRaw = tokenPriceScaled > BigInt(0)
1799
2171
  ? (lifetimePnlUsdScaled * BigInt(10 ** decimals)) / tokenPriceScaled
@@ -1821,6 +2193,14 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1821
2193
  throw new Error(`#getVaultUserLifetimePnl::${vault}::${wallet}:${errorMessage}`);
1822
2194
  }
1823
2195
  }
2196
+ /**
2197
+ * Calculate PnL for a vault (vault-level, not user-specific).
2198
+ * Returns the vault's overall profit and loss across all users.
2199
+ *
2200
+ * @param vault - Vault contract address
2201
+ * @param options - RPC configuration and service options
2202
+ * @returns Vault PnL in USD and notional value
2203
+ */
1824
2204
  async function getVaultPnl({ vault, options, }) {
1825
2205
  try {
1826
2206
  if (!vault)
@@ -1828,6 +2208,7 @@ async function getVaultPnl({ vault, options, }) {
1828
2208
  (0, utils_3.assertNotStellar)(vault, 'Vault PnL');
1829
2209
  if (!(0, ethers_1.isAddress)(vault))
1830
2210
  throw new Error(`Vault input parameter is not an address: ${String(vault)}`);
2211
+ // Get vault data
1831
2212
  const vaultData = await getVault({
1832
2213
  vault,
1833
2214
  loans: false,
@@ -1837,6 +2218,7 @@ async function getVaultPnl({ vault, options, }) {
1837
2218
  if (!vaultData) {
1838
2219
  throw new Error(`Vault ${vault} not found`);
1839
2220
  }
2221
+ // Check if vault has daily_pnl_per_share and historical_snapshots
1840
2222
  if (!vaultData.dailyPnlPerShare ||
1841
2223
  !Array.isArray(vaultData.dailyPnlPerShare) ||
1842
2224
  vaultData.dailyPnlPerShare.length === 0) {
@@ -1854,38 +2236,49 @@ async function getVaultPnl({ vault, options, }) {
1854
2236
  };
1855
2237
  }
1856
2238
  const decimals = vaultData.decimals;
2239
+ // Filter out dates with zero PnL
1857
2240
  const validDailyPnl = vaultData.dailyPnlPerShare.filter((dayData) => dayData.value && dayData.value !== 0);
2241
+ // Create a map of historical_snapshots by date (YYYY-MM-DD) for quick lookup
1858
2242
  const snapshotsByDate = new Map();
1859
2243
  vaultData.historical_snapshots.forEach((snapshot) => {
1860
2244
  const snapshotDate = new Date(snapshot.snapshot_datetime);
1861
- const dateKey = snapshotDate.toISOString().split('T')[0];
2245
+ const dateKey = snapshotDate.toISOString().split('T')[0]; // YYYY-MM-DD
1862
2246
  snapshotsByDate.set(dateKey, snapshot);
1863
2247
  });
2248
+ // Calculate PnL for each day using historical_snapshots
1864
2249
  let totalPnlRaw = BigInt(0);
1865
2250
  validDailyPnl.forEach((dayData) => {
1866
2251
  const dayTimestamp = dayData.timestamp;
1867
2252
  const dailyPnlPerShare = dayData.value;
2253
+ // Get date key (YYYY-MM-DD) from timestamp
1868
2254
  const dayDate = new Date(dayTimestamp);
1869
2255
  const dateKey = dayDate.toISOString().split('T')[0];
2256
+ // Find matching snapshot for this date
1870
2257
  const snapshot = snapshotsByDate.get(dateKey);
1871
- if (!snapshot || !snapshot.total_shares) {
2258
+ if (!snapshot?.total_shares) {
1872
2259
  core_1.Logger.log.warn('getVaultPnl:snapshotNotFound', `No snapshot found for date ${dateKey}`);
1873
2260
  return;
1874
2261
  }
2262
+ // Convert normalized numbers to BigInt using toNormalizedBn's normalized string
1875
2263
  const dailyPnlNorm = (0, core_1.toNormalizedBn)(dailyPnlPerShare, decimals);
1876
2264
  const totalSharesNorm = (0, core_1.toNormalizedBn)(snapshot.total_shares, decimals);
2265
+ // Convert normalized strings to BigInt (split, pad fraction, combine) - all inline
1877
2266
  const [dailyWhole = '0', dailyFrac = ''] = dailyPnlNorm.normalized.split('.');
1878
2267
  const dailyPnlPerShareScaled = BigInt(dailyWhole + (dailyFrac || '').padEnd(decimals, '0').slice(0, decimals));
1879
2268
  const [sharesWhole = '0', sharesFrac = ''] = totalSharesNorm.normalized.split('.');
1880
2269
  const totalSharesScaled = BigInt(sharesWhole +
1881
2270
  (sharesFrac || '').padEnd(decimals, '0').slice(0, decimals));
2271
+ // Calculate: daily_pnl_per_share[day] * total_shares[day] (both in raw units)
1882
2272
  const dayPnlRaw = (dailyPnlPerShareScaled * totalSharesScaled) / BigInt(10 ** decimals);
1883
2273
  totalPnlRaw += dayPnlRaw;
1884
2274
  });
2275
+ // Convert totalPnlRaw (BigInt) to normalized number using toNormalizedBn
1885
2276
  const totalPnl = (0, core_1.toNormalizedBn)(totalPnlRaw, decimals);
1886
2277
  const totalPnlNormalized = Number(totalPnl.normalized);
1887
2278
  const chainId = vaultData.chainId;
2279
+ // Get receipt token address (HLPe) from vaultData - no need for on-chain lookup
1888
2280
  const lpTokenAddress = vaultData.receipt.address;
2281
+ // Fetch HLPe (lpTokenAddress) price in USD
1889
2282
  let tokenPriceUsd = 0;
1890
2283
  try {
1891
2284
  tokenPriceUsd = await (0, core_1.fetchTokenPriceByAddress)(lpTokenAddress, chainId, options?.headers);
@@ -1893,6 +2286,7 @@ async function getVaultPnl({ vault, options, }) {
1893
2286
  catch (error) {
1894
2287
  core_1.Logger.log.warn('getVaultPnl:priceFetch', `Failed to fetch HLPe token price: ${error}`);
1895
2288
  }
2289
+ // Convert to USD and format to 6 decimal places
1896
2290
  const pnlUsd = Number((totalPnlNormalized * tokenPriceUsd).toFixed(6));
1897
2291
  return {
1898
2292
  totalPnl,
@@ -1905,6 +2299,14 @@ async function getVaultPnl({ vault, options, }) {
1905
2299
  throw new Error(`#getVaultPnl::${vault}:${errorMessage}`);
1906
2300
  }
1907
2301
  }
2302
+ /*
2303
+ * Fetch historical timeseries data for a vault.
2304
+ * Returns TVL, APY, PnL, share price, and other metrics over time.
2305
+ * @param vault - Vault contract address
2306
+ * @param nDays - Number of days of historical data (default 30, min 1)
2307
+ * @param options - Request options including headers
2308
+ * @returns Historical timeseries data with date string keys
2309
+ */
1908
2310
  async function getVaultHistoricalTimeseries({ vault, nDays, options, }) {
1909
2311
  try {
1910
2312
  if (!vault)
@@ -1918,6 +2320,19 @@ async function getVaultHistoricalTimeseries({ vault, nDays, options, }) {
1918
2320
  throw new Error(`#getVaultHistoricalTimeseries::${vault}:${errorMessage}`);
1919
2321
  }
1920
2322
  }
2323
+ /**
2324
+ * Fetch annualized APY metrics for a vault.
2325
+ *
2326
+ * Supported Vaults: cUSDO, tETH, wstETH, rsETH
2327
+ *
2328
+ * @deprecated The `hgETH30dLiquidAPY` and `hgETH7dLiquidAPY` response fields are deprecated.
2329
+ * These fields will be removed on 2026-01-01.
2330
+ * Use `liquidAPY30Day` and `liquidAPY7Day` fields instead.
2331
+ *
2332
+ * @param vault - Vault contract address
2333
+ * @param options - Request options including headers
2334
+ * @returns Annualized APY data including liquidity APY
2335
+ */
1921
2336
  async function getVaultAnnualizedApy({ vault, options, }) {
1922
2337
  try {
1923
2338
  if (!vault) {
@@ -1942,6 +2357,12 @@ async function getVaultAnnualizedApy({ vault, options, }) {
1942
2357
  throw new Error(`#getVaultAnnualizedApy::${vault}:${errorMessage}`);
1943
2358
  }
1944
2359
  }
2360
+ /**
2361
+ * Fetch summary data for a vault (name, type, chain, recent returns).
2362
+ * @param vault - Vault contract address
2363
+ * @param options - Request options including headers
2364
+ * @returns Vault summary data
2365
+ */
1945
2366
  async function getVaultSummary({ vault, options, }) {
1946
2367
  try {
1947
2368
  if (!vault) {
@@ -1967,6 +2388,13 @@ async function getVaultSummary({ vault, options, }) {
1967
2388
  throw new Error(`#getVaultSummary::${vault}:${errorMessage}`);
1968
2389
  }
1969
2390
  }
2391
+ /**
2392
+ * Fetch withdrawal summary and pending queue for a vault.
2393
+ * @param vault - Vault contract address
2394
+ * @param chain - Chain identifier (e.g., chain ID as string)
2395
+ * @param options - Request options including headers
2396
+ * @returns Withdrawal summary and pending queue
2397
+ */
1970
2398
  async function getVaultWithdrawals({ vault, chain, options, }) {
1971
2399
  try {
1972
2400
  if (!vault) {
@@ -1995,6 +2423,14 @@ async function getVaultWithdrawals({ vault, chain, options, }) {
1995
2423
  throw new Error(`#getVaultWithdrawals::${vault}::${chain}:${errorMessage}`);
1996
2424
  }
1997
2425
  }
2426
+ /**
2427
+ * Fetch pending redemptions for a vault with liquidity analysis.
2428
+ * @param vault - Vault contract address
2429
+ * @param pastDays - Number of past days to include (default 7, min 1, max 30)
2430
+ * @param futureDays - Number of future days to include (default 14, min 1, max 30)
2431
+ * @param options - Request options including headers
2432
+ * @returns Pending redemptions grouped by date with liquidity summary
2433
+ */
1998
2434
  async function getVaultPendingRedemptions({ vault, pastDays, futureDays, options, }) {
1999
2435
  try {
2000
2436
  if (!vault)
@@ -2062,6 +2498,13 @@ async function getVaultPendingRedemptions({ vault, pastDays, futureDays, options
2062
2498
  throw new Error(`#getVaultPendingRedemptions::${vault}:${errorMessage}`);
2063
2499
  }
2064
2500
  }
2501
+ /**
2502
+ * Preview the amount of assets that would be received for redeeming shares (queued redemption).
2503
+ * @param vault - Vault contract address
2504
+ * @param sharesAmount - Amount of shares to redeem (human-readable, raw bigint, or string)
2505
+ * @param options - RPC configuration
2506
+ * @returns The amount of assets as an INormalizedNumber with `normalized` and `raw` fields
2507
+ */
2065
2508
  async function getPreviewRedemption({ vault, sharesAmount, options, }) {
2066
2509
  try {
2067
2510
  if (!vault)
@@ -2109,6 +2552,13 @@ async function getPreviewRedemption({ vault, sharesAmount, options, }) {
2109
2552
  throw new Error(`Failed to preview redemption for ${vault}: ${e instanceof Error ? e.message : 'Unknown error'}`);
2110
2553
  }
2111
2554
  }
2555
+ /**
2556
+ * Helper: Decode a processing transaction to extract (year, month, day).
2557
+ *
2558
+ * Note: Multicall-wrapped transactions cannot be decoded from calldata or logs
2559
+ * (the WithdrawalProcessed event doesn't include the date). When decoding fails,
2560
+ * the caller falls back to timestamp-based date estimation.
2561
+ */
2112
2562
  async function decodeProcessingTransaction(txHash, provider, iface) {
2113
2563
  const tx = await provider.getTransaction(txHash);
2114
2564
  if (!tx) {
@@ -2122,14 +2572,74 @@ async function decodeProcessingTransaction(txHash, provider, iface) {
2122
2572
  }
2123
2573
  return (0, call_data_decoder_1.decodeWithdrawalProcessing)(tx.data, iface);
2124
2574
  }
2575
+ /**
2576
+ * Returns all withdrawal requests for a vault, enriched with computed claimable dates
2577
+ * and current processing status.
2578
+ *
2579
+ * The unique identifier for matching requests to processed events is:
2580
+ * (receiver, claimableDate.year, claimableDate.month, claimableDate.day)
2581
+ *
2582
+ * This is the primary method for integrators to reliably track withdrawal status
2583
+ * when multiple requests are submitted by the same receiver.
2584
+ *
2585
+ * ## Data sources
2586
+ * - **Withdrawal requests** come from the subgraph (all historical requests).
2587
+ * - **Processed events** come from on-chain `WithdrawalProcessed` logs, which are
2588
+ * limited to a finite lookback window determined by the chain's block parameters.
2589
+ *
2590
+ * ## Lookback window & filtering
2591
+ * On-chain logs only cover a limited number of recent blocks (e.g. ~20 days on Monad,
2592
+ * ~21 days on Ethereum). The `lookbackBlocks` parameter controls how far back to scan
2593
+ * and is applied uniformly to both data sources: it bounds the `WithdrawalProcessed`
2594
+ * log query *and* drops any withdrawal request whose submission timestamp falls outside
2595
+ * the window — regardless of whether a matching processed event was found. Callers who
2596
+ * need deeper history should widen `lookbackBlocks`.
2597
+ *
2598
+ * ## Status determination
2599
+ * - `'processed'` — A matching `WithdrawalProcessed` event was found (by receiver + claimable date).
2600
+ * - `'ready_to_claim'` — Claimable date has passed (within the lookback window) but no processed event found.
2601
+ * - `'pending'` — Claimable date is still in the future.
2602
+ *
2603
+ * @param params - Query parameters:
2604
+ * - `vault` — Vault address to query.
2605
+ * - `receiver` — (Optional) Filter by receiver address.
2606
+ * - `lookbackBlocks` — (Optional) On-chain log lookback window in blocks.
2607
+ * Defaults to a chain-specific value (e.g. 150,000 for Ethereum, 3,456,000 for Monad).
2608
+ * Increasing this scans more history but requires more RPC calls.
2609
+ * - `options` — Standard vault query options (provider, RPC URL, etc.).
2610
+ * @returns Array of withdrawal requests with status and claimable dates
2611
+ *
2612
+ * @example
2613
+ * ```typescript
2614
+ * const requests = await getWithdrawalRequestsWithStatus({
2615
+ * vault: '0xVault...',
2616
+ * receiver: '0xReceiver...'
2617
+ * });
2618
+ *
2619
+ * // Results show exact status + claimable date
2620
+ * requests.forEach(req => {
2621
+ * console.log(
2622
+ * `${req.claimableDate.year}-${req.claimableDate.month}-${req.claimableDate.day}: ${req.status}`
2623
+ * );
2624
+ * });
2625
+ *
2626
+ * // With custom lookback window (e.g. 30 days on Ethereum ≈ 216,000 blocks)
2627
+ * const moreResults = await getWithdrawalRequestsWithStatus({
2628
+ * vault: '0xVault...',
2629
+ * lookbackBlocks: 216_000,
2630
+ * });
2631
+ * ```
2632
+ */
2125
2633
  async function getWithdrawalRequestsWithStatus(params) {
2126
2634
  try {
2127
2635
  const { vault, receiver, lookbackBlocks, options } = params;
2636
+ // Input validation
2128
2637
  if (!vault || !(0, ethers_1.isAddress)(vault)) {
2129
2638
  throw new Error('Invalid vault address');
2130
2639
  }
2131
2640
  const vaultAddress = (0, ethers_1.getAddress)(vault);
2132
2641
  const provider = (0, core_1.createProvider)(options.rpcUrl);
2642
+ // Fetch vault metadata and lag duration
2133
2643
  const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vaultAddress, options?.headers, false, false))?.[0];
2134
2644
  if (!tokenizedVault) {
2135
2645
  core_1.Logger.log.warn('getWithdrawalRequestsWithStatus', `Vault not found in backend: ${vaultAddress}`);
@@ -2143,7 +2653,7 @@ async function getWithdrawalRequestsWithStatus(params) {
2143
2653
  abi: vaultAbi,
2144
2654
  provider,
2145
2655
  });
2146
- let lagDuration = 86400;
2656
+ let lagDuration = 86400; // Default: 1 day
2147
2657
  try {
2148
2658
  const lagValue = await vaultContract.lagDuration?.();
2149
2659
  if (lagValue !== undefined && lagValue !== null) {
@@ -2153,6 +2663,9 @@ async function getWithdrawalRequestsWithStatus(params) {
2153
2663
  catch (e) {
2154
2664
  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
2665
  }
2666
+ // Fetch subgraph requests, on-chain processed events, and the current block
2667
+ // in parallel. Current block is needed to derive the block-based lookback
2668
+ // cutoff applied uniformly to both data sources.
2156
2669
  const [withdrawalRequests, processedEvents, currentBlock] = await Promise.all([
2157
2670
  (0, vaults_1.getSubgraphWithdrawRequests)(vaultAddress, provider),
2158
2671
  getVaultRedemptionHistory({
@@ -2170,6 +2683,10 @@ async function getWithdrawalRequestsWithStatus(params) {
2170
2683
  const effectiveLookbackBlocks = lookbackBlocks ?? (0, core_1.determineBlockCutoff)(chainId);
2171
2684
  const blockCutoff = currentBlock - effectiveLookbackBlocks;
2172
2685
  const now = Math.floor(Date.now() / 1000);
2686
+ // Filter by receiver (if provided) and drop any request whose submission
2687
+ // block is outside the lookback window — uniformly, regardless of whether
2688
+ // a matching processed event exists. Using block_number (not timestamp)
2689
+ // keeps this consistent with the on-chain log scan's block-based cutoff.
2173
2690
  const filteredRequests = withdrawalRequests.filter((req) => {
2174
2691
  if (receiver &&
2175
2692
  req.receiverAddr?.toLowerCase() !== receiver.toLowerCase()) {
@@ -2177,8 +2694,10 @@ async function getWithdrawalRequestsWithStatus(params) {
2177
2694
  }
2178
2695
  return Number(req.block_number) >= blockCutoff;
2179
2696
  });
2697
+ // Pre-decode all processed transactions in parallel for performance
2180
2698
  const iface = new ethers_1.Interface(vaultAbi);
2181
2699
  const decodedProcessed = await Promise.all((processedEvents || []).map(async (processed) => {
2700
+ // Try calldata decoding first (deterministic)
2182
2701
  if (processed.transactionHash) {
2183
2702
  try {
2184
2703
  const decoded = await decodeProcessingTransaction(processed.transactionHash, provider, iface);
@@ -2197,8 +2716,12 @@ async function getWithdrawalRequestsWithStatus(params) {
2197
2716
  core_1.Logger.log.warn('getWithdrawalRequestsWithStatus', `Failed to decode processed transaction ${processed.transactionHash}: ${e instanceof Error ? e.message : 'Unknown error'}`);
2198
2717
  }
2199
2718
  }
2719
+ // Fallback: use original request timestamp to compute claimable date,
2720
+ // or if unavailable (evm-2: requested=epoch 0), use the processed block
2721
+ // timestamp as the date directly (processing happens on the claimable date).
2200
2722
  const requestedEpoch = Math.floor(processed.requested.getTime() / 1000);
2201
2723
  if (requestedEpoch > 0) {
2724
+ // evm-1: we have the original request timestamp → compute claimable date
2202
2725
  const fallbackDate = (0, date_utils_1.computeClaimableDate)(requestedEpoch, lagDuration);
2203
2726
  return {
2204
2727
  ...processed,
@@ -2209,6 +2732,8 @@ async function getWithdrawalRequestsWithStatus(params) {
2209
2732
  },
2210
2733
  };
2211
2734
  }
2735
+ // evm-2: no request timestamp — use processed block date directly
2736
+ // (processing occurs on the claimable date, so the block date IS the date)
2212
2737
  const processedDate = processed.processed;
2213
2738
  return {
2214
2739
  ...processed,
@@ -2219,6 +2744,11 @@ async function getWithdrawalRequestsWithStatus(params) {
2219
2744
  },
2220
2745
  };
2221
2746
  }));
2747
+ // Build deterministic lookup map: (receiver + year + month + day) → processed events
2748
+ // A single WithdrawalProcessed event covers the entire day's batch for a
2749
+ // receiver — all requests whose claimable date falls on that day should
2750
+ // match it. We keep an array per key only to preserve every event we saw
2751
+ // for visibility; matching is a non-destructive peek at the first entry.
2222
2752
  const processedMap = new Map();
2223
2753
  for (const processed of decodedProcessed) {
2224
2754
  if (!processed.decodedDate) {
@@ -2229,15 +2759,24 @@ async function getWithdrawalRequestsWithStatus(params) {
2229
2759
  const existing = processedMap.get(key) ?? [];
2230
2760
  processedMap.set(key, [...existing, processed]);
2231
2761
  }
2762
+ // Compute status for each withdrawal request. The lookback window was
2763
+ // already applied up-front when pruning filteredRequests, so every request
2764
+ // reaching this loop is within the window by construction.
2232
2765
  const results = [];
2233
2766
  for (const req of filteredRequests) {
2767
+ // timestamp_ is a Unix epoch string (seconds)
2234
2768
  const requestTimestamp = Number(req.timestamp_);
2769
+ // Compute claimable date from timestamp + lagDuration (UTC)
2235
2770
  const claimableDate = (0, date_utils_1.computeClaimableDate)(requestTimestamp, lagDuration);
2771
+ // Determine status: check processed first, then claimability
2236
2772
  let status = 'pending';
2237
2773
  let processedTxHash;
2238
2774
  let assetsReceived;
2775
+ // Match deterministically by (receiver, year, month, day)
2239
2776
  const dateKey = (0, date_utils_1.formatDateKey)(claimableDate.year, claimableDate.month, claimableDate.day);
2240
2777
  const lookupKey = `${req.receiverAddr.toLowerCase()}:${dateKey}`;
2778
+ // Peek — do not consume. The processing event covers the whole day for
2779
+ // this receiver, so every request on that day matches the same event.
2241
2780
  const processedEvent = processedMap.get(lookupKey)?.[0];
2242
2781
  if (processedEvent) {
2243
2782
  status = 'processed';
@@ -2250,6 +2789,7 @@ async function getWithdrawalRequestsWithStatus(params) {
2250
2789
  }
2251
2790
  }
2252
2791
  else if ((0, date_utils_1.isClaimableNow)(claimableDate.epoch, now)) {
2792
+ // Claimable date is recent (within lookback) and not yet processed
2253
2793
  status = 'ready_to_claim';
2254
2794
  }
2255
2795
  results.push({
@@ -2260,6 +2800,7 @@ async function getWithdrawalRequestsWithStatus(params) {
2260
2800
  return BigInt(req.shares);
2261
2801
  }
2262
2802
  catch {
2803
+ // Handle decimal strings from subgraph (e.g. "1234.0")
2263
2804
  const truncated = Math.trunc(Number(req.shares));
2264
2805
  if (!Number.isFinite(truncated)) {
2265
2806
  core_1.Logger.log.warn('getWithdrawalRequestsWithStatus', `Cannot parse shares value "${req.shares}" — defaulting to 0`);