upshift-sdk 8.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (316) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +349 -0
  3. package/lib/abis/AddressResolver.d.ts +28 -0
  4. package/lib/abis/AddressResolver.js +23 -0
  5. package/lib/abis/ChainlinkV3.d.ts +87 -0
  6. package/lib/abis/ChainlinkV3.js +117 -0
  7. package/lib/abis/ERC20.d.ts +168 -0
  8. package/lib/abis/ERC20.js +226 -0
  9. package/lib/abis/ERC20_Bytes32.d.ts +139 -0
  10. package/lib/abis/ERC20_Bytes32.js +196 -0
  11. package/lib/abis/ERC4626.d.ts +364 -0
  12. package/lib/abis/ERC4626.js +507 -0
  13. package/lib/abis/ERC721.d.ts +231 -0
  14. package/lib/abis/ERC721.js +321 -0
  15. package/lib/abis/FeeOracle.d.ts +120 -0
  16. package/lib/abis/FeeOracle.js +162 -0
  17. package/lib/abis/LendingPool.d.ts +1393 -0
  18. package/lib/abis/LendingPool.js +1807 -0
  19. package/lib/abis/LendingPoolV2.d.ts +1413 -0
  20. package/lib/abis/LendingPoolV2.js +1833 -0
  21. package/lib/abis/LendingPoolV3.d.ts +1677 -0
  22. package/lib/abis/LendingPoolV3.js +1160 -0
  23. package/lib/abis/Loan.d.ts +837 -0
  24. package/lib/abis/Loan.js +1080 -0
  25. package/lib/abis/MultiAssetNativeDepositWrapper.d.ts +137 -0
  26. package/lib/abis/MultiAssetNativeDepositWrapper.js +125 -0
  27. package/lib/abis/Multicall3.d.ts +30 -0
  28. package/lib/abis/Multicall3.js +97 -0
  29. package/lib/abis/OFT.d.ts +116 -0
  30. package/lib/abis/OFT.js +85 -0
  31. package/lib/abis/PoolAdapter.d.ts +36 -0
  32. package/lib/abis/PoolAdapter.js +51 -0
  33. package/lib/abis/RewardDistributor.d.ts +267 -0
  34. package/lib/abis/RewardDistributor.js +352 -0
  35. package/lib/abis/RwaRedeemSubaccount.d.ts +747 -0
  36. package/lib/abis/RwaRedeemSubaccount.js +548 -0
  37. package/lib/abis/SmartAccount.d.ts +17 -0
  38. package/lib/abis/SmartAccount.js +19 -0
  39. package/lib/abis/SwapRouter.d.ts +1043 -0
  40. package/lib/abis/SwapRouter.js +740 -0
  41. package/lib/abis/TextResolver.d.ts +16 -0
  42. package/lib/abis/TextResolver.js +16 -0
  43. package/lib/abis/TokenizedVaultV2.d.ts +1364 -0
  44. package/lib/abis/TokenizedVaultV2.js +1041 -0
  45. package/lib/abis/TokenizedVaultV2DepositWithPermit.d.ts +1456 -0
  46. package/lib/abis/TokenizedVaultV2DepositWithPermit.js +1878 -0
  47. package/lib/abis/TokenizedVaultV2Receipt.d.ts +1568 -0
  48. package/lib/abis/TokenizedVaultV2Receipt.js +1061 -0
  49. package/lib/abis/TokenizedVaultV2SenderAllocationWhitelist.d.ts +454 -0
  50. package/lib/abis/TokenizedVaultV2SenderAllocationWhitelist.js +327 -0
  51. package/lib/abis/TokenizedVaultV2WhitelistedAllocation.d.ts +1466 -0
  52. package/lib/abis/TokenizedVaultV2WhitelistedAllocation.js +1092 -0
  53. package/lib/abis/TokenizedVaultV2WhitelistedAssets.d.ts +274 -0
  54. package/lib/abis/TokenizedVaultV2WhitelistedAssets.js +167 -0
  55. package/lib/abis/UniversalResolverResolve.d.ts +69 -0
  56. package/lib/abis/UniversalResolverResolve.js +35 -0
  57. package/lib/abis/UniversalSignatureValidator.d.ts +17 -0
  58. package/lib/abis/UniversalSignatureValidator.js +30 -0
  59. package/lib/abis/WrapperAdapter.d.ts +71 -0
  60. package/lib/abis/WrapperAdapter.js +77 -0
  61. package/lib/abis/index.d.ts +34 -0
  62. package/lib/abis/index.js +51 -0
  63. package/lib/adapters/evm/getters.d.ts +19 -0
  64. package/lib/adapters/evm/getters.js +209 -0
  65. package/lib/adapters/evm/index.d.ts +353 -0
  66. package/lib/adapters/evm/index.js +434 -0
  67. package/lib/adapters/evm/utils.d.ts +8 -0
  68. package/lib/adapters/evm/utils.js +51 -0
  69. package/lib/adapters/solana/constants.d.ts +33 -0
  70. package/lib/adapters/solana/constants.js +52 -0
  71. package/lib/adapters/solana/getters.d.ts +11 -0
  72. package/lib/adapters/solana/getters.js +165 -0
  73. package/lib/adapters/solana/idl/vault-idl.d.ts +272 -0
  74. package/lib/adapters/solana/idl/vault-idl.js +1084 -0
  75. package/lib/adapters/solana/index.d.ts +233 -0
  76. package/lib/adapters/solana/index.js +291 -0
  77. package/lib/adapters/solana/types.d.ts +67 -0
  78. package/lib/adapters/solana/types.js +3 -0
  79. package/lib/adapters/solana/utils.d.ts +141 -0
  80. package/lib/adapters/solana/utils.js +595 -0
  81. package/lib/adapters/solana/vault.actions.d.ts +57 -0
  82. package/lib/adapters/solana/vault.actions.js +379 -0
  83. package/lib/adapters/stellar/actions.d.ts +28 -0
  84. package/lib/adapters/stellar/actions.js +77 -0
  85. package/lib/adapters/stellar/constants.d.ts +43 -0
  86. package/lib/adapters/stellar/constants.js +53 -0
  87. package/lib/adapters/stellar/getters.d.ts +68 -0
  88. package/lib/adapters/stellar/getters.js +290 -0
  89. package/lib/adapters/stellar/index.d.ts +114 -0
  90. package/lib/adapters/stellar/index.js +175 -0
  91. package/lib/adapters/stellar/soroban.d.ts +123 -0
  92. package/lib/adapters/stellar/soroban.js +613 -0
  93. package/lib/adapters/stellar/submit.d.ts +34 -0
  94. package/lib/adapters/stellar/submit.js +149 -0
  95. package/lib/adapters/stellar/types.d.ts +58 -0
  96. package/lib/adapters/stellar/types.js +6 -0
  97. package/lib/adapters/stellar/utils.d.ts +24 -0
  98. package/lib/adapters/stellar/utils.js +34 -0
  99. package/lib/adapters/sui/constants.d.ts +14 -0
  100. package/lib/adapters/sui/constants.js +29 -0
  101. package/lib/adapters/sui/getters.d.ts +9 -0
  102. package/lib/adapters/sui/getters.js +60 -0
  103. package/lib/adapters/sui/index.d.ts +45 -0
  104. package/lib/adapters/sui/index.js +101 -0
  105. package/lib/adapters/sui/transformer.d.ts +10 -0
  106. package/lib/adapters/sui/transformer.js +107 -0
  107. package/lib/adapters/sui/types.d.ts +66 -0
  108. package/lib/adapters/sui/types.js +3 -0
  109. package/lib/adapters/sui/utils.d.ts +10 -0
  110. package/lib/adapters/sui/utils.js +29 -0
  111. package/lib/core/analytics/chain-name.d.ts +9 -0
  112. package/lib/core/analytics/chain-name.js +34 -0
  113. package/lib/core/analytics/constants.d.ts +5 -0
  114. package/lib/core/analytics/constants.js +9 -0
  115. package/lib/core/analytics/env.d.ts +29 -0
  116. package/lib/core/analytics/env.js +59 -0
  117. package/lib/core/analytics/index.d.ts +36 -0
  118. package/lib/core/analytics/index.js +79 -0
  119. package/lib/core/analytics/instrumentation.d.ts +29 -0
  120. package/lib/core/analytics/instrumentation.js +277 -0
  121. package/lib/core/analytics/method-taxonomy.d.ts +19 -0
  122. package/lib/core/analytics/method-taxonomy.js +128 -0
  123. package/lib/core/analytics/metrics.d.ts +33 -0
  124. package/lib/core/analytics/metrics.js +116 -0
  125. package/lib/core/analytics/sanitize.d.ts +44 -0
  126. package/lib/core/analytics/sanitize.js +260 -0
  127. package/lib/core/analytics/sentry-runtime.d.ts +15 -0
  128. package/lib/core/analytics/sentry-runtime.js +97 -0
  129. package/lib/core/analytics/sentry.d.ts +67 -0
  130. package/lib/core/analytics/sentry.js +613 -0
  131. package/lib/core/analytics/types.d.ts +48 -0
  132. package/lib/core/analytics/types.js +3 -0
  133. package/lib/core/analytics/user-identity.d.ts +41 -0
  134. package/lib/core/analytics/user-identity.js +141 -0
  135. package/lib/core/analytics/version.d.ts +6 -0
  136. package/lib/core/analytics/version.js +10 -0
  137. package/lib/core/attribution.d.ts +111 -0
  138. package/lib/core/attribution.js +142 -0
  139. package/lib/core/auth/index.d.ts +1 -0
  140. package/lib/core/auth/index.js +18 -0
  141. package/lib/core/auth/verify.d.ts +2 -0
  142. package/lib/core/auth/verify.js +31 -0
  143. package/lib/core/base.class.d.ts +152 -0
  144. package/lib/core/base.class.js +172 -0
  145. package/lib/core/cache.d.ts +9 -0
  146. package/lib/core/cache.js +31 -0
  147. package/lib/core/constants/adapters.d.ts +103 -0
  148. package/lib/core/constants/adapters.js +180 -0
  149. package/lib/core/constants/core.d.ts +133 -0
  150. package/lib/core/constants/core.js +209 -0
  151. package/lib/core/constants/swap-router.d.ts +150 -0
  152. package/lib/core/constants/swap-router.js +169 -0
  153. package/lib/core/constants/vaults.d.ts +93 -0
  154. package/lib/core/constants/vaults.js +273 -0
  155. package/lib/core/constants/web3.d.ts +91 -0
  156. package/lib/core/constants/web3.js +229 -0
  157. package/lib/core/errors/index.d.ts +114 -0
  158. package/lib/core/errors/index.js +183 -0
  159. package/lib/core/fetcher.d.ts +198 -0
  160. package/lib/core/fetcher.js +903 -0
  161. package/lib/core/helpers/adapters.d.ts +13 -0
  162. package/lib/core/helpers/adapters.js +39 -0
  163. package/lib/core/helpers/chain-address.d.ts +13 -0
  164. package/lib/core/helpers/chain-address.js +47 -0
  165. package/lib/core/helpers/chain-error.d.ts +207 -0
  166. package/lib/core/helpers/chain-error.js +682 -0
  167. package/lib/core/helpers/chain-support.d.ts +80 -0
  168. package/lib/core/helpers/chain-support.js +115 -0
  169. package/lib/core/helpers/core.d.ts +68 -0
  170. package/lib/core/helpers/core.js +285 -0
  171. package/lib/core/helpers/explorer-link.d.ts +16 -0
  172. package/lib/core/helpers/explorer-link.js +26 -0
  173. package/lib/core/helpers/multicall.d.ts +68 -0
  174. package/lib/core/helpers/multicall.js +103 -0
  175. package/lib/core/helpers/revert-decode.d.ts +248 -0
  176. package/lib/core/helpers/revert-decode.js +515 -0
  177. package/lib/core/helpers/signer.d.ts +52 -0
  178. package/lib/core/helpers/signer.js +145 -0
  179. package/lib/core/helpers/swap-router.d.ts +211 -0
  180. package/lib/core/helpers/swap-router.js +480 -0
  181. package/lib/core/helpers/vault-version.d.ts +23 -0
  182. package/lib/core/helpers/vault-version.js +75 -0
  183. package/lib/core/helpers/vaults.d.ts +89 -0
  184. package/lib/core/helpers/vaults.js +235 -0
  185. package/lib/core/helpers/web3.d.ts +353 -0
  186. package/lib/core/helpers/web3.js +992 -0
  187. package/lib/core/index.d.ts +23 -0
  188. package/lib/core/index.js +40 -0
  189. package/lib/core/logger/index.d.ts +98 -0
  190. package/lib/core/logger/index.js +144 -0
  191. package/lib/core/logger/slack.d.ts +16 -0
  192. package/lib/core/logger/slack.js +57 -0
  193. package/lib/core/vault-metadata.d.ts +12 -0
  194. package/lib/core/vault-metadata.js +42 -0
  195. package/lib/core/version-check.d.ts +58 -0
  196. package/lib/core/version-check.js +182 -0
  197. package/lib/evm/index.d.ts +2 -0
  198. package/lib/evm/index.js +19 -0
  199. package/lib/evm/methods/crossChainVault.d.ts +128 -0
  200. package/lib/evm/methods/crossChainVault.js +853 -0
  201. package/lib/evm/methods/crossChainVaultRegistry.d.ts +93 -0
  202. package/lib/evm/methods/crossChainVaultRegistry.js +240 -0
  203. package/lib/evm/methods/index.d.ts +2 -0
  204. package/lib/evm/methods/index.js +19 -0
  205. package/lib/evm/types/crossChain.d.ts +363 -0
  206. package/lib/evm/types/crossChain.js +20 -0
  207. package/lib/evm/types/index.d.ts +1 -0
  208. package/lib/evm/types/index.js +18 -0
  209. package/lib/index.d.ts +30 -0
  210. package/lib/index.js +52 -0
  211. package/lib/main.d.ts +527 -0
  212. package/lib/main.js +601 -0
  213. package/lib/modules/api/fetcher.d.ts +82 -0
  214. package/lib/modules/api/fetcher.js +150 -0
  215. package/lib/modules/api/index.d.ts +1 -0
  216. package/lib/modules/api/index.js +6 -0
  217. package/lib/modules/api/main.d.ts +313 -0
  218. package/lib/modules/api/main.js +479 -0
  219. package/lib/modules/sub-accounts/fetcher.d.ts +53 -0
  220. package/lib/modules/sub-accounts/fetcher.js +120 -0
  221. package/lib/modules/sub-accounts/index.d.ts +2 -0
  222. package/lib/modules/sub-accounts/index.js +19 -0
  223. package/lib/modules/sub-accounts/main.d.ts +243 -0
  224. package/lib/modules/sub-accounts/main.js +205 -0
  225. package/lib/modules/sub-accounts/utils.d.ts +106 -0
  226. package/lib/modules/sub-accounts/utils.js +112 -0
  227. package/lib/modules/vaults/adapter.helpers.d.ts +64 -0
  228. package/lib/modules/vaults/adapter.helpers.js +184 -0
  229. package/lib/modules/vaults/fetcher.d.ts +147 -0
  230. package/lib/modules/vaults/fetcher.js +368 -0
  231. package/lib/modules/vaults/getters.d.ts +570 -0
  232. package/lib/modules/vaults/getters.js +3051 -0
  233. package/lib/modules/vaults/index.d.ts +20 -0
  234. package/lib/modules/vaults/index.js +44 -0
  235. package/lib/modules/vaults/main.d.ts +601 -0
  236. package/lib/modules/vaults/main.js +1623 -0
  237. package/lib/modules/vaults/prefetch.d.ts +65 -0
  238. package/lib/modules/vaults/prefetch.js +120 -0
  239. package/lib/modules/vaults/read.actions.d.ts +225 -0
  240. package/lib/modules/vaults/read.actions.js +596 -0
  241. package/lib/modules/vaults/types.d.ts +71 -0
  242. package/lib/modules/vaults/types.js +3 -0
  243. package/lib/modules/vaults/utils/call-data-decoder.d.ts +61 -0
  244. package/lib/modules/vaults/utils/call-data-decoder.js +194 -0
  245. package/lib/modules/vaults/utils/date-utils.d.ts +50 -0
  246. package/lib/modules/vaults/utils/date-utils.js +84 -0
  247. package/lib/modules/vaults/utils.d.ts +140 -0
  248. package/lib/modules/vaults/utils.js +799 -0
  249. package/lib/modules/vaults/write.actions.d.ts +529 -0
  250. package/lib/modules/vaults/write.actions.js +1749 -0
  251. package/lib/polyfills.d.ts +1 -0
  252. package/lib/polyfills.js +12 -0
  253. package/lib/sdk.d.ts +26721 -0
  254. package/lib/services/coingecko/fetcher.d.ts +15 -0
  255. package/lib/services/coingecko/fetcher.js +67 -0
  256. package/lib/services/coingecko/index.d.ts +2 -0
  257. package/lib/services/coingecko/index.js +19 -0
  258. package/lib/services/coingecko/utils.d.ts +1 -0
  259. package/lib/services/coingecko/utils.js +24 -0
  260. package/lib/services/debank/fetcher.d.ts +126 -0
  261. package/lib/services/debank/fetcher.js +47 -0
  262. package/lib/services/debank/index.d.ts +2 -0
  263. package/lib/services/debank/index.js +19 -0
  264. package/lib/services/debank/utils.d.ts +38 -0
  265. package/lib/services/debank/utils.js +297 -0
  266. package/lib/services/layerzero/deposits.d.ts +49 -0
  267. package/lib/services/layerzero/deposits.js +166 -0
  268. package/lib/services/layerzero/redeems.d.ts +20 -0
  269. package/lib/services/layerzero/redeems.js +92 -0
  270. package/lib/services/layerzero/utils.d.ts +9 -0
  271. package/lib/services/layerzero/utils.js +22 -0
  272. package/lib/services/octavfi/fetcher.d.ts +9 -0
  273. package/lib/services/octavfi/fetcher.js +103 -0
  274. package/lib/services/octavfi/index.d.ts +3 -0
  275. package/lib/services/octavfi/index.js +20 -0
  276. package/lib/services/octavfi/types.d.ts +34 -0
  277. package/lib/services/octavfi/types.js +3 -0
  278. package/lib/services/octavfi/utils.d.ts +16 -0
  279. package/lib/services/octavfi/utils.js +212 -0
  280. package/lib/services/subgraph/fetcher.d.ts +2 -0
  281. package/lib/services/subgraph/fetcher.js +61 -0
  282. package/lib/services/subgraph/index.d.ts +2 -0
  283. package/lib/services/subgraph/index.js +19 -0
  284. package/lib/services/subgraph/schema.d.ts +45 -0
  285. package/lib/services/subgraph/schema.js +75 -0
  286. package/lib/services/subgraph/vaults.d.ts +25 -0
  287. package/lib/services/subgraph/vaults.js +1106 -0
  288. package/lib/services/swap-quotes/index.d.ts +105 -0
  289. package/lib/services/swap-quotes/index.js +77 -0
  290. package/lib/services/swap-quotes/paraswap.d.ts +51 -0
  291. package/lib/services/swap-quotes/paraswap.js +138 -0
  292. package/lib/types/api.d.ts +280 -0
  293. package/lib/types/api.js +3 -0
  294. package/lib/types/index.d.ts +12 -0
  295. package/lib/types/index.js +28 -0
  296. package/lib/types/points.d.ts +28 -0
  297. package/lib/types/points.js +3 -0
  298. package/lib/types/pools.d.ts +144 -0
  299. package/lib/types/pools.js +3 -0
  300. package/lib/types/staking.d.ts +28 -0
  301. package/lib/types/staking.js +3 -0
  302. package/lib/types/sub-accounts.d.ts +140 -0
  303. package/lib/types/sub-accounts.js +3 -0
  304. package/lib/types/subgraph.d.ts +74 -0
  305. package/lib/types/subgraph.js +3 -0
  306. package/lib/types/typed-contract.d.ts +102 -0
  307. package/lib/types/typed-contract.js +3 -0
  308. package/lib/types/user.d.ts +1 -0
  309. package/lib/types/user.js +3 -0
  310. package/lib/types/vaults.d.ts +714 -0
  311. package/lib/types/vaults.js +24 -0
  312. package/lib/types/web3.d.ts +51 -0
  313. package/lib/types/web3.js +14 -0
  314. package/lib/types/webserver.d.ts +698 -0
  315. package/lib/types/webserver.js +3 -0
  316. package/package.json +78 -0
