@defisaver/positions-sdk 2.1.142 → 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.
@@ -1,5 +1,6 @@
1
1
  import { Client } from 'viem';
2
2
  import { EthAddress, NetworkNumber } from '../types';
3
- import { ClaimableToken } from '../types/claiming';
3
+ import { ClaimableToken, MerklRewardsClaimableToken } from '../types/claiming';
4
4
  export declare function getUnclaimedRewardsForAllMarkets(provider: Client, network: NetworkNumber, walletAddress: EthAddress, marketAddress: EthAddress): Promise<ClaimableToken[]>;
5
+ export declare function getMerklUnclaimedRewards(account: EthAddress, network: NetworkNumber): Promise<MerklRewardsClaimableToken[]>;
5
6
  export declare function getMeritUnclaimedRewards(account: EthAddress, network: NetworkNumber): Promise<ClaimableToken[]>;
@@ -13,6 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.getUnclaimedRewardsForAllMarkets = getUnclaimedRewardsForAllMarkets;
16
+ exports.getMerklUnclaimedRewards = getMerklUnclaimedRewards;
16
17
  exports.getMeritUnclaimedRewards = getMeritUnclaimedRewards;
17
18
  const decimal_js_1 = __importDefault(require("decimal.js"));
18
19
  const claiming_1 = require("../types/claiming");
@@ -95,7 +96,7 @@ function getUnclaimedRewardsForAllMarkets(provider, network, walletAddress, mark
95
96
  return mapAaveRewardsToClaimableTokens(Object.values(totalUnclaimedPerRewardToken), marketAddress, walletAddress);
96
97
  });
97
98
  }
