@defisaver/positions-sdk 2.1.157 → 2.1.158-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.
@@ -19,8 +19,12 @@ const merkl_1 = require("../services/merkl");
19
19
  const types_1 = require("../types");
20
20
  /**
21
21
  * Merkl tags Aave V4 reward campaigns by scope via the `type` field:
22
- * - AAVE_V4_HUB_SUPPLY / AAVE_V4_HUB_BORROW / AAVE_V4_HUB_NET_LENDING hub asset
22
+ * - AAVE_V4_HUB_SUPPLY / AAVE_V4_HUB_BORROW reward tied to a hub asset
23
23
  * - AAVE_V4_SPOKE_SUPPLY / AAVE_V4_SPOKE_BORROW → reward tied to a spoke reserve
24
+ * - AAVE_V4_HUB_NET_LENDING / AAVE_V4_SPOKE_NET_BORROWING → reward on the NET position
25
+ * (supply minus same-token borrows, or vice versa); params list scopes as `hubs[]`/`spokes[]`
26
+ * arrays instead of a flat address+id pair, and only the campaign's own side maps to a reward
27
+ * (the opposite-side ids describe what reduces the accrual)
24
28
  * Embedded campaign params provide the exact on-chain identifiers. Token addresses cannot safely
25
29
  * identify Aave V4 rewards because one spoke can expose the same underlying from multiple hubs.
26
30
  * Each stored reward is also tagged with campaign identity (`campaignIds`/`parentCampaignIds`) so
@@ -36,11 +40,22 @@ const buildIncentive = (opportunity) => {
36
40
  apy: (0, moneymarket_1.aprToApy)(opportunity.apr),
37
41
  token,
38
42
  incentiveKind: types_1.IncentiveKind.Reward,
39
- description: `Eligible for ${token} rewards through Merkl.${opportunity.description ? `\n${opportunity.description}` : ''}`,
43
+ source: types_1.IncentiveSource.Merkl,
44
+ name: opportunity.name,
45
+ description: opportunity.description || `Eligible for ${token} rewards through Merkl.`,
40
46
  };
41
47
  };
42
48
  const buildAaveV4MerklRewardMap = (opportunities, chainId) => {
43
49
  const result = { hub: {}, spoke: {} };
50
+ const endByCampaignId = {};
51
+ opportunities.forEach((o) => {
52
+ var _a;
53
+ return (_a = o.campaigns) === null || _a === void 0 ? void 0 : _a.forEach((c) => {
54
+ if (c.id && c.endTimestamp)
55
+ endByCampaignId[c.id] = +c.endTimestamp;
56
+ });
57
+ });
58
+ const effectiveEndTimestamp = (campaign) => Math.max(campaign.endTimestamp ? +campaign.endTimestamp : 0, ...(campaign.childCampaignIds || []).map((id) => endByCampaignId[id] || 0));
44
59
  opportunities
45
60
  .filter((o) => o.chainId === chainId)
46
61
  .filter((o) => o.status === types_1.OpportunityStatus.LIVE)
@@ -69,12 +84,24 @@ const buildAaveV4MerklRewardMap = (opportunities, chainId) => {
69
84
  idsByKey[key].campaignIds.add(campaign.id);
70
85
  if (campaign.parentCampaignId)
71
86
  idsByKey[key].parentCampaignIds.add(campaign.parentCampaignId);
87
+ const endTimestamp = effectiveEndTimestamp(campaign);
88
+ if (endTimestamp)
89
+ idsByKey[key].endTimestamp = Math.max(idsByKey[key].endTimestamp || 0, endTimestamp);
72
90
  };
73
91
  if (o.type.includes('HUB')) {
74
92
  (_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;
93
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
94
+ if ((_b = (_a = c.params) === null || _a === void 0 ? void 0 : _a.hubs) === null || _b === void 0 ? void 0 : _b.length) {
95
+ c.params.hubs.forEach((hub) => {
96
+ if (!hub.hubAddress)
97
+ return;
98
+ const rewardedIds = (side === types_1.IncentiveSide.Borrow ? hub.borrowAssetIds : hub.lendingAssetIds) || [];
99
+ rewardedIds.forEach((assetId) => collect(scopeKey(hub.hubAddress, assetId), c));
100
+ });
101
+ return;
102
+ }
103
+ const hubAddress = (_d = (_c = c.params) === null || _c === void 0 ? void 0 : _c.hubAddress) !== null && _d !== void 0 ? _d : (_g = (_f = (_e = c.params) === null || _e === void 0 ? void 0 : _e.distributionMethodParameters) === null || _f === void 0 ? void 0 : _f.distributionSettings) === null || _g === void 0 ? void 0 : _g.hubAddress;
104
+ const assetId = (_j = (_h = c.params) === null || _h === void 0 ? void 0 : _h.assetId) !== null && _j !== void 0 ? _j : (_m = (_l = (_k = c.params) === null || _k === void 0 ? void 0 : _k.distributionMethodParameters) === null || _l === void 0 ? void 0 : _l.distributionSettings) === null || _m === void 0 ? void 0 : _m.assetId;
78
105
  if (!hubAddress || assetId === undefined || assetId === null)
79
106
  return;
80
107
  collect(scopeKey(hubAddress, assetId), c);
@@ -82,20 +109,33 @@ const buildAaveV4MerklRewardMap = (opportunities, chainId) => {
82
109
  Object.entries(idsByKey).forEach(([key, ids]) => {
83
110
  if (!result.hub[key])
84
111
  result.hub[key] = {};
85
- result.hub[key][side] = [...(result.hub[key][side] || []), Object.assign(Object.assign({}, incentive), { campaignIds: [...ids.campaignIds] })];
112
+ result.hub[key][side] = [...(result.hub[key][side] || []), Object.assign(Object.assign({}, incentive), { endTimestamp: ids.endTimestamp, campaignIds: [...ids.campaignIds] })];
86
113
  });
87
114
  }
88
115
  else if (o.type.includes('SPOKE')) {
89
116
  (_c = o.campaigns) === null || _c === void 0 ? void 0 : _c.forEach((c) => {
90
- var _a;
91
- if (!((_a = c.params) === null || _a === void 0 ? void 0 : _a.spokeAddress) || c.params.reserveId === undefined || c.params.reserveId === null)
117
+ var _a, _b, _c;
118
+ if ((_b = (_a = c.params) === null || _a === void 0 ? void 0 : _a.spokes) === null || _b === void 0 ? void 0 : _b.length) {
119
+ c.params.spokes.forEach((spoke) => {
120
+ if (!spoke.spokeAddress)
121
+ return;
122
+ const rewardedTokens = (side === types_1.IncentiveSide.Borrow ? spoke.borrowTokens : spoke.supplyTokens) || [];
123
+ rewardedTokens.forEach((tokenScope) => {
124
+ if (tokenScope.reserveId === undefined || tokenScope.reserveId === null)
125
+ return;
126
+ collect(scopeKey(spoke.spokeAddress, tokenScope.reserveId), c);
127
+ });
128
+ });
129
+ return;
130
+ }
131
+ if (!((_c = c.params) === null || _c === void 0 ? void 0 : _c.spokeAddress) || c.params.reserveId === undefined || c.params.reserveId === null)
92
132
  return;
93
133
  collect(scopeKey(c.params.spokeAddress, c.params.reserveId), c);
94
134
  });
95
135
  Object.entries(idsByKey).forEach(([key, ids]) => {
96
136
  if (!result.spoke[key])
97
137
  result.spoke[key] = {};
98
- result.spoke[key][side] = [...(result.spoke[key][side] || []), Object.assign(Object.assign({}, incentive), { campaignIds: [...ids.campaignIds], parentCampaignIds: [...ids.parentCampaignIds] })];
138
+ result.spoke[key][side] = [...(result.spoke[key][side] || []), Object.assign(Object.assign({}, incentive), { endTimestamp: ids.endTimestamp, campaignIds: [...ids.campaignIds], parentCampaignIds: [...ids.parentCampaignIds] })];
99
139
  });
100
140
  }
101
141
  });
@@ -106,7 +146,7 @@ const getAaveV4MerkleCampaigns = (chainId) => __awaiter(void 0, void 0, void 0,
106
146
  try {
107
147
  const opportunities = yield (0, merkl_1.fetchAllMerklOpportunities)({
108
148
  mainProtocolId: 'aave',
109
- type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,AAVE_V4_HUB_NET_LENDING,AAVE_V4_SPOKE_SUPPLY,AAVE_V4_SPOKE_BORROW',
149
+ type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,AAVE_V4_SPOKE_SUPPLY,AAVE_V4_SPOKE_BORROW,AAVE_V4_HUB_NET_LENDING,AAVE_V4_HUB_NET_BORROWING,AAVE_V4_SPOKE_NET_LENDING,AAVE_V4_SPOKE_NET_BORROWING',
110
150
  status: types_1.OpportunityStatus.LIVE,
111
151
  campaigns: 'true',
112
152
  });
@@ -29,7 +29,9 @@ export interface IncentiveData {
29
29
  apy: string;
30
30
  incentiveKind?: IncentiveKind;
31
31
  source?: IncentiveSource;
32
+ name?: string;
32
33
  description?: string;
34
+ endTimestamp?: number;
33
35
  eligibilityId?: IncentiveEligibilityId;
34
36
  }
35
37
  export type EthAddress = HexString;
@@ -11,8 +11,6 @@ export declare enum OpportunityStatus {
11
11
  export type MerklCampaign = {
12
12
  id: string;
13
13
  campaignId: string;
14
- startTimestamp?: number;
15
- endTimestamp?: number;
16
14
  /**
17
15
  * Merkl-internal `id` of the parent campaign — set on child campaigns, which re-publish a hub
18
16
  * (parent) campaign's reward scoped to a single spoke reserve. Absent on standalone campaigns,
@@ -20,12 +18,49 @@ export type MerklCampaign = {
20
18
  */
