@defisaver/positions-sdk 2.1.101 → 2.1.102-aave-v4-merkl-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.
@@ -1,6 +1,7 @@
1
1
  import { Client } from 'viem';
2
2
  import { AaveV4AccountData, AaveV4SpokeData, AaveV4SpokeInfo, EthAddress, EthereumProvider, NetworkNumber } from '../types';
3
3
  export * as lend from './lend';
4
+ export { getAaveV4MerkleCampaigns } from './merkl';
4
5
  export declare function _getAaveV4SpokeData(provider: Client, network: NetworkNumber, market: AaveV4SpokeInfo, blockNumber?: 'latest' | number): Promise<AaveV4SpokeData>;
5
6
  export declare function getAaveV4SpokeData(provider: EthereumProvider, network: NetworkNumber, spoke: AaveV4SpokeInfo, blockNumber?: 'latest' | number): Promise<AaveV4SpokeData>;
6
7
  export declare function _getAaveV4AccountData(provider: Client, network: NetworkNumber, spokeData: AaveV4SpokeData, address: EthAddress, blockNumber?: 'latest' | number): Promise<AaveV4AccountData>;
@@ -45,7 +45,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
45
45
  return (mod && mod.__esModule) ? mod : { "default": mod };
46
46
  };
47
47
  Object.defineProperty(exports, "__esModule", { value: true });
48
- exports.lend = void 0;
48
+ exports.getAaveV4MerkleCampaigns = exports.lend = void 0;
49
49
  exports._getAaveV4SpokeData = _getAaveV4SpokeData;
50
50
  exports.getAaveV4SpokeData = getAaveV4SpokeData;
51
51
  exports._getAaveV4AccountData = _getAaveV4AccountData;
@@ -61,7 +61,10 @@ const utils_1 = require("../services/utils");
61
61
  const aaveV4Helpers_1 = require("../helpers/aaveV4Helpers");
62
62
  const aaveV4_1 = require("../markets/aaveV4");
63
63
  const moneymarket_1 = require("../moneymarket");
64
+ const merkl_1 = require("./merkl");
64
65
  exports.lend = __importStar(require("./lend"));
