@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.
Files changed (61) hide show
  1. package/cjs/aaveV3/merkl.js +4 -7
  2. package/cjs/aaveV4/merkl.js +5 -6
  3. package/cjs/claiming/aaveV3.d.ts +2 -1
  4. package/cjs/claiming/aaveV3.js +13 -4
  5. package/cjs/compoundV3/merkl.js +4 -6
  6. package/cjs/fluid/index.d.ts +9 -1
  7. package/cjs/fluid/index.js +10 -4
  8. package/cjs/fluid/merkl.d.ts +7 -4
  9. package/cjs/fluid/merkl.js +22 -16
  10. package/cjs/helpers/fluidHelpers/index.d.ts +3 -1
  11. package/cjs/helpers/fluidHelpers/index.js +37 -4
  12. package/cjs/morphoBlue/index.d.ts +1 -1
  13. package/cjs/morphoBlue/index.js +2 -2
  14. package/cjs/morphoBlue/merkl.d.ts +6 -4
  15. package/cjs/morphoBlue/merkl.js +17 -16
  16. package/cjs/portfolio/index.js +5 -5
  17. package/cjs/services/merkl.d.ts +6 -0
  18. package/cjs/services/merkl.js +39 -0
  19. package/cjs/types/claiming.d.ts +12 -1
  20. package/cjs/types/claiming.js +2 -0
  21. package/cjs/types/common.d.ts +4 -0
  22. package/cjs/types/common.js +5 -1
  23. package/cjs/types/fluid.d.ts +2 -0
  24. package/esm/aaveV3/merkl.js +5 -8
  25. package/esm/aaveV4/merkl.js +5 -6
  26. package/esm/claiming/aaveV3.d.ts +2 -1
  27. package/esm/claiming/aaveV3.js +12 -4
  28. package/esm/compoundV3/merkl.js +5 -7
  29. package/esm/fluid/index.d.ts +9 -1
  30. package/esm/fluid/index.js +9 -3
  31. package/esm/fluid/merkl.d.ts +7 -4
  32. package/esm/fluid/merkl.js +19 -15
  33. package/esm/helpers/fluidHelpers/index.d.ts +3 -1
  34. package/esm/helpers/fluidHelpers/index.js +38 -6
  35. package/esm/morphoBlue/index.d.ts +1 -1
  36. package/esm/morphoBlue/index.js +1 -1
  37. package/esm/morphoBlue/merkl.d.ts +6 -4
  38. package/esm/morphoBlue/merkl.js +16 -15
  39. package/esm/portfolio/index.js +6 -6
  40. package/esm/services/merkl.d.ts +6 -0
  41. package/esm/services/merkl.js +35 -0
  42. package/esm/types/claiming.d.ts +12 -1
  43. package/esm/types/claiming.js +2 -0
  44. package/esm/types/common.d.ts +4 -0
  45. package/esm/types/common.js +4 -0
  46. package/esm/types/fluid.d.ts +2 -0
  47. package/package.json +1 -1
  48. package/src/aaveV3/merkl.ts +5 -8
  49. package/src/aaveV4/merkl.ts +5 -5
  50. package/src/claiming/aaveV3.ts +19 -7
  51. package/src/compoundV3/merkl.ts +5 -6
  52. package/src/fluid/index.ts +9 -3
  53. package/src/fluid/merkl.ts +18 -13
  54. package/src/helpers/fluidHelpers/index.ts +68 -6
  55. package/src/morphoBlue/index.ts +1 -1
  56. package/src/morphoBlue/merkl.ts +16 -15
  57. package/src/portfolio/index.ts +6 -6
  58. package/src/services/merkl.ts +31 -0
  59. package/src/types/claiming.ts +13 -0
  60. package/src/types/common.ts +5 -0
  61. package/src/types/fluid.ts +3 -1
@@ -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;
@@ -8,7 +8,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { aprToApy } from '../moneymarket';
11
- import { LONGER_TIMEOUT, wethToEth } from '../services/utils';
11
+ import { fetchAllMerklOpportunities } from '../services/merkl';
12
+ import { wethToEth } from '../services/utils';
12
13
  import { OpportunityAction, OpportunityStatus, } from '../types';
