@augustdigital/sdk 8.11.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.
- package/lib/adapters/evm/index.d.ts +17 -1
- package/lib/adapters/evm/index.js +20 -0
- package/lib/core/analytics/method-taxonomy.js +1 -0
- package/lib/core/analytics/version.d.ts +1 -1
- package/lib/core/analytics/version.js +1 -1
- package/lib/core/constants/swap-router.d.ts +2 -0
- package/lib/core/constants/swap-router.js +1 -0
- package/lib/core/constants/web3.d.ts +19 -0
- package/lib/core/constants/web3.js +37 -1
- package/lib/core/helpers/multicall.d.ts +68 -0
- package/lib/core/helpers/multicall.js +103 -0
- package/lib/core/helpers/swap-router.d.ts +36 -1
- package/lib/core/helpers/swap-router.js +80 -0
- package/lib/core/helpers/vault-version.d.ts +4 -1
- package/lib/core/helpers/vaults.d.ts +1 -1
- package/lib/core/index.d.ts +1 -0
- package/lib/core/index.js +1 -0
- package/lib/modules/vaults/getters.d.ts +25 -2
- package/lib/modules/vaults/getters.js +41 -11
- package/lib/modules/vaults/main.d.ts +22 -0
- package/lib/modules/vaults/main.js +54 -0
- package/lib/modules/vaults/prefetch.d.ts +65 -0
- package/lib/modules/vaults/prefetch.js +120 -0
- package/lib/sdk.d.ts +6689 -6550
- package/lib/types/subgraph.d.ts +9 -0
- package/lib/types/vaults.d.ts +34 -0
- 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
|
|
596
|
-
|
|
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) {
|
|
@@ -1100,24 +1107,37 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
|
|
|
1100
1107
|
abi: abis_1.ABI_LENDING_POOL_V2,
|
|
1101
1108
|
address: v.address,
|
|
1102
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;
|
|
1103
1115
|
// get vault metadata and balance based on version
|
|
1104
1116
|
let bal = BigInt(0);
|
|
1105
1117
|
if (version === 'evm-2') {
|
|
1106
1118
|
const receiptAddress = await (0, core_1.getReceiptTokenAddress)(provider, vault);
|
|
1107
1119
|
decimals = await (0, core_1.getDecimals)(provider, receiptAddress, false);
|
|
1108
1120
|
if (wallet && (0, ethers_1.isAddress)(wallet)) {
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
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
|
+
}
|
|
1115
1132
|
}
|
|
1116
1133
|
}
|
|
1117
1134
|
else {
|
|
1118
1135
|
decimals = await (0, core_1.getDecimals)(provider, vault);
|
|
1119
1136
|
if (wallet && (0, ethers_1.isAddress)(wallet)) {
|
|
1120
|
-
bal =
|
|
1137
|
+
bal =
|
|
1138
|
+
typeof prefetched?.balance === 'bigint'
|
|
1139
|
+
? prefetched.balance
|
|
1140
|
+
: await vaultContract.balanceOf(wallet);
|
|
1121
1141
|
}
|
|
1122
1142
|
}
|
|
1123
1143
|
const balance = (0, core_1.toNormalizedBn)(bal, decimals);
|
|
@@ -1126,6 +1146,7 @@ async function getVaultPositions({ vault, wallet, solanaWallet, stellarWallet, o
|
|
|
1126
1146
|
vault: v.address,
|
|
1127
1147
|
wallet,
|
|
1128
1148
|
options,
|
|
1149
|
+
prefetchedReads: prefetched,
|
|
1129
1150
|
});
|
|
1130
1151
|
// get aggregate redemption amount
|
|
1131
1152
|
const aggregateAvailableRedemptions = availableRedemptions.reduce((acc, curr) => acc + BigInt(curr.amount.raw), BigInt(0));
|
|
@@ -1859,6 +1880,15 @@ async function getVaultUserLifetimePnl({ vault, wallet, options, }) {
|
|
|
1859
1880
|
if (!tokenizedVault) {
|
|
1860
1881
|
throw new Error(`Vault ${vault} not found`);
|
|
1861
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
|
+
}
|
|
1862
1892
|
// Fetch LayerZero deposits/redeems for supported vaults
|
|
1863
1893
|
const lzVaultKey = (0, deposits_1.isLayerZeroVault)(vault);
|
|
1864
1894
|
let lzDeposits = [];
|
|
@@ -50,6 +50,17 @@ export declare class AugustVaults extends AugustBase {
|
|
|
50
50
|
* Includes closed vaults that are still visible (`status: 'closed'`,
|
|
51
51
|
* `is_visible: true`) so consumers can render positions held in closed
|
|
52
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).
|
|
53
64
|
* @param options Filtering and enrichment configuration
|
|
54
65
|
* @returns Array of vault objects with optional loans/allocations/positions
|
|
55
66
|
*/
|
|
@@ -202,7 +213,18 @@ export declare class AugustVaults extends AugustBase {
|
|
|
202
213
|
lookbackBlocks?: number;
|
|
203
214
|
}): Promise<import("../../types").IVaultRedemptionHistoryItem[]>;
|
|
204
215
|
/**
|
|
216
|
+
* Fetch the wallet's positions across vaults — a single vault, one chain,
|
|
217
|
+
* or every chain with a configured provider.
|
|
205
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.
|
|
206
228
|
*/
|
|
207
229
|
getVaultPositions({ vault, wallet, chainId, showAllVaults, solanaWallet, stellarWallet, options, }: {
|
|
208
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.
|
|
@@ -120,6 +121,17 @@ class AugustVaults extends core_1.AugustBase {
|
|
|
120
121
|
* Includes closed vaults that are still visible (`status: 'closed'`,
|
|
121
122
|
* `is_visible: true`) so consumers can render positions held in closed
|
|
122
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).
|
|
123
135
|
* @param options Filtering and enrichment configuration
|
|
124
136
|
* @returns Array of vault objects with optional loans/allocations/positions
|
|
125
137
|
*/
|
|
@@ -248,6 +260,14 @@ class AugustVaults extends core_1.AugustBase {
|
|
|
248
260
|
if ((options.wallet && (0, ethers_1.isAddress)(options.wallet)) ||
|
|
249
261
|
(options.solanaWallet &&
|
|
250
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
|
+
});
|
|
251
271
|
// fetch all positions for user wallet
|
|
252
272
|
const vaultResponses = await (0, core_1.promiseSettle)(vaultsPerAvailableProviders.map((v) => (0, getters_1.getVaultPositions)({
|
|
253
273
|
vault: v.address,
|
|
@@ -262,6 +282,7 @@ class AugustVaults extends core_1.AugustBase {
|
|
|
262
282
|
augustKey: this.keys?.august,
|
|
263
283
|
subgraphKey: this.keys?.graph,
|
|
264
284
|
},
|
|
285
|
+
prefetchedReads: prefetchedReads.get(String(v.address).toLowerCase()),
|
|
265
286
|
})), 5, // 5 retries
|
|
266
287
|
2000);
|
|
267
288
|
// return vault object with position property
|
|
@@ -609,7 +630,18 @@ class AugustVaults extends core_1.AugustBase {
|
|
|
609
630
|
});
|
|
610
631
|
}
|
|
611
632
|
/**
|
|
633
|
+
* Fetch the wallet's positions across vaults — a single vault, one chain,
|
|
634
|
+
* or every chain with a configured provider.
|
|
612
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.
|
|
613
645
|
*/
|
|
614
646
|
async getVaultPositions({ vault, wallet, chainId, showAllVaults, solanaWallet, stellarWallet, options, }) {
|
|
615
647
|
// if (!this.authorized) throw new Error('Not authorized.');
|
|
@@ -646,6 +678,13 @@ class AugustVaults extends core_1.AugustBase {
|
|
|
646
678
|
const allVaults = await (0, core_1.fetchTokenizedVaults)(undefined, this.headers, false, false);
|
|
647
679
|
if (chainId) {
|
|
648
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
|
+
});
|
|
649
688
|
const vaultResponses = await Promise.all(vaultsPerChain.map((v) => (0, getters_1.getVaultPositions)({
|
|
650
689
|
vault: v.address,
|
|
651
690
|
solanaWallet,
|
|
@@ -660,6 +699,7 @@ class AugustVaults extends core_1.AugustBase {
|
|
|
660
699
|
subgraphKey: this.keys?.graph,
|
|
661
700
|
headers: this.headers,
|
|
662
701
|
},
|
|
702
|
+
prefetchedReads: prefetchedReads.get(String(v.address).toLowerCase()),
|
|
663
703
|
})));
|
|
664
704
|
const flattened = vaultResponses.flat();
|
|
665
705
|
let final;
|
|
@@ -682,6 +722,13 @@ class AugustVaults extends core_1.AugustBase {
|
|
|
682
722
|
// outer catch, and silently return PENDING (then get filtered out).
|
|
683
723
|
(v?.chain_type === 'solana' && !!this.solanaService) ||
|
|
684
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
|
+
});
|
|
685
732
|
const vaultResponses = await Promise.all(vaultsPerAvailableProviders.map((v) => (0, getters_1.getVaultPositions)({
|
|
686
733
|
vault: v.address,
|
|
687
734
|
solanaWallet,
|
|
@@ -696,6 +743,7 @@ class AugustVaults extends core_1.AugustBase {
|
|
|
696
743
|
subgraphKey: this.keys?.graph,
|
|
697
744
|
headers: this.headers,
|
|
698
745
|
},
|
|
746
|
+
prefetchedReads: prefetchedReads.get(String(v.address).toLowerCase()),
|
|
699
747
|
})));
|
|
700
748
|
const flattened = vaultResponses.flat();
|
|
701
749
|
let final;
|
|
@@ -734,6 +782,12 @@ class AugustVaults extends core_1.AugustBase {
|
|
|
734
782
|
// deposited token address; propagate it so consumers render the right
|
|
735
783
|
// asset symbol instead of guessing the vault's first deposit asset.
|
|
736
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,
|
|
737
791
|
}));
|
|
738
792
|
}
|
|
739
793
|
// Helper function to fetch history for a vault
|
|
@@ -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
|