@augustdigital/sdk 8.10.0 → 8.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/lib/adapters/evm/index.d.ts +17 -1
  2. package/lib/adapters/evm/index.js +20 -0
  3. package/lib/adapters/stellar/actions.js +4 -4
  4. package/lib/adapters/stellar/getters.d.ts +9 -3
  5. package/lib/adapters/stellar/getters.js +18 -6
  6. package/lib/adapters/stellar/index.d.ts +17 -2
  7. package/lib/adapters/stellar/index.js +24 -5
  8. package/lib/adapters/stellar/soroban.d.ts +89 -2
  9. package/lib/adapters/stellar/soroban.js +487 -64
  10. package/lib/adapters/stellar/submit.d.ts +4 -1
  11. package/lib/adapters/stellar/submit.js +7 -3
  12. package/lib/adapters/stellar/types.d.ts +12 -0
  13. package/lib/core/analytics/method-taxonomy.js +1 -0
  14. package/lib/core/analytics/sanitize.js +19 -3
  15. package/lib/core/analytics/version.d.ts +1 -1
  16. package/lib/core/analytics/version.js +1 -1
  17. package/lib/core/base.class.d.ts +2 -1
  18. package/lib/core/constants/swap-router.d.ts +2 -0
  19. package/lib/core/constants/swap-router.js +1 -0
  20. package/lib/core/constants/web3.d.ts +19 -0
  21. package/lib/core/constants/web3.js +37 -1
  22. package/lib/core/helpers/multicall.d.ts +68 -0
  23. package/lib/core/helpers/multicall.js +103 -0
  24. package/lib/core/helpers/swap-router.d.ts +36 -1
  25. package/lib/core/helpers/swap-router.js +80 -0
  26. package/lib/core/helpers/vault-version.d.ts +4 -1
  27. package/lib/core/helpers/vaults.d.ts +1 -1
  28. package/lib/core/index.d.ts +1 -0
  29. package/lib/core/index.js +1 -0
  30. package/lib/main.js +6 -1
  31. package/lib/modules/vaults/getters.d.ts +25 -2
  32. package/lib/modules/vaults/getters.js +46 -12
  33. package/lib/modules/vaults/main.d.ts +28 -0
  34. package/lib/modules/vaults/main.js +89 -0
  35. package/lib/modules/vaults/prefetch.d.ts +65 -0
  36. package/lib/modules/vaults/prefetch.js +120 -0
  37. package/lib/modules/vaults/types.d.ts +11 -1
  38. package/lib/sdk.d.ts +6725 -6507
  39. package/lib/types/subgraph.d.ts +9 -0
  40. package/lib/types/vaults.d.ts +44 -0
  41. package/lib/types/web3.d.ts +15 -0
  42. package/package.json +1 -1
@@ -36,6 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.getSwapRouterDepositResult = void 0;
39
40
  exports.getVault = getVault;
40
41
  exports.getVaultLoans = getVaultLoans;
41
42
  exports.getVaultSubaccountLoans = getVaultSubaccountLoans;
@@ -548,7 +549,7 @@ async function getVaultAllocations(vault, options) {
548
549
  unfilteredTokens: unfilteredTokens,
549
550
  };
550
551
  }