13
14
  export const getAaveUnderlyingSymbol = (_symbol = '') => {
14
15
  let symbol = _symbol
@@ -35,14 +36,10 @@ export const formatAaveAsset = (_symbol) => {
35
36
  };
36
37
  export const getMerkleCampaigns = (chainId) => __awaiter(void 0, void 0, void 0, function* () {
37
38
  try {
38
- // status/items narrow the query because Merkl paginates at 20 items by default — an
39
- // unfiltered query silently drops live campaigns once the protocol has enough opportunities
40
- const res = yield fetch('https://fe.defisaver.com/api/merkl/opportunities?mainProtocolId=aave&status=LIVE&items=100', {
41
- signal: AbortSignal.timeout(LONGER_TIMEOUT),
39
+ const opportunities = yield fetchAllMerklOpportunities({
40
+ mainProtocolId: 'aave',
41
+ status: OpportunityStatus.LIVE,
42
42
  });
43
- if (!res.ok)
44
- throw new Error('Failed to fetch Merkle campaigns');
45
- const opportunities = yield res.json();
46
43
  const relevantOpportunities = opportunities
47
44
  .filter((o) => o.chainId === chainId)
48
45
  .filter((o) => o.status === OpportunityStatus.LIVE);
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { aprToApy } from '../moneymarket';
11
- import { LONGER_TIMEOUT } from '../services/utils';
11
+ import { fetchAllMerklOpportunities } from '../services/merkl';
12
12
  import { IncentiveKind, IncentiveSide, OpportunityAction, OpportunityStatus, } from '../types';
13
13
  /**
14
14
  * Merkl tags Aave V4 reward campaigns by scope via the `type` field:
@@ -63,12 +63,11 @@ export const buildAaveV4MerklRewardMap = (opportunities, chainId) => {
63
63
  };
64
64
  export const getAaveV4MerkleCampaigns = (chainId) => __awaiter(void 0, void 0, void 0, function* () {
65
65
  try {
66
- const res = yield fetch('https://fe.defisaver.com/api/merkl/opportunities?mainProtocolId=aave&type=AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,AAVE_V4_SPOKE_SUPPLY,AAVE_V4_SPOKE_BORROW&status=LIVE&items=100', {
67
- signal: AbortSignal.timeout(LONGER_TIMEOUT),
66
+ const opportunities = yield fetchAllMerklOpportunities({
67
+ mainProtocolId: 'aave',
68
+ type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,AAVE_V4_SPOKE_SUPPLY,AAVE_V4_SPOKE_BORROW',
69
+ status: OpportunityStatus.LIVE,
68
70
  });
69
- if (!res.ok)
70
- throw new Error('Failed to fetch Aave V4 Merkle campaigns');
71
- const opportunities = yield res.json();
72
71
  return buildAaveV4MerklRewardMap(opportunities, chainId);
73
72
  }
74
73
  catch (e) {
@@ -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
+ }
@@ -9,7 +9,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { getAssetInfoByAddress } from '@defisaver/tokens';
11
11
  import { aprToApy } from '../moneymarket';
12
- import { compareAddresses, LONGER_TIMEOUT, wethToEth } from '../services/utils';
12
+ import { fetchAllMerklOpportunities } from '../services/merkl';
13
+ import { compareAddresses, wethToEth } from '../services/utils';
13
14
  import { IncentiveKind, OpportunityAction, OpportunityStatus, } from '../types';
14
15
  /**
15
16
  * Merkl tags Compound V3 reward campaigns with COMPOUND_V3_* opportunity types (e.g.
@@ -21,13 +22,10 @@ import { IncentiveKind, OpportunityAction, OpportunityStatus, } from '../types';
21
22
  */
22
23
  export const getCompoundV3MerklOpportunities = () => __awaiter(void 0, void 0, void 0, function* () {
23
24
  try {
24
- const res = yield fetch('https://fe.defisaver.com/api/merkl/opportunities?mainProtocolId=compound-v3&status=LIVE&items=100', {
25
- signal: AbortSignal.timeout(LONGER_TIMEOUT),
25
+ return yield fetchAllMerklOpportunities({
26
+ mainProtocolId: 'compound-v3',
27
+ status: OpportunityStatus.LIVE,
26
28
  });
27
- if (!res.ok)
28
- throw new Error('Failed to fetch Compound V3 Merkl campaigns');
29
- const data = yield res.json();
30
- return Array.isArray(data) ? data : [];
31
29
  }
32
30
  catch (e) {
33
31
  console.error('Failed to fetch Compound V3 Merkl campaigns', e);
@@ -1,7 +1,7 @@
1
1
  import { Client, PublicClient } from 'viem';
2
2
  import { EthAddress, EthereumProvider, NetworkNumber } from '../types/common';
3
3
  import { FluidAssetsData, FluidMarketData, FluidMarketInfo, FluidUsedAssets, FluidVaultData, InnerFluidMarketData } from '../types';
4
- export { FLUID_MERKL_OPPORTUNITY_TYPES, buildFluidMerklRewardMap, getFluidMerklCampaigns, attachFluidMerklIncentives, } from './merkl';
4
+ export { FluidMerklOpportunityType, buildFluidMerklRewardMap, getFluidMerklCampaigns, attachFluidMerklIncentives, } from './merkl';
5
5
  export declare const EMPTY_USED_ASSET: {
6
6
  isSupplied: boolean;
7
7
  isBorrowed: boolean;
@@ -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;
@@ -20,7 +20,7 @@ import { getChainlinkAssetAddress, getSyrupUSDTChainLinkPriceCalls, getSyrupUSDT
20
20
  import { getStakingApy, STAKING_ASSETS } from '../staking';
21
21
  import { getViemProvider } from '../services/viem';
22
22
  import { attachFluidMerklIncentives, getFluidMerklCampaigns } from './merkl';
23
- export { FLUID_MERKL_OPPORTUNITY_TYPES, buildFluidMerklRewardMap, getFluidMerklCampaigns, attachFluidMerklIncentives, } from './merkl';
23
+ export { FluidMerklOpportunityType, buildFluidMerklRewardMap, getFluidMerklCampaigns, attachFluidMerklIncentives, } from './merkl';
24
24
  export const EMPTY_USED_ASSET = {
25
25
  isSupplied: false,
26
26
  isBorrowed: false,
@@ -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:
@@ -13,14 +14,16 @@ import { FluidMarketData, FluidMerklRewardMap, MerklOpportunity, NetworkNumber }
13
14
  * ids apply unconditionally, mirroring Aave V3. Whitelist-gated campaigns only accrue to
14
15
  * whitelisted addresses, so they are skipped rather than advertised to every user.
15
16
  */
16
- export declare const FLUID_MERKL_OPPORTUNITY_TYPES: readonly ["FLUIDVAULT_COLLATERAL", "FLUIDVAULT_BORROW"];
17
+ export declare enum FluidMerklOpportunityType {
18
+ Collateral = "FLUIDVAULT_COLLATERAL",
19
+ Borrow = "FLUIDVAULT_BORROW"
20
+ }
17
21
  export declare const buildFluidMerklRewardMap: (opportunities: MerklOpportunity[], chainId: NetworkNumber) => FluidMerklRewardMap;
18
22
  /**
19
23
  * Fetches live vault-scoped Fluid campaigns for the chain, keyed by vault address. Never throws —
20
- * on any failure it returns an empty map, so Merkl being down can't break market data. The query
21
- * is narrowed by `type=` and capped at `items=100` because Merkl paginates at 20 items by default.
24
+ * on any failure it returns an empty map, so Merkl being down can't break market data.
22
25
  */
23
- export declare const getFluidMerklCampaigns: (chainId: NetworkNumber) => Promise<FluidMerklRewardMap>;
26
+ export declare const getFluidMerklCampaigns: ((chainId: NetworkNumber) => Promise<FluidMerklRewardMap>) & memoize.Memoized<(chainId: NetworkNumber) => Promise<FluidMerklRewardMap>>;
24
27
  /**
25
28
  * Appends the vault's Merkl campaigns to its assets' incentive arrays — supply rewards on every
26
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
- import { LONGER_TIMEOUT } from '../services/utils';
12
- import { IncentiveKind, OpportunityStatus, } from '../types';
12
+ import { fetchAllMerklOpportunities } from '../services/merkl';
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
@@ -24,7 +25,11 @@ import { IncentiveKind, OpportunityStatus, } from '../types';
24
25
  * ids apply unconditionally, mirroring Aave V3. Whitelist-gated campaigns only accrue to
25
26
  * whitelisted addresses, so they are skipped rather than advertised to every user.
26
27
  */
27
- export const FLUID_MERKL_OPPORTUNITY_TYPES = ['FLUIDVAULT_COLLATERAL', 'FLUIDVAULT_BORROW'];
28
+ export var FluidMerklOpportunityType;
29
+ (function (FluidMerklOpportunityType) {
30
+ FluidMerklOpportunityType["Collateral"] = "FLUIDVAULT_COLLATERAL";
31
+ FluidMerklOpportunityType["Borrow"] = "FLUIDVAULT_BORROW";
32
+ })(FluidMerklOpportunityType || (FluidMerklOpportunityType = {}));
28
33
  // '0x' + 20-byte vault address; conditional campaigns append a marker after it
29
34
  const VAULT_ADDRESS_LENGTH = 42;
30
35
  const buildMerklIncentive = (opportunity) => {
@@ -34,6 +39,7 @@ const buildMerklIncentive = (opportunity) => {
34
39
  apy: aprToApy(opportunity.apr),
35
40
  token,
36
41
  incentiveKind: IncentiveKind.Reward,
42
+ source: IncentiveSource.Merkl,
37
43
  description: `Eligible for ${token} rewards through Merkl.${opportunity.description ? `\n${opportunity.description}` : ''}`,
38
44
  eligibilityId: opportunity.identifier,
39
45
  };
@@ -43,7 +49,7 @@ export const buildFluidMerklRewardMap = (opportunities, chainId) => {
43
49
  opportunities
44
50
  .filter((o) => o.chainId === chainId)
45
51
  .filter((o) => o.status === OpportunityStatus.LIVE)
46
- .filter((o) => FLUID_MERKL_OPPORTUNITY_TYPES.includes(o.type))
52
+ .filter((o) => Object.values(FluidMerklOpportunityType).includes(o.type))
47
53
  .filter((o) => { var _a; return !((_a = o.identifier) === null || _a === void 0 ? void 0 : _a.includes('WHITELIST')); })
48
54
  .forEach((o) => {
49
55
  var _a;
@@ -52,30 +58,28 @@ export const buildFluidMerklRewardMap = (opportunities, chainId) => {
52
58
  return;
53
59
  if (!result[vaultAddress])
54
60
  result[vaultAddress] = { supply: [], borrow: [] };
55
- result[vaultAddress][o.type === 'FLUIDVAULT_BORROW' ? 'borrow' : 'supply'].push(buildMerklIncentive(o));
61
+ result[vaultAddress][o.type === FluidMerklOpportunityType.Borrow ? 'borrow' : 'supply'].push(buildMerklIncentive(o));
56
62
  });
57
63
  return result;
58
64
  };
59
65
  /**
60
66
  * Fetches live vault-scoped Fluid campaigns for the chain, keyed by vault address. Never throws —
61
- * on any failure it returns an empty map, so Merkl being down can't break market data. The query
62
- * is narrowed by `type=` and capped at `items=100` because Merkl paginates at 20 items by default.
67
+ * on any failure it returns an empty map, so Merkl being down can't break market data.
63
68
  */
64
- 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* () {
65
70
  try {
66
- const res = yield fetch(`https://fe.defisaver.com/api/merkl/opportunities?mainProtocolId=fluid&type=${FLUID_MERKL_OPPORTUNITY_TYPES.join(',')}&status=LIVE&items=100`, {
67
- signal: AbortSignal.timeout(LONGER_TIMEOUT),
71
+ const opportunities = yield fetchAllMerklOpportunities({
72
+ mainProtocolId: 'fluid',
73
+ type: Object.values(FluidMerklOpportunityType).join(','),
74
+ status: OpportunityStatus.LIVE,
68
75
  });
69
- if (!res.ok)
70
- throw new Error('Failed to fetch Fluid Merkl campaigns');
71
- const opportunities = yield res.json();
72
- return buildFluidMerklRewardMap(Array.isArray(opportunities) ? opportunities : [], chainId);
76
+ return buildFluidMerklRewardMap(opportunities, chainId);
73
77
  }
74
78
  catch (e) {
75
79
  console.error('Failed to fetch Fluid Merkl campaigns', e);
76
80
  return {};
77
81
  }
78
- });
82
+ }), { promise: true, maxAge: 60 * 1000 });
79
83
  /**
80
84
  * Appends the vault's Merkl campaigns to its assets' incentive arrays — supply rewards on every
81
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;
@@ -1,7 +1,7 @@
1
1
  import { Client } from 'viem';
2
2
  import { Blockish, EthAddress, EthereumProvider, NetworkNumber, PositionBalances } from '../types/common';
3
3
  import { MorphoBlueEarnData, MorphoBlueMarketData, MorphoBlueMarketInfo, MorphoBlueMarketRewards, MorphoBluePositionData } from '../types';
4
- export { MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES, addMorphoBlueMerklOpportunitiesToMarketInfo, getMorphoBlueMerklOpportunities, addMorphoBlueMerklRewardsToMarketInfo, } from './merkl';
4
+ export { MorphoBlueMerklOpportunityType, addMorphoBlueMerklOpportunitiesToMarketInfo, getMorphoBlueMerklOpportunities, addMorphoBlueMerklRewardsToMarketInfo, } from './merkl';
5
5
  export declare const addMorphoBlueRewardsToMarketInfo: (marketInfo: MorphoBlueMarketInfo, rewards: MorphoBlueMarketRewards) => MorphoBlueMarketInfo;
6
6
  export declare function _getMorphoBlueMarketData(provider: Client, network: NetworkNumber, selectedMarket: MorphoBlueMarketData): Promise<MorphoBlueMarketInfo>;
7
7
  export declare function _getMorphoBluePortfolioMarketData(provider: Client, network: NetworkNumber, selectedMarket: MorphoBlueMarketData): Promise<MorphoBlueMarketInfo>;
@@ -18,7 +18,7 @@ import { getBorrowRate, getMorphoBlueAggregatedPositionData, getRewardsForMarket
18
18
  import { getChainlinkAssetAddress } from '../services/priceService';
19
19
  import { getViemProvider, setViemBlockNumber } from '../services/viem';
20
20
  import { addMorphoBlueMerklOpportunitiesToMarketInfo, getMorphoBlueMerklOpportunities } from './merkl';
21
- export { MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES, addMorphoBlueMerklOpportunitiesToMarketInfo, getMorphoBlueMerklOpportunities, addMorphoBlueMerklRewardsToMarketInfo, } from './merkl';
21
+ export { MorphoBlueMerklOpportunityType, addMorphoBlueMerklOpportunitiesToMarketInfo, getMorphoBlueMerklOpportunities, addMorphoBlueMerklRewardsToMarketInfo, } from './merkl';
22
22
  const HARDCODED_USD_STABLE_PRICE = '100000000'; // $1 with 8 decimals
23
23
  const getMorphoRewardIncentives = (apy) => [{
24
24
  token: 'MORPHO',
@@ -12,7 +12,11 @@ import { MerklOpportunity, MorphoBlueMarketData, MorphoBlueMarketInfo, NetworkNu
12
12
  * identifier (e.g. `0x54cf…c46WHITELIST_CAMPAIGN`); their rewards only accrue to whitelisted
13
13
  * addresses, so they are skipped rather than advertised to every user.
14
14
  */
15
- export declare const MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES: readonly ["MORPHOSUPPLY", "MORPHOCOLLATERAL", "MORPHOBORROW"];
15
+ export declare enum MorphoBlueMerklOpportunityType {
16
+ Supply = "MORPHOSUPPLY",
17
+ Collateral = "MORPHOCOLLATERAL",
18
+ Borrow = "MORPHOBORROW"
19
+ }
16
20
  /**
17
21
  * Appends the given live Merkl campaigns for the market to its `assetsData` incentive arrays
18
22
  * (collateral supply / loan supply / loan borrow). Existing Merkl incentives are replaced rather
@@ -21,9 +25,7 @@ export declare const MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES: readonly ["MORPHOSUPPL
21
25
  export declare const addMorphoBlueMerklOpportunitiesToMarketInfo: (marketInfo: MorphoBlueMarketInfo, selectedMarket: MorphoBlueMarketData, chainId: NetworkNumber, opportunities: MerklOpportunity[]) => MorphoBlueMarketInfo;
22
26
  /**
23
27
  * Fetches live market-scoped Morpho Blue campaigns for all chains. Never throws — on any failure
24
- * it returns an empty list, so Merkl being down can't break market data. The query is narrowed by
25
- * `type=` and capped at `items=100` because Merkl paginates at 20 items by default — an
26
- * unfiltered protocol query would silently drop campaigns once Morpho has enough opportunities.
28
+ * it returns an empty list, so Merkl being down can't break market data.
27
29
  */
28
30
  export declare const getMorphoBlueMerklOpportunities: () => Promise<MerklOpportunity[]>;
29
31
  /**
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { aprToApy } from '../moneymarket';
11
- import { LONGER_TIMEOUT } from '../services/utils';
11
+ import { fetchAllMerklOpportunities } from '../services/merkl';
12
12
  import { IncentiveKind, OpportunityStatus, } from '../types';
13
13
  /**
14
14
  * Merkl tags Morpho Blue market-level reward campaigns via the opportunity `type` field:
@@ -23,7 +23,12 @@ import { IncentiveKind, OpportunityStatus, } from '../types';
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 const MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES = ['MORPHOSUPPLY', 'MORPHOCOLLATERAL', 'MORPHOBORROW'];
26
+ export var MorphoBlueMerklOpportunityType;
27
+ (function (MorphoBlueMerklOpportunityType) {
28
+ MorphoBlueMerklOpportunityType["Supply"] = "MORPHOSUPPLY";
29
+ MorphoBlueMerklOpportunityType["Collateral"] = "MORPHOCOLLATERAL";
30
+ MorphoBlueMerklOpportunityType["Borrow"] = "MORPHOBORROW";
31
+ })(MorphoBlueMerklOpportunityType || (MorphoBlueMerklOpportunityType = {}));
27
32
  const MERKL_DESCRIPTION_MARKER = 'through Merkl';
28
33
  // '0x' + first 20 bytes of the market id
29
34
  const MARKET_ID_PREFIX_LENGTH = 42;
@@ -54,7 +59,7 @@ export const addMorphoBlueMerklOpportunitiesToMarketInfo = (marketInfo, selected
54
59
  var _a;
55
60
  return o.chainId === chainId
56
61
  && o.status === OpportunityStatus.LIVE
57
- && MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES.includes(o.type)
62
+ && Object.values(MorphoBlueMerklOpportunityType).includes(o.type)
58
63
  // a suffixed identifier marks a whitelist-gated campaign — skip those
59
64
  && ((_a = o.identifier) === null || _a === void 0 ? void 0 : _a.length) === MARKET_ID_PREFIX_LENGTH
60
65
  && o.identifier.toLowerCase() === marketIdPrefix;
@@ -62,26 +67,22 @@ export const addMorphoBlueMerklOpportunitiesToMarketInfo = (marketInfo, selected
62
67
  if (!relevant.length)
63
68
  return marketInfo;
64
69
  const incentivesByType = (type) => relevant.filter((o) => o.type === type).map(buildMerklIncentive);
65
- const collateralSupply = incentivesByType('MORPHOCOLLATERAL');
66
- const loanSupply = incentivesByType('MORPHOSUPPLY');
67
- const loanBorrow = incentivesByType('MORPHOBORROW');
70
+ const collateralSupply = incentivesByType(MorphoBlueMerklOpportunityType.Collateral);
71
+ const loanSupply = incentivesByType(MorphoBlueMerklOpportunityType.Supply);
72
+ const loanBorrow = incentivesByType(MorphoBlueMerklOpportunityType.Borrow);
68
73
  return Object.assign(Object.assign({}, marketInfo), { assetsData: Object.assign(Object.assign({}, marketInfo.assetsData), { [marketInfo.collateralToken]: Object.assign(Object.assign({}, collateralAsset), { supplyIncentives: [...withoutMerklIncentives(collateralAsset.supplyIncentives), ...collateralSupply] }), [marketInfo.loanToken]: Object.assign(Object.assign({}, loanAsset), { supplyIncentives: [...withoutMerklIncentives(loanAsset.supplyIncentives), ...loanSupply], borrowIncentives: [...withoutMerklIncentives(loanAsset.borrowIncentives), ...loanBorrow] }) }) });
69
74
  };
70
75
  /**
71
76
  * Fetches live market-scoped Morpho Blue campaigns for all chains. Never throws — on any failure
72
- * it returns an empty list, so Merkl being down can't break market data. The query is narrowed by
73
- * `type=` and capped at `items=100` because Merkl paginates at 20 items by default — an
74
- * unfiltered protocol query would silently drop campaigns once Morpho has enough opportunities.
77
+ * it returns an empty list, so Merkl being down can't break market data.
75
78
  */
76
79
  export const getMorphoBlueMerklOpportunities = () => __awaiter(void 0, void 0, void 0, function* () {
77
80
  try {
78
- const res = yield fetch(`https://fe.defisaver.com/api/merkl/opportunities?mainProtocolId=morpho&type=${MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES.join(',')}&status=LIVE&items=100`, {
79
- signal: AbortSignal.timeout(LONGER_TIMEOUT),
81
+ return yield fetchAllMerklOpportunities({
82
+ mainProtocolId: 'morpho',
83
+ type: Object.values(MorphoBlueMerklOpportunityType).join(','),
84
+ status: OpportunityStatus.LIVE,
80
85
  });
81
- if (!res.ok)
82
- throw new Error('Failed to fetch Morpho Blue Merkl campaigns');
83
- const data = yield res.json();
84
- return Array.isArray(data) ? data : [];
85
86
  }
86
87
  catch (e) {
87
88
  console.error('Failed to fetch Morpho Blue Merkl campaigns', e);