@defisaver/positions-sdk 2.1.156 → 2.1.157

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.
@@ -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[];
@@ -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",
4
4
  "description": "",
5
5
  "main": "./cjs/index.js",
6
6
  "module": "./esm/index.js",
@@ -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[] };