@defisaver/positions-sdk 2.1.165 → 2.1.166

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.
@@ -32,6 +32,45 @@ export declare const EMPTY_AAVE_DATA: {
32
32
  };
33
33
  export declare const _getAaveV3AccountBalances: (provider: Client, network: NetworkNumber, block: Blockish, addressMapping: boolean, address: EthAddress) => Promise<PositionBalances>;
34
34
  export declare const getAaveV3AccountBalances: (provider: EthereumProvider, network: NetworkNumber, block: Blockish, addressMapping: boolean, address: EthAddress) => Promise<PositionBalances>;
35
+ export interface AaveV3ReserveTokenAddresses {
36
+ [symbol: string]: {
37
+ symbol: string;
38
+ underlyingAddress: EthAddress;
39
+ aTokenAddress: EthAddress;
40
+ stableDebtTokenAddress: EthAddress;
41
+ variableDebtTokenAddress: EthAddress;
42
+ };
43
+ }
44
+ export interface AaveV3HistoricalBalance {
45
+ block: number;
46
+ suppliedUsd: string;
47
+ borrowedUsd: string;
48
+ netUsd: string;
49
+ }
50
+ /**
51
+ * Fetches the aToken / stable-debt / variable-debt token addresses for every asset in the market.
52
+ * These are effectively immutable per reserve, so fetch once and reuse across all history points.
53
+ */
54
+ export declare const _getAaveV3ReserveTokenAddresses: (provider: Client, network: NetworkNumber, market: AaveMarketInfo) => Promise<AaveV3ReserveTokenAddresses>;
55
+ export declare const getAaveV3ReserveTokenAddresses: (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo) => Promise<AaveV3ReserveTokenAddresses>;
56
+ /**
57
+ * Resolves the price oracle the market's addresses provider points at (at `block`, or latest).
58
+ * Aave can rotate the oracle over time, so a caller hoisting one address across many historical
59
+ * points must resolve it at both ends of its block range and only reuse it when the two agree —
60
+ * otherwise leave `oracleAddress` unset on _getAaveV3HistoricalBalance and it resolves per block.
61
+ */
62
+ export declare const _getAaveV3OracleAddress: (provider: Client, network: NetworkNumber, market: AaveMarketInfo, block?: Blockish) => Promise<EthAddress>;
63
+ export declare const getAaveV3OracleAddress: (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo, block?: Blockish) => Promise<EthAddress>;
64
+ /**
65
+ * Computes a user's Aave v3 net USD balance (supplied collateral - borrowed debt) at a historical block,
66
+ * without touching the AaveV3View contract. Pass `reserveTokens` (from getAaveV3ReserveTokenAddresses)
67
+ * to avoid refetching token addresses for every point, and `oracleAddress` (from getAaveV3OracleAddress,
68
+ * verified identical at both ends of the sampled range) to collapse the point into a single multicall —
69
+ * with it the balances and all prices ride one request; without it the oracle is resolved at the block
70
+ * inside the balance multicall and the active assets are priced in a second one.
71
+ */
72
+ export declare const _getAaveV3HistoricalBalance: (provider: Client, network: NetworkNumber, market: AaveMarketInfo, address: EthAddress, block: number, reserveTokens?: AaveV3ReserveTokenAddresses, oracleAddress?: EthAddress) => Promise<AaveV3HistoricalBalance>;
73
+ export declare const getAaveV3HistoricalBalance: (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo, address: EthAddress, block: number, reserveTokens?: AaveV3ReserveTokenAddresses, oracleAddress?: EthAddress) => Promise<AaveV3HistoricalBalance>;
35
74
  export declare const _getAaveV3AccountData: (provider: Client, network: NetworkNumber, address: EthAddress, extractedState: any, blockNumber?: "latest" | number) => Promise<AaveV3PositionData>;
36
75
  export declare const getAaveV3AccountData: (provider: EthereumProvider, network: NetworkNumber, address: EthAddress, extractedState: any, blockNumber?: "latest" | number) => Promise<AaveV3PositionData>;
37
76
  export declare const getAaveV3FullPositionData: (provider: EthereumProvider, network: NetworkNumber, address: EthAddress, market: AaveMarketInfo) => Promise<AaveV3PositionData>;
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.getSghoData = exports.getMerkleCampaigns = exports.getMeritCampaigns = exports.getStakeAaveData = exports.fetchYearlyMeritApyForStakingGho = exports.REWARDABLE_ASSETS = exports.getAaveV3FullPositionData = exports.getAaveV3AccountData = exports._getAaveV3AccountData = exports.getAaveV3AccountBalances = exports._getAaveV3AccountBalances = exports.EMPTY_AAVE_DATA = exports.aaveV3EmodeCategoriesMapping = void 0;
15
+ exports.getSghoData = exports.getMerkleCampaigns = exports.getMeritCampaigns = exports.getStakeAaveData = exports.fetchYearlyMeritApyForStakingGho = exports.REWARDABLE_ASSETS = exports.getAaveV3FullPositionData = exports.getAaveV3AccountData = exports._getAaveV3AccountData = exports.getAaveV3HistoricalBalance = exports._getAaveV3HistoricalBalance = exports.getAaveV3OracleAddress = exports._getAaveV3OracleAddress = exports.getAaveV3ReserveTokenAddresses = exports._getAaveV3ReserveTokenAddresses = exports.getAaveV3AccountBalances = exports._getAaveV3AccountBalances = exports.EMPTY_AAVE_DATA = exports.aaveV3EmodeCategoriesMapping = void 0;
16
16
  exports._getAaveV3MarketData = _getAaveV3MarketData;
17
17
  exports.getAaveV3MarketData = getAaveV3MarketData;
18
18
  const tokens_1 = require("@defisaver/tokens");
