@defisaver/positions-sdk 2.1.156 → 2.1.157-pos-history-dev

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,34 @@ 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
+ * Computes a user's Aave v3 net USD balance (supplied collateral - borrowed debt) at a historical block,
58
+ * without touching the AaveV3View contract. Pass `reserveTokens` (from getAaveV3ReserveTokenAddresses)
59
+ * to avoid refetching token addresses for every point.
60
+ */
61
+ export declare const _getAaveV3HistoricalBalance: (provider: Client, network: NetworkNumber, market: AaveMarketInfo, address: EthAddress, block: number, reserveTokens?: AaveV3ReserveTokenAddresses) => Promise<AaveV3HistoricalBalance>;
62
+ export declare const getAaveV3HistoricalBalance: (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo, address: EthAddress, block: number, reserveTokens?: AaveV3ReserveTokenAddresses) => Promise<AaveV3HistoricalBalance>;
35
63
  export declare const _getAaveV3AccountData: (provider: Client, network: NetworkNumber, address: EthAddress, extractedState: any, blockNumber?: "latest" | number) => Promise<AaveV3PositionData>;
36
64
  export declare const getAaveV3AccountData: (provider: EthereumProvider, network: NetworkNumber, address: EthAddress, extractedState: any, blockNumber?: "latest" | number) => Promise<AaveV3PositionData>;
37
65
  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.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");
@@ -375,6 +375,155 @@ const _getAaveV3AccountBalances = (provider, network, block, addressMapping, add
375
375
  exports._getAaveV3AccountBalances = _getAaveV3AccountBalances;
376
376
  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); });
377
377
  exports.getAaveV3AccountBalances = getAaveV3AccountBalances;
378
+ /**
379
+ * Historical net-balance helpers that bypass the AaveV3View contract.
380
+ *
381
+ * The View contract (and therefore `getAaveV3AccountData` / `getAaveV3AccountBalances`) can only be
382
+ * queried from its deployment block onwards, so it cannot read balances for positions older than that.
383
+ * The aTokens/debt tokens, the ProtocolDataProvider and the Aave price oracle all exist from Aave v3
384
+ * launch, so reading `balanceOf` on those tokens + the oracle price directly reaches much further back
385
+ * and costs ~1 multicall per point. Used to build a position balance-history chart.
386
+ */
387
+ // Minimal Aave price oracle ABI (getAssetPrice returns the asset price in the market base currency).
388
+ const AAVE_ORACLE_ABI = [
389
+ {
390
+ inputs: [{ internalType: 'address', name: 'asset', type: 'address' }],
391
+ name: 'getAssetPrice',
392
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
393
+ stateMutability: 'view',
394
+ type: 'function',
395
+ },
396
+ ];
397
+ /**
398
+ * Fetches the aToken / stable-debt / variable-debt token addresses for every asset in the market.
399
+ * These are effectively immutable per reserve, so fetch once and reuse across all history points.
400
+ */
401
+ const _getAaveV3ReserveTokenAddresses = (provider, network, market) => __awaiter(void 0, void 0, void 0, function* () {
402
+ const symbols = market.assets;
403
+ const underlyingAddresses = symbols.map((a) => (0, tokens_1.getAssetInfo)((0, utils_1.getWrappedNativeAssetFromUnwrapped)(a), network).address);
404
+ // @ts-ignore market.protocolData is a valid config key at runtime
405
+ const dataProviderAbi = (0, contracts_1.getConfigContractAbi)(market.protocolData, network);
406
+ const contracts = underlyingAddresses.map((underlying) => ({
407
+ address: market.protocolDataAddress,
408
+ abi: dataProviderAbi,
409
+ functionName: 'getReserveTokensAddresses',
410
+ args: [underlying],
411
+ }));
412
+ // @ts-ignore
413
+ const results = yield provider.multicall({ contracts, allowFailure: true });
414
+ const mapping = {};
415
+ results.forEach((res, i) => {
416
+ if (res.status !== 'success' || !res.result)
417
+ return;
418
+ // outputs order: [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress]
419
+ const [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress] = res.result;
420
+ mapping[symbols[i]] = {
421
+ symbol: symbols[i],
422
+ underlyingAddress: underlyingAddresses[i],
423
+ aTokenAddress,
424
+ stableDebtTokenAddress,
425
+ variableDebtTokenAddress,
426
+ };
427
+ });
428
+ return mapping;
429
+ });
430
+ exports._getAaveV3ReserveTokenAddresses = _getAaveV3ReserveTokenAddresses;
431
+ 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); });
432
+ exports.getAaveV3ReserveTokenAddresses = getAaveV3ReserveTokenAddresses;
433
+ /**
434
+ * Computes a user's Aave v3 net USD balance (supplied collateral - borrowed debt) at a historical block,
435
+ * without touching the AaveV3View contract. Pass `reserveTokens` (from getAaveV3ReserveTokenAddresses)
436
+ * to avoid refetching token addresses for every point.
437
+ */
438
+ const _getAaveV3HistoricalBalance = (provider, network, market, address, block, reserveTokens) => __awaiter(void 0, void 0, void 0, function* () {
439
+ const empty = {
440
+ block, suppliedUsd: '0', borrowedUsd: '0', netUsd: '0',
441
+ };
442
+ if (!address)
443
+ return empty;
444
+ const tokens = reserveTokens || (yield (0, exports._getAaveV3ReserveTokenAddresses)(provider, network, market));
445
+ const entries = Object.values(tokens);
446
+ if (!entries.length)
447
+ return empty;
448
+ const erc20Abi = (0, contracts_1.getConfigContractAbi)('Erc20');
449
+ const blockNumber = BigInt(block);
450
+ // supply = aToken.balanceOf, debt = variableDebtToken.balanceOf + stableDebtToken.balanceOf, all at the block.
451
+ const balanceContracts = entries.flatMap((e) => ([
452
+ {
453
+ address: e.aTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
454
+ },
455
+ {
456
+ address: e.variableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
457
+ },
458
+ {
459
+ address: e.stableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
460
+ },
461
+ ]));
462
+ // @ts-ignore market.provider is a valid config key at runtime
463
+ const providerAbi = (0, contracts_1.getConfigContractAbi)(market.provider, network);
464
+ const [balanceResults, oracleAddress] = yield Promise.all([
465
+ // @ts-ignore
466
+ provider.multicall({ contracts: balanceContracts, allowFailure: true, blockNumber }),
467
+ // resolve the oracle that was active at the block (Aave can rotate the oracle over time).
468
+ // No catch: if this read fails (bad archive node, rate limit, ...) we must surface the failure
469
+ // rather than silently returning $0 — the caller renders a gap instead of a misleading zero.
470
+ // @ts-ignore readContract exists on the public client returned by getViemProvider
471
+ provider.readContract({
472
+ address: market.providerAddress,
473
+ abi: providerAbi,
474
+ functionName: 'getPriceOracle',
475
+ blockNumber,
476
+ }),
477
+ ]);
478
+ // A totally failed balance read must not masquerade as a real $0 balance — throw so the caller
479
+ // can distinguish "fetch failed" (gap) from "position was empty" (genuine 0).
480
+ const anyBalanceRead = balanceResults.some((r) => (r === null || r === void 0 ? void 0 : r.status) === 'success');
481
+ if (!anyBalanceRead)
482
+ throw new Error(`AaveV3 historical balance: all balance reads failed at block ${block}`);
483
+ if (!oracleAddress)
484
+ throw new Error(`AaveV3 historical balance: oracle unavailable at block ${block}`);
485
+ const activeAssets = entries.map((e, i) => {
486
+ const supplyRes = balanceResults[i * 3];
487
+ const varDebtRes = balanceResults[(i * 3) + 1];
488
+ const stableDebtRes = balanceResults[(i * 3) + 2];
489
+ const supplied = (supplyRes === null || supplyRes === void 0 ? void 0 : supplyRes.status) === 'success' ? supplyRes.result.toString() : '0';
490
+ const varDebt = (varDebtRes === null || varDebtRes === void 0 ? void 0 : varDebtRes.status) === 'success' ? varDebtRes.result.toString() : '0';
491
+ const stableDebt = (stableDebtRes === null || stableDebtRes === void 0 ? void 0 : stableDebtRes.status) === 'success' ? stableDebtRes.result.toString() : '0';
492
+ const debt = new decimal_js_1.default(varDebt).add(stableDebt).toString();
493
+ return Object.assign(Object.assign({}, e), { supplied, debt });
494
+ }).filter((a) => a.supplied !== '0' || a.debt !== '0');
495
+ if (!activeAssets.length)
496
+ return empty;
497
+ const priceContracts = activeAssets.map((a) => ({
498
+ address: oracleAddress,
499
+ abi: AAVE_ORACLE_ABI,
500
+ functionName: 'getAssetPrice',
501
+ args: [a.underlyingAddress],
502
+ }));
503
+ // @ts-ignore
504
+ const priceResults = yield provider.multicall({ contracts: priceContracts, allowFailure: true, blockNumber });
505
+ let suppliedUsd = new decimal_js_1.default(0);
506
+ let borrowedUsd = new decimal_js_1.default(0);
507
+ activeAssets.forEach((a, i) => {
508
+ const priceRes = priceResults[i];
509
+ if ((priceRes === null || priceRes === void 0 ? void 0 : priceRes.status) !== 'success')
510
+ return;
511
+ const priceUsd = new decimal_js_1.default(priceRes.result.toString()).div(1e8); // Aave v3 base currency is USD with 8 decimals
512
+ if (a.supplied !== '0')
513
+ suppliedUsd = suppliedUsd.add(new decimal_js_1.default((0, tokens_1.assetAmountInEth)(a.supplied, a.symbol)).mul(priceUsd));
514
+ if (a.debt !== '0')
515
+ borrowedUsd = borrowedUsd.add(new decimal_js_1.default((0, tokens_1.assetAmountInEth)(a.debt, a.symbol)).mul(priceUsd));
516
+ });
517
+ return {
518
+ block,
519
+ suppliedUsd: suppliedUsd.toString(),
520
+ borrowedUsd: borrowedUsd.toString(),
521
+ netUsd: suppliedUsd.minus(borrowedUsd).toString(),
522
+ };
523
+ });
524
+ exports._getAaveV3HistoricalBalance = _getAaveV3HistoricalBalance;
525
+ const getAaveV3HistoricalBalance = (provider, network, market, address, block, reserveTokens) => __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); });
526
+ exports.getAaveV3HistoricalBalance = getAaveV3HistoricalBalance;
378
527
  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') {