66
+ var merkl_2 = require("./merkl");
67
+ Object.defineProperty(exports, "getAaveV4MerkleCampaigns", { enumerable: true, get: function () { return merkl_2.getAaveV4MerkleCampaigns; } });
65
68
  const fetchHubData = (viewContract, hubAddress) => __awaiter(void 0, void 0, void 0, function* () {
66
69
  const hubData = yield viewContract.read.getHubAllAssetsData([hubAddress]);
67
70
  return {
@@ -173,6 +176,7 @@ function _getAaveV4SpokeData(provider_1, network_1, market_1) {
173
176
  return __awaiter(this, arguments, void 0, function* (provider, network, market, blockNumber = 'latest') {
174
177
  const viewContract = (0, contracts_1.AaveV4ViewContractViem)(provider, network, blockNumber);
175
178
  const hubsData = {};
179
+ const merklCampaignsPromise = (0, merkl_1.getAaveV4MerkleCampaigns)(network);
176
180
  const [spokeData] = yield Promise.all([
177
181
  viewContract.read.getSpokeDataFull([market.address]),
178
182
  ...market.hubs.map((hubAddress) => __awaiter(this, void 0, void 0, function* () {
@@ -180,8 +184,10 @@ function _getAaveV4SpokeData(provider_1, network_1, market_1) {
180
184
  })),
181
185
  ]);
182
186
  const reserveAssetsArray = yield Promise.all(spokeData[1].map((reserveAssetOnChain, index) => __awaiter(this, void 0, void 0, function* () { return formatReserveAsset(reserveAssetOnChain, hubsData[reserveAssetOnChain.hub].assets[reserveAssetOnChain.assetId], index, +spokeData[0].oracleDecimals.toString(), network); })));
187
+ const merklCampaigns = yield merklCampaignsPromise;
188
+ const enrichedAssets = reserveAssetsArray.map((asset) => (0, merkl_1.attachAaveV4MerklIncentives)(asset, market.address, merklCampaigns));
183
189
  return {
184
- assetsData: reserveAssetsArray.reduce((acc, reserveAsset) => {
190
+ assetsData: enrichedAssets.reduce((acc, reserveAsset) => {
185
191
  acc[`${reserveAsset.symbol}-${reserveAsset.reserveId}`] = reserveAsset;
186
192
  return acc;
187
193
  }, {}),
@@ -0,0 +1,8 @@
1
+ import { AaveV4MerklRewardMap, AaveV4ReserveAssetData } from '../types';
2
+ import { NetworkNumber } from '../types/common';
3
+ export declare const getAaveV4MerkleCampaigns: (chainId: NetworkNumber) => Promise<AaveV4MerklRewardMap>;
4
+ /**
5
+ * Returns a copy of the asset with scope-specific incentive arrays pre-combined with the asset's
6
+ * intrinsic (staking) incentives, so each surface can render base yield + the rewards that apply to it.
7
+ */
8
+ export declare const attachAaveV4MerklIncentives: (asset: AaveV4ReserveAssetData, spokeAddress: string, campaigns: AaveV4MerklRewardMap) => AaveV4ReserveAssetData;
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.attachAaveV4MerklIncentives = exports.getAaveV4MerkleCampaigns = void 0;
13
+ const moneymarket_1 = require("../moneymarket");
14
+ const utils_1 = require("../services/utils");
15
+ const types_1 = require("../types");
16
+ /**
17
+ * 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 (matched per underlying token)
19
+ * - AAVE_V4_SPOKE_SUPPLY / AAVE_V4_SPOKE_BORROW → reward tied to a spoke (matched per spoke contract + underlying)
20
+ * Hub campaigns identify the underlying via `tokens[0]`; spoke campaigns identify the spoke via `explorerAddress`.
21
+ */
22
+ const spokeKey = (spokeAddress, underlying) => `${spokeAddress.toLowerCase()}_${underlying.toLowerCase()}`;
23
+ const buildIncentive = (opportunity) => {
24
+ var _a, _b, _c, _d, _e;
25
+ const rewardToken = (_c = (_b = (_a = opportunity.rewardsRecord) === null || _a === void 0 ? void 0 : _a.breakdowns) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.token;
26
+ const token = (rewardToken === null || rewardToken === void 0 ? void 0 : rewardToken.symbol) || ((_e = (_d = opportunity.tokens) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.symbol) || '';
27
+ return {
28
+ apy: (0, moneymarket_1.aprToApy)(opportunity.apr),
29
+ token,
30
+ incentiveKind: types_1.IncentiveKind.Reward,
31
+ description: `Eligible for ${token} rewards through Merkl.`,
32
+ };
33
+ };
34
+ const getAaveV4MerkleCampaigns = (chainId) => __awaiter(void 0, void 0, void 0, function* () {
35
+ const result = { hub: {}, spoke: {} };
36
+ try {
37
+ const res = yield fetch('https://api-merkl.angle.money/v4/opportunities?mainProtocolId=aave', {
38
+ signal: AbortSignal.timeout(utils_1.DEFAULT_TIMEOUT),
39
+ });
40
+ if (!res.ok)
41
+ throw new Error('Failed to fetch Aave V4 Merkle campaigns');
42
+ const opportunities = yield res.json();
43
+ opportunities
44
+ .filter((o) => o.chainId === chainId)
45
+ .filter((o) => o.status === types_1.OpportunityStatus.LIVE)
46
+ .filter((o) => typeof o.type === 'string' && o.type.startsWith('AAVE_V4_'))
47
+ .forEach((o) => {
48
+ var _a, _b, _c, _d;
49
+ const underlying = (_c = (_b = (_a = o.tokens) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.address) === null || _c === void 0 ? void 0 : _c.toLowerCase();
50
+ if (!underlying)
51
+ return;
52
+ const side = o.action === types_1.OpportunityAction.BORROW ? 'borrow' : 'supply';
53
+ const incentive = buildIncentive(o);
54
+ if (o.type.includes('HUB')) {
55
+ if (!result.hub[underlying])
56
+ result.hub[underlying] = {};
57
+ result.hub[underlying][side] = incentive;
58
+ }
59
+ else if (o.type.includes('SPOKE')) {
60
+ const spokeAddress = (_d = o.explorerAddress) === null || _d === void 0 ? void 0 : _d.toLowerCase();
61
+ if (!spokeAddress)
62
+ return;
63
+ const key = spokeKey(spokeAddress, underlying);
64
+ if (!result.spoke[key])
65
+ result.spoke[key] = {};
66
+ result.spoke[key][side] = incentive;
67
+ }
68
+ });
69
+ return result;
70
+ }
71
+ catch (e) {
72
+ console.error('Failed to fetch Aave V4 Merkle campaigns', e);
73
+ return result;
74
+ }
75
+ });
76
+ exports.getAaveV4MerkleCampaigns = getAaveV4MerkleCampaigns;
77
+ /**
78
+ * Returns a copy of the asset with scope-specific incentive arrays pre-combined with the asset's
79
+ * intrinsic (staking) incentives, so each surface can render base yield + the rewards that apply to it.
80
+ */
81
+ const attachAaveV4MerklIncentives = (asset, spokeAddress, campaigns) => {
82
+ var _a;
83
+ const underlying = (_a = asset.underlying) === null || _a === void 0 ? void 0 : _a.toLowerCase();
84
+ const baseSupply = asset.supplyIncentives || [];
85
+ const baseBorrow = asset.borrowIncentives || [];
86
+ const spokeScoped = (spokeAddress && underlying) ? campaigns.spoke[spokeKey(spokeAddress, underlying)] : undefined;
87
+ const hubScoped = underlying ? campaigns.hub[underlying] : undefined;
88
+ return Object.assign(Object.assign({}, asset), { spokeSupplyIncentives: (spokeScoped === null || spokeScoped === void 0 ? void 0 : spokeScoped.supply) ? [...baseSupply, spokeScoped.supply] : baseSupply, spokeBorrowIncentives: (spokeScoped === null || spokeScoped === void 0 ? void 0 : spokeScoped.borrow) ? [...baseBorrow, spokeScoped.borrow] : baseBorrow, hubSupplyIncentives: (hubScoped === null || hubScoped === void 0 ? void 0 : hubScoped.supply) ? [...baseSupply, hubScoped.supply] : baseSupply, hubBorrowIncentives: (hubScoped === null || hubScoped === void 0 ? void 0 : hubScoped.borrow) ? [...baseBorrow, hubScoped.borrow] : baseBorrow });
89
+ };
90
+ exports.attachAaveV4MerklIncentives = attachAaveV4MerklIncentives;
@@ -21,7 +21,7 @@ const utils_1 = require("../services/utils");
21
21
  const fetchEthenaAirdropReward = (address) => __awaiter(void 0, void 0, void 0, function* () {
22
22
  try {
23
23
  const checksumAddress = (0, viem_1.getAddress)(address);
24
- const response = yield fetch(`https://airdrop-data-ethena-s4.s3.us-west-2.amazonaws.com/${checksumAddress}/0x3d99219fbd49ace3f48d6ca1340e505ec1bdf27d1f8d0e15ec9f286cc9215fcd-${checksumAddress}.json`);
24
+ const response = yield fetch(`https://d1o76ps6187jke.cloudfront.net/${checksumAddress}/0xb92954d91aa2793b3718414d8df2413d5bd648955dec4a75471e86ded263d585-${checksumAddress}.json`);
25
25
  if (!response.ok) {
26
26
  if (response.status === 403) {
27
27
  // This is also okay, means that there are no rewards for the address
@@ -100,6 +100,16 @@ export interface AaveV4ReserveAssetData {
100
100
  borrowRate: string;
101
101
  supplyIncentives: IncentiveData[];
102
102
  borrowIncentives: IncentiveData[];
103
+ /**
104
+ * Intrinsic incentives pre-combined with scope-specific Merkl rewards, ready to render per surface:
105
+ * - spoke* → spoke supply/borrow (Create page + dashboard market table)
106
+ * - hub* → hub supply/borrow (Lend page)
107
+ * `supplyIncentives`/`borrowIncentives` above remain intrinsic-only so the net-APY calc is unaffected.
108
+ */
109
+ spokeSupplyIncentives?: IncentiveData[];
110
+ spokeBorrowIncentives?: IncentiveData[];
111
+ hubSupplyIncentives?: IncentiveData[];
112
+ hubBorrowIncentives?: IncentiveData[];
103
113
  canBeBorrowed: boolean;
104
114
  canBeSupplied: boolean;
105
115
  canBeWithdrawn: boolean;
@@ -1,4 +1,4 @@
1
- import { EthAddress } from './common';
1
+ import { EthAddress, IncentiveData } from './common';
2
2
  export declare enum OpportunityAction {
3
3
  LEND = "LEND",
4
4
  BORROW = "BORROW"
@@ -73,3 +73,16 @@ export type MerkleRewardMap = Record<EthAddress, {
73
73
  supply?: MerkleRewardInfo;
74
74
  borrow?: MerkleRewardInfo;
75
75
  }>;
76
+ export type AaveV4MerklScopedReward = {
77
+ supply?: IncentiveData;
78
+ borrow?: IncentiveData;
79
+ };
80
+ /**
81
+ * Aave V4 Merkl reward campaigns split by scope:
82
+ * - `hub`: keyed by underlying token address (lowercase) — rewards for supplying to a hub
83
+ * - `spoke`: keyed by `${spokeAddress}_${underlyingAddress}` (both lowercase) — rewards for supplying/borrowing on a spoke
84
+ */
85
+ export type AaveV4MerklRewardMap = {
86
+ hub: Record<string, AaveV4MerklScopedReward>;
87
+ spoke: Record<string, AaveV4MerklScopedReward>;
88
+ };
@@ -1,6 +1,7 @@
1
1
  import { Client } from 'viem';
2
2
  import { AaveV4AccountData, AaveV4SpokeData, AaveV4SpokeInfo, EthAddress, EthereumProvider, NetworkNumber } from '../types';
3
3
  export * as lend from './lend';
4
+ export { getAaveV4MerkleCampaigns } from './merkl';
4
5
  export declare function _getAaveV4SpokeData(provider: Client, network: NetworkNumber, market: AaveV4SpokeInfo, blockNumber?: 'latest' | number): Promise<AaveV4SpokeData>;
5
6
  export declare function getAaveV4SpokeData(provider: EthereumProvider, network: NetworkNumber, spoke: AaveV4SpokeInfo, blockNumber?: 'latest' | number): Promise<AaveV4SpokeData>;
6
7
  export declare function _getAaveV4AccountData(provider: Client, network: NetworkNumber, spokeData: AaveV4SpokeData, address: EthAddress, blockNumber?: 'latest' | number): Promise<AaveV4AccountData>;
@@ -17,7 +17,9 @@ import { isMaxUint, wethToEth } from '../services/utils';
17
17
  import { aaveV4GetAggregatedPositionData, calcUserRiskPremiumBps } from '../helpers/aaveV4Helpers';
18
18
  import { getAaveV4HubByAddress } from '../markets/aaveV4';
19
19
  import { aprToApy } from '../moneymarket';
20
+ import { attachAaveV4MerklIncentives, getAaveV4MerkleCampaigns } from './merkl';
20
21
  export * as lend from './lend';
22
+ export { getAaveV4MerkleCampaigns } from './merkl';
21
23
  const fetchHubData = (viewContract, hubAddress) => __awaiter(void 0, void 0, void 0, function* () {
22
24
  const hubData = yield viewContract.read.getHubAllAssetsData([hubAddress]);
23
25
  return {
@@ -129,6 +131,7 @@ export function _getAaveV4SpokeData(provider_1, network_1, market_1) {
129
131
  return __awaiter(this, arguments, void 0, function* (provider, network, market, blockNumber = 'latest') {
130
132
  const viewContract = AaveV4ViewContractViem(provider, network, blockNumber);
131
133
  const hubsData = {};
134
+ const merklCampaignsPromise = getAaveV4MerkleCampaigns(network);
132
135
  const [spokeData] = yield Promise.all([
133
136
  viewContract.read.getSpokeDataFull([market.address]),
134
137
  ...market.hubs.map((hubAddress) => __awaiter(this, void 0, void 0, function* () {
@@ -136,8 +139,10 @@ export function _getAaveV4SpokeData(provider_1, network_1, market_1) {
136
139
  })),
137
140
  ]);
138
141
  const reserveAssetsArray = yield Promise.all(spokeData[1].map((reserveAssetOnChain, index) => __awaiter(this, void 0, void 0, function* () { return formatReserveAsset(reserveAssetOnChain, hubsData[reserveAssetOnChain.hub].assets[reserveAssetOnChain.assetId], index, +spokeData[0].oracleDecimals.toString(), network); })));
142
+ const merklCampaigns = yield merklCampaignsPromise;
143
+ const enrichedAssets = reserveAssetsArray.map((asset) => attachAaveV4MerklIncentives(asset, market.address, merklCampaigns));
139
144
  return {
140
- assetsData: reserveAssetsArray.reduce((acc, reserveAsset) => {
145
+ assetsData: enrichedAssets.reduce((acc, reserveAsset) => {
141
146
  acc[`${reserveAsset.symbol}-${reserveAsset.reserveId}`] = reserveAsset;
142
147
  return acc;
143
148
  }, {}),
@@ -0,0 +1,8 @@
1
+ import { AaveV4MerklRewardMap, AaveV4ReserveAssetData } from '../types';
2
+ import { NetworkNumber } from '../types/common';
3
+ export declare const getAaveV4MerkleCampaigns: (chainId: NetworkNumber) => Promise<AaveV4MerklRewardMap>;
4
+ /**
5
+ * Returns a copy of the asset with scope-specific incentive arrays pre-combined with the asset's
6
+ * intrinsic (staking) incentives, so each surface can render base yield + the rewards that apply to it.
7
+ */
8
+ export declare const attachAaveV4MerklIncentives: (asset: AaveV4ReserveAssetData, spokeAddress: string, campaigns: AaveV4MerklRewardMap) => AaveV4ReserveAssetData;
@@ -0,0 +1,85 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { aprToApy } from '../moneymarket';
11
+ import { DEFAULT_TIMEOUT } from '../services/utils';
12
+ import { IncentiveKind, OpportunityAction, OpportunityStatus, } from '../types';
13
+ /**
14
+ * 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 (matched per underlying token)
16
+ * - AAVE_V4_SPOKE_SUPPLY / AAVE_V4_SPOKE_BORROW → reward tied to a spoke (matched per spoke contract + underlying)
17
+ * Hub campaigns identify the underlying via `tokens[0]`; spoke campaigns identify the spoke via `explorerAddress`.
18
+ */
19
+ const spokeKey = (spokeAddress, underlying) => `${spokeAddress.toLowerCase()}_${underlying.toLowerCase()}`;
20
+ const buildIncentive = (opportunity) => {
21
+ var _a, _b, _c, _d, _e;
22
+ const rewardToken = (_c = (_b = (_a = opportunity.rewardsRecord) === null || _a === void 0 ? void 0 : _a.breakdowns) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.token;
23
+ const token = (rewardToken === null || rewardToken === void 0 ? void 0 : rewardToken.symbol) || ((_e = (_d = opportunity.tokens) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.symbol) || '';
24
+ return {
25
+ apy: aprToApy(opportunity.apr),
26
+ token,
27
+ incentiveKind: IncentiveKind.Reward,
28
+ description: `Eligible for ${token} rewards through Merkl.`,
29
+ };
30
+ };
31
+ export const getAaveV4MerkleCampaigns = (chainId) => __awaiter(void 0, void 0, void 0, function* () {
32
+ const result = { hub: {}, spoke: {} };
33
+ try {
34
+ const res = yield fetch('https://api-merkl.angle.money/v4/opportunities?mainProtocolId=aave', {
35
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT),
36
+ });
37
+ if (!res.ok)
38
+ throw new Error('Failed to fetch Aave V4 Merkle campaigns');
39
+ const opportunities = yield res.json();
40
+ opportunities
41
+ .filter((o) => o.chainId === chainId)
42
+ .filter((o) => o.status === OpportunityStatus.LIVE)
43
+ .filter((o) => typeof o.type === 'string' && o.type.startsWith('AAVE_V4_'))
44
+ .forEach((o) => {
45
+ var _a, _b, _c, _d;
46
+ const underlying = (_c = (_b = (_a = o.tokens) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.address) === null || _c === void 0 ? void 0 : _c.toLowerCase();
47
+ if (!underlying)
48
+ return;
49
+ const side = o.action === OpportunityAction.BORROW ? 'borrow' : 'supply';
50
+ const incentive = buildIncentive(o);
51
+ if (o.type.includes('HUB')) {
52
+ if (!result.hub[underlying])
53
+ result.hub[underlying] = {};
54
+ result.hub[underlying][side] = incentive;
55
+ }
56
+ else if (o.type.includes('SPOKE')) {
57
+ const spokeAddress = (_d = o.explorerAddress) === null || _d === void 0 ? void 0 : _d.toLowerCase();
58
+ if (!spokeAddress)
59
+ return;
60
+ const key = spokeKey(spokeAddress, underlying);
61
+ if (!result.spoke[key])
62
+ result.spoke[key] = {};
63
+ result.spoke[key][side] = incentive;
64
+ }
65
+ });
66
+ return result;
67
+ }
68
+ catch (e) {
69
+ console.error('Failed to fetch Aave V4 Merkle campaigns', e);
70
+ return result;
71
+ }
72
+ });
73
+ /**
74
+ * Returns a copy of the asset with scope-specific incentive arrays pre-combined with the asset's
75
+ * intrinsic (staking) incentives, so each surface can render base yield + the rewards that apply to it.
76
+ */
77
+ export const attachAaveV4MerklIncentives = (asset, spokeAddress, campaigns) => {
78
+ var _a;
79
+ const underlying = (_a = asset.underlying) === null || _a === void 0 ? void 0 : _a.toLowerCase();
80
+ const baseSupply = asset.supplyIncentives || [];
81
+ const baseBorrow = asset.borrowIncentives || [];
82
+ const spokeScoped = (spokeAddress && underlying) ? campaigns.spoke[spokeKey(spokeAddress, underlying)] : undefined;
83
+ const hubScoped = underlying ? campaigns.hub[underlying] : undefined;
84
+ return Object.assign(Object.assign({}, asset), { spokeSupplyIncentives: (spokeScoped === null || spokeScoped === void 0 ? void 0 : spokeScoped.supply) ? [...baseSupply, spokeScoped.supply] : baseSupply, spokeBorrowIncentives: (spokeScoped === null || spokeScoped === void 0 ? void 0 : spokeScoped.borrow) ? [...baseBorrow, spokeScoped.borrow] : baseBorrow, hubSupplyIncentives: (hubScoped === null || hubScoped === void 0 ? void 0 : hubScoped.supply) ? [...baseSupply, hubScoped.supply] : baseSupply, hubBorrowIncentives: (hubScoped === null || hubScoped === void 0 ? void 0 : hubScoped.borrow) ? [...baseBorrow, hubScoped.borrow] : baseBorrow });
85
+ };
@@ -15,7 +15,7 @@ import { getEthAmountForDecimals } from '../services/utils';
15
15
  export const fetchEthenaAirdropReward = (address) => __awaiter(void 0, void 0, void 0, function* () {
16
16
  try {
17
17
  const checksumAddress = getAddress(address);
18
- const response = yield fetch(`https://airdrop-data-ethena-s4.s3.us-west-2.amazonaws.com/${checksumAddress}/0x3d99219fbd49ace3f48d6ca1340e505ec1bdf27d1f8d0e15ec9f286cc9215fcd-${checksumAddress}.json`);
18
+ const response = yield fetch(`https://d1o76ps6187jke.cloudfront.net/${checksumAddress}/0xb92954d91aa2793b3718414d8df2413d5bd648955dec4a75471e86ded263d585-${checksumAddress}.json`);
19
19
  if (!response.ok) {
20
20
  if (response.status === 403) {
21
21
  // This is also okay, means that there are no rewards for the address
@@ -100,6 +100,16 @@ export interface AaveV4ReserveAssetData {
100
100
  borrowRate: string;
101
101
  supplyIncentives: IncentiveData[];
102
102
  borrowIncentives: IncentiveData[];
103
+ /**
104
+ * Intrinsic incentives pre-combined with scope-specific Merkl rewards, ready to render per surface:
105
+ * - spoke* → spoke supply/borrow (Create page + dashboard market table)
106
+ * - hub* → hub supply/borrow (Lend page)
107
+ * `supplyIncentives`/`borrowIncentives` above remain intrinsic-only so the net-APY calc is unaffected.
108
+ */
109
+ spokeSupplyIncentives?: IncentiveData[];
110
+ spokeBorrowIncentives?: IncentiveData[];
111
+ hubSupplyIncentives?: IncentiveData[];
112
+ hubBorrowIncentives?: IncentiveData[];
103
113
  canBeBorrowed: boolean;
104
114
  canBeSupplied: boolean;
105
115
  canBeWithdrawn: boolean;
@@ -1,4 +1,4 @@
1
- import { EthAddress } from './common';
1
+ import { EthAddress, IncentiveData } from './common';
2
2
  export declare enum OpportunityAction {
3
3
  LEND = "LEND",
4
4
  BORROW = "BORROW"
@@ -73,3 +73,16 @@ export type MerkleRewardMap = Record<EthAddress, {
73
73
  supply?: MerkleRewardInfo;
74
74
  borrow?: MerkleRewardInfo;
75
75
  }>;
76
+ export type AaveV4MerklScopedReward = {
77
+ supply?: IncentiveData;
78
+ borrow?: IncentiveData;
79
+ };
80
+ /**
81
+ * Aave V4 Merkl reward campaigns split by scope:
82
+ * - `hub`: keyed by underlying token address (lowercase) — rewards for supplying to a hub
83
+ * - `spoke`: keyed by `${spokeAddress}_${underlyingAddress}` (both lowercase) — rewards for supplying/borrowing on a spoke
84
+ */
85
+ export type AaveV4MerklRewardMap = {
86
+ hub: Record<string, AaveV4MerklScopedReward>;
87
+ spoke: Record<string, AaveV4MerklScopedReward>;
88
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defisaver/positions-sdk",
3
- "version": "2.1.101",
3
+ "version": "2.1.102-aave-v4-merkl-dev",
4
4
  "description": "",
5
5
  "main": "./cjs/index.js",
6
6
  "module": "./esm/index.js",
@@ -23,8 +23,10 @@ import { isMaxUint, wethToEth } from '../services/utils';
23
23
  import { aaveV4GetAggregatedPositionData, calcUserRiskPremiumBps } from '../helpers/aaveV4Helpers';
24
24
  import { getAaveV4HubByAddress } from '../markets/aaveV4';
25
25
  import { aprToApy } from '../moneymarket';
26
+ import { attachAaveV4MerklIncentives, getAaveV4MerkleCampaigns } from './merkl';
26
27
 
27
28
  export * as lend from './lend';
29
+ export { getAaveV4MerkleCampaigns } from './merkl';
28
30
 
29
31
  const fetchHubData = async (viewContract: ReturnType<typeof AaveV4ViewContractViem>, hubAddress: EthAddress): Promise<AaveV4HubOnChainData> => {
30
32
  const hubData = await viewContract.read.getHubAllAssetsData([hubAddress]);
@@ -145,6 +147,7 @@ export async function _getAaveV4SpokeData(provider: Client, network: NetworkNumb
145
147
  const viewContract = AaveV4ViewContractViem(provider, network, blockNumber);
146
148
 
147
149
  const hubsData: Record<EthAddress, AaveV4HubOnChainData> = {};
150
+ const merklCampaignsPromise = getAaveV4MerkleCampaigns(network);
148
151
  const [spokeData] = await Promise.all([
149
152
  viewContract.read.getSpokeDataFull([market.address]),
150
153
  ...market.hubs.map(async (hubAddress) => {
@@ -154,8 +157,11 @@ export async function _getAaveV4SpokeData(provider: Client, network: NetworkNumb
154
157
 
155
158
  const reserveAssetsArray = await Promise.all(spokeData[1].map(async (reserveAssetOnChain: AaveV4ReserveAssetOnChain, index: number) => formatReserveAsset(reserveAssetOnChain, hubsData[reserveAssetOnChain.hub].assets[reserveAssetOnChain.assetId], index, +spokeData[0].oracleDecimals.toString(), network)));
156
159
 
160
+ const merklCampaigns = await merklCampaignsPromise;
161
+ const enrichedAssets = reserveAssetsArray.map((asset) => attachAaveV4MerklIncentives(asset, market.address, merklCampaigns));
162
+
157
163
  return {
158
- assetsData: reserveAssetsArray.reduce((acc: Record<string, AaveV4ReserveAssetData>, reserveAsset: AaveV4ReserveAssetData) => {
164
+ assetsData: enrichedAssets.reduce((acc: Record<string, AaveV4ReserveAssetData>, reserveAsset: AaveV4ReserveAssetData) => {
159
165
  acc[`${reserveAsset.symbol}-${reserveAsset.reserveId}`] = reserveAsset;
160
166
  return acc;
161
167
  }, {}),
@@ -0,0 +1,92 @@
1
+ import { aprToApy } from '../moneymarket';
2
+ import { DEFAULT_TIMEOUT } from '../services/utils';
3
+ import {
4
+ AaveV4MerklRewardMap,
5
+ AaveV4ReserveAssetData,
6
+ IncentiveData,
7
+ IncentiveKind,
8
+ MerklOpportunity,
9
+ OpportunityAction,
10
+ OpportunityStatus,
11
+ } from '../types';
12
+ import { NetworkNumber } from '../types/common';
13
+
14
+ /**
15
+ * Merkl tags Aave V4 reward campaigns by scope via the `type` field:
16
+ * - AAVE_V4_HUB_SUPPLY / AAVE_V4_HUB_BORROW → reward tied to a hub (matched per underlying token)
17
+ * - AAVE_V4_SPOKE_SUPPLY / AAVE_V4_SPOKE_BORROW → reward tied to a spoke (matched per spoke contract + underlying)
18
+ * Hub campaigns identify the underlying via `tokens[0]`; spoke campaigns identify the spoke via `explorerAddress`.
19
+ */
20
+
21
+ const spokeKey = (spokeAddress: string, underlying: string) => `${spokeAddress.toLowerCase()}_${underlying.toLowerCase()}`;
22
+
23
+ const buildIncentive = (opportunity: MerklOpportunity): IncentiveData => {
24
+ const rewardToken = opportunity.rewardsRecord?.breakdowns?.[0]?.token;
25
+ const token = rewardToken?.symbol || opportunity.tokens?.[0]?.symbol || '';
26
+ return {
27
+ apy: aprToApy(opportunity.apr),
28
+ token,
29
+ incentiveKind: IncentiveKind.Reward,
30
+ description: `Eligible for ${token} rewards through Merkl.`,
31
+ };
32
+ };
33
+
34
+ export const getAaveV4MerkleCampaigns = async (chainId: NetworkNumber): Promise<AaveV4MerklRewardMap> => {
35
+ const result: AaveV4MerklRewardMap = { hub: {}, spoke: {} };
36
+ try {
37
+ const res = await fetch('https://api-merkl.angle.money/v4/opportunities?mainProtocolId=aave', {
38
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT),
39
+ });
40
+ if (!res.ok) throw new Error('Failed to fetch Aave V4 Merkle campaigns');
41
+ const opportunities = await res.json() as MerklOpportunity[];
42
+
43
+ opportunities
44
+ .filter((o) => o.chainId === chainId)
45
+ .filter((o) => o.status === OpportunityStatus.LIVE)
46
+ .filter((o) => typeof o.type === 'string' && o.type.startsWith('AAVE_V4_'))
47
+ .forEach((o) => {
48
+ const underlying = o.tokens?.[0]?.address?.toLowerCase();
49
+ if (!underlying) return;
50
+
51
+ const side: 'supply' | 'borrow' = o.action === OpportunityAction.BORROW ? 'borrow' : 'supply';
52
+ const incentive = buildIncentive(o);
53
+
54
+ if (o.type.includes('HUB')) {
55
+ if (!result.hub[underlying]) result.hub[underlying] = {};
56
+ result.hub[underlying][side] = incentive;
57
+ } else if (o.type.includes('SPOKE')) {
58
+ const spokeAddress = o.explorerAddress?.toLowerCase();
59
+ if (!spokeAddress) return;
60
+ const key = spokeKey(spokeAddress, underlying);
61
+ if (!result.spoke[key]) result.spoke[key] = {};
62
+ result.spoke[key][side] = incentive;
63
+ }
64
+ });
65
+
66
+ return result;
67
+ } catch (e) {
68
+ console.error('Failed to fetch Aave V4 Merkle campaigns', e);
69
+ return result;
70
+ }
71
+ };
72
+
73
+ /**
74
+ * Returns a copy of the asset with scope-specific incentive arrays pre-combined with the asset's
75
+ * intrinsic (staking) incentives, so each surface can render base yield + the rewards that apply to it.
76
+ */
77
+ export const attachAaveV4MerklIncentives = (asset: AaveV4ReserveAssetData, spokeAddress: string, campaigns: AaveV4MerklRewardMap): AaveV4ReserveAssetData => {
78
+ const underlying = asset.underlying?.toLowerCase();
79
+ const baseSupply = asset.supplyIncentives || [];
80
+ const baseBorrow = asset.borrowIncentives || [];
81
+
82
+ const spokeScoped = (spokeAddress && underlying) ? campaigns.spoke[spokeKey(spokeAddress, underlying)] : undefined;
83
+ const hubScoped = underlying ? campaigns.hub[underlying] : undefined;
84
+
85
+ return {
86
+ ...asset,
87
+ spokeSupplyIncentives: spokeScoped?.supply ? [...baseSupply, spokeScoped.supply] : baseSupply,
88
+ spokeBorrowIncentives: spokeScoped?.borrow ? [...baseBorrow, spokeScoped.borrow] : baseBorrow,
89
+ hubSupplyIncentives: hubScoped?.supply ? [...baseSupply, hubScoped.supply] : baseSupply,
90
+ hubBorrowIncentives: hubScoped?.borrow ? [...baseBorrow, hubScoped.borrow] : baseBorrow,
91
+ };
92
+ };
@@ -8,8 +8,7 @@ import { getEthAmountForDecimals } from '../services/utils';
8
8
  export const fetchEthenaAirdropReward = async (address: EthAddress) => {
9
9
  try {
10
10
  const checksumAddress = getAddress(address);
11
- const response = await fetch(`https://airdrop-data-ethena-s4.s3.us-west-2.amazonaws.com/${checksumAddress}/0x3d99219fbd49ace3f48d6ca1340e505ec1bdf27d1f8d0e15ec9f286cc9215fcd-${checksumAddress}.json`);
12
-
11
+ const response = await fetch(`https://d1o76ps6187jke.cloudfront.net/${checksumAddress}/0xb92954d91aa2793b3718414d8df2413d5bd648955dec4a75471e86ded263d585-${checksumAddress}.json`);
13
12
  if (!response.ok) {
14
13
  if (response.status === 403) {
15
14
  // This is also okay, means that there are no rewards for the address
@@ -111,6 +111,16 @@ export interface AaveV4ReserveAssetData {
111
111
  borrowRate: string,
112
112
  supplyIncentives: IncentiveData[];
113
113
  borrowIncentives: IncentiveData[];
114
+ /**
115
+ * Intrinsic incentives pre-combined with scope-specific Merkl rewards, ready to render per surface:
116
+ * - spoke* → spoke supply/borrow (Create page + dashboard market table)
117
+ * - hub* → hub supply/borrow (Lend page)
118
+ * `supplyIncentives`/`borrowIncentives` above remain intrinsic-only so the net-APY calc is unaffected.
119
+ */
120
+ spokeSupplyIncentives?: IncentiveData[];
121
+ spokeBorrowIncentives?: IncentiveData[];
122
+ hubSupplyIncentives?: IncentiveData[];
123
+ hubBorrowIncentives?: IncentiveData[];
114
124
  canBeBorrowed: boolean;
115
125
  canBeSupplied: boolean;
116
126
  canBeWithdrawn: boolean;
@@ -1,4 +1,4 @@
1
- import { EthAddress } from './common';
1
+ import { EthAddress, IncentiveData } from './common';
2
2
 
3
3
  export enum OpportunityAction {
4
4
  LEND = 'LEND',
@@ -68,4 +68,16 @@ export type MerklOpportunity = {
68
68
  };
69
69
 
70
70
  export type MerkleRewardInfo = { apy: string; rewardTokenSymbol: string, description: string, identifier: string };
71
- export type MerkleRewardMap = Record<EthAddress, { supply?: MerkleRewardInfo; borrow?: MerkleRewardInfo }>;
71
+ export type MerkleRewardMap = Record<EthAddress, { supply?: MerkleRewardInfo; borrow?: MerkleRewardInfo }>;
72
+
73
+ export type AaveV4MerklScopedReward = { supply?: IncentiveData; borrow?: IncentiveData };
74
+
75
+ /**
76
+ * Aave V4 Merkl reward campaigns split by scope:
77
+ * - `hub`: keyed by underlying token address (lowercase) — rewards for supplying to a hub
78
+ * - `spoke`: keyed by `${spokeAddress}_${underlyingAddress}` (both lowercase) — rewards for supplying/borrowing on a spoke
79
+ */
80
+ export type AaveV4MerklRewardMap = {
81
+ hub: Record<string, AaveV4MerklScopedReward>;
82
+ spoke: Record<string, AaveV4MerklScopedReward>;
83
+ };