@defisaver/positions-sdk 2.1.156 → 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.
- package/cjs/aaveV4/merkl.js +72 -14
- package/cjs/types/common.d.ts +2 -0
- package/cjs/types/merkl.d.ts +45 -0
- package/esm/aaveV4/merkl.js +70 -15
- package/esm/types/common.d.ts +2 -0
- package/esm/types/merkl.d.ts +45 -0
- package/package.json +1 -1
- package/src/aaveV4/merkl.ts +71 -10
- package/src/types/common.ts +2 -0
- package/src/types/merkl.ts +28 -0
package/cjs/aaveV4/merkl.js
CHANGED
|
@@ -8,8 +8,12 @@ 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");
|
|
@@ -17,6 +21,10 @@ const types_1 = require("../types");
|
|
|
17
21
|
* Merkl tags Aave V4 reward campaigns by scope via the `type` field:
|
|
18
22
|
* - AAVE_V4_HUB_SUPPLY / AAVE_V4_HUB_BORROW → reward tied to a hub asset
|
|
19
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)
|
|
20
28
|
* Embedded campaign params provide the exact on-chain identifiers. Token addresses cannot safely
|
|
21
29
|
* identify Aave V4 rewards because one spoke can expose the same underlying from multiple hubs.
|
|
22
30
|
* Each stored reward is also tagged with campaign identity (`campaignIds`/`parentCampaignIds`) so
|
|
@@ -32,19 +40,40 @@ const buildIncentive = (opportunity) => {
|
|
|
32
40
|
apy: (0, moneymarket_1.aprToApy)(opportunity.apr),
|
|
33
41
|
token,
|
|
34
42
|
incentiveKind: types_1.IncentiveKind.Reward,
|
|
35
|
-
|
|
43
|
+
source: types_1.IncentiveSource.Merkl,
|
|
44
|
+
name: opportunity.name,
|
|
45
|
+
description: opportunity.description || `Eligible for ${token} rewards through Merkl.`,
|
|
36
46
|
};
|
|
37
47
|
};
|
|
38
48
|
const buildAaveV4MerklRewardMap = (opportunities, chainId) => {
|
|
39
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));
|
|
40
59
|
opportunities
|
|
41
60
|
.filter((o) => o.chainId === chainId)
|
|
42
61
|
.filter((o) => o.status === types_1.OpportunityStatus.LIVE)
|
|
43
62
|
.filter((o) => typeof o.type === 'string' && o.type.startsWith('AAVE_V4_'))
|
|
44
63
|
.forEach((o) => {
|
|
45
|
-
var _a, _b;
|
|
64
|
+
var _a, _b, _c;
|
|
46
65
|
const side = o.action === types_1.OpportunityAction.BORROW ? types_1.IncentiveSide.Borrow : types_1.IncentiveSide.Supply;
|
|
47
|
-
const
|
|
66
|
+
const now = Date.now() / 1000;
|
|
67
|
+
const methods = (_a = o.campaigns) === null || _a === void 0 ? void 0 : _a.filter((c) => (c.startTimestamp === undefined || c.startTimestamp <= now)
|
|
68
|
+
&& (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; });
|
|
69
|
+
// An opportunity-level APR cannot be split between simultaneous net and additive campaigns.
|
|
70
|
+
if (side === types_1.IncentiveSide.Supply && (methods === null || methods === void 0 ? void 0 : methods.includes('AAVE_V4_NET_APR'))
|
|
71
|
+
&& methods.some((method) => !!method && method !== 'AAVE_V4_NET_APR'))
|
|
72
|
+
return;
|
|
73
|
+
const incentive = Object.assign(Object.assign({}, buildIncentive(o)), {
|
|
74
|
+
// Missing methods keep the existing target-yield behavior.
|
|
75
|
+
isAdditiveReward: side === types_1.IncentiveSide.Supply && !!(methods === null || methods === void 0 ? void 0 : methods.length)
|
|
76
|
+
&& methods.every((method) => !!method && method !== 'AAVE_V4_NET_APR') });
|
|
48
77
|
// one opportunity can span several campaigns (e.g. renewed periods), so campaign identity is
|
|
49
78
|
// collected per scope key before the reward entries are written
|
|
50
79
|
const idsByKey = {};
|
|
@@ -55,31 +84,58 @@ const buildAaveV4MerklRewardMap = (opportunities, chainId) => {
|
|
|
55
84
|
idsByKey[key].campaignIds.add(campaign.id);
|
|
56
85
|
if (campaign.parentCampaignId)
|
|
57
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);
|
|
58
90
|
};
|
|
59
91
|
if (o.type.includes('HUB')) {
|
|
60
|
-
(
|
|
61
|
-
var _a;
|
|
62
|
-
if (
|
|
92
|
+
(_b = o.campaigns) === null || _b === void 0 ? void 0 : _b.forEach((c) => {
|
|
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
|
+
});
|
|
63
101
|
return;
|
|
64
|
-
|
|
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;
|
|
105
|
+
if (!hubAddress || assetId === undefined || assetId === null)
|
|
106
|
+
return;
|
|
107
|
+
collect(scopeKey(hubAddress, assetId), c);
|
|
65
108
|
});
|
|
66
109
|
Object.entries(idsByKey).forEach(([key, ids]) => {
|
|
67
110
|
if (!result.hub[key])
|
|
68
111
|
result.hub[key] = {};
|
|
69
|
-
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] })];
|
|
70
113
|
});
|
|
71
114
|
}
|
|
72
115
|
else if (o.type.includes('SPOKE')) {
|
|
73
|
-
(
|
|
74
|
-
var _a;
|
|
75
|
-
if (
|
|
116
|
+
(_c = o.campaigns) === null || _c === void 0 ? void 0 : _c.forEach((c) => {
|
|
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)
|
|
76
132
|
return;
|
|
77
133
|
collect(scopeKey(c.params.spokeAddress, c.params.reserveId), c);
|
|
78
134
|
});
|
|
79
135
|
Object.entries(idsByKey).forEach(([key, ids]) => {
|
|
80
136
|
if (!result.spoke[key])
|
|
81
137
|
result.spoke[key] = {};
|
|
82
|
-
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] })];
|
|
83
139
|
});
|
|
84
140
|
}
|
|
85
141
|
});
|
|
@@ -90,7 +146,7 @@ const getAaveV4MerkleCampaigns = (chainId) => __awaiter(void 0, void 0, void 0,
|
|
|
90
146
|
try {
|
|
91
147
|
const opportunities = yield (0, merkl_1.fetchAllMerklOpportunities)({
|
|
92
148
|
mainProtocolId: 'aave',
|
|
93
|
-
type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,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',
|
|
94
150
|
status: types_1.OpportunityStatus.LIVE,
|
|
95
151
|
campaigns: 'true',
|
|
96
152
|
});
|
|
@@ -112,6 +168,8 @@ const attachAaveV4MerklIncentives = (asset, spokeAddress, campaigns) => {
|
|
|
112
168
|
const baseBorrow = asset.borrowIncentives || [];
|
|
113
169
|
const spokeScoped = spokeAddress ? campaigns.spoke[scopeKey(spokeAddress, asset.reserveId)] : undefined;
|
|
114
170
|
const hubScoped = asset.hub ? campaigns.hub[scopeKey(asset.hub, asset.assetId)] : undefined;
|
|
115
|
-
|
|
171
|
+
// Net-APR campaigns top up native yield; additive campaigns pay their APR on top.
|
|
172
|
+
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() })));
|
|
173
|
+
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
174
|
};
|
|
117
175
|
exports.attachAaveV4MerklIncentives = attachAaveV4MerklIncentives;
|
package/cjs/types/common.d.ts
CHANGED
|
@@ -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;
|
package/cjs/types/merkl.d.ts
CHANGED
|
@@ -18,12 +18,56 @@ export type MerklCampaign = {
|
|
|
18
18
|
*/
|
|
19
19
|
parentCampaignId?: string;
|
|
20
20
|
childCampaignIds?: string[];
|
|
21
|
+
startTimestamp?: number;
|
|
22
|
+
endTimestamp?: number;
|
|
21
23
|
params?: {
|
|
22
24
|
reserveId?: string | number;
|
|
23
25
|
spokeAddress?: EthAddress;
|
|
24
26
|
hubAddress?: EthAddress;
|
|
25
27
|
hubAssetId?: string | number;
|
|
26
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
|
+
}[];
|
|
64
|
+
distributionMethodParameters?: {
|
|
65
|
+
distributionMethod?: string;
|
|
66
|
+
distributionSettings?: {
|
|
67
|
+
hubAddress?: EthAddress;
|
|
68
|
+
assetId?: string | number;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
27
71
|
};
|
|
28
72
|
};
|
|
29
73
|
export type MerklOpportunity = {
|
|
@@ -102,6 +146,7 @@ export type MerkleRewardMap = Record<EthAddress, {
|
|
|
102
146
|
export type AaveV4MerklIncentive = IncentiveData & {
|
|
103
147
|
campaignIds?: string[];
|
|
104
148
|
parentCampaignIds?: string[];
|
|
149
|
+
isAdditiveReward?: boolean;
|
|
105
150
|
};
|
|
106
151
|
export type AaveV4MerklScopedReward = {
|
|
107
152
|
[side in IncentiveSide]?: AaveV4MerklIncentive[];
|
package/esm/aaveV4/merkl.js
CHANGED
|
@@ -7,13 +7,18 @@ 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
|
-
import { IncentiveKind, IncentiveSide, OpportunityAction, OpportunityStatus, } from '../types';
|
|
13
|
+
import { IncentiveKind, IncentiveSide, IncentiveSource, OpportunityAction, OpportunityStatus, } from '../types';
|
|
13
14
|
/**
|
|
14
15
|
* Merkl tags Aave V4 reward campaigns by scope via the `type` field:
|
|
15
16
|
* - AAVE_V4_HUB_SUPPLY / AAVE_V4_HUB_BORROW → reward tied to a hub asset
|
|
16
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)
|
|
17
22
|
* Embedded campaign params provide the exact on-chain identifiers. Token addresses cannot safely
|
|
18
23
|
* identify Aave V4 rewards because one spoke can expose the same underlying from multiple hubs.
|
|
19
24
|
* Each stored reward is also tagged with campaign identity (`campaignIds`/`parentCampaignIds`) so
|
|
@@ -29,19 +34,40 @@ const buildIncentive = (opportunity) => {
|
|
|
29
34
|
apy: aprToApy(opportunity.apr),
|
|
30
35
|
token,
|
|
31
36
|
incentiveKind: IncentiveKind.Reward,
|
|
32
|
-
|
|
37
|
+
source: IncentiveSource.Merkl,
|
|
38
|
+
name: opportunity.name,
|
|
39
|
+
description: opportunity.description || `Eligible for ${token} rewards through Merkl.`,
|
|
33
40
|
};
|
|
34
41
|
};
|
|
35
42
|
export const buildAaveV4MerklRewardMap = (opportunities, chainId) => {
|
|
36
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));
|
|
37
53
|
opportunities
|
|
38
54
|
.filter((o) => o.chainId === chainId)
|
|
39
55
|
.filter((o) => o.status === OpportunityStatus.LIVE)
|
|
40
56
|
.filter((o) => typeof o.type === 'string' && o.type.startsWith('AAVE_V4_'))
|
|
41
57
|
.forEach((o) => {
|
|
42
|
-
var _a, _b;
|
|
58
|
+
var _a, _b, _c;
|
|
43
59
|
const side = o.action === OpportunityAction.BORROW ? IncentiveSide.Borrow : IncentiveSide.Supply;
|
|
44
|
-
const
|
|
60
|
+
const now = Date.now() / 1000;
|
|
61
|
+
const methods = (_a = o.campaigns) === null || _a === void 0 ? void 0 : _a.filter((c) => (c.startTimestamp === undefined || c.startTimestamp <= now)
|
|
62
|
+
&& (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; });
|
|
63
|
+
// An opportunity-level APR cannot be split between simultaneous net and additive campaigns.
|
|
64
|
+
if (side === IncentiveSide.Supply && (methods === null || methods === void 0 ? void 0 : methods.includes('AAVE_V4_NET_APR'))
|
|
65
|
+
&& methods.some((method) => !!method && method !== 'AAVE_V4_NET_APR'))
|
|
66
|
+
return;
|
|
67
|
+
const incentive = Object.assign(Object.assign({}, buildIncentive(o)), {
|
|
68
|
+
// Missing methods keep the existing target-yield behavior.
|
|
69
|
+
isAdditiveReward: side === IncentiveSide.Supply && !!(methods === null || methods === void 0 ? void 0 : methods.length)
|
|
70
|
+
&& methods.every((method) => !!method && method !== 'AAVE_V4_NET_APR') });
|
|
45
71
|
// one opportunity can span several campaigns (e.g. renewed periods), so campaign identity is
|
|
46
72
|
// collected per scope key before the reward entries are written
|
|
47
73
|
const idsByKey = {};
|
|
@@ -52,31 +78,58 @@ export const buildAaveV4MerklRewardMap = (opportunities, chainId) => {
|
|
|
52
78
|
idsByKey[key].campaignIds.add(campaign.id);
|
|
53
79
|
if (campaign.parentCampaignId)
|
|
54
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);
|
|
55
84
|
};
|
|
56
85
|
if (o.type.includes('HUB')) {
|
|
57
|
-
(
|
|
58
|
-
var _a;
|
|
59
|
-
if (
|
|
86
|
+
(_b = o.campaigns) === null || _b === void 0 ? void 0 : _b.forEach((c) => {
|
|
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
|
+
});
|
|
60
95
|
return;
|
|
61
|
-
|
|
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;
|
|
99
|
+
if (!hubAddress || assetId === undefined || assetId === null)
|
|
100
|
+
return;
|
|
101
|
+
collect(scopeKey(hubAddress, assetId), c);
|
|
62
102
|
});
|
|
63
103
|
Object.entries(idsByKey).forEach(([key, ids]) => {
|
|
64
104
|
if (!result.hub[key])
|
|
65
105
|
result.hub[key] = {};
|
|
66
|
-
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] })];
|
|
67
107
|
});
|
|
68
108
|
}
|
|
69
109
|
else if (o.type.includes('SPOKE')) {
|
|
70
|
-
(
|
|
71
|
-
var _a;
|
|
72
|
-
if (
|
|
110
|
+
(_c = o.campaigns) === null || _c === void 0 ? void 0 : _c.forEach((c) => {
|
|
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)
|
|
73
126
|
return;
|
|
74
127
|
collect(scopeKey(c.params.spokeAddress, c.params.reserveId), c);
|
|
75
128
|
});
|
|
76
129
|
Object.entries(idsByKey).forEach(([key, ids]) => {
|
|
77
130
|
if (!result.spoke[key])
|
|
78
131
|
result.spoke[key] = {};
|
|
79
|
-
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] })];
|
|
80
133
|
});
|
|
81
134
|
}
|
|
82
135
|
});
|
|
@@ -86,7 +139,7 @@ export const getAaveV4MerkleCampaigns = (chainId) => __awaiter(void 0, void 0, v
|
|
|
86
139
|
try {
|
|
87
140
|
const opportunities = yield fetchAllMerklOpportunities({
|
|
88
141
|
mainProtocolId: 'aave',
|
|
89
|
-
type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,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',
|
|
90
143
|
status: OpportunityStatus.LIVE,
|
|
91
144
|
campaigns: 'true',
|
|
92
145
|
});
|
|
@@ -107,5 +160,7 @@ export const attachAaveV4MerklIncentives = (asset, spokeAddress, campaigns) => {
|
|
|
107
160
|
const baseBorrow = asset.borrowIncentives || [];
|
|
108
161
|
const spokeScoped = spokeAddress ? campaigns.spoke[scopeKey(spokeAddress, asset.reserveId)] : undefined;
|
|
109
162
|
const hubScoped = asset.hub ? campaigns.hub[scopeKey(asset.hub, asset.assetId)] : undefined;
|
|
110
|
-
|
|
163
|
+
// Net-APR campaigns top up native yield; additive campaigns pay their APR on top.
|
|
164
|
+
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() })));
|
|
165
|
+
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
166
|
};
|
package/esm/types/common.d.ts
CHANGED
|
@@ -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;
|
package/esm/types/merkl.d.ts
CHANGED
|
@@ -18,12 +18,56 @@ export type MerklCampaign = {
|
|
|
18
18
|
*/
|
|
19
19
|
parentCampaignId?: string;
|
|
20
20
|
childCampaignIds?: string[];
|
|
21
|
+
startTimestamp?: number;
|
|
22
|
+
endTimestamp?: number;
|
|
21
23
|
params?: {
|
|
22
24
|
reserveId?: string | number;
|
|
23
25
|
spokeAddress?: EthAddress;
|
|
24
26
|
hubAddress?: EthAddress;
|
|
25
27
|
hubAssetId?: string | number;
|
|
26
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
|
+
}[];
|
|
64
|
+
distributionMethodParameters?: {
|
|
65
|
+
distributionMethod?: string;
|
|
66
|
+
distributionSettings?: {
|
|
67
|
+
hubAddress?: EthAddress;
|
|
68
|
+
assetId?: string | number;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
27
71
|
};
|
|
28
72
|
};
|
|
29
73
|
export type MerklOpportunity = {
|
|
@@ -102,6 +146,7 @@ export type MerkleRewardMap = Record<EthAddress, {
|
|
|
102
146
|
export type AaveV4MerklIncentive = IncentiveData & {
|
|
103
147
|
campaignIds?: string[];
|
|
104
148
|
parentCampaignIds?: string[];
|
|
149
|
+
isAdditiveReward?: boolean;
|
|
105
150
|
};
|
|
106
151
|
export type AaveV4MerklScopedReward = {
|
|
107
152
|
[side in IncentiveSide]?: AaveV4MerklIncentive[];
|
package/package.json
CHANGED
package/src/aaveV4/merkl.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
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,
|
|
8
10
|
IncentiveSide,
|
|
11
|
+
IncentiveSource,
|
|
9
12
|
MerklCampaign,
|
|
10
13
|
MerklOpportunity,
|
|
11
14
|
OpportunityAction,
|
|
@@ -17,6 +20,10 @@ import {
|
|
|
17
20
|
* Merkl tags Aave V4 reward campaigns by scope via the `type` field:
|
|
18
21
|
* - AAVE_V4_HUB_SUPPLY / AAVE_V4_HUB_BORROW → reward tied to a hub asset
|
|
19
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)
|
|
20
27
|
* Embedded campaign params provide the exact on-chain identifiers. Token addresses cannot safely
|
|
21
28
|
* identify Aave V4 rewards because one spoke can expose the same underlying from multiple hubs.
|
|
22
29
|
* Each stored reward is also tagged with campaign identity (`campaignIds`/`parentCampaignIds`) so
|
|
@@ -33,46 +40,95 @@ const buildIncentive = (opportunity: MerklOpportunity): IncentiveData => {
|
|
|
33
40
|
apy: aprToApy(opportunity.apr),
|
|
34
41
|
token,
|
|
35
42
|
incentiveKind: IncentiveKind.Reward,
|
|
36
|
-
|
|
43
|
+
source: IncentiveSource.Merkl,
|
|
44
|
+
name: opportunity.name,
|
|
45
|
+
description: opportunity.description || `Eligible for ${token} rewards through Merkl.`,
|
|
37
46
|
};
|
|
38
47
|
};
|
|
39
48
|
|
|
40
49
|
export const buildAaveV4MerklRewardMap = (opportunities: MerklOpportunity[], chainId: NetworkNumber): AaveV4MerklRewardMap => {
|
|
41
50
|
const result: AaveV4MerklRewardMap = { hub: {}, spoke: {} };
|
|
42
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
|
+
|
|
43
61
|
opportunities
|
|
44
62
|
.filter((o) => o.chainId === chainId)
|
|
45
63
|
.filter((o) => o.status === OpportunityStatus.LIVE)
|
|
46
64
|
.filter((o) => typeof o.type === 'string' && o.type.startsWith('AAVE_V4_'))
|
|
47
65
|
.forEach((o) => {
|
|
48
66
|
const side = o.action === OpportunityAction.BORROW ? IncentiveSide.Borrow : IncentiveSide.Supply;
|
|
49
|
-
const
|
|
67
|
+
const now = Date.now() / 1000;
|
|
68
|
+
const methods = o.campaigns
|
|
69
|
+
?.filter((c) => (c.startTimestamp === undefined || c.startTimestamp <= now)
|
|
70
|
+
&& (c.endTimestamp === undefined || c.endTimestamp > now))
|
|
71
|
+
.map((c) => c.params?.distributionMethodParameters?.distributionMethod);
|
|
72
|
+
// An opportunity-level APR cannot be split between simultaneous net and additive campaigns.
|
|
73
|
+
if (side === IncentiveSide.Supply && methods?.includes('AAVE_V4_NET_APR')
|
|
74
|
+
&& methods.some((method) => !!method && method !== 'AAVE_V4_NET_APR')) return;
|
|
75
|
+
const incentive = {
|
|
76
|
+
...buildIncentive(o),
|
|
77
|
+
// Missing methods keep the existing target-yield behavior.
|
|
78
|
+
isAdditiveReward: side === IncentiveSide.Supply && !!methods?.length
|
|
79
|
+
&& methods.every((method) => !!method && method !== 'AAVE_V4_NET_APR'),
|
|
80
|
+
};
|
|
50
81
|
// one opportunity can span several campaigns (e.g. renewed periods), so campaign identity is
|
|
51
82
|
// collected per scope key before the reward entries are written
|
|
52
|
-
const idsByKey: Record<string, { campaignIds: Set<string>, parentCampaignIds: Set<string
|
|
83
|
+
const idsByKey: Record<string, { campaignIds: Set<string>, parentCampaignIds: Set<string>, endTimestamp?: number }> = {};
|
|
53
84
|
const collect = (key: string, campaign: MerklCampaign) => {
|
|
54
85
|
if (!idsByKey[key]) idsByKey[key] = { campaignIds: new Set(), parentCampaignIds: new Set() };
|
|
55
86
|
if (campaign.id) idsByKey[key].campaignIds.add(campaign.id);
|
|
56
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);
|
|
57
90
|
};
|
|
58
91
|
|
|
59
92
|
if (o.type.includes('HUB')) {
|
|
60
93
|
o.campaigns?.forEach((c) => {
|
|
61
|
-
if (
|
|
62
|
-
|
|
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
|
+
}
|
|
102
|
+
const hubAddress = c.params?.hubAddress ?? c.params?.distributionMethodParameters?.distributionSettings?.hubAddress;
|
|
103
|
+
const assetId = c.params?.assetId ?? c.params?.distributionMethodParameters?.distributionSettings?.assetId;
|
|
104
|
+
if (!hubAddress || assetId === undefined || assetId === null) return;
|
|
105
|
+
collect(scopeKey(hubAddress, assetId), c);
|
|
63
106
|
});
|
|
64
107
|
Object.entries(idsByKey).forEach(([key, ids]) => {
|
|
65
108
|
if (!result.hub[key]) result.hub[key] = {};
|
|
66
|
-
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] }];
|
|
67
110
|
});
|
|
68
111
|
} else if (o.type.includes('SPOKE')) {
|
|
69
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
|
+
}
|
|
70
124
|
if (!c.params?.spokeAddress || c.params.reserveId === undefined || c.params.reserveId === null) return;
|
|
71
125
|
collect(scopeKey(c.params.spokeAddress, c.params.reserveId), c);
|
|
72
126
|
});
|
|
73
127
|
Object.entries(idsByKey).forEach(([key, ids]) => {
|
|
74
128
|
if (!result.spoke[key]) result.spoke[key] = {};
|
|
75
|
-
result.spoke[key][side] = [...(result.spoke[key][side] || []), {
|
|
129
|
+
result.spoke[key][side] = [...(result.spoke[key][side] || []), {
|
|
130
|
+
...incentive, endTimestamp: ids.endTimestamp, campaignIds: [...ids.campaignIds], parentCampaignIds: [...ids.parentCampaignIds],
|
|
131
|
+
}];
|
|
76
132
|
});
|
|
77
133
|
}
|
|
78
134
|
});
|
|
@@ -84,7 +140,7 @@ export const getAaveV4MerkleCampaigns = async (chainId: NetworkNumber): Promise<
|
|
|
84
140
|
try {
|
|
85
141
|
const opportunities = await fetchAllMerklOpportunities({
|
|
86
142
|
mainProtocolId: 'aave',
|
|
87
|
-
type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,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',
|
|
88
144
|
status: OpportunityStatus.LIVE,
|
|
89
145
|
campaigns: 'true',
|
|
90
146
|
});
|
|
@@ -105,12 +161,17 @@ export const attachAaveV4MerklIncentives = (asset: AaveV4ReserveAssetData, spoke
|
|
|
105
161
|
|
|
106
162
|
const spokeScoped = spokeAddress ? campaigns.spoke[scopeKey(spokeAddress, asset.reserveId)] : undefined;
|
|
107
163
|
const hubScoped = asset.hub ? campaigns.hub[scopeKey(asset.hub, asset.assetId)] : undefined;
|
|
164
|
+
// Net-APR campaigns top up native yield; additive campaigns pay their APR on top.
|
|
165
|
+
const supplyRewards = (rewards: AaveV4MerklIncentive[] = []) => rewards.map((reward) => ({
|
|
166
|
+
...reward,
|
|
167
|
+
apy: reward.isAdditiveReward ? reward.apy : Dec.max(0, new Dec(reward.apy).minus(asset.supplyRate || 0)).toString(),
|
|
168
|
+
}));
|
|
108
169
|
|
|
109
170
|
return {
|
|
110
171
|
...asset,
|
|
111
|
-
spokeSupplyIncentives: spokeScoped?.supply?.length ? [...baseSupply, ...spokeScoped.supply] : baseSupply,
|
|
172
|
+
spokeSupplyIncentives: spokeScoped?.supply?.length ? [...baseSupply, ...supplyRewards(spokeScoped.supply)] : baseSupply,
|
|
112
173
|
spokeBorrowIncentives: spokeScoped?.borrow?.length ? [...baseBorrow, ...spokeScoped.borrow] : baseBorrow,
|
|
113
|
-
hubSupplyIncentives: hubScoped?.supply?.length ? [...baseSupply, ...hubScoped.supply] : baseSupply,
|
|
174
|
+
hubSupplyIncentives: hubScoped?.supply?.length ? [...baseSupply, ...supplyRewards(hubScoped.supply)] : baseSupply,
|
|
114
175
|
hubBorrowIncentives: hubScoped?.borrow?.length ? [...baseBorrow, ...hubScoped.borrow] : baseBorrow,
|
|
115
176
|
};
|
|
116
177
|
};
|
package/src/types/common.ts
CHANGED
package/src/types/merkl.ts
CHANGED
|
@@ -21,12 +21,39 @@ export type MerklCampaign = {
|
|
|
21
21
|
*/
|
|
22
22
|
parentCampaignId?: string;
|
|
23
23
|
childCampaignIds?: string[];
|
|
24
|
+
startTimestamp?: number;
|
|
25
|
+
endTimestamp?: number;
|
|
24
26
|
params?: {
|
|
25
27
|
reserveId?: string | number;
|
|
26
28
|
spokeAddress?: EthAddress;
|
|
27
29
|
hubAddress?: EthAddress;
|
|
28
30
|
hubAssetId?: string | number;
|
|
29
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
|
+
}[];
|
|
50
|
+
distributionMethodParameters?: {
|
|
51
|
+
distributionMethod?: string;
|
|
52
|
+
distributionSettings?: {
|
|
53
|
+
hubAddress?: EthAddress;
|
|
54
|
+
assetId?: string | number;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
30
57
|
};
|
|
31
58
|
};
|
|
32
59
|
|
|
@@ -100,6 +127,7 @@ export type MerkleRewardMap = Record<EthAddress, { supply?: MerkleRewardInfo; bo
|
|
|
100
127
|
export type AaveV4MerklIncentive = IncentiveData & {
|
|
101
128
|
campaignIds?: string[];
|
|
102
129
|
parentCampaignIds?: string[];
|
|
130
|
+
isAdditiveReward?: boolean;
|
|
103
131
|
};
|
|
104
132
|
|
|
105
133
|
export type AaveV4MerklScopedReward = { [side in IncentiveSide]?: AaveV4MerklIncentive[] };
|