379
528
  const { selectedMarket: market, assetsData, eModeCategoriesData, } = extractedState;
380
529
  let payload = Object.assign(Object.assign({}, exports.EMPTY_AAVE_DATA), { lastUpdated: Date.now() });
@@ -8,14 +8,18 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
15
  exports.attachAaveV4MerklIncentives = exports.getAaveV4MerkleCampaigns = exports.buildAaveV4MerklRewardMap = void 0;
16
+ const decimal_js_1 = __importDefault(require("decimal.js"));
13
17
  const moneymarket_1 = require("../moneymarket");
14
18
  const merkl_1 = require("../services/merkl");
15
19
  const types_1 = require("../types");
16
20
  /**
17
21
  * Merkl tags Aave V4 reward campaigns by scope via the `type` field:
18
- * - AAVE_V4_HUB_SUPPLY / AAVE_V4_HUB_BORROW reward tied to a hub asset
22
+ * - AAVE_V4_HUB_SUPPLY / AAVE_V4_HUB_BORROW / AAVE_V4_HUB_NET_LENDING hub asset
19
23
  * - AAVE_V4_SPOKE_SUPPLY / AAVE_V4_SPOKE_BORROW → reward tied to a spoke reserve
20
24
  * Embedded campaign params provide the exact on-chain identifiers. Token addresses cannot safely
21
25
  * identify Aave V4 rewards because one spoke can expose the same underlying from multiple hubs.
@@ -42,9 +46,19 @@ const buildAaveV4MerklRewardMap = (opportunities, chainId) => {
42
46
  .filter((o) => o.status === types_1.OpportunityStatus.LIVE)
43
47
  .filter((o) => typeof o.type === 'string' && o.type.startsWith('AAVE_V4_'))
44
48
  .forEach((o) => {
45
- var _a, _b;
49
+ var _a, _b, _c;
46
50
  const side = o.action === types_1.OpportunityAction.BORROW ? types_1.IncentiveSide.Borrow : types_1.IncentiveSide.Supply;
47
- const incentive = buildIncentive(o);
51
+ const now = Date.now() / 1000;
52
+ const methods = (_a = o.campaigns) === null || _a === void 0 ? void 0 : _a.filter((c) => (c.startTimestamp === undefined || c.startTimestamp <= now)
53
+ && (c.endTimestamp === undefined || c.endTimestamp > now)).map((c) => { var _a, _b; return (_b = (_a = c.params) === null || _a === void 0 ? void 0 : _a.distributionMethodParameters) === null || _b === void 0 ? void 0 : _b.distributionMethod; });
54
+ // An opportunity-level APR cannot be split between simultaneous net and additive campaigns.
55
+ if (side === types_1.IncentiveSide.Supply && (methods === null || methods === void 0 ? void 0 : methods.includes('AAVE_V4_NET_APR'))
56
+ && methods.some((method) => !!method && method !== 'AAVE_V4_NET_APR'))
57
+ return;
58
+ const incentive = Object.assign(Object.assign({}, buildIncentive(o)), {
59
+ // Missing methods keep the existing target-yield behavior.
60
+ isAdditiveReward: side === types_1.IncentiveSide.Supply && !!(methods === null || methods === void 0 ? void 0 : methods.length)
61
+ && methods.every((method) => !!method && method !== 'AAVE_V4_NET_APR') });
48
62
  // one opportunity can span several campaigns (e.g. renewed periods), so campaign identity is
49
63
  // collected per scope key before the reward entries are written
50
64
  const idsByKey = {};
@@ -57,11 +71,13 @@ const buildAaveV4MerklRewardMap = (opportunities, chainId) => {
57
71
  idsByKey[key].parentCampaignIds.add(campaign.parentCampaignId);
58
72
  };
59
73
  if (o.type.includes('HUB')) {
60
- (_a = o.campaigns) === null || _a === void 0 ? void 0 : _a.forEach((c) => {
61
- var _a;
62
- if (!((_a = c.params) === null || _a === void 0 ? void 0 : _a.hubAddress) || c.params.assetId === undefined || c.params.assetId === null)
74
+ (_b = o.campaigns) === null || _b === void 0 ? void 0 : _b.forEach((c) => {
75
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
76
+ const hubAddress = (_b = (_a = c.params) === null || _a === void 0 ? void 0 : _a.hubAddress) !== null && _b !== void 0 ? _b : (_e = (_d = (_c = c.params) === null || _c === void 0 ? void 0 : _c.distributionMethodParameters) === null || _d === void 0 ? void 0 : _d.distributionSettings) === null || _e === void 0 ? void 0 : _e.hubAddress;
77
+ const assetId = (_g = (_f = c.params) === null || _f === void 0 ? void 0 : _f.assetId) !== null && _g !== void 0 ? _g : (_k = (_j = (_h = c.params) === null || _h === void 0 ? void 0 : _h.distributionMethodParameters) === null || _j === void 0 ? void 0 : _j.distributionSettings) === null || _k === void 0 ? void 0 : _k.assetId;
78
+ if (!hubAddress || assetId === undefined || assetId === null)
63
79
  return;
64
- collect(scopeKey(c.params.hubAddress, c.params.assetId), c);
80
+ collect(scopeKey(hubAddress, assetId), c);
65
81
  });
66
82
  Object.entries(idsByKey).forEach(([key, ids]) => {
67
83
  if (!result.hub[key])
@@ -70,7 +86,7 @@ const buildAaveV4MerklRewardMap = (opportunities, chainId) => {
70
86
  });
71
87
  }
72
88
  else if (o.type.includes('SPOKE')) {
73
- (_b = o.campaigns) === null || _b === void 0 ? void 0 : _b.forEach((c) => {
89
+ (_c = o.campaigns) === null || _c === void 0 ? void 0 : _c.forEach((c) => {
74
90
  var _a;
75
91
  if (!((_a = c.params) === null || _a === void 0 ? void 0 : _a.spokeAddress) || c.params.reserveId === undefined || c.params.reserveId === null)
76
92
  return;
@@ -90,7 +106,7 @@ const getAaveV4MerkleCampaigns = (chainId) => __awaiter(void 0, void 0, void 0,
90
106
  try {
91
107
  const opportunities = yield (0, merkl_1.fetchAllMerklOpportunities)({
92
108
  mainProtocolId: 'aave',
93
- type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,AAVE_V4_SPOKE_SUPPLY,AAVE_V4_SPOKE_BORROW',
109
+ type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,AAVE_V4_HUB_NET_LENDING,AAVE_V4_SPOKE_SUPPLY,AAVE_V4_SPOKE_BORROW',
94
110
  status: types_1.OpportunityStatus.LIVE,
95
111
  campaigns: 'true',
96
112
  });
@@ -112,6 +128,8 @@ const attachAaveV4MerklIncentives = (asset, spokeAddress, campaigns) => {
112
128
  const baseBorrow = asset.borrowIncentives || [];
113
129
  const spokeScoped = spokeAddress ? campaigns.spoke[scopeKey(spokeAddress, asset.reserveId)] : undefined;
114
130
  const hubScoped = asset.hub ? campaigns.hub[scopeKey(asset.hub, asset.assetId)] : undefined;
115
- return Object.assign(Object.assign({}, asset), { spokeSupplyIncentives: ((_a = spokeScoped === null || spokeScoped === void 0 ? void 0 : spokeScoped.supply) === null || _a === void 0 ? void 0 : _a.length) ? [...baseSupply, ...spokeScoped.supply] : baseSupply, spokeBorrowIncentives: ((_b = spokeScoped === null || spokeScoped === void 0 ? void 0 : spokeScoped.borrow) === null || _b === void 0 ? void 0 : _b.length) ? [...baseBorrow, ...spokeScoped.borrow] : baseBorrow, hubSupplyIncentives: ((_c = hubScoped === null || hubScoped === void 0 ? void 0 : hubScoped.supply) === null || _c === void 0 ? void 0 : _c.length) ? [...baseSupply, ...hubScoped.supply] : baseSupply, hubBorrowIncentives: ((_d = hubScoped === null || hubScoped === void 0 ? void 0 : hubScoped.borrow) === null || _d === void 0 ? void 0 : _d.length) ? [...baseBorrow, ...hubScoped.borrow] : baseBorrow });
131
+ // Net-APR campaigns top up native yield; additive campaigns pay their APR on top.
132
+ const supplyRewards = (rewards = []) => rewards.map((reward) => (Object.assign(Object.assign({}, reward), { apy: reward.isAdditiveReward ? reward.apy : decimal_js_1.default.max(0, new decimal_js_1.default(reward.apy).minus(asset.supplyRate || 0)).toString() })));
133
+ return Object.assign(Object.assign({}, asset), { spokeSupplyIncentives: ((_a = spokeScoped === null || spokeScoped === void 0 ? void 0 : spokeScoped.supply) === null || _a === void 0 ? void 0 : _a.length) ? [...baseSupply, ...supplyRewards(spokeScoped.supply)] : baseSupply, spokeBorrowIncentives: ((_b = spokeScoped === null || spokeScoped === void 0 ? void 0 : spokeScoped.borrow) === null || _b === void 0 ? void 0 : _b.length) ? [...baseBorrow, ...spokeScoped.borrow] : baseBorrow, hubSupplyIncentives: ((_c = hubScoped === null || hubScoped === void 0 ? void 0 : hubScoped.supply) === null || _c === void 0 ? void 0 : _c.length) ? [...baseSupply, ...supplyRewards(hubScoped.supply)] : baseSupply, hubBorrowIncentives: ((_d = hubScoped === null || hubScoped === void 0 ? void 0 : hubScoped.borrow) === null || _d === void 0 ? void 0 : _d.length) ? [...baseBorrow, ...hubScoped.borrow] : baseBorrow });
116
134
  };
117
135
  exports.attachAaveV4MerklIncentives = attachAaveV4MerklIncentives;
@@ -11,6 +11,8 @@ export declare enum OpportunityStatus {
11
11
  export type MerklCampaign = {
12
12
  id: string;
13
13
  campaignId: string;
14
+ startTimestamp?: number;
15
+ endTimestamp?: number;
14
16
  /**
15
17
  * Merkl-internal `id` of the parent campaign — set on child campaigns, which re-publish a hub
16
18
  * (parent) campaign's reward scoped to a single spoke reserve. Absent on standalone campaigns,
@@ -24,6 +26,13 @@ export type MerklCampaign = {
24
26
  hubAddress?: EthAddress;
25
27
  hubAssetId?: string | number;
26
28
  assetId?: string | number;
29
+ distributionMethodParameters?: {
30
+ distributionMethod?: string;
31
+ distributionSettings?: {
32
+ hubAddress?: EthAddress;
33
+ assetId?: string | number;
34
+ };
35
+ };
27
36
  };
28
37
  };
29
38
  export type MerklOpportunity = {
@@ -102,6 +111,7 @@ export type MerkleRewardMap = Record<EthAddress, {
102
111
  export type AaveV4MerklIncentive = IncentiveData & {
103
112
  campaignIds?: string[];
104
113
  parentCampaignIds?: string[];
114
+ isAdditiveReward?: boolean;
105
115
  };
106
116
  export type AaveV4MerklScopedReward = {
107
117
  [side in IncentiveSide]?: AaveV4MerklIncentive[];
@@ -32,6 +32,34 @@ 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
+ * Computes a user's Aave v3 net USD balance (supplied collateral - borrowed debt) at a historical block,
58
+ * without touching the AaveV3View contract. Pass `reserveTokens` (from getAaveV3ReserveTokenAddresses)
59
+ * to avoid refetching token addresses for every point.
60
+ */
61
+ export declare const _getAaveV3HistoricalBalance: (provider: Client, network: NetworkNumber, market: AaveMarketInfo, address: EthAddress, block: number, reserveTokens?: AaveV3ReserveTokenAddresses) => Promise<AaveV3HistoricalBalance>;
62
+ export declare const getAaveV3HistoricalBalance: (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo, address: EthAddress, block: number, reserveTokens?: AaveV3ReserveTokenAddresses) => Promise<AaveV3HistoricalBalance>;
35
63
  export declare const _getAaveV3AccountData: (provider: Client, network: NetworkNumber, address: EthAddress, extractedState: any, blockNumber?: "latest" | number) => Promise<AaveV3PositionData>;
