@defisaver/positions-sdk 2.1.102 → 2.1.104

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,15 +176,17 @@ 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 = {};
176
- const [spokeData] = yield Promise.all([
179
+ const [spokeData, merklCampaigns] = yield Promise.all([
177
180
  viewContract.read.getSpokeDataFull([market.address]),
181
+ (0, merkl_1.getAaveV4MerkleCampaigns)(network),
178
182
  ...market.hubs.map((hubAddress) => __awaiter(this, void 0, void 0, function* () {
179
183
  hubsData[hubAddress] = yield fetchHubData(viewContract, hubAddress);
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 enrichedAssets = reserveAssetsArray.map((asset) => (0, merkl_1.attachAaveV4MerklIncentives)(asset, market.address, merklCampaigns));
183
188
  return {
184
- assetsData: reserveAssetsArray.reduce((acc, reserveAsset) => {
189
+ assetsData: enrichedAssets.reduce((acc, reserveAsset) => {
185
190
  acc[`${reserveAsset.symbol}-${reserveAsset.reserveId}`] = reserveAsset;
186
191
  return acc;
187
192
  }, {}),
@@ -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.${opportunity.description ? `\n${opportunity.description}` : ''}`,
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;
@@ -54,25 +54,22 @@ const morphoVaultsOptions = __importStar(require("./options"));
54
54
  exports.morphoVaultsOptions = morphoVaultsOptions;
55
55
  const viem_1 = require("../../services/viem");
56
56
  const contracts_1 = require("../../contracts");
57
- const vaultDataQuery = (vaultAddress) => `query vaultByAddress {
58
- vaultByAddress(chainId: 1, address: "${vaultAddress}") {
59
- id,
60
- dailyApy,
61
- dailyApys {
62
- apy, netApy
63
- },
64
- monthlyApys {
65
- apy, netApy
66
- },
67
- liquidity {
68
- underlying, usd,
69
- },
70
- asset {
71
- priceUsd
57
+ const vaultDataQuery = `
58
+ query VaultByAddress($address: String!, $chainId: Int!) {
59
+ vaultByAddress(address: $address, chainId: $chainId) {
60
+ address
61
+ state {
62
+ totalAssets
63
+ totalAssetsUsd
64
+ totalSupply
65
+ }
66
+ liquidity {
67
+ underlying
68
+ usd
69
+ }
72
70
  }
73
- }
74
71
  }`;
75
- const MORPHO_BLUE_API = 'https://blue-api.morpho.org/graphql';
72
+ const MORPHO_BLUE_API = 'https://api.morpho.org/graphql';
76
73
  const _getMorphoVaultData = (provider, network, morphoVault, accounts) => __awaiter(void 0, void 0, void 0, function* () {
77
74
  const morphoVaultContract = (0, contracts_1.getMorphoVaultContractViem)(provider, morphoVault.address);
78
75
  const shares = {};
@@ -81,7 +78,7 @@ const _getMorphoVaultData = (provider, network, morphoVault, accounts) => __awai
81
78
  morphoVaultContract.read.totalSupply(),
82
79
  morphoVaultContract.read.decimals(),
83
80
  morphoVaultContract.read.DECIMALS_OFFSET(),
84
- (0, graphql_request_1.request)(MORPHO_BLUE_API, vaultDataQuery(morphoVault.address)),
81
+ (0, graphql_request_1.request)(MORPHO_BLUE_API, vaultDataQuery, { address: morphoVault.address, chainId: network }),
85
82
  ...accounts.map((account) => __awaiter(void 0, void 0, void 0, function* () {
86
83
  const share = yield morphoVaultContract.read.balanceOf([account]);
87
84
  shares[account] = share;
@@ -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,15 +131,17 @@ 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 = {};
132
- const [spokeData] = yield Promise.all([
134
+ const [spokeData, merklCampaigns] = yield Promise.all([
133
135
  viewContract.read.getSpokeDataFull([market.address]),
136
+ getAaveV4MerkleCampaigns(network),
134
137
  ...market.hubs.map((hubAddress) => __awaiter(this, void 0, void 0, function* () {
135
138
  hubsData[hubAddress] = yield fetchHubData(viewContract, hubAddress);
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 enrichedAssets = reserveAssetsArray.map((asset) => attachAaveV4MerklIncentives(asset, market.address, merklCampaigns));
139
143
  return {
140
- assetsData: reserveAssetsArray.reduce((acc, reserveAsset) => {
144
+ assetsData: enrichedAssets.reduce((acc, reserveAsset) => {
141
145
  acc[`${reserveAsset.symbol}-${reserveAsset.reserveId}`] = reserveAsset;
142
146
  return acc;
143
147
  }, {}),
@@ -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.${opportunity.description ? `\n${opportunity.description}` : ''}`,
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
+ };
@@ -14,25 +14,22 @@ import * as morphoVaultsOptions from './options';
14
14
  import { getViemProvider } from '../../services/viem';
15
15
  import { getMorphoVaultContractViem } from '../../contracts';
16
16
  export { morphoVaultsOptions, };
17
- const vaultDataQuery = (vaultAddress) => `query vaultByAddress {
18
- vaultByAddress(chainId: 1, address: "${vaultAddress}") {
19
- id,
20
- dailyApy,
21
- dailyApys {
22
- apy, netApy
23
- },
24
- monthlyApys {
25
- apy, netApy
26
- },
27
- liquidity {
28
- underlying, usd,
29
- },
30
- asset {
31
- priceUsd
17
+ const vaultDataQuery = `
18
+ query VaultByAddress($address: String!, $chainId: Int!) {
19
+ vaultByAddress(address: $address, chainId: $chainId) {
20
+ address
21
+ state {
22
+ totalAssets
23
+ totalAssetsUsd
24
+ totalSupply
25
+ }
26
+ liquidity {
27
+ underlying
28
+ usd
29
+ }
32
30
  }
33
- }
34
31
  }`;
35
- const MORPHO_BLUE_API = 'https://blue-api.morpho.org/graphql';
32
+ const MORPHO_BLUE_API = 'https://api.morpho.org/graphql';
36
33
  export const _getMorphoVaultData = (provider, network, morphoVault, accounts) => __awaiter(void 0, void 0, void 0, function* () {
37
34
  const morphoVaultContract = getMorphoVaultContractViem(provider, morphoVault.address);
38
35
  const shares = {};
@@ -41,7 +38,7 @@ export const _getMorphoVaultData = (provider, network, morphoVault, accounts) =>
41
38
  morphoVaultContract.read.totalSupply(),
42
39
  morphoVaultContract.read.decimals(),
43
40
  morphoVaultContract.read.DECIMALS_OFFSET(),
44
- graphqlRequest(MORPHO_BLUE_API, vaultDataQuery(morphoVault.address)),
41
+ graphqlRequest(MORPHO_BLUE_API, vaultDataQuery, { address: morphoVault.address, chainId: network }),
45
42
  ...accounts.map((account) => __awaiter(void 0, void 0, void 0, function* () {
46
43
  const share = yield morphoVaultContract.read.balanceOf([account]);
47
44
  shares[account] = share;
@@ -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.102",
3
+ "version": "2.1.104",
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,8 +147,9 @@ 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> = {};
148
- const [spokeData] = await Promise.all([
150
+ const [spokeData, merklCampaigns] = await Promise.all([
149
151
  viewContract.read.getSpokeDataFull([market.address]),
152
+ getAaveV4MerkleCampaigns(network),
150
153
  ...market.hubs.map(async (hubAddress) => {
151
154
  hubsData[hubAddress] = await fetchHubData(viewContract, hubAddress);
152
155
  }),
@@ -154,8 +157,10 @@ 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 enrichedAssets = reserveAssetsArray.map((asset) => attachAaveV4MerklIncentives(asset, market.address, merklCampaigns));
161
+
157
162
  return {
158
- assetsData: reserveAssetsArray.reduce((acc: Record<string, AaveV4ReserveAssetData>, reserveAsset: AaveV4ReserveAssetData) => {
163
+ assetsData: enrichedAssets.reduce((acc: Record<string, AaveV4ReserveAssetData>, reserveAsset: AaveV4ReserveAssetData) => {
159
164
  acc[`${reserveAsset.symbol}-${reserveAsset.reserveId}`] = reserveAsset;
160
165
  return acc;
161
166
  }, {}),
@@ -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.${opportunity.description ? `\n${opportunity.description}` : ''}`,
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
+ };
@@ -12,26 +12,23 @@ export {
12
12
  morphoVaultsOptions,
13
13
  };
14
14
 
15
- const vaultDataQuery = (vaultAddress: EthAddress) => `query vaultByAddress {
16
- vaultByAddress(chainId: 1, address: "${vaultAddress}") {
17
- id,
18
- dailyApy,
19
- dailyApys {
20
- apy, netApy
21
- },
22
- monthlyApys {
23
- apy, netApy
24
- },
25
- liquidity {
26
- underlying, usd,
27
- },
28
- asset {
29
- priceUsd
15
+ const vaultDataQuery = `
16
+ query VaultByAddress($address: String!, $chainId: Int!) {
17
+ vaultByAddress(address: $address, chainId: $chainId) {
18
+ address
19
+ state {
20
+ totalAssets
21
+ totalAssetsUsd
22
+ totalSupply
23
+ }
24
+ liquidity {
25
+ underlying
26
+ usd
27
+ }
30
28
  }
31
- }
32
29
  }`;
33
30
 
34
- const MORPHO_BLUE_API = 'https://blue-api.morpho.org/graphql';
31
+ const MORPHO_BLUE_API = 'https://api.morpho.org/graphql';
35
32
 
36
33
  export const _getMorphoVaultData = async (provider: Client, network: NetworkNumber, morphoVault: MorphoVault, accounts: EthAddress[]): Promise<SavingsVaultData> => {
37
34
  const morphoVaultContract = getMorphoVaultContractViem(provider, morphoVault.address);
@@ -43,7 +40,7 @@ export const _getMorphoVaultData = async (provider: Client, network: NetworkNumb
43
40
  morphoVaultContract.read.totalSupply(),
44
41
  morphoVaultContract.read.decimals(),
45
42
  morphoVaultContract.read.DECIMALS_OFFSET(),
46
- graphqlRequest(MORPHO_BLUE_API, vaultDataQuery(morphoVault.address)),
43
+ graphqlRequest(MORPHO_BLUE_API, vaultDataQuery, { address: morphoVault.address, chainId: network }),
47
44
  ...accounts.map(async (account) => {
48
45
  const share = await morphoVaultContract.read.balanceOf([account]);
49
46
  shares[account] = share;
@@ -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
+ };