551
- async function getVaultAvailableRedemptions({ vault, wallet, options, }) {
552
+ async function getVaultAvailableRedemptions({ vault, wallet, options, prefetchedReads, }) {
552
553
  try {
553
554
  // Stellar vaults don't support on-chain redemptions yet
554
555
  if ((0, utils_3.isStellarAddress)(vault)) {
@@ -592,8 +593,9 @@ async function getVaultAvailableRedemptions({ vault, wallet, options, }) {
592
593
  else {
593
594
  decimals = await (0, core_1.getDecimals)(provider, vault);
594
595
  }
595
- const ld = await vaultContract.lagDuration();
596
- const lagDuration = Number(ld);
596
+ const lagDuration = typeof prefetchedReads?.lagDuration === 'number'
597
+ ? prefetchedReads.lagDuration
598
+ : Number(await vaultContract.lagDuration());
597
599
  const { withdrawalRequesteds, withdrawalProcesseds } = await (0, subgraph_1.getSubgraphAllWithdrawals)(vault, provider);
598
600
  // format
599
601
  const availableRedemptions = [];
@@ -991,10 +993,15 @@ async function getVaultRedemptionHistory({ vault, wallet, lookbackBlocks, option
991
993
  throw e;
992
994
  }
993
995
  }
996
+ // Moved to core/helpers/swap-router.ts to break the adapters/evm ↔
997
+ // modules/vaults/getters import cycle (CLAUDE.md §9); re-exported here for
998
+ // back-compat with existing importers.
999
+ var swap_router_1 = require("../../core/helpers/swap-router");
1000
+ Object.defineProperty(exports, "getSwapRouterDepositResult", { enumerable: true, get: function () { return swap_router_1.getSwapRouterDepositResult; } });
994
1001
  /**
995
1002
  * Vault Positions
996
1003
  */
997
- async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, options, }) {
1004
+ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, options, prefetchedReads, }) {
998
1005
  try {
999
1006
  const tokenizedVaults = await (0, core_1.fetchTokenizedVault)(vault ? vault : undefined, options?.headers, false, false);
1000
1007
  if (!tokenizedVaults || tokenizedVaults.length === 0) {
@@ -1058,7 +1065,11 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
1058
1065
  let balance = (0, core_1.toNormalizedBn)(0);
1059
1066
  if (stellarWallet && (0, utils_3.isStellarAddress)(stellarWallet)) {
1060
1067
  const stellarNetwork = v.stellar_vault_metadata?.network_name ?? 'mainnet';
1061
- const position = await StellarGetters.getStellarUserPosition(v.address, stellarWallet, stellarNetwork);
1068
+ // Only use the configured RPC override for its own network.
1069
+ const stellarOverrideRpcUrl = options.stellarRpc?.network === stellarNetwork
1070
+ ? options.stellarRpc.rpcUrl
1071
+ : undefined;
1072
+ const position = await StellarGetters.getStellarUserPosition(v.address, stellarWallet, stellarNetwork, stellarOverrideRpcUrl);
1062
1073
  if (position && !position.decimalsFromFallback) {
1063
1074
  balance = (0, core_1.toNormalizedBn)(BigInt(position.shares), position.decimals);
1064
1075
  }
@@ -1096,24 +1107,37 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
1096
1107
  abi: abis_1.ABI_LENDING_POOL_V2,
1097
1108
  address: v.address,
1098
1109
  });
1110
+ // Batched reads only apply to the vault this call was made for —
1111
+ // guard against a multi-row backend response reusing them.
1112
+ const prefetched = v.address?.toLowerCase?.() === String(vault).toLowerCase()
1113
+ ? prefetchedReads
1114
+ : undefined;
1099
1115
  // get vault metadata and balance based on version
1100
1116
  let bal = BigInt(0);
1101
1117
  if (version === 'evm-2') {
1102
1118
  const receiptAddress = await (0, core_1.getReceiptTokenAddress)(provider, vault);
1103
1119
  decimals = await (0, core_1.getDecimals)(provider, receiptAddress, false);
1104
1120
  if (wallet && (0, ethers_1.isAddress)(wallet)) {
1105
- const receiptContract = (0, core_1.createContract)({
1106
- provider,
1107
- abi: TokenizedVaultV2Receipt_1.ABI_TOKENIZED_VAULT_V2_RECEIPT,
1108
- address: receiptAddress,
1109
- });
1110
- bal = await receiptContract.balanceOf(wallet);
1121
+ if (typeof prefetched?.balance === 'bigint') {
1122
+ bal = prefetched.balance;
1123
+ }
1124
+ else {
1125
+ const receiptContract = (0, core_1.createContract)({
1126
+ provider,
1127
+ abi: TokenizedVaultV2Receipt_1.ABI_TOKENIZED_VAULT_V2_RECEIPT,
1128
+ address: receiptAddress,
1129
+ });
1130
+ bal = await receiptContract.balanceOf(wallet);
1131
+ }
1111
1132
  }
1112
1133
  }
1113
1134
  else {
1114
1135
  decimals = await (0, core_1.getDecimals)(provider, vault);
1115
1136
  if (wallet && (0, ethers_1.isAddress)(wallet)) {
1116
- bal = await vaultContract.balanceOf(wallet);
1137
+ bal =
1138
+ typeof prefetched?.balance === 'bigint'
1139
+ ? prefetched.balance
1140
+ : await vaultContract.balanceOf(wallet);
1117
1141
  }
1118
1142
  }
1119
1143
  const balance = (0, core_1.toNormalizedBn)(bal, decimals);
@@ -1122,6 +1146,7 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
1122
1146
  vault: v.address,
1123
1147
  wallet,
1124
1148
  options,
1149
+ prefetchedReads: prefetched,
1125
1150
  });
1126
1151
  // get aggregate redemption amount
1127
1152
  const aggregateAvailableRedemptions = availableRedemptions.reduce((acc, curr) => acc + BigInt(curr.amount.raw), BigInt(0));
@@ -1855,6 +1880,15 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
1855
1880
  if (!tokenizedVault) {
1856
1881
  throw new Error(`Vault ${vault} not found`);
1857
1882
  }
1883
+ // getDecimals resolves to `undefined` on a failed/absent decimals() read.
1884
+ // Unguarded, that undefined flows into `10 ** decimals` (=> NaN) and
1885
+ // toNormalizedBn(x, undefined), and the first BigInt(NaN) throws the opaque
1886
+ // "The number NaN cannot be converted to a BigInt" error. Decimals must come
1887
+ // from the chain — never assume a fallback — so fail fast with actionable
1888
+ // context. Transient RPC blips recover on the caller's retry.
1889
+ if (typeof decimals !== 'number' || !Number.isFinite(decimals)) {
1890
+ throw new Error(`Failed to resolve decimals for vault ${vault}`);
1891
+ }
1858
1892
  // Fetch LayerZero deposits/redeems for supported vaults
1859
1893
  const lzVaultKey = (0, deposits_1.isLayerZeroVault)(vault);
1860
1894
  let lzDeposits = [];
@@ -23,6 +23,12 @@ export declare class AugustVaults extends AugustBase {
23
23
  private solanaService;
24
24
  private suiService;
25
25
  private headers;
26
+ /**
27
+ * Consumer-supplied Soroban RPC override for Stellar vault reads, with the
28
+ * network it was configured for so it's only applied to matching-network
29
+ * vaults (see `IVaultBaseOptions.stellarRpc`).
30
+ */
31
+ private _stellarRpc?;
26
32
  constructor(baseConfig: IAugustBase, solana: SolanaAdapter, sui: SuiAdapter);
27
33
  /**
28
34
  * @description deposit underlying token into the specified pool with adapter support. This is for when we cannot pass in a signer in sdk and need to do so as an arg
@@ -44,6 +50,17 @@ export declare class AugustVaults extends AugustBase {
44
50
  * Includes closed vaults that are still visible (`status: 'closed'`,
45
51
  * `is_visible: true`) so consumers can render positions held in closed
46
52
  * vaults; closed vaults with `is_visible: false` are excluded.
53
+ *
54
+ * RPC volume (worst case, wallet enrichment path): the per-vault
55
+ * `balanceOf`/`lagDuration` reads are aggregated into chunked Multicall3
56
+ * `aggregate3` batches per chain (10 calls per chunk) on chains where the
57
+ * canonical Multicall3 deployment is verified; other chains (e.g. Citrea
58
+ * 4114) keep one `eth_call` per read. Measured with
59
+ * `benchmarks/request-counter.js` on a cold cache for a wallet with 62
60
+ * vaults across Ethereum + Avalanche: 83 RPC HTTP requests before batching,
61
+ * 54 after (~35% fewer; 124 per-vault `eth_call`s collapse into ~13
62
+ * `aggregate3` calls — the remaining requests are the per-vault cached
63
+ * decimals/receipt-token reads on a cold cache).
47
64
  * @param options Filtering and enrichment configuration
48
65
  * @returns Array of vault objects with optional loans/allocations/positions
49
66
  */
@@ -196,7 +213,18 @@ export declare class AugustVaults extends AugustBase {
196
213
  lookbackBlocks?: number;
197
214
  }): Promise<import("../../types").IVaultRedemptionHistoryItem[]>;
198
215
  /**
216
+ * Fetch the wallet's positions across vaults — a single vault, one chain,
217
+ * or every chain with a configured provider.
199
218
  *
219
+ * RPC volume (worst case, multi-vault paths): the two non-cacheable
220
+ * per-vault reads (`balanceOf`, `lagDuration`) are aggregated into chunked
221
+ * Multicall3 `aggregate3` batches per chain (10 calls per chunk) on chains
222
+ * where the canonical deployment is verified; unverified chains (e.g.
223
+ * Citrea 4114) and non-EVM vaults keep one `eth_call` per read. Measured
224
+ * with `benchmarks/request-counter.js` on a cold cache for a wallet with 62
225
+ * vaults across Ethereum + Avalanche: 83 RPC HTTP requests before batching,
226
+ * 54 after (~35% fewer). A vault whose batched read fails (e.g. paused)
227
+ * falls back to its own per-vault reads — never to a zeroed balance.
200
228
  */
201
229
  getVaultPositions({ vault, wallet, chainId, showAllVaults, solanaWallet, stellarWallet, options, }: {
202
230
  vault?: IAddress;
@@ -66,6 +66,7 @@ const constants_1 = require("../../adapters/sui/constants");
66
66
  const vaults_2 = require("../../core/constants/vaults");
67
67
  const write_actions_1 = require("./write.actions");
68
68
  const read_actions_1 = require("./read.actions");
69
+ const prefetch_1 = require("./prefetch");
69
70
  /**
70
71
  * Vault operations class handling multi-chain vault queries and user positions.
71
72
  * Supports both EVM and Solana vaults with unified interface.
@@ -74,10 +75,23 @@ class AugustVaults extends core_1.AugustBase {
74
75
  solanaService;
75
76
  suiService;
76
77
  headers = null;
78
+ /**
79
+ * Consumer-supplied Soroban RPC override for Stellar vault reads, with the
80
+ * network it was configured for so it's only applied to matching-network
81
+ * vaults (see `IVaultBaseOptions.stellarRpc`).
82
+ */
83
+ _stellarRpc;
77
84
  constructor(baseConfig, solana, sui) {
78
85
  super(baseConfig);
79
86
  this.solanaService = solana;
80
87
  this.suiService = sui || new sui_1.default();
88
+ // Network defaults to mainnet, matching the StellarAdapter constructor.
89
+ this._stellarRpc = baseConfig.stellar?.rpcUrl
90
+ ? {
91
+ rpcUrl: baseConfig.stellar.rpcUrl,
92
+ network: baseConfig.stellar.network ?? 'mainnet',
93
+ }
94
+ : undefined;
81
95
  this.headers = {
82
96
  'x-environment': this.monitoring?.['x-environment'],
83
97
  'x-user-id': this.monitoring?.['x-user-id'],
@@ -107,6 +121,17 @@ class AugustVaults extends core_1.AugustBase {
107
121
  * Includes closed vaults that are still visible (`status: 'closed'`,
108
122
  * `is_visible: true`) so consumers can render positions held in closed
109
123
  * vaults; closed vaults with `is_visible: false` are excluded.
124
+ *
125
+ * RPC volume (worst case, wallet enrichment path): the per-vault
126
+ * `balanceOf`/`lagDuration` reads are aggregated into chunked Multicall3
127
+ * `aggregate3` batches per chain (10 calls per chunk) on chains where the
128
+ * canonical Multicall3 deployment is verified; other chains (e.g. Citrea
129
+ * 4114) keep one `eth_call` per read. Measured with
130
+ * `benchmarks/request-counter.js` on a cold cache for a wallet with 62
131
+ * vaults across Ethereum + Avalanche: 83 RPC HTTP requests before batching,
132
+ * 54 after (~35% fewer; 124 per-vault `eth_call`s collapse into ~13
133
+ * `aggregate3` calls — the remaining requests are the per-vault cached
134
+ * decimals/receipt-token reads on a cold cache).
110
135
  * @param options Filtering and enrichment configuration
111
136
  * @returns Array of vault objects with optional loans/allocations/positions
112
137
  */
@@ -166,6 +191,7 @@ class AugustVaults extends core_1.AugustBase {
166
191
  options: {
167
192
  rpcUrl,
168
193
  solanaService: this.solanaService,
194
+ stellarRpc: this._stellarRpc,
169
195
  env: this.monitoring?.env,
170
196
  augustKey: this.keys?.august,
171
197
  subgraphKey: this.keys?.graph,
@@ -234,6 +260,14 @@ class AugustVaults extends core_1.AugustBase {
234
260
  if ((options.wallet && (0, ethers_1.isAddress)(options.wallet)) ||
235
261
  (options.solanaWallet &&
236
262
  utils_1.SolanaUtils.isSolanaAddress(options.solanaWallet))) {
263
+ // Batch the per-vault balanceOf/lagDuration reads (Multicall3, grouped
264
+ // by chain) before fanning out — vaults the batch couldn't serve fall
265
+ // back to their own reads inside getVaultPositions.
266
+ const prefetchedReads = await (0, prefetch_1.prefetchVaultPositionReads)({
267
+ vaults: vaultsPerAvailableProviders,
268
+ wallet: options.wallet,
269
+ providers: this.providers,
270
+ });
237
271
  // fetch all positions for user wallet
238
272
  const vaultResponses = await (0, core_1.promiseSettle)(vaultsPerAvailableProviders.map((v) => (0, getters_1.getVaultPositions)({
239
273
  vault: v.address,
@@ -243,10 +277,12 @@ class AugustVaults extends core_1.AugustBase {
243
277
  options: {
244
278
  rpcUrl: this.providers?.[v.chain],
245
279
  solanaService: this.solanaService,
280
+ stellarRpc: this._stellarRpc,
246
281
  env: this.monitoring?.env,
247
282
  augustKey: this.keys?.august,
248
283
  subgraphKey: this.keys?.graph,
249
284
  },
285
+ prefetchedReads: prefetchedReads.get(String(v.address).toLowerCase()),
250
286
  })), 5, // 5 retries
251
287
  2000);
252
288
  // return vault object with position property
@@ -316,6 +352,7 @@ class AugustVaults extends core_1.AugustBase {
316
352
  ? this.providers?.[chainId]
317
353
  : this.activeNetwork?.rpcUrl,
318
354
  solanaService: this.solanaService,
355
+ stellarRpc: this._stellarRpc,
319
356
  env: this.monitoring?.env,
320
357
  augustKey: this.keys?.august,
321
358
  subgraphKey: this.keys?.graph,
@@ -340,6 +377,7 @@ class AugustVaults extends core_1.AugustBase {
340
377
  ? this.providers?.[chainId]
341
378
  : this.activeNetwork?.rpcUrl,
342
379
  solanaService: this.solanaService,
380
+ stellarRpc: this._stellarRpc,
343
381
  env: this.monitoring?.env,
344
382
  augustKey: this.keys?.august,
345
383
  subgraphKey: this.keys?.graph,
@@ -385,6 +423,7 @@ class AugustVaults extends core_1.AugustBase {
385
423
  subgraphKey: this.keys?.graph,
386
424
  chainId: chainId,
387
425
  solanaService: this.solanaService,
426
+ stellarRpc: this._stellarRpc,
388
427
  headers: this.headers,
389
428
  });
390
429
  return vaultResponse;
@@ -416,6 +455,7 @@ class AugustVaults extends core_1.AugustBase {
416
455
  subgraphKey: this.keys?.graph,
417
456
  chainId: chainId,
418
457
  solanaService: this.solanaService,
458
+ stellarRpc: this._stellarRpc,
419
459
  headers: this.headers,
420
460
  });
421
461
  return vaultResponse;
@@ -447,6 +487,7 @@ class AugustVaults extends core_1.AugustBase {
447
487
  ? this.providers?.[chainId]
448
488
  : this.activeNetwork?.rpcUrl,
449
489
  solanaService: this.solanaService,
490
+ stellarRpc: this._stellarRpc,
450
491
  env: this.monitoring?.env,
451
492
  augustKey: this.keys?.august,
452
493
  subgraphKey: this.keys?.graph,
@@ -527,6 +568,7 @@ class AugustVaults extends core_1.AugustBase {
527
568
  ? this.providers?.[chainId]
528
569
  : this.activeNetwork?.rpcUrl,
529
570
  solanaService: this.solanaService,
571
+ stellarRpc: this._stellarRpc,
530
572
  env: this.monitoring?.env,
531
573
  augustKey: this.keys?.august,
532
574
  subgraphKey: this.keys?.graph,
@@ -588,7 +630,18 @@ class AugustVaults extends core_1.AugustBase {
588
630
  });
589
631
  }
590
632
  /**
633
+ * Fetch the wallet's positions across vaults — a single vault, one chain,
634
+ * or every chain with a configured provider.
591
635
  *
636
+ * RPC volume (worst case, multi-vault paths): the two non-cacheable
637
+ * per-vault reads (`balanceOf`, `lagDuration`) are aggregated into chunked
638
+ * Multicall3 `aggregate3` batches per chain (10 calls per chunk) on chains
639
+ * where the canonical deployment is verified; unverified chains (e.g.
640
+ * Citrea 4114) and non-EVM vaults keep one `eth_call` per read. Measured
641
+ * with `benchmarks/request-counter.js` on a cold cache for a wallet with 62
642
+ * vaults across Ethereum + Avalanche: 83 RPC HTTP requests before batching,
643
+ * 54 after (~35% fewer). A vault whose batched read fails (e.g. paused)
644
+ * falls back to its own per-vault reads — never to a zeroed balance.
592
645
  */
593
646
  async getVaultPositions({ vault, wallet, chainId, showAllVaults, solanaWallet, stellarWallet, options, }) {
594
647
  // if (!this.authorized) throw new Error('Not authorized.');
@@ -611,6 +664,7 @@ class AugustVaults extends core_1.AugustBase {
611
664
  ? this.providers?.[chainId]
612
665
  : this.activeNetwork?.rpcUrl,
613
666
  solanaService: this.solanaService,
667
+ stellarRpc: this._stellarRpc,
614
668
  env: this.monitoring?.env,
615
669
  augustKey: this.keys?.august,
616
670
  subgraphKey: this.keys?.graph,
@@ -624,6 +678,13 @@ class AugustVaults extends core_1.AugustBase {
624
678
  const allVaults = await (0, core_1.fetchTokenizedVaults)(undefined, this.headers, false, false);
625
679
  if (chainId) {
626
680
  const vaultsPerChain = allVaults.filter((v) => v.chain === chainId);
681
+ // Batch balanceOf/lagDuration via Multicall3 before the per-vault
682
+ // fan-out; un-batched vaults fall back to their own reads.
683
+ const prefetchedReads = await (0, prefetch_1.prefetchVaultPositionReads)({
684
+ vaults: vaultsPerChain,
685
+ wallet,
686
+ providers: this.providers,
687
+ });
627
688
  const vaultResponses = await Promise.all(vaultsPerChain.map((v) => (0, getters_1.getVaultPositions)({
628
689
  vault: v.address,
629
690
  solanaWallet,
@@ -632,11 +693,13 @@ class AugustVaults extends core_1.AugustBase {
632
693
  options: {
633
694
  rpcUrl: this.providers?.[v.chain],
634
695
  solanaService: this.solanaService,
696
+ stellarRpc: this._stellarRpc,
635
697
  env: this.monitoring?.env,
636
698
  augustKey: this.keys?.august,
637
699
  subgraphKey: this.keys?.graph,
638
700
  headers: this.headers,
639
701
  },
702
+ prefetchedReads: prefetchedReads.get(String(v.address).toLowerCase()),
640
703
  })));
641
704
  const flattened = vaultResponses.flat();
642
705
  let final;
@@ -659,6 +722,13 @@ class AugustVaults extends core_1.AugustBase {
659
722
  // outer catch, and silently return PENDING (then get filtered out).
660
723
  (v?.chain_type === 'solana' && !!this.solanaService) ||
661
724
  (this.providers?.[v?.chain] ? true : false));
725
+ // Batch balanceOf/lagDuration via Multicall3, grouped by chain, before
726
+ // the per-vault fan-out; un-batched vaults fall back to their own reads.
727
+ const prefetchedReads = await (0, prefetch_1.prefetchVaultPositionReads)({
728
+ vaults: vaultsPerAvailableProviders,
729
+ wallet,
730
+ providers: this.providers,
731
+ });
662
732
  const vaultResponses = await Promise.all(vaultsPerAvailableProviders.map((v) => (0, getters_1.getVaultPositions)({
663
733
  vault: v.address,
664
734
  solanaWallet,
@@ -667,11 +737,13 @@ class AugustVaults extends core_1.AugustBase {
667
737
  options: {
668
738
  rpcUrl: this.providers?.[v.chain],
669
739
  solanaService: this.solanaService,
740
+ stellarRpc: this._stellarRpc,
670
741
  env: this.monitoring?.env,
671
742
  augustKey: this.keys?.august,
672
743
  subgraphKey: this.keys?.graph,
673
744
  headers: this.headers,
674
745
  },
746
+ prefetchedReads: prefetchedReads.get(String(v.address).toLowerCase()),
675
747
  })));
676
748
  const flattened = vaultResponses.flat();
677
749
  let final;
@@ -706,6 +778,16 @@ class AugustVaults extends core_1.AugustBase {
706
778
  chainId: _chainId,
707
779
  type: h.type,
708
780
  transactionHash: h.transactionHash_,
781
+ // Deposit rows on multi-asset (pre-deposit) vaults carry the actual
782
+ // deposited token address; propagate it so consumers render the right
783
+ // asset symbol instead of guessing the vault's first deposit asset.
784
+ assetIn: h.assetIn,
785
+ // withdraw-request rows carry the burned share amount. Normalize with
786
+ // the vault decimals (the share token's own decimals) so UIs can show
787
+ // the receipt-token quantity that was actually burned.
788
+ shares: h.shares
789
+ ? (0, core_1.toNormalizedBn)(BigInt(h.shares), h.decimals)
790
+ : undefined,
709
791
  }));
710
792
  }
711
793
  // Helper function to fetch history for a vault
@@ -826,6 +908,7 @@ class AugustVaults extends core_1.AugustBase {
826
908
  ? this.providers?.[chainId]
827
909
  : this.requireActiveNetwork().rpcUrl,
828
910
  solanaService: this.solanaService,
911
+ stellarRpc: this._stellarRpc,
829
912
  env: this.monitoring?.env,
830
913
  augustKey: this.keys?.august,
831
914
  subgraphKey: this.keys?.graph,
@@ -851,6 +934,7 @@ class AugustVaults extends core_1.AugustBase {
851
934
  ? this.providers?.[chainId]
852
935
  : this.requireActiveNetwork().rpcUrl,
853
936
  solanaService: this.solanaService,
937
+ stellarRpc: this._stellarRpc,
854
938
  env: this.monitoring?.env,
855
939
  augustKey: this.keys?.august,
856
940
  subgraphKey: this.keys?.graph,
@@ -1033,6 +1117,7 @@ class AugustVaults extends core_1.AugustBase {
1033
1117
  rpcUrl: rpcUrl,
1034
1118
  chainId: resolvedChainId,
1035
1119
  solanaService: this.solanaService,
1120
+ stellarRpc: this._stellarRpc,
1036
1121
  env: this.monitoring?.env,
1037
1122
  augustKey: this.keys?.august,
1038
1123
  subgraphKey: this.keys?.graph,
@@ -1056,6 +1141,7 @@ class AugustVaults extends core_1.AugustBase {
1056
1141
  options: {
1057
1142
  rpcUrl: rpcUrl,
1058
1143
  solanaService: this.solanaService,
1144
+ stellarRpc: this._stellarRpc,
1059
1145
  env: this.monitoring?.env,
1060
1146
  augustKey: this.keys?.august,
1061
1147
  subgraphKey: this.keys?.graph,
@@ -1103,6 +1189,7 @@ class AugustVaults extends core_1.AugustBase {
1103
1189
  ? this.providers?.[chainId]
1104
1190
  : rpcUrl || this.requireActiveNetwork().rpcUrl,
1105
1191
  solanaService: this.solanaService,
1192
+ stellarRpc: this._stellarRpc,
1106
1193
  env: this.monitoring?.env,
1107
1194
  augustKey: this.keys?.august,
1108
1195
  subgraphKey: this.keys?.graph,
@@ -1376,6 +1463,7 @@ class AugustVaults extends core_1.AugustBase {
1376
1463
  options: {
1377
1464
  rpcUrl: chainId ? this.providers?.[chainId] : this.activeNetwork.rpcUrl,
1378
1465
  solanaService: this.solanaService,
1466
+ stellarRpc: this._stellarRpc,
1379
1467
  env: this.monitoring?.env,
1380
1468
  augustKey: this.keys?.august,
1381
1469
  subgraphKey: this.keys?.graph,
@@ -1428,6 +1516,7 @@ class AugustVaults extends core_1.AugustBase {
1428
1516
  options: {
1429
1517
  rpcUrl,
1430
1518
  solanaService: this.solanaService,
1519
+ stellarRpc: this._stellarRpc,
1431
1520
  env: this.monitoring?.env,
1432
1521
  augustKey: this.keys?.august,
1433
1522
  subgraphKey: this.keys?.graph,
@@ -0,0 +1,65 @@
1
+ import type { IAddress, ITokenizedVault } from '../../types';
2
+ /**
3
+ * Per-vault on-chain reads resolved ahead of the positions fan-out, so
4
+ * `getVaultPositions` / `getVaultAvailableRedemptions` can skip their own
5
+ * `balanceOf` / `lagDuration` RPCs. A missing field means the read was not
6
+ * (or could not be) prefetched — the getter falls back to its own call.
7
+ * @internal
8
+ */
9
+ export interface IPrefetchedVaultReads {
10
+ /** Raw share/receipt-token balance of the wallet (`balanceOf`). */
11
+ balance?: bigint;
12
+ /** Vault redemption lag in seconds (`lagDuration`). */
13
+ lagDuration?: number;
14
+ }
15
+ /**
16
+ * Minimal shape of a backend tokenized-vault row the prefetch needs. The
17
+ * full backend response type is looser than this; only these fields are read.
18
+ * @internal
19
+ */
20
+ interface IPrefetchableVault {
21
+ address: string;
22
+ chain: number;
23
+ chain_type?: string;
24
+ /**
25
+ * Backend vault kind. `getVaultVersionV2` reads this to classify evm-2
26
+ * (`multiAssetVault`) vaults whose address isn't in the hardcoded
27
+ * `MULTI_ASSET_VAULTS` list; without it such a vault would be misclassified
28
+ * as evm-1 and the batch would target the vault itself for `balanceOf`
29
+ * instead of its receipt token.
30
+ */
31
+ internal_type?: ITokenizedVault['internal_type'];
32
+ }
33
+ /**
34
+ * Batch the two non-cacheable per-vault reads (`balanceOf`, `lagDuration`)
35
+ * for a vault list via Multicall3, grouped by chain, ahead of the per-vault
36
+ * positions fan-out.
37
+ *
38
+ * Scope and fallback rules (task 52):
39
+ * - Only EVM vaults on chains with a configured provider AND a verified
40
+ * Multicall3 deployment are batched. Solana/Stellar vaults, unverified
41
+ * chains (e.g. Citrea 4114), and provider-less chains are skipped — they
42
+ * keep today's exact per-vault read path.
43
+ * - `balanceOf` targets the receipt token for evm-2 vaults (resolved through
44
+ * the same cached `getReceiptTokenAddress` the getters use) and the vault
45
+ * itself for evm-1. Without an EVM wallet only `lagDuration` is batched.
46
+ * - Failures never propagate: a reverting vault call is simply absent from
47
+ * the result map, and a whole-chain error drops that chain's entries. The
48
+ * worst case is always "no prefetch → per-vault fallback", never wrong data.
49
+ *
50
+ * RPC cost: per batched chain, `ceil(reads / 10)` `eth_call`s (+1 cold
51
+ * `lpTokenAddress` per evm-2 vault) instead of ~2 `eth_call`s per vault.
52
+ *
53
+ * @param vaults Backend tokenized-vault rows about to be fanned out over.
54
+ * @param wallet Optional EVM wallet for `balanceOf` reads.
55
+ * @param providers Chain-id → RPC-url map (the module's `this.providers`).
56
+ * @returns Map keyed by lowercased vault address; vaults with no batched
57
+ * reads have no entry.
58
+ * @internal
59
+ */
60
+ export declare function prefetchVaultPositionReads({ vaults, wallet, providers, }: {
61
+ vaults: IPrefetchableVault[];
62
+ wallet?: IAddress;
63
+ providers?: Partial<Record<number, string>>;
64
+ }): Promise<Map<string, IPrefetchedVaultReads>>;
65
+ export {};
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prefetchVaultPositionReads = prefetchVaultPositionReads;
4
+ const ethers_1 = require("ethers");
5
+ const abis_1 = require("../../abis");
6
+ const TokenizedVaultV2Receipt_1 = require("../../abis/TokenizedVaultV2Receipt");
7
+ const core_1 = require("../../core");
8
+ /**
9
+ * Batch the two non-cacheable per-vault reads (`balanceOf`, `lagDuration`)
10
+ * for a vault list via Multicall3, grouped by chain, ahead of the per-vault
11
+ * positions fan-out.
12
+ *
13
+ * Scope and fallback rules (task 52):
14
+ * - Only EVM vaults on chains with a configured provider AND a verified
15
+ * Multicall3 deployment are batched. Solana/Stellar vaults, unverified
16
+ * chains (e.g. Citrea 4114), and provider-less chains are skipped — they
17
+ * keep today's exact per-vault read path.
18
+ * - `balanceOf` targets the receipt token for evm-2 vaults (resolved through
19
+ * the same cached `getReceiptTokenAddress` the getters use) and the vault
20
+ * itself for evm-1. Without an EVM wallet only `lagDuration` is batched.
21
+ * - Failures never propagate: a reverting vault call is simply absent from
22
+ * the result map, and a whole-chain error drops that chain's entries. The
23
+ * worst case is always "no prefetch → per-vault fallback", never wrong data.
24
+ *
25
+ * RPC cost: per batched chain, `ceil(reads / 10)` `eth_call`s (+1 cold
26
+ * `lpTokenAddress` per evm-2 vault) instead of ~2 `eth_call`s per vault.
27
+ *
28
+ * @param vaults Backend tokenized-vault rows about to be fanned out over.
29
+ * @param wallet Optional EVM wallet for `balanceOf` reads.
30
+ * @param providers Chain-id → RPC-url map (the module's `this.providers`).
31
+ * @returns Map keyed by lowercased vault address; vaults with no batched
32
+ * reads have no entry.
33
+ * @internal
34
+ */
35
+ async function prefetchVaultPositionReads({ vaults, wallet, providers, }) {
36
+ const reads = new Map();
37
+ const hasWallet = !!(wallet && (0, ethers_1.isAddress)(wallet));
38
+ const byChain = new Map();
39
+ for (const v of vaults) {
40
+ if (!v?.address || !(0, ethers_1.isAddress)(v.address))
41
+ continue;
42
+ if (!(0, core_1.isMulticall3Supported)(v.chain))
43
+ continue;
44
+ if (!providers?.[v.chain])
45
+ continue;
46
+ const group = byChain.get(v.chain) ?? [];
47
+ group.push(v);
48
+ byChain.set(v.chain, group);
49
+ }
50
+ await Promise.all(Array.from(byChain.entries()).map(async ([chain, chainVaults]) => {
51
+ try {
52
+ // Same cache key as the getters (`createProvider(rpcUrl)` without a
53
+ // chainId) so the receipt-token/decimals caches stay shared.
54
+ const provider = (0, core_1.createProvider)(providers[chain]);
55
+ // Resolve each vault's `balanceOf` target — the receipt token for
56
+ // evm-2 vaults (one `lpTokenAddress` read each on a cold cache),
57
+ // the vault itself otherwise. Bound the fan-out to
58
+ // `MULTICALL3_BATCH_SIZE` concurrent reads (CLAUDE.md §4.1): a wallet
59
+ // with many uncached evm-2 vaults on one chain would otherwise burst
60
+ // N simultaneous reads before the aggregate3 batch even fires.
61
+ const balanceTargets = [];
62
+ for (let i = 0; i < chainVaults.length; i += core_1.MULTICALL3_BATCH_SIZE) {
63
+ const slice = chainVaults.slice(i, i + core_1.MULTICALL3_BATCH_SIZE);
64
+ const resolved = await Promise.all(slice.map(async (v) => {
65
+ if (!hasWallet)
66
+ return undefined;
67
+ if ((0, core_1.getVaultVersionV2)(v) === 'evm-2') {
68
+ // Cached + in-flight-deduped; a cold miss costs the same
69
+ // `lpTokenAddress` read the getter itself would make.
70
+ return (0, core_1.getReceiptTokenAddress)(provider, v.address);
71
+ }
72
+ return v.address;
73
+ }));
74
+ balanceTargets.push(...resolved);
75
+ }
76
+ const calls = [];
77
+ const slots = [];
78
+ chainVaults.forEach((v, i) => {
79
+ calls.push({
80
+ target: v.address,
81
+ abi: abis_1.ABI_LENDING_POOL_V2,
82
+ functionName: 'lagDuration',
83
+ });
84
+ slots.push({ address: v.address, field: 'lagDuration' });
85
+ const target = balanceTargets[i];
86
+ if (hasWallet && target && (0, ethers_1.isAddress)(target)) {
87
+ calls.push({
88
+ target: target,
89
+ abi: target === v.address
90
+ ? abis_1.ABI_LENDING_POOL_V2
91
+ : TokenizedVaultV2Receipt_1.ABI_TOKENIZED_VAULT_V2_RECEIPT,
92
+ functionName: 'balanceOf',
93
+ args: [wallet],
94
+ });
95
+ slots.push({ address: v.address, field: 'balance' });
96
+ }
97
+ });
98
+ const results = await (0, core_1.multicall3Aggregate)(provider, chain, calls);
99
+ results.forEach((r, i) => {
100
+ if (!r.success)
101
+ return;
102
+ const slot = slots[i];
103
+ const key = slot.address.toLowerCase();
104
+ const entry = reads.get(key) ?? {};
105
+ if (slot.field === 'lagDuration') {
106
+ entry.lagDuration = Number(r.value);
107
+ }
108
+ else {
109
+ entry.balance = BigInt(r.value);
110
+ }
111
+ reads.set(key, entry);
112
+ });
113
+ }
114
+ catch (e) {
115
+ core_1.Logger.log.warn('prefetchVaultPositionReads', `chain ${chain} prefetch failed — falling back to per-vault reads`, e);
116
+ }
117
+ }));
118
+ return reads;
119
+ }
120
+ //# sourceMappingURL=prefetch.js.map
@@ -1,5 +1,5 @@
1
1
  import type { Connection } from '@solana/web3.js';
2
- import type { IAddress, IEnv, IWSMonitorHeaders } from '../../types';
2
+ import type { IAddress, IEnv, IStellarNetwork, IWSMonitorHeaders } from '../../types';
3
3
  /**
4
4
  * Structural interface for the Solana adapter surface used by vault operations.
5
5
  * Avoids importing the concrete SolanaAdapter class to prevent circular dependencies.
@@ -40,6 +40,16 @@ export interface IVaultBaseOptions {
40
40
  subgraphKey?: string;
41
41
  chainId?: number;
42
42
  solanaService?: ISolanaService;
43
+ /**
44
+ * Optional Soroban RPC override (e.g. a keyed Alchemy URL) for Stellar vault
45
+ * reads, threaded from the SDK's `stellar` config. Carries the network it was
46
+ * configured for so it is only applied to matching-network vaults — a testnet
47
+ * override must not be used for a mainnet read. Unset → built-in endpoints.
48
+ */
49
+ stellarRpc?: {
50
+ rpcUrl: string;
51
+ network: IStellarNetwork;
52
+ };
43
53
  headers?: IWSMonitorHeaders;
44
54
  /**
45
55
  * Portfolio mode: when true, the per-vault EVM getter does NOT null out a