36
64
  export declare const getAaveV3AccountData: (provider: EthereumProvider, network: NetworkNumber, address: EthAddress, extractedState: any, blockNumber?: "latest" | number) => Promise<AaveV3PositionData>;
37
65
  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';
@@ -362,6 +362,151 @@ export const _getAaveV3AccountBalances = (provider, network, block, addressMappi
362
362
  return balances;
363
363
  });
364
364
  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); });
365
+ /**
366
+ * Historical net-balance helpers that bypass the AaveV3View contract.
367
+ *
368
+ * The View contract (and therefore `getAaveV3AccountData` / `getAaveV3AccountBalances`) can only be
369
+ * queried from its deployment block onwards, so it cannot read balances for positions older than that.
370
+ * The aTokens/debt tokens, the ProtocolDataProvider and the Aave price oracle all exist from Aave v3
371
+ * launch, so reading `balanceOf` on those tokens + the oracle price directly reaches much further back
372
+ * and costs ~1 multicall per point. Used to build a position balance-history chart.
373
+ */
374
+ // Minimal Aave price oracle ABI (getAssetPrice returns the asset price in the market base currency).
375
+ const AAVE_ORACLE_ABI = [
376
+ {
377
+ inputs: [{ internalType: 'address', name: 'asset', type: 'address' }],
378
+ name: 'getAssetPrice',
379
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
380
+ stateMutability: 'view',
381
+ type: 'function',
382
+ },
383
+ ];
384
+ /**
385
+ * Fetches the aToken / stable-debt / variable-debt token addresses for every asset in the market.
386
+ * These are effectively immutable per reserve, so fetch once and reuse across all history points.
387
+ */
388
+ export const _getAaveV3ReserveTokenAddresses = (provider, network, market) => __awaiter(void 0, void 0, void 0, function* () {
389
+ const symbols = market.assets;
390
+ const underlyingAddresses = symbols.map((a) => getAssetInfo(getWrappedNativeAssetFromUnwrapped(a), network).address);
391
+ // @ts-ignore market.protocolData is a valid config key at runtime
392
+ const dataProviderAbi = getConfigContractAbi(market.protocolData, network);
393
+ const contracts = underlyingAddresses.map((underlying) => ({
394
+ address: market.protocolDataAddress,
395
+ abi: dataProviderAbi,
396
+ functionName: 'getReserveTokensAddresses',
397
+ args: [underlying],
398
+ }));
399
+ // @ts-ignore
400
+ const results = yield provider.multicall({ contracts, allowFailure: true });
401
+ const mapping = {};
402
+ results.forEach((res, i) => {
403
+ if (res.status !== 'success' || !res.result)
404
+ return;
405
+ // outputs order: [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress]
406
+ const [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress] = res.result;
407
+ mapping[symbols[i]] = {
408
+ symbol: symbols[i],
409
+ underlyingAddress: underlyingAddresses[i],
410
+ aTokenAddress,
411
+ stableDebtTokenAddress,
412
+ variableDebtTokenAddress,
413
+ };
414
+ });
415
+ return mapping;
416
+ });
417
+ export const getAaveV3ReserveTokenAddresses = (provider, network, market) => __awaiter(void 0, void 0, void 0, function* () { return _getAaveV3ReserveTokenAddresses(getViemProvider(provider, network, { batch: { multicall: true } }), network, market); });
418
+ /**
419
+ * Computes a user's Aave v3 net USD balance (supplied collateral - borrowed debt) at a historical block,
420
+ * without touching the AaveV3View contract. Pass `reserveTokens` (from getAaveV3ReserveTokenAddresses)
421
+ * to avoid refetching token addresses for every point.
422
+ */
423
+ export const _getAaveV3HistoricalBalance = (provider, network, market, address, block, reserveTokens) => __awaiter(void 0, void 0, void 0, function* () {
424
+ const empty = {
425
+ block, suppliedUsd: '0', borrowedUsd: '0', netUsd: '0',
426
+ };
427
+ if (!address)
428
+ return empty;
429
+ const tokens = reserveTokens || (yield _getAaveV3ReserveTokenAddresses(provider, network, market));
430
+ const entries = Object.values(tokens);
431
+ if (!entries.length)
432
+ return empty;
433
+ const erc20Abi = getConfigContractAbi('Erc20');
434
+ const blockNumber = BigInt(block);
435
+ // supply = aToken.balanceOf, debt = variableDebtToken.balanceOf + stableDebtToken.balanceOf, all at the block.
436
+ const balanceContracts = entries.flatMap((e) => ([
437
+ {
438
+ address: e.aTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
439
+ },
440
+ {
441
+ address: e.variableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
442
+ },
443
+ {
444
+ address: e.stableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
445
+ },
446
+ ]));
447
+ // @ts-ignore market.provider is a valid config key at runtime
448
+ const providerAbi = getConfigContractAbi(market.provider, network);
449
+ const [balanceResults, oracleAddress] = yield Promise.all([
450
+ // @ts-ignore
451
+ provider.multicall({ contracts: balanceContracts, allowFailure: true, blockNumber }),
452
+ // resolve the oracle that was active at the block (Aave can rotate the oracle over time).
453
+ // No catch: if this read fails (bad archive node, rate limit, ...) we must surface the failure
454
+ // rather than silently returning $0 — the caller renders a gap instead of a misleading zero.
455
+ // @ts-ignore readContract exists on the public client returned by getViemProvider
456
+ provider.readContract({
457
+ address: market.providerAddress,
458
+ abi: providerAbi,
459
+ functionName: 'getPriceOracle',
460
+ blockNumber,
461
+ }),
462
+ ]);
463
+ // A totally failed balance read must not masquerade as a real $0 balance — throw so the caller
464
+ // can distinguish "fetch failed" (gap) from "position was empty" (genuine 0).
465
+ const anyBalanceRead = balanceResults.some((r) => (r === null || r === void 0 ? void 0 : r.status) === 'success');
466
+ if (!anyBalanceRead)
467
+ throw new Error(`AaveV3 historical balance: all balance reads failed at block ${block}`);
468
+ if (!oracleAddress)
469
+ throw new Error(`AaveV3 historical balance: oracle unavailable at block ${block}`);
470
+ const activeAssets = entries.map((e, i) => {
471
+ const supplyRes = balanceResults[i * 3];
472
+ const varDebtRes = balanceResults[(i * 3) + 1];
473
+ const stableDebtRes = balanceResults[(i * 3) + 2];
474
+ const supplied = (supplyRes === null || supplyRes === void 0 ? void 0 : supplyRes.status) === 'success' ? supplyRes.result.toString() : '0';
475
+ const varDebt = (varDebtRes === null || varDebtRes === void 0 ? void 0 : varDebtRes.status) === 'success' ? varDebtRes.result.toString() : '0';
476
+ const stableDebt = (stableDebtRes === null || stableDebtRes === void 0 ? void 0 : stableDebtRes.status) === 'success' ? stableDebtRes.result.toString() : '0';
477
+ const debt = new Dec(varDebt).add(stableDebt).toString();
478
+ return Object.assign(Object.assign({}, e), { supplied, debt });
479
+ }).filter((a) => a.supplied !== '0' || a.debt !== '0');
480
+ if (!activeAssets.length)
481
+ return empty;
482
+ const priceContracts = activeAssets.map((a) => ({
483
+ address: oracleAddress,
484
+ abi: AAVE_ORACLE_ABI,
485
+ functionName: 'getAssetPrice',
486
+ args: [a.underlyingAddress],
487
+ }));
488
+ // @ts-ignore
489
+ const priceResults = yield provider.multicall({ contracts: priceContracts, allowFailure: true, blockNumber });
490
+ let suppliedUsd = new Dec(0);
491
+ let borrowedUsd = new Dec(0);
492
+ activeAssets.forEach((a, i) => {
493
+ const priceRes = priceResults[i];
494
+ if ((priceRes === null || priceRes === void 0 ? void 0 : priceRes.status) !== 'success')
495
+ return;
496
+ const priceUsd = new Dec(priceRes.result.toString()).div(1e8); // Aave v3 base currency is USD with 8 decimals
497
+ if (a.supplied !== '0')
498
+ suppliedUsd = suppliedUsd.add(new Dec(assetAmountInEth(a.supplied, a.symbol)).mul(priceUsd));
499
+ if (a.debt !== '0')
500
+ borrowedUsd = borrowedUsd.add(new Dec(assetAmountInEth(a.debt, a.symbol)).mul(priceUsd));
501
+ });
502
+ return {
503
+ block,
504
+ suppliedUsd: suppliedUsd.toString(),
505
+ borrowedUsd: borrowedUsd.toString(),
506
+ netUsd: suppliedUsd.minus(borrowedUsd).toString(),
507
+ };
508
+ });
509
+ export const getAaveV3HistoricalBalance = (provider, network, market, address, block, reserveTokens) => __awaiter(void 0, void 0, void 0, function* () { return _getAaveV3HistoricalBalance(getViemProvider(provider, network, { batch: { multicall: true } }), network, market, address, block, reserveTokens); });
365
510
  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') {
