@defisaver/positions-sdk 2.1.142-dev → 2.1.143
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/aaveV3/merkl.js +4 -7
- package/cjs/aaveV4/merkl.js +5 -6
- package/cjs/claiming/aaveV3.d.ts +2 -1
- package/cjs/claiming/aaveV3.js +13 -4
- package/cjs/compoundV3/merkl.js +4 -6
- package/cjs/fluid/index.d.ts +9 -1
- package/cjs/fluid/index.js +10 -4
- package/cjs/fluid/merkl.d.ts +7 -4
- package/cjs/fluid/merkl.js +22 -16
- package/cjs/helpers/fluidHelpers/index.d.ts +3 -1
- package/cjs/helpers/fluidHelpers/index.js +37 -4
- package/cjs/morphoBlue/index.d.ts +1 -1
- package/cjs/morphoBlue/index.js +2 -2
- package/cjs/morphoBlue/merkl.d.ts +6 -4
- package/cjs/morphoBlue/merkl.js +17 -16
- package/cjs/portfolio/index.js +5 -5
- package/cjs/services/merkl.d.ts +6 -0
- package/cjs/services/merkl.js +39 -0
- package/cjs/types/claiming.d.ts +12 -1
- package/cjs/types/claiming.js +2 -0
- package/cjs/types/common.d.ts +4 -0
- package/cjs/types/common.js +5 -1
- package/cjs/types/fluid.d.ts +2 -0
- package/esm/aaveV3/merkl.js +5 -8
- package/esm/aaveV4/merkl.js +5 -6
- package/esm/claiming/aaveV3.d.ts +2 -1
- package/esm/claiming/aaveV3.js +12 -4
- package/esm/compoundV3/merkl.js +5 -7
- package/esm/fluid/index.d.ts +9 -1
- package/esm/fluid/index.js +9 -3
- package/esm/fluid/merkl.d.ts +7 -4
- package/esm/fluid/merkl.js +19 -15
- package/esm/helpers/fluidHelpers/index.d.ts +3 -1
- package/esm/helpers/fluidHelpers/index.js +38 -6
- package/esm/morphoBlue/index.d.ts +1 -1
- package/esm/morphoBlue/index.js +1 -1
- package/esm/morphoBlue/merkl.d.ts +6 -4
- package/esm/morphoBlue/merkl.js +16 -15
- package/esm/portfolio/index.js +6 -6
- package/esm/services/merkl.d.ts +6 -0
- package/esm/services/merkl.js +35 -0
- package/esm/types/claiming.d.ts +12 -1
- package/esm/types/claiming.js +2 -0
- package/esm/types/common.d.ts +4 -0
- package/esm/types/common.js +4 -0
- package/esm/types/fluid.d.ts +2 -0
- package/package.json +1 -1
- package/src/aaveV3/merkl.ts +5 -8
- package/src/aaveV4/merkl.ts +5 -5
- package/src/claiming/aaveV3.ts +19 -7
- package/src/compoundV3/merkl.ts +5 -6
- package/src/fluid/index.ts +9 -3
- package/src/fluid/merkl.ts +18 -13
- package/src/helpers/fluidHelpers/index.ts +68 -6
- package/src/morphoBlue/index.ts +1 -1
- package/src/morphoBlue/merkl.ts +16 -15
- package/src/portfolio/index.ts +6 -6
- package/src/services/merkl.ts +31 -0
- package/src/types/claiming.ts +13 -0
- package/src/types/common.ts +5 -0
- package/src/types/fluid.ts +3 -1
package/esm/portfolio/index.js
CHANGED
|
@@ -26,7 +26,7 @@ import { _getLiquityTroveInfo, getLiquityStakingData } from '../liquity';
|
|
|
26
26
|
import { _getLiquityV2MarketData, getLiquitySAndYBold, getLiquityV2Staking } from '../liquityV2';
|
|
27
27
|
import { _getAllUserEarnPositionsWithFTokens, _getUserPositionsPortfolio } from '../fluid';
|
|
28
28
|
import { getUmbrellaData } from '../umbrella';
|
|
29
|
-
import {
|
|
29
|
+
import { getMerklUnclaimedRewards, getUnclaimedRewardsForAllMarkets } from '../claiming/aaveV3';
|
|
30
30
|
import { getCompoundV3Rewards } from '../claiming/compV3';
|
|
31
31
|
import { fetchSparkAirdropRewards, fetchSparkRewards } from '../claiming/spark';
|
|
32
32
|
import { getKingRewards } from '../claiming/king';
|
|
@@ -114,7 +114,7 @@ export function getPortfolioData(provider_1, network_1, defaultProvider_1, addre
|
|
|
114
114
|
},
|
|
115
115
|
};
|
|
116
116
|
rewardsData[address.toLowerCase()] = {
|
|
117
|
-
|
|
117
|
+
merkl: {},
|
|
118
118
|
aaveV3: {},
|
|
119
119
|
compV3: {},
|
|
120
120
|
spark: {},
|
|
@@ -335,12 +335,12 @@ export function getPortfolioData(provider_1, network_1, defaultProvider_1, addre
|
|
|
335
335
|
}))).flat(),
|
|
336
336
|
...addresses.map((address) => __awaiter(this, void 0, void 0, function* () {
|
|
337
337
|
try {
|
|
338
|
-
const
|
|
339
|
-
rewardsData[address.toLowerCase()].
|
|
338
|
+
const merklData = yield getMerklUnclaimedRewards(address, network);
|
|
339
|
+
rewardsData[address.toLowerCase()].merkl = { error: '', data: merklData };
|
|
340
340
|
}
|
|
341
341
|
catch (error) {
|
|
342
|
-
console.error(`Error fetching
|
|
343
|
-
rewardsData[address.toLowerCase()].
|
|
342
|
+
console.error(`Error fetching Merkl rewards data for address ${address}:`, error);
|
|
343
|
+
rewardsData[address.toLowerCase()].merkl = { error: `Error fetching Merkl rewards data for address ${address}`, data: null };
|
|
344
344
|
}
|
|
345
345
|
})),
|
|
346
346
|
...aaveV3Markets.map(market => addresses.map((address) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { MerklOpportunity } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Fetches every page matching the query. Merkl returns 20 opportunities by default, so omitting
|
|
4
|
+
* `items` and advancing `page` avoids imposing a client-side maximum on the result set.
|
|
5
|
+
*/
|
|
6
|
+
export declare const fetchAllMerklOpportunities: (query: Record<string, string>) => Promise<MerklOpportunity[]>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { LONGER_TIMEOUT } from './utils';
|
|
11
|
+
const MERKL_OPPORTUNITIES_URL = 'https://fe.defisaver.com/api/merkl/opportunities';
|
|
12
|
+
/**
|
|
13
|
+
* Fetches every page matching the query. Merkl returns 20 opportunities by default, so omitting
|
|
14
|
+
* `items` and advancing `page` avoids imposing a client-side maximum on the result set.
|
|
15
|
+
*/
|
|
16
|
+
export const fetchAllMerklOpportunities = (query) => __awaiter(void 0, void 0, void 0, function* () {
|
|
17
|
+
const opportunities = [];
|
|
18
|
+
let page = 0;
|
|
19
|
+
let pageOpportunities;
|
|
20
|
+
do {
|
|
21
|
+
const searchParams = new URLSearchParams(Object.assign(Object.assign({}, query), { page: page.toString() }));
|
|
22
|
+
const res = yield fetch(`${MERKL_OPPORTUNITIES_URL}?${searchParams}`, {
|
|
23
|
+
signal: AbortSignal.timeout(LONGER_TIMEOUT),
|
|
24
|
+
});
|
|
25
|
+
if (!res.ok)
|
|
26
|
+
throw new Error(`Failed to fetch Merkl opportunities page ${page}`);
|
|
27
|
+
const data = yield res.json(); // eslint-disable-line no-await-in-loop
|
|
28
|
+
if (!Array.isArray(data))
|
|
29
|
+
throw new Error(`Invalid Merkl opportunities response on page ${page}`);
|
|
30
|
+
pageOpportunities = data;
|
|
31
|
+
opportunities.push(...pageOpportunities);
|
|
32
|
+
page += 1;
|
|
33
|
+
} while (pageOpportunities.length > 0);
|
|
34
|
+
return opportunities;
|
|
35
|
+
});
|
package/esm/types/claiming.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export declare enum ClaimType {
|
|
|
4
4
|
AAVE_REWARDS = "AAVE_REWARDS",
|
|
5
5
|
/** Merit rewards from AAVE (various tokens) */
|
|
6
6
|
AAVE_MERIT_REWARDS = "AAVE_MERIT_REWARDS",
|
|
7
|
+
/** Rewards distributed through Merkl across supported protocols */
|
|
8
|
+
MERKL_REWARDS = "MERKL_REWARDS",
|
|
7
9
|
/** Rewards from Compound V3 (only in COMP) */
|
|
8
10
|
COMPOUND_V3_COMP = "COMPOUND_V3_COMP",
|
|
9
11
|
/** Rewards from Spark (wstETH only for now) */
|
|
@@ -43,6 +45,15 @@ export type AaveMeritRewardsClaimableToken = _ClaimableTokenPartial & {
|
|
|
43
45
|
unclaimed: string;
|
|
44
46
|
};
|
|
45
47
|
};
|
|
48
|
+
export type MerklRewardsClaimableToken = _ClaimableTokenPartial & {
|
|
49
|
+
claimType: ClaimType.MERKL_REWARDS;
|
|
50
|
+
additionalClaimFields: {
|
|
51
|
+
accumulated: string;
|
|
52
|
+
proof: string[];
|
|
53
|
+
decimals: string;
|
|
54
|
+
unclaimed: string;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
46
57
|
export type SparkRewardsClaimableToken = _ClaimableTokenPartial & {
|
|
47
58
|
claimType: ClaimType.SPARK_REWARDS;
|
|
48
59
|
additionalClaimFields: {
|
|
@@ -91,5 +102,5 @@ export type UniswapAirdropClaimableToken = _ClaimableTokenPartial & {
|
|
|
91
102
|
proof: string[];
|
|
92
103
|
};
|
|
93
104
|
};
|
|
94
|
-
export type ClaimableToken = AaveRewardsClaimableToken | AaveMeritRewardsClaimableToken | CompoundV3CompClaimableToken | SparkRewardsClaimableToken | KingRewardsClaimableToken | SparkAirdropClaimableToken | SparkWstEthRewardsClaimableToken | EthenaAirdropClaimableToken;
|
|
105
|
+
export type ClaimableToken = AaveRewardsClaimableToken | AaveMeritRewardsClaimableToken | MerklRewardsClaimableToken | CompoundV3CompClaimableToken | SparkRewardsClaimableToken | KingRewardsClaimableToken | SparkAirdropClaimableToken | SparkWstEthRewardsClaimableToken | EthenaAirdropClaimableToken;
|
|
95
106
|
export {};
|
package/esm/types/claiming.js
CHANGED
|
@@ -4,6 +4,8 @@ export var ClaimType;
|
|
|
4
4
|
ClaimType["AAVE_REWARDS"] = "AAVE_REWARDS";
|
|
5
5
|
/** Merit rewards from AAVE (various tokens) */
|
|
6
6
|
ClaimType["AAVE_MERIT_REWARDS"] = "AAVE_MERIT_REWARDS";
|
|
7
|
+
/** Rewards distributed through Merkl across supported protocols */
|
|
8
|
+
ClaimType["MERKL_REWARDS"] = "MERKL_REWARDS";
|
|
7
9
|
/** Rewards from Compound V3 (only in COMP) */
|
|
8
10
|
ClaimType["COMPOUND_V3_COMP"] = "COMPOUND_V3_COMP";
|
|
9
11
|
/** Rewards from Spark (wstETH only for now) */
|
package/esm/types/common.d.ts
CHANGED
|
@@ -6,6 +6,9 @@ export declare enum IncentiveSide {
|
|
|
6
6
|
Supply = "supply",
|
|
7
7
|
Borrow = "borrow"
|
|
8
8
|
}
|
|
9
|
+
export declare enum IncentiveSource {
|
|
10
|
+
Merkl = "merkl"
|
|
11
|
+
}
|
|
9
12
|
export declare enum IncentiveEligibilityId {
|
|
10
13
|
AaveV3EthenaLiquidLeverage = "0x0dC599DBB180E64E42b87332FDeC3a551445ea44BORROW_BL",
|
|
11
14
|
AaveV3ArbitrumEthSupply = "0x5d16261c6715a653248269861bbacf68a9774cde",
|
|
@@ -25,6 +28,7 @@ export interface IncentiveData {
|
|
|
25
28
|
token: string;
|
|
26
29
|
apy: string;
|
|
27
30
|
incentiveKind?: IncentiveKind;
|
|
31
|
+
source?: IncentiveSource;
|
|
28
32
|
description?: string;
|
|
29
33
|
eligibilityId?: IncentiveEligibilityId;
|
|
30
34
|
}
|
package/esm/types/common.js
CHANGED
|
@@ -8,6 +8,10 @@ export var IncentiveSide;
|
|
|
8
8
|
IncentiveSide["Supply"] = "supply";
|
|
9
9
|
IncentiveSide["Borrow"] = "borrow";
|
|
10
10
|
})(IncentiveSide || (IncentiveSide = {}));
|
|
11
|
+
export var IncentiveSource;
|
|
12
|
+
(function (IncentiveSource) {
|
|
13
|
+
IncentiveSource["Merkl"] = "merkl";
|
|
14
|
+
})(IncentiveSource || (IncentiveSource = {}));
|
|
11
15
|
export var IncentiveEligibilityId;
|
|
12
16
|
(function (IncentiveEligibilityId) {
|
|
13
17
|
IncentiveEligibilityId["AaveV3EthenaLiquidLeverage"] = "0x0dC599DBB180E64E42b87332FDeC3a551445ea44BORROW_BL";
|
package/esm/types/fluid.d.ts
CHANGED
|
@@ -319,6 +319,8 @@ export interface FluidAggregatedVaultData {
|
|
|
319
319
|
leftToBorrowUsd: string;
|
|
320
320
|
netApy: string;
|
|
321
321
|
incentiveUsd: string;
|
|
322
|
+
merklSupplyIncentives: IncentiveData[];
|
|
323
|
+
merklBorrowIncentives: IncentiveData[];
|
|
322
324
|
ratio: string;
|
|
323
325
|
collRatio: string;
|
|
324
326
|
minRatio: string;
|
package/package.json
CHANGED
package/src/aaveV3/merkl.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { aprToApy } from '../moneymarket';
|
|
2
|
-
import {
|
|
2
|
+
import { fetchAllMerklOpportunities } from '../services/merkl';
|
|
3
|
+
import { wethToEth } from '../services/utils';
|
|
3
4
|
import {
|
|
4
5
|
MerkleRewardMap, MerklOpportunity, OpportunityAction, OpportunityStatus,
|
|
5
6
|
EthAddress, NetworkNumber,
|
|
@@ -31,13 +32,10 @@ export const formatAaveAsset = (_symbol: string) => {
|
|
|
31
32
|
|
|
32
33
|
export const getMerkleCampaigns = async (chainId: NetworkNumber): Promise<MerkleRewardMap> => {
|
|
33
34
|
try {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
signal: AbortSignal.timeout(LONGER_TIMEOUT),
|
|
35
|
+
const opportunities = await fetchAllMerklOpportunities({
|
|
36
|
+
mainProtocolId: 'aave',
|
|
37
|
+
status: OpportunityStatus.LIVE,
|
|
38
38
|
});
|
|
39
|
-
if (!res.ok) throw new Error('Failed to fetch Merkle campaigns');
|
|
40
|
-
const opportunities = await res.json() as MerklOpportunity[];
|
|
41
39
|
const relevantOpportunities = opportunities
|
|
42
40
|
.filter((o: MerklOpportunity) => o.chainId === chainId)
|
|
43
41
|
.filter((o: MerklOpportunity) => o.status === OpportunityStatus.LIVE);
|
|
@@ -74,4 +72,3 @@ export const getMerkleCampaigns = async (chainId: NetworkNumber): Promise<Merkle
|
|
|
74
72
|
return {};
|
|
75
73
|
}
|
|
76
74
|
};
|
|
77
|
-
|
package/src/aaveV4/merkl.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { aprToApy } from '../moneymarket';
|
|
2
|
-
import {
|
|
2
|
+
import { fetchAllMerklOpportunities } from '../services/merkl';
|
|
3
3
|
import {
|
|
4
4
|
AaveV4MerklRewardMap,
|
|
5
5
|
AaveV4ReserveAssetData,
|
|
@@ -67,11 +67,11 @@ export const buildAaveV4MerklRewardMap = (opportunities: MerklOpportunity[], cha
|
|
|
67
67
|
|
|
68
68
|
export const getAaveV4MerkleCampaigns = async (chainId: NetworkNumber): Promise<AaveV4MerklRewardMap> => {
|
|
69
69
|
try {
|
|
70
|
-
const
|
|
71
|
-
|
|
70
|
+
const opportunities = await fetchAllMerklOpportunities({
|
|
71
|
+
mainProtocolId: 'aave',
|
|
72
|
+
type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,AAVE_V4_SPOKE_SUPPLY,AAVE_V4_SPOKE_BORROW',
|
|
73
|
+
status: OpportunityStatus.LIVE,
|
|
72
74
|
});
|
|
73
|
-
if (!res.ok) throw new Error('Failed to fetch Aave V4 Merkle campaigns');
|
|
74
|
-
const opportunities = await res.json() as MerklOpportunity[];
|
|
75
75
|
return buildAaveV4MerklRewardMap(opportunities, chainId);
|
|
76
76
|
} catch (e) {
|
|
77
77
|
console.error('Failed to fetch Aave V4 Merkle campaigns', e);
|
package/src/claiming/aaveV3.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Client } from 'viem';
|
|
2
2
|
import Dec from 'decimal.js';
|
|
3
3
|
import { EthAddress, NetworkNumber } from '../types';
|
|
4
|
-
import { ClaimableToken, ClaimType } from '../types/claiming';
|
|
4
|
+
import { ClaimableToken, ClaimType, MerklRewardsClaimableToken } from '../types/claiming';
|
|
5
5
|
import {
|
|
6
6
|
AaveIncentiveDataProviderV3ContractViem,
|
|
7
7
|
AaveRewardsControllerViem,
|
|
@@ -103,18 +103,18 @@ export async function getUnclaimedRewardsForAllMarkets(
|
|
|
103
103
|
return mapAaveRewardsToClaimableTokens(Object.values(totalUnclaimedPerRewardToken), marketAddress, walletAddress);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
export async function
|
|
106
|
+
export async function getMerklUnclaimedRewards(account: EthAddress, network: NetworkNumber): Promise<MerklRewardsClaimableToken[]> {
|
|
107
107
|
let data;
|
|
108
108
|
try {
|
|
109
109
|
const res = await fetch(`https://fe.defisaver.com/api/merkl/get-user-rewards/${account}?chainId=${network}`,
|
|
110
110
|
{ signal: AbortSignal.timeout(LONGER_TIMEOUT) });
|
|
111
111
|
data = await res.json();
|
|
112
112
|
} catch (error) {
|
|
113
|
-
console.error('External API Failure:
|
|
113
|
+
console.error('External API Failure: Merkl rewards', error);
|
|
114
114
|
data = [];
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
const claimableTokens:
|
|
117
|
+
const claimableTokens: MerklRewardsClaimableToken[] = [];
|
|
118
118
|
|
|
119
119
|
// Merkl (or our proxy, on a non-2xx) can return a non-array error body; `fetch` doesn't throw on
|
|
120
120
|
// HTTP errors, so guard before iterating. Mirrors the app-side getMeritUnclaimedRewards.
|
|
@@ -137,12 +137,12 @@ export async function getMeritUnclaimedRewards(account: EthAddress, network: Net
|
|
|
137
137
|
const unclaimed = getEthAmountForDecimals(unclaimedAmount, token.decimals);
|
|
138
138
|
|
|
139
139
|
claimableTokens.push({
|
|
140
|
-
claimType: ClaimType.
|
|
140
|
+
claimType: ClaimType.MERKL_REWARDS,
|
|
141
141
|
amount: unclaimed,
|
|
142
142
|
symbol: token.symbol,
|
|
143
143
|
tokenAddress: token.address,
|
|
144
144
|
walletAddress: account,
|
|
145
|
-
label: '
|
|
145
|
+
label: 'Merkl Rewards',
|
|
146
146
|
underlyingSymbol: getAaveUnderlyingSymbol(token.symbol),
|
|
147
147
|
additionalClaimFields: {
|
|
148
148
|
accumulated: amount,
|
|
@@ -155,4 +155,16 @@ export async function getMeritUnclaimedRewards(account: EthAddress, network: Net
|
|
|
155
155
|
});
|
|
156
156
|
|
|
157
157
|
return claimableTokens;
|
|
158
|
-
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function getMeritUnclaimedRewards(account: EthAddress, network: NetworkNumber): Promise<ClaimableToken[]> {
|
|
161
|
+
const rewards = await getMerklUnclaimedRewards(account, network);
|
|
162
|
+
|
|
163
|
+
return rewards
|
|
164
|
+
.filter(reward => reward.symbol.startsWith('a'))
|
|
165
|
+
.map(reward => ({
|
|
166
|
+
...reward,
|
|
167
|
+
claimType: ClaimType.AAVE_MERIT_REWARDS,
|
|
168
|
+
label: 'AAVE Merit Rewards',
|
|
169
|
+
}));
|
|
170
|
+
}
|
package/src/compoundV3/merkl.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getAssetInfoByAddress } from '@defisaver/tokens';
|
|
2
2
|
import { aprToApy } from '../moneymarket';
|
|
3
|
-
import {
|
|
3
|
+
import { fetchAllMerklOpportunities } from '../services/merkl';
|
|
4
|
+
import { compareAddresses, wethToEth } from '../services/utils';
|
|
4
5
|
import {
|
|
5
6
|
CompoundV3AssetData,
|
|
6
7
|
IncentiveData,
|
|
@@ -22,12 +23,10 @@ import {
|
|
|
22
23
|
*/
|
|
23
24
|
export const getCompoundV3MerklOpportunities = async (): Promise<MerklOpportunity[]> => {
|
|
24
25
|
try {
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
return await fetchAllMerklOpportunities({
|
|
27
|
+
mainProtocolId: 'compound-v3',
|
|
28
|
+
status: OpportunityStatus.LIVE,
|
|
27
29
|
});
|
|
28
|
-
if (!res.ok) throw new Error('Failed to fetch Compound V3 Merkl campaigns');
|
|
29
|
-
const data = await res.json();
|
|
30
|
-
return Array.isArray(data) ? data as MerklOpportunity[] : [];
|
|
31
30
|
} catch (e) {
|
|
32
31
|
console.error('Failed to fetch Compound V3 Merkl campaigns', e);
|
|
33
32
|
return [];
|
package/src/fluid/index.ts
CHANGED
|
@@ -69,7 +69,7 @@ import { getViemProvider } from '../services/viem';
|
|
|
69
69
|
import { attachFluidMerklIncentives, getFluidMerklCampaigns } from './merkl';
|
|
70
70
|
|
|
71
71
|
export {
|
|
72
|
-
|
|
72
|
+
FluidMerklOpportunityType,
|
|
73
73
|
buildFluidMerklRewardMap,
|
|
74
74
|
getFluidMerklCampaigns,
|
|
75
75
|
attachFluidMerklIncentives,
|
|
@@ -1154,6 +1154,8 @@ export const EMPTY_FLUID_DATA = {
|
|
|
1154
1154
|
minRatio: '0',
|
|
1155
1155
|
netApy: '0',
|
|
1156
1156
|
incentiveUsd: '0',
|
|
1157
|
+
merklSupplyIncentives: [],
|
|
1158
|
+
merklBorrowIncentives: [],
|
|
1157
1159
|
totalInterestUsd: '0',
|
|
1158
1160
|
isSubscribedToAutomation: false,
|
|
1159
1161
|
automationResubscribeRequired: false,
|
|
@@ -1809,9 +1811,13 @@ export const _getUserPositionsPortfolio = async (provider: PublicClient, network
|
|
|
1809
1811
|
if (!tokens.includes('ETH')) tokens.push('ETH');
|
|
1810
1812
|
if (!tokens.includes('WBTC')) tokens.push('WBTC');
|
|
1811
1813
|
|
|
1812
|
-
const tokenPrices = await
|
|
1814
|
+
const [tokenPrices, merklCampaigns] = await Promise.all([
|
|
1815
|
+
getTokensPricesForPortfolio(tokens, provider, network),
|
|
1816
|
+
getFluidMerklCampaigns(network),
|
|
1817
|
+
]);
|
|
1813
1818
|
|
|
1814
|
-
const parsedMarketData = (await Promise.all(data[1].map(async (vaultData) => parseMarketData(provider, vaultData, network, tokenPrices))))
|
|
1819
|
+
const parsedMarketData = (await Promise.all(data[1].map(async (vaultData) => parseMarketData(provider, vaultData, network, tokenPrices))))
|
|
1820
|
+
.map(marketData => (marketData ? attachFluidMerklIncentives(marketData, merklCampaigns) : marketData));
|
|
1815
1821
|
|
|
1816
1822
|
const userData = data[0].map((position, i) => (parsedMarketData[i] && { ...parseUserData(position, parsedMarketData[i]) }));
|
|
1817
1823
|
|
package/src/fluid/merkl.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import memoize from 'memoizee';
|
|
1
2
|
import { aprToApy } from '../moneymarket';
|
|
2
|
-
import {
|
|
3
|
+
import { fetchAllMerklOpportunities } from '../services/merkl';
|
|
3
4
|
import {
|
|
4
5
|
FluidMarketData,
|
|
5
6
|
FluidMerklRewardMap,
|
|
6
7
|
IncentiveData,
|
|
7
8
|
IncentiveEligibilityId,
|
|
8
9
|
IncentiveKind,
|
|
10
|
+
IncentiveSource,
|
|
9
11
|
MerklOpportunity,
|
|
10
12
|
NetworkNumber,
|
|
11
13
|
OpportunityStatus,
|
|
@@ -25,7 +27,10 @@ import {
|
|
|
25
27
|
* ids apply unconditionally, mirroring Aave V3. Whitelist-gated campaigns only accrue to
|
|
26
28
|
* whitelisted addresses, so they are skipped rather than advertised to every user.
|
|
27
29
|
*/
|
|
28
|
-
export
|
|
30
|
+
export enum FluidMerklOpportunityType {
|
|
31
|
+
Collateral = 'FLUIDVAULT_COLLATERAL',
|
|
32
|
+
Borrow = 'FLUIDVAULT_BORROW',
|
|
33
|
+
}
|
|
29
34
|
|
|
30
35
|
// '0x' + 20-byte vault address; conditional campaigns append a marker after it
|
|
31
36
|
const VAULT_ADDRESS_LENGTH = 42;
|
|
@@ -36,6 +41,7 @@ const buildMerklIncentive = (opportunity: MerklOpportunity): IncentiveData => {
|
|
|
36
41
|
apy: aprToApy(opportunity.apr),
|
|
37
42
|
token,
|
|
38
43
|
incentiveKind: IncentiveKind.Reward,
|
|
44
|
+
source: IncentiveSource.Merkl,
|
|
39
45
|
description: `Eligible for ${token} rewards through Merkl.${opportunity.description ? `\n${opportunity.description}` : ''}`,
|
|
40
46
|
eligibilityId: opportunity.identifier as IncentiveEligibilityId,
|
|
41
47
|
};
|
|
@@ -47,13 +53,13 @@ export const buildFluidMerklRewardMap = (opportunities: MerklOpportunity[], chai
|
|
|
47
53
|
opportunities
|
|
48
54
|
.filter((o) => o.chainId === chainId)
|
|
49
55
|
.filter((o) => o.status === OpportunityStatus.LIVE)
|
|
50
|
-
.filter((o) => (
|
|
56
|
+
.filter((o) => Object.values(FluidMerklOpportunityType).includes(o.type as FluidMerklOpportunityType))
|
|
51
57
|
.filter((o) => !o.identifier?.includes('WHITELIST'))
|
|
52
58
|
.forEach((o) => {
|
|
53
59
|
const vaultAddress = o.identifier?.slice(0, VAULT_ADDRESS_LENGTH).toLowerCase();
|
|
54
60
|
if (!vaultAddress || vaultAddress.length !== VAULT_ADDRESS_LENGTH) return;
|
|
55
61
|
if (!result[vaultAddress]) result[vaultAddress] = { supply: [], borrow: [] };
|
|
56
|
-
result[vaultAddress][o.type ===
|
|
62
|
+
result[vaultAddress][o.type === FluidMerklOpportunityType.Borrow ? 'borrow' : 'supply'].push(buildMerklIncentive(o));
|
|
57
63
|
});
|
|
58
64
|
|
|
59
65
|
return result;
|
|
@@ -61,22 +67,21 @@ export const buildFluidMerklRewardMap = (opportunities: MerklOpportunity[], chai
|
|
|
61
67
|
|
|
62
68
|
/**
|
|
63
69
|
* Fetches live vault-scoped Fluid campaigns for the chain, keyed by vault address. Never throws —
|
|
64
|
-
* on any failure it returns an empty map, so Merkl being down can't break market data.
|
|
65
|
-
* is narrowed by `type=` and capped at `items=100` because Merkl paginates at 20 items by default.
|
|
70
|
+
* on any failure it returns an empty map, so Merkl being down can't break market data.
|
|
66
71
|
*/
|
|
67
|
-
export const getFluidMerklCampaigns = async (chainId: NetworkNumber): Promise<FluidMerklRewardMap> => {
|
|
72
|
+
export const getFluidMerklCampaigns = memoize(async (chainId: NetworkNumber): Promise<FluidMerklRewardMap> => {
|
|
68
73
|
try {
|
|
69
|
-
const
|
|
70
|
-
|
|
74
|
+
const opportunities = await fetchAllMerklOpportunities({
|
|
75
|
+
mainProtocolId: 'fluid',
|
|
76
|
+
type: Object.values(FluidMerklOpportunityType).join(','),
|
|
77
|
+
status: OpportunityStatus.LIVE,
|
|
71
78
|
});
|
|
72
|
-
|
|
73
|
-
const opportunities = await res.json();
|
|
74
|
-
return buildFluidMerklRewardMap(Array.isArray(opportunities) ? opportunities : [], chainId);
|
|
79
|
+
return buildFluidMerklRewardMap(opportunities, chainId);
|
|
75
80
|
} catch (e) {
|
|
76
81
|
console.error('Failed to fetch Fluid Merkl campaigns', e);
|
|
77
82
|
return {};
|
|
78
83
|
}
|
|
79
|
-
};
|
|
84
|
+
}, { promise: true, maxAge: 60 * 1000 });
|
|
80
85
|
|
|
81
86
|
/**
|
|
82
87
|
* Appends the vault's Merkl campaigns to its assets' incentive arrays — supply rewards on every
|
|
@@ -5,16 +5,56 @@ import {
|
|
|
5
5
|
FluidAssetsData, FluidDexBorrowDataStructOutput, FluidDexSupplyDataStructOutput, FluidUsedAsset,
|
|
6
6
|
FluidUsedAssets,
|
|
7
7
|
FluidVaultType,
|
|
8
|
+
IncentiveData,
|
|
9
|
+
IncentiveSource,
|
|
8
10
|
InnerFluidMarketData,
|
|
9
11
|
} from '../../types';
|
|
10
12
|
import {
|
|
11
13
|
calcLeverageLiqPrice, getAssetsTotal, getExposure, isLeveragedPos,
|
|
12
14
|
} from '../../moneymarket';
|
|
13
|
-
import { calculateNetApy } from '../../staking';
|
|
14
|
-
import { LeverageType, MMAssetsData } from '../../types/common';
|
|
15
|
+
import { calculateInterestEarned, calculateNetApy, EligibilityMapping } from '../../staking';
|
|
16
|
+
import { IncentiveSide, LeverageType, MMAssetsData } from '../../types/common';
|
|
15
17
|
import { getEthAmountForDecimals } from '../../services/utils';
|
|
16
18
|
|
|
17
|
-
const
|
|
19
|
+
export const getUniqueFluidMerklIncentives = (assetsData: FluidAssetsData, side: IncentiveSide): IncentiveData[] => {
|
|
20
|
+
const field = side === IncentiveSide.Supply ? 'supplyIncentives' : 'borrowIncentives';
|
|
21
|
+
const incentives = Object.values(assetsData)
|
|
22
|
+
.flatMap(asset => asset[field])
|
|
23
|
+
.filter(incentive => incentive.source === IncentiveSource.Merkl);
|
|
24
|
+
|
|
25
|
+
return [...new Map(incentives.map(incentive => [
|
|
26
|
+
[incentive.eligibilityId, incentive.token, incentive.apy, incentive.description].join('|'),
|
|
27
|
+
incentive,
|
|
28
|
+
])).values()];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const getMerklIncentiveInterest = (
|
|
32
|
+
incentives: IncentiveData[],
|
|
33
|
+
principal: string,
|
|
34
|
+
usedAssets: FluidUsedAssets,
|
|
35
|
+
) => incentives.reduce((total, { apy, eligibilityId }) => {
|
|
36
|
+
const eligibilityCheck = eligibilityId ? EligibilityMapping[eligibilityId] : null;
|
|
37
|
+
const { isEligible, eligibleUSDAmount } = eligibilityCheck
|
|
38
|
+
? eligibilityCheck(usedAssets, undefined)
|
|
39
|
+
: { isEligible: true, eligibleUSDAmount: principal };
|
|
40
|
+
const incentiveInterest = isEligible ? calculateInterestEarned(eligibleUSDAmount, apy, 'year', true) : 0;
|
|
41
|
+
|
|
42
|
+
return new Dec(total).add(incentiveInterest).toString();
|
|
43
|
+
}, '0');
|
|
44
|
+
|
|
45
|
+
const calculateNetApyDex = ({
|
|
46
|
+
marketData,
|
|
47
|
+
assetsData,
|
|
48
|
+
usedAssets,
|
|
49
|
+
suppliedUsd,
|
|
50
|
+
borrowedUsd,
|
|
51
|
+
}: {
|
|
52
|
+
marketData: InnerFluidMarketData,
|
|
53
|
+
assetsData: FluidAssetsData,
|
|
54
|
+
usedAssets: FluidUsedAssets,
|
|
55
|
+
suppliedUsd: string,
|
|
56
|
+
borrowedUsd: string,
|
|
57
|
+
}) => {
|
|
18
58
|
const {
|
|
19
59
|
borrowRate,
|
|
20
60
|
supplyRate,
|
|
@@ -29,7 +69,21 @@ const calculateNetApyDex = ({ marketData, suppliedUsd, borrowedUsd }: { marketDa
|
|
|
29
69
|
|
|
30
70
|
const borrowIncentive = new Dec(incentiveBorrowRate || '0').mul(borrowedUsd).div(100).toString();
|
|
31
71
|
const supplyIncentive = new Dec(incentiveSupplyRate || '0').mul(suppliedUsd).div(100).toString();
|
|
32
|
-
const
|
|
72
|
+
const merklSupplyIncentive = getMerklIncentiveInterest(
|
|
73
|
+
getUniqueFluidMerklIncentives(assetsData, IncentiveSide.Supply),
|
|
74
|
+
suppliedUsd,
|
|
75
|
+
usedAssets,
|
|
76
|
+
);
|
|
77
|
+
const merklBorrowIncentive = getMerklIncentiveInterest(
|
|
78
|
+
getUniqueFluidMerklIncentives(assetsData, IncentiveSide.Borrow),
|
|
79
|
+
borrowedUsd,
|
|
80
|
+
usedAssets,
|
|
81
|
+
);
|
|
82
|
+
const incentiveUsd = new Dec(supplyIncentive)
|
|
83
|
+
.minus(borrowIncentive)
|
|
84
|
+
.add(merklSupplyIncentive)
|
|
85
|
+
.add(merklBorrowIncentive)
|
|
86
|
+
.toString();
|
|
33
87
|
|
|
34
88
|
const borrowInterest = new Dec(totalBorrowRate).mul(borrowedUsd).div(100).toString();
|
|
35
89
|
const supplyInterest = new Dec(totalSupplyRate).mul(suppliedUsd).div(100).toString();
|
|
@@ -66,7 +120,15 @@ borrowShares?: string,
|
|
|
66
120
|
: new Dec(marketData.debtSharePrice!).mul(borrowShares!).toString();
|
|
67
121
|
|
|
68
122
|
const isDex = [FluidVaultType.T2, FluidVaultType.T3, FluidVaultType.T4].includes(marketData.vaultType);
|
|
69
|
-
|
|
123
|
+
payload.merklSupplyIncentives = getUniqueFluidMerklIncentives(assetsData, IncentiveSide.Supply);
|
|
124
|
+
payload.merklBorrowIncentives = getUniqueFluidMerklIncentives(assetsData, IncentiveSide.Borrow);
|
|
125
|
+
const { netApy, incentiveUsd, totalInterestUsd } = isDex ? calculateNetApyDex({
|
|
126
|
+
marketData,
|
|
127
|
+
assetsData,
|
|
128
|
+
usedAssets,
|
|
129
|
+
suppliedUsd: payload.suppliedUsd,
|
|
130
|
+
borrowedUsd: payload.borrowedUsd,
|
|
131
|
+
}) : calculateNetApy({ usedAssets, assetsData: assetsData as unknown as MMAssetsData });
|
|
70
132
|
payload.netApy = netApy;
|
|
71
133
|
payload.incentiveUsd = incentiveUsd;
|
|
72
134
|
payload.totalInterestUsd = totalInterestUsd;
|
|
@@ -337,4 +399,4 @@ export const mergeUsedAssets = (existing: Partial<FluidUsedAsset> = {}, addition
|
|
|
337
399
|
...EMPTY_USED_ASSET,
|
|
338
400
|
...existing,
|
|
339
401
|
...additional,
|
|
340
|
-
});
|
|
402
|
+
});
|
package/src/morphoBlue/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { getViemProvider, setViemBlockNumber } from '../services/viem';
|
|
|
22
22
|
import { addMorphoBlueMerklOpportunitiesToMarketInfo, getMorphoBlueMerklOpportunities } from './merkl';
|
|
23
23
|
|
|
24
24
|
export {
|
|
25
|
-
|
|
25
|
+
MorphoBlueMerklOpportunityType,
|
|
26
26
|
addMorphoBlueMerklOpportunitiesToMarketInfo,
|
|
27
27
|
getMorphoBlueMerklOpportunities,
|
|
28
28
|
addMorphoBlueMerklRewardsToMarketInfo,
|
package/src/morphoBlue/merkl.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { aprToApy } from '../moneymarket';
|
|
2
|
-
import {
|
|
2
|
+
import { fetchAllMerklOpportunities } from '../services/merkl';
|
|
3
3
|
import {
|
|
4
4
|
IncentiveData,
|
|
5
5
|
IncentiveKind,
|
|
@@ -23,7 +23,11 @@ import {
|
|
|
23
23
|
* identifier (e.g. `0x54cf…c46WHITELIST_CAMPAIGN`); their rewards only accrue to whitelisted
|
|
24
24
|
* addresses, so they are skipped rather than advertised to every user.
|
|
25
25
|
*/
|
|
26
|
-
export
|
|
26
|
+
export enum MorphoBlueMerklOpportunityType {
|
|
27
|
+
Supply = 'MORPHOSUPPLY',
|
|
28
|
+
Collateral = 'MORPHOCOLLATERAL',
|
|
29
|
+
Borrow = 'MORPHOBORROW',
|
|
30
|
+
}
|
|
27
31
|
|
|
28
32
|
const MERKL_DESCRIPTION_MARKER = 'through Merkl';
|
|
29
33
|
|
|
@@ -61,16 +65,16 @@ export const addMorphoBlueMerklOpportunitiesToMarketInfo = (
|
|
|
61
65
|
|
|
62
66
|
const relevant = opportunities.filter((o) => o.chainId === chainId
|
|
63
67
|
&& o.status === OpportunityStatus.LIVE
|
|
64
|
-
&& (
|
|
68
|
+
&& Object.values(MorphoBlueMerklOpportunityType).includes(o.type as MorphoBlueMerklOpportunityType)
|
|
65
69
|
// a suffixed identifier marks a whitelist-gated campaign — skip those
|
|
66
70
|
&& o.identifier?.length === MARKET_ID_PREFIX_LENGTH
|
|
67
71
|
&& o.identifier.toLowerCase() === marketIdPrefix);
|
|
68
72
|
if (!relevant.length) return marketInfo;
|
|
69
73
|
|
|
70
|
-
const incentivesByType = (type:
|
|
71
|
-
const collateralSupply = incentivesByType(
|
|
72
|
-
const loanSupply = incentivesByType(
|
|
73
|
-
const loanBorrow = incentivesByType(
|
|
74
|
+
const incentivesByType = (type: MorphoBlueMerklOpportunityType) => relevant.filter((o) => o.type === type).map(buildMerklIncentive);
|
|
75
|
+
const collateralSupply = incentivesByType(MorphoBlueMerklOpportunityType.Collateral);
|
|
76
|
+
const loanSupply = incentivesByType(MorphoBlueMerklOpportunityType.Supply);
|
|
77
|
+
const loanBorrow = incentivesByType(MorphoBlueMerklOpportunityType.Borrow);
|
|
74
78
|
|
|
75
79
|
return {
|
|
76
80
|
...marketInfo,
|
|
@@ -91,18 +95,15 @@ export const addMorphoBlueMerklOpportunitiesToMarketInfo = (
|
|
|
91
95
|
|
|
92
96
|
/**
|
|
93
97
|
* Fetches live market-scoped Morpho Blue campaigns for all chains. Never throws — on any failure
|
|
94
|
-
* it returns an empty list, so Merkl being down can't break market data.
|
|
95
|
-
* `type=` and capped at `items=100` because Merkl paginates at 20 items by default — an
|
|
96
|
-
* unfiltered protocol query would silently drop campaigns once Morpho has enough opportunities.
|
|
98
|
+
* it returns an empty list, so Merkl being down can't break market data.
|
|
97
99
|
*/
|
|
98
100
|
export const getMorphoBlueMerklOpportunities = async (): Promise<MerklOpportunity[]> => {
|
|
99
101
|
try {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
return await fetchAllMerklOpportunities({
|
|
103
|
+
mainProtocolId: 'morpho',
|
|
104
|
+
type: Object.values(MorphoBlueMerklOpportunityType).join(','),
|
|
105
|
+
status: OpportunityStatus.LIVE,
|
|
102
106
|
});
|
|
103
|
-
if (!res.ok) throw new Error('Failed to fetch Morpho Blue Merkl campaigns');
|
|
104
|
-
const data = await res.json();
|
|
105
|
-
return Array.isArray(data) ? data as MerklOpportunity[] : [];
|
|
106
107
|
} catch (e) {
|
|
107
108
|
console.error('Failed to fetch Morpho Blue Merkl campaigns', e);
|
|
108
109
|
return [];
|
package/src/portfolio/index.ts
CHANGED
|
@@ -41,7 +41,7 @@ import { _getLiquityTroveInfo, getLiquityStakingData } from '../liquity';
|
|
|
41
41
|
import { _getLiquityV2MarketData, getLiquitySAndYBold, getLiquityV2Staking } from '../liquityV2';
|
|
42
42
|
import { _getAllUserEarnPositionsWithFTokens, _getUserPositionsPortfolio } from '../fluid';
|
|
43
43
|
import { getUmbrellaData } from '../umbrella';
|
|
44
|
-
import {
|
|
44
|
+
import { getMerklUnclaimedRewards, getUnclaimedRewardsForAllMarkets } from '../claiming/aaveV3';
|
|
45
45
|
import { getCompoundV3Rewards } from '../claiming/compV3';
|
|
46
46
|
import { fetchSparkAirdropRewards, fetchSparkRewards } from '../claiming/spark';
|
|
47
47
|
import { getKingRewards } from '../claiming/king';
|
|
@@ -143,7 +143,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
143
143
|
};
|
|
144
144
|
|
|
145
145
|
rewardsData[address.toLowerCase() as EthAddress] = {
|
|
146
|
-
|
|
146
|
+
merkl: {},
|
|
147
147
|
aaveV3: {},
|
|
148
148
|
compV3: {},
|
|
149
149
|
spark: {},
|
|
@@ -352,11 +352,11 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
352
352
|
})).flat(),
|
|
353
353
|
...addresses.map(async (address) => {
|
|
354
354
|
try {
|
|
355
|
-
const
|
|
356
|
-
rewardsData[address.toLowerCase() as EthAddress].
|
|
355
|
+
const merklData = await getMerklUnclaimedRewards(address, network);
|
|
356
|
+
rewardsData[address.toLowerCase() as EthAddress].merkl = { error: '', data: merklData };
|
|
357
357
|
} catch (error) {
|
|
358
|
-
console.error(`Error fetching
|
|
359
|
-
rewardsData[address.toLowerCase() as EthAddress].
|
|
358
|
+
console.error(`Error fetching Merkl rewards data for address ${address}:`, error);
|
|
359
|
+
rewardsData[address.toLowerCase() as EthAddress].merkl = { error: `Error fetching Merkl rewards data for address ${address}`, data: null };
|
|
360
360
|
}
|
|
361
361
|
}),
|
|
362
362
|
...aaveV3Markets.map(market => addresses.map(async (address) => {
|