@@ -0,0 +1,3051 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.getSwapRouterDepositResult = void 0;
40
+ exports.getVault = getVault;
41
+ exports.getVaultLoans = getVaultLoans;
42
+ exports.getVaultSubaccountLoans = getVaultSubaccountLoans;
43
+ exports.getVaultAllocations = getVaultAllocations;
44
+ exports.getVaultAvailableRedemptions = getVaultAvailableRedemptions;
45
+ exports.getVaultRedemptionHistory = getVaultRedemptionHistory;
46
+ exports.getVaultPositions = getVaultPositions;
47
+ exports.getVaultApy = getVaultApy;
48
+ exports.getRewardsStakingPositions = getRewardsStakingPositions;
49
+ exports.getVaultTvl = getVaultTvl;
50
+ exports.getVaultBorrowerHealthFactor = getVaultBorrowerHealthFactor;
51
+ exports.getHealthFactorOfBorrowersByVault = getHealthFactorOfBorrowersByVault;
52
+ exports.getUserPoints = getUserPoints;
53
+ exports.registerUserForPoints = registerUserForPoints;
54
+ exports.fetchPointsLeaderboard = fetchPointsLeaderboard;
55
+ exports.getYieldLastRealizedOn = getYieldLastRealizedOn;
56
+ exports.assertPnlHistoryConsistent = assertPnlHistoryConsistent;
57
+ exports.sumDepositsAtFaceValue = sumDepositsAtFaceValue;
58
+ exports.getVaultUserLifetimePnl = getVaultUserLifetimePnl;
59
+ exports.getVaultPnl = getVaultPnl;
60
+ exports.getVaultHistoricalTimeseries = getVaultHistoricalTimeseries;
61
+ exports.getVaultAnnualizedApy = getVaultAnnualizedApy;
62
+ exports.getVaultSummary = getVaultSummary;
63
+ exports.getVaultWithdrawals = getVaultWithdrawals;
64
+ exports.getVaultPendingRedemptions = getVaultPendingRedemptions;
65
+ exports.getPreviewRedemption = getPreviewRedemption;
66
+ exports.getWithdrawalRequestsWithStatus = getWithdrawalRequestsWithStatus;
67
+ const abis_1 = require("../../abis");
68
+ const types_1 = require("../../types");
69
+ const core_1 = require("../../core");
70
+ const ethers_1 = require("ethers");
71
+ const utils_1 = require("./utils");
72
+ const subgraph_1 = require("../../services/subgraph");
73
+ const vaults_1 = require("../../services/subgraph/vaults");
74
+ const debank_1 = require("../../services/debank");
75
+ const utils_2 = require("../../adapters/solana/utils");
76
+ const SolanaGetters = __importStar(require("../../adapters/solana/getters"));
77
+ const SolanaConstants = __importStar(require("../../adapters/solana/constants"));
78
+ const StellarGetters = __importStar(require("../../adapters/stellar/getters"));
79
+ const utils_3 = require("../../adapters/stellar/utils");
80
+ const constants_1 = require("../../adapters/stellar/constants");
81
+ const EVM = __importStar(require("../../adapters/evm"));
82
+ const TokenizedVaultV2_1 = require("../../abis/TokenizedVaultV2");
83
+ const TokenizedVaultV2Receipt_1 = require("../../abis/TokenizedVaultV2Receipt");
84
+ const ethereum_block_by_date_1 = __importDefault(require("ethereum-block-by-date"));
85
+ const octavfi_1 = require("../../services/octavfi");
86
+ const date_utils_1 = require("./utils/date-utils");
87
+ const call_data_decoder_1 = require("./utils/call-data-decoder");
88
+ const deposits_1 = require("../../services/layerzero/deposits");
89
+ const redeems_1 = require("../../services/layerzero/redeems");
90
+ const errors_1 = require("../../core/errors");
91
+ /**
92
+ * Vault Data Getters
93
+ *
94
+ * Main query functions for vault data across chains:
95
+ * - getVault: Comprehensive vault details with loans/allocations
96
+ * - getVaultLoans: Active loan data with on-chain enrichment
97
+ * - getVaultAllocations: DeFi/CeFi/OTC allocation breakdowns
98
+ * - getVaultAvailableRedemptions: Claimable redemption requests
99
+ * - getVaultRedemptionHistory: Historical redemption data
100
+ * - getVaultPositions: User positions across vaults
101
+ * - getVaultApy: @deprecated Current and historical APY data
102
+ * - getRewardsStakingPositions: Staking positions and rewards
103
+ * - getVaultTvl: Current and historical TVL
104
+ * - getVaultHistoricalTimeseries: Time series data for vault metrics
105
+ */
106
+ /**
107
+ * Fetch comprehensive vault data including on-chain state and backend metadata.
108
+ * Routes to appropriate chain adapter (EVM v1/v2, Solana, or Stellar) based on vault version.
109
+ * Optionally enriches with loan and allocation data.
110
+ * @param vault - Vault contract address (EVM hex, Stellar C-address, or Solana program ID)
111
+ * @param loans - Include active loan data
112
+ * @param allocations - Include DeFi/CeFi allocation breakdowns
113
+ * @param options - RPC and service configuration
114
+ * @returns Complete vault object with optional enrichments
115
+ */
116
+ async function getVault({ vault, loans = false, allocations = false, options, loadSubaccounts, loadSnapshots, }) {
117
+ let returnedVault;
118
+ try {
119
+ const tokenizedVaultRaw = await (0, core_1.fetchTokenizedVault)(vault, undefined, loadSubaccounts, loadSnapshots);
120
+ const tokenizedVault = tokenizedVaultRaw?.[0];
121
+ const vaultVersion = (0, core_1.getVaultVersionV2)(tokenizedVault);
122
+ switch (vaultVersion) {
123
+ case 'sol-0': {
124
+ returnedVault = await SolanaGetters.getSolanaVault(tokenizedVault, options);
125
+ break;
126
+ }
127
+ case 'stellar-0': {
128
+ returnedVault = await StellarGetters.getStellarVault(tokenizedVault, options);
129
+ break;
130
+ }
131
+ case 'evm-2': {
132
+ returnedVault = await EVM.getEvmVaultV2(tokenizedVault, options);
133
+ break;
134
+ }
135
+ default: {
136
+ returnedVault = await EVM.getEvmVaultV1(tokenizedVault, options);
137
+ break;
138
+ }
139
+ }
140
+ if (returnedVault === null) {
141
+ return null;
142
+ }
143
+ }
144
+ catch (err) {
145
+ core_1.Logger.log.error('getVault', err, { vault });
146
+ throw new Error(`#getVault::${vault}: ${err?.message}`);
147
+ }
148
+ // Loans/allocations enrichment is only supported for EVM vaults.
149
+ // Non-EVM vaults already include empty defaults.
150
+ const isEvmVault = returnedVault.version !== 'sol-0' &&
151
+ returnedVault.version !== 'stellar-0' &&
152
+ returnedVault.version !== 'sui-0';
153
+ if (!isEvmVault && (loans || allocations)) {
154
+ core_1.Logger.log.warn('getVault', 'Loans/allocations enrichment is not supported for non-EVM vaults — skipping', { vault, version: returnedVault.version });
155
+ }
156
+ if (isEvmVault && !(0, core_1.isBadVault)(vault) && (loans || allocations)) {
157
+ const [loansResult, allocationsResult] = await Promise.allSettled([
158
+ loans
159
+ ? getVaultLoans(returnedVault, options)
160
+ : Promise.resolve(undefined),
161
+ allocations
162
+ ? getVaultAllocations(vault, options)
163
+ : Promise.resolve(undefined),
164
+ ]);
165
+ if (loans) {
166
+ if (loansResult.status === 'fulfilled') {
167
+ returnedVault = {
168
+ ...returnedVault,
169
+ loans: loansResult.value,
170
+ };
171
+ }
172
+ else {
173
+ core_1.Logger.log.error('getVault:loans', loansResult.reason, {
174
+ symbol: returnedVault.receipt.symbol,
175
+ });
176
+ }
177
+ }
178
+ if (allocations) {
179
+ if (allocationsResult.status === 'fulfilled') {
180
+ returnedVault = {
181
+ ...returnedVault,
182
+ allocations: allocationsResult.value,
183
+ };
184
+ }
185
+ else {
186
+ core_1.Logger.log.error('getVault:allocations', allocationsResult.reason, {
187
+ symbol: returnedVault.receipt.symbol,
188
+ });
189
+ throw new Error('Failure fetching debank res');
190
+ }
191
+ }
192
+ }
193
+ return returnedVault;
194
+ }
195
+ /**
196
+ * Vault Loans
197
+ */
198
+ async function getVaultLoans(vault, options) {
199
+ // sanitize
200
+ const vaultAddress = typeof vault === 'string' ? vault : vault.address;
201
+ const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vaultAddress, options?.headers))?.[0];
202
+ const vaultVersion = (0, core_1.getVaultVersionV2)(tokenizedVault);
203
+ if (vaultVersion !== 'evm-0')
204
+ return []; // only evm-0 uses subaccount loans
205
+ try {
206
+ let poolTotalSupply;
207
+ let poolDecimals;
208
+ const chainId = options?.chainId || tokenizedVault?.chain;
209
+ const provider = (0, core_1.createProvider)(options.rpcUrl);
210
+ const tokenizedVaultLoans = await (0, core_1.fetchTokenizedVaultLoans)(vaultAddress, chainId);
211
+ // if user passed IAddress or a full IVault
212
+ if (typeof vault === 'string') {
213
+ // fetch necessary data
214
+ const poolContract = (0, core_1.createContract)({
215
+ provider,
216
+ address: vaultAddress,
217
+ abi: abis_1.ABI_LENDING_POOL_V2,
218
+ });
219
+ [poolTotalSupply, poolDecimals] = await Promise.all([
220
+ poolContract.totalSupply(),
221
+ (0, core_1.getDecimals)(provider, vaultAddress),
222
+ ]);
223
+ poolTotalSupply = BigInt(poolTotalSupply).toString();
224
+ poolDecimals = Number(poolDecimals);
225
+ }
226
+ else {
227
+ poolTotalSupply = vault.totalSupply.raw;
228
+ poolDecimals = vault.decimals;
229
+ }
230
+ // fetch and format all the loans
231
+ const newLoans = (await Promise.all((Array.isArray(tokenizedVaultLoans) ? tokenizedVaultLoans : [])?.map(async (l) => {
232
+ // variables
233
+ const borrower = l.borrower;
234
+ // calculate allocation
235
+ const allocation = l.principal_amount /
236
+ Number((0, ethers_1.formatUnits)(poolTotalSupply, poolDecimals));
237
+ // get august loan fee from oracle
238
+ const loanFeeRate = await (0, core_1.getLoanOracleFeeRate)(provider, 'LOAN.REPAY.INTERESTS', l.address, chainId);
239
+ // calculate loan level apr
240
+ const loanApr = Number(l.apr || 0) / 100;
241
+ const loanAprAfterFees = loanApr * (1 - loanFeeRate / 100);
242
+ // determine if capital is deployed
243
+ const isIdleCapital = core_1.IDLE_CAPITAL_BORROWER_ADDRESS.includes(borrower);
244
+ // build new obj
245
+ const newLoanObj = {
246
+ vault: vaultAddress,
247
+ address: l.address,
248
+ lender: l.lender,
249
+ borrower: l.borrower,
250
+ state: l.state,
251
+ totalRepaid: l.total_repaid,
252
+ principalToken: l.principal_token,
253
+ principalAmount: l.principal_amount,
254
+ interestAmount: l.interest_amount,
255
+ upcomingPayment: {
256
+ amount: l.upcoming_payment.amount,
257
+ dueDate: l.upcoming_payment.due_date,
258
+ },
259
+ apr: loanAprAfterFees,
260
+ initialPrincipalAmount: l.initial_principal_amount,
261
+ deployedDate: l.deployed_date,
262
+ isIdleCapital,
263
+ paymentInterval: l.payment_interval,
264
+ allocation,
265
+ };
266
+ return newLoanObj;
267
+ }))).filter((l) => l !== undefined);
268
+ return newLoans;
269
+ }
270
+ catch (e) {
271
+ core_1.Logger.log.error('getVaultLoans', e, { vault });
272
+ throw new Error(`#getVaultLoans::${vault}:${e?.message}`);
273
+ }
274
+ }
275
+ /**
276
+ * Vault Subaccount Loans
277
+ */
278
+ async function getVaultSubaccountLoans(vault, options) {
279
+ // sanitize
280
+ const vaultAddress = typeof vault === 'string' ? vault : vault.address;
281
+ const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vaultAddress, options?.headers))?.[0];
282
+ const vaultVersion = (0, core_1.getVaultVersionV2)(tokenizedVault);
283
+ if (vaultVersion !== 'evm-0')
284
+ return [];
285
+ try {
286
+ let poolTotalSupply;
287
+ let poolDecimals;
288
+ const chainId = options?.chainId || tokenizedVault?.chain;
289
+ const provider = (0, core_1.createProvider)(options.rpcUrl);
290
+ const tokenizedVaultLoans = await (0, core_1.fetchTokenizedVaultSubaccountLoans)(vaultAddress, chainId);
291
+ // if user passed IAddress or a full IVault
292
+ if (typeof vault === 'string') {
293
+ // fetch necessary data
294
+ const poolContract = (0, core_1.createContract)({
295
+ provider,
296
+ address: vaultAddress,
297
+ abi: abis_1.ABI_LENDING_POOL_V2,
298
+ });
299
+ [poolTotalSupply, poolDecimals] = await Promise.all([
300
+ poolContract.totalSupply(),
301
+ (0, core_1.getDecimals)(provider, vaultAddress),
302
+ ]);
303
+ poolTotalSupply = BigInt(poolTotalSupply).toString();
304
+ poolDecimals = Number(poolDecimals);
305
+ }
306
+ else {
307
+ poolTotalSupply = vault.totalSupply.raw;
308
+ poolDecimals = vault.decimals;
309
+ }
310
+ // fetch and format all the loans
311
+ const newLoans = (await Promise.all((Array.isArray(tokenizedVaultLoans) ? tokenizedVaultLoans : [])?.map(async (l) => {
312
+ // variables
313
+ const borrower = l.borrower;
314
+ // calculate allocation
315
+ const allocation = l.principal_amount /
316
+ Number((0, ethers_1.formatUnits)(poolTotalSupply, poolDecimals));
317
+ // get august loan fee from oracle
318
+ const loanFeeRate = await (0, core_1.getLoanOracleFeeRate)(provider, 'LOAN.REPAY.INTERESTS', l.address, chainId);
319
+ // calculate loan level apr
320
+ const loanApr = Number(l.apr || 0) / 100;
321
+ const loanAprAfterFees = loanApr * (1 - loanFeeRate / 100);
322
+ // determine if capital is deployed
323
+ const isIdleCapital = core_1.IDLE_CAPITAL_BORROWER_ADDRESS.includes(borrower);
324
+ // build new obj
325
+ const newLoanObj = {
326
+ vault: vaultAddress,
327
+ address: l.address,
328
+ lender: l.lender,
329
+ borrower: l.borrower,
330
+ state: l.state,
331
+ totalRepaid: l.total_repaid,
332
+ principalToken: l.principal_token,
333
+ principalAmount: l.principal_amount,
334
+ interestAmount: l.interest_amount,
335
+ upcomingPayment: {
336
+ amount: l.upcoming_payment.amount,
337
+ dueDate: l.upcoming_payment.due_date,
338
+ },
339
+ apr: loanAprAfterFees,
340
+ initialPrincipalAmount: l.initial_principal_amount,
341
+ deployedDate: l.deployed_date,
342
+ isIdleCapital,
343
+ paymentInterval: l.payment_interval,
344
+ allocation,
345
+ };
346
+ return newLoanObj;
347
+ }))).filter((l) => l !== undefined);
348
+ return newLoans;
349
+ }
350
+ catch (e) {
351
+ core_1.Logger.log.error('getVaultSubaccountLoans', e, { vault });
352
+ throw new Error(`#getVaultSubaccountLoans::${vault}:${e?.message}`);
353
+ }
354
+ }
355
+ /**
356
+ * HTTP statuses that mean "no such record for this subaccount", not "something
357
+ * broke". The backend returns 404 for a subaccount with no CeFi/OTC position,
358
+ * which is the normal case for most vault borrowers.
359
+ */
360
+ const EXPECTED_SUBACCOUNT_FETCH_STATUSES = new Set([204, 404]);
361
+ /**
362
+ * Log a per-subaccount enrichment fetch failure at the right severity.
363
+ *
364
+ * Why: `getVaultAllocations` issues one CeFi and one OTC request per borrower
365
+ * on every call. Logging the "no record" responses at `error` level captured
366
+ * them as Sentry issues, producing thousands of `AugustServerError: Request
367
+ * failed: 404` events a day for a condition that is not a fault and that
368
+ * callers never need to act on. Expected statuses become breadcrumbs; genuine
369
+ * failures (5xx, auth, transport) stay at `error`.
370
+ *
371
+ * @param tag - Call-site tag, e.g. `getVaultAllocations:cefi`. Becomes the
372
+ * `sdk.origin` Sentry tag for grouping.
373
+ * @param status - HTTP status, or `undefined` when the failure was a thrown
374
+ * transport/typed error with no status attached.
375
+ * @param detail - Status text or error to attach as context.
376
+ * @param context - Identifiers scoping the failure (`vault`, `borrower`).
377
+ */
378
+ function logSubaccountFetchFailure(tag, status, detail, context) {
379
+ if (status !== undefined && EXPECTED_SUBACCOUNT_FETCH_STATUSES.has(status)) {
380
+ core_1.Logger.log.warn(tag, { ...context, status, reason: 'no record' });
381
+ return;
382
+ }
383
+ core_1.Logger.log.error(tag, detail, { ...context, status });
384
+ }
385
+ /**
386
+ * Extract the HTTP status from a thrown fetch error, when it carries one.
387
+ *
388
+ * `fetchAugustWithKey` throws `AugustServerError`/`AugustAuthError` for non-2xx
389
+ * responses, so the status-code branch in the callers is unreachable for those
390
+ * — the status has to be recovered from the error to classify it.
391
+ *
392
+ * @param error - Any thrown value.
393
+ * @returns The HTTP status, or `undefined` for errors without one (network
394
+ * failures, aborts, programming errors).
395
+ */
396
+ function httpStatusFromError(error) {
397
+ const status = error?.status;
398
+ return typeof status === 'number' ? status : undefined;
399
+ }
400
+ /**
401
+ * Vault Allocations
402
+ */
403
+ /**
404
+ * Fetch comprehensive vault asset allocation breakdown.
405
+ * Includes DeFi protocols (via DeBank), CeFi balances, OTC positions, and loans.
406
+ * Categorizes exposures by borrowing, supplying, lending, and wallet holdings.
407
+ * @param vault - Vault address
408
+ * @param options - RPC configuration
409
+ * @returns Detailed allocation data with exposure categorization
410
+ */
411
+ async function getVaultAllocations(vault, options) {
412
+ // to be used on upshift UI
413
+ const protocolExposure = [];
414
+ const tokenExposure = [];
415
+ let cefiExposure = [];
416
+ const otcPositions = {};
417
+ const defiPerBorrower = {};
418
+ const exposurePerCategory = {
419
+ supplying: [],
420
+ borrowing: [],
421
+ wallet: [],
422
+ walletBalance: 0,
423
+ cefiBalance: 0,
424
+ defiBalance: 0,
425
+ lending: [],
426
+ loanBalance: 0,
427
+ };
428
+ const netValue = { value: 0 };
429
+ let debankErr = false;
430
+ let unfilteredTokens = [];
431
+ const borrowerPortfolioFetchers = {
432
+ solana: async (borrower) => {
433
+ const portfolios = await (0, octavfi_1.fetchOctavfiPortfolios)([borrower]);
434
+ return portfolios.length > 0
435
+ ? (0, octavfi_1.transformOctavfiToDebank)(portfolios[0])
436
+ : null;
437
+ },
438
+ };
439
+ // Determine wallets to fetch allocation responses from
440
+ let uniqueBorrowers = [];
441
+ const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vault, options?.headers))?.[0];
442
+ // fetch all vault unique subaccounts/loan borrowers
443
+ try {
444
+ const vaultVersion = (0, core_1.getVaultVersionV2)(tokenizedVault);
445
+ if (vaultVersion === 'evm-0') {
446
+ const loans = await getVaultLoans(vault, options);
447
+ uniqueBorrowers = [...new Set(loans.map((l) => l.borrower))];
448
+ }
449
+ else {
450
+ const eoa_operators = tokenizedVault.eoa_operators.map((s) => s.address);
451
+ uniqueBorrowers = [
452
+ ...tokenizedVault.subaccounts.map((s) => s.address),
453
+ ...eoa_operators,
454
+ ];
455
+ }
456
+ }
457
+ catch (e) {
458
+ core_1.Logger.log.error('getVaultAllocations:borrowers', e);
459
+ }
460
+ // if hardcoded subaccount exists for vault, use that
461
+ const hardcodedSubaccount = Object.values(core_1.VAULT_ALLOCATION_SUBACCOUNTS).find((entry) => entry.address.toLowerCase() === vault.toLowerCase() && !entry.useDebank);
462
+ if (hardcodedSubaccount) {
463
+ const debankRes = await (0, debank_1.fetchDebankResponse)(hardcodedSubaccount.subaccount);
464
+ if (debankRes === false) {
465
+ debankErr = true;
466
+ }
467
+ // pull data from debank results
468
+ (0, debank_1.parseVaultLevelDebank)(debankRes, protocolExposure, tokenExposure, hardcodedSubaccount.subaccount, exposurePerCategory, netValue);
469
+ unfilteredTokens = debankRes?.subaccount?.tokens;
470
+ const debankPerLoan = (0, debank_1.parseLoanLevelDebank)(debankRes);
471
+ defiPerBorrower[hardcodedSubaccount.subaccount] = debankPerLoan;
472
+ }
473
+ else {
474
+ // Fetch DeBank data for all subaccounts in a single request (EVM only)
475
+ if ((0, core_1.getAddressChainType)(vault) === 'evm') {
476
+ const vaultDebankData = await (0, debank_1.fetchVaultDebankResponse)(vault, options?.headers);
477
+ if (vaultDebankData === false) {
478
+ debankErr = true;
479
+ }
480
+ else {
481
+ // Process DeBank data for each subaccount
482
+ for (const [subaccountAddress, subaccountData] of Object.entries(vaultDebankData)) {
483
+ // Skip if subaccount has an error
484
+ if ('error' in subaccountData) {
485
+ core_1.Logger.log.error('getVaultAllocations:debank:subaccount', {
486
+ subaccountAddress,
487
+ error: subaccountData.error,
488
+ });
489
+ continue;
490
+ }
491
+ // Transform to match existing parser format: { subaccount: { positions, app_positions, tokens } }
492
+ const debankRes = {
493
+ subaccount: {
494
+ positions: subaccountData.positions || [],
495
+ app_positions: subaccountData.app_positions || [],
496
+ tokens: subaccountData.tokens || [],
497
+ },
498
+ };
499
+ (0, debank_1.parseVaultLevelDebank)(debankRes, protocolExposure, tokenExposure, subaccountAddress, exposurePerCategory, netValue);
500
+ unfilteredTokens = debankRes?.subaccount?.tokens;
501
+ const debankPerLoan = (0, debank_1.parseLoanLevelDebank)(debankRes);
502
+ defiPerBorrower[subaccountAddress] = debankPerLoan;
503
+ }
504
+ }
505
+ }
506
+ // Fetch portfolio data for non-EVM borrowers (EVM handled by fetchVaultDebankResponse above).
507
+ // CeFi and OTC fetches below run for ALL borrowers regardless of chain type.
508
+ for (const borrower of uniqueBorrowers) {
509
+ const chainType = (0, core_1.getAddressChainType)(borrower);
510
+ if (chainType !== 'evm') {
511
+ const fetcher = borrowerPortfolioFetchers[chainType];
512
+ if (fetcher) {
513
+ try {
514
+ const debankRes = await fetcher(borrower);
515
+ if (debankRes) {
516
+ (0, debank_1.parseVaultLevelDebank)(debankRes, protocolExposure, tokenExposure, borrower, exposurePerCategory, netValue);
517
+ unfilteredTokens = debankRes?.subaccount?.tokens;
518
+ defiPerBorrower[borrower] = (0, debank_1.parseLoanLevelDebank)(debankRes);
519
+ }
520
+ else {
521
+ debankErr = true;
522
+ }
523
+ }
524
+ catch (error) {
525
+ core_1.Logger.log.error(`getVaultAllocations:${chainType}`, error, {
526
+ vault,
527
+ borrower,
528
+ });
529
+ debankErr = true;
530
+ }
531
+ }
532
+ else {
533
+ // Not an error: portfolio providers simply have no coverage for this
534
+ // chain type yet (Stellar, Sui). Reporting it as an exception filled
535
+ // Sentry with a known, permanent gap, and setting `debankErr` made
536
+ // every Stellar vault throw `failure to fetch debank response`
537
+ // instead of returning the CeFi/OTC/loan allocations it did resolve.
538
+ core_1.Logger.log.warn(`getVaultAllocations:no_fetcher`, {
539
+ vault,
540
+ borrower,
541
+ chainType,
542
+ reason: `No portfolio fetcher for chain type: ${chainType}`,
543
+ });
544
+ }
545
+ }
546
+ // fetch cefi_positions
547
+ try {
548
+ const cefiResponse = await (0, core_1.fetchAugustWithKey)(options.augustKey, core_1.WEBSERVER_ENDPOINTS.subaccount.cefi(borrower), { headers: options?.headers });
549
+ if (cefiResponse.status !== 200) {
550
+ // 404 means "this subaccount has no CeFi position", which is the
551
+ // common case for most borrowers — a breadcrumb, not an exception.
552
+ logSubaccountFetchFailure('getVaultAllocations:cefi', cefiResponse.status, cefiResponse.statusText, { vault, borrower });
553
+ }
554
+ if (cefiResponse.status === 200) {
555
+ const cefiRes = (await cefiResponse.json());
556
+ cefiExposure = [...cefiExposure, ...cefiRes];
557
+ }
558
+ }
559
+ catch (e) {
560
+ logSubaccountFetchFailure('getVaultAllocations:cefi', httpStatusFromError(e), e, { vault, borrower });
561
+ }
562
+ // fetch otc_positions
563
+ try {
564
+ const otcResponse = await (0, core_1.fetchAugustWithKey)(options.augustKey, core_1.WEBSERVER_ENDPOINTS.subaccount.otc_positions(borrower), { headers: options?.headers });
565
+ if (otcResponse.status !== 200) {
566
+ // 404 means "this subaccount has no OTC positions" — expected for
567
+ // nearly every borrower. See logSubaccountFetchFailure.
568
+ logSubaccountFetchFailure('getVaultAllocations:otc', otcResponse.status, otcResponse.statusText, { vault, borrower });
569
+ }
570
+ if (otcResponse.status === 200) {
571
+ const otcRes = (await otcResponse.json());
572
+ otcPositions[borrower] = otcRes;
573
+ }
574
+ }
575
+ catch (e) {
576
+ logSubaccountFetchFailure('getVaultAllocations:otc', httpStatusFromError(e), e, { vault, borrower });
577
+ }
578
+ }
579
+ }
580
+ const walletBalance = exposurePerCategory.wallet.reduce((sum, item) => sum + item.amount, 0);
581
+ const defiBalance = protocolExposure.reduce((sum, item) => sum + item.netUsdValue, 0);
582
+ const cefiBalance = cefiExposure.reduce((sum, item) => sum + item.value, 0);
583
+ exposurePerCategory.walletBalance = walletBalance;
584
+ exposurePerCategory.defiBalance = defiBalance;
585
+ exposurePerCategory.cefiBalance = cefiBalance;
586
+ netValue.value += cefiBalance;
587
+ if (debankErr) {
588
+ throw Error('failure to fetch debank response');
589
+ }
590
+ return {
591
+ defi: protocolExposure,
592
+ cefi: cefiExposure,
593
+ otc: otcPositions,
594
+ tokens: tokenExposure,
595
+ defiPerBorrower,
596
+ exposurePerCategory,
597
+ netValue: netValue.value,
598
+ // this would be the debank token response field without any parsing. Different from
599
+ // tokens. Could use a better name tho
600
+ unfilteredTokens: unfilteredTokens,
601
+ };
602
+ }
603
+ /**
604
+ * Coerce a subgraph numeric field to `bigint`, treating absent fields as zero.
605
+ *
606
+ * Why: the evm-1 and evm-2 subgraphs expose different field names on
607
+ * `WithdrawalRequested` — evm-1 emits `assets` plus explicit `year`/`month`/
608
+ * `day`, evm-2 emits `shares` and a raw `timestamp_`. Reading the wrong one
609
+ * yields `undefined`, and bare `BigInt(undefined)` throws
610
+ * `TypeError: Cannot convert undefined to a BigInt`, which aborted the whole
611
+ * redemption scan and returned an empty list to the caller (the second
612
+ * highest-volume SDK error in production).
613
+ *
614
+ * `BigInt(x) || BigInt(0)` does **not** protect against this — the throw
615
+ * happens before `||` is ever evaluated.
616
+ *
617
+ * @param value - Subgraph field: decimal string, number, bigint, `null`, or
618
+ * `undefined`.
619
+ * @returns The value as `bigint`, or `0n` when absent or non-numeric.
620
+ */
621
+ function subgraphAmountToBigInt(value) {
622
+ if (value === undefined || value === null || value === '')
623
+ return BigInt(0);
624
+ try {
625
+ return BigInt(value);
626
+ }
627
+ catch {
628
+ return BigInt(0);
629
+ }
630
+ }
631
+ async function getVaultAvailableRedemptions({ vault, wallet, options, prefetchedReads, }) {
632
+ try {
633
+ // Stellar vaults don't support on-chain redemptions yet
634
+ if ((0, utils_3.isStellarAddress)(vault)) {
635
+ core_1.Logger.log.warn('getVaultAvailableRedemptions', `Available redemptions is not yet supported for Stellar vaults: ${vault}`);
636
+ return {
637
+ availableRedemptions: [],
638
+ pendingRedemptions: [],
639
+ ...(options.verbose && {
640
+ processedWithdrawals: [],
641
+ requestedWithdrawals: [],
642
+ }),
643
+ };
644
+ }
645
+ // setup
646
+ const provider = (0, core_1.createProvider)(options.rpcUrl);
647
+ const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vault, options?.headers, false, false))?.[0];
648
+ if (!tokenizedVault) {
649
+ return {
650
+ ...{
651
+ availableRedemptions: [],
652
+ pendingRedemptions: [],
653
+ },
654
+ ...(options.verbose && {
655
+ processedWithdrawals: [],
656
+ requestedWithdrawals: [],
657
+ }),
658
+ };
659
+ }
660
+ // version split logic
661
+ const version = (0, core_1.getVaultVersionV2)(tokenizedVault);
662
+ let decimals;
663
+ // Use ABI_LENDING_POOL_V2 for both versions as it has the methods we need
664
+ const vaultContract = (0, core_1.createContract)({
665
+ address: vault,
666
+ abi: abis_1.ABI_LENDING_POOL_V2,
667
+ provider,
668
+ });
669
+ if (version === 'evm-2') {
670
+ decimals = await (0, core_1.getDecimals)(provider, await (0, core_1.getReceiptTokenAddress)(provider, vault), false);
671
+ }
672
+ else {
673
+ decimals = await (0, core_1.getDecimals)(provider, vault);
674
+ }
675
+ const lagDuration = typeof prefetchedReads?.lagDuration === 'number'
676
+ ? prefetchedReads.lagDuration
677
+ : Number(await vaultContract.lagDuration());
678
+ const { withdrawalRequesteds, withdrawalProcesseds } = await (0, subgraph_1.getSubgraphAllWithdrawals)(vault, provider);
679
+ // format
680
+ const availableRedemptions = [];
681
+ const pendingRedemptions = [];
682
+ for (const ev of withdrawalRequesteds) {
683
+ // ensure event is defined
684
+ if (!ev || typeof ev !== 'object') {
685
+ core_1.Logger.log.warn('getVaultAvailableRedemptions', `Skipping invalid event: ${ev}`);
686
+ continue;
687
+ }
688
+ let month = 0;
689
+ let day = 0;
690
+ let year = 0;
691
+ if (version === 'evm-2') {
692
+ // Use UTC-based computeClaimableDate to mirror contract logic exactly
693
+ const claimable = (0, date_utils_1.computeClaimableDate)(Number(ev.timestamp_), lagDuration);
694
+ year = claimable.year;
695
+ month = claimable.month;
696
+ day = claimable.day;
697
+ }
698
+ // Validate required event properties
699
+ if (!ev.year || !ev.month || !ev.day || !ev.receiverAddr) {
700
+ if (version !== 'evm-2') {
701
+ core_1.Logger.log.warn('getVaultAvailableRedemptions', `Skipping event with missing required properties: ${ev}`);
702
+ continue;
703
+ }
704
+ else {
705
+ if (!year || !month || !day || !ev.receiverAddr) {
706
+ if (version !== 'evm-2') {
707
+ core_1.Logger.log.warn('getVaultAvailableRedemptions', `Skipping event with missing required properties: ${ev}`);
708
+ continue;
709
+ }
710
+ }
711
+ }
712
+ }
713
+ const fullDate = version === 'evm-2'
714
+ ? new Date(Date.UTC(year, month - 1, day))
715
+ : new Date(Number(ev.year), Number(ev.month) - 1, Number(ev.day));
716
+ // Match request to processed event using deterministic UTC date key
717
+ const requestDateKey = version === 'evm-2'
718
+ ? (0, date_utils_1.formatDateKey)(year, month, day)
719
+ : (0, date_utils_1.formatDateKey)(Number(ev.year), Number(ev.month), Number(ev.day));
720
+ // NOTE: evm-2 subgraph does not emit WithdrawalProcessed events, so matching
721
+ // relies on timestamp-based date comparison for evm-2 (deterministic for evm-1).
722
+ const foundRedemptionAgainstClaim = withdrawalProcesseds.find((h) => {
723
+ const proc = h;
724
+ if (proc.receiverAddr !== ev.receiverAddr)
725
+ return false;
726
+ if (version === 'evm-2') {
727
+ const procDate = new Date(Number(proc.timestamp_) * 1000);
728
+ const procKey = (0, date_utils_1.formatDateKey)(procDate.getUTCFullYear(), procDate.getUTCMonth() + 1, procDate.getUTCDate());
729
+ return procKey === requestDateKey;
730
+ }
731
+ const procDate = new Date(proc.processedOn);
732
+ const procKey = (0, date_utils_1.formatDateKey)(procDate.getUTCFullYear(), procDate.getUTCMonth() + 1, procDate.getUTCDate());
733
+ return procKey === requestDateKey;
734
+ });
735
+ if (wallet && (0, ethers_1.isAddress)(wallet)) {
736
+ // check if connected users address
737
+ if (ev?.receiverAddr?.toLowerCase() === wallet.toLowerCase()) {
738
+ const alreadyRedeemed = version === 'evm-2'
739
+ ? withdrawalProcesseds.find((red) => {
740
+ // Match by receiver + UTC date key (deterministic)
741
+ if (red.receiverAddr.toLowerCase() !== wallet.toLowerCase()) {
742
+ return false;
743
+ }
744
+ const redDate = new Date(Number(red.timestamp_) * 1000);
745
+ const redKey = (0, date_utils_1.formatDateKey)(redDate.getUTCFullYear(), redDate.getUTCMonth() + 1, redDate.getUTCDate());
746
+ return redKey === requestDateKey;
747
+ })
748
+ : availableRedemptions.find((red) => subgraphAmountToBigInt(red.day.raw) ===
749
+ subgraphAmountToBigInt(ev.day) &&
750
+ subgraphAmountToBigInt(red.month.raw) ===
751
+ subgraphAmountToBigInt(ev.month) &&
752
+ subgraphAmountToBigInt(red.year.raw) ===
753
+ subgraphAmountToBigInt(ev.year));
754
+ if (!(foundRedemptionAgainstClaim && alreadyRedeemed)) {
755
+ // double check if user has already claimed
756
+ try {
757
+ switch (version) {
758
+ case 'evm-2': {
759
+ // 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
760
+ if (alreadyRedeemed) {
761
+ const index = withdrawalProcesseds.findIndex((item) => item.id === alreadyRedeemed.id);
762
+ if (index !== -1) {
763
+ withdrawalProcesseds.splice(index, 1);
764
+ }
765
+ }
766
+ if (!alreadyRedeemed) {
767
+ // Recompute claimable date (includes lag + 300s manipulation window) to determine if claimable now
768
+ const claimable = (0, date_utils_1.computeClaimableDate)(Number(ev.timestamp_), lagDuration);
769
+ const pendingCondition = (0, date_utils_1.isClaimableNow)(claimable.epoch, Math.floor(Date.now() / 1000));
770
+ if (pendingCondition) {
771
+ // Has passed lag duration - check if claim is not settled before adding to pending
772
+ try {
773
+ const burnableAmount = (await vaultContract?.getBurnableAmountByReceiver?.(BigInt(year), BigInt(month), BigInt(day), (0, ethers_1.getAddress)(ev.receiverAddr))) || BigInt(0);
774
+ const claimAmount = subgraphAmountToBigInt(ev.shares);
775
+ // If burnableAmount >= claimAmount, claim hasn't been settled by processAllClaims
776
+ // Only add to pending if it's still unsettled
777
+ if (burnableAmount >= claimAmount) {
778
+ pendingRedemptions.push({
779
+ id: ev.transactionHash_ || ev.id,
780
+ hash: ev.transactionHash_ || ev.id,
781
+ timestamp: Number(ev.timestamp_),
782
+ receiver: ev.receiverAddr,
783
+ day: (0, core_1.toNormalizedBn)(day, 0),
784
+ month: (0, core_1.toNormalizedBn)(month, 0),
785
+ year: (0, core_1.toNormalizedBn)(year, 0),
786
+ amount: (0, core_1.toNormalizedBn)(subgraphAmountToBigInt(ev.shares), decimals),
787
+ date: fullDate,
788
+ vault,
789
+ status: types_1.VaultRedemptionStatus.READY_TO_CLAIM,
790
+ });
791
+ }
792
+ }
793
+ catch (burnableError) {
794
+ core_1.Logger.log.warn('getVaultAvailableRedemptions', `Failed to check burnable amount for pending redemption: ${burnableError}`);
795
+ // If check fails, don't add to pending to be safe
796
+ }
797
+ }
798
+ else {
799
+ // Hasn't passed lag duration - add to available redemptions only
800
+ availableRedemptions.push({
801
+ id: ev.transactionHash_ || ev.id,
802
+ hash: ev.transactionHash_ || ev.id,
803
+ timestamp: Number(ev.timestamp_),
804
+ receiver: ev.receiverAddr,
805
+ day: (0, core_1.toNormalizedBn)(day, 0),
806
+ month: (0, core_1.toNormalizedBn)(month, 0),
807
+ year: (0, core_1.toNormalizedBn)(year, 0),
808
+ amount: (0, core_1.toNormalizedBn)(subgraphAmountToBigInt(ev.shares), decimals),
809
+ date: fullDate,
810
+ vault,
811
+ status: types_1.VaultRedemptionStatus.AWAITING_COOLDOWN,
812
+ });
813
+ }
814
+ }
815
+ break;
816
+ }
817
+ default: {
818
+ // Safe to read the raw subgraph dates here: the validation
819
+ // block above `continue`s for any non-evm-2 event missing
820
+ // `year`/`month`/`day`, so this path is only reached with all
821
+ // three present. The coercion below therefore never
822
+ // substitutes 0 for a real date.
823
+ const trueClaimableAmount = await vaultContract?.getClaimableAmountByReceiver?.(subgraphAmountToBigInt(ev.year), subgraphAmountToBigInt(ev.month), subgraphAmountToBigInt(ev.day), (0, ethers_1.getAddress)(wallet));
824
+ if (trueClaimableAmount > BigInt(0)) {
825
+ // Use computeClaimableDate for UTC-correct date with lag
826
+ const v1Claimable = (0, date_utils_1.computeClaimableDate)(Number(ev.timestamp_), lagDuration);
827
+ const pendingCondition = (0, date_utils_1.isClaimableNow)(v1Claimable.epoch, Math.floor(Date.now() / 1000));
828
+ if (pendingCondition) {
829
+ // Has passed lag duration - check if claim is not settled before adding to pending
830
+ try {
831
+ year = v1Claimable.year;
832
+ month = v1Claimable.month;
833
+ day = v1Claimable.day;
834
+ const burnableAmount = (await vaultContract?.getBurnableAmountByReceiver?.(BigInt(year), BigInt(month), BigInt(day), (0, ethers_1.getAddress)(ev.receiverAddr))) || BigInt(0);
835
+ const claimAmount = trueClaimableAmount || BigInt(0);
836
+ // If burnableAmount >= claimAmount, claim hasn't been settled by processAllClaims
837
+ // Only add to pending if it's still unsettled
838
+ if (burnableAmount >= claimAmount) {
839
+ pendingRedemptions.push({
840
+ id: ev.transactionHash_ || ev.id,
841
+ hash: ev.transactionHash_ || ev.id,
842
+ timestamp: Number(ev.timestamp_),
843
+ receiver: ev.receiverAddr,
844
+ day: (0, core_1.toNormalizedBn)(ev.day, 0),
845
+ month: (0, core_1.toNormalizedBn)(ev.month, 0),
846
+ year: (0, core_1.toNormalizedBn)(ev.year, 0),
847
+ amount: (0, core_1.toNormalizedBn)(trueClaimableAmount || BigInt(0), decimals),
848
+ date: fullDate,
849
+ vault,
850
+ status: types_1.VaultRedemptionStatus.READY_TO_CLAIM,
851
+ });
852
+ }
853
+ }
854
+ catch (burnableError) {
855
+ core_1.Logger.log.warn('getVaultAvailableRedemptions', `Failed to check burnable amount for pending redemption: ${burnableError}`);
856
+ // If check fails, don't add to pending to be safe
857
+ }
858
+ }
859
+ else {
860
+ // Hasn't passed lag duration - add to available redemptions only
861
+ availableRedemptions.push({
862
+ id: ev.transactionHash_ || ev.id,
863
+ hash: ev.transactionHash_ || ev.id,
864
+ timestamp: Number(ev.timestamp_),
865
+ receiver: ev.receiverAddr,
866
+ day: (0, core_1.toNormalizedBn)(ev.day, 0),
867
+ month: (0, core_1.toNormalizedBn)(ev.month, 0),
868
+ year: (0, core_1.toNormalizedBn)(ev.year, 0),
869
+ amount: (0, core_1.toNormalizedBn)(trueClaimableAmount, decimals),
870
+ date: fullDate,
871
+ vault,
872
+ status: types_1.VaultRedemptionStatus.AWAITING_COOLDOWN,
873
+ });
874
+ }
875
+ }
876
+ break;
877
+ }
878
+ }
879
+ }
880
+ catch (contractError) {
881
+ core_1.Logger.log.error('getVaultAvailableRedemptions', `Contract call failed: ${contractError}`);
882
+ }
883
+ }
884
+ }
885
+ }
886
+ else {
887
+ if (foundRedemptionAgainstClaim) {
888
+ availableRedemptions.push({
889
+ id: ev.transactionHash_ || ev.id,
890
+ hash: ev.transactionHash_ || ev.id,
891
+ timestamp: Number(ev.timestamp_),
892
+ receiver: ev.receiverAddr,
893
+ day: (0, core_1.toNormalizedBn)(ev.day, 0),
894
+ month: (0, core_1.toNormalizedBn)(ev.month, 0),
895
+ year: (0, core_1.toNormalizedBn)(ev.year, 0),
896
+ amount: (0, core_1.toNormalizedBn)(subgraphAmountToBigInt(ev.assets), decimals),
897
+ date: fullDate,
898
+ vault,
899
+ status: types_1.VaultRedemptionStatus.AWAITING_COOLDOWN,
900
+ });
901
+ }
902
+ }
903
+ }
904
+ return {
905
+ ...{
906
+ availableRedemptions,
907
+ pendingRedemptions,
908
+ },
909
+ ...(options.verbose && {
910
+ processedWithdrawals: withdrawalProcesseds,
911
+ requestedWithdrawals: withdrawalRequesteds,
912
+ }),
913
+ };
914
+ }
915
+ catch (e) {
916
+ core_1.Logger.log.error('getVaultAvailableRedemptions', e, { vault, wallet });
917
+ return {
918
+ ...{
919
+ availableRedemptions: [],
920
+ pendingRedemptions: [],
921
+ },
922
+ ...(options.verbose && {
923
+ processedWithdrawals: [],
924
+ requestedWithdrawals: [],
925
+ }),
926
+ };
927
+ }
928
+ }
929
+ async function getVaultRedemptionHistory({ vault, wallet, lookbackBlocks, options, }) {
930
+ try {
931
+ // Stellar vaults: no on-chain redemption history yet
932
+ if ((0, utils_3.isStellarAddress)(vault)) {
933
+ core_1.Logger.log.warn('getVaultRedemptionHistory', `Redemption history is not yet supported for Stellar vaults: ${vault}`);
934
+ return [];
935
+ }
936
+ // setup
937
+ const provider = (0, core_1.createProvider)(options.rpcUrl);
938
+ const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vault, options?.headers, false, false))?.[0];
939
+ if (!tokenizedVault) {
940
+ core_1.Logger.log.warn('getVaultRedemptionHistory', `Vault not found in backend: ${vault}`);
941
+ return [];
942
+ }
943
+ const chainId = tokenizedVault.chain;
944
+ const version = (0, core_1.getVaultVersionV2)(tokenizedVault);
945
+ const isV2 = version === 'evm-2';
946
+ // Use correct ABI based on vault version — different WithdrawalProcessed event signatures
947
+ const vaultAbi = isV2 ? TokenizedVaultV2_1.ABI_TOKENIZED_VAULT_V2 : abis_1.ABI_LENDING_POOL_V2;
948
+ const poolContract = (0, core_1.createContract)({
949
+ address: vault,
950
+ abi: vaultAbi,
951
+ provider,
952
+ });
953
+ // query getLogs
954
+ const currentBlock = await provider.getBlockNumber();
955
+ const blockSkip = (0, core_1.determineBlockSkipInternal)(chainId);
956
+ const cutoffBlock = currentBlock - (lookbackBlocks ?? (0, core_1.determineBlockCutoff)(chainId));
957
+ const BATCH_SIZE = 20; // Max concurrent RPC requests per batch
958
+ core_1.Logger.log.info('getVaultRedemptionHistory', {
959
+ vault,
960
+ version,
961
+ isV2,
962
+ chainId,
963
+ currentBlock,
964
+ cutoffBlock,
965
+ blockSkip,
966
+ totalRangeBlocks: currentBlock - cutoffBlock,
967
+ });
968
+ // Build all query ranges
969
+ const ranges = [];
970
+ let endBlock = currentBlock;
971
+ while (endBlock >= cutoffBlock) {
972
+ const fromBlock = Math.max(endBlock - blockSkip, cutoffBlock);
973
+ ranges.push({ from: fromBlock, to: endBlock });
974
+ endBlock = fromBlock - 1;
975
+ }
976
+ core_1.Logger.log.info('getVaultRedemptionHistory', {
977
+ totalBatches: Math.ceil(ranges.length / BATCH_SIZE),
978
+ totalChunks: ranges.length,
979
+ firstRange: ranges[0],
980
+ lastRange: ranges[ranges.length - 1],
981
+ });
982
+ // Execute in batches to avoid RPC rate limits on high-block-count chains.
983
+ // Use allSettled + explicit throw so a chunk failure surfaces to the caller
984
+ // instead of silently returning an empty result (which used to corrupt
985
+ // `getWithdrawalRequestsWithStatus` into reporting `ready_to_claim` for
986
+ // requests that were already processed).
987
+ const logs = [];
988
+ for (let i = 0; i < ranges.length; i += BATCH_SIZE) {
989
+ const batch = ranges.slice(i, i + BATCH_SIZE);
990
+ const batchResults = await Promise.allSettled(batch.map((r) => poolContract.queryFilter('WithdrawalProcessed', BigInt(r.from), BigInt(r.to))));
991
+ const failed = batchResults.filter((r) => r.status === 'rejected');
992
+ if (failed.length > 0) {
993
+ const firstReason = failed[0].reason;
994
+ const firstMessage = firstReason instanceof Error
995
+ ? firstReason.message
996
+ : String(firstReason);
997
+ // Breadcrumb only — the throw below is caught and logged by this
998
+ // function's own catch block, so capturing here duplicated every
999
+ // failure into a second Sentry issue ("log fetch chunk(s) failed").
1000
+ core_1.Logger.log.warn('getVaultRedemptionHistory', {
1001
+ reason: 'log fetch chunk(s) failed',
1002
+ failedChunks: failed.length,
1003
+ totalChunks: batch.length,
1004
+ firstReason: firstMessage,
1005
+ });
1006
+ throw new Error(`Failed to fetch WithdrawalProcessed logs (${failed.length}/${batch.length} chunk(s) rejected): ${firstMessage}`);
1007
+ }
1008
+ const batchLogs = batchResults.flatMap((r) => r.status === 'fulfilled' ? r.value : []);
1009
+ logs.push(...batchLogs);
1010
+ }
1011
+ // evm-1: WithdrawalProcessed(uint256 assetsAmount, uint256 processedOn, address receiverAddr, uint256 requestedOn)
1012
+ // evm-2: WithdrawalProcessed(uint256 assetsAmount, address indexed receiverAddr)
1013
+ const iface = new ethers_1.ethers.Interface(isV2
1014
+ ? [
1015
+ 'event WithdrawalProcessed(uint256 assetsAmount, address indexed receiverAddr)',
1016
+ ]
1017
+ : [
1018
+ 'event WithdrawalProcessed(uint256 assetsAmount, uint256 processedOn, address receiverAddr, uint256 requestedOn)',
1019
+ ]);
1020
+ // get pool decimals
1021
+ const decimals = await (0, core_1.getDecimals)(provider, vault);
1022
+ // For evm-2 events (no timestamps in event), fetch block timestamps
1023
+ let blockTimestamps;
1024
+ if (isV2 && logs.length > 0) {
1025
+ const uniqueBlocks = [...new Set(logs.map((l) => l.blockNumber))];
1026
+ const blockResults = await Promise.all(uniqueBlocks.map((bn) => provider.getBlock(bn)));
1027
+ const timestamps = new Map();
1028
+ blockResults.forEach((block) => {
1029
+ if (block)
1030
+ timestamps.set(block.number, block.timestamp);
1031
+ });
1032
+ blockTimestamps = timestamps;
1033
+ }
1034
+ // format — iterate logs directly to preserve transactionHash
1035
+ const redemptions = [];
1036
+ logs.forEach((log) => {
1037
+ const ev = iface.parseLog(log);
1038
+ if (!ev?.args)
1039
+ return;
1040
+ let item;
1041
+ if (isV2) {
1042
+ const [assetsAmount, receiverAddr] = ev.args;
1043
+ const blockTs = blockTimestamps?.get(log.blockNumber) ?? 0;
1044
+ const blockDate = new Date(blockTs * 1000);
1045
+ item = {
1046
+ receiver: receiverAddr,
1047
+ amount: (0, core_1.toNormalizedBn)(assetsAmount, decimals),
1048
+ processed: blockDate,
1049
+ requested: new Date(0), // evm-2 event has no requestedOn field; epoch 0 is a sentinel for "unknown"
1050
+ vault,
1051
+ transactionHash: log.transactionHash,
1052
+ };
1053
+ }
1054
+ else {
1055
+ const [assetsAmount, processedOn, receiverAddr, requestedOn] = ev.args;
1056
+ item = {
1057
+ receiver: receiverAddr,
1058
+ amount: (0, core_1.toNormalizedBn)(assetsAmount, decimals),
1059
+ processed: new Date(Number(processedOn) * 1000),
1060
+ requested: new Date(Number(requestedOn) * 1000),
1061
+ vault,
1062
+ transactionHash: log.transactionHash,
1063
+ };
1064
+ }
1065
+ if (wallet) {
1066
+ if (item.receiver?.toLowerCase() === wallet.toLowerCase()) {
1067
+ redemptions.push(item);
1068
+ }
1069
+ }
1070
+ else {
1071
+ redemptions.push(item);
1072
+ }
1073
+ });
1074
+ core_1.Logger.log.info('getVaultRedemptionHistory', redemptions.slice(0, 1));
1075
+ return redemptions;
1076
+ }
1077
+ catch (e) {
1078
+ // Log loudly and re-throw. Previously this caught all errors and returned
1079
+ // [], which silently corrupted callers (notably getWithdrawalRequestsWithStatus,
1080
+ // which would then report `ready_to_claim` for already-processed requests).
1081
+ // Returning [] is reserved for the explicit early returns inside the try
1082
+ // (unsupported chains, vault not in backend) — not for unexpected failures.
1083
+ core_1.Logger.log.error('getVaultRedemptionHistory', e, { vault, wallet });
1084
+ throw e;
1085
+ }
1086
+ }
1087
+ // Moved to core/helpers/swap-router.ts to break the adapters/evm ↔
1088
+ // modules/vaults/getters import cycle (CLAUDE.md §9); re-exported here for
1089
+ // back-compat with existing importers.
1090
+ var swap_router_1 = require("../../core/helpers/swap-router");
1091
+ Object.defineProperty(exports, "getSwapRouterDepositResult", { enumerable: true, get: function () { return swap_router_1.getSwapRouterDepositResult; } });
1092
+ /**
1093
+ * Vault Positions
1094
+ */
1095
+ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, options, prefetchedReads, }) {
1096
+ try {
1097
+ const tokenizedVaults = await (0, core_1.fetchTokenizedVault)(vault ? vault : undefined, options?.headers, false, false);
1098
+ if (!tokenizedVaults || tokenizedVaults.length === 0) {
1099
+ return [];
1100
+ }
1101
+ // check if there are redemptions available or if you stake in this pool
1102
+ function renderStatus(_redemptions, _balance) {
1103
+ if (_redemptions?.length)
1104
+ return 'REDEEM';
1105
+ if (BigInt(_balance.raw) > BigInt(0))
1106
+ return 'STAKED';
1107
+ return 'PENDING';
1108
+ }
1109
+ const promises = await Promise.all(tokenizedVaults.map(async (v) => {
1110
+ try {
1111
+ // @solana: skip if not solana address
1112
+ if (utils_2.SolanaUtils.isSolanaAddress(v.address)) {
1113
+ // Default decimals: backend metadata, then SDK fallback. The mint's
1114
+ // own decimals (read below) override this once we have a real
1115
+ // tokenAmount response.
1116
+ const fallbackShareDecimals = v.solana_vault_metadata?.deposit_token_decimals ??
1117
+ SolanaConstants.fallbackDecimals;
1118
+ let balance = (0, core_1.toNormalizedBn)(0, fallbackShareDecimals);
1119
+ // Defensive: without a Solana adapter we can't fetch on-chain
1120
+ // state. The outer filter in modules/vaults/main.ts already gates
1121
+ // on `this.solanaService`, so this is a belt-and-suspenders guard
1122
+ // for any external caller that constructs `options` by hand.
1123
+ if (utils_2.SolanaUtils.isSolanaAddress(solanaWallet) &&
1124
+ options.solanaService) {
1125
+ const vaultProgramId = utils_2.SolanaUtils.resolveProgramId(String(v.address), v.solana_vault_metadata);
1126
+ const vaultStateRes = await options.solanaService.getVaultState(vaultProgramId, SolanaConstants.vaultIdl, v.solana_vault_metadata?.vault_state_pda ?? undefined);
1127
+ const vaultState = vaultStateRes?.vaultState;
1128
+ const { shareMint } = vaultState ?? { shareMint: null };
1129
+ if (shareMint) {
1130
+ // Always use the raw helper — it returns the on-chain u64
1131
+ // string and the mint's true decimals. The legacy `uiAmount`
1132
+ // (JS number) path lost precision and silently encoded `.raw`
1133
+ // against 18 decimals when fed back through `toNormalizedBn`
1134
+ // without a decimals argument, which broke every downstream
1135
+ // BigInt consumer (redemptions, max actions, allowances).
1136
+ const raw = await options.solanaService.fetchUserShareBalanceRaw(solanaWallet, shareMint);
1137
+ const decimals = raw.decimals ?? fallbackShareDecimals;
1138
+ balance = (0, core_1.toNormalizedBn)(BigInt(raw.amount), decimals);
1139
+ }
1140
+ }
1141
+ return {
1142
+ // Mirror the Stellar branch — return the backend's `v.address`,
1143
+ // not the outer `vault` parameter (which can be undefined for
1144
+ // multi-vault calls). No `as IAddress` cast: Solana base58 is
1145
+ // not 0x-hex; the IAddress typing across non-EVM positions is a
1146
+ // known wart documented in IVaultPosition.
1147
+ vault: v.address,
1148
+ status: renderStatus([], balance),
1149
+ availableRedemptions: [],
1150
+ pendingRedemptions: [],
1151
+ redeemable: balance,
1152
+ walletBalance: balance,
1153
+ };
1154
+ }
1155
+ if ((0, utils_3.isStellarAddress)(v.address)) {
1156
+ let balance = (0, core_1.toNormalizedBn)(0);
1157
+ if (stellarWallet && (0, utils_3.isStellarAddress)(stellarWallet)) {
1158
+ const stellarNetwork = v.stellar_vault_metadata?.network_name ?? 'mainnet';
1159
+ // Only use the configured RPC override for its own network.
1160
+ const stellarOverrideRpcUrl = options.stellarRpc?.network === stellarNetwork
1161
+ ? options.stellarRpc.rpcUrl
1162
+ : undefined;
1163
+ const position = await StellarGetters.getStellarUserPosition(v.address, stellarWallet, stellarNetwork, stellarOverrideRpcUrl);
1164
+ if (position && !position.decimalsFromFallback) {
1165
+ balance = (0, core_1.toNormalizedBn)(BigInt(position.shares), position.decimals);
1166
+ }
1167
+ else if (position) {
1168
+ // decimals() read failed: trusting the fallback (7) would
1169
+ // mis-scale the balance for ERC4626 offset vaults (AUGUST-6381).
1170
+ // Show zero rather than a confidently-wrong value.
1171
+ core_1.Logger.log.warn('getVaultPositions', 'Stellar decimals() unresolved — refusing to scale balance with fallback decimals (showing zero)', { vault: v.address, wallet: stellarWallet });
1172
+ }
1173
+ else {
1174
+ core_1.Logger.log.warn('getVaultPositions', 'Stellar position query failed — showing zero balance', { vault: v.address, wallet: stellarWallet });
1175
+ }
1176
+ }
1177
+ else if (stellarWallet) {
1178
+ core_1.Logger.log.warn('getVaultPositions', 'Invalid stellarWallet address format', { vault: v.address, stellarWallet });
1179
+ }
1180
+ // Stellar vaults have no on-chain redemption queue yet — redeemable is always 0
1181
+ return {
1182
+ vault: v.address,
1183
+ status: renderStatus([], balance),
1184
+ availableRedemptions: [],
1185
+ pendingRedemptions: [],
1186
+ redeemable: (0, core_1.toNormalizedBn)(0),
1187
+ walletBalance: balance,
1188
+ };
1189
+ }
1190
+ // create provider
1191
+ const provider = (0, core_1.createProvider)(options.rpcUrl);
1192
+ // version split logic
1193
+ let decimals;
1194
+ const version = (0, core_1.getVaultVersionV2)(v);
1195
+ // Use ABI_LENDING_POOL_V2 as the common ABI for vault operations
1196
+ const vaultContract = (0, core_1.createContract)({
1197
+ provider,
1198
+ abi: abis_1.ABI_LENDING_POOL_V2,
1199
+ address: v.address,
1200
+ });
1201
+ // Batched reads only apply to the vault this call was made for —
1202
+ // guard against a multi-row backend response reusing them.
1203
+ const prefetched = v.address?.toLowerCase?.() === String(vault).toLowerCase()
1204
+ ? prefetchedReads
1205
+ : undefined;
1206
+ // get vault metadata and balance based on version
1207
+ let bal = BigInt(0);
1208
+ if (version === 'evm-2') {
1209
+ const receiptAddress = await (0, core_1.getReceiptTokenAddress)(provider, vault);
1210
+ decimals = await (0, core_1.getDecimals)(provider, receiptAddress, false);
1211
+ if (wallet && (0, ethers_1.isAddress)(wallet)) {
1212
+ if (typeof prefetched?.balance === 'bigint') {
1213
+ bal = prefetched.balance;
1214
+ }
1215
+ else {
1216
+ const receiptContract = (0, core_1.createContract)({
1217
+ provider,
1218
+ abi: TokenizedVaultV2Receipt_1.ABI_TOKENIZED_VAULT_V2_RECEIPT,
1219
+ address: receiptAddress,
1220
+ });
1221
+ bal = await receiptContract.balanceOf(wallet);
1222
+ }
1223
+ }
1224
+ }
1225
+ else {
1226
+ decimals = await (0, core_1.getDecimals)(provider, vault);
1227
+ if (wallet && (0, ethers_1.isAddress)(wallet)) {
1228
+ bal =
1229
+ typeof prefetched?.balance === 'bigint'
1230
+ ? prefetched.balance
1231
+ : await vaultContract.balanceOf(wallet);
1232
+ }
1233
+ }
1234
+ const balance = (0, core_1.toNormalizedBn)(bal, decimals);
1235
+ // check available redemptions
1236
+ const { availableRedemptions, pendingRedemptions } = await getVaultAvailableRedemptions({
1237
+ vault: v.address,
1238
+ wallet,
1239
+ options,
1240
+ prefetchedReads: prefetched,
1241
+ });
1242
+ // get aggregate redemption amount
1243
+ const aggregateAvailableRedemptions = availableRedemptions.reduce((acc, curr) => acc + BigInt(curr.amount.raw), BigInt(0));
1244
+ // return obj
1245
+ return {
1246
+ vault,
1247
+ status: renderStatus(availableRedemptions, balance),
1248
+ availableRedemptions,
1249
+ pendingRedemptions,
1250
+ redeemable: (0, core_1.toNormalizedBn)(aggregateAvailableRedemptions, decimals),
1251
+ walletBalance: balance,
1252
+ };
1253
+ }
1254
+ catch (vaultError) {
1255
+ core_1.Logger.log.error(`getVaultPositions`, `Error processing vault ${v.address}: ${vaultError}`);
1256
+ return {
1257
+ vault,
1258
+ status: 'PENDING',
1259
+ availableRedemptions: [],
1260
+ pendingRedemptions: [],
1261
+ redeemable: (0, core_1.toNormalizedBn)(0),
1262
+ walletBalance: (0, core_1.toNormalizedBn)(0),
1263
+ };
1264
+ }
1265
+ }));
1266
+ return promises.flat();
1267
+ }
1268
+ catch (e) {
1269
+ core_1.Logger.log.error('getVaultPositions', e, {
1270
+ vault,
1271
+ wallet,
1272
+ solanaWallet,
1273
+ stellarWallet,
1274
+ });
1275
+ throw new Error(`#getVaultPositions::${vault}:${e?.message}`);
1276
+ }
1277
+ }
1278
+ /**
1279
+ * @deprecated use getVaultHistoricalTimeseries instead
1280
+ */
1281
+ async function getVaultApy({ vault, options, historical, }) {
1282
+ try {
1283
+ if (!vault)
1284
+ throw new Error('Vault input parameter is undefined.');
1285
+ // only looking for current apy
1286
+ if (!historical) {
1287
+ const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vault, options?.headers))?.[0];
1288
+ return [
1289
+ {
1290
+ value: tokenizedVault?.reported_apy?.apy || 0,
1291
+ timestamp: new Date().toISOString(),
1292
+ },
1293
+ ];
1294
+ }
1295
+ else {
1296
+ // historical apy
1297
+ function paramBuilder(params, baseUrl) {
1298
+ if (!params)
1299
+ return '';
1300
+ const urlParams = new URLSearchParams();
1301
+ if (params.daysAgo)
1302
+ urlParams.append('days_ago', params.daysAgo.toString());
1303
+ if (params.order)
1304
+ urlParams.append('order', params.order.toString());
1305
+ if (params.interval)
1306
+ urlParams.append('interval', params.interval.toString());
1307
+ const query = urlParams.toString();
1308
+ if (!query)
1309
+ return '';
1310
+ return baseUrl?.includes('?') ? `&${query}` : `?${query}`;
1311
+ }
1312
+ const baseUrl = core_1.WEBSERVER_ENDPOINTS.public.tokenizedVault.historicalApy(vault);
1313
+ const historicalApy = await (0, core_1.fetchAugustPublic)(`${baseUrl}${paramBuilder(historical, baseUrl)}`, {
1314
+ headers: options?.headers
1315
+ ? options?.headers
1316
+ : undefined,
1317
+ });
1318
+ if (!historicalApy.ok)
1319
+ throw new Error('Failed to fetch historical apy.');
1320
+ const historicalApyData = (await historicalApy.json());
1321
+ return historicalApyData.data.values.map((val, index) => {
1322
+ const respectiveDate = historicalApyData.data.labels[index];
1323
+ return {
1324
+ timestamp: new Date(respectiveDate).toISOString(),
1325
+ value: val,
1326
+ };
1327
+ });
1328
+ }
1329
+ }
1330
+ catch (e) {
1331
+ core_1.Logger.log.error('getVaultApy', e, { vault });
1332
+ throw new Error(`#getVaultApy::${vault}:${e?.message}`);
1333
+ }
1334
+ }
1335
+ async function getRewardsStakingPositions({ rpcUrl, wallet, coinGeckoKey, }) {
1336
+ const REWARDS_CHAIN = 43114; // avalanche
1337
+ const REWARDS_SYMBOL = 'AVAX'; // avalanche
1338
+ const REWARDS_DECIMALS = 18; // avalanche
1339
+ const REWARDS_NAME = 'Avalanche'; // avalanche
1340
+ const APR_MULTIPLIER = 31536000;
1341
+ const UP_AUSD_SYMBOL = 'upAUSD';
1342
+ try {
1343
+ const provider = (0, core_1.createProvider)(rpcUrl);
1344
+ const chainId = await (0, core_1.getChainId)(provider);
1345
+ // Validate chain has rewards distributor
1346
+ if (chainId !== REWARDS_CHAIN) {
1347
+ core_1.Logger.log.warn('getStakingPositions:unsupported_chain', chainId);
1348
+ return [];
1349
+ }
1350
+ if (wallet && !(0, ethers_1.isAddress)(wallet)) {
1351
+ core_1.Logger.log.warn('getStakingPositions:invalid_wallet', wallet);
1352
+ return [];
1353
+ }
1354
+ // get reward distributor contract addresses
1355
+ const rewardDistributorAddresses = (0, core_1.REWARD_DISTRIBUTOR_ADDRESS)(chainId);
1356
+ // loop through each reward distributor contract address
1357
+ const positions = await Promise.all(rewardDistributorAddresses.map(async (contract, i) => {
1358
+ const rewardContract = (0, core_1.createContract)({
1359
+ address: contract,
1360
+ provider: provider,
1361
+ abi: abis_1.ABI_REWARD_DISTRIBUTOR,
1362
+ });
1363
+ // get reward distributor metadata
1364
+ const totalStaked = await rewardContract.totalStaked();
1365
+ const rewardsPerSecond = await rewardContract.rewardsPerSecond();
1366
+ // get staking token metadata
1367
+ const stakingTokenCalls = ['decimals', 'symbol', 'name', 'totalSupply'];
1368
+ const stakingTokenAddress = (await rewardContract.stakingToken());
1369
+ const stakingTokenContract = (0, core_1.createContract)({
1370
+ address: stakingTokenAddress,
1371
+ provider: provider,
1372
+ abi: abis_1.ABI_ERC20,
1373
+ });
1374
+ const [decimals, symbol, name] = await Promise.all(stakingTokenCalls.map((staking) => stakingTokenContract[staking]()));
1375
+ // if wallet address is passed, pull user balance of staked tokens
1376
+ let balance;
1377
+ let earned;
1378
+ if (wallet) {
1379
+ const formattedWallet = (0, ethers_1.getAddress)(wallet);
1380
+ balance = await rewardContract.balanceOf(formattedWallet);
1381
+ earned = await rewardContract.earned(formattedWallet);
1382
+ }
1383
+ // get appropriate token price
1384
+ const rewardTokenPriceInUsd = await (0, core_1.fetchTokenPrice)(REWARDS_SYMBOL, null, coinGeckoKey);
1385
+ const normalizedRewardTokenPrice = (0, core_1.toNormalizedBn)(rewardTokenPriceInUsd);
1386
+ // normalize numbers
1387
+ const normalizedTotalStakedInPool = (0, core_1.toNormalizedBn)(totalStaked, decimals);
1388
+ const normalizedRedeemable = (0, core_1.toNormalizedBn)(earned, REWARDS_DECIMALS);
1389
+ const normalizedRewardsPerSecond = (0, core_1.toNormalizedBn)(rewardsPerSecond, REWARDS_DECIMALS);
1390
+ const normalizedTotalStakedBalance = (0, core_1.toNormalizedBn)(balance, decimals);
1391
+ // calculate APR's
1392
+ const rewardTokenPriceToMultiply = symbol === UP_AUSD_SYMBOL
1393
+ ? Number(normalizedRewardTokenPrice?.normalized)
1394
+ : 1;
1395
+ const STAKED_APR = ((Number(normalizedRewardsPerSecond?.normalized) *
1396
+ APR_MULTIPLIER *
1397
+ rewardTokenPriceToMultiply) /
1398
+ (Number(normalizedTotalStakedInPool?.normalized) * Number(1))) *
1399
+ 100;
1400
+ const MAX_APR = ((Number(normalizedRewardsPerSecond?.normalized) *
1401
+ APR_MULTIPLIER *
1402
+ rewardTokenPriceToMultiply) /
1403
+ (Number(normalizedTotalStakedInPool?.normalized) * Number(1))) *
1404
+ 100;
1405
+ const activePosition = {
1406
+ id: String(i),
1407
+ rewardToken: {
1408
+ decimals: REWARDS_DECIMALS,
1409
+ symbol: REWARDS_SYMBOL,
1410
+ address: ethers_1.ZeroAddress,
1411
+ chain: REWARDS_CHAIN,
1412
+ redeemable: normalizedRedeemable,
1413
+ usd: normalizedRewardTokenPrice,
1414
+ name: REWARDS_NAME,
1415
+ },
1416
+ stakingToken: {
1417
+ decimals: Number(decimals),
1418
+ symbol: symbol,
1419
+ address: stakingTokenAddress,
1420
+ chain: REWARDS_CHAIN,
1421
+ totalStaked: normalizedTotalStakedBalance,
1422
+ usd: normalizedRewardTokenPrice,
1423
+ totalSupply: normalizedTotalStakedInPool,
1424
+ name: name,
1425
+ },
1426
+ rewardDistributor: contract,
1427
+ rewardPerSecond: (0, core_1.toNormalizedBn)(rewardsPerSecond),
1428
+ apy: STAKED_APR,
1429
+ maxApy: MAX_APR,
1430
+ chainId: REWARDS_CHAIN,
1431
+ };
1432
+ return activePosition;
1433
+ }));
1434
+ return positions;
1435
+ }
1436
+ catch (e) {
1437
+ core_1.Logger.log.error('getStakingPositions', e, { wallet });
1438
+ return [];
1439
+ }
1440
+ }
1441
+ async function getVaultTvl({ vault, options, historical, }) {
1442
+ try {
1443
+ if (!vault)
1444
+ throw new Error('Vault input parameter is undefined.');
1445
+ // Verify vault exists
1446
+ const _vaultExists = (await (0, core_1.fetchTokenizedVault)(vault))?.[0];
1447
+ if (!_vaultExists) {
1448
+ throw new Error(`Vault ${vault} not found in backend`);
1449
+ }
1450
+ // only wanting current APR
1451
+ if (!historical) {
1452
+ const version = (0, core_1.getVaultVersionV2)(_vaultExists);
1453
+ // Non-EVM early returns (no provider needed)
1454
+ if (version === 'sol-0') {
1455
+ return [
1456
+ {
1457
+ value: (0, core_1.toNormalizedBn)(0),
1458
+ timestamp: new Date().toISOString(),
1459
+ },
1460
+ ];
1461
+ }
1462
+ if (version === 'stellar-0') {
1463
+ const stellarDecimals = _vaultExists?.stellar_vault_metadata?.deposit_token_decimals ??
1464
+ constants_1.STELLAR_FALLBACK_DECIMALS;
1465
+ return [
1466
+ {
1467
+ value: (0, utils_1.backendTvlToNormalizedBn)(_vaultExists?.latest_reported_tvl, stellarDecimals),
1468
+ timestamp: new Date().toISOString(),
1469
+ },
1470
+ ];
1471
+ }
1472
+ const provider = (0, core_1.createProvider)(options.rpcUrl, options.chainId);
1473
+ switch (version) {
1474
+ case 'evm-2': {
1475
+ const receiptTokenAddress = await (0, core_1.getReceiptTokenAddress)(provider, vault);
1476
+ const receiptTokenDecimals = await (0, core_1.getDecimals)(provider, receiptTokenAddress, false);
1477
+ const vaultContract = (0, core_1.createContract)({
1478
+ address: vault,
1479
+ abi: TokenizedVaultV2_1.ABI_TOKENIZED_VAULT_V2,
1480
+ provider,
1481
+ });
1482
+ const totalAssets = await vaultContract.getTotalAssets();
1483
+ return [
1484
+ {
1485
+ value: (0, core_1.toNormalizedBn)(totalAssets, receiptTokenDecimals),
1486
+ timestamp: new Date().toISOString(),
1487
+ },
1488
+ ];
1489
+ }
1490
+ default: {
1491
+ const vaultContract = (0, core_1.createContract)({
1492
+ address: vault,
1493
+ abi: abis_1.ABI_LENDING_POOL_V2,
1494
+ provider,
1495
+ });
1496
+ const decimals = await (0, core_1.getDecimals)(provider, vault);
1497
+ const totalAssets = await vaultContract.totalAssets();
1498
+ return [
1499
+ {
1500
+ value: (0, core_1.toNormalizedBn)(totalAssets, decimals),
1501
+ timestamp: new Date().toISOString(),
1502
+ },
1503
+ ];
1504
+ }
1505
+ }
1506
+ // const vaultRes = await getVault({
1507
+ // vault,
1508
+ // options,
1509
+ // loans: false,
1510
+ // allocations: false,
1511
+ // });
1512
+ // const returnObj = [
1513
+ // { value: vaultRes.totalSupply, timestamp: new Date().toISOString() },
1514
+ // ];
1515
+ // Logger.log.info('getVaultTvl:current', returnObj);
1516
+ // return returnObj;
1517
+ }
1518
+ else {
1519
+ // historical tvl
1520
+ // check vault version early to guard non-EVM chains
1521
+ const version = (0, core_1.getVaultVersionV2)(_vaultExists);
1522
+ if (version === 'stellar-0' || version === 'sol-0') {
1523
+ core_1.Logger.log.warn('getVaultTvl:historical', 'Historical TVL not supported for non-EVM vaults', { vault, version });
1524
+ return [];
1525
+ }
1526
+ // if no order is passed but inputParams are passed
1527
+ if (typeof historical !== 'undefined' && !historical.order)
1528
+ historical.order = 'desc';
1529
+ // days is default interval
1530
+ if (typeof historical !== 'undefined' && !historical.interval)
1531
+ historical.interval = 'days';
1532
+ // setup
1533
+ const provider = (0, core_1.createProvider)(options.rpcUrl);
1534
+ const vaultContract = (0, core_1.createContract)({
1535
+ address: vault,
1536
+ abi: version === 'evm-2' ? TokenizedVaultV2_1.ABI_TOKENIZED_VAULT_V2 : abis_1.ABI_LENDING_POOL_V2,
1537
+ provider,
1538
+ });
1539
+ let minAbi = [];
1540
+ let functionName = '';
1541
+ let vaultDecimals = 0;
1542
+ switch (version) {
1543
+ case 'evm-2': {
1544
+ minAbi = ['function getTotalAssets() view returns (uint256)'];
1545
+ functionName = 'getTotalAssets';
1546
+ const receiptTokenAddress = await (0, core_1.getReceiptTokenAddress)(provider, vault);
1547
+ vaultDecimals = await (0, core_1.getDecimals)(provider, receiptTokenAddress, false);
1548
+ break;
1549
+ }
1550
+ default: {
1551
+ minAbi = ['function totalAssets() view returns (uint256)'];
1552
+ functionName = 'totalAssets';
1553
+ vaultDecimals = await (0, core_1.getDecimals)(provider, vault);
1554
+ break;
1555
+ }
1556
+ }
1557
+ // block trackback setup
1558
+ // Get the latest finalized block to ensure all queried blocks are confirmed
1559
+ const finalizedBlock = await provider.getBlock('finalized');
1560
+ const finalizedTimestamp = finalizedBlock
1561
+ ? finalizedBlock.timestamp * 1000
1562
+ : Date.now() * 60 * 1000;
1563
+ const daysAgo = historical.daysAgo * 24 * 60 * 60 * 1000;
1564
+ const dater = new ethereum_block_by_date_1.default(provider);
1565
+ const startDate = historical.daysAgo
1566
+ ? new Date(finalizedTimestamp - daysAgo)
1567
+ : new Date(finalizedTimestamp);
1568
+ const blocks = await dater.getEvery(historical.interval, // Period
1569
+ startDate.toUTCString(), // Start date
1570
+ new Date(finalizedTimestamp).toUTCString());
1571
+ // order blocks array
1572
+ const orderedBlocks = historical.order === 'desc' ? (0, core_1.orderObjArrByDate)(blocks) : blocks;
1573
+ // minimal ABI
1574
+ const totalAssetsHistorical = await (0, core_1.promiseSettle)(orderedBlocks?.map(async ({ block, date }) => {
1575
+ // get totalAssets at said block
1576
+ const totalAssetsAtBlock = await provider.call({
1577
+ to: vaultContract,
1578
+ data: new ethers_1.Interface(minAbi).encodeFunctionData(functionName, []),
1579
+ blockTag: block,
1580
+ });
1581
+ // cover case when data returned is 0
1582
+ if (totalAssetsAtBlock === '0x')
1583
+ return {
1584
+ timestamp: (0, core_1.dateToUnix)(new Date(date)),
1585
+ value: (0, core_1.toNormalizedBn)(0, 0),
1586
+ };
1587
+ // decode back to readable
1588
+ const readableTotalAssetsAtBlock = new ethers_1.Interface(minAbi).decodeFunctionResult(functionName, totalAssetsAtBlock);
1589
+ return {
1590
+ timestamp: (0, core_1.dateToUnix)(new Date(date)),
1591
+ value: (0, core_1.toNormalizedBn)(readableTotalAssetsAtBlock?.[0], vaultDecimals),
1592
+ };
1593
+ }));
1594
+ core_1.Logger.log.info('getVaultTvl:historical', totalAssetsHistorical.length);
1595
+ return totalAssetsHistorical;
1596
+ }
1597
+ }
1598
+ catch (e) {
1599
+ core_1.Logger.log.error('getVaultTvl', e, { vault });
1600
+ throw new Error(`Failed to fetch TVL for ${vault}: ${e instanceof Error ? e.message : 'Unknown error'}`);
1601
+ }
1602
+ }
1603
+ /**
1604
+ * Build the per-loan borrower / health-factor list for a single vault.
1605
+ *
1606
+ * Resilience: individual loan reads (`loanState()`, `borrower()`) and the
1607
+ * per-borrower August backend call are isolated with `Promise.allSettled` and
1608
+ * try/catch so one bad loan — a test loan whose address isn't a real contract,
1609
+ * a borrower the backend doesn't recognize, an intermittent RPC error — drops
1610
+ * that single row from the result instead of rejecting the whole batch. Failed
1611
+ * rows are logged via `Logger.log.warn`; the loan is still returned with
1612
+ * `health_factor: undefined` so callers can render an explicit empty state
1613
+ * rather than a perpetual loading skeleton.
1614
+ *
1615
+ * @param vault - Vault address to scope the fetch to.
1616
+ * @param options - Standard vault options — `rpcUrl` must point at `vault`'s chain.
1617
+ * @returns Array of `IVaultBorrowerHealthFactor`, one entry per active loan.
1618
+ */
1619
+ async function getVaultBorrowerHealthFactor({ vault, options, }) {
1620
+ const provider = (0, core_1.createProvider)(options.rpcUrl);
1621
+ const loans = await getVaultLoans(vault, options);
1622
+ // Read each loan's on-chain state; drop loans whose RPC call fails so a
1623
+ // single bad contract (e.g. a test loan address) can't sink the batch.
1624
+ const activeLoanResults = await Promise.allSettled(loans.map(async (l) => {
1625
+ const loanContract = (0, core_1.createContract)({
1626
+ provider,
1627
+ address: l.address,
1628
+ abi: abis_1.ABI_LOAN,
1629
+ });
1630
+ const loanState = await loanContract.loanState();
1631
+ if ((0, core_1.loanStateToReadable)(loanState) !== 'ACTIVE')
1632
+ return undefined;
1633
+ return l.address;
1634
+ }));
1635
+ const activeLoans = activeLoanResults
1636
+ .map((r, i) => {
1637
+ if (r.status === 'fulfilled')
1638
+ return r.value;
1639
+ core_1.Logger.log.warn('getVaultBorrowerHealthFactor:loanState', r.reason, {
1640
+ vault,
1641
+ loan: loans[i]?.address,
1642
+ });
1643
+ return undefined;
1644
+ })
1645
+ .filter((l) => l !== undefined);
1646
+ // Same fault-tolerance for the per-loan borrower() read.
1647
+ const formattedSettled = await Promise.allSettled(activeLoans.map(async (l) => {
1648
+ const loanContract = (0, core_1.createContract)({
1649
+ provider,
1650
+ address: l,
1651
+ abi: abis_1.ABI_LOAN,
1652
+ });
1653
+ return {
1654
+ borrower: (await loanContract.borrower()),
1655
+ loan: l,
1656
+ };
1657
+ }));
1658
+ const formattedLoansArray = formattedSettled
1659
+ .map((r, i) => {
1660
+ if (r.status === 'fulfilled')
1661
+ return r.value;
1662
+ core_1.Logger.log.warn('getVaultBorrowerHealthFactor:borrower', r.reason, {
1663
+ vault,
1664
+ loan: activeLoans[i],
1665
+ });
1666
+ return undefined;
1667
+ })
1668
+ .filter((entry) => entry !== undefined);
1669
+ const uniqueBorrowers = [
1670
+ ...new Set(formattedLoansArray.map((l) => l.borrower)),
1671
+ ];
1672
+ const borrowerResponses = {};
1673
+ for (const b of uniqueBorrowers) {
1674
+ try {
1675
+ const response = await (0, core_1.fetchAugustWithKey)(options.augustKey, core_1.WEBSERVER_ENDPOINTS.subaccount.health_factor(b));
1676
+ if (response.status === 200) {
1677
+ borrowerResponses[b] = await response.json();
1678
+ }
1679
+ }
1680
+ catch (err) {
1681
+ core_1.Logger.log.warn('getVaultBorrowerHealthFactor:health_factor', err, {
1682
+ vault,
1683
+ borrower: b,
1684
+ });
1685
+ }
1686
+ }
1687
+ formattedLoansArray.forEach((loan) => {
1688
+ loan.health_factor = borrowerResponses[loan.borrower];
1689
+ });
1690
+ return formattedLoansArray;
1691
+ }
1692
+ /**
1693
+ * Aggregate borrower-health-factor data, optionally scoped to a single vault.
1694
+ *
1695
+ * - When `vault` is provided, only that vault's tokenized record is fetched
1696
+ * and only its loans are read. This is the path callers should use when
1697
+ * they already know which vault they care about — it skips the cross-chain
1698
+ * fanout entirely.
1699
+ * - When `vault` is omitted, the function fetches every tokenized vault and
1700
+ * builds health factors for each, preserving the legacy behavior. Per-vault
1701
+ * failures are isolated with `Promise.allSettled` so one bad vault returns
1702
+ * an empty array instead of rejecting the whole map.
1703
+ *
1704
+ * The returned map is keyed by **lowercased** vault address so callers can
1705
+ * look up entries without worrying about EIP-55 checksum casing in either the
1706
+ * backend response or the caller's input.
1707
+ *
1708
+ * @param options - Standard vault options — `rpcUrl` should match `vault`'s chain.
1709
+ * @param vault - Optional vault address to scope the fetch to.
1710
+ * @returns Map of lowercased vault address → array of borrower-health-factor rows.
1711
+ */
1712
+ async function getHealthFactorOfBorrowersByVault({ options, vault, }) {
1713
+ const vaults = vault
1714
+ ? ((await (0, core_1.fetchTokenizedVault)(vault, options?.headers)) ?? [])
1715
+ : await (0, core_1.fetchTokenizedVaults)(undefined, options?.headers, false, false);
1716
+ const healthFactorResults = await Promise.allSettled(vaults.map(async (v) => {
1717
+ return getVaultBorrowerHealthFactor({
1718
+ vault: v.address,
1719
+ options,
1720
+ });
1721
+ }));
1722
+ const healthFactorsByPool = {};
1723
+ vaults.forEach((pool, index) => {
1724
+ const result = healthFactorResults[index];
1725
+ const key = pool.address.toLowerCase();
1726
+ if (result?.status === 'fulfilled') {
1727
+ healthFactorsByPool[key] = result.value;
1728
+ return;
1729
+ }
1730
+ core_1.Logger.log.warn('getHealthFactorOfBorrowersByVault:vault', result?.reason, {
1731
+ vault: pool.address,
1732
+ });
1733
+ healthFactorsByPool[key] = [];
1734
+ });
1735
+ return healthFactorsByPool;
1736
+ }
1737
+ /**
1738
+ * Fetch user points from the backend API endpoint.
1739
+ * This replaces client-side points calculation with server-side processing.
1740
+ * @param userAddress - User wallet address (EVM or Solana)
1741
+ * @param options - Request options including headers
1742
+ * @returns Points data from the backend API
1743
+ */
1744
+ async function getUserPoints({ userAddress, options, }) {
1745
+ try {
1746
+ if (!userAddress) {
1747
+ throw new Error('User address parameter is undefined.');
1748
+ }
1749
+ const endpoint = core_1.WEBSERVER_ENDPOINTS.public.points.byUserAddress(userAddress);
1750
+ const response = await (0, core_1.fetchAugustPublic)(endpoint, {
1751
+ headers: options?.headers,
1752
+ });
1753
+ if (!response.ok) {
1754
+ throw new Error(`Failed to fetch points: ${response.statusText}`);
1755
+ }
1756
+ const data = await response.json();
1757
+ return data;
1758
+ }
1759
+ catch (e) {
1760
+ core_1.Logger.log.error('getUserPoints', e, { userAddress });
1761
+ const errorMessage = e instanceof Error ? e.message : String(e);
1762
+ throw new Error(`#getUserPoints::${userAddress}:${errorMessage}`);
1763
+ }
1764
+ }
1765
+ /**
1766
+ * Register a user for the points program, authenticated by a wallet signature.
1767
+ *
1768
+ * The caller must obtain a personal_sign (EIP-191) signature over the
1769
+ * canonical message template below — the backend reconstructs the same
1770
+ * string from these fields and verifies the signature recovers to
1771
+ * `userAddress`. Building the message client-side and server-side from the
1772
+ * same primitives means a hostile client cannot show one message in the
1773
+ * wallet prompt while submitting a different one for validation.
1774
+ *
1775
+ * Canonical message (`\n`-separated, no trailing newline):
1776
+ *
1777
+ * ```
1778
+ * Upshift: register {userAddress.toLowerCase()}
1779
+ * referrer: {referrerAddress.toLowerCase() || "none"}
1780
+ * chain: {chainId}
1781
+ * nonce: {nonce}
1782
+ * expires: {expiry}
1783
+ * ```
1784
+ *
1785
+ * @param userAddress - EVM wallet address being registered. Must match the
1786
+ * signer that produced `signature`. Lowercased before being embedded in
1787
+ * the canonical message.
1788
+ * @param referrerAddress - Optional EVM address that referred this user.
1789
+ * Lowercased when embedded; the literal string `"none"` is used when
1790
+ * absent. Tampering with this field after signing produces 401.
1791
+ * @param chainId - Chain on which the wallet signed. For smart-contract wallets
1792
+ * (Safe etc.) this is the chain whose RPC the backend will use to run the
1793
+ * EIP-1271 `isValidSignature` check; for EOAs it still pins the signature
1794
+ * to a chain so it can't be replayed cross-chain. Must be one of the chains
1795
+ * Upshift supports (see backend `SUPPORTED_REGISTRATION_CHAINS`).
1796
+ * @param signature - `0x`-prefixed hex of the personal_sign signature over
1797
+ * the canonical message. Single-use: replays within ~10 min are rejected.
1798
+ * @param nonce - Caller-generated random string (8–128 chars). Pair with
1799
+ * `userAddress` to form the single-use key in the backend's nonce store.
1800
+ * @param expiry - Unix timestamp (seconds). Must be in the future and within
1801
+ * the backend's TTL window (currently 10 min).
1802
+ * @param options - Request options (headers).
1803
+ * @returns Raw `Response` from the backend. 200 on success, 401 for
1804
+ * expired / replayed / mismatched-signer / tampered-referrer, 422 for an
1805
+ * unsupported `chainId`.
1806
+ *
1807
+ * @example
1808
+ * ```ts
1809
+ * const nonce = crypto.randomUUID().replace(/-/g, '');
1810
+ * const expiry = Math.floor(Date.now() / 1000) + 300;
1811
+ * const message =
1812
+ * `Upshift: register ${address.toLowerCase()}\n` +
1813
+ * `referrer: ${referrer?.toLowerCase() ?? 'none'}\n` +
1814
+ * `chain: ${chainId}\n` +
1815
+ * `nonce: ${nonce}\n` +
1816
+ * `expires: ${expiry}`;
1817
+ * const signature = await walletClient.signMessage({ account: address, message });
1818
+ * await registerUserForPoints({ userAddress: address, referrerAddress: referrer, chainId, signature, nonce, expiry });
1819
+ * ```
1820
+ */
1821
+ async function registerUserForPoints({ userAddress, referrerAddress, chainId, signature, nonce, expiry, options, }) {
1822
+ try {
1823
+ if (!userAddress) {
1824
+ throw new Error('User address parameter is undefined.');
1825
+ }
1826
+ if (!chainId || !signature || !nonce || !expiry) {
1827
+ throw new Error('chainId, signature, nonce, and expiry are required for registering a user');
1828
+ }
1829
+ const response = await (0, core_1.fetchAugustPublic)(core_1.WEBSERVER_ENDPOINTS.public.points.register, {
1830
+ method: 'post',
1831
+ server: 'public',
1832
+ headers: options?.headers,
1833
+ data: {
1834
+ user_address: userAddress,
1835
+ ...(referrerAddress && { referrer_address: referrerAddress }),
1836
+ chain_id: chainId,
1837
+ signature,
1838
+ nonce,
1839
+ expiry,
1840
+ },
1841
+ });
1842
+ return response;
1843
+ }
1844
+ catch (e) {
1845
+ core_1.Logger.log.error('registerUserForPoints', e, {
1846
+ userAddress,
1847
+ referrerAddress,
1848
+ });
1849
+ const errorMessage = e instanceof Error ? e.message : String(e);
1850
+ throw new Error(`#registerUserForPoints::${userAddress}:${errorMessage}`);
1851
+ }
1852
+ }
1853
+ /**
1854
+ * Fetch the points leaderboard data.
1855
+ * @param params - Optional parameters for pagination and sorting
1856
+ * @param options - Request options including headers
1857
+ * @returns Leaderboard response data
1858
+ */
1859
+ async function fetchPointsLeaderboard({ params, options, }) {
1860
+ try {
1861
+ const { page = 1, perPage = 10, sortBy = 'totalPoints' } = params || {};
1862
+ const searchParams = new URLSearchParams({
1863
+ page: page.toString(),
1864
+ perPage: perPage.toString(),
1865
+ sortBy,
1866
+ });
1867
+ const response = await (0, core_1.fetchAugustPublic)(`${core_1.WEBSERVER_ENDPOINTS.public.points.leaderboard}?${searchParams.toString()}`, {
1868
+ headers: options?.headers,
1869
+ });
1870
+ if (!response.ok) {
1871
+ throw new Error(`Failed to fetch leaderboard: ${response.statusText}`);
1872
+ }
1873
+ return await response.json();
1874
+ }
1875
+ catch (e) {
1876
+ core_1.Logger.log.error('fetchPointsLeaderboard', e, { params });
1877
+ const errorMessage = e instanceof Error ? e.message : String(e);
1878
+ throw new Error(`#fetchPointsLeaderboard::${errorMessage}`);
1879
+ }
1880
+ }
1881
+ /**
1882
+ * Fetch the timestamp when yield was last realized for a vault.
1883
+ * Returns the assetsUpdatedOn timestamp from the vault contract.
1884
+ * @param vault - Vault contract address
1885
+ * @param options - RPC configuration
1886
+ * @returns Timestamp (Unix timestamp in seconds) when yield was last realized
1887
+ */
1888
+ async function getYieldLastRealizedOn({ vault, options, }) {
1889
+ try {
1890
+ const provider = (0, core_1.createProvider)(options.rpcUrl);
1891
+ const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vault, options?.headers))?.[0];
1892
+ if (!tokenizedVault) {
1893
+ throw new Error(`Vault ${vault} not found`);
1894
+ }
1895
+ const version = (0, core_1.getVaultVersionV2)(tokenizedVault);
1896
+ let assetsUpdatedOn;
1897
+ switch (version) {
1898
+ case 'evm-2': {
1899
+ const contract = (0, core_1.createContract)({
1900
+ provider,
1901
+ address: vault,
1902
+ abi: TokenizedVaultV2_1.ABI_TOKENIZED_VAULT_V2,
1903
+ });
1904
+ assetsUpdatedOn = await contract.assetsUpdatedOn();
1905
+ break;
1906
+ }
1907
+ case 'evm-0': {
1908
+ const contract = (0, core_1.createContract)({
1909
+ provider,
1910
+ address: vault,
1911
+ abi: abis_1.ABI_LENDING_POOL_V3,
1912
+ });
1913
+ assetsUpdatedOn = await contract.assetsUpdatedOn();
1914
+ break;
1915
+ }
1916
+ case 'sol-0':
1917
+ case 'stellar-0': {
1918
+ return 0;
1919
+ }
1920
+ default: {
1921
+ // Old lending pools don't have assetsUpdatedOn function
1922
+ // Return 0 for legacy vaults
1923
+ return 0;
1924
+ }
1925
+ }
1926
+ return Number(assetsUpdatedOn);
1927
+ }
1928
+ catch (e) {
1929
+ core_1.Logger.log.error('getYieldLastRealizedOn', e, { vault });
1930
+ const errorMessage = e instanceof Error ? e.message : String(e);
1931
+ throw new Error(`#getYieldLastRealizedOn::${vault}:${errorMessage}`);
1932
+ }
1933
+ }
1934
+ /**
1935
+ * Guard against the 2026-07-09 lifetime-PnL over-inflation failure mode.
1936
+ *
1937
+ * Lifetime PnL is `currentPosition + totalWithdrawn - totalDeposited`. When the
1938
+ * deposit/withdrawal history fails to load it collapses to empty, so both
1939
+ * `totalDeposited` and `totalWithdrawn` read as zero. If the wallet still holds
1940
+ * a live on-chain position, the formula degenerates to reporting the entire
1941
+ * position as pure profit — a plausible-looking but wrong number.
1942
+ *
1943
+ * This guard rejects exactly that state: a non-zero position with no recorded
1944
+ * deposits *and* no recorded withdrawals. Any of the following is treated as
1945
+ * consistent and passes:
1946
+ * - deposits are present (the normal, computable case);
1947
+ * - withdrawals are present (a fully-exited-then-re-entered position is still
1948
+ * computable — the withdrawal records prove history loaded);
1949
+ * - the current position is zero (the wallet never interacted or fully exited,
1950
+ * so a zero PnL is correct, not inflated).
1951
+ *
1952
+ * Pure and synchronous — no RPC, no I/O — so it is safe to call inside the hot
1953
+ * path with no added latency.
1954
+ *
1955
+ * Known imprecision — vault shares are transferable ERC-20 receipt tokens, so a
1956
+ * wallet that *received* shares via a direct token transfer (never deposited,
1957
+ * never withdrew) legitimately has `position > 0` with no history records and
1958
+ * will throw `HISTORY_UNAVAILABLE` even though its history loaded fine. This is
1959
+ * not a regression — the pre-guard formula reported that same position as 100%
1960
+ * profit, which was also wrong — but the error message is misleading for that
1961
+ * (rare) path. If receipt-token transfer becomes a real user flow, distinguish
1962
+ * it here (e.g. by checking for a share-transfer event) rather than widening
1963
+ * the guard.
1964
+ *
1965
+ * @param params - See {@link PnlHistoryConsistencyParams}.
1966
+ * @returns `void` when the history is consistent.
1967
+ * @throws {@link AugustHistoryUnavailableError} with code `HISTORY_UNAVAILABLE`
1968
+ * and `context: { vault, wallet }` when a non-zero position has neither
1969
+ * deposits nor withdrawals recorded. The message carries the
1970
+ * `#getVaultUserLifetimePnl::<vault>::<wallet>` prefix so it groups with the
1971
+ * other errors leaving the getter.
1972
+ * @example
1973
+ * ```ts
1974
+ * assertPnlHistoryConsistent({
1975
+ * vault,
1976
+ * wallet,
1977
+ * hasDeposits: depositsByAsset.size > 0,
1978
+ * totalWithdrawnRaw,
1979
+ * currentPositionRaw: currentPositionValue.raw,
1980
+ * });
1981
+ * ```
1982
+ */
1983
+ function assertPnlHistoryConsistent({ vault, wallet, hasDeposits, totalWithdrawnRaw, currentPositionRaw, }) {
1984
+ const positionExists = currentPositionRaw > BigInt(0);
1985
+ const noHistory = !hasDeposits && totalWithdrawnRaw === BigInt(0);
1986
+ if (positionExists && noHistory) {
1987
+ throw new errors_1.AugustHistoryUnavailableError(`#getVaultUserLifetimePnl::${vault}::${wallet}:transaction history unavailable — a live position exists but no deposits or withdrawals were indexed, so lifetime PnL cannot be computed without over-reporting the entire position as profit`, { context: { vault, wallet } });
1988
+ }
1989
+ }
1990
+ /**
1991
+ * Sum a wallet's per-asset deposits into the vault's own decimals, at face
1992
+ * value — 1 unit of any deposited asset is treated as 1 unit of the vault's
1993
+ * reporting currency, with no live price conversion.
1994
+ *
1995
+ * Used by {@link getVaultUserLifetimePnl} for `totalDeposited`. Pre-deposit
1996
+ * multi-asset vaults only accept dollar-pegged stablecoins (USDC, USDT, DAI,
1997
+ * USDS, RLUSD, etc.) into a dollar-denominated vault, so face value is a
1998
+ * reasonable stand-in for USD value. This intentionally does NOT re-quote
1999
+ * through a live price oracle: doing so priced a historical deposit at
2000
+ * today's market rate, so `totalDeposited` (and lifetimePnl) drifted between
2001
+ * calls purely from stablecoin peg noise, and approximately re-inflated
2002
+ * deposits back toward face value — which silently excluded the vault's
2003
+ * entry fee from PnL. The fee is money the user actually paid, so PnL must
2004
+ * be computed net of it.
2005
+ *
2006
+ * @param depositsByAsset - Raw deposited amount per asset, keyed by
2007
+ * lowercased asset address (or `'default'` / `'lz-default'` for
2008
+ * single-asset / cross-chain rows).
2009
+ * @param assetDecimals - Decimals for each key in `depositsByAsset`.
2010
+ * @param vaultDecimals - The vault's own decimals — the target scale every
2011
+ * asset is rescaled to.
2012
+ * @returns Total deposited, in raw base units at `vaultDecimals`.
2013
+ */
2014
+ function sumDepositsAtFaceValue(depositsByAsset, assetDecimals, vaultDecimals) {
2015
+ let total = BigInt(0);
2016
+ for (const [assetAddress, rawAmount] of depositsByAsset) {
2017
+ const assetDec = assetDecimals.get(assetAddress) ?? vaultDecimals;
2018
+ if (assetDec === vaultDecimals) {
2019
+ total += rawAmount;
2020
+ }
2021
+ else if (assetDec > vaultDecimals) {
2022
+ total += rawAmount / BigInt(10 ** (assetDec - vaultDecimals));
2023
+ }
2024
+ else {
2025
+ total += rawAmount * BigInt(10 ** (vaultDecimals - assetDec));
2026
+ }
2027
+ }
2028
+ return total;
2029
+ }
2030
+ /**
2031
+ * Calculate lifetime PnL for a user in a specific vault.
2032
+ *
2033
+ * Basic logic:
2034
+ * 1. Get user's list of deposits and withdrawals from subgraph
2035
+ * 2. Fetch user's current assets (balanceOf * sharePrice via convertToAssets)
2036
+ * 3. Calculate PnL = assets withdrawn + current assets - assets deposited
2037
+ *
2038
+ * `totalDeposited` is the face value of what the user actually paid in
2039
+ * (each deposited asset's raw amount, rescaled to the vault's decimals — no
2040
+ * live price conversion), so any entry/exit fee the vault charges is
2041
+ * automatically counted as a cost against PnL rather than excluded from it.
2042
+ * Pre-deposit multi-asset vaults are assumed to only accept dollar-pegged
2043
+ * stablecoins into a dollar-denominated vault; a vault accepting a
2044
+ * non-pegged deposit asset would need deposit-time price conversion, which
2045
+ * this function does not perform.
2046
+ *
2047
+ * @param vault - Vault contract address
2048
+ * @param wallet - User wallet address
2049
+ * @param options - RPC configuration and service options
2050
+ * @returns Lifetime PnL data in both native token and USD
2051
+ */
2052
+ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
2053
+ try {
2054
+ if (!vault)
2055
+ throw new Error('Vault input parameter is undefined.');
2056
+ if (!wallet)
2057
+ throw new Error('Wallet input parameter is undefined.');
2058
+ (0, utils_3.assertNotStellar)(vault, 'Lifetime PnL');
2059
+ if (!(0, ethers_1.isAddress)(vault))
2060
+ throw new Error(`Vault input parameter is not an address: ${String(vault)}`);
2061
+ if (!(0, ethers_1.isAddress)(wallet))
2062
+ throw new Error(`Wallet input parameter is not an address: ${String(wallet)}`);
2063
+ const provider = (0, core_1.createProvider)(options.rpcUrl);
2064
+ // Parallelize independent operations
2065
+ const [decimals, userHistory, positions, tokenizedVaultResult] = await Promise.all([
2066
+ (0, core_1.getDecimals)(provider, vault),
2067
+ (0, vaults_1.getSubgraphUserHistory)(wallet, provider, vault),
2068
+ getVaultPositions({
2069
+ vault,
2070
+ wallet,
2071
+ options,
2072
+ }),
2073
+ (0, core_1.fetchTokenizedVault)(vault, options?.headers, false, false),
2074
+ ]);
2075
+ const tokenizedVault = tokenizedVaultResult?.[0];
2076
+ if (!tokenizedVault) {
2077
+ throw new Error(`Vault ${vault} not found`);
2078
+ }
2079
+ // getDecimals resolves to `undefined` on a failed/absent decimals() read.
2080
+ // Unguarded, that undefined flows into `10 ** decimals` (=> NaN) and
2081
+ // toNormalizedBn(x, undefined), and the first BigInt(NaN) throws the opaque
2082
+ // "The number NaN cannot be converted to a BigInt" error. Decimals must come
2083
+ // from the chain — never assume a fallback — so fail fast with actionable
2084
+ // context. Transient RPC blips recover on the caller's retry.
2085
+ if (typeof decimals !== 'number' || !Number.isFinite(decimals)) {
2086
+ throw new Error(`Failed to resolve decimals for vault ${vault}`);
2087
+ }
2088
+ // Fetch LayerZero deposits/redeems for supported vaults
2089
+ const lzVaultKey = (0, deposits_1.isLayerZeroVault)(vault);
2090
+ let lzDeposits = [];
2091
+ let lzRedeems = [];
2092
+ if (lzVaultKey) {
2093
+ const lzPromises = [
2094
+ (0, deposits_1.queryLayerZeroDeposits)(lzVaultKey, wallet)
2095
+ .then((d) => {
2096
+ lzDeposits = d;
2097
+ })
2098
+ .catch((e) => {
2099
+ core_1.Logger.log.warn('getVaultUserLifetimePnl:lzDeposits', `Failed to fetch LZ deposits for ${lzVaultKey}: ${e}`);
2100
+ }),
2101
+ ];
2102
+ // Only earnAUSD has redeem events currently
2103
+ if (lzVaultKey === 'earnAUSD') {
2104
+ lzPromises.push((0, redeems_1.queryLayerZeroRedeems)(wallet)
2105
+ .then((r) => {
2106
+ lzRedeems = r;
2107
+ })
2108
+ .catch((e) => {
2109
+ core_1.Logger.log.warn('getVaultUserLifetimePnl:lzRedeems', `Failed to fetch LZ redeems for ${lzVaultKey}: ${e}`);
2110
+ }));
2111
+ }
2112
+ await Promise.all(lzPromises);
2113
+ }
2114
+ const version = (0, core_1.getVaultVersionV2)(tokenizedVault);
2115
+ const currentPosition = positions.find((pos) => pos.vault?.toLowerCase() === vault.toLowerCase());
2116
+ // Start underlyingSymbol fetch (runs in parallel with processHistory)
2117
+ const underlyingSymbolPromise = (async () => {
2118
+ let underlyingTokenSymbol = 'UNKNOWN';
2119
+ let underlyingAssetAddress;
2120
+ try {
2121
+ // Use ABI_LENDING_POOL_V2 which has the asset() method
2122
+ const vaultContract = (0, core_1.createContract)({
2123
+ provider,
2124
+ address: vault,
2125
+ abi: abis_1.ABI_LENDING_POOL_V2,
2126
+ });
2127
+ try {
2128
+ underlyingAssetAddress = (await vaultContract.asset());
2129
+ const underlyingContract = (0, core_1.createContract)({
2130
+ provider,
2131
+ address: underlyingAssetAddress,
2132
+ abi: abis_1.ABI_ERC20,
2133
+ });
2134
+ underlyingTokenSymbol = await underlyingContract.symbol();
2135
+ }
2136
+ catch (e) {
2137
+ core_1.Logger.log.warn('getVaultUserLifetimePnl:getUnderlyingSymbol', `Failed to get underlying token symbol: ${e}, falling back to receipt token symbol`);
2138
+ underlyingTokenSymbol =
2139
+ tokenizedVault.receipt_token_symbol || 'UNKNOWN';
2140
+ }
2141
+ }
2142
+ catch (e) {
2143
+ core_1.Logger.log.warn('getVaultUserLifetimePnl:getUnderlyingSymbol', `Failed to get underlying token symbol: ${e}, falling back to receipt token symbol`);
2144
+ underlyingTokenSymbol =
2145
+ tokenizedVault.receipt_token_symbol || 'UNKNOWN';
2146
+ }
2147
+ return { underlyingTokenSymbol, underlyingAssetAddress };
2148
+ })();
2149
+ // Process history - count all withdrawal requests (both processed and pending)
2150
+ // We don't count processed withdrawals separately to avoid double counting
2151
+ // Group deposit amounts by asset address to handle different decimals
2152
+ const depositsByAsset = new Map();
2153
+ // Separate withdrawal tracking: shares need conversion, assets don't
2154
+ let totalWithdrawalsInShares = BigInt(0);
2155
+ let totalWithdrawalsInAssets = BigInt(0);
2156
+ for (const historyItem of userHistory) {
2157
+ const amount = BigInt(historyItem.amount);
2158
+ // ignore bad transactions when calculating pnl
2159
+ if ((0, core_1.isBadTransaction)(historyItem.transactionHash_, tokenizedVault.chain)) {
2160
+ continue;
2161
+ }
2162
+ if (historyItem.type === 'deposit') {
2163
+ // Use assetIn if available (V2 vaults), otherwise use a default key
2164
+ // Use proper type guard instead of type assertion
2165
+ const assetKey = 'assetIn' in historyItem && typeof historyItem.assetIn === 'string'
2166
+ ? historyItem.assetIn.toLowerCase()
2167
+ : 'default';
2168
+ const current = depositsByAsset.get(assetKey) ?? BigInt(0);
2169
+ depositsByAsset.set(assetKey, current + amount);
2170
+ }
2171
+ else if (historyItem.type === 'withdraw-request') {
2172
+ // Withdraw requests are in shares, need conversion to assets
2173
+ totalWithdrawalsInShares += amount;
2174
+ }
2175
+ else if (historyItem.type === 'withdraw-processed' &&
2176
+ historyItem.isInstant) {
2177
+ // Instant withdrawals are already in assets, no conversion needed
2178
+ totalWithdrawalsInAssets += amount;
2179
+ }
2180
+ }
2181
+ // Include LayerZero cross-chain deposits and redeems
2182
+ if (lzVaultKey && lzDeposits.length > 0) {
2183
+ for (const lzDeposit of lzDeposits) {
2184
+ // LZ deposits assetAmt is the underlying asset amount deposited in hub-chain decimals.
2185
+ // This assumes the LZ subgraph indexes amounts in the same decimals as the hub vault
2186
+ // (e.g. 6 for USDC). If source/hub decimals diverge, normalization would be needed here.
2187
+ const assetKey = 'lz-default';
2188
+ const amount = BigInt(lzDeposit.assetAmt);
2189
+ const current = depositsByAsset.get(assetKey) ?? BigInt(0);
2190
+ depositsByAsset.set(assetKey, current + amount);
2191
+ }
2192
+ }
2193
+ if (lzVaultKey && lzRedeems.length > 0) {
2194
+ for (const lzRedeem of lzRedeems) {
2195
+ // LZ redeems shareAmt is in shares, needs conversion via share price
2196
+ totalWithdrawalsInShares += BigInt(lzRedeem.shareAmt);
2197
+ }
2198
+ }
2199
+ // Fetch decimals for each unique asset in parallel
2200
+ const assetDecimals = new Map();
2201
+ await Promise.all(Array.from(depositsByAsset.keys()).map(async (assetAddress) => {
2202
+ if (assetAddress === 'default' || assetAddress === 'lz-default') {
2203
+ assetDecimals.set(assetAddress, decimals);
2204
+ }
2205
+ else {
2206
+ const dec = await (0, core_1.getDecimals)(provider, assetAddress, false);
2207
+ assetDecimals.set(assetAddress, dec);
2208
+ }
2209
+ }));
2210
+ const totalDepositedNativeRaw = sumDepositsAtFaceValue(depositsByAsset, assetDecimals, decimals);
2211
+ const { underlyingTokenSymbol, underlyingAssetAddress } = await underlyingSymbolPromise;
2212
+ // Calculate current position value and fetch price in parallel
2213
+ let currentPositionValue = (0, core_1.toNormalizedBn)(0, decimals);
2214
+ const pricePromise = underlyingAssetAddress && tokenizedVault.chain
2215
+ ? (0, core_1.fetchTokenPriceByAddress)(underlyingAssetAddress, tokenizedVault.chain, options?.headers).catch(() => 0)
2216
+ : Promise.resolve(0);
2217
+ const vaultContract = (0, core_1.createContract)({
2218
+ provider,
2219
+ address: vault,
2220
+ abi: version === 'evm-2' ? TokenizedVaultV2_1.ABI_TOKENIZED_VAULT_V2 : abis_1.ABI_LENDING_POOL_V2,
2221
+ });
2222
+ let sharePriceRaw = BigInt(0);
2223
+ try {
2224
+ // Retried on transport blips only (bounded, no fallback); a vault that
2225
+ // genuinely has no `lpTokenAddress()` still throws into the catch below.
2226
+ const lpTokenAddress = version === 'evm-2'
2227
+ ? await (0, core_1.getReceiptTokenAddressOrThrow)(provider, vault, 'getVaultUserLifetimePnl:receiptToken')
2228
+ : vault;
2229
+ let currentShares = BigInt(0);
2230
+ if (version === 'evm-2') {
2231
+ const lpTokenContract = (0, core_1.createContract)({
2232
+ provider,
2233
+ address: lpTokenAddress,
2234
+ abi: TokenizedVaultV2Receipt_1.ABI_TOKENIZED_VAULT_V2_RECEIPT,
2235
+ });
2236
+ currentShares = await lpTokenContract.balanceOf(wallet);
2237
+ }
2238
+ else {
2239
+ currentShares = await vaultContract.balanceOf(wallet);
2240
+ }
2241
+ // For LayerZero vaults, include receipt token balances on remote chains
2242
+ if (lzVaultKey) {
2243
+ // Build list of remote chains to check: prefer receipt_token_integrations from API,
2244
+ // fall back to LAYERZERO_VAULTS spoke chain config (OVault shareOFT addresses)
2245
+ const remoteChains = [];
2246
+ if (tokenizedVault.receipt_token_integrations?.length) {
2247
+ for (const rti of tokenizedVault.receipt_token_integrations) {
2248
+ if (rti.chain !== tokenizedVault.chain &&
2249
+ rti.address &&
2250
+ rti.chain) {
2251
+ remoteChains.push({
2252
+ chain: rti.chain,
2253
+ address: rti.address,
2254
+ });
2255
+ }
2256
+ }
2257
+ }
2258
+ else {
2259
+ // Fallback: use spoke chain addresses from LAYERZERO_VAULTS config
2260
+ const lzVaultConfig = deposits_1.LAYERZERO_VAULTS[lzVaultKey];
2261
+ if (lzVaultConfig.spokeChains) {
2262
+ for (const [chainIdStr, address] of Object.entries(lzVaultConfig.spokeChains)) {
2263
+ const chainId = Number(chainIdStr);
2264
+ if (chainId !== tokenizedVault.chain) {
2265
+ remoteChains.push({
2266
+ chain: chainId,
2267
+ address: address,
2268
+ });
2269
+ }
2270
+ }
2271
+ }
2272
+ }
2273
+ if (remoteChains.length > 0) {
2274
+ const remoteBalances = await Promise.all(remoteChains.map(async ({ chain, address }) => {
2275
+ try {
2276
+ const rpcUrls = (0, core_1.getFallbackRpcUrls)(chain);
2277
+ if (!rpcUrls.length) {
2278
+ core_1.Logger.log.warn('getVaultUserLifetimePnl:lzRemoteBalance', `No RPC URL for chain ${chain}, skipping balance check`);
2279
+ return BigInt(0);
2280
+ }
2281
+ const remoteProvider = (0, core_1.createProvider)(rpcUrls[0]);
2282
+ const remoteTokenContract = (0, core_1.createContract)({
2283
+ provider: remoteProvider,
2284
+ address,
2285
+ abi: abis_1.ABI_ERC20,
2286
+ });
2287
+ const [rawBalance, remoteDecimals] = await Promise.all([
2288
+ remoteTokenContract.balanceOf(wallet),
2289
+ remoteTokenContract.decimals(),
2290
+ ]);
2291
+ const remDec = Number(remoteDecimals) || decimals;
2292
+ // Normalize remote balance to hub chain decimals
2293
+ if (remDec > decimals) {
2294
+ return rawBalance / BigInt(10 ** (remDec - decimals));
2295
+ }
2296
+ else if (remDec < decimals) {
2297
+ return rawBalance * BigInt(10 ** (decimals - remDec));
2298
+ }
2299
+ return rawBalance;
2300
+ }
2301
+ catch (e) {
2302
+ core_1.Logger.log.warn('getVaultUserLifetimePnl:lzRemoteBalance', `Failed to fetch balance on chain ${chain}: ${e}`);
2303
+ return BigInt(0);
2304
+ }
2305
+ }));
2306
+ for (const bal of remoteBalances) {
2307
+ currentShares += bal;
2308
+ }
2309
+ }
2310
+ }
2311
+ if (version === 'evm-2') {
2312
+ sharePriceRaw = await vaultContract.getSharePrice();
2313
+ }
2314
+ else {
2315
+ const [totalAssets, totalSupply] = await Promise.all([
2316
+ vaultContract.totalAssets(),
2317
+ vaultContract.totalSupply(),
2318
+ ]);
2319
+ if (totalSupply === BigInt(0)) {
2320
+ sharePriceRaw = BigInt(10 ** decimals); // 1:1 if no supply
2321
+ }
2322
+ else {
2323
+ sharePriceRaw = (totalAssets * BigInt(10 ** decimals)) / totalSupply;
2324
+ }
2325
+ }
2326
+ const currentPositionValueRaw = (currentShares * sharePriceRaw) / BigInt(10 ** decimals);
2327
+ currentPositionValue = (0, core_1.toNormalizedBn)(currentPositionValueRaw, decimals);
2328
+ }
2329
+ catch (e) {
2330
+ core_1.Logger.log.warn('getVaultUserLifetimePnl:getCurrentSharesAndPrice', `Failed to get current shares and share price: ${e}, falling back to wallet balance`);
2331
+ if (currentPosition?.walletBalance) {
2332
+ currentPositionValue = currentPosition.walletBalance;
2333
+ }
2334
+ }
2335
+ // Calculate totalWithdrawn combining shares (converted) and assets (as-is)
2336
+ let totalWithdrawnRaw = totalWithdrawalsInAssets;
2337
+ if (version === 'evm-2' && sharePriceRaw > BigInt(0)) {
2338
+ // Only convert shares to assets, instant withdrawals are already in assets
2339
+ totalWithdrawnRaw +=
2340
+ (totalWithdrawalsInShares * sharePriceRaw) / BigInt(10 ** decimals);
2341
+ }
2342
+ else {
2343
+ // For non-evm-2 or when share price is 0, treat shares as assets
2344
+ totalWithdrawnRaw += totalWithdrawalsInShares;
2345
+ }
2346
+ const totalWithdrawn = (0, core_1.toNormalizedBn)(totalWithdrawnRaw, decimals);
2347
+ // Guard the 2026-07-09 over-inflation incident: a live position with no
2348
+ // deposits *and* no withdrawals means the history failed to load, and the
2349
+ // PnL formula would report the whole position as profit. Fail loudly with a
2350
+ // typed error instead of returning a fabricated number.
2351
+ assertPnlHistoryConsistent({
2352
+ vault,
2353
+ wallet,
2354
+ hasDeposits: depositsByAsset.size > 0,
2355
+ totalWithdrawnRaw,
2356
+ currentPositionRaw: BigInt(currentPositionValue.raw),
2357
+ });
2358
+ const tokenPriceUsd = await pricePromise;
2359
+ // Validate token price and log warning if invalid
2360
+ const effectiveTokenPrice = tokenPriceUsd > 0 ? tokenPriceUsd : 1;
2361
+ if (tokenPriceUsd <= 0) {
2362
+ core_1.Logger.log.warn('getVaultUserLifetimePnl:tokenPrice', `Invalid underlying token price: ${tokenPriceUsd}, using fallback of 1`);
2363
+ }
2364
+ // totalDeposited is already in the vault's own decimals (face value,
2365
+ // computed above) — convert to USD with the same underlying price used
2366
+ // for the other two legs below, so all three legs share one conversion
2367
+ // factor instead of drifting independently against each other.
2368
+ const totalDeposited = (0, core_1.toNormalizedBn)(totalDepositedNativeRaw, decimals);
2369
+ const totalDepositedUsd = Number(totalDeposited.normalized) * effectiveTokenPrice;
2370
+ const totalWithdrawnUsd = Number(totalWithdrawn.normalized) * effectiveTokenPrice;
2371
+ const currentPositionValueUsd = Number(currentPositionValue.normalized) * effectiveTokenPrice;
2372
+ // Calculate lifetimePnl in USD first (since deposits may be in different tokens with different prices)
2373
+ const lifetimePnlUsd = currentPositionValueUsd + totalWithdrawnUsd - totalDepositedUsd;
2374
+ // Use BigInt-scaled arithmetic to minimize precision loss when converting USD to native
2375
+ const scaleFactor = BigInt(10 ** 18); // High precision scale
2376
+ const tokenPriceScaled = BigInt(Math.round(effectiveTokenPrice * Number(scaleFactor)));
2377
+ // Convert lifetimePnlUsd to native units
2378
+ const lifetimePnlUsdScaled = BigInt(Math.round(lifetimePnlUsd * Number(scaleFactor)));
2379
+ const lifetimePnlRaw = tokenPriceScaled > BigInt(0)
2380
+ ? (lifetimePnlUsdScaled * BigInt(10 ** decimals)) / tokenPriceScaled
2381
+ : BigInt(0);
2382
+ const lifetimePnl = (0, core_1.toNormalizedBn)(lifetimePnlRaw, decimals);
2383
+ const pnlPercentage = totalDepositedUsd > 0
2384
+ ? Number.parseFloat(((lifetimePnlUsd / totalDepositedUsd) * 100).toFixed(2))
2385
+ : 0;
2386
+ return {
2387
+ tokenSymbol: underlyingTokenSymbol,
2388
+ totalDeposited,
2389
+ totalDepositedUsd,
2390
+ totalWithdrawn,
2391
+ totalWithdrawnUsd,
2392
+ currentPositionValue,
2393
+ currentPositionValueUsd,
2394
+ lifetimePnl,
2395
+ lifetimePnlUsd,
2396
+ pnlPercentage,
2397
+ };
2398
+ }
2399
+ catch (e) {
2400
+ core_1.Logger.log.error('getVaultUserLifetimePnl', e, { vault, wallet });
2401
+ // Preserve the typed history guard so callers can distinguish an
2402
+ // unavailable-history failure from a generic getter error. Its message
2403
+ // already carries the `#getVaultUserLifetimePnl::vault::wallet` prefix.
2404
+ if (e instanceof errors_1.AugustHistoryUnavailableError)
2405
+ throw e;
2406
+ const errorMessage = e instanceof Error ? e.message : String(e);
2407
+ throw new Error(`#getVaultUserLifetimePnl::${vault}::${wallet}:${errorMessage}`);
2408
+ }
2409
+ }
2410
+ /**
2411
+ * Calculate PnL for a vault (vault-level, not user-specific).
2412
+ * Returns the vault's overall profit and loss across all users.
2413
+ *
2414
+ * @param vault - Vault contract address
2415
+ * @param options - RPC configuration and service options
2416
+ * @returns Vault PnL in USD and notional value
2417
+ */
2418
+ async function getVaultPnl({ vault, options, }) {
2419
+ try {
2420
+ if (!vault)
2421
+ throw new Error('Vault input parameter is undefined.');
2422
+ (0, utils_3.assertNotStellar)(vault, 'Vault PnL');
2423
+ if (!(0, ethers_1.isAddress)(vault))
2424
+ throw new Error(`Vault input parameter is not an address: ${String(vault)}`);
2425
+ // Get vault data
2426
+ const vaultData = await getVault({
2427
+ vault,
2428
+ loans: false,
2429
+ allocations: false,
2430
+ options,
2431
+ });
2432
+ if (!vaultData) {
2433
+ throw new Error(`Vault ${vault} not found`);
2434
+ }
2435
+ // Check if vault has daily_pnl_per_share and historical_snapshots
2436
+ if (!vaultData.dailyPnlPerShare ||
2437
+ !Array.isArray(vaultData.dailyPnlPerShare) ||
2438
+ vaultData.dailyPnlPerShare.length === 0) {
2439
+ return {
2440
+ totalPnl: (0, core_1.toNormalizedBn)(0, vaultData.decimals),
2441
+ pnlUsd: 0,
2442
+ };
2443
+ }
2444
+ if (!vaultData.historical_snapshots ||
2445
+ !Array.isArray(vaultData.historical_snapshots) ||
2446
+ vaultData.historical_snapshots.length === 0) {
2447
+ return {
2448
+ totalPnl: (0, core_1.toNormalizedBn)(0, vaultData.decimals),
2449
+ pnlUsd: 0,
2450
+ };
2451
+ }
2452
+ const decimals = vaultData.decimals;
2453
+ // Filter out dates with zero PnL
2454
+ const validDailyPnl = vaultData.dailyPnlPerShare.filter((dayData) => dayData.value && dayData.value !== 0);
2455
+ // Create a map of historical_snapshots by date (YYYY-MM-DD) for quick lookup
2456
+ const snapshotsByDate = new Map();
2457
+ vaultData.historical_snapshots.forEach((snapshot) => {
2458
+ const snapshotDate = new Date(snapshot.snapshot_datetime);
2459
+ const dateKey = snapshotDate.toISOString().split('T')[0]; // YYYY-MM-DD
2460
+ snapshotsByDate.set(dateKey, snapshot);
2461
+ });
2462
+ // Calculate PnL for each day using historical_snapshots
2463
+ let totalPnlRaw = BigInt(0);
2464
+ validDailyPnl.forEach((dayData) => {
2465
+ const dayTimestamp = dayData.timestamp;
2466
+ const dailyPnlPerShare = dayData.value;
2467
+ // Get date key (YYYY-MM-DD) from timestamp
2468
+ const dayDate = new Date(dayTimestamp);
2469
+ const dateKey = dayDate.toISOString().split('T')[0];
2470
+ // Find matching snapshot for this date
2471
+ const snapshot = snapshotsByDate.get(dateKey);
2472
+ if (!snapshot?.total_shares) {
2473
+ core_1.Logger.log.warn('getVaultPnl:snapshotNotFound', `No snapshot found for date ${dateKey}`);
2474
+ return;
2475
+ }
2476
+ // Convert normalized numbers to BigInt using toNormalizedBn's normalized string
2477
+ const dailyPnlNorm = (0, core_1.toNormalizedBn)(dailyPnlPerShare, decimals);
2478
+ const totalSharesNorm = (0, core_1.toNormalizedBn)(snapshot.total_shares, decimals);
2479
+ // Convert normalized strings to BigInt (split, pad fraction, combine) - all inline
2480
+ const [dailyWhole = '0', dailyFrac = ''] = dailyPnlNorm.normalized.split('.');
2481
+ const dailyPnlPerShareScaled = BigInt(dailyWhole + (dailyFrac || '').padEnd(decimals, '0').slice(0, decimals));
2482
+ const [sharesWhole = '0', sharesFrac = ''] = totalSharesNorm.normalized.split('.');
2483
+ const totalSharesScaled = BigInt(sharesWhole +
2484
+ (sharesFrac || '').padEnd(decimals, '0').slice(0, decimals));
2485
+ // Calculate: daily_pnl_per_share[day] * total_shares[day] (both in raw units)
2486
+ const dayPnlRaw = (dailyPnlPerShareScaled * totalSharesScaled) / BigInt(10 ** decimals);
2487
+ totalPnlRaw += dayPnlRaw;
2488
+ });
2489
+ // Convert totalPnlRaw (BigInt) to normalized number using toNormalizedBn
2490
+ const totalPnl = (0, core_1.toNormalizedBn)(totalPnlRaw, decimals);
2491
+ const totalPnlNormalized = Number(totalPnl.normalized);
2492
+ const chainId = vaultData.chainId;
2493
+ // Get receipt token address (HLPe) from vaultData - no need for on-chain lookup
2494
+ const lpTokenAddress = vaultData.receipt.address;
2495
+ // Fetch HLPe (lpTokenAddress) price in USD
2496
+ let tokenPriceUsd = 0;
2497
+ try {
2498
+ tokenPriceUsd = await (0, core_1.fetchTokenPriceByAddress)(lpTokenAddress, chainId, options?.headers);
2499
+ }
2500
+ catch (error) {
2501
+ core_1.Logger.log.warn('getVaultPnl:priceFetch', `Failed to fetch HLPe token price: ${error}`);
2502
+ }
2503
+ // Convert to USD and format to 6 decimal places
2504
+ const pnlUsd = Number((totalPnlNormalized * tokenPriceUsd).toFixed(6));
2505
+ return {
2506
+ totalPnl,
2507
+ pnlUsd,
2508
+ };
2509
+ }
2510
+ catch (e) {
2511
+ core_1.Logger.log.error('getVaultPnl', e, { vault });
2512
+ const errorMessage = e instanceof Error ? e.message : String(e);
2513
+ throw new Error(`#getVaultPnl::${vault}:${errorMessage}`);
2514
+ }
2515
+ }
2516
+ /*
2517
+ * Fetch historical timeseries data for a vault.
2518
+ * Returns TVL, APY, PnL, share price, and other metrics over time.
2519
+ * @param vault - Vault contract address
2520
+ * @param nDays - Number of days of historical data (default 30, min 1)
2521
+ * @param options - Request options including headers
2522
+ * @returns Historical timeseries data with date string keys
2523
+ */
2524
+ async function getVaultHistoricalTimeseries({ vault, nDays, options, }) {
2525
+ try {
2526
+ if (!vault)
2527
+ throw new Error('Vault input parameter is undefined.');
2528
+ const response = await (0, core_1.fetchVaultHistoricalTimeseries)(vault, nDays, options?.headers);
2529
+ return response;
2530
+ }
2531
+ catch (e) {
2532
+ core_1.Logger.log.error('getVaultHistoricalTimeseries', e, { vault, nDays });
2533
+ const errorMessage = e instanceof Error ? e.message : String(e);
2534
+ throw new Error(`#getVaultHistoricalTimeseries::${vault}:${errorMessage}`);
2535
+ }
2536
+ }
2537
+ /**
2538
+ * Fetch annualized APY metrics for a vault.
2539
+ *
2540
+ * Supported Vaults: cUSDO, tETH, wstETH, rsETH
2541
+ *
2542
+ * @deprecated The `hgETH30dLiquidAPY` and `hgETH7dLiquidAPY` response fields are deprecated.
2543
+ * These fields will be removed on 2026-01-01.
2544
+ * Use `liquidAPY30Day` and `liquidAPY7Day` fields instead.
2545
+ *
2546
+ * @param vault - Vault contract address
2547
+ * @param options - Request options including headers
2548
+ * @returns Annualized APY data including liquidity APY
2549
+ */
2550
+ async function getVaultAnnualizedApy({ vault, options, }) {
2551
+ try {
2552
+ if (!vault) {
2553
+ throw new Error('Vault address parameter is undefined.');
2554
+ }
2555
+ if (!(0, ethers_1.isAddress)(vault)) {
2556
+ throw new Error(`Vault parameter is not an address: ${String(vault)}`);
2557
+ }
2558
+ const endpoint = core_1.WEBSERVER_ENDPOINTS.public.tokenizedVault.annualizedApy(vault);
2559
+ const response = await (0, core_1.fetchAugustPublic)(endpoint, {
2560
+ headers: options?.headers,
2561
+ server: 'public',
2562
+ });
2563
+ if (!response.ok) {
2564
+ throw new Error(`Failed to fetch annualized APY: ${response.statusText}`);
2565
+ }
2566
+ return (await response.json());
2567
+ }
2568
+ catch (e) {
2569
+ core_1.Logger.log.error('getVaultAnnualizedApy', e, { vault });
2570
+ const errorMessage = e instanceof Error ? e.message : String(e);
2571
+ throw new Error(`#getVaultAnnualizedApy::${vault}:${errorMessage}`);
2572
+ }
2573
+ }
2574
+ /**
2575
+ * Fetch summary data for a vault (name, type, chain, recent returns).
2576
+ * @param vault - Vault contract address
2577
+ * @param options - Request options including headers
2578
+ * @returns Vault summary data
2579
+ */
2580
+ async function getVaultSummary({ vault, options, }) {
2581
+ try {
2582
+ if (!vault) {
2583
+ throw new Error('Vault address parameter is undefined.');
2584
+ }
2585
+ (0, utils_3.assertNotStellar)(vault, 'Vault summary');
2586
+ if (!(0, ethers_1.isAddress)(vault)) {
2587
+ throw new Error(`Vault parameter is not an address: ${String(vault)}`);
2588
+ }
2589
+ const endpoint = core_1.WEBSERVER_ENDPOINTS.public.tokenizedVault.summary(vault);
2590
+ const response = await (0, core_1.fetchAugustPublic)(endpoint, {
2591
+ headers: options?.headers,
2592
+ server: 'public',
2593
+ });
2594
+ if (!response.ok) {
2595
+ throw new Error(`Failed to fetch vault summary: ${response.statusText}`);
2596
+ }
2597
+ return (await response.json());
2598
+ }
2599
+ catch (e) {
2600
+ core_1.Logger.log.error('getVaultSummary', e, { vault });
2601
+ const errorMessage = e instanceof Error ? e.message : String(e);
2602
+ throw new Error(`#getVaultSummary::${vault}:${errorMessage}`);
2603
+ }
2604
+ }
2605
+ /**
2606
+ * Fetch withdrawal summary and pending queue for a vault.
2607
+ * @param vault - Vault contract address
2608
+ * @param chain - Chain identifier (e.g., chain ID as string)
2609
+ * @param options - Request options including headers
2610
+ * @returns Withdrawal summary and pending queue
2611
+ */
2612
+ async function getVaultWithdrawals({ vault, chain, options, }) {
2613
+ try {
2614
+ if (!vault) {
2615
+ throw new Error('Vault address parameter is undefined.');
2616
+ }
2617
+ (0, utils_3.assertNotStellar)(vault, 'Vault withdrawals');
2618
+ if (!(0, ethers_1.isAddress)(vault)) {
2619
+ throw new Error(`Vault parameter is not an address: ${String(vault)}`);
2620
+ }
2621
+ if (!chain) {
2622
+ throw new Error('Chain parameter is undefined.');
2623
+ }
2624
+ const endpoint = core_1.WEBSERVER_ENDPOINTS.public.tokenizedVault.withdrawals(chain, vault);
2625
+ const response = await (0, core_1.fetchAugustPublic)(endpoint, {
2626
+ headers: options?.headers,
2627
+ server: 'public',
2628
+ });
2629
+ if (!response.ok) {
2630
+ throw new Error(`Failed to fetch vault withdrawals: ${response.statusText}`);
2631
+ }
2632
+ return (await response.json());
2633
+ }
2634
+ catch (e) {
2635
+ core_1.Logger.log.error('getVaultWithdrawals', e, { vault, chain });
2636
+ const errorMessage = e instanceof Error ? e.message : String(e);
2637
+ throw new Error(`#getVaultWithdrawals::${vault}::${chain}:${errorMessage}`);
2638
+ }
2639
+ }
2640
+ /**
2641
+ * Fetch pending redemptions for a vault with liquidity analysis.
2642
+ * @param vault - Vault contract address
2643
+ * @param pastDays - Number of past days to include (default 7, min 1, max 30)
2644
+ * @param futureDays - Number of future days to include (default 14, min 1, max 30)
2645
+ * @param options - Request options including headers
2646
+ * @returns Pending redemptions grouped by date with liquidity summary
2647
+ */
2648
+ async function getVaultPendingRedemptions({ vault, pastDays, futureDays, options, }) {
2649
+ try {
2650
+ if (!vault)
2651
+ throw new Error('Vault address parameter is undefined.');
2652
+ if (!(0, ethers_1.isAddress)(vault))
2653
+ throw new Error(`Vault parameter is not an address: ${String(vault)}`);
2654
+ if (pastDays !== undefined && (pastDays < 1 || pastDays > 30))
2655
+ throw new Error('pastDays must be between 1 and 30');
2656
+ if (futureDays !== undefined && (futureDays < 1 || futureDays > 30))
2657
+ throw new Error('futureDays must be between 1 and 30');
2658
+ const params = new URLSearchParams();
2659
+ if (pastDays !== undefined)
2660
+ params.set('past_days', pastDays.toString());
2661
+ if (futureDays !== undefined)
2662
+ params.set('future_days', futureDays.toString());
2663
+ const qs = params.toString();
2664
+ const endpoint = `${core_1.WEBSERVER_ENDPOINTS.public.vaults.pendingRedemptions(vault)}${qs ? `?${qs}` : ''}`;
2665
+ const response = await (0, core_1.fetchAugustPublic)(endpoint, {
2666
+ headers: options?.headers,
2667
+ server: 'public',
2668
+ });
2669
+ if (!response.ok) {
2670
+ throw new Error(`Failed to fetch pending redemptions: ${response.statusText}`);
2671
+ }
2672
+ const raw = await response.json();
2673
+ return {
2674
+ vault: {
2675
+ address: raw.vault.address,
2676
+ chainId: raw.vault.chain_id,
2677
+ name: raw.vault.name,
2678
+ symbol: raw.vault.symbol,
2679
+ },
2680
+ asset: {
2681
+ address: raw.asset.address,
2682
+ symbol: raw.asset.symbol,
2683
+ decimals: raw.asset.decimals,
2684
+ },
2685
+ liquidity: {
2686
+ available: raw.liquidity.available,
2687
+ },
2688
+ summary: {
2689
+ totalPending: raw.summary.total_pending,
2690
+ pendingCount: raw.summary.pending_count,
2691
+ pendingToLiquidityRatio: raw.summary.pending_to_liquidity_ratio,
2692
+ isLiquiditySufficient: raw.summary.is_liquidity_sufficient,
2693
+ shortfall: raw.summary.shortfall,
2694
+ nextRedemptionDate: raw.summary.next_redemption_date,
2695
+ isOverdue: raw.summary.is_overdue,
2696
+ },
2697
+ redemptionsByDate: raw.redemptions_by_date.map((item) => ({
2698
+ date: item.date,
2699
+ assets: item.assets,
2700
+ daysUntilDue: item.days_until_due,
2701
+ isOverdue: item.is_overdue,
2702
+ })),
2703
+ };
2704
+ }
2705
+ catch (e) {
2706
+ core_1.Logger.log.error('getVaultPendingRedemptions', e, {
2707
+ vault,
2708
+ pastDays,
2709
+ futureDays,
2710
+ });
2711
+ const errorMessage = e instanceof Error ? e.message : String(e);
2712
+ throw new Error(`#getVaultPendingRedemptions::${vault}:${errorMessage}`);
2713
+ }
2714
+ }
2715
+ /**
2716
+ * Preview the amount of assets that would be received for redeeming shares (queued redemption).
2717
+ * @param vault - Vault contract address
2718
+ * @param sharesAmount - Amount of shares to redeem (human-readable, raw bigint, or string)
2719
+ * @param options - RPC configuration
2720
+ * @returns The amount of assets as an INormalizedNumber with `normalized` and `raw` fields
2721
+ */
2722
+ async function getPreviewRedemption({ vault, sharesAmount, options, }) {
2723
+ try {
2724
+ if (!vault)
2725
+ throw new Error('Vault address parameter is undefined.');
2726
+ (0, utils_3.assertNotStellar)(vault, 'Preview redemption');
2727
+ if (!(0, ethers_1.isAddress)(vault)) {
2728
+ throw new Error(`Vault parameter is not an address: ${String(vault)}`);
2729
+ }
2730
+ const provider = (0, core_1.createProvider)(options.rpcUrl);
2731
+ const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vault, options?.headers))?.[0];
2732
+ const vaultVersion = (0, core_1.getVaultVersionV2)(tokenizedVault);
2733
+ let assets;
2734
+ let decimals;
2735
+ if (vaultVersion === 'evm-2') {
2736
+ const contract = (0, core_1.createContract)({
2737
+ address: vault,
2738
+ abi: TokenizedVaultV2_1.ABI_TOKENIZED_VAULT_V2,
2739
+ provider,
2740
+ });
2741
+ // Retried on transport blips only (bounded, no fallback); a misroute
2742
+ // still surfaces the original error.
2743
+ const lpTokenAddress = await (0, core_1.getReceiptTokenAddressOrThrow)(provider, vault, 'getPreviewRedemption:receiptToken');
2744
+ const lpTokenContract = (0, core_1.createContract)({
2745
+ address: lpTokenAddress,
2746
+ abi: abis_1.ABI_LENDING_POOL_V2,
2747
+ provider,
2748
+ });
2749
+ decimals = Number(await lpTokenContract.decimals());
2750
+ const sharesBigInt = BigInt((0, core_1.toNormalizedBn)(sharesAmount, decimals).raw);
2751
+ const [assetsAmount] = await contract.previewRedemption(sharesBigInt, false);
2752
+ assets = assetsAmount;
2753
+ }
2754
+ else {
2755
+ const contract = (0, core_1.createContract)({
2756
+ address: vault,
2757
+ abi: abis_1.ABI_LENDING_POOL_V2,
2758
+ provider,
2759
+ });
2760
+ decimals = Number(await contract.decimals());
2761
+ const sharesBigInt = BigInt((0, core_1.toNormalizedBn)(sharesAmount, decimals).raw);
2762
+ assets = await contract.previewRedeem(sharesBigInt);
2763
+ }
2764
+ return (0, core_1.toNormalizedBn)(assets, decimals);
2765
+ }
2766
+ catch (e) {
2767
+ core_1.Logger.log.error('previewRedemption', e, { vault, sharesAmount });
2768
+ throw new Error(`Failed to preview redemption for ${vault}: ${e instanceof Error ? e.message : 'Unknown error'}`);
2769
+ }
2770
+ }
2771
+ /**
2772
+ * Helper: Decode a processing transaction to extract (year, month, day).
2773
+ *
2774
+ * Note: Multicall-wrapped transactions cannot be decoded from calldata or logs
2775
+ * (the WithdrawalProcessed event doesn't include the date). When decoding fails,
2776
+ * the caller falls back to timestamp-based date estimation.
2777
+ */
2778
+ async function decodeProcessingTransaction(txHash, provider, iface) {
2779
+ const tx = await provider.getTransaction(txHash);
2780
+ if (!tx) {
2781
+ return {
2782
+ functionName: 'unknown',
2783
+ year: 0,
2784
+ month: 0,
2785
+ day: 0,
2786
+ error: 'Transaction not found',
2787
+ };
2788
+ }
2789
+ return (0, call_data_decoder_1.decodeWithdrawalProcessing)(tx.data, iface);
2790
+ }
2791
+ /**
2792
+ * Returns all withdrawal requests for a vault, enriched with computed claimable dates
2793
+ * and current processing status.
2794
+ *
2795
+ * The unique identifier for matching requests to processed events is:
2796
+ * (receiver, claimableDate.year, claimableDate.month, claimableDate.day)
2797
+ *
2798
+ * This is the primary method for integrators to reliably track withdrawal status
2799
+ * when multiple requests are submitted by the same receiver.
2800
+ *
2801
+ * ## Data sources
2802
+ * - **Withdrawal requests** come from the subgraph (all historical requests).
2803
+ * - **Processed events** come from on-chain `WithdrawalProcessed` logs, which are
2804
+ * limited to a finite lookback window determined by the chain's block parameters.
2805
+ *
2806
+ * ## Lookback window & filtering
2807
+ * On-chain logs only cover a limited number of recent blocks (e.g. ~20 days on Monad,
2808
+ * ~21 days on Ethereum). The `lookbackBlocks` parameter controls how far back to scan
2809
+ * and is applied uniformly to both data sources: it bounds the `WithdrawalProcessed`
2810
+ * log query *and* drops any withdrawal request whose submission timestamp falls outside
2811
+ * the window — regardless of whether a matching processed event was found. Callers who
2812
+ * need deeper history should widen `lookbackBlocks`.
2813
+ *
2814
+ * ## Status determination
2815
+ * - `'processed'` — A matching `WithdrawalProcessed` event was found (by receiver + claimable date).
2816
+ * - `'ready_to_claim'` — Claimable date has passed (within the lookback window) but no processed event found.
2817
+ * - `'pending'` — Claimable date is still in the future.
2818
+ *
2819
+ * @param params - Query parameters:
2820
+ * - `vault` — Vault address to query.
2821
+ * - `receiver` — (Optional) Filter by receiver address.
2822
+ * - `lookbackBlocks` — (Optional) On-chain log lookback window in blocks.
2823
+ * Defaults to a chain-specific value (e.g. 150,000 for Ethereum, 3,456,000 for Monad).
2824
+ * Increasing this scans more history but requires more RPC calls.
2825
+ * - `options` — Standard vault query options (provider, RPC URL, etc.).
2826
+ * @returns Array of withdrawal requests with status and claimable dates
2827
+ *
2828
+ * @example
2829
+ * ```typescript
2830
+ * const requests = await getWithdrawalRequestsWithStatus({
2831
+ * vault: '0xVault...',
2832
+ * receiver: '0xReceiver...'
2833
+ * });
2834
+ *
2835
+ * // Results show exact status + claimable date
2836
+ * requests.forEach(req => {
2837
+ * console.log(
2838
+ * `${req.claimableDate.year}-${req.claimableDate.month}-${req.claimableDate.day}: ${req.status}`
2839
+ * );
2840
+ * });
2841
+ *
2842
+ * // With custom lookback window (e.g. 30 days on Ethereum ≈ 216,000 blocks)
2843
+ * const moreResults = await getWithdrawalRequestsWithStatus({
2844
+ * vault: '0xVault...',
2845
+ * lookbackBlocks: 216_000,
2846
+ * });
2847
+ * ```
2848
+ */
2849
+ async function getWithdrawalRequestsWithStatus(params) {
2850
+ try {
2851
+ const { vault, receiver, lookbackBlocks, options } = params;
2852
+ // Input validation
2853
+ if (!vault || !(0, ethers_1.isAddress)(vault)) {
2854
+ throw new Error('Invalid vault address');
2855
+ }
2856
+ const vaultAddress = (0, ethers_1.getAddress)(vault);
2857
+ const provider = (0, core_1.createProvider)(options.rpcUrl);
2858
+ // Fetch vault metadata and lag duration
2859
+ const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(vaultAddress, options?.headers, false, false))?.[0];
2860
+ if (!tokenizedVault) {
2861
+ core_1.Logger.log.warn('getWithdrawalRequestsWithStatus', `Vault not found in backend: ${vaultAddress}`);
2862
+ return [];
2863
+ }
2864
+ const version = (0, core_1.getVaultVersionV2)(tokenizedVault);
2865
+ const isV2 = version === 'evm-2';
2866
+ const vaultAbi = isV2 ? TokenizedVaultV2_1.ABI_TOKENIZED_VAULT_V2 : abis_1.ABI_LENDING_POOL_V2;
2867
+ const vaultContract = (0, core_1.createContract)({
2868
+ address: vaultAddress,
2869
+ abi: vaultAbi,
2870
+ provider,
2871
+ });
2872
+ let lagDuration = 86400; // Default: 1 day
2873
+ try {
2874
+ const lagValue = await vaultContract.lagDuration?.();
2875
+ if (lagValue !== undefined && lagValue !== null) {
2876
+ lagDuration = Number(lagValue);
2877
+ }
2878
+ }
2879
+ catch (e) {
2880
+ 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'}`);
2881
+ }
2882
+ // Fetch subgraph requests, on-chain processed events, and the current block
2883
+ // in parallel. Current block is needed to derive the block-based lookback
2884
+ // cutoff applied uniformly to both data sources.
2885
+ const [withdrawalRequests, processedEvents, currentBlock] = await Promise.all([
2886
+ (0, vaults_1.getSubgraphWithdrawRequests)(vaultAddress, provider),
2887
+ getVaultRedemptionHistory({
2888
+ vault: vaultAddress,
2889
+ wallet: receiver,
2890
+ lookbackBlocks,
2891
+ options,
2892
+ }),
2893
+ provider.getBlockNumber(),
2894
+ ]);
2895
+ if (!withdrawalRequests.length) {
2896
+ return [];
2897
+ }
2898
+ const chainId = tokenizedVault.chain;
2899
+ const effectiveLookbackBlocks = lookbackBlocks ?? (0, core_1.determineBlockCutoff)(chainId);
2900
+ const blockCutoff = currentBlock - effectiveLookbackBlocks;
2901
+ const now = Math.floor(Date.now() / 1000);
2902
+ // Filter by receiver (if provided) and drop any request whose submission
2903
+ // block is outside the lookback window — uniformly, regardless of whether
2904
+ // a matching processed event exists. Using block_number (not timestamp)
2905
+ // keeps this consistent with the on-chain log scan's block-based cutoff.
2906
+ const filteredRequests = withdrawalRequests.filter((req) => {
2907
+ if (receiver &&
2908
+ req.receiverAddr?.toLowerCase() !== receiver.toLowerCase()) {
2909
+ return false;
2910
+ }
2911
+ return Number(req.block_number) >= blockCutoff;
2912
+ });
2913
+ // Pre-decode all processed transactions in parallel for performance
2914
+ const iface = new ethers_1.Interface(vaultAbi);
2915
+ const decodedProcessed = await Promise.all((processedEvents || []).map(async (processed) => {
2916
+ // Try calldata decoding first (deterministic)
2917
+ if (processed.transactionHash) {
2918
+ try {
2919
+ const decoded = await decodeProcessingTransaction(processed.transactionHash, provider, iface);
2920
+ if (!decoded.error) {
2921
+ return {
2922
+ ...processed,
2923
+ decodedDate: {
2924
+ year: decoded.year,
2925
+ month: decoded.month,
2926
+ day: decoded.day,
2927
+ },
2928
+ };
2929
+ }
2930
+ }
2931
+ catch (e) {
2932
+ core_1.Logger.log.warn('getWithdrawalRequestsWithStatus', `Failed to decode processed transaction ${processed.transactionHash}: ${e instanceof Error ? e.message : 'Unknown error'}`);
2933
+ }
2934
+ }
2935
+ // Fallback: use original request timestamp to compute claimable date,
2936
+ // or if unavailable (evm-2: requested=epoch 0), use the processed block
2937
+ // timestamp as the date directly (processing happens on the claimable date).
2938
+ const requestedEpoch = Math.floor(processed.requested.getTime() / 1000);
2939
+ if (requestedEpoch > 0) {
2940
+ // evm-1: we have the original request timestamp → compute claimable date
2941
+ const fallbackDate = (0, date_utils_1.computeClaimableDate)(requestedEpoch, lagDuration);
2942
+ return {
2943
+ ...processed,
2944
+ decodedDate: {
2945
+ year: fallbackDate.year,
2946
+ month: fallbackDate.month,
2947
+ day: fallbackDate.day,
2948
+ },
2949
+ };
2950
+ }
2951
+ // evm-2: no request timestamp — use processed block date directly
2952
+ // (processing occurs on the claimable date, so the block date IS the date)
2953
+ const processedDate = processed.processed;
2954
+ return {
2955
+ ...processed,
2956
+ decodedDate: {
2957
+ year: processedDate.getUTCFullYear(),
2958
+ month: processedDate.getUTCMonth() + 1,
2959
+ day: processedDate.getUTCDate(),
2960
+ },
2961
+ };
2962
+ }));
2963
+ // Build deterministic lookup map: (receiver + year + month + day) → processed events
2964
+ // A single WithdrawalProcessed event covers the entire day's batch for a
2965
+ // receiver — all requests whose claimable date falls on that day should
2966
+ // match it. We keep an array per key only to preserve every event we saw
2967
+ // for visibility; matching is a non-destructive peek at the first entry.
2968
+ const processedMap = new Map();
2969
+ for (const processed of decodedProcessed) {
2970
+ if (!processed.decodedDate) {
2971
+ continue;
2972
+ }
2973
+ const dateKey = (0, date_utils_1.formatDateKey)(processed.decodedDate.year, processed.decodedDate.month, processed.decodedDate.day);
2974
+ const key = `${processed.receiver.toLowerCase()}:${dateKey}`;
2975
+ const existing = processedMap.get(key) ?? [];
2976
+ processedMap.set(key, [...existing, processed]);
2977
+ }
2978
+ // Compute status for each withdrawal request. The lookback window was
2979
+ // already applied up-front when pruning filteredRequests, so every request
2980
+ // reaching this loop is within the window by construction.
2981
+ const results = [];
2982
+ for (const req of filteredRequests) {
2983
+ // timestamp_ is a Unix epoch string (seconds)
2984
+ const requestTimestamp = Number(req.timestamp_);
2985
+ // Compute claimable date from timestamp + lagDuration (UTC)
2986
+ const claimableDate = (0, date_utils_1.computeClaimableDate)(requestTimestamp, lagDuration);
2987
+ // Determine status: check processed first, then claimability
2988
+ let status = 'pending';
2989
+ let processedTxHash;
2990
+ let assetsReceived;
2991
+ // Match deterministically by (receiver, year, month, day)
2992
+ const dateKey = (0, date_utils_1.formatDateKey)(claimableDate.year, claimableDate.month, claimableDate.day);
2993
+ const lookupKey = `${req.receiverAddr.toLowerCase()}:${dateKey}`;
2994
+ // Peek — do not consume. The processing event covers the whole day for
2995
+ // this receiver, so every request on that day matches the same event.
2996
+ const processedEvent = processedMap.get(lookupKey)?.[0];
2997
+ if (processedEvent) {
2998
+ status = 'processed';
2999
+ processedTxHash = processedEvent.transactionHash;
3000
+ try {
3001
+ assetsReceived = BigInt(processedEvent.amount.raw);
3002
+ }
3003
+ catch (e) {
3004
+ core_1.Logger.log.warn(`Failed to parse assets as BigInt: ${e}`);
3005
+ }
3006
+ }
3007
+ else if ((0, date_utils_1.isClaimableNow)(claimableDate.epoch, now)) {
3008
+ // Claimable date is recent (within lookback) and not yet processed
3009
+ status = 'ready_to_claim';
3010
+ }
3011
+ results.push({
3012
+ transactionHash: req.transactionHash_,
3013
+ requestTimestamp,
3014
+ shares: (() => {
3015
+ try {
3016
+ return BigInt(req.shares);
3017
+ }
3018
+ catch {
3019
+ // Handle decimal strings from subgraph (e.g. "1234.0")
3020
+ const truncated = Math.trunc(Number(req.shares));
3021
+ if (!Number.isFinite(truncated)) {
3022
+ core_1.Logger.log.warn('getWithdrawalRequestsWithStatus', `Cannot parse shares value "${req.shares}" — defaulting to 0`);
3023
+ return BigInt(0);
3024
+ }
3025
+ core_1.Logger.log.info('getWithdrawalRequestsWithStatus', `Truncated decimal shares "${req.shares}" → ${truncated}`);
3026
+ return BigInt(truncated);
3027
+ }
3028
+ })(),
3029
+ receiver: req.receiverAddr,
3030
+ claimableDate: {
3031
+ year: claimableDate.year,
3032
+ month: claimableDate.month,
3033
+ day: claimableDate.day,
3034
+ },
3035
+ claimableEpoch: claimableDate.epoch,
3036
+ status,
3037
+ processedTransactionHash: processedTxHash,
3038
+ assetsReceived,
3039
+ });
3040
+ }
3041
+ return results;
3042
+ }
3043
+ catch (e) {
3044
+ core_1.Logger.log.error('getWithdrawalRequestsWithStatus', e, {
3045
+ vaultAddress: params.vault,
3046
+ hasReceiver: !!params.receiver,
3047
+ });
3048
+ throw new Error(`Failed to get withdrawal requests with status: ${e instanceof Error ? e.message : 'Unknown error'}`);
3049
+ }
3050
+ }
3051
+ //# sourceMappingURL=getters.js.map