@@ -381,6 +381,235 @@ const _getAaveV3AccountBalances = (provider, network, block, addressMapping, add
381
381
  exports._getAaveV3AccountBalances = _getAaveV3AccountBalances;
382
382
  const getAaveV3AccountBalances = (provider, network, block, addressMapping, address) => __awaiter(void 0, void 0, void 0, function* () { return (0, exports._getAaveV3AccountBalances)((0, viem_1.getViemProvider)(provider, network), network, block, addressMapping, address); });
383
383
  exports.getAaveV3AccountBalances = getAaveV3AccountBalances;
384
+ /**
385
+ * Historical net-balance helpers that bypass the AaveV3View contract.
386
+ *
387
+ * The View contract (and therefore `getAaveV3AccountData` / `getAaveV3AccountBalances`) can only be
388
+ * queried from its deployment block onwards, so it cannot read balances for positions older than that.
389
+ * The aTokens/debt tokens, the ProtocolDataProvider and the Aave price oracle all exist from Aave v3
390
+ * launch, so reading `balanceOf` on those tokens + the oracle price directly reaches much further back
391
+ * and costs ~1 multicall per point. Used to build a position balance-history chart.
392
+ */
393
+ // matches morphoVaults' batchSize; stays under the 500k portfolio discovery allows on simulation fork RPCs
394
+ const HISTORICAL_MULTICALL_BATCH_SIZE = 250000;
395
+ // Minimal Aave price oracle ABI (getAssetPrice returns the asset price in the market base currency).
396
+ const AAVE_ORACLE_ABI = [
397
+ {
398
+ inputs: [{ internalType: 'address', name: 'asset', type: 'address' }],
399
+ name: 'getAssetPrice',
400
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
401
+ stateMutability: 'view',
402
+ type: 'function',
403
+ },
404
+ ];
405
+ /**
406
+ * Fetches the aToken / stable-debt / variable-debt token addresses for every asset in the market.
407
+ * These are effectively immutable per reserve, so fetch once and reuse across all history points.
408
+ */
409
+ const _getAaveV3ReserveTokenAddresses = (provider, network, market) => __awaiter(void 0, void 0, void 0, function* () {
410
+ const symbols = market.assets;
411
+ const underlyingAddresses = symbols.map((a) => (0, tokens_1.getAssetInfo)((0, utils_1.getWrappedNativeAssetFromUnwrapped)(a), network).address);
412
+ // @ts-ignore market.protocolData is a valid config key at runtime
413
+ const dataProviderAbi = (0, contracts_1.getConfigContractAbi)(market.protocolData, network);
414
+ const contracts = underlyingAddresses.map((underlying) => ({
415
+ address: market.protocolDataAddress,
416
+ abi: dataProviderAbi,
417
+ functionName: 'getReserveTokensAddresses',
418
+ args: [underlying],
419
+ }));
420
+ // @ts-ignore
421
+ const results = yield provider.multicall({ contracts, allowFailure: true, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE });
422
+ const mapping = {};
423
+ results.forEach((res, i) => {
424
+ if (res.status !== 'success' || !res.result)
425
+ return;
426
+ // outputs order: [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress]
427
+ const [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress] = res.result;
428
+ mapping[symbols[i]] = {
429
+ symbol: symbols[i],
430
+ underlyingAddress: underlyingAddresses[i],
431
+ aTokenAddress,
432
+ stableDebtTokenAddress,
433
+ variableDebtTokenAddress,
434
+ };
435
+ });
436
+ return mapping;
437
+ });
438
+ exports._getAaveV3ReserveTokenAddresses = _getAaveV3ReserveTokenAddresses;
439
+ const getAaveV3ReserveTokenAddresses = (provider, network, market) => __awaiter(void 0, void 0, void 0, function* () { return (0, exports._getAaveV3ReserveTokenAddresses)((0, viem_1.getViemProvider)(provider, network, { batch: { multicall: true } }), network, market); });
440
+ exports.getAaveV3ReserveTokenAddresses = getAaveV3ReserveTokenAddresses;
441
+ /**
442
+ * Resolves the price oracle the market's addresses provider points at (at `block`, or latest).
443
+ * Aave can rotate the oracle over time, so a caller hoisting one address across many historical
444
+ * points must resolve it at both ends of its block range and only reuse it when the two agree —
445
+ * otherwise leave `oracleAddress` unset on _getAaveV3HistoricalBalance and it resolves per block.
446
+ */
447
+ const _getAaveV3OracleAddress = (provider_1, network_1, market_1, ...args_1) => __awaiter(void 0, [provider_1, network_1, market_1, ...args_1], void 0, function* (provider, network, market, block = 'latest') {
448
+ // @ts-ignore market.provider is a valid config key at runtime
449
+ const providerAbi = (0, contracts_1.getConfigContractAbi)(market.provider, network);
450
+ // @ts-ignore readContract exists on the public client returned by getViemProvider
451
+ const oracleAddress = yield provider.readContract(Object.assign({ address: market.providerAddress, abi: providerAbi, functionName: 'getPriceOracle' }, (0, viem_1.setViemBlockNumber)(block)));
452
+ if (!oracleAddress)
453
+ throw new Error(`AaveV3 oracle unavailable at block ${block}`);
454
+ return oracleAddress;
455
+ });
456
+ exports._getAaveV3OracleAddress = _getAaveV3OracleAddress;
457
+ const getAaveV3OracleAddress = (provider_1, network_1, market_1, ...args_1) => __awaiter(void 0, [provider_1, network_1, market_1, ...args_1], void 0, function* (provider, network, market, block = 'latest') { return (0, exports._getAaveV3OracleAddress)((0, viem_1.getViemProvider)(provider, network, { batch: { multicall: true } }), network, market, block); });
458
+ exports.getAaveV3OracleAddress = getAaveV3OracleAddress;
459
+ // supply = aToken.balanceOf, debt = variableDebtToken.balanceOf + stableDebtToken.balanceOf — three reads per reserve
460
+ const getHistoricalBalanceContracts = (entries, address) => {
461
+ const erc20Abi = (0, contracts_1.getConfigContractAbi)('Erc20');
462
+ return entries.flatMap((e) => ([
463
+ {
464
+ address: e.aTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
465
+ },
466
+ {
467
+ address: e.variableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
468
+ },
469
+ {
470
+ address: e.stableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
471
+ },
472
+ ]));
473
+ };
474
+ const getAssetPriceContract = (oracle, underlyingAddress) => ({
475
+ address: oracle,
476
+ abi: AAVE_ORACLE_ABI,
477
+ functionName: 'getAssetPrice',
478
+ args: [underlyingAddress],
479
+ });
480
+ /**
481
+ * Pairs each reserve with its three balance reads and keeps the assets the wallet actually held.
482
+ * Throws when every read failed — a totally failed fetch must not masquerade as a real $0 balance,
483
+ * so the caller can distinguish "fetch failed" (gap) from "position was empty" (genuine 0).
484
+ */
485
+ const toActiveHistoricalAssets = (entries, balanceResults, block) => {
486
+ const anyBalanceRead = balanceResults.some((r) => (r === null || r === void 0 ? void 0 : r.status) === 'success');
487
+ if (!anyBalanceRead)
488
+ throw new Error(`AaveV3 historical balance: all balance reads failed at block ${block}`);
489
+ return entries.map((e, i) => {
490
+ const supplyRes = balanceResults[i * 3];
491
+ const varDebtRes = balanceResults[(i * 3) + 1];
492
+ const stableDebtRes = balanceResults[(i * 3) + 2];
493
+ const supplied = (supplyRes === null || supplyRes === void 0 ? void 0 : supplyRes.status) === 'success' ? supplyRes.result.toString() : '0';
494
+ const varDebt = (varDebtRes === null || varDebtRes === void 0 ? void 0 : varDebtRes.status) === 'success' ? varDebtRes.result.toString() : '0';
495
+ const stableDebt = (stableDebtRes === null || stableDebtRes === void 0 ? void 0 : stableDebtRes.status) === 'success' ? stableDebtRes.result.toString() : '0';
496
+ const debt = new decimal_js_1.default(varDebt).add(stableDebt).toString();
497
+ return Object.assign(Object.assign({}, e), { supplied, debt });
498
+ }).filter((a) => a.supplied !== '0' || a.debt !== '0');
499
+ };
500
+ /**
501
+ * Sums the active assets into the point's USD totals. Throws when every price read failed — the
502
+ * oracle couldn't serve this block (pruned state, or a hoisted oracle that wasn't deployed yet back
503
+ * then), and totalling it up anyway would return a fake near-$0 balance. A single asset's price
504
+ * failure still just skips that asset, matching the live data path.
505
+ */
506
+ const sumHistoricalUsd = (activeAssets, priceResBySymbol, block) => {
507
+ const anyPriceRead = activeAssets.some((a) => { var _a; return ((_a = priceResBySymbol[a.symbol]) === null || _a === void 0 ? void 0 : _a.status) === 'success'; });
508
+ if (!anyPriceRead)
509
+ throw new Error(`AaveV3 historical balance: all price reads failed at block ${block}`);
510
+ let suppliedUsd = new decimal_js_1.default(0);
511
+ let borrowedUsd = new decimal_js_1.default(0);
512
+ activeAssets.forEach((a) => {
513
+ const priceRes = priceResBySymbol[a.symbol];
514
+ if ((priceRes === null || priceRes === void 0 ? void 0 : priceRes.status) !== 'success')
515
+ return;
516
+ const priceUsd = new decimal_js_1.default(priceRes.result.toString()).div(1e8); // Aave v3 base currency is USD with 8 decimals
517
+ if (a.supplied !== '0')
518
+ suppliedUsd = suppliedUsd.add(new decimal_js_1.default((0, tokens_1.assetAmountInEth)(a.supplied, a.symbol)).mul(priceUsd));
519
+ if (a.debt !== '0')
520
+ borrowedUsd = borrowedUsd.add(new decimal_js_1.default((0, tokens_1.assetAmountInEth)(a.debt, a.symbol)).mul(priceUsd));
521
+ });
522
+ return {
523
+ block,
524
+ suppliedUsd: suppliedUsd.toString(),
525
+ borrowedUsd: borrowedUsd.toString(),
526
+ netUsd: suppliedUsd.minus(borrowedUsd).toString(),
527
+ };
528
+ };
529
+ /**
530
+ * Fast path, ONE multicall: balances plus a price read for every reserve through the caller-hoisted
531
+ * oracle. Prices are read for all reserves because which assets are active isn't known until the
532
+ * balances come back — extra reads inside an already-paid multicall are nearly free.
533
+ */
534
+ const fetchHistoricalPointWithOracle = (provider, entries, address, block, oracleAddress) => __awaiter(void 0, void 0, void 0, function* () {
535
+ const balanceContracts = getHistoricalBalanceContracts(entries, address);
536
+ const priceContracts = entries.map((e) => getAssetPriceContract(oracleAddress, e.underlyingAddress));
537
+ // @ts-ignore
538
+ const results = yield provider.multicall({
539
+ contracts: [...balanceContracts, ...priceContracts], allowFailure: true, blockNumber: BigInt(block), batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
540
+ });
541
+ const activeAssets = toActiveHistoricalAssets(entries, results.slice(0, balanceContracts.length), block);
542
+ const priceResBySymbol = {};
543
+ results.slice(balanceContracts.length).forEach((res, i) => {
544
+ priceResBySymbol[entries[i].symbol] = res;
545
+ });
546
+ return { activeAssets, priceResBySymbol };
547
+ });
548
+ /**
549
+ * Fallback path, TWO multicalls: balances plus getPriceOracle (resolving the oracle that was active
550
+ * AT the block — Aave can rotate it over time), then prices for just the active assets. The oracle
551
+ * read has no fallback: if it fails (bad archive node, rate limit, ...) the failure must surface
552
+ * rather than silently returning $0 — the caller renders a gap instead of a misleading zero.
553
+ */
554
+ const fetchHistoricalPointResolvingOracle = (provider, network, market, entries, address, block) => __awaiter(void 0, void 0, void 0, function* () {
555
+ const balanceContracts = getHistoricalBalanceContracts(entries, address);
556
+ // @ts-ignore market.provider is a valid config key at runtime
557
+ const providerAbi = (0, contracts_1.getConfigContractAbi)(market.provider, network);
558
+ const oracleContract = {
559
+ address: market.providerAddress,
560
+ abi: providerAbi,
561
+ functionName: 'getPriceOracle',
562
+ };
563
+ const blockNumber = BigInt(block);
564
+ // @ts-ignore
565
+ const results = yield provider.multicall({
566
+ contracts: [...balanceContracts, oracleContract], allowFailure: true, blockNumber, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
567
+ });
568
+ const oracleRes = results[balanceContracts.length];
569
+ if ((oracleRes === null || oracleRes === void 0 ? void 0 : oracleRes.status) !== 'success' || !oracleRes.result)
570
+ throw new Error(`AaveV3 historical balance: oracle unavailable at block ${block}`);
571
+ const activeAssets = toActiveHistoricalAssets(entries, results.slice(0, balanceContracts.length), block);
572
+ if (!activeAssets.length)
573
+ return { activeAssets, priceResBySymbol: {} };
574
+ const priceContracts = activeAssets.map((a) => getAssetPriceContract(oracleRes.result, a.underlyingAddress));
575
+ // @ts-ignore
576
+ const priceResults = yield provider.multicall({
577
+ contracts: priceContracts, allowFailure: true, blockNumber, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
578
+ });
579
+ const priceResBySymbol = {};
580
+ priceResults.forEach((res, i) => {
581
+ priceResBySymbol[activeAssets[i].symbol] = res;
582
+ });
583
+ return { activeAssets, priceResBySymbol };
584
+ });
585
+ /**
586
+ * Computes a user's Aave v3 net USD balance (supplied collateral - borrowed debt) at a historical block,
587
+ * without touching the AaveV3View contract. Pass `reserveTokens` (from getAaveV3ReserveTokenAddresses)
588
+ * to avoid refetching token addresses for every point, and `oracleAddress` (from getAaveV3OracleAddress,
589
+ * verified identical at both ends of the sampled range) to collapse the point into a single multicall —
590
+ * with it the balances and all prices ride one request; without it the oracle is resolved at the block
591
+ * inside the balance multicall and the active assets are priced in a second one.
592
+ */
593
+ const _getAaveV3HistoricalBalance = (provider, network, market, address, block, reserveTokens, oracleAddress) => __awaiter(void 0, void 0, void 0, function* () {
594
+ const empty = {
595
+ block, suppliedUsd: '0', borrowedUsd: '0', netUsd: '0',
596
+ };
597
+ if (!address)
598
+ return empty;
599
+ const tokens = reserveTokens || (yield (0, exports._getAaveV3ReserveTokenAddresses)(provider, network, market));
600
+ const entries = Object.values(tokens);
601
+ if (!entries.length)
602
+ return empty;
603
+ const { activeAssets, priceResBySymbol } = oracleAddress
604
+ ? yield fetchHistoricalPointWithOracle(provider, entries, address, block, oracleAddress)
605
+ : yield fetchHistoricalPointResolvingOracle(provider, network, market, entries, address, block);
606
+ if (!activeAssets.length)
607
+ return empty;
608
+ return sumHistoricalUsd(activeAssets, priceResBySymbol, block);
609
+ });
610
+ exports._getAaveV3HistoricalBalance = _getAaveV3HistoricalBalance;
611
+ const getAaveV3HistoricalBalance = (provider, network, market, address, block, reserveTokens, oracleAddress) => __awaiter(void 0, void 0, void 0, function* () { return (0, exports._getAaveV3HistoricalBalance)((0, viem_1.getViemProvider)(provider, network, { batch: { multicall: true } }), network, market, address, block, reserveTokens, oracleAddress); });
612
+ exports.getAaveV3HistoricalBalance = getAaveV3HistoricalBalance;
384
613
  const _getAaveV3AccountData = (provider_1, network_1, address_1, extractedState_1, ...args_1) => __awaiter(void 0, [provider_1, network_1, address_1, extractedState_1, ...args_1], void 0, function* (provider, network, address, extractedState, blockNumber = 'latest') {
385
614
  const { selectedMarket: market, assetsData, eModeCategoriesData, } = extractedState;
386
615
  let payload = Object.assign(Object.assign({}, exports.EMPTY_AAVE_DATA), { lastUpdated: Date.now() });
@@ -32,6 +32,45 @@ export declare const EMPTY_AAVE_DATA: {
32
32
  };
33
33
  export declare const _getAaveV3AccountBalances: (provider: Client, network: NetworkNumber, block: Blockish, addressMapping: boolean, address: EthAddress) => Promise<PositionBalances>;
34
34
  export declare const getAaveV3AccountBalances: (provider: EthereumProvider, network: NetworkNumber, block: Blockish, addressMapping: boolean, address: EthAddress) => Promise<PositionBalances>;
35
+ export interface AaveV3ReserveTokenAddresses {
36
+ [symbol: string]: {
37
+ symbol: string;
38
+ underlyingAddress: EthAddress;
39
+ aTokenAddress: EthAddress;
40
+ stableDebtTokenAddress: EthAddress;
41
+ variableDebtTokenAddress: EthAddress;
42
+ };
43
+ }
44
+ export interface AaveV3HistoricalBalance {
45
+ block: number;
46
+ suppliedUsd: string;
47
+ borrowedUsd: string;
48
+ netUsd: string;
49
+ }
50
+ /**
51
+ * Fetches the aToken / stable-debt / variable-debt token addresses for every asset in the market.
52
+ * These are effectively immutable per reserve, so fetch once and reuse across all history points.
53
+ */
54
+ export declare const _getAaveV3ReserveTokenAddresses: (provider: Client, network: NetworkNumber, market: AaveMarketInfo) => Promise<AaveV3ReserveTokenAddresses>;
55
+ export declare const getAaveV3ReserveTokenAddresses: (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo) => Promise<AaveV3ReserveTokenAddresses>;
56
+ /**
57
+ * Resolves the price oracle the market's addresses provider points at (at `block`, or latest).
58
+ * Aave can rotate the oracle over time, so a caller hoisting one address across many historical
59
+ * points must resolve it at both ends of its block range and only reuse it when the two agree —
60
+ * otherwise leave `oracleAddress` unset on _getAaveV3HistoricalBalance and it resolves per block.
61
+ */
62
+ export declare const _getAaveV3OracleAddress: (provider: Client, network: NetworkNumber, market: AaveMarketInfo, block?: Blockish) => Promise<EthAddress>;
63
+ export declare const getAaveV3OracleAddress: (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo, block?: Blockish) => Promise<EthAddress>;
64
+ /**
65
+ * Computes a user's Aave v3 net USD balance (supplied collateral - borrowed debt) at a historical block,
66
+ * without touching the AaveV3View contract. Pass `reserveTokens` (from getAaveV3ReserveTokenAddresses)
67
+ * to avoid refetching token addresses for every point, and `oracleAddress` (from getAaveV3OracleAddress,
68
+ * verified identical at both ends of the sampled range) to collapse the point into a single multicall —
69
+ * with it the balances and all prices ride one request; without it the oracle is resolved at the block
70
+ * inside the balance multicall and the active assets are priced in a second one.
71
+ */
72
+ export declare const _getAaveV3HistoricalBalance: (provider: Client, network: NetworkNumber, market: AaveMarketInfo, address: EthAddress, block: number, reserveTokens?: AaveV3ReserveTokenAddresses, oracleAddress?: EthAddress) => Promise<AaveV3HistoricalBalance>;
73
+ export declare const getAaveV3HistoricalBalance: (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo, address: EthAddress, block: number, reserveTokens?: AaveV3ReserveTokenAddresses, oracleAddress?: EthAddress) => Promise<AaveV3HistoricalBalance>;
35
74
  export declare const _getAaveV3AccountData: (provider: Client, network: NetworkNumber, address: EthAddress, extractedState: any, blockNumber?: "latest" | number) => Promise<AaveV3PositionData>;
36
75
  export declare const getAaveV3AccountData: (provider: EthereumProvider, network: NetworkNumber, address: EthAddress, extractedState: any, blockNumber?: "latest" | number) => Promise<AaveV3PositionData>;
37
76
  export declare const getAaveV3FullPositionData: (provider: EthereumProvider, network: NetworkNumber, address: EthAddress, market: AaveMarketInfo) => Promise<AaveV3PositionData>;
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { assetAmountInEth, assetAmountInWei, getAssetInfo } from '@defisaver/tokens';
11
11
  import Dec from 'decimal.js';
12
- import { AaveIncentiveDataProviderV3ContractViem, AaveIncentivesControllerViem, AaveV3ViewContractViem, createViemContractFromConfigFunc, StkAAVEViem, } from '../contracts';
12
+ import { AaveIncentiveDataProviderV3ContractViem, AaveIncentivesControllerViem, AaveV3ViewContractViem, createViemContractFromConfigFunc, getConfigContractAbi, StkAAVEViem, } from '../contracts';
13
13
  import { aaveAnyGetAggregatedPositionData, aaveV3IsInIsolationMode, aaveV3IsInSiloedMode } from '../helpers/aaveHelpers';
14
14
  import { AAVE_V3 } from '../markets/aave';
15
15
  import { aprToApy, calculateBorrowingAssetLimit } from '../moneymarket';
@@ -368,6 +368,229 @@ export const _getAaveV3AccountBalances = (provider, network, block, addressMappi
368
368
  return balances;
369
369
  });
370
370
  export const getAaveV3AccountBalances = (provider, network, block, addressMapping, address) => __awaiter(void 0, void 0, void 0, function* () { return _getAaveV3AccountBalances(getViemProvider(provider, network), network, block, addressMapping, address); });
371
+ /**
372
+ * Historical net-balance helpers that bypass the AaveV3View contract.
373
+ *
374
+ * The View contract (and therefore `getAaveV3AccountData` / `getAaveV3AccountBalances`) can only be
375
+ * queried from its deployment block onwards, so it cannot read balances for positions older than that.
376
+ * The aTokens/debt tokens, the ProtocolDataProvider and the Aave price oracle all exist from Aave v3
377
+ * launch, so reading `balanceOf` on those tokens + the oracle price directly reaches much further back
378
+ * and costs ~1 multicall per point. Used to build a position balance-history chart.
379
+ */
380
+ // matches morphoVaults' batchSize; stays under the 500k portfolio discovery allows on simulation fork RPCs
381
+ const HISTORICAL_MULTICALL_BATCH_SIZE = 250000;
382
+ // Minimal Aave price oracle ABI (getAssetPrice returns the asset price in the market base currency).
383
+ const AAVE_ORACLE_ABI = [
384
+ {
385
+ inputs: [{ internalType: 'address', name: 'asset', type: 'address' }],
386
+ name: 'getAssetPrice',
387
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
388
+ stateMutability: 'view',
389
+ type: 'function',
390
+ },
391
+ ];
392
+ /**
393
+ * Fetches the aToken / stable-debt / variable-debt token addresses for every asset in the market.
394
+ * These are effectively immutable per reserve, so fetch once and reuse across all history points.
395
+ */
396
+ export const _getAaveV3ReserveTokenAddresses = (provider, network, market) => __awaiter(void 0, void 0, void 0, function* () {
397
+ const symbols = market.assets;
398
+ const underlyingAddresses = symbols.map((a) => getAssetInfo(getWrappedNativeAssetFromUnwrapped(a), network).address);
399
+ // @ts-ignore market.protocolData is a valid config key at runtime
400
+ const dataProviderAbi = getConfigContractAbi(market.protocolData, network);
401
+ const contracts = underlyingAddresses.map((underlying) => ({
402
+ address: market.protocolDataAddress,
403
+ abi: dataProviderAbi,
404
+ functionName: 'getReserveTokensAddresses',
405
+ args: [underlying],
406
+ }));
407
+ // @ts-ignore
408
+ const results = yield provider.multicall({ contracts, allowFailure: true, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE });
409
+ const mapping = {};
410
+ results.forEach((res, i) => {
411
+ if (res.status !== 'success' || !res.result)
412
+ return;
413
+ // outputs order: [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress]
414
+ const [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress] = res.result;
415
+ mapping[symbols[i]] = {
416
+ symbol: symbols[i],
417
+ underlyingAddress: underlyingAddresses[i],
418
+ aTokenAddress,
419
+ stableDebtTokenAddress,
420
+ variableDebtTokenAddress,
421
+ };
422
+ });
423
+ return mapping;
424
+ });
425
+ export const getAaveV3ReserveTokenAddresses = (provider, network, market) => __awaiter(void 0, void 0, void 0, function* () { return _getAaveV3ReserveTokenAddresses(getViemProvider(provider, network, { batch: { multicall: true } }), network, market); });
426
+ /**
427
+ * Resolves the price oracle the market's addresses provider points at (at `block`, or latest).
428
+ * Aave can rotate the oracle over time, so a caller hoisting one address across many historical
429
+ * points must resolve it at both ends of its block range and only reuse it when the two agree —
430
+ * otherwise leave `oracleAddress` unset on _getAaveV3HistoricalBalance and it resolves per block.
431
+ */
432
+ export const _getAaveV3OracleAddress = (provider_1, network_1, market_1, ...args_1) => __awaiter(void 0, [provider_1, network_1, market_1, ...args_1], void 0, function* (provider, network, market, block = 'latest') {
433
+ // @ts-ignore market.provider is a valid config key at runtime
434
+ const providerAbi = getConfigContractAbi(market.provider, network);
435
+ // @ts-ignore readContract exists on the public client returned by getViemProvider
436
+ const oracleAddress = yield provider.readContract(Object.assign({ address: market.providerAddress, abi: providerAbi, functionName: 'getPriceOracle' }, setViemBlockNumber(block)));
437
+ if (!oracleAddress)
438
+ throw new Error(`AaveV3 oracle unavailable at block ${block}`);
439
+ return oracleAddress;
440
+ });
441
+ export const getAaveV3OracleAddress = (provider_1, network_1, market_1, ...args_1) => __awaiter(void 0, [provider_1, network_1, market_1, ...args_1], void 0, function* (provider, network, market, block = 'latest') { return _getAaveV3OracleAddress(getViemProvider(provider, network, { batch: { multicall: true } }), network, market, block); });
442
+ // supply = aToken.balanceOf, debt = variableDebtToken.balanceOf + stableDebtToken.balanceOf — three reads per reserve
443
+ const getHistoricalBalanceContracts = (entries, address) => {
444
+ const erc20Abi = getConfigContractAbi('Erc20');
445
+ return entries.flatMap((e) => ([
446
+ {
447
+ address: e.aTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
448
+ },
449
+ {
450
+ address: e.variableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
451
+ },
452
+ {
453
+ address: e.stableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
454
+ },
455
+ ]));
456
+ };
457
+ const getAssetPriceContract = (oracle, underlyingAddress) => ({
458
+ address: oracle,
459
+ abi: AAVE_ORACLE_ABI,
460
+ functionName: 'getAssetPrice',
461
+ args: [underlyingAddress],
462
+ });
463
+ /**
464
+ * Pairs each reserve with its three balance reads and keeps the assets the wallet actually held.
465
+ * Throws when every read failed — a totally failed fetch must not masquerade as a real $0 balance,
466
+ * so the caller can distinguish "fetch failed" (gap) from "position was empty" (genuine 0).
467
+ */
468
+ const toActiveHistoricalAssets = (entries, balanceResults, block) => {
469
+ const anyBalanceRead = balanceResults.some((r) => (r === null || r === void 0 ? void 0 : r.status) === 'success');
470
+ if (!anyBalanceRead)
471
+ throw new Error(`AaveV3 historical balance: all balance reads failed at block ${block}`);
472
+ return entries.map((e, i) => {
473
+ const supplyRes = balanceResults[i * 3];
474
+ const varDebtRes = balanceResults[(i * 3) + 1];
475
+ const stableDebtRes = balanceResults[(i * 3) + 2];
476
+ const supplied = (supplyRes === null || supplyRes === void 0 ? void 0 : supplyRes.status) === 'success' ? supplyRes.result.toString() : '0';
477
+ const varDebt = (varDebtRes === null || varDebtRes === void 0 ? void 0 : varDebtRes.status) === 'success' ? varDebtRes.result.toString() : '0';
478
+ const stableDebt = (stableDebtRes === null || stableDebtRes === void 0 ? void 0 : stableDebtRes.status) === 'success' ? stableDebtRes.result.toString() : '0';
479
+ const debt = new Dec(varDebt).add(stableDebt).toString();
480
+ return Object.assign(Object.assign({}, e), { supplied, debt });
481
+ }).filter((a) => a.supplied !== '0' || a.debt !== '0');
482
+ };
483
+ /**
484
+ * Sums the active assets into the point's USD totals. Throws when every price read failed — the
485
+ * oracle couldn't serve this block (pruned state, or a hoisted oracle that wasn't deployed yet back
486
+ * then), and totalling it up anyway would return a fake near-$0 balance. A single asset's price
487
+ * failure still just skips that asset, matching the live data path.
488
+ */
489
+ const sumHistoricalUsd = (activeAssets, priceResBySymbol, block) => {
490
+ const anyPriceRead = activeAssets.some((a) => { var _a; return ((_a = priceResBySymbol[a.symbol]) === null || _a === void 0 ? void 0 : _a.status) === 'success'; });
491
+ if (!anyPriceRead)
492
+ throw new Error(`AaveV3 historical balance: all price reads failed at block ${block}`);
493
+ let suppliedUsd = new Dec(0);
494
+ let borrowedUsd = new Dec(0);
495
+ activeAssets.forEach((a) => {
496
+ const priceRes = priceResBySymbol[a.symbol];
497
+ if ((priceRes === null || priceRes === void 0 ? void 0 : priceRes.status) !== 'success')
498
+ return;
499
+ const priceUsd = new Dec(priceRes.result.toString()).div(1e8); // Aave v3 base currency is USD with 8 decimals
500
+ if (a.supplied !== '0')
501
+ suppliedUsd = suppliedUsd.add(new Dec(assetAmountInEth(a.supplied, a.symbol)).mul(priceUsd));
502
+ if (a.debt !== '0')
503
+ borrowedUsd = borrowedUsd.add(new Dec(assetAmountInEth(a.debt, a.symbol)).mul(priceUsd));
504
+ });
505
+ return {
506
+ block,
507
+ suppliedUsd: suppliedUsd.toString(),
508
+ borrowedUsd: borrowedUsd.toString(),
509
+ netUsd: suppliedUsd.minus(borrowedUsd).toString(),
510
+ };
511
+ };
512
+ /**
513
+ * Fast path, ONE multicall: balances plus a price read for every reserve through the caller-hoisted
514
+ * oracle. Prices are read for all reserves because which assets are active isn't known until the
515
+ * balances come back — extra reads inside an already-paid multicall are nearly free.
516
+ */
517
+ const fetchHistoricalPointWithOracle = (provider, entries, address, block, oracleAddress) => __awaiter(void 0, void 0, void 0, function* () {
518
+ const balanceContracts = getHistoricalBalanceContracts(entries, address);
519
+ const priceContracts = entries.map((e) => getAssetPriceContract(oracleAddress, e.underlyingAddress));
520
+ // @ts-ignore
521
+ const results = yield provider.multicall({
522
+ contracts: [...balanceContracts, ...priceContracts], allowFailure: true, blockNumber: BigInt(block), batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
523
+ });
524
+ const activeAssets = toActiveHistoricalAssets(entries, results.slice(0, balanceContracts.length), block);
525
+ const priceResBySymbol = {};
526
+ results.slice(balanceContracts.length).forEach((res, i) => {
527
+ priceResBySymbol[entries[i].symbol] = res;
528
+ });
529
+ return { activeAssets, priceResBySymbol };
530
+ });
531
+ /**
532
+ * Fallback path, TWO multicalls: balances plus getPriceOracle (resolving the oracle that was active
533
+ * AT the block — Aave can rotate it over time), then prices for just the active assets. The oracle
534
+ * read has no fallback: if it fails (bad archive node, rate limit, ...) the failure must surface
535
+ * rather than silently returning $0 — the caller renders a gap instead of a misleading zero.
536
+ */
537
+ const fetchHistoricalPointResolvingOracle = (provider, network, market, entries, address, block) => __awaiter(void 0, void 0, void 0, function* () {
538
+ const balanceContracts = getHistoricalBalanceContracts(entries, address);
539
+ // @ts-ignore market.provider is a valid config key at runtime
540
+ const providerAbi = getConfigContractAbi(market.provider, network);
541
+ const oracleContract = {
542
+ address: market.providerAddress,
543
+ abi: providerAbi,
544
+ functionName: 'getPriceOracle',
545
+ };
546
+ const blockNumber = BigInt(block);
547
+ // @ts-ignore
548
+ const results = yield provider.multicall({
549
+ contracts: [...balanceContracts, oracleContract], allowFailure: true, blockNumber, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
550
+ });
551
+ const oracleRes = results[balanceContracts.length];
552
+ if ((oracleRes === null || oracleRes === void 0 ? void 0 : oracleRes.status) !== 'success' || !oracleRes.result)
553
+ throw new Error(`AaveV3 historical balance: oracle unavailable at block ${block}`);
554
+ const activeAssets = toActiveHistoricalAssets(entries, results.slice(0, balanceContracts.length), block);
555
+ if (!activeAssets.length)
556
+ return { activeAssets, priceResBySymbol: {} };
557
+ const priceContracts = activeAssets.map((a) => getAssetPriceContract(oracleRes.result, a.underlyingAddress));
558
+ // @ts-ignore
559
+ const priceResults = yield provider.multicall({
560
+ contracts: priceContracts, allowFailure: true, blockNumber, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
561
+ });
562
+ const priceResBySymbol = {};
563
+ priceResults.forEach((res, i) => {
564
+ priceResBySymbol[activeAssets[i].symbol] = res;
565
+ });
566
+ return { activeAssets, priceResBySymbol };
567
+ });
568
+ /**
569
+ * Computes a user's Aave v3 net USD balance (supplied collateral - borrowed debt) at a historical block,
570
+ * without touching the AaveV3View contract. Pass `reserveTokens` (from getAaveV3ReserveTokenAddresses)
571
+ * to avoid refetching token addresses for every point, and `oracleAddress` (from getAaveV3OracleAddress,
572
+ * verified identical at both ends of the sampled range) to collapse the point into a single multicall —
573
+ * with it the balances and all prices ride one request; without it the oracle is resolved at the block
574
+ * inside the balance multicall and the active assets are priced in a second one.
575
+ */
576
+ export const _getAaveV3HistoricalBalance = (provider, network, market, address, block, reserveTokens, oracleAddress) => __awaiter(void 0, void 0, void 0, function* () {
577
+ const empty = {
578
+ block, suppliedUsd: '0', borrowedUsd: '0', netUsd: '0',
579
+ };
580
+ if (!address)
581
+ return empty;
582
+ const tokens = reserveTokens || (yield _getAaveV3ReserveTokenAddresses(provider, network, market));
583
+ const entries = Object.values(tokens);
584
+ if (!entries.length)
585
+ return empty;
586
+ const { activeAssets, priceResBySymbol } = oracleAddress
587
+ ? yield fetchHistoricalPointWithOracle(provider, entries, address, block, oracleAddress)
588
+ : yield fetchHistoricalPointResolvingOracle(provider, network, market, entries, address, block);
589
+ if (!activeAssets.length)
590
+ return empty;
591
+ return sumHistoricalUsd(activeAssets, priceResBySymbol, block);
592
+ });
593
+ export const getAaveV3HistoricalBalance = (provider, network, market, address, block, reserveTokens, oracleAddress) => __awaiter(void 0, void 0, void 0, function* () { return _getAaveV3HistoricalBalance(getViemProvider(provider, network, { batch: { multicall: true } }), network, market, address, block, reserveTokens, oracleAddress); });
371
594
  export const _getAaveV3AccountData = (provider_1, network_1, address_1, extractedState_1, ...args_1) => __awaiter(void 0, [provider_1, network_1, address_1, extractedState_1, ...args_1], void 0, function* (provider, network, address, extractedState, blockNumber = 'latest') {
372
595
  const { selectedMarket: market, assetsData, eModeCategoriesData, } = extractedState;
373
596
  let payload = Object.assign(Object.assign({}, EMPTY_AAVE_DATA), { lastUpdated: Date.now() });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defisaver/positions-sdk",
3
- "version": "2.1.165",
3
+ "version": "2.1.166",
4
4
  "description": "",
5
5
  "main": "./cjs/index.js",
6
6
  "module": "./esm/index.js",
@@ -6,6 +6,7 @@ import {
6
6
  AaveIncentivesControllerViem,
7
7
  AaveV3ViewContractViem,
8
8
  createViemContractFromConfigFunc,
9
+ getConfigContractAbi,
9
10
  StkAAVEViem,
10
11
  } from '../contracts';
11
12
  import { aaveAnyGetAggregatedPositionData, aaveV3IsInIsolationMode, aaveV3IsInSiloedMode } from '../helpers/aaveHelpers';
@@ -435,6 +436,290 @@ export const _getAaveV3AccountBalances = async (provider: Client, network: Netwo
435
436
 
436
437
  export const getAaveV3AccountBalances = async (provider: EthereumProvider, network: NetworkNumber, block: Blockish, addressMapping: boolean, address: EthAddress): Promise<PositionBalances> => _getAaveV3AccountBalances(getViemProvider(provider, network), network, block, addressMapping, address);
437
438
 
439
+ /**
440
+ * Historical net-balance helpers that bypass the AaveV3View contract.
441
+ *
442
+ * The View contract (and therefore `getAaveV3AccountData` / `getAaveV3AccountBalances`) can only be
443
+ * queried from its deployment block onwards, so it cannot read balances for positions older than that.
444
+ * The aTokens/debt tokens, the ProtocolDataProvider and the Aave price oracle all exist from Aave v3
445
+ * launch, so reading `balanceOf` on those tokens + the oracle price directly reaches much further back
446
+ * and costs ~1 multicall per point. Used to build a position balance-history chart.
447
+ */
448
+
449
+ // matches morphoVaults' batchSize; stays under the 500k portfolio discovery allows on simulation fork RPCs
450
+ const HISTORICAL_MULTICALL_BATCH_SIZE = 250_000;
451
+
452
+ // Minimal Aave price oracle ABI (getAssetPrice returns the asset price in the market base currency).
453
+ const AAVE_ORACLE_ABI = [
454
+ {
455
+ inputs: [{ internalType: 'address', name: 'asset', type: 'address' }],
456
+ name: 'getAssetPrice',
457
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
458
+ stateMutability: 'view',
459
+ type: 'function',
460
+ },
461
+ ] as const;
462
+
463
+ export interface AaveV3ReserveTokenAddresses {
464
+ [symbol: string]: {
465
+ symbol: string,
466
+ underlyingAddress: EthAddress,
467
+ aTokenAddress: EthAddress,
468
+ stableDebtTokenAddress: EthAddress,
469
+ variableDebtTokenAddress: EthAddress,
470
+ };
471
+ }
472
+
473
+ export interface AaveV3HistoricalBalance {
474
+ block: number,
475
+ suppliedUsd: string,
476
+ borrowedUsd: string,
477
+ netUsd: string,
478
+ }
479
+
480
+ /**
481
+ * Fetches the aToken / stable-debt / variable-debt token addresses for every asset in the market.
482
+ * These are effectively immutable per reserve, so fetch once and reuse across all history points.
483
+ */
484
+ export const _getAaveV3ReserveTokenAddresses = async (provider: Client, network: NetworkNumber, market: AaveMarketInfo): Promise<AaveV3ReserveTokenAddresses> => {
485
+ const symbols = market.assets;
486
+ const underlyingAddresses = symbols.map((a: string) => getAssetInfo(getWrappedNativeAssetFromUnwrapped(a), network).address as EthAddress);
487
+ // @ts-ignore market.protocolData is a valid config key at runtime
488
+ const dataProviderAbi = getConfigContractAbi(market.protocolData, network);
489
+
490
+ const contracts = underlyingAddresses.map((underlying) => ({
491
+ address: market.protocolDataAddress as EthAddress,
492
+ abi: dataProviderAbi,
493
+ functionName: 'getReserveTokensAddresses',
494
+ args: [underlying],
495
+ }));
496
+
497
+ // @ts-ignore
498
+ const results = await provider.multicall({ contracts, allowFailure: true, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE });
499
+
500
+ const mapping: AaveV3ReserveTokenAddresses = {};
501
+ results.forEach((res: any, i: number) => {
502
+ if (res.status !== 'success' || !res.result) return;
503
+ // outputs order: [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress]
504
+ const [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress] = res.result as [EthAddress, EthAddress, EthAddress];
505
+ mapping[symbols[i]] = {
506
+ symbol: symbols[i],
507
+ underlyingAddress: underlyingAddresses[i],
508
+ aTokenAddress,
509
+ stableDebtTokenAddress,
510
+ variableDebtTokenAddress,
511
+ };
512
+ });
513
+
514
+ return mapping;
515
+ };
516
+
517
+ export const getAaveV3ReserveTokenAddresses = async (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo): Promise<AaveV3ReserveTokenAddresses> => _getAaveV3ReserveTokenAddresses(getViemProvider(provider, network, { batch: { multicall: true } }), network, market);
518
+
519
+ /**
520
+ * Resolves the price oracle the market's addresses provider points at (at `block`, or latest).
521
+ * Aave can rotate the oracle over time, so a caller hoisting one address across many historical
522
+ * points must resolve it at both ends of its block range and only reuse it when the two agree —
523
+ * otherwise leave `oracleAddress` unset on _getAaveV3HistoricalBalance and it resolves per block.
524
+ */
525
+ export const _getAaveV3OracleAddress = async (provider: Client, network: NetworkNumber, market: AaveMarketInfo, block: Blockish = 'latest'): Promise<EthAddress> => {
526
+ // @ts-ignore market.provider is a valid config key at runtime
527
+ const providerAbi = getConfigContractAbi(market.provider, network);
528
+ // @ts-ignore readContract exists on the public client returned by getViemProvider
529
+ const oracleAddress = await provider.readContract({
530
+ address: market.providerAddress as EthAddress,
531
+ abi: providerAbi as any,
532
+ functionName: 'getPriceOracle',
533
+ ...setViemBlockNumber(block),
534
+ });
535
+ if (!oracleAddress) throw new Error(`AaveV3 oracle unavailable at block ${block}`);
536
+ return oracleAddress as EthAddress;
537
+ };
538
+
539
+ export const getAaveV3OracleAddress = async (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo, block: Blockish = 'latest'): Promise<EthAddress> => _getAaveV3OracleAddress(getViemProvider(provider, network, { batch: { multicall: true } }), network, market, block);
540
+
541
+ type AaveV3ReserveTokenEntry = AaveV3ReserveTokenAddresses[string];
542
+ type AaveV3ActiveHistoricalAsset = AaveV3ReserveTokenEntry & { supplied: string, debt: string };
543
+ // raw getAssetPrice multicall result per asset symbol
544
+ type AaveV3PriceResultsBySymbol = Record<string, any>;
545
+
546
+ // supply = aToken.balanceOf, debt = variableDebtToken.balanceOf + stableDebtToken.balanceOf — three reads per reserve
547
+ const getHistoricalBalanceContracts = (entries: AaveV3ReserveTokenEntry[], address: EthAddress) => {
548
+ const erc20Abi = getConfigContractAbi('Erc20');
549
+ return entries.flatMap((e) => ([
550
+ {
551
+ address: e.aTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
552
+ },
553
+ {
554
+ address: e.variableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
555
+ },
556
+ {
557
+ address: e.stableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
558
+ },
559
+ ]));
560
+ };
561
+
562
+ const getAssetPriceContract = (oracle: EthAddress, underlyingAddress: EthAddress) => ({
563
+ address: oracle,
564
+ abi: AAVE_ORACLE_ABI,
565
+ functionName: 'getAssetPrice',
566
+ args: [underlyingAddress],
567
+ });
568
+
569
+ /**
570
+ * Pairs each reserve with its three balance reads and keeps the assets the wallet actually held.
571
+ * Throws when every read failed — a totally failed fetch must not masquerade as a real $0 balance,
572
+ * so the caller can distinguish "fetch failed" (gap) from "position was empty" (genuine 0).
573
+ */
574
+ const toActiveHistoricalAssets = (entries: AaveV3ReserveTokenEntry[], balanceResults: any[], block: number): AaveV3ActiveHistoricalAsset[] => {
575
+ const anyBalanceRead = balanceResults.some((r) => r?.status === 'success');
576
+ if (!anyBalanceRead) throw new Error(`AaveV3 historical balance: all balance reads failed at block ${block}`);
577
+
578
+ return entries.map((e, i) => {
579
+ const supplyRes = balanceResults[i * 3];
580
+ const varDebtRes = balanceResults[(i * 3) + 1];
581
+ const stableDebtRes = balanceResults[(i * 3) + 2];
582
+ const supplied = supplyRes?.status === 'success' ? (supplyRes.result as bigint).toString() : '0';
583
+ const varDebt = varDebtRes?.status === 'success' ? (varDebtRes.result as bigint).toString() : '0';
584
+ const stableDebt = stableDebtRes?.status === 'success' ? (stableDebtRes.result as bigint).toString() : '0';
585
+ const debt = new Dec(varDebt).add(stableDebt).toString();
586
+ return { ...e, supplied, debt };
587
+ }).filter((a) => a.supplied !== '0' || a.debt !== '0');
588
+ };
589
+
590
+ /**
591
+ * Sums the active assets into the point's USD totals. Throws when every price read failed — the
592
+ * oracle couldn't serve this block (pruned state, or a hoisted oracle that wasn't deployed yet back
593
+ * then), and totalling it up anyway would return a fake near-$0 balance. A single asset's price
594
+ * failure still just skips that asset, matching the live data path.
595
+ */
596
+ const sumHistoricalUsd = (activeAssets: AaveV3ActiveHistoricalAsset[], priceResBySymbol: AaveV3PriceResultsBySymbol, block: number): AaveV3HistoricalBalance => {
597
+ const anyPriceRead = activeAssets.some((a) => priceResBySymbol[a.symbol]?.status === 'success');
598
+ if (!anyPriceRead) throw new Error(`AaveV3 historical balance: all price reads failed at block ${block}`);
599
+
600
+ let suppliedUsd = new Dec(0);
601
+ let borrowedUsd = new Dec(0);
602
+ activeAssets.forEach((a) => {
603
+ const priceRes = priceResBySymbol[a.symbol];
604
+ if (priceRes?.status !== 'success') return;
605
+ const priceUsd = new Dec((priceRes.result as bigint).toString()).div(1e8); // Aave v3 base currency is USD with 8 decimals
606
+ if (a.supplied !== '0') suppliedUsd = suppliedUsd.add(new Dec(assetAmountInEth(a.supplied, a.symbol)).mul(priceUsd));
607
+ if (a.debt !== '0') borrowedUsd = borrowedUsd.add(new Dec(assetAmountInEth(a.debt, a.symbol)).mul(priceUsd));
608
+ });
609
+
610
+ return {
611
+ block,
612
+ suppliedUsd: suppliedUsd.toString(),
613
+ borrowedUsd: borrowedUsd.toString(),
614
+ netUsd: suppliedUsd.minus(borrowedUsd).toString(),
615
+ };
616
+ };
617
+
618
+ /**
619
+ * Fast path, ONE multicall: balances plus a price read for every reserve through the caller-hoisted
620
+ * oracle. Prices are read for all reserves because which assets are active isn't known until the
621
+ * balances come back — extra reads inside an already-paid multicall are nearly free.
622
+ */
623
+ const fetchHistoricalPointWithOracle = async (provider: Client, entries: AaveV3ReserveTokenEntry[], address: EthAddress, block: number, oracleAddress: EthAddress) => {
624
+ const balanceContracts = getHistoricalBalanceContracts(entries, address);
625
+ const priceContracts = entries.map((e) => getAssetPriceContract(oracleAddress, e.underlyingAddress));
626
+
627
+ // @ts-ignore
628
+ const results = await provider.multicall({
629
+ contracts: [...balanceContracts, ...priceContracts], allowFailure: true, blockNumber: BigInt(block), batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
630
+ });
631
+
632
+ const activeAssets = toActiveHistoricalAssets(entries, results.slice(0, balanceContracts.length), block);
633
+ const priceResBySymbol: AaveV3PriceResultsBySymbol = {};
634
+ results.slice(balanceContracts.length).forEach((res: any, i: number) => {
635
+ priceResBySymbol[entries[i].symbol] = res;
636
+ });
637
+ return { activeAssets, priceResBySymbol };
638
+ };
639
+
640
+ /**
641
+ * Fallback path, TWO multicalls: balances plus getPriceOracle (resolving the oracle that was active
642
+ * AT the block — Aave can rotate it over time), then prices for just the active assets. The oracle
643
+ * read has no fallback: if it fails (bad archive node, rate limit, ...) the failure must surface
644
+ * rather than silently returning $0 — the caller renders a gap instead of a misleading zero.
645
+ */
646
+ const fetchHistoricalPointResolvingOracle = async (provider: Client, network: NetworkNumber, market: AaveMarketInfo, entries: AaveV3ReserveTokenEntry[], address: EthAddress, block: number) => {
647
+ const balanceContracts = getHistoricalBalanceContracts(entries, address);
648
+ // @ts-ignore market.provider is a valid config key at runtime
649
+ const providerAbi = getConfigContractAbi(market.provider, network);
650
+ const oracleContract = {
651
+ address: market.providerAddress as EthAddress,
652
+ abi: providerAbi,
653
+ functionName: 'getPriceOracle',
654
+ };
655
+ const blockNumber = BigInt(block);
656
+
657
+ // @ts-ignore
658
+ const results = await provider.multicall({
659
+ contracts: [...balanceContracts, oracleContract], allowFailure: true, blockNumber, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
660
+ });
661
+ const oracleRes = results[balanceContracts.length];
662
+ if (oracleRes?.status !== 'success' || !oracleRes.result) throw new Error(`AaveV3 historical balance: oracle unavailable at block ${block}`);
663
+
664
+ const activeAssets = toActiveHistoricalAssets(entries, results.slice(0, balanceContracts.length), block);
665
+ if (!activeAssets.length) return { activeAssets, priceResBySymbol: {} };
666
+
667
+ const priceContracts = activeAssets.map((a) => getAssetPriceContract(oracleRes.result as EthAddress, a.underlyingAddress));
668
+ // @ts-ignore
669
+ const priceResults = await provider.multicall({
670
+ contracts: priceContracts, allowFailure: true, blockNumber, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
671
+ });
672
+ const priceResBySymbol: AaveV3PriceResultsBySymbol = {};
673
+ priceResults.forEach((res: any, i: number) => {
674
+ priceResBySymbol[activeAssets[i].symbol] = res;
675
+ });
676
+ return { activeAssets, priceResBySymbol };
677
+ };
678
+
679
+ /**
680
+ * Computes a user's Aave v3 net USD balance (supplied collateral - borrowed debt) at a historical block,
681
+ * without touching the AaveV3View contract. Pass `reserveTokens` (from getAaveV3ReserveTokenAddresses)
682
+ * to avoid refetching token addresses for every point, and `oracleAddress` (from getAaveV3OracleAddress,
683
+ * verified identical at both ends of the sampled range) to collapse the point into a single multicall —
684
+ * with it the balances and all prices ride one request; without it the oracle is resolved at the block
685
+ * inside the balance multicall and the active assets are priced in a second one.
686
+ */
687
+ export const _getAaveV3HistoricalBalance = async (
688
+ provider: Client,
689
+ network: NetworkNumber,
690
+ market: AaveMarketInfo,
691
+ address: EthAddress,
692
+ block: number,
693
+ reserveTokens?: AaveV3ReserveTokenAddresses,
694
+ oracleAddress?: EthAddress,
695
+ ): Promise<AaveV3HistoricalBalance> => {
696
+ const empty: AaveV3HistoricalBalance = {
697
+ block, suppliedUsd: '0', borrowedUsd: '0', netUsd: '0',
698
+ };
699
+ if (!address) return empty;
700
+
701
+ const tokens = reserveTokens || await _getAaveV3ReserveTokenAddresses(provider, network, market);
702
+ const entries = Object.values(tokens);
703
+ if (!entries.length) return empty;
704
+
705
+ const { activeAssets, priceResBySymbol } = oracleAddress
706
+ ? await fetchHistoricalPointWithOracle(provider, entries, address, block, oracleAddress)
707
+ : await fetchHistoricalPointResolvingOracle(provider, network, market, entries, address, block);
708
+
709
+ if (!activeAssets.length) return empty;
710
+ return sumHistoricalUsd(activeAssets, priceResBySymbol, block);
711
+ };
712
+
713
+ export const getAaveV3HistoricalBalance = async (
714
+ provider: EthereumProvider,
715
+ network: NetworkNumber,
716
+ market: AaveMarketInfo,
717
+ address: EthAddress,
718
+ block: number,
719
+ reserveTokens?: AaveV3ReserveTokenAddresses,
720
+ oracleAddress?: EthAddress,
721
+ ): Promise<AaveV3HistoricalBalance> => _getAaveV3HistoricalBalance(getViemProvider(provider, network, { batch: { multicall: true } }), network, market, address, block, reserveTokens, oracleAddress);
722
+
438
723
  export const _getAaveV3AccountData = async (provider: Client, network: NetworkNumber, address: EthAddress, extractedState: any, blockNumber: 'latest' | number = 'latest'): Promise<AaveV3PositionData> => {
439
724
  const {
440
725
  selectedMarket: market, assetsData, eModeCategoriesData,