98
- function getMeritUnclaimedRewards(account, network) {
99
+ function getMerklUnclaimedRewards(account, network) {
99
100
  return __awaiter(this, void 0, void 0, function* () {
100
101
  let data;
101
102
  try {
@@ -103,7 +104,7 @@ function getMeritUnclaimedRewards(account, network) {
103
104
  data = yield res.json();
104
105
  }
105
106
  catch (error) {
106
- console.error('External API Failure: Aave Merit', error);
107
+ console.error('External API Failure: Merkl rewards', error);
107
108
  data = [];
108
109
  }
109
110
  const claimableTokens = [];
@@ -121,12 +122,12 @@ function getMeritUnclaimedRewards(account, network) {
121
122
  return;
122
123
  const unclaimed = (0, utils_1.getEthAmountForDecimals)(unclaimedAmount, token.decimals);
123
124
  claimableTokens.push({
124
- claimType: claiming_1.ClaimType.AAVE_MERIT_REWARDS,
125
+ claimType: claiming_1.ClaimType.MERKL_REWARDS,
125
126
  amount: unclaimed,
126
127
  symbol: token.symbol,
127
128
  tokenAddress: token.address,
128
129
  walletAddress: account,
129
- label: 'AAVE Merit Rewards',
130
+ label: 'Merkl Rewards',
130
131
  underlyingSymbol: (0, aaveHelpers_1.getAaveUnderlyingSymbol)(token.symbol),
131
132
  additionalClaimFields: {
132
133
  accumulated: amount,
@@ -140,3 +141,11 @@ function getMeritUnclaimedRewards(account, network) {
140
141
  return claimableTokens;
141
142
  });
142
143
  }
144
+ function getMeritUnclaimedRewards(account, network) {
145
+ return __awaiter(this, void 0, void 0, function* () {
146
+ const rewards = yield getMerklUnclaimedRewards(account, network);
147
+ return rewards
148
+ .filter(reward => reward.symbol.startsWith('a'))
149
+ .map(reward => (Object.assign(Object.assign({}, reward), { claimType: claiming_1.ClaimType.AAVE_MERIT_REWARDS, label: 'AAVE Merit Rewards' })));
150
+ });
151
+ }
@@ -22,6 +22,8 @@ export declare const EMPTY_FLUID_DATA: {
22
22
  minRatio: string;
23
23
  netApy: string;
24
24
  incentiveUsd: string;
25
+ merklSupplyIncentives: never[];
26
+ merklBorrowIncentives: never[];
25
27
  totalInterestUsd: string;
26
28
  isSubscribedToAutomation: boolean;
27
29
  automationResubscribeRequired: boolean;
@@ -127,6 +129,8 @@ export declare const _getUserPositions: (provider: PublicClient, network: Networ
127
129
  leftToBorrowUsd: string;
128
130
  netApy: string;
129
131
  incentiveUsd: string;
132
+ merklSupplyIncentives: import("../types").IncentiveData[];
133
+ merklBorrowIncentives: import("../types").IncentiveData[];
130
134
  ratio: string;
131
135
  collRatio: string;
132
136
  minRatio: string;
@@ -159,6 +163,8 @@ export declare const getUserPositions: (provider: EthereumProvider, network: Net
159
163
  leftToBorrowUsd: string;
160
164
  netApy: string;
161
165
  incentiveUsd: string;
166
+ merklSupplyIncentives: import("../types").IncentiveData[];
167
+ merklBorrowIncentives: import("../types").IncentiveData[];
162
168
  ratio: string;
163
169
  collRatio: string;
164
170
  minRatio: string;
@@ -191,6 +197,8 @@ export declare const _getUserPositionsPortfolio: (provider: PublicClient, networ
191
197
  leftToBorrowUsd: string;
192
198
  netApy: string;
193
199
  incentiveUsd: string;
200
+ merklSupplyIncentives: import("../types").IncentiveData[];
201
+ merklBorrowIncentives: import("../types").IncentiveData[];
194
202
  ratio: string;
195
203
  collRatio: string;
196
204
  minRatio: string;
@@ -922,6 +922,8 @@ exports.EMPTY_FLUID_DATA = {
922
922
  minRatio: '0',
923
923
  netApy: '0',
924
924
  incentiveUsd: '0',
925
+ merklSupplyIncentives: [],
926
+ merklBorrowIncentives: [],
925
927
  totalInterestUsd: '0',
926
928
  isSubscribedToAutomation: false,
927
929
  automationResubscribeRequired: false,
@@ -1391,8 +1393,12 @@ const _getUserPositionsPortfolio = (provider, network, user) => __awaiter(void 0
1391
1393
  tokens.push('ETH');
1392
1394
  if (!tokens.includes('WBTC'))
1393
1395
  tokens.push('WBTC');
1394
- const tokenPrices = yield getTokensPricesForPortfolio(tokens, provider, network);
1395
- const parsedMarketData = (yield Promise.all(data[1].map((vaultData) => __awaiter(void 0, void 0, void 0, function* () { return parseMarketData(provider, vaultData, network, tokenPrices); }))));
1396
+ const [tokenPrices, merklCampaigns] = yield Promise.all([
1397
+ getTokensPricesForPortfolio(tokens, provider, network),
1398
+ (0, merkl_1.getFluidMerklCampaigns)(network),
1399
+ ]);
1400
+ const parsedMarketData = (yield Promise.all(data[1].map((vaultData) => __awaiter(void 0, void 0, void 0, function* () { return parseMarketData(provider, vaultData, network, tokenPrices); }))))
1401
+ .map(marketData => (marketData ? (0, merkl_1.attachFluidMerklIncentives)(marketData, merklCampaigns) : marketData));
1396
1402
  const userData = data[0].map((position, i) => (parsedMarketData[i] && Object.assign({}, parseUserData(position, parsedMarketData[i]))));
1397
1403
  return parsedMarketData.map((market, i) => ({
1398
1404
  marketData: market,
@@ -1,3 +1,4 @@
1
+ import memoize from 'memoizee';
1
2
  import { FluidMarketData, FluidMerklRewardMap, MerklOpportunity, NetworkNumber } from '../types';
2
3
  /**
3
4
  * Merkl tags Fluid vault-scoped reward campaigns via the opportunity `type` field:
@@ -22,7 +23,7 @@ export declare const buildFluidMerklRewardMap: (opportunities: MerklOpportunity[
22
23
  * Fetches live vault-scoped Fluid campaigns for the chain, keyed by vault address. Never throws —
23
24
  * on any failure it returns an empty map, so Merkl being down can't break market data.
24
25
  */
25
- export declare const getFluidMerklCampaigns: (chainId: NetworkNumber) => Promise<FluidMerklRewardMap>;
26
+ export declare const getFluidMerklCampaigns: ((chainId: NetworkNumber) => Promise<FluidMerklRewardMap>) & memoize.Memoized<(chainId: NetworkNumber) => Promise<FluidMerklRewardMap>>;
26
27
  /**
27
28
  * Appends the vault's Merkl campaigns to its assets' incentive arrays — supply rewards on every
28
29
  * suppliable asset and borrow rewards on every borrowable one. Smart collateral/debt vaults hold
@@ -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.attachFluidMerklIncentives = exports.getFluidMerklCampaigns = exports.buildFluidMerklRewardMap = exports.FluidMerklOpportunityType = void 0;
16
+ const memoizee_1 = __importDefault(require("memoizee"));
13
17
  const moneymarket_1 = require("../moneymarket");
14
18
  const merkl_1 = require("../services/merkl");
15
19
  const types_1 = require("../types");
@@ -41,6 +45,7 @@ const buildMerklIncentive = (opportunity) => {
41
45
  apy: (0, moneymarket_1.aprToApy)(opportunity.apr),
42
46
  token,
43
47
  incentiveKind: types_1.IncentiveKind.Reward,
48
+ source: types_1.IncentiveSource.Merkl,
44
49
  description: `Eligible for ${token} rewards through Merkl.${opportunity.description ? `\n${opportunity.description}` : ''}`,
45
50
  eligibilityId: opportunity.identifier,
46
51
  };
@@ -68,7 +73,7 @@ exports.buildFluidMerklRewardMap = buildFluidMerklRewardMap;
68
73
  * Fetches live vault-scoped Fluid campaigns for the chain, keyed by vault address. Never throws —
69
74
  * on any failure it returns an empty map, so Merkl being down can't break market data.
70
75
  */
71
- const getFluidMerklCampaigns = (chainId) => __awaiter(void 0, void 0, void 0, function* () {
76
+ exports.getFluidMerklCampaigns = (0, memoizee_1.default)((chainId) => __awaiter(void 0, void 0, void 0, function* () {
72
77
  try {
73
78
  const opportunities = yield (0, merkl_1.fetchAllMerklOpportunities)({
74
79
  mainProtocolId: 'fluid',
@@ -81,8 +86,7 @@ const getFluidMerklCampaigns = (chainId) => __awaiter(void 0, void 0, void 0, fu
81
86
  console.error('Failed to fetch Fluid Merkl campaigns', e);
82
87
  return {};
83
88
  }
84
- });
85
- exports.getFluidMerklCampaigns = getFluidMerklCampaigns;
89
+ }), { promise: true, maxAge: 60 * 1000 });
86
90
  /**
87
91
  * Appends the vault's Merkl campaigns to its assets' incentive arrays — supply rewards on every
88
92
  * suppliable asset and borrow rewards on every borrowable one. Smart collateral/debt vaults hold
@@ -1,4 +1,6 @@
1
- import { FluidAggregatedVaultData, FluidAssetData, FluidAssetsData, FluidDexBorrowDataStructOutput, FluidDexSupplyDataStructOutput, FluidUsedAsset, FluidUsedAssets, InnerFluidMarketData } from '../../types';
1
+ import { FluidAggregatedVaultData, FluidAssetData, FluidAssetsData, FluidDexBorrowDataStructOutput, FluidDexSupplyDataStructOutput, FluidUsedAsset, FluidUsedAssets, IncentiveData, InnerFluidMarketData } from '../../types';
2
+ import { IncentiveSide } from '../../types/common';
3
+ export declare const getUniqueFluidMerklIncentives: (assetsData: FluidAssetsData, side: IncentiveSide) => IncentiveData[];
2
4
  export declare const getFluidAggregatedData: ({ usedAssets, assetsData, marketData, }: {
3
5
  usedAssets: FluidUsedAssets;
4
6
  marketData: InnerFluidMarketData;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.mergeUsedAssets = exports.EMPTY_USED_ASSET = exports.mergeAssetData = exports.parseDexBorrowData = exports.parseDexSupplyData = exports.getFluidAggregatedData = void 0;
6
+ exports.mergeUsedAssets = exports.EMPTY_USED_ASSET = exports.mergeAssetData = exports.parseDexBorrowData = exports.parseDexSupplyData = exports.getFluidAggregatedData = exports.getUniqueFluidMerklIncentives = void 0;
7
7
  const decimal_js_1 = __importDefault(require("decimal.js"));
8
8
  const tokens_1 = require("@defisaver/tokens");
9
9
  const types_1 = require("../../types");
@@ -11,13 +11,38 @@ const moneymarket_1 = require("../../moneymarket");
11
11
  const staking_1 = require("../../staking");
12
12
  const common_1 = require("../../types/common");
13
13
  const utils_1 = require("../../services/utils");
14
- const calculateNetApyDex = ({ marketData, suppliedUsd, borrowedUsd }) => {
14
+ const getUniqueFluidMerklIncentives = (assetsData, side) => {
15
+ const field = side === common_1.IncentiveSide.Supply ? 'supplyIncentives' : 'borrowIncentives';
16
+ const incentives = Object.values(assetsData)
17
+ .flatMap(asset => asset[field])
18
+ .filter(incentive => incentive.source === types_1.IncentiveSource.Merkl);
19
+ return [...new Map(incentives.map(incentive => [
20
+ [incentive.eligibilityId, incentive.token, incentive.apy, incentive.description].join('|'),
21
+ incentive,
22
+ ])).values()];
23
+ };
24
+ exports.getUniqueFluidMerklIncentives = getUniqueFluidMerklIncentives;
25
+ const getMerklIncentiveInterest = (incentives, principal, usedAssets) => incentives.reduce((total, { apy, eligibilityId }) => {
26
+ const eligibilityCheck = eligibilityId ? staking_1.EligibilityMapping[eligibilityId] : null;
27
+ const { isEligible, eligibleUSDAmount } = eligibilityCheck
28
+ ? eligibilityCheck(usedAssets, undefined)
29
+ : { isEligible: true, eligibleUSDAmount: principal };
30
+ const incentiveInterest = isEligible ? (0, staking_1.calculateInterestEarned)(eligibleUSDAmount, apy, 'year', true) : 0;
31
+ return new decimal_js_1.default(total).add(incentiveInterest).toString();
32
+ }, '0');
33
+ const calculateNetApyDex = ({ marketData, assetsData, usedAssets, suppliedUsd, borrowedUsd, }) => {
15
34
  const { borrowRate, supplyRate, incentiveBorrowRate, incentiveSupplyRate, tradingBorrowRate, tradingSupplyRate, } = marketData;
16
35
  const totalBorrowRate = new decimal_js_1.default(borrowRate).minus(tradingBorrowRate || '0').toString();
17
36
  const totalSupplyRate = new decimal_js_1.default(supplyRate).add(tradingSupplyRate || '0').toString();
18
37
  const borrowIncentive = new decimal_js_1.default(incentiveBorrowRate || '0').mul(borrowedUsd).div(100).toString();
19
38
  const supplyIncentive = new decimal_js_1.default(incentiveSupplyRate || '0').mul(suppliedUsd).div(100).toString();
20
- const incentiveUsd = new decimal_js_1.default(supplyIncentive).minus(borrowIncentive).toString();
39
+ const merklSupplyIncentive = getMerklIncentiveInterest((0, exports.getUniqueFluidMerklIncentives)(assetsData, common_1.IncentiveSide.Supply), suppliedUsd, usedAssets);
40
+ const merklBorrowIncentive = getMerklIncentiveInterest((0, exports.getUniqueFluidMerklIncentives)(assetsData, common_1.IncentiveSide.Borrow), borrowedUsd, usedAssets);
41
+ const incentiveUsd = new decimal_js_1.default(supplyIncentive)
42
+ .minus(borrowIncentive)
43
+ .add(merklSupplyIncentive)
44
+ .add(merklBorrowIncentive)
45
+ .toString();
21
46
  const borrowInterest = new decimal_js_1.default(totalBorrowRate).mul(borrowedUsd).div(100).toString();
22
47
  const supplyInterest = new decimal_js_1.default(totalSupplyRate).mul(suppliedUsd).div(100).toString();
23
48
  const totalInterestUsd = new decimal_js_1.default(supplyInterest).add(incentiveUsd).minus(borrowInterest).toString();
@@ -38,7 +63,15 @@ const getFluidAggregatedData = ({ usedAssets, assetsData, marketData, }, supplyS
38
63
  ? (0, moneymarket_1.getAssetsTotal)(usedAssets, ({ isBorrowed }) => isBorrowed, ({ borrowedUsd }) => borrowedUsd)
39
64
  : new decimal_js_1.default(marketData.debtSharePrice).mul(borrowShares).toString();
40
65
  const isDex = [types_1.FluidVaultType.T2, types_1.FluidVaultType.T3, types_1.FluidVaultType.T4].includes(marketData.vaultType);
41
- const { netApy, incentiveUsd, totalInterestUsd } = isDex ? calculateNetApyDex({ marketData, suppliedUsd: payload.suppliedUsd, borrowedUsd: payload.borrowedUsd }) : (0, staking_1.calculateNetApy)({ usedAssets, assetsData: assetsData });
66
+ payload.merklSupplyIncentives = (0, exports.getUniqueFluidMerklIncentives)(assetsData, common_1.IncentiveSide.Supply);
67
+ payload.merklBorrowIncentives = (0, exports.getUniqueFluidMerklIncentives)(assetsData, common_1.IncentiveSide.Borrow);
68
+ const { netApy, incentiveUsd, totalInterestUsd } = isDex ? calculateNetApyDex({
69
+ marketData,
70
+ assetsData,
71
+ usedAssets,
72
+ suppliedUsd: payload.suppliedUsd,
73
+ borrowedUsd: payload.borrowedUsd,
74
+ }) : (0, staking_1.calculateNetApy)({ usedAssets, assetsData: assetsData });
42
75
  payload.netApy = netApy;
43
76
  payload.incentiveUsd = incentiveUsd;
44
77
  payload.totalInterestUsd = totalInterestUsd;
@@ -134,7 +134,7 @@ function getPortfolioData(provider_1, network_1, defaultProvider_1, addresses_1)
134
134
  },
135
135
  };
136
136
  rewardsData[address.toLowerCase()] = {
137
- aaveV3merit: {},
137
+ merkl: {},
138
138
  aaveV3: {},
139
139
  compV3: {},
140
140
  spark: {},
@@ -355,12 +355,12 @@ function getPortfolioData(provider_1, network_1, defaultProvider_1, addresses_1)
355
355
  }))).flat(),
356
356
  ...addresses.map((address) => __awaiter(this, void 0, void 0, function* () {
357
357
  try {
358
- const aaveMeritData = yield (0, aaveV3_2.getMeritUnclaimedRewards)(address, network);
359
- rewardsData[address.toLowerCase()].aaveV3merit = { error: '', data: aaveMeritData };
358
+ const merklData = yield (0, aaveV3_2.getMerklUnclaimedRewards)(address, network);
359
+ rewardsData[address.toLowerCase()].merkl = { error: '', data: merklData };
360
360
  }
361
361
  catch (error) {
362
- console.error(`Error fetching Aave V3 Merit rewards data for address ${address}:`, error);
363
- rewardsData[address.toLowerCase()].aaveV3merit = { error: `Error fetching Aave V3 Merit rewards data for address ${address}`, data: null };
362
+ console.error(`Error fetching Merkl rewards data for address ${address}:`, error);
363
+ rewardsData[address.toLowerCase()].merkl = { error: `Error fetching Merkl rewards data for address ${address}`, data: null };
364
364
  }
365
365
  })),
366
366
  ...aaveV3Markets.map(market => addresses.map((address) => __awaiter(this, void 0, void 0, function* () {
@@ -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 {};
@@ -7,6 +7,8 @@ var ClaimType;
7
7
  ClaimType["AAVE_REWARDS"] = "AAVE_REWARDS";
8
8
  /** Merit rewards from AAVE (various tokens) */
9
9
  ClaimType["AAVE_MERIT_REWARDS"] = "AAVE_MERIT_REWARDS";
10
+ /** Rewards distributed through Merkl across supported protocols */
11
+ ClaimType["MERKL_REWARDS"] = "MERKL_REWARDS";
10
12
  /** Rewards from Compound V3 (only in COMP) */
11
13
  ClaimType["COMPOUND_V3_COMP"] = "COMPOUND_V3_COMP";
12
14
  /** Rewards from Spark (wstETH only for now) */
@@ -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
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.NetworkNumber = exports.LeverageType = exports.IncentiveEligibilityId = exports.IncentiveSide = exports.IncentiveKind = void 0;
3
+ exports.NetworkNumber = exports.LeverageType = exports.IncentiveEligibilityId = exports.IncentiveSource = exports.IncentiveSide = exports.IncentiveKind = void 0;
4
4
  var IncentiveKind;
5
5
  (function (IncentiveKind) {
6
6
  IncentiveKind["Staking"] = "staking";
@@ -11,6 +11,10 @@ var IncentiveSide;
11
11
  IncentiveSide["Supply"] = "supply";
12
12
  IncentiveSide["Borrow"] = "borrow";
13
13
  })(IncentiveSide || (exports.IncentiveSide = IncentiveSide = {}));
14
+ var IncentiveSource;
15
+ (function (IncentiveSource) {
16
+ IncentiveSource["Merkl"] = "merkl";
17
+ })(IncentiveSource || (exports.IncentiveSource = IncentiveSource = {}));
14
18
  var IncentiveEligibilityId;
15
19
  (function (IncentiveEligibilityId) {
16
20
  IncentiveEligibilityId["AaveV3EthenaLiquidLeverage"] = "0x0dC599DBB180E64E42b87332FDeC3a551445ea44BORROW_BL";
@@ -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;
@@ -1,5 +1,6 @@
1
1
  import { Client } from 'viem';
2
2
  import { EthAddress, NetworkNumber } from '../types';
3
- import { ClaimableToken } from '../types/claiming';
3
+ import { ClaimableToken, MerklRewardsClaimableToken } from '../types/claiming';
4
4
  export declare function getUnclaimedRewardsForAllMarkets(provider: Client, network: NetworkNumber, walletAddress: EthAddress, marketAddress: EthAddress): Promise<ClaimableToken[]>;
5
+ export declare function getMerklUnclaimedRewards(account: EthAddress, network: NetworkNumber): Promise<MerklRewardsClaimableToken[]>;
5
6
  export declare function getMeritUnclaimedRewards(account: EthAddress, network: NetworkNumber): Promise<ClaimableToken[]>;
@@ -88,7 +88,7 @@ export function getUnclaimedRewardsForAllMarkets(provider, network, walletAddres
88
88
  return mapAaveRewardsToClaimableTokens(Object.values(totalUnclaimedPerRewardToken), marketAddress, walletAddress);
89
89
  });
90
90
  }
91
- export function getMeritUnclaimedRewards(account, network) {
91
+ export function getMerklUnclaimedRewards(account, network) {
92
92
  return __awaiter(this, void 0, void 0, function* () {
93
93
  let data;
94
94
  try {
@@ -96,7 +96,7 @@ export function getMeritUnclaimedRewards(account, network) {
96
96
  data = yield res.json();
97
97
  }
98
98
  catch (error) {
99
- console.error('External API Failure: Aave Merit', error);
99
+ console.error('External API Failure: Merkl rewards', error);
100
100
  data = [];
101
101
  }
102
102
  const claimableTokens = [];
@@ -114,12 +114,12 @@ export function getMeritUnclaimedRewards(account, network) {
114
114
  return;
115
115
  const unclaimed = getEthAmountForDecimals(unclaimedAmount, token.decimals);
116
116
  claimableTokens.push({
117
- claimType: ClaimType.AAVE_MERIT_REWARDS,
117
+ claimType: ClaimType.MERKL_REWARDS,
118
118
  amount: unclaimed,
119
119
  symbol: token.symbol,
120
120
  tokenAddress: token.address,
121
121
  walletAddress: account,
122
- label: 'AAVE Merit Rewards',
122
+ label: 'Merkl Rewards',
123
123
  underlyingSymbol: getAaveUnderlyingSymbol(token.symbol),
124
124
  additionalClaimFields: {
125
125
  accumulated: amount,
@@ -133,3 +133,11 @@ export function getMeritUnclaimedRewards(account, network) {
133
133
  return claimableTokens;
134
134
  });
135
135
  }
136
+ export function getMeritUnclaimedRewards(account, network) {
137
+ return __awaiter(this, void 0, void 0, function* () {
138
+ const rewards = yield getMerklUnclaimedRewards(account, network);
139
+ return rewards
140
+ .filter(reward => reward.symbol.startsWith('a'))
141
+ .map(reward => (Object.assign(Object.assign({}, reward), { claimType: ClaimType.AAVE_MERIT_REWARDS, label: 'AAVE Merit Rewards' })));
142
+ });
143
+ }
@@ -22,6 +22,8 @@ export declare const EMPTY_FLUID_DATA: {
22
22
  minRatio: string;
23
23
  netApy: string;
24
24
  incentiveUsd: string;
25
+ merklSupplyIncentives: never[];
26
+ merklBorrowIncentives: never[];
25
27
  totalInterestUsd: string;
26
28
  isSubscribedToAutomation: boolean;
27
29
  automationResubscribeRequired: boolean;
@@ -127,6 +129,8 @@ export declare const _getUserPositions: (provider: PublicClient, network: Networ
127
129
  leftToBorrowUsd: string;
128
130
  netApy: string;
129
131
  incentiveUsd: string;
132
+ merklSupplyIncentives: import("../types").IncentiveData[];
133
+ merklBorrowIncentives: import("../types").IncentiveData[];
130
134
  ratio: string;
131
135
  collRatio: string;
132
136
  minRatio: string;
@@ -159,6 +163,8 @@ export declare const getUserPositions: (provider: EthereumProvider, network: Net
159
163
  leftToBorrowUsd: string;
160
164
  netApy: string;
161
165
  incentiveUsd: string;
166
+ merklSupplyIncentives: import("../types").IncentiveData[];
167
+ merklBorrowIncentives: import("../types").IncentiveData[];
162
168
  ratio: string;
163
169
  collRatio: string;
164
170
  minRatio: string;
@@ -191,6 +197,8 @@ export declare const _getUserPositionsPortfolio: (provider: PublicClient, networ
191
197
  leftToBorrowUsd: string;
192
198
  netApy: string;
193
199
  incentiveUsd: string;
200
+ merklSupplyIncentives: import("../types").IncentiveData[];
201
+ merklBorrowIncentives: import("../types").IncentiveData[];
194
202
  ratio: string;
195
203
  collRatio: string;
196
204
  minRatio: string;
@@ -912,6 +912,8 @@ export const EMPTY_FLUID_DATA = {
912
912
  minRatio: '0',
913
913
  netApy: '0',
914
914
  incentiveUsd: '0',
915
+ merklSupplyIncentives: [],
916
+ merklBorrowIncentives: [],
915
917
  totalInterestUsd: '0',
916
918
  isSubscribedToAutomation: false,
917
919
  automationResubscribeRequired: false,
@@ -1363,8 +1365,12 @@ export const _getUserPositionsPortfolio = (provider, network, user) => __awaiter
1363
1365
  tokens.push('ETH');
1364
1366
  if (!tokens.includes('WBTC'))
1365
1367
  tokens.push('WBTC');
1366
- const tokenPrices = yield getTokensPricesForPortfolio(tokens, provider, network);
1367
- const parsedMarketData = (yield Promise.all(data[1].map((vaultData) => __awaiter(void 0, void 0, void 0, function* () { return parseMarketData(provider, vaultData, network, tokenPrices); }))));
1368
+ const [tokenPrices, merklCampaigns] = yield Promise.all([
1369
+ getTokensPricesForPortfolio(tokens, provider, network),
1370
+ getFluidMerklCampaigns(network),
1371
+ ]);
1372
+ const parsedMarketData = (yield Promise.all(data[1].map((vaultData) => __awaiter(void 0, void 0, void 0, function* () { return parseMarketData(provider, vaultData, network, tokenPrices); }))))
1373
+ .map(marketData => (marketData ? attachFluidMerklIncentives(marketData, merklCampaigns) : marketData));
1368
1374
  const userData = data[0].map((position, i) => (parsedMarketData[i] && Object.assign({}, parseUserData(position, parsedMarketData[i]))));
1369
1375
  return parsedMarketData.map((market, i) => ({
1370
1376
  marketData: market,
@@ -1,3 +1,4 @@
1
+ import memoize from 'memoizee';
1
2
  import { FluidMarketData, FluidMerklRewardMap, MerklOpportunity, NetworkNumber } from '../types';
2
3
  /**
3
4
  * Merkl tags Fluid vault-scoped reward campaigns via the opportunity `type` field:
@@ -22,7 +23,7 @@ export declare const buildFluidMerklRewardMap: (opportunities: MerklOpportunity[
22
23
  * Fetches live vault-scoped Fluid campaigns for the chain, keyed by vault address. Never throws —
23
24
  * on any failure it returns an empty map, so Merkl being down can't break market data.
24
25
  */
25
- export declare const getFluidMerklCampaigns: (chainId: NetworkNumber) => Promise<FluidMerklRewardMap>;
26
+ export declare const getFluidMerklCampaigns: ((chainId: NetworkNumber) => Promise<FluidMerklRewardMap>) & memoize.Memoized<(chainId: NetworkNumber) => Promise<FluidMerklRewardMap>>;
26
27
  /**
27
28
  * Appends the vault's Merkl campaigns to its assets' incentive arrays — supply rewards on every
28
29
  * suppliable asset and borrow rewards on every borrowable one. Smart collateral/debt vaults hold
@@ -7,9 +7,10 @@ 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 memoize from 'memoizee';
10
11
  import { aprToApy } from '../moneymarket';
11
12
  import { fetchAllMerklOpportunities } from '../services/merkl';
12
- import { IncentiveKind, OpportunityStatus, } from '../types';
13
+ import { IncentiveKind, IncentiveSource, OpportunityStatus, } from '../types';
13
14
  /**
14
15
  * Merkl tags Fluid vault-scoped reward campaigns via the opportunity `type` field:
15
16
  * - FLUIDVAULT_COLLATERAL → reward for supplying collateral to the vault
@@ -38,6 +39,7 @@ const buildMerklIncentive = (opportunity) => {
38
39
  apy: aprToApy(opportunity.apr),
39
40
  token,
40
41
  incentiveKind: IncentiveKind.Reward,
42
+ source: IncentiveSource.Merkl,
41
43
  description: `Eligible for ${token} rewards through Merkl.${opportunity.description ? `\n${opportunity.description}` : ''}`,
42
44
  eligibilityId: opportunity.identifier,
43
45
  };
@@ -64,7 +66,7 @@ export const buildFluidMerklRewardMap = (opportunities, chainId) => {
64
66
  * Fetches live vault-scoped Fluid campaigns for the chain, keyed by vault address. Never throws —
65
67
  * on any failure it returns an empty map, so Merkl being down can't break market data.
66
68
  */
67
- export const getFluidMerklCampaigns = (chainId) => __awaiter(void 0, void 0, void 0, function* () {
69
+ export const getFluidMerklCampaigns = memoize((chainId) => __awaiter(void 0, void 0, void 0, function* () {
68
70
  try {
69
71
  const opportunities = yield fetchAllMerklOpportunities({
70
72
  mainProtocolId: 'fluid',
@@ -77,7 +79,7 @@ export const getFluidMerklCampaigns = (chainId) => __awaiter(void 0, void 0, voi
77
79
  console.error('Failed to fetch Fluid Merkl campaigns', e);
78
80
  return {};
79
81
  }
80
- });
82
+ }), { promise: true, maxAge: 60 * 1000 });
81
83
  /**
82
84
  * Appends the vault's Merkl campaigns to its assets' incentive arrays — supply rewards on every
83
85
  * suppliable asset and borrow rewards on every borrowable one. Smart collateral/debt vaults hold
@@ -1,4 +1,6 @@
1
- import { FluidAggregatedVaultData, FluidAssetData, FluidAssetsData, FluidDexBorrowDataStructOutput, FluidDexSupplyDataStructOutput, FluidUsedAsset, FluidUsedAssets, InnerFluidMarketData } from '../../types';
1
+ import { FluidAggregatedVaultData, FluidAssetData, FluidAssetsData, FluidDexBorrowDataStructOutput, FluidDexSupplyDataStructOutput, FluidUsedAsset, FluidUsedAssets, IncentiveData, InnerFluidMarketData } from '../../types';
2
+ import { IncentiveSide } from '../../types/common';
3
+ export declare const getUniqueFluidMerklIncentives: (assetsData: FluidAssetsData, side: IncentiveSide) => IncentiveData[];
2
4
  export declare const getFluidAggregatedData: ({ usedAssets, assetsData, marketData, }: {
3
5
  usedAssets: FluidUsedAssets;
4
6
  marketData: InnerFluidMarketData;
@@ -1,17 +1,41 @@
1
1
  import Dec from 'decimal.js';
2
2
  import { assetAmountInEth } from '@defisaver/tokens';
3
- import { FluidVaultType, } from '../../types';
3
+ import { FluidVaultType, IncentiveSource, } from '../../types';
4
4
  import { calcLeverageLiqPrice, getAssetsTotal, getExposure, isLeveragedPos, } from '../../moneymarket';
5
- import { calculateNetApy } from '../../staking';
6
- import { LeverageType } from '../../types/common';
5
+ import { calculateInterestEarned, calculateNetApy, EligibilityMapping } from '../../staking';
6
+ import { IncentiveSide, LeverageType } from '../../types/common';
7
7
  import { getEthAmountForDecimals } from '../../services/utils';
8
- const calculateNetApyDex = ({ marketData, suppliedUsd, borrowedUsd }) => {
8
+ export const getUniqueFluidMerklIncentives = (assetsData, side) => {
9
+ const field = side === IncentiveSide.Supply ? 'supplyIncentives' : 'borrowIncentives';
10
+ const incentives = Object.values(assetsData)
11
+ .flatMap(asset => asset[field])
12
+ .filter(incentive => incentive.source === IncentiveSource.Merkl);
13
+ return [...new Map(incentives.map(incentive => [
14
+ [incentive.eligibilityId, incentive.token, incentive.apy, incentive.description].join('|'),
15
+ incentive,
16
+ ])).values()];
17
+ };
18
+ const getMerklIncentiveInterest = (incentives, principal, usedAssets) => incentives.reduce((total, { apy, eligibilityId }) => {
19
+ const eligibilityCheck = eligibilityId ? EligibilityMapping[eligibilityId] : null;
20
+ const { isEligible, eligibleUSDAmount } = eligibilityCheck
21
+ ? eligibilityCheck(usedAssets, undefined)
22
+ : { isEligible: true, eligibleUSDAmount: principal };
23
+ const incentiveInterest = isEligible ? calculateInterestEarned(eligibleUSDAmount, apy, 'year', true) : 0;
24
+ return new Dec(total).add(incentiveInterest).toString();
25
+ }, '0');
26
+ const calculateNetApyDex = ({ marketData, assetsData, usedAssets, suppliedUsd, borrowedUsd, }) => {
9
27
  const { borrowRate, supplyRate, incentiveBorrowRate, incentiveSupplyRate, tradingBorrowRate, tradingSupplyRate, } = marketData;
10
28
  const totalBorrowRate = new Dec(borrowRate).minus(tradingBorrowRate || '0').toString();
11
29
  const totalSupplyRate = new Dec(supplyRate).add(tradingSupplyRate || '0').toString();
12
30
  const borrowIncentive = new Dec(incentiveBorrowRate || '0').mul(borrowedUsd).div(100).toString();
13
31
  const supplyIncentive = new Dec(incentiveSupplyRate || '0').mul(suppliedUsd).div(100).toString();
14
- const incentiveUsd = new Dec(supplyIncentive).minus(borrowIncentive).toString();
32
+ const merklSupplyIncentive = getMerklIncentiveInterest(getUniqueFluidMerklIncentives(assetsData, IncentiveSide.Supply), suppliedUsd, usedAssets);
33
+ const merklBorrowIncentive = getMerklIncentiveInterest(getUniqueFluidMerklIncentives(assetsData, IncentiveSide.Borrow), borrowedUsd, usedAssets);
34
+ const incentiveUsd = new Dec(supplyIncentive)
35
+ .minus(borrowIncentive)
36
+ .add(merklSupplyIncentive)
37
+ .add(merklBorrowIncentive)
38
+ .toString();
15
39
  const borrowInterest = new Dec(totalBorrowRate).mul(borrowedUsd).div(100).toString();
16
40
  const supplyInterest = new Dec(totalSupplyRate).mul(suppliedUsd).div(100).toString();
17
41
  const totalInterestUsd = new Dec(supplyInterest).add(incentiveUsd).minus(borrowInterest).toString();
@@ -32,7 +56,15 @@ export const getFluidAggregatedData = ({ usedAssets, assetsData, marketData, },
32
56
  ? getAssetsTotal(usedAssets, ({ isBorrowed }) => isBorrowed, ({ borrowedUsd }) => borrowedUsd)
33
57
  : new Dec(marketData.debtSharePrice).mul(borrowShares).toString();
34
58
  const isDex = [FluidVaultType.T2, FluidVaultType.T3, FluidVaultType.T4].includes(marketData.vaultType);
35
- const { netApy, incentiveUsd, totalInterestUsd } = isDex ? calculateNetApyDex({ marketData, suppliedUsd: payload.suppliedUsd, borrowedUsd: payload.borrowedUsd }) : calculateNetApy({ usedAssets, assetsData: assetsData });
59
+ payload.merklSupplyIncentives = getUniqueFluidMerklIncentives(assetsData, IncentiveSide.Supply);
60
+ payload.merklBorrowIncentives = getUniqueFluidMerklIncentives(assetsData, IncentiveSide.Borrow);
61
+ const { netApy, incentiveUsd, totalInterestUsd } = isDex ? calculateNetApyDex({
62
+ marketData,
63
+ assetsData,
64
+ usedAssets,
65
+ suppliedUsd: payload.suppliedUsd,
66
+ borrowedUsd: payload.borrowedUsd,
67
+ }) : calculateNetApy({ usedAssets, assetsData: assetsData });
36
68
  payload.netApy = netApy;
37
69
  payload.incentiveUsd = incentiveUsd;
38
70
  payload.totalInterestUsd = totalInterestUsd;
@@ -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 { getMeritUnclaimedRewards, getUnclaimedRewardsForAllMarkets } from '../claiming/aaveV3';
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
- aaveV3merit: {},
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 aaveMeritData = yield getMeritUnclaimedRewards(address, network);
339
- rewardsData[address.toLowerCase()].aaveV3merit = { error: '', data: aaveMeritData };
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 Aave V3 Merit rewards data for address ${address}:`, error);
343
- rewardsData[address.toLowerCase()].aaveV3merit = { error: `Error fetching Aave V3 Merit rewards data for address ${address}`, data: null };
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* () {
@@ -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 {};
@@ -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) */
@@ -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
  }
@@ -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";
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defisaver/positions-sdk",
3
- "version": "2.1.142",
3
+ "version": "2.1.143",
4
4
  "description": "",
5
5
  "main": "./cjs/index.js",
6
6
  "module": "./esm/index.js",
@@ -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 getMeritUnclaimedRewards(account: EthAddress, network: NetworkNumber): Promise<ClaimableToken[]> {
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: Aave Merit', error);
113
+ console.error('External API Failure: Merkl rewards', error);
114
114
  data = [];
115
115
  }
116
116
 
117
- const claimableTokens: ClaimableToken[] = [];
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.AAVE_MERIT_REWARDS,
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: 'AAVE Merit Rewards',
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
+ }
@@ -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 getTokensPricesForPortfolio(tokens, provider, network);
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
 
@@ -1,3 +1,4 @@
1
+ import memoize from 'memoizee';
1
2
  import { aprToApy } from '../moneymarket';
2
3
  import { fetchAllMerklOpportunities } from '../services/merkl';
3
4
  import {
@@ -6,6 +7,7 @@ import {
6
7
  IncentiveData,
7
8
  IncentiveEligibilityId,
8
9
  IncentiveKind,
10
+ IncentiveSource,
9
11
  MerklOpportunity,
10
12
  NetworkNumber,
11
13
  OpportunityStatus,
@@ -39,6 +41,7 @@ const buildMerklIncentive = (opportunity: MerklOpportunity): IncentiveData => {
39
41
  apy: aprToApy(opportunity.apr),
40
42
  token,
41
43
  incentiveKind: IncentiveKind.Reward,
44
+ source: IncentiveSource.Merkl,
42
45
  description: `Eligible for ${token} rewards through Merkl.${opportunity.description ? `\n${opportunity.description}` : ''}`,
43
46
  eligibilityId: opportunity.identifier as IncentiveEligibilityId,
44
47
  };
@@ -66,7 +69,7 @@ export const buildFluidMerklRewardMap = (opportunities: MerklOpportunity[], chai
66
69
  * Fetches live vault-scoped Fluid campaigns for the chain, keyed by vault address. Never throws —
67
70
  * on any failure it returns an empty map, so Merkl being down can't break market data.
68
71
  */
69
- export const getFluidMerklCampaigns = async (chainId: NetworkNumber): Promise<FluidMerklRewardMap> => {
72
+ export const getFluidMerklCampaigns = memoize(async (chainId: NetworkNumber): Promise<FluidMerklRewardMap> => {
70
73
  try {
71
74
  const opportunities = await fetchAllMerklOpportunities({
72
75
  mainProtocolId: 'fluid',
@@ -78,7 +81,7 @@ export const getFluidMerklCampaigns = async (chainId: NetworkNumber): Promise<Fl
78
81
  console.error('Failed to fetch Fluid Merkl campaigns', e);
79
82
  return {};
80
83
  }
81
- };
84
+ }, { promise: true, maxAge: 60 * 1000 });
82
85
 
83
86
  /**
84
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 calculateNetApyDex = ({ marketData, suppliedUsd, borrowedUsd }: { marketData: InnerFluidMarketData, suppliedUsd: string, borrowedUsd: string }) => {
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 incentiveUsd = new Dec(supplyIncentive).minus(borrowIncentive).toString();
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
- const { netApy, incentiveUsd, totalInterestUsd } = isDex ? calculateNetApyDex({ marketData, suppliedUsd: payload.suppliedUsd, borrowedUsd: payload.borrowedUsd }) : calculateNetApy({ usedAssets, assetsData: assetsData as unknown as MMAssetsData });
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
+ });
@@ -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 { getMeritUnclaimedRewards, getUnclaimedRewardsForAllMarkets } from '../claiming/aaveV3';
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
- aaveV3merit: {},
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 aaveMeritData = await getMeritUnclaimedRewards(address, network);
356
- rewardsData[address.toLowerCase() as EthAddress].aaveV3merit = { error: '', data: aaveMeritData };
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 Aave V3 Merit rewards data for address ${address}:`, error);
359
- rewardsData[address.toLowerCase() as EthAddress].aaveV3merit = { error: `Error fetching Aave V3 Merit rewards data for address ${address}`, data: null };
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) => {
@@ -5,6 +5,8 @@ export enum ClaimType {
5
5
  AAVE_REWARDS = 'AAVE_REWARDS',
6
6
  /** Merit rewards from AAVE (various tokens) */
7
7
  AAVE_MERIT_REWARDS = 'AAVE_MERIT_REWARDS',
8
+ /** Rewards distributed through Merkl across supported protocols */
9
+ MERKL_REWARDS = 'MERKL_REWARDS',
8
10
  /** Rewards from Compound V3 (only in COMP) */
9
11
  COMPOUND_V3_COMP = 'COMPOUND_V3_COMP',
10
12
  /** Rewards from Spark (wstETH only for now) */
@@ -47,6 +49,16 @@ export type AaveMeritRewardsClaimableToken = _ClaimableTokenPartial & {
47
49
  };
48
50
  };
49
51
 
52
+ export type MerklRewardsClaimableToken = _ClaimableTokenPartial & {
53
+ claimType: ClaimType.MERKL_REWARDS;
54
+ additionalClaimFields: {
55
+ accumulated: string;
56
+ proof: string[];
57
+ decimals: string;
58
+ unclaimed: string;
59
+ };
60
+ };
61
+
50
62
  export type SparkRewardsClaimableToken = _ClaimableTokenPartial & {
51
63
  claimType: ClaimType.SPARK_REWARDS;
52
64
  additionalClaimFields: {
@@ -102,6 +114,7 @@ export type UniswapAirdropClaimableToken = _ClaimableTokenPartial & {
102
114
  export type ClaimableToken =
103
115
  AaveRewardsClaimableToken
104
116
  | AaveMeritRewardsClaimableToken
117
+ | MerklRewardsClaimableToken
105
118
  | CompoundV3CompClaimableToken
106
119
  | SparkRewardsClaimableToken
107
120
  | KingRewardsClaimableToken
@@ -8,6 +8,10 @@ export enum IncentiveSide {
8
8
  Borrow = 'borrow',
9
9
  }
10
10
 
11
+ export enum IncentiveSource {
12
+ Merkl = 'merkl',
13
+ }
14
+
11
15
  export enum IncentiveEligibilityId {
12
16
  AaveV3EthenaLiquidLeverage = '0x0dC599DBB180E64E42b87332FDeC3a551445ea44BORROW_BL',
13
17
  AaveV3ArbitrumEthSupply = '0x5d16261c6715a653248269861bbacf68a9774cde',
@@ -29,6 +33,7 @@ export interface IncentiveData {
29
33
  token: string,
30
34
  apy: string,
31
35
  incentiveKind?: IncentiveKind;
36
+ source?: IncentiveSource;
32
37
  description?: string;
33
38
  eligibilityId?: IncentiveEligibilityId;
34
39
  }
@@ -340,6 +340,8 @@ export interface FluidAggregatedVaultData {
340
340
  leftToBorrowUsd: string,
341
341
  netApy: string,
342
342
  incentiveUsd: string,
343
+ merklSupplyIncentives: IncentiveData[],
344
+ merklBorrowIncentives: IncentiveData[],
343
345
  ratio: string,
344
346
  collRatio: string,
345
347
  minRatio: string,
@@ -484,4 +486,4 @@ export interface FluidFTokenDataStructOutput {
484
486
  supplyRate: bigint;
485
487
  rewardsRate: bigint;
486
488
  withdrawable: bigint;
487
- }
489
+ }