21
19
  parentCampaignId?: string;
22
20
  childCampaignIds?: string[];
21
+ startTimestamp?: number;
22
+ endTimestamp?: number;
23
23
  params?: {
24
24
  reserveId?: string | number;
25
25
  spokeAddress?: EthAddress;
26
26
  hubAddress?: EthAddress;
27
27
  hubAssetId?: string | number;
28
28
  assetId?: string | number;
29
+ /**
30
+ * AAVE_V4_HUB_NET_* campaigns scope per hub: the reward applies to the ids on the campaign's
31
+ * own side (`lendingAssetIds` for LEND, `borrowAssetIds` for BORROW) — the opposite-side ids
32
+ * only describe positions that reduce the net accrual, so they never map to a reward.
33
+ */
34
+ hubs?: {
35
+ hubAddress: EthAddress;
36
+ assets?: {
37
+ symbol: string;
38
+ assetId: string | number;
39
+ decimals?: number;
40
+ underlyingToken?: EthAddress;
41
+ }[];
42
+ lendingAssetIds?: (string | number)[];
43
+ borrowAssetIds?: (string | number)[];
44
+ }[];
45
+ /** AAVE_V4_SPOKE_NET_* campaigns scope per spoke, with the same own-side rule as `hubs`. */
46
+ spokes?: {
47
+ spokeAddress: EthAddress;
48
+ spokeName?: string;
49
+ supplyTokens?: {
50
+ symbol: string;
51
+ reserveId: string | number;
52
+ hubAddress?: EthAddress;
53
+ hubAssetId?: string | number;
54
+ underlyingToken?: EthAddress;
55
+ }[];
56
+ borrowTokens?: {
57
+ symbol: string;
58
+ reserveId: string | number;
59
+ hubAddress?: EthAddress;
60
+ hubAssetId?: string | number;
61
+ underlyingToken?: EthAddress;
62
+ }[];
63
+ }[];
29
64
  distributionMethodParameters?: {
30
65
  distributionMethod?: string;
31
66
  distributionSettings?: {
@@ -10,11 +10,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import Dec from 'decimal.js';
11
11
  import { aprToApy } from '../moneymarket';
12
12
  import { fetchAllMerklOpportunities } from '../services/merkl';
13
- import { IncentiveKind, IncentiveSide, OpportunityAction, OpportunityStatus, } from '../types';
13
+ import { IncentiveKind, IncentiveSide, IncentiveSource, OpportunityAction, OpportunityStatus, } from '../types';
14
14
  /**
15
15
  * Merkl tags Aave V4 reward campaigns by scope via the `type` field:
16
- * - AAVE_V4_HUB_SUPPLY / AAVE_V4_HUB_BORROW / AAVE_V4_HUB_NET_LENDING hub asset
16
+ * - AAVE_V4_HUB_SUPPLY / AAVE_V4_HUB_BORROW reward tied to a hub asset
17
17
  * - AAVE_V4_SPOKE_SUPPLY / AAVE_V4_SPOKE_BORROW → reward tied to a spoke reserve
18
+ * - AAVE_V4_HUB_NET_LENDING / AAVE_V4_SPOKE_NET_BORROWING → reward on the NET position
19
+ * (supply minus same-token borrows, or vice versa); params list scopes as `hubs[]`/`spokes[]`
20
+ * arrays instead of a flat address+id pair, and only the campaign's own side maps to a reward
21
+ * (the opposite-side ids describe what reduces the accrual)
18
22
  * Embedded campaign params provide the exact on-chain identifiers. Token addresses cannot safely
19
23
  * identify Aave V4 rewards because one spoke can expose the same underlying from multiple hubs.
20
24
  * Each stored reward is also tagged with campaign identity (`campaignIds`/`parentCampaignIds`) so
@@ -30,11 +34,22 @@ const buildIncentive = (opportunity) => {
30
34
  apy: aprToApy(opportunity.apr),
31
35
  token,
32
36
  incentiveKind: IncentiveKind.Reward,
33
- description: `Eligible for ${token} rewards through Merkl.${opportunity.description ? `\n${opportunity.description}` : ''}`,
37
+ source: IncentiveSource.Merkl,
38
+ name: opportunity.name,
39
+ description: opportunity.description || `Eligible for ${token} rewards through Merkl.`,
34
40
  };
35
41
  };
36
42
  export const buildAaveV4MerklRewardMap = (opportunities, chainId) => {
37
43
  const result = { hub: {}, spoke: {} };
44
+ const endByCampaignId = {};
45
+ opportunities.forEach((o) => {
46
+ var _a;
47
+ return (_a = o.campaigns) === null || _a === void 0 ? void 0 : _a.forEach((c) => {
48
+ if (c.id && c.endTimestamp)
49
+ endByCampaignId[c.id] = +c.endTimestamp;
50
+ });
51
+ });
52
+ const effectiveEndTimestamp = (campaign) => Math.max(campaign.endTimestamp ? +campaign.endTimestamp : 0, ...(campaign.childCampaignIds || []).map((id) => endByCampaignId[id] || 0));
38
53
  opportunities
39
54
  .filter((o) => o.chainId === chainId)
40
55
  .filter((o) => o.status === OpportunityStatus.LIVE)
@@ -63,12 +78,24 @@ export const buildAaveV4MerklRewardMap = (opportunities, chainId) => {
63
78
  idsByKey[key].campaignIds.add(campaign.id);
64
79
  if (campaign.parentCampaignId)
65
80
  idsByKey[key].parentCampaignIds.add(campaign.parentCampaignId);
81
+ const endTimestamp = effectiveEndTimestamp(campaign);
82
+ if (endTimestamp)
83
+ idsByKey[key].endTimestamp = Math.max(idsByKey[key].endTimestamp || 0, endTimestamp);
66
84
  };
67
85
  if (o.type.includes('HUB')) {
68
86
  (_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;
87
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
88
+ if ((_b = (_a = c.params) === null || _a === void 0 ? void 0 : _a.hubs) === null || _b === void 0 ? void 0 : _b.length) {
89
+ c.params.hubs.forEach((hub) => {
90
+ if (!hub.hubAddress)
91
+ return;
92
+ const rewardedIds = (side === IncentiveSide.Borrow ? hub.borrowAssetIds : hub.lendingAssetIds) || [];
93
+ rewardedIds.forEach((assetId) => collect(scopeKey(hub.hubAddress, assetId), c));
94
+ });
95
+ return;
96
+ }
97
+ const hubAddress = (_d = (_c = c.params) === null || _c === void 0 ? void 0 : _c.hubAddress) !== null && _d !== void 0 ? _d : (_g = (_f = (_e = c.params) === null || _e === void 0 ? void 0 : _e.distributionMethodParameters) === null || _f === void 0 ? void 0 : _f.distributionSettings) === null || _g === void 0 ? void 0 : _g.hubAddress;
98
+ const assetId = (_j = (_h = c.params) === null || _h === void 0 ? void 0 : _h.assetId) !== null && _j !== void 0 ? _j : (_m = (_l = (_k = c.params) === null || _k === void 0 ? void 0 : _k.distributionMethodParameters) === null || _l === void 0 ? void 0 : _l.distributionSettings) === null || _m === void 0 ? void 0 : _m.assetId;
72
99
  if (!hubAddress || assetId === undefined || assetId === null)
73
100
  return;
74
101
  collect(scopeKey(hubAddress, assetId), c);
@@ -76,20 +103,33 @@ export const buildAaveV4MerklRewardMap = (opportunities, chainId) => {
76
103
  Object.entries(idsByKey).forEach(([key, ids]) => {
77
104
  if (!result.hub[key])
78
105
  result.hub[key] = {};
79
- result.hub[key][side] = [...(result.hub[key][side] || []), Object.assign(Object.assign({}, incentive), { campaignIds: [...ids.campaignIds] })];
106
+ result.hub[key][side] = [...(result.hub[key][side] || []), Object.assign(Object.assign({}, incentive), { endTimestamp: ids.endTimestamp, campaignIds: [...ids.campaignIds] })];
80
107
  });
81
108
  }
82
109
  else if (o.type.includes('SPOKE')) {
83
110
  (_c = o.campaigns) === null || _c === void 0 ? void 0 : _c.forEach((c) => {
84
- var _a;
85
- if (!((_a = c.params) === null || _a === void 0 ? void 0 : _a.spokeAddress) || c.params.reserveId === undefined || c.params.reserveId === null)
111
+ var _a, _b, _c;
112
+ if ((_b = (_a = c.params) === null || _a === void 0 ? void 0 : _a.spokes) === null || _b === void 0 ? void 0 : _b.length) {
113
+ c.params.spokes.forEach((spoke) => {
114
+ if (!spoke.spokeAddress)
115
+ return;
116
+ const rewardedTokens = (side === IncentiveSide.Borrow ? spoke.borrowTokens : spoke.supplyTokens) || [];
117
+ rewardedTokens.forEach((tokenScope) => {
118
+ if (tokenScope.reserveId === undefined || tokenScope.reserveId === null)
119
+ return;
120
+ collect(scopeKey(spoke.spokeAddress, tokenScope.reserveId), c);
121
+ });
122
+ });
123
+ return;
124
+ }
125
+ if (!((_c = c.params) === null || _c === void 0 ? void 0 : _c.spokeAddress) || c.params.reserveId === undefined || c.params.reserveId === null)
86
126
  return;
87
127
  collect(scopeKey(c.params.spokeAddress, c.params.reserveId), c);
88
128
  });
89
129
  Object.entries(idsByKey).forEach(([key, ids]) => {
90
130
  if (!result.spoke[key])
91
131
  result.spoke[key] = {};
92
- result.spoke[key][side] = [...(result.spoke[key][side] || []), Object.assign(Object.assign({}, incentive), { campaignIds: [...ids.campaignIds], parentCampaignIds: [...ids.parentCampaignIds] })];
132
+ result.spoke[key][side] = [...(result.spoke[key][side] || []), Object.assign(Object.assign({}, incentive), { endTimestamp: ids.endTimestamp, campaignIds: [...ids.campaignIds], parentCampaignIds: [...ids.parentCampaignIds] })];
93
133
  });
94
134
  }
95
135
  });
@@ -99,7 +139,7 @@ export const getAaveV4MerkleCampaigns = (chainId) => __awaiter(void 0, void 0, v
99
139
  try {
100
140
  const opportunities = yield fetchAllMerklOpportunities({
101
141
  mainProtocolId: 'aave',
102
- type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,AAVE_V4_HUB_NET_LENDING,AAVE_V4_SPOKE_SUPPLY,AAVE_V4_SPOKE_BORROW',
142
+ type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,AAVE_V4_SPOKE_SUPPLY,AAVE_V4_SPOKE_BORROW,AAVE_V4_HUB_NET_LENDING,AAVE_V4_HUB_NET_BORROWING,AAVE_V4_SPOKE_NET_LENDING,AAVE_V4_SPOKE_NET_BORROWING',
103
143
  status: OpportunityStatus.LIVE,
104
144
  campaigns: 'true',
105
145
  });
@@ -29,7 +29,9 @@ export interface IncentiveData {
29
29
  apy: string;
30
30
  incentiveKind?: IncentiveKind;
31
31
  source?: IncentiveSource;
32
+ name?: string;
32
33
  description?: string;
34
+ endTimestamp?: number;
33
35
  eligibilityId?: IncentiveEligibilityId;
34
36
  }
35
37
  export type EthAddress = HexString;
@@ -11,8 +11,6 @@ export declare enum OpportunityStatus {
11
11
  export type MerklCampaign = {
12
12
  id: string;
13
13
  campaignId: string;
14
- startTimestamp?: number;
15
- endTimestamp?: number;
16
14
  /**
17
15
  * Merkl-internal `id` of the parent campaign — set on child campaigns, which re-publish a hub
18
16
  * (parent) campaign's reward scoped to a single spoke reserve. Absent on standalone campaigns,
@@ -20,12 +18,49 @@ export type MerklCampaign = {
20
18
  */
21
19
  parentCampaignId?: string;
22
20
  childCampaignIds?: string[];
21
+ startTimestamp?: number;
22
+ endTimestamp?: number;
23
23
  params?: {
24
24
  reserveId?: string | number;
25
25
  spokeAddress?: EthAddress;
26
26
  hubAddress?: EthAddress;
27
27
  hubAssetId?: string | number;
28
28
  assetId?: string | number;
29
+ /**
30
+ * AAVE_V4_HUB_NET_* campaigns scope per hub: the reward applies to the ids on the campaign's
31
+ * own side (`lendingAssetIds` for LEND, `borrowAssetIds` for BORROW) — the opposite-side ids
32
+ * only describe positions that reduce the net accrual, so they never map to a reward.
33
+ */
34
+ hubs?: {
35
+ hubAddress: EthAddress;
36
+ assets?: {
37
+ symbol: string;
38
+ assetId: string | number;
39
+ decimals?: number;
40
+ underlyingToken?: EthAddress;
41
+ }[];
42
+ lendingAssetIds?: (string | number)[];
43
+ borrowAssetIds?: (string | number)[];
44
+ }[];
45
+ /** AAVE_V4_SPOKE_NET_* campaigns scope per spoke, with the same own-side rule as `hubs`. */
46
+ spokes?: {
47
+ spokeAddress: EthAddress;
48
+ spokeName?: string;
49
+ supplyTokens?: {
50
+ symbol: string;
51
+ reserveId: string | number;
52
+ hubAddress?: EthAddress;
53
+ hubAssetId?: string | number;
54
+ underlyingToken?: EthAddress;
55
+ }[];
56
+ borrowTokens?: {
57
+ symbol: string;
58
+ reserveId: string | number;
59
+ hubAddress?: EthAddress;
60
+ hubAssetId?: string | number;
61
+ underlyingToken?: EthAddress;
62
+ }[];
63
+ }[];
29
64
  distributionMethodParameters?: {
30
65
  distributionMethod?: string;
31
66
  distributionSettings?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defisaver/positions-sdk",
3
- "version": "2.1.157",
3
+ "version": "2.1.158-dev",
4
4
  "description": "",
5
5
  "main": "./cjs/index.js",
6
6
  "module": "./esm/index.js",
@@ -8,6 +8,7 @@ import {
8
8
  IncentiveData,
9
9
  IncentiveKind,
10
10
  IncentiveSide,
11
+ IncentiveSource,
11
12
  MerklCampaign,
12
13
  MerklOpportunity,
13
14
  OpportunityAction,
@@ -17,8 +18,12 @@ import {
17
18
 
18
19
  /**
19
20
  * Merkl tags Aave V4 reward campaigns by scope via the `type` field:
20
- * - AAVE_V4_HUB_SUPPLY / AAVE_V4_HUB_BORROW / AAVE_V4_HUB_NET_LENDING hub asset
21
+ * - AAVE_V4_HUB_SUPPLY / AAVE_V4_HUB_BORROW reward tied to a hub asset
21
22
  * - AAVE_V4_SPOKE_SUPPLY / AAVE_V4_SPOKE_BORROW → reward tied to a spoke reserve
23
+ * - AAVE_V4_HUB_NET_LENDING / AAVE_V4_SPOKE_NET_BORROWING → reward on the NET position
24
+ * (supply minus same-token borrows, or vice versa); params list scopes as `hubs[]`/`spokes[]`
25
+ * arrays instead of a flat address+id pair, and only the campaign's own side maps to a reward
26
+ * (the opposite-side ids describe what reduces the accrual)
22
27
  * Embedded campaign params provide the exact on-chain identifiers. Token addresses cannot safely
23
28
  * identify Aave V4 rewards because one spoke can expose the same underlying from multiple hubs.
24
29
  * Each stored reward is also tagged with campaign identity (`campaignIds`/`parentCampaignIds`) so
@@ -35,13 +40,24 @@ const buildIncentive = (opportunity: MerklOpportunity): IncentiveData => {
35
40
  apy: aprToApy(opportunity.apr),
36
41
  token,
37
42
  incentiveKind: IncentiveKind.Reward,
38
- description: `Eligible for ${token} rewards through Merkl.${opportunity.description ? `\n${opportunity.description}` : ''}`,
43
+ source: IncentiveSource.Merkl,
44
+ name: opportunity.name,
45
+ description: opportunity.description || `Eligible for ${token} rewards through Merkl.`,
39
46
  };
40
47
  };
41
48
 
42
49
  export const buildAaveV4MerklRewardMap = (opportunities: MerklOpportunity[], chainId: NetworkNumber): AaveV4MerklRewardMap => {
43
50
  const result: AaveV4MerklRewardMap = { hub: {}, spoke: {} };
44
51
 
52
+ const endByCampaignId: Record<string, number> = {};
53
+ opportunities.forEach((o) => o.campaigns?.forEach((c) => {
54
+ if (c.id && c.endTimestamp) endByCampaignId[c.id] = +c.endTimestamp;
55
+ }));
56
+ const effectiveEndTimestamp = (campaign: MerklCampaign): number => Math.max(
57
+ campaign.endTimestamp ? +campaign.endTimestamp : 0,
58
+ ...(campaign.childCampaignIds || []).map((id) => endByCampaignId[id] || 0),
59
+ );
60
+
45
61
  opportunities
46
62
  .filter((o) => o.chainId === chainId)
47
63
  .filter((o) => o.status === OpportunityStatus.LIVE)
@@ -64,15 +80,25 @@ export const buildAaveV4MerklRewardMap = (opportunities: MerklOpportunity[], cha
64
80
  };
65
81
  // one opportunity can span several campaigns (e.g. renewed periods), so campaign identity is
66
82
  // collected per scope key before the reward entries are written
67
- const idsByKey: Record<string, { campaignIds: Set<string>, parentCampaignIds: Set<string> }> = {};
83
+ const idsByKey: Record<string, { campaignIds: Set<string>, parentCampaignIds: Set<string>, endTimestamp?: number }> = {};
68
84
  const collect = (key: string, campaign: MerklCampaign) => {
69
85
  if (!idsByKey[key]) idsByKey[key] = { campaignIds: new Set(), parentCampaignIds: new Set() };
70
86
  if (campaign.id) idsByKey[key].campaignIds.add(campaign.id);
71
87
  if (campaign.parentCampaignId) idsByKey[key].parentCampaignIds.add(campaign.parentCampaignId);
88
+ const endTimestamp = effectiveEndTimestamp(campaign);
89
+ if (endTimestamp) idsByKey[key].endTimestamp = Math.max(idsByKey[key].endTimestamp || 0, endTimestamp);
72
90
  };
73
91
 
74
92
  if (o.type.includes('HUB')) {
75
93
  o.campaigns?.forEach((c) => {
94
+ if (c.params?.hubs?.length) {
95
+ c.params.hubs.forEach((hub) => {
96
+ if (!hub.hubAddress) return;
97
+ const rewardedIds = (side === IncentiveSide.Borrow ? hub.borrowAssetIds : hub.lendingAssetIds) || [];
98
+ rewardedIds.forEach((assetId) => collect(scopeKey(hub.hubAddress, assetId), c));
99
+ });
100
+ return;
101
+ }
76
102
  const hubAddress = c.params?.hubAddress ?? c.params?.distributionMethodParameters?.distributionSettings?.hubAddress;
77
103
  const assetId = c.params?.assetId ?? c.params?.distributionMethodParameters?.distributionSettings?.assetId;
78
104
  if (!hubAddress || assetId === undefined || assetId === null) return;
@@ -80,16 +106,29 @@ export const buildAaveV4MerklRewardMap = (opportunities: MerklOpportunity[], cha
80
106
  });
81
107
  Object.entries(idsByKey).forEach(([key, ids]) => {
82
108
  if (!result.hub[key]) result.hub[key] = {};
83
- result.hub[key][side] = [...(result.hub[key][side] || []), { ...incentive, campaignIds: [...ids.campaignIds] }];
109
+ result.hub[key][side] = [...(result.hub[key][side] || []), { ...incentive, endTimestamp: ids.endTimestamp, campaignIds: [...ids.campaignIds] }];
84
110
  });
85
111
  } else if (o.type.includes('SPOKE')) {
86
112
  o.campaigns?.forEach((c) => {
113
+ if (c.params?.spokes?.length) {
114
+ c.params.spokes.forEach((spoke) => {
115
+ if (!spoke.spokeAddress) return;
116
+ const rewardedTokens = (side === IncentiveSide.Borrow ? spoke.borrowTokens : spoke.supplyTokens) || [];
117
+ rewardedTokens.forEach((tokenScope) => {
118
+ if (tokenScope.reserveId === undefined || tokenScope.reserveId === null) return;
119
+ collect(scopeKey(spoke.spokeAddress, tokenScope.reserveId), c);
120
+ });
121
+ });
122
+ return;
123
+ }
87
124
  if (!c.params?.spokeAddress || c.params.reserveId === undefined || c.params.reserveId === null) return;
88
125
  collect(scopeKey(c.params.spokeAddress, c.params.reserveId), c);
89
126
  });
90
127
  Object.entries(idsByKey).forEach(([key, ids]) => {
91
128
  if (!result.spoke[key]) result.spoke[key] = {};
92
- result.spoke[key][side] = [...(result.spoke[key][side] || []), { ...incentive, campaignIds: [...ids.campaignIds], parentCampaignIds: [...ids.parentCampaignIds] }];
129
+ result.spoke[key][side] = [...(result.spoke[key][side] || []), {
130
+ ...incentive, endTimestamp: ids.endTimestamp, campaignIds: [...ids.campaignIds], parentCampaignIds: [...ids.parentCampaignIds],
131
+ }];
93
132
  });
94
133
  }
95
134
  });
@@ -101,7 +140,7 @@ export const getAaveV4MerkleCampaigns = async (chainId: NetworkNumber): Promise<
101
140
  try {
102
141
  const opportunities = await fetchAllMerklOpportunities({
103
142
  mainProtocolId: 'aave',
104
- type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,AAVE_V4_HUB_NET_LENDING,AAVE_V4_SPOKE_SUPPLY,AAVE_V4_SPOKE_BORROW',
143
+ type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,AAVE_V4_SPOKE_SUPPLY,AAVE_V4_SPOKE_BORROW,AAVE_V4_HUB_NET_LENDING,AAVE_V4_HUB_NET_BORROWING,AAVE_V4_SPOKE_NET_LENDING,AAVE_V4_SPOKE_NET_BORROWING',
105
144
  status: OpportunityStatus.LIVE,
106
145
  campaigns: 'true',
107
146
  });
@@ -34,7 +34,9 @@ export interface IncentiveData {
34
34
  apy: string,
35
35
  incentiveKind?: IncentiveKind;
36
36
  source?: IncentiveSource;
37
+ name?: string;
37
38
  description?: string;
39
+ endTimestamp?: number;
38
40
  eligibilityId?: IncentiveEligibilityId;
39
41
  }
40
42
 
@@ -14,8 +14,6 @@ export enum OpportunityStatus {
14
14
  export type MerklCampaign = {
15
15
  id: string;
16
16
  campaignId: string;
17
- startTimestamp?: number;
18
- endTimestamp?: number;
19
17
  /**
20
18
  * Merkl-internal `id` of the parent campaign — set on child campaigns, which re-publish a hub
21
19
  * (parent) campaign's reward scoped to a single spoke reserve. Absent on standalone campaigns,
@@ -23,12 +21,32 @@ export type MerklCampaign = {
23
21
  */
24
22
  parentCampaignId?: string;
25
23
  childCampaignIds?: string[];
24
+ startTimestamp?: number;
25
+ endTimestamp?: number;
26
26
  params?: {
27
27
  reserveId?: string | number;
28
28
  spokeAddress?: EthAddress;
29
29
  hubAddress?: EthAddress;
30
30
  hubAssetId?: string | number;
31
31
  assetId?: string | number;
32
+ /**
33
+ * AAVE_V4_HUB_NET_* campaigns scope per hub: the reward applies to the ids on the campaign's
34
+ * own side (`lendingAssetIds` for LEND, `borrowAssetIds` for BORROW) — the opposite-side ids
35
+ * only describe positions that reduce the net accrual, so they never map to a reward.
36
+ */
37
+ hubs?: {
38
+ hubAddress: EthAddress;
39
+ assets?: { symbol: string; assetId: string | number; decimals?: number; underlyingToken?: EthAddress }[];
40
+ lendingAssetIds?: (string | number)[];
41
+ borrowAssetIds?: (string | number)[];
42
+ }[];
43
+ /** AAVE_V4_SPOKE_NET_* campaigns scope per spoke, with the same own-side rule as `hubs`. */
44
+ spokes?: {
45
+ spokeAddress: EthAddress;
46
+ spokeName?: string;
47
+ supplyTokens?: { symbol: string; reserveId: string | number; hubAddress?: EthAddress; hubAssetId?: string | number; underlyingToken?: EthAddress }[];
48
+ borrowTokens?: { symbol: string; reserveId: string | number; hubAddress?: EthAddress; hubAssetId?: string | number; underlyingToken?: EthAddress }[];
49
+ }[];
32
50
  distributionMethodParameters?: {
33
51
  distributionMethod?: string;
34
52
  distributionSettings?: {