366
511
  const { selectedMarket: market, assetsData, eModeCategoriesData, } = extractedState;
367
512
  let payload = Object.assign(Object.assign({}, EMPTY_AAVE_DATA), { lastUpdated: Date.now() });
@@ -7,12 +7,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
+ import Dec from 'decimal.js';
10
11
  import { aprToApy } from '../moneymarket';
11
12
  import { fetchAllMerklOpportunities } from '../services/merkl';
12
13
  import { IncentiveKind, IncentiveSide, OpportunityAction, OpportunityStatus, } from '../types';
13
14
  /**
14
15
  * Merkl tags Aave V4 reward campaigns by scope via the `type` field:
15
- * - AAVE_V4_HUB_SUPPLY / AAVE_V4_HUB_BORROW reward tied to a hub asset
16
+ * - AAVE_V4_HUB_SUPPLY / AAVE_V4_HUB_BORROW / AAVE_V4_HUB_NET_LENDING hub asset
16
17
  * - AAVE_V4_SPOKE_SUPPLY / AAVE_V4_SPOKE_BORROW → reward tied to a spoke reserve
17
18
  * Embedded campaign params provide the exact on-chain identifiers. Token addresses cannot safely
18
19
  * identify Aave V4 rewards because one spoke can expose the same underlying from multiple hubs.
@@ -39,9 +40,19 @@ export const buildAaveV4MerklRewardMap = (opportunities, chainId) => {
39
40
  .filter((o) => o.status === OpportunityStatus.LIVE)
40
41
  .filter((o) => typeof o.type === 'string' && o.type.startsWith('AAVE_V4_'))
41
42
  .forEach((o) => {
42
- var _a, _b;
43
+ var _a, _b, _c;
43
44
  const side = o.action === OpportunityAction.BORROW ? IncentiveSide.Borrow : IncentiveSide.Supply;
44
- const incentive = buildIncentive(o);
45
+ const now = Date.now() / 1000;
46
+ const methods = (_a = o.campaigns) === null || _a === void 0 ? void 0 : _a.filter((c) => (c.startTimestamp === undefined || c.startTimestamp <= now)
47
+ && (c.endTimestamp === undefined || c.endTimestamp > now)).map((c) => { var _a, _b; return (_b = (_a = c.params) === null || _a === void 0 ? void 0 : _a.distributionMethodParameters) === null || _b === void 0 ? void 0 : _b.distributionMethod; });
48
+ // An opportunity-level APR cannot be split between simultaneous net and additive campaigns.
49
+ if (side === IncentiveSide.Supply && (methods === null || methods === void 0 ? void 0 : methods.includes('AAVE_V4_NET_APR'))
50
+ && methods.some((method) => !!method && method !== 'AAVE_V4_NET_APR'))
51
+ return;
52
+ const incentive = Object.assign(Object.assign({}, buildIncentive(o)), {
53
+ // Missing methods keep the existing target-yield behavior.
54
+ isAdditiveReward: side === IncentiveSide.Supply && !!(methods === null || methods === void 0 ? void 0 : methods.length)
55
+ && methods.every((method) => !!method && method !== 'AAVE_V4_NET_APR') });
45
56
  // one opportunity can span several campaigns (e.g. renewed periods), so campaign identity is
46
57
  // collected per scope key before the reward entries are written
47
58
  const idsByKey = {};
@@ -54,11 +65,13 @@ export const buildAaveV4MerklRewardMap = (opportunities, chainId) => {
54
65
  idsByKey[key].parentCampaignIds.add(campaign.parentCampaignId);
55
66
  };
56
67
  if (o.type.includes('HUB')) {
57
- (_a = o.campaigns) === null || _a === void 0 ? void 0 : _a.forEach((c) => {
58
- var _a;
59
- if (!((_a = c.params) === null || _a === void 0 ? void 0 : _a.hubAddress) || c.params.assetId === undefined || c.params.assetId === null)
68
+ (_b = o.campaigns) === null || _b === void 0 ? void 0 : _b.forEach((c) => {
69
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
70
+ const hubAddress = (_b = (_a = c.params) === null || _a === void 0 ? void 0 : _a.hubAddress) !== null && _b !== void 0 ? _b : (_e = (_d = (_c = c.params) === null || _c === void 0 ? void 0 : _c.distributionMethodParameters) === null || _d === void 0 ? void 0 : _d.distributionSettings) === null || _e === void 0 ? void 0 : _e.hubAddress;
71
+ const assetId = (_g = (_f = c.params) === null || _f === void 0 ? void 0 : _f.assetId) !== null && _g !== void 0 ? _g : (_k = (_j = (_h = c.params) === null || _h === void 0 ? void 0 : _h.distributionMethodParameters) === null || _j === void 0 ? void 0 : _j.distributionSettings) === null || _k === void 0 ? void 0 : _k.assetId;
72
+ if (!hubAddress || assetId === undefined || assetId === null)
60
73
  return;
61
- collect(scopeKey(c.params.hubAddress, c.params.assetId), c);
74
+ collect(scopeKey(hubAddress, assetId), c);
62
75
  });
63
76
  Object.entries(idsByKey).forEach(([key, ids]) => {
64
77
  if (!result.hub[key])
@@ -67,7 +80,7 @@ export const buildAaveV4MerklRewardMap = (opportunities, chainId) => {
67
80
  });
68
81
  }
69
82
  else if (o.type.includes('SPOKE')) {
70
- (_b = o.campaigns) === null || _b === void 0 ? void 0 : _b.forEach((c) => {
83
+ (_c = o.campaigns) === null || _c === void 0 ? void 0 : _c.forEach((c) => {
71
84
  var _a;
72
85
  if (!((_a = c.params) === null || _a === void 0 ? void 0 : _a.spokeAddress) || c.params.reserveId === undefined || c.params.reserveId === null)
73
86
  return;
@@ -86,7 +99,7 @@ export const getAaveV4MerkleCampaigns = (chainId) => __awaiter(void 0, void 0, v
86
99
  try {
87
100
  const opportunities = yield fetchAllMerklOpportunities({
88
101
  mainProtocolId: 'aave',
89
- type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,AAVE_V4_SPOKE_SUPPLY,AAVE_V4_SPOKE_BORROW',
102
+ type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,AAVE_V4_HUB_NET_LENDING,AAVE_V4_SPOKE_SUPPLY,AAVE_V4_SPOKE_BORROW',
90
103
  status: OpportunityStatus.LIVE,
91
104
  campaigns: 'true',
92
105
  });
@@ -107,5 +120,7 @@ export const attachAaveV4MerklIncentives = (asset, spokeAddress, campaigns) => {
107
120
  const baseBorrow = asset.borrowIncentives || [];
108
121
  const spokeScoped = spokeAddress ? campaigns.spoke[scopeKey(spokeAddress, asset.reserveId)] : undefined;
109
122
  const hubScoped = asset.hub ? campaigns.hub[scopeKey(asset.hub, asset.assetId)] : undefined;
110
- return Object.assign(Object.assign({}, asset), { spokeSupplyIncentives: ((_a = spokeScoped === null || spokeScoped === void 0 ? void 0 : spokeScoped.supply) === null || _a === void 0 ? void 0 : _a.length) ? [...baseSupply, ...spokeScoped.supply] : baseSupply, spokeBorrowIncentives: ((_b = spokeScoped === null || spokeScoped === void 0 ? void 0 : spokeScoped.borrow) === null || _b === void 0 ? void 0 : _b.length) ? [...baseBorrow, ...spokeScoped.borrow] : baseBorrow, hubSupplyIncentives: ((_c = hubScoped === null || hubScoped === void 0 ? void 0 : hubScoped.supply) === null || _c === void 0 ? void 0 : _c.length) ? [...baseSupply, ...hubScoped.supply] : baseSupply, hubBorrowIncentives: ((_d = hubScoped === null || hubScoped === void 0 ? void 0 : hubScoped.borrow) === null || _d === void 0 ? void 0 : _d.length) ? [...baseBorrow, ...hubScoped.borrow] : baseBorrow });
123
+ // Net-APR campaigns top up native yield; additive campaigns pay their APR on top.
124
+ const supplyRewards = (rewards = []) => rewards.map((reward) => (Object.assign(Object.assign({}, reward), { apy: reward.isAdditiveReward ? reward.apy : Dec.max(0, new Dec(reward.apy).minus(asset.supplyRate || 0)).toString() })));
125
+ return Object.assign(Object.assign({}, asset), { spokeSupplyIncentives: ((_a = spokeScoped === null || spokeScoped === void 0 ? void 0 : spokeScoped.supply) === null || _a === void 0 ? void 0 : _a.length) ? [...baseSupply, ...supplyRewards(spokeScoped.supply)] : baseSupply, spokeBorrowIncentives: ((_b = spokeScoped === null || spokeScoped === void 0 ? void 0 : spokeScoped.borrow) === null || _b === void 0 ? void 0 : _b.length) ? [...baseBorrow, ...spokeScoped.borrow] : baseBorrow, hubSupplyIncentives: ((_c = hubScoped === null || hubScoped === void 0 ? void 0 : hubScoped.supply) === null || _c === void 0 ? void 0 : _c.length) ? [...baseSupply, ...supplyRewards(hubScoped.supply)] : baseSupply, hubBorrowIncentives: ((_d = hubScoped === null || hubScoped === void 0 ? void 0 : hubScoped.borrow) === null || _d === void 0 ? void 0 : _d.length) ? [...baseBorrow, ...hubScoped.borrow] : baseBorrow });
111
126
  };
@@ -11,6 +11,8 @@ export declare enum OpportunityStatus {
11
11
  export type MerklCampaign = {
12
12
  id: string;
13
13
  campaignId: string;
14
+ startTimestamp?: number;
15
+ endTimestamp?: number;
14
16
  /**
15
17
  * Merkl-internal `id` of the parent campaign — set on child campaigns, which re-publish a hub
16
18
  * (parent) campaign's reward scoped to a single spoke reserve. Absent on standalone campaigns,
@@ -24,6 +26,13 @@ export type MerklCampaign = {
24
26
  hubAddress?: EthAddress;
25
27
  hubAssetId?: string | number;
26
28
  assetId?: string | number;
29
+ distributionMethodParameters?: {
30
+ distributionMethod?: string;
31
+ distributionSettings?: {
32
+ hubAddress?: EthAddress;
33
+ assetId?: string | number;
34
+ };
35
+ };
27
36
  };
28
37
  };
29
38
  export type MerklOpportunity = {
@@ -102,6 +111,7 @@ export type MerkleRewardMap = Record<EthAddress, {
102
111
  export type AaveV4MerklIncentive = IncentiveData & {
103
112
  campaignIds?: string[];
104
113
  parentCampaignIds?: string[];
114
+ isAdditiveReward?: boolean;
105
115
  };
106
116
  export type AaveV4MerklScopedReward = {
107
117
  [side in IncentiveSide]?: AaveV4MerklIncentive[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defisaver/positions-sdk",
3
- "version": "2.1.156",
3
+ "version": "2.1.157-pos-history-dev",
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';
@@ -430,6 +431,195 @@ export const _getAaveV3AccountBalances = async (provider: Client, network: Netwo
430
431
 
431
432
  export const getAaveV3AccountBalances = async (provider: EthereumProvider, network: NetworkNumber, block: Blockish, addressMapping: boolean, address: EthAddress): Promise<PositionBalances> => _getAaveV3AccountBalances(getViemProvider(provider, network), network, block, addressMapping, address);
432
433
 
434
+ /**
435
+ * Historical net-balance helpers that bypass the AaveV3View contract.
436
+ *
437
+ * The View contract (and therefore `getAaveV3AccountData` / `getAaveV3AccountBalances`) can only be
438
+ * queried from its deployment block onwards, so it cannot read balances for positions older than that.
439
+ * The aTokens/debt tokens, the ProtocolDataProvider and the Aave price oracle all exist from Aave v3
440
+ * launch, so reading `balanceOf` on those tokens + the oracle price directly reaches much further back
441
+ * and costs ~1 multicall per point. Used to build a position balance-history chart.
442
+ */
443
+
444
+ // Minimal Aave price oracle ABI (getAssetPrice returns the asset price in the market base currency).
445
+ const AAVE_ORACLE_ABI = [
446
+ {
447
+ inputs: [{ internalType: 'address', name: 'asset', type: 'address' }],
448
+ name: 'getAssetPrice',
449
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
450
+ stateMutability: 'view',
451
+ type: 'function',
452
+ },
453
+ ] as const;
454
+
455
+ export interface AaveV3ReserveTokenAddresses {
456
+ [symbol: string]: {
457
+ symbol: string,
458
+ underlyingAddress: EthAddress,
459
+ aTokenAddress: EthAddress,
460
+ stableDebtTokenAddress: EthAddress,
461
+ variableDebtTokenAddress: EthAddress,
462
+ };
463
+ }
464
+
465
+ export interface AaveV3HistoricalBalance {
466
+ block: number,
467
+ suppliedUsd: string,
468
+ borrowedUsd: string,
469
+ netUsd: string,
470
+ }
471
+
472
+ /**
473
+ * Fetches the aToken / stable-debt / variable-debt token addresses for every asset in the market.
474
+ * These are effectively immutable per reserve, so fetch once and reuse across all history points.
475
+ */
476
+ export const _getAaveV3ReserveTokenAddresses = async (provider: Client, network: NetworkNumber, market: AaveMarketInfo): Promise<AaveV3ReserveTokenAddresses> => {
477
+ const symbols = market.assets;
478
+ const underlyingAddresses = symbols.map((a: string) => getAssetInfo(getWrappedNativeAssetFromUnwrapped(a), network).address as EthAddress);
479
+ // @ts-ignore market.protocolData is a valid config key at runtime
480
+ const dataProviderAbi = getConfigContractAbi(market.protocolData, network);
481
+
482
+ const contracts = underlyingAddresses.map((underlying) => ({
483
+ address: market.protocolDataAddress as EthAddress,
484
+ abi: dataProviderAbi,
485
+ functionName: 'getReserveTokensAddresses',
486
+ args: [underlying],
487
+ }));
488
+
489
+ // @ts-ignore
490
+ const results = await provider.multicall({ contracts, allowFailure: true });
491
+
492
+ const mapping: AaveV3ReserveTokenAddresses = {};
493
+ results.forEach((res: any, i: number) => {
494
+ if (res.status !== 'success' || !res.result) return;
495
+ // outputs order: [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress]
496
+ const [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress] = res.result as [EthAddress, EthAddress, EthAddress];
497
+ mapping[symbols[i]] = {
498
+ symbol: symbols[i],
499
+ underlyingAddress: underlyingAddresses[i],
500
+ aTokenAddress,
501
+ stableDebtTokenAddress,
502
+ variableDebtTokenAddress,
503
+ };
504
+ });
505
+
506
+ return mapping;
507
+ };
508
+
509
+ export const getAaveV3ReserveTokenAddresses = async (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo): Promise<AaveV3ReserveTokenAddresses> => _getAaveV3ReserveTokenAddresses(getViemProvider(provider, network, { batch: { multicall: true } }), network, market);
510
+
511
+ /**
512
+ * Computes a user's Aave v3 net USD balance (supplied collateral - borrowed debt) at a historical block,
513
+ * without touching the AaveV3View contract. Pass `reserveTokens` (from getAaveV3ReserveTokenAddresses)
514
+ * to avoid refetching token addresses for every point.
515
+ */
516
+ export const _getAaveV3HistoricalBalance = async (
517
+ provider: Client,
518
+ network: NetworkNumber,
519
+ market: AaveMarketInfo,
520
+ address: EthAddress,
521
+ block: number,
522
+ reserveTokens?: AaveV3ReserveTokenAddresses,
523
+ ): Promise<AaveV3HistoricalBalance> => {
524
+ const empty: AaveV3HistoricalBalance = {
525
+ block, suppliedUsd: '0', borrowedUsd: '0', netUsd: '0',
526
+ };
527
+ if (!address) return empty;
528
+
529
+ const tokens = reserveTokens || await _getAaveV3ReserveTokenAddresses(provider, network, market);
530
+ const entries = Object.values(tokens);
531
+ if (!entries.length) return empty;
532
+
533
+ const erc20Abi = getConfigContractAbi('Erc20');
534
+ const blockNumber = BigInt(block);
535
+
536
+ // supply = aToken.balanceOf, debt = variableDebtToken.balanceOf + stableDebtToken.balanceOf, all at the block.
537
+ const balanceContracts = entries.flatMap((e) => ([
538
+ {
539
+ address: e.aTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
540
+ },
541
+ {
542
+ address: e.variableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
543
+ },
544
+ {
545
+ address: e.stableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
546
+ },
547
+ ]));
548
+
549
+ // @ts-ignore market.provider is a valid config key at runtime
550
+ const providerAbi = getConfigContractAbi(market.provider, network);
551
+
552
+ const [balanceResults, oracleAddress] = await Promise.all([
553
+ // @ts-ignore
554
+ provider.multicall({ contracts: balanceContracts, allowFailure: true, blockNumber }),
555
+ // resolve the oracle that was active at the block (Aave can rotate the oracle over time).
556
+ // No catch: if this read fails (bad archive node, rate limit, ...) we must surface the failure
557
+ // rather than silently returning $0 — the caller renders a gap instead of a misleading zero.
558
+ // @ts-ignore readContract exists on the public client returned by getViemProvider
559
+ provider.readContract({
560
+ address: market.providerAddress as EthAddress,
561
+ abi: providerAbi as any,
562
+ functionName: 'getPriceOracle',
563
+ blockNumber,
564
+ }),
565
+ ]);
566
+
567
+ // A totally failed balance read must not masquerade as a real $0 balance — throw so the caller
568
+ // can distinguish "fetch failed" (gap) from "position was empty" (genuine 0).
569
+ const anyBalanceRead = (balanceResults as any[]).some((r) => r?.status === 'success');
570
+ if (!anyBalanceRead) throw new Error(`AaveV3 historical balance: all balance reads failed at block ${block}`);
571
+ if (!oracleAddress) throw new Error(`AaveV3 historical balance: oracle unavailable at block ${block}`);
572
+
573
+ const activeAssets = entries.map((e, i) => {
574
+ const supplyRes = balanceResults[i * 3];
575
+ const varDebtRes = balanceResults[(i * 3) + 1];
576
+ const stableDebtRes = balanceResults[(i * 3) + 2];
577
+ const supplied = supplyRes?.status === 'success' ? (supplyRes.result as bigint).toString() : '0';
578
+ const varDebt = varDebtRes?.status === 'success' ? (varDebtRes.result as bigint).toString() : '0';
579
+ const stableDebt = stableDebtRes?.status === 'success' ? (stableDebtRes.result as bigint).toString() : '0';
580
+ const debt = new Dec(varDebt).add(stableDebt).toString();
581
+ return { ...e, supplied, debt };
582
+ }).filter((a) => a.supplied !== '0' || a.debt !== '0');
583
+
584
+ if (!activeAssets.length) return empty;
585
+
586
+ const priceContracts = activeAssets.map((a) => ({
587
+ address: oracleAddress as EthAddress,
588
+ abi: AAVE_ORACLE_ABI,
589
+ functionName: 'getAssetPrice',
590
+ args: [a.underlyingAddress],
591
+ }));
592
+
593
+ // @ts-ignore
594
+ const priceResults = await provider.multicall({ contracts: priceContracts, allowFailure: true, blockNumber });
595
+
596
+ let suppliedUsd = new Dec(0);
597
+ let borrowedUsd = new Dec(0);
598
+ activeAssets.forEach((a, i) => {
599
+ const priceRes = priceResults[i];
600
+ if (priceRes?.status !== 'success') return;
601
+ const priceUsd = new Dec((priceRes.result as bigint).toString()).div(1e8); // Aave v3 base currency is USD with 8 decimals
602
+ if (a.supplied !== '0') suppliedUsd = suppliedUsd.add(new Dec(assetAmountInEth(a.supplied, a.symbol)).mul(priceUsd));
603
+ if (a.debt !== '0') borrowedUsd = borrowedUsd.add(new Dec(assetAmountInEth(a.debt, a.symbol)).mul(priceUsd));
604
+ });
605
+
606
+ return {
607
+ block,
608
+ suppliedUsd: suppliedUsd.toString(),
609
+ borrowedUsd: borrowedUsd.toString(),
610
+ netUsd: suppliedUsd.minus(borrowedUsd).toString(),
611
+ };
612
+ };
613
+
614
+ export const getAaveV3HistoricalBalance = async (
615
+ provider: EthereumProvider,
616
+ network: NetworkNumber,
617
+ market: AaveMarketInfo,
618
+ address: EthAddress,
619
+ block: number,
620
+ reserveTokens?: AaveV3ReserveTokenAddresses,
621
+ ): Promise<AaveV3HistoricalBalance> => _getAaveV3HistoricalBalance(getViemProvider(provider, network, { batch: { multicall: true } }), network, market, address, block, reserveTokens);
622
+
433
623
  export const _getAaveV3AccountData = async (provider: Client, network: NetworkNumber, address: EthAddress, extractedState: any, blockNumber: 'latest' | number = 'latest'): Promise<AaveV3PositionData> => {
434
624
  const {
435
625
  selectedMarket: market, assetsData, eModeCategoriesData,
@@ -1,7 +1,9 @@
1
+ import Dec from 'decimal.js';
1
2
  import { aprToApy } from '../moneymarket';
2
3
  import { fetchAllMerklOpportunities } from '../services/merkl';
3
4
  import {
4
5
  AaveV4MerklRewardMap,
6
+ AaveV4MerklIncentive,
5
7
  AaveV4ReserveAssetData,
6
8
  IncentiveData,
7
9
  IncentiveKind,
@@ -15,7 +17,7 @@ import {
15
17
 
16
18
  /**
17
19
  * Merkl tags Aave V4 reward campaigns by scope via the `type` field:
18
- * - AAVE_V4_HUB_SUPPLY / AAVE_V4_HUB_BORROW reward tied to a hub asset
20
+ * - AAVE_V4_HUB_SUPPLY / AAVE_V4_HUB_BORROW / AAVE_V4_HUB_NET_LENDING hub asset
19
21
  * - AAVE_V4_SPOKE_SUPPLY / AAVE_V4_SPOKE_BORROW → reward tied to a spoke reserve
20
22
  * Embedded campaign params provide the exact on-chain identifiers. Token addresses cannot safely
21
23
  * identify Aave V4 rewards because one spoke can expose the same underlying from multiple hubs.
@@ -46,7 +48,20 @@ export const buildAaveV4MerklRewardMap = (opportunities: MerklOpportunity[], cha
46
48
  .filter((o) => typeof o.type === 'string' && o.type.startsWith('AAVE_V4_'))
47
49
  .forEach((o) => {
48
50
  const side = o.action === OpportunityAction.BORROW ? IncentiveSide.Borrow : IncentiveSide.Supply;
49
- const incentive = buildIncentive(o);
51
+ const now = Date.now() / 1000;
52
+ const methods = o.campaigns
53
+ ?.filter((c) => (c.startTimestamp === undefined || c.startTimestamp <= now)
54
+ && (c.endTimestamp === undefined || c.endTimestamp > now))
55
+ .map((c) => c.params?.distributionMethodParameters?.distributionMethod);
56
+ // An opportunity-level APR cannot be split between simultaneous net and additive campaigns.
57
+ if (side === IncentiveSide.Supply && methods?.includes('AAVE_V4_NET_APR')
58
+ && methods.some((method) => !!method && method !== 'AAVE_V4_NET_APR')) return;
59
+ const incentive = {
60
+ ...buildIncentive(o),
61
+ // Missing methods keep the existing target-yield behavior.
62
+ isAdditiveReward: side === IncentiveSide.Supply && !!methods?.length
63
+ && methods.every((method) => !!method && method !== 'AAVE_V4_NET_APR'),
64
+ };
50
65
  // one opportunity can span several campaigns (e.g. renewed periods), so campaign identity is
51
66
  // collected per scope key before the reward entries are written
52
67
  const idsByKey: Record<string, { campaignIds: Set<string>, parentCampaignIds: Set<string> }> = {};
@@ -58,8 +73,10 @@ export const buildAaveV4MerklRewardMap = (opportunities: MerklOpportunity[], cha
58
73
 
59
74
  if (o.type.includes('HUB')) {
60
75
  o.campaigns?.forEach((c) => {
61
- if (!c.params?.hubAddress || c.params.assetId === undefined || c.params.assetId === null) return;
62
- collect(scopeKey(c.params.hubAddress, c.params.assetId), c);
76
+ const hubAddress = c.params?.hubAddress ?? c.params?.distributionMethodParameters?.distributionSettings?.hubAddress;
77
+ const assetId = c.params?.assetId ?? c.params?.distributionMethodParameters?.distributionSettings?.assetId;
78
+ if (!hubAddress || assetId === undefined || assetId === null) return;
79
+ collect(scopeKey(hubAddress, assetId), c);
63
80
  });
64
81
  Object.entries(idsByKey).forEach(([key, ids]) => {
65
82
  if (!result.hub[key]) result.hub[key] = {};
@@ -84,7 +101,7 @@ export const getAaveV4MerkleCampaigns = async (chainId: NetworkNumber): Promise<
84
101
  try {
85
102
  const opportunities = await fetchAllMerklOpportunities({
86
103
  mainProtocolId: 'aave',
87
- type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,AAVE_V4_SPOKE_SUPPLY,AAVE_V4_SPOKE_BORROW',
104
+ type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,AAVE_V4_HUB_NET_LENDING,AAVE_V4_SPOKE_SUPPLY,AAVE_V4_SPOKE_BORROW',
88
105
  status: OpportunityStatus.LIVE,
89
106
  campaigns: 'true',
90
107
  });
@@ -105,12 +122,17 @@ export const attachAaveV4MerklIncentives = (asset: AaveV4ReserveAssetData, spoke
105
122
 
106
123
  const spokeScoped = spokeAddress ? campaigns.spoke[scopeKey(spokeAddress, asset.reserveId)] : undefined;
107
124
  const hubScoped = asset.hub ? campaigns.hub[scopeKey(asset.hub, asset.assetId)] : undefined;
125
+ // Net-APR campaigns top up native yield; additive campaigns pay their APR on top.
126
+ const supplyRewards = (rewards: AaveV4MerklIncentive[] = []) => rewards.map((reward) => ({
127
+ ...reward,
128
+ apy: reward.isAdditiveReward ? reward.apy : Dec.max(0, new Dec(reward.apy).minus(asset.supplyRate || 0)).toString(),
129
+ }));
108
130
 
109
131
  return {
110
132
  ...asset,
111
- spokeSupplyIncentives: spokeScoped?.supply?.length ? [...baseSupply, ...spokeScoped.supply] : baseSupply,
133
+ spokeSupplyIncentives: spokeScoped?.supply?.length ? [...baseSupply, ...supplyRewards(spokeScoped.supply)] : baseSupply,
112
134
  spokeBorrowIncentives: spokeScoped?.borrow?.length ? [...baseBorrow, ...spokeScoped.borrow] : baseBorrow,
113
- hubSupplyIncentives: hubScoped?.supply?.length ? [...baseSupply, ...hubScoped.supply] : baseSupply,
135
+ hubSupplyIncentives: hubScoped?.supply?.length ? [...baseSupply, ...supplyRewards(hubScoped.supply)] : baseSupply,
114
136
  hubBorrowIncentives: hubScoped?.borrow?.length ? [...baseBorrow, ...hubScoped.borrow] : baseBorrow,
115
137
  };
116
138
  };
@@ -14,6 +14,8 @@ export enum OpportunityStatus {
14
14
  export type MerklCampaign = {
15
15
  id: string;
16
16
  campaignId: string;
17
+ startTimestamp?: number;
18
+ endTimestamp?: number;
17
19
  /**
18
20
  * Merkl-internal `id` of the parent campaign — set on child campaigns, which re-publish a hub
19
21
  * (parent) campaign's reward scoped to a single spoke reserve. Absent on standalone campaigns,
@@ -27,6 +29,13 @@ export type MerklCampaign = {
27
29
  hubAddress?: EthAddress;
28
30
  hubAssetId?: string | number;
29
31
  assetId?: string | number;
32
+ distributionMethodParameters?: {
33
+ distributionMethod?: string;
34
+ distributionSettings?: {
35
+ hubAddress?: EthAddress;
36
+ assetId?: string | number;
37
+ };
38
+ };
30
39
  };
31
40
  };
32
41
 
@@ -100,6 +109,7 @@ export type MerkleRewardMap = Record<EthAddress, { supply?: MerkleRewardInfo; bo
100
109
  export type AaveV4MerklIncentive = IncentiveData & {
101
110
  campaignIds?: string[];
102
111
  parentCampaignIds?: string[];
112
+ isAdditiveReward?: boolean;
103
113
  };
104
114
 
105
115
  export type AaveV4MerklScopedReward = { [side in IncentiveSide]?: AaveV4MerklIncentive[] };