@defisaver/positions-sdk 2.1.142-dev → 2.1.142

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.
@@ -11,6 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.getMerkleCampaigns = exports.formatAaveAsset = exports.getAaveUnderlyingSymbol = void 0;
13
13
  const moneymarket_1 = require("../moneymarket");
14
+ const merkl_1 = require("../services/merkl");
14
15
  const utils_1 = require("../services/utils");
15
16
  const types_1 = require("../types");
16
17
  const getAaveUnderlyingSymbol = (_symbol = '') => {
@@ -40,14 +41,10 @@ const formatAaveAsset = (_symbol) => {
40
41
  exports.formatAaveAsset = formatAaveAsset;
41
42
  const getMerkleCampaigns = (chainId) => __awaiter(void 0, void 0, void 0, function* () {
42
43
  try {
43
- // status/items narrow the query because Merkl paginates at 20 items by default — an
44
- // unfiltered query silently drops live campaigns once the protocol has enough opportunities
45
- const res = yield fetch('https://fe.defisaver.com/api/merkl/opportunities?mainProtocolId=aave&status=LIVE&items=100', {
46
- signal: AbortSignal.timeout(utils_1.LONGER_TIMEOUT),
44
+ const opportunities = yield (0, merkl_1.fetchAllMerklOpportunities)({
45
+ mainProtocolId: 'aave',
46
+ status: types_1.OpportunityStatus.LIVE,
47
47
  });
48
- if (!res.ok)
49
- throw new Error('Failed to fetch Merkle campaigns');
50
- const opportunities = yield res.json();
51
48
  const relevantOpportunities = opportunities
52
49
  .filter((o) => o.chainId === chainId)
53
50
  .filter((o) => o.status === types_1.OpportunityStatus.LIVE);
@@ -11,7 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.attachAaveV4MerklIncentives = exports.getAaveV4MerkleCampaigns = exports.buildAaveV4MerklRewardMap = void 0;
13
13
  const moneymarket_1 = require("../moneymarket");
14
- const utils_1 = require("../services/utils");
14
+ const merkl_1 = require("../services/merkl");
15
15
  const types_1 = require("../types");
16
16
  /**
17
17
  * Merkl tags Aave V4 reward campaigns by scope via the `type` field:
@@ -67,12 +67,11 @@ const buildAaveV4MerklRewardMap = (opportunities, chainId) => {
67
67
  exports.buildAaveV4MerklRewardMap = buildAaveV4MerklRewardMap;
68
68
  const getAaveV4MerkleCampaigns = (chainId) => __awaiter(void 0, void 0, void 0, function* () {
69
69
  try {
70
- 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', {
71
- signal: AbortSignal.timeout(utils_1.LONGER_TIMEOUT),
70
+ const opportunities = yield (0, merkl_1.fetchAllMerklOpportunities)({
71
+ mainProtocolId: 'aave',
72
+ type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,AAVE_V4_SPOKE_SUPPLY,AAVE_V4_SPOKE_BORROW',
73
+ status: types_1.OpportunityStatus.LIVE,
72
74
  });
73
- if (!res.ok)
74
- throw new Error('Failed to fetch Aave V4 Merkle campaigns');
75
- const opportunities = yield res.json();
76
75
  return (0, exports.buildAaveV4MerklRewardMap)(opportunities, chainId);
77
76
  }
78
77
  catch (e) {
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.attachCompoundV3MerklIncentives = exports.getCompoundV3MerklOpportunities = void 0;
13
13
  const tokens_1 = require("@defisaver/tokens");
14
14
  const moneymarket_1 = require("../moneymarket");
15
+ const merkl_1 = require("../services/merkl");
15
16
  const utils_1 = require("../services/utils");
16
17
  const types_1 = require("../types");
17
18
  /**
@@ -24,13 +25,10 @@ const types_1 = require("../types");
24
25
  */
25
26
  const getCompoundV3MerklOpportunities = () => __awaiter(void 0, void 0, void 0, function* () {
26
27
  try {
27
- const res = yield fetch('https://fe.defisaver.com/api/merkl/opportunities?mainProtocolId=compound-v3&status=LIVE&items=100', {
28
- signal: AbortSignal.timeout(utils_1.LONGER_TIMEOUT),
28
+ return yield (0, merkl_1.fetchAllMerklOpportunities)({
29
+ mainProtocolId: 'compound-v3',
30
+ status: types_1.OpportunityStatus.LIVE,
29
31
  });
30
- if (!res.ok)
31
- throw new Error('Failed to fetch Compound V3 Merkl campaigns');
32
- const data = yield res.json();
33
- return Array.isArray(data) ? data : [];
34
32
  }
35
33
  catch (e) {
36
34
  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;
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports._getUserPositionsPortfolio = exports.getUserPositions = exports._getUserPositions = exports.getAllUserEarnPositionsWithFTokens = exports._getAllUserEarnPositionsWithFTokens = exports.getFluidDepositData = exports._getFluidDepositData = exports.getFluidTokenData = exports._getFluidTokenData = exports.getAllFluidMarketDataChunked = exports._getAllFluidMarketDataChunked = exports.getFluidPositionWithMarket = exports._getFluidPositionWithMarket = exports.getFluidPosition = exports._getFluidPosition = exports.getFluidVaultIdsForUser = exports._getFluidVaultIdsForUser = exports.getFluidMarketData = exports._getFluidMarketData = exports.EMPTY_FLUID_DATA = exports.EMPTY_USED_ASSET = exports.attachFluidMerklIncentives = exports.getFluidMerklCampaigns = exports.buildFluidMerklRewardMap = exports.FLUID_MERKL_OPPORTUNITY_TYPES = void 0;
15
+ exports._getUserPositionsPortfolio = exports.getUserPositions = exports._getUserPositions = exports.getAllUserEarnPositionsWithFTokens = exports._getAllUserEarnPositionsWithFTokens = exports.getFluidDepositData = exports._getFluidDepositData = exports.getFluidTokenData = exports._getFluidTokenData = exports.getAllFluidMarketDataChunked = exports._getAllFluidMarketDataChunked = exports.getFluidPositionWithMarket = exports._getFluidPositionWithMarket = exports.getFluidPosition = exports._getFluidPosition = exports.getFluidVaultIdsForUser = exports._getFluidVaultIdsForUser = exports.getFluidMarketData = exports._getFluidMarketData = exports.EMPTY_FLUID_DATA = exports.EMPTY_USED_ASSET = exports.attachFluidMerklIncentives = exports.getFluidMerklCampaigns = exports.buildFluidMerklRewardMap = exports.FluidMerklOpportunityType = void 0;
16
16
  const decimal_js_1 = __importDefault(require("decimal.js"));
17
17
  const tokens_1 = require("@defisaver/tokens");
18
18
  const common_1 = require("../types/common");
@@ -27,7 +27,7 @@ const staking_1 = require("../staking");
27
27
  const viem_1 = require("../services/viem");
28
28
  const merkl_1 = require("./merkl");
29
29
  var merkl_2 = require("./merkl");
30
- Object.defineProperty(exports, "FLUID_MERKL_OPPORTUNITY_TYPES", { enumerable: true, get: function () { return merkl_2.FLUID_MERKL_OPPORTUNITY_TYPES; } });
30
+ Object.defineProperty(exports, "FluidMerklOpportunityType", { enumerable: true, get: function () { return merkl_2.FluidMerklOpportunityType; } });
31
31
  Object.defineProperty(exports, "buildFluidMerklRewardMap", { enumerable: true, get: function () { return merkl_2.buildFluidMerklRewardMap; } });
32
32
  Object.defineProperty(exports, "getFluidMerklCampaigns", { enumerable: true, get: function () { return merkl_2.getFluidMerklCampaigns; } });
33
33
  Object.defineProperty(exports, "attachFluidMerklIncentives", { enumerable: true, get: function () { return merkl_2.attachFluidMerklIncentives; } });
@@ -13,12 +13,14 @@ import { FluidMarketData, FluidMerklRewardMap, MerklOpportunity, NetworkNumber }
13
13
  * ids apply unconditionally, mirroring Aave V3. Whitelist-gated campaigns only accrue to
14
14
  * whitelisted addresses, so they are skipped rather than advertised to every user.
15
15
  */
16
- export declare const FLUID_MERKL_OPPORTUNITY_TYPES: readonly ["FLUIDVAULT_COLLATERAL", "FLUIDVAULT_BORROW"];
16
+ export declare enum FluidMerklOpportunityType {
17
+ Collateral = "FLUIDVAULT_COLLATERAL",
18
+ Borrow = "FLUIDVAULT_BORROW"
19
+ }
17
20
  export declare const buildFluidMerklRewardMap: (opportunities: MerklOpportunity[], chainId: NetworkNumber) => FluidMerklRewardMap;
18
21
  /**
19
22
  * 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.
23
+ * on any failure it returns an empty map, so Merkl being down can't break market data.
22
24
  */
23
25
  export declare const getFluidMerklCampaigns: (chainId: NetworkNumber) => Promise<FluidMerklRewardMap>;
24
26
  /**
@@ -9,9 +9,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.attachFluidMerklIncentives = exports.getFluidMerklCampaigns = exports.buildFluidMerklRewardMap = exports.FLUID_MERKL_OPPORTUNITY_TYPES = void 0;
12
+ exports.attachFluidMerklIncentives = exports.getFluidMerklCampaigns = exports.buildFluidMerklRewardMap = exports.FluidMerklOpportunityType = void 0;
13
13
  const moneymarket_1 = require("../moneymarket");
14
- const utils_1 = require("../services/utils");
14
+ const merkl_1 = require("../services/merkl");
15
15
  const types_1 = require("../types");
16
16
  /**
17
17
  * Merkl tags Fluid vault-scoped reward campaigns via the opportunity `type` field:
@@ -27,7 +27,11 @@ const types_1 = require("../types");
27
27
  * ids apply unconditionally, mirroring Aave V3. Whitelist-gated campaigns only accrue to
28
28
  * whitelisted addresses, so they are skipped rather than advertised to every user.
29
29
  */
30
- exports.FLUID_MERKL_OPPORTUNITY_TYPES = ['FLUIDVAULT_COLLATERAL', 'FLUIDVAULT_BORROW'];
30
+ var FluidMerklOpportunityType;
31
+ (function (FluidMerklOpportunityType) {
32
+ FluidMerklOpportunityType["Collateral"] = "FLUIDVAULT_COLLATERAL";
33
+ FluidMerklOpportunityType["Borrow"] = "FLUIDVAULT_BORROW";
34
+ })(FluidMerklOpportunityType || (exports.FluidMerklOpportunityType = FluidMerklOpportunityType = {}));
31
35
  // '0x' + 20-byte vault address; conditional campaigns append a marker after it
32
36
  const VAULT_ADDRESS_LENGTH = 42;
33
37
  const buildMerklIncentive = (opportunity) => {
@@ -46,7 +50,7 @@ const buildFluidMerklRewardMap = (opportunities, chainId) => {
46
50
  opportunities
47
51
  .filter((o) => o.chainId === chainId)
48
52
  .filter((o) => o.status === types_1.OpportunityStatus.LIVE)
49
- .filter((o) => exports.FLUID_MERKL_OPPORTUNITY_TYPES.includes(o.type))
53
+ .filter((o) => Object.values(FluidMerklOpportunityType).includes(o.type))
50
54
  .filter((o) => { var _a; return !((_a = o.identifier) === null || _a === void 0 ? void 0 : _a.includes('WHITELIST')); })
51
55
  .forEach((o) => {
52
56
  var _a;
@@ -55,25 +59,23 @@ const buildFluidMerklRewardMap = (opportunities, chainId) => {
55
59
  return;
56
60
  if (!result[vaultAddress])
57
61
  result[vaultAddress] = { supply: [], borrow: [] };
58
- result[vaultAddress][o.type === 'FLUIDVAULT_BORROW' ? 'borrow' : 'supply'].push(buildMerklIncentive(o));
62
+ result[vaultAddress][o.type === FluidMerklOpportunityType.Borrow ? 'borrow' : 'supply'].push(buildMerklIncentive(o));
59
63
  });
60
64
  return result;
61
65
  };
62
66
  exports.buildFluidMerklRewardMap = buildFluidMerklRewardMap;
63
67
  /**
64
68
  * Fetches live vault-scoped Fluid campaigns for the chain, keyed by vault address. Never throws —
65
- * on any failure it returns an empty map, so Merkl being down can't break market data. The query
66
- * is narrowed by `type=` and capped at `items=100` because Merkl paginates at 20 items by default.
69
+ * on any failure it returns an empty map, so Merkl being down can't break market data.
67
70
  */
68
71
  const getFluidMerklCampaigns = (chainId) => __awaiter(void 0, void 0, void 0, function* () {
69
72
  try {
70
- const res = yield fetch(`https://fe.defisaver.com/api/merkl/opportunities?mainProtocolId=fluid&type=${exports.FLUID_MERKL_OPPORTUNITY_TYPES.join(',')}&status=LIVE&items=100`, {
71
- signal: AbortSignal.timeout(utils_1.LONGER_TIMEOUT),
73
+ const opportunities = yield (0, merkl_1.fetchAllMerklOpportunities)({
74
+ mainProtocolId: 'fluid',
75
+ type: Object.values(FluidMerklOpportunityType).join(','),
76
+ status: types_1.OpportunityStatus.LIVE,
72
77
  });
73
- if (!res.ok)
74
- throw new Error('Failed to fetch Fluid Merkl campaigns');
75
- const opportunities = yield res.json();
76
- return (0, exports.buildFluidMerklRewardMap)(Array.isArray(opportunities) ? opportunities : [], chainId);
78
+ return (0, exports.buildFluidMerklRewardMap)(opportunities, chainId);
77
79
  }
78
80
  catch (e) {
79
81
  console.error('Failed to fetch Fluid Merkl campaigns', e);
@@ -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>;
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.getMorphoBlueAccountBalances = exports._getMorphoBlueAccountBalances = exports.getMorphoEarnDataWithMarketInfo = exports.getMorphoBluePositionDataWithMarketInfo = exports.addMorphoBlueRewardsToMarketInfo = exports.addMorphoBlueMerklRewardsToMarketInfo = exports.getMorphoBlueMerklOpportunities = exports.addMorphoBlueMerklOpportunitiesToMarketInfo = exports.MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES = void 0;
15
+ exports.getMorphoBlueAccountBalances = exports._getMorphoBlueAccountBalances = exports.getMorphoEarnDataWithMarketInfo = exports.getMorphoBluePositionDataWithMarketInfo = exports.addMorphoBlueRewardsToMarketInfo = exports.addMorphoBlueMerklRewardsToMarketInfo = exports.getMorphoBlueMerklOpportunities = exports.addMorphoBlueMerklOpportunitiesToMarketInfo = exports.MorphoBlueMerklOpportunityType = void 0;
16
16
  exports._getMorphoBlueMarketData = _getMorphoBlueMarketData;
17
17
  exports._getMorphoBluePortfolioMarketData = _getMorphoBluePortfolioMarketData;
18
18
  exports.getMorphoBlueMarketData = getMorphoBlueMarketData;
@@ -33,7 +33,7 @@ const priceService_1 = require("../services/priceService");
33
33
  const viem_1 = require("../services/viem");
34
34
  const merkl_1 = require("./merkl");
35
35
  var merkl_2 = require("./merkl");
36
- Object.defineProperty(exports, "MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES", { enumerable: true, get: function () { return merkl_2.MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES; } });
36
+ Object.defineProperty(exports, "MorphoBlueMerklOpportunityType", { enumerable: true, get: function () { return merkl_2.MorphoBlueMerklOpportunityType; } });
37
37
  Object.defineProperty(exports, "addMorphoBlueMerklOpportunitiesToMarketInfo", { enumerable: true, get: function () { return merkl_2.addMorphoBlueMerklOpportunitiesToMarketInfo; } });
38
38
  Object.defineProperty(exports, "getMorphoBlueMerklOpportunities", { enumerable: true, get: function () { return merkl_2.getMorphoBlueMerklOpportunities; } });
39
39
  Object.defineProperty(exports, "addMorphoBlueMerklRewardsToMarketInfo", { enumerable: true, get: function () { return merkl_2.addMorphoBlueMerklRewardsToMarketInfo; } });
@@ -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
  /**
@@ -9,9 +9,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.addMorphoBlueMerklRewardsToMarketInfo = exports.getMorphoBlueMerklOpportunities = exports.addMorphoBlueMerklOpportunitiesToMarketInfo = exports.MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES = void 0;
12
+ exports.addMorphoBlueMerklRewardsToMarketInfo = exports.getMorphoBlueMerklOpportunities = exports.addMorphoBlueMerklOpportunitiesToMarketInfo = exports.MorphoBlueMerklOpportunityType = void 0;
13
13
  const moneymarket_1 = require("../moneymarket");
14
- const utils_1 = require("../services/utils");
14
+ const merkl_1 = require("../services/merkl");
15
15
  const types_1 = require("../types");
16
16
  /**
17
17
  * Merkl tags Morpho Blue market-level reward campaigns via the opportunity `type` field:
@@ -26,7 +26,12 @@ const types_1 = require("../types");
26
26
  * identifier (e.g. `0x54cf…c46WHITELIST_CAMPAIGN`); their rewards only accrue to whitelisted
27
27
  * addresses, so they are skipped rather than advertised to every user.
28
28
  */
29
- exports.MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES = ['MORPHOSUPPLY', 'MORPHOCOLLATERAL', 'MORPHOBORROW'];
29
+ var MorphoBlueMerklOpportunityType;
30
+ (function (MorphoBlueMerklOpportunityType) {
31
+ MorphoBlueMerklOpportunityType["Supply"] = "MORPHOSUPPLY";
32
+ MorphoBlueMerklOpportunityType["Collateral"] = "MORPHOCOLLATERAL";
33
+ MorphoBlueMerklOpportunityType["Borrow"] = "MORPHOBORROW";
34
+ })(MorphoBlueMerklOpportunityType || (exports.MorphoBlueMerklOpportunityType = MorphoBlueMerklOpportunityType = {}));
30
35
  const MERKL_DESCRIPTION_MARKER = 'through Merkl';
31
36
  // '0x' + first 20 bytes of the market id
32
37
  const MARKET_ID_PREFIX_LENGTH = 42;
@@ -57,7 +62,7 @@ const addMorphoBlueMerklOpportunitiesToMarketInfo = (marketInfo, selectedMarket,
57
62
  var _a;
58
63
  return o.chainId === chainId
59
64
  && o.status === types_1.OpportunityStatus.LIVE
60
- && exports.MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES.includes(o.type)
65
+ && Object.values(MorphoBlueMerklOpportunityType).includes(o.type)
61
66
  // a suffixed identifier marks a whitelist-gated campaign — skip those
62
67
  && ((_a = o.identifier) === null || _a === void 0 ? void 0 : _a.length) === MARKET_ID_PREFIX_LENGTH
63
68
  && o.identifier.toLowerCase() === marketIdPrefix;
@@ -65,27 +70,23 @@ const addMorphoBlueMerklOpportunitiesToMarketInfo = (marketInfo, selectedMarket,
65
70
  if (!relevant.length)
66
71
  return marketInfo;
67
72
  const incentivesByType = (type) => relevant.filter((o) => o.type === type).map(buildMerklIncentive);
68
- const collateralSupply = incentivesByType('MORPHOCOLLATERAL');
69
- const loanSupply = incentivesByType('MORPHOSUPPLY');
70
- const loanBorrow = incentivesByType('MORPHOBORROW');
73
+ const collateralSupply = incentivesByType(MorphoBlueMerklOpportunityType.Collateral);
74
+ const loanSupply = incentivesByType(MorphoBlueMerklOpportunityType.Supply);
75
+ const loanBorrow = incentivesByType(MorphoBlueMerklOpportunityType.Borrow);
71
76
  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] }) }) });
72
77
  };
73
78
  exports.addMorphoBlueMerklOpportunitiesToMarketInfo = addMorphoBlueMerklOpportunitiesToMarketInfo;
74
79
  /**
75
80
  * Fetches live market-scoped Morpho Blue campaigns for all chains. Never throws — on any failure
76
- * it returns an empty list, so Merkl being down can't break market data. The query is narrowed by
77
- * `type=` and capped at `items=100` because Merkl paginates at 20 items by default — an
78
- * unfiltered protocol query would silently drop campaigns once Morpho has enough opportunities.
81
+ * it returns an empty list, so Merkl being down can't break market data.
79
82
  */
80
83
  const getMorphoBlueMerklOpportunities = () => __awaiter(void 0, void 0, void 0, function* () {
81
84
  try {
82
- const res = yield fetch(`https://fe.defisaver.com/api/merkl/opportunities?mainProtocolId=morpho&type=${exports.MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES.join(',')}&status=LIVE&items=100`, {
83
- signal: AbortSignal.timeout(utils_1.LONGER_TIMEOUT),
85
+ return yield (0, merkl_1.fetchAllMerklOpportunities)({
86
+ mainProtocolId: 'morpho',
87
+ type: Object.values(MorphoBlueMerklOpportunityType).join(','),
88
+ status: types_1.OpportunityStatus.LIVE,
84
89
  });
85
- if (!res.ok)
86
- throw new Error('Failed to fetch Morpho Blue Merkl campaigns');
87
- const data = yield res.json();
88
- return Array.isArray(data) ? data : [];
89
90
  }
90
91
  catch (e) {
91
92
  console.error('Failed to fetch Morpho Blue Merkl campaigns', e);
@@ -0,0 +1,6 @@
1
+ import { MerklOpportunity } from '../types';
2
+ /**
3
+ * Fetches every page matching the query. Merkl returns 20 opportunities by default, so omitting
4
+ * `items` and advancing `page` avoids imposing a client-side maximum on the result set.
5
+ */
6
+ export declare const fetchAllMerklOpportunities: (query: Record<string, string>) => Promise<MerklOpportunity[]>;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.fetchAllMerklOpportunities = void 0;
13
+ const utils_1 = require("./utils");
14
+ const MERKL_OPPORTUNITIES_URL = 'https://fe.defisaver.com/api/merkl/opportunities';
15
+ /**
16
+ * Fetches every page matching the query. Merkl returns 20 opportunities by default, so omitting
17
+ * `items` and advancing `page` avoids imposing a client-side maximum on the result set.
18
+ */
19
+ const fetchAllMerklOpportunities = (query) => __awaiter(void 0, void 0, void 0, function* () {
20
+ const opportunities = [];
21
+ let page = 0;
22
+ let pageOpportunities;
23
+ do {
24
+ const searchParams = new URLSearchParams(Object.assign(Object.assign({}, query), { page: page.toString() }));
25
+ const res = yield fetch(`${MERKL_OPPORTUNITIES_URL}?${searchParams}`, {
26
+ signal: AbortSignal.timeout(utils_1.LONGER_TIMEOUT),
27
+ });
28
+ if (!res.ok)
29
+ throw new Error(`Failed to fetch Merkl opportunities page ${page}`);
30
+ const data = yield res.json(); // eslint-disable-line no-await-in-loop
31
+ if (!Array.isArray(data))
32
+ throw new Error(`Invalid Merkl opportunities response on page ${page}`);
33
+ pageOpportunities = data;
34
+ opportunities.push(...pageOpportunities);
35
+ page += 1;
36
+ } while (pageOpportunities.length > 0);
37
+ return opportunities;
38
+ });
39
+ exports.fetchAllMerklOpportunities = fetchAllMerklOpportunities;
@@ -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) {
@@ -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;
@@ -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,
@@ -13,12 +13,14 @@ import { FluidMarketData, FluidMerklRewardMap, MerklOpportunity, NetworkNumber }
13
13
  * ids apply unconditionally, mirroring Aave V3. Whitelist-gated campaigns only accrue to
14
14
  * whitelisted addresses, so they are skipped rather than advertised to every user.
15
15
  */
16
- export declare const FLUID_MERKL_OPPORTUNITY_TYPES: readonly ["FLUIDVAULT_COLLATERAL", "FLUIDVAULT_BORROW"];
16
+ export declare enum FluidMerklOpportunityType {
17
+ Collateral = "FLUIDVAULT_COLLATERAL",
18
+ Borrow = "FLUIDVAULT_BORROW"
19
+ }
17
20
  export declare const buildFluidMerklRewardMap: (opportunities: MerklOpportunity[], chainId: NetworkNumber) => FluidMerklRewardMap;
18
21
  /**
19
22
  * 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.
23
+ * on any failure it returns an empty map, so Merkl being down can't break market data.
22
24
  */
23
25
  export declare const getFluidMerklCampaigns: (chainId: NetworkNumber) => Promise<FluidMerklRewardMap>;
24
26
  /**
@@ -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 Fluid vault-scoped reward campaigns via the opportunity `type` field:
@@ -24,7 +24,11 @@ import { IncentiveKind, OpportunityStatus, } from '../types';
24
24
  * ids apply unconditionally, mirroring Aave V3. Whitelist-gated campaigns only accrue to
25
25
  * whitelisted addresses, so they are skipped rather than advertised to every user.
26
26
  */
27
- export const FLUID_MERKL_OPPORTUNITY_TYPES = ['FLUIDVAULT_COLLATERAL', 'FLUIDVAULT_BORROW'];
27
+ export var FluidMerklOpportunityType;
28
+ (function (FluidMerklOpportunityType) {
29
+ FluidMerklOpportunityType["Collateral"] = "FLUIDVAULT_COLLATERAL";
30
+ FluidMerklOpportunityType["Borrow"] = "FLUIDVAULT_BORROW";
31
+ })(FluidMerklOpportunityType || (FluidMerklOpportunityType = {}));
28
32
  // '0x' + 20-byte vault address; conditional campaigns append a marker after it
29
33
  const VAULT_ADDRESS_LENGTH = 42;
30
34
  const buildMerklIncentive = (opportunity) => {
@@ -43,7 +47,7 @@ export const buildFluidMerklRewardMap = (opportunities, chainId) => {
43
47
  opportunities
44
48
  .filter((o) => o.chainId === chainId)
45
49
  .filter((o) => o.status === OpportunityStatus.LIVE)
46
- .filter((o) => FLUID_MERKL_OPPORTUNITY_TYPES.includes(o.type))
50
+ .filter((o) => Object.values(FluidMerklOpportunityType).includes(o.type))
47
51
  .filter((o) => { var _a; return !((_a = o.identifier) === null || _a === void 0 ? void 0 : _a.includes('WHITELIST')); })
48
52
  .forEach((o) => {
49
53
  var _a;
@@ -52,24 +56,22 @@ export const buildFluidMerklRewardMap = (opportunities, chainId) => {
52
56
  return;
53
57
  if (!result[vaultAddress])
54
58
  result[vaultAddress] = { supply: [], borrow: [] };
55
- result[vaultAddress][o.type === 'FLUIDVAULT_BORROW' ? 'borrow' : 'supply'].push(buildMerklIncentive(o));
59
+ result[vaultAddress][o.type === FluidMerklOpportunityType.Borrow ? 'borrow' : 'supply'].push(buildMerklIncentive(o));
56
60
  });
57
61
  return result;
58
62
  };
59
63
  /**
60
64
  * 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.
65
+ * on any failure it returns an empty map, so Merkl being down can't break market data.
63
66
  */
64
67
  export const getFluidMerklCampaigns = (chainId) => __awaiter(void 0, void 0, void 0, function* () {
65
68
  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),
69
+ const opportunities = yield fetchAllMerklOpportunities({
70
+ mainProtocolId: 'fluid',
71
+ type: Object.values(FluidMerklOpportunityType).join(','),
72
+ status: OpportunityStatus.LIVE,
68
73
  });
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);
74
+ return buildFluidMerklRewardMap(opportunities, chainId);
73
75
  }
74
76
  catch (e) {
75
77
  console.error('Failed to fetch Fluid Merkl campaigns', e);
@@ -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);
@@ -0,0 +1,6 @@
1
+ import { MerklOpportunity } from '../types';
2
+ /**
3
+ * Fetches every page matching the query. Merkl returns 20 opportunities by default, so omitting
4
+ * `items` and advancing `page` avoids imposing a client-side maximum on the result set.
5
+ */
6
+ export declare const fetchAllMerklOpportunities: (query: Record<string, string>) => Promise<MerklOpportunity[]>;
@@ -0,0 +1,35 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { LONGER_TIMEOUT } from './utils';
11
+ const MERKL_OPPORTUNITIES_URL = 'https://fe.defisaver.com/api/merkl/opportunities';
12
+ /**
13
+ * Fetches every page matching the query. Merkl returns 20 opportunities by default, so omitting
14
+ * `items` and advancing `page` avoids imposing a client-side maximum on the result set.
15
+ */
16
+ export const fetchAllMerklOpportunities = (query) => __awaiter(void 0, void 0, void 0, function* () {
17
+ const opportunities = [];
18
+ let page = 0;
19
+ let pageOpportunities;
20
+ do {
21
+ const searchParams = new URLSearchParams(Object.assign(Object.assign({}, query), { page: page.toString() }));
22
+ const res = yield fetch(`${MERKL_OPPORTUNITIES_URL}?${searchParams}`, {
23
+ signal: AbortSignal.timeout(LONGER_TIMEOUT),
24
+ });
25
+ if (!res.ok)
26
+ throw new Error(`Failed to fetch Merkl opportunities page ${page}`);
27
+ const data = yield res.json(); // eslint-disable-line no-await-in-loop
28
+ if (!Array.isArray(data))
29
+ throw new Error(`Invalid Merkl opportunities response on page ${page}`);
30
+ pageOpportunities = data;
31
+ opportunities.push(...pageOpportunities);
32
+ page += 1;
33
+ } while (pageOpportunities.length > 0);
34
+ return opportunities;
35
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defisaver/positions-sdk",
3
- "version": "2.1.142-dev",
3
+ "version": "2.1.142",
4
4
  "description": "",
5
5
  "main": "./cjs/index.js",
6
6
  "module": "./esm/index.js",
@@ -1,5 +1,6 @@
1
1
  import { aprToApy } from '../moneymarket';
2
- import { LONGER_TIMEOUT, wethToEth } from '../services/utils';
2
+ import { fetchAllMerklOpportunities } from '../services/merkl';
3
+ import { wethToEth } from '../services/utils';
3
4
  import {
4
5
  MerkleRewardMap, MerklOpportunity, OpportunityAction, OpportunityStatus,
5
6
  EthAddress, NetworkNumber,
@@ -31,13 +32,10 @@ export const formatAaveAsset = (_symbol: string) => {
31
32
 
32
33
  export const getMerkleCampaigns = async (chainId: NetworkNumber): Promise<MerkleRewardMap> => {
33
34
  try {
34
- // status/items narrow the query because Merkl paginates at 20 items by default — an
35
- // unfiltered query silently drops live campaigns once the protocol has enough opportunities
36
- const res = await fetch('https://fe.defisaver.com/api/merkl/opportunities?mainProtocolId=aave&status=LIVE&items=100', {
37
- signal: AbortSignal.timeout(LONGER_TIMEOUT),
35
+ const opportunities = await fetchAllMerklOpportunities({
36
+ mainProtocolId: 'aave',
37
+ status: OpportunityStatus.LIVE,
38
38
  });
39
- if (!res.ok) throw new Error('Failed to fetch Merkle campaigns');
40
- const opportunities = await res.json() as MerklOpportunity[];
41
39
  const relevantOpportunities = opportunities
42
40
  .filter((o: MerklOpportunity) => o.chainId === chainId)
43
41
  .filter((o: MerklOpportunity) => o.status === OpportunityStatus.LIVE);
@@ -74,4 +72,3 @@ export const getMerkleCampaigns = async (chainId: NetworkNumber): Promise<Merkle
74
72
  return {};
75
73
  }
76
74
  };
77
-
@@ -1,5 +1,5 @@
1
1
  import { aprToApy } from '../moneymarket';
2
- import { LONGER_TIMEOUT } from '../services/utils';
2
+ import { fetchAllMerklOpportunities } from '../services/merkl';
3
3
  import {
4
4
  AaveV4MerklRewardMap,
5
5
  AaveV4ReserveAssetData,
@@ -67,11 +67,11 @@ export const buildAaveV4MerklRewardMap = (opportunities: MerklOpportunity[], cha
67
67
 
68
68
  export const getAaveV4MerkleCampaigns = async (chainId: NetworkNumber): Promise<AaveV4MerklRewardMap> => {
69
69
  try {
70
- const res = await 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', {
71
- signal: AbortSignal.timeout(LONGER_TIMEOUT),
70
+ const opportunities = await fetchAllMerklOpportunities({
71
+ mainProtocolId: 'aave',
72
+ type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,AAVE_V4_SPOKE_SUPPLY,AAVE_V4_SPOKE_BORROW',
73
+ status: OpportunityStatus.LIVE,
72
74
  });
73
- if (!res.ok) throw new Error('Failed to fetch Aave V4 Merkle campaigns');
74
- const opportunities = await res.json() as MerklOpportunity[];
75
75
  return buildAaveV4MerklRewardMap(opportunities, chainId);
76
76
  } catch (e) {
77
77
  console.error('Failed to fetch Aave V4 Merkle campaigns', e);
@@ -1,6 +1,7 @@
1
1
  import { getAssetInfoByAddress } from '@defisaver/tokens';
2
2
  import { aprToApy } from '../moneymarket';
3
- import { compareAddresses, LONGER_TIMEOUT, wethToEth } from '../services/utils';
3
+ import { fetchAllMerklOpportunities } from '../services/merkl';
4
+ import { compareAddresses, wethToEth } from '../services/utils';
4
5
  import {
5
6
  CompoundV3AssetData,
6
7
  IncentiveData,
@@ -22,12 +23,10 @@ import {
22
23
  */
23
24
  export const getCompoundV3MerklOpportunities = async (): Promise<MerklOpportunity[]> => {
24
25
  try {
25
- const res = await fetch('https://fe.defisaver.com/api/merkl/opportunities?mainProtocolId=compound-v3&status=LIVE&items=100', {
26
- signal: AbortSignal.timeout(LONGER_TIMEOUT),
26
+ return await fetchAllMerklOpportunities({
27
+ mainProtocolId: 'compound-v3',
28
+ status: OpportunityStatus.LIVE,
27
29
  });
28
- if (!res.ok) throw new Error('Failed to fetch Compound V3 Merkl campaigns');
29
- const data = await res.json();
30
- return Array.isArray(data) ? data as MerklOpportunity[] : [];
31
30
  } catch (e) {
32
31
  console.error('Failed to fetch Compound V3 Merkl campaigns', e);
33
32
  return [];
@@ -69,7 +69,7 @@ import { getViemProvider } from '../services/viem';
69
69
  import { attachFluidMerklIncentives, getFluidMerklCampaigns } from './merkl';
70
70
 
71
71
  export {
72
- FLUID_MERKL_OPPORTUNITY_TYPES,
72
+ FluidMerklOpportunityType,
73
73
  buildFluidMerklRewardMap,
74
74
  getFluidMerklCampaigns,
75
75
  attachFluidMerklIncentives,
@@ -1,5 +1,5 @@
1
1
  import { aprToApy } from '../moneymarket';
2
- import { LONGER_TIMEOUT } from '../services/utils';
2
+ import { fetchAllMerklOpportunities } from '../services/merkl';
3
3
  import {
4
4
  FluidMarketData,
5
5
  FluidMerklRewardMap,
@@ -25,7 +25,10 @@ import {
25
25
  * ids apply unconditionally, mirroring Aave V3. Whitelist-gated campaigns only accrue to
26
26
  * whitelisted addresses, so they are skipped rather than advertised to every user.
27
27
  */
28
- export const FLUID_MERKL_OPPORTUNITY_TYPES = ['FLUIDVAULT_COLLATERAL', 'FLUIDVAULT_BORROW'] as const;
28
+ export enum FluidMerklOpportunityType {
29
+ Collateral = 'FLUIDVAULT_COLLATERAL',
30
+ Borrow = 'FLUIDVAULT_BORROW',
31
+ }
29
32
 
30
33
  // '0x' + 20-byte vault address; conditional campaigns append a marker after it
31
34
  const VAULT_ADDRESS_LENGTH = 42;
@@ -47,13 +50,13 @@ export const buildFluidMerklRewardMap = (opportunities: MerklOpportunity[], chai
47
50
  opportunities
48
51
  .filter((o) => o.chainId === chainId)
49
52
  .filter((o) => o.status === OpportunityStatus.LIVE)
50
- .filter((o) => (FLUID_MERKL_OPPORTUNITY_TYPES as readonly string[]).includes(o.type))
53
+ .filter((o) => Object.values(FluidMerklOpportunityType).includes(o.type as FluidMerklOpportunityType))
51
54
  .filter((o) => !o.identifier?.includes('WHITELIST'))
52
55
  .forEach((o) => {
53
56
  const vaultAddress = o.identifier?.slice(0, VAULT_ADDRESS_LENGTH).toLowerCase();
54
57
  if (!vaultAddress || vaultAddress.length !== VAULT_ADDRESS_LENGTH) return;
55
58
  if (!result[vaultAddress]) result[vaultAddress] = { supply: [], borrow: [] };
56
- result[vaultAddress][o.type === 'FLUIDVAULT_BORROW' ? 'borrow' : 'supply'].push(buildMerklIncentive(o));
59
+ result[vaultAddress][o.type === FluidMerklOpportunityType.Borrow ? 'borrow' : 'supply'].push(buildMerklIncentive(o));
57
60
  });
58
61
 
59
62
  return result;
@@ -61,17 +64,16 @@ export const buildFluidMerklRewardMap = (opportunities: MerklOpportunity[], chai
61
64
 
62
65
  /**
63
66
  * Fetches live vault-scoped Fluid campaigns for the chain, keyed by vault address. Never throws —
64
- * on any failure it returns an empty map, so Merkl being down can't break market data. The query
65
- * 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.
66
68
  */
67
69
  export const getFluidMerklCampaigns = async (chainId: NetworkNumber): Promise<FluidMerklRewardMap> => {
68
70
  try {
69
- const res = await fetch(`https://fe.defisaver.com/api/merkl/opportunities?mainProtocolId=fluid&type=${FLUID_MERKL_OPPORTUNITY_TYPES.join(',')}&status=LIVE&items=100`, {
70
- signal: AbortSignal.timeout(LONGER_TIMEOUT),
71
+ const opportunities = await fetchAllMerklOpportunities({
72
+ mainProtocolId: 'fluid',
73
+ type: Object.values(FluidMerklOpportunityType).join(','),
74
+ status: OpportunityStatus.LIVE,
71
75
  });
72
- if (!res.ok) throw new Error('Failed to fetch Fluid Merkl campaigns');
73
- const opportunities = await res.json();
74
- return buildFluidMerklRewardMap(Array.isArray(opportunities) ? opportunities : [], chainId);
76
+ return buildFluidMerklRewardMap(opportunities, chainId);
75
77
  } catch (e) {
76
78
  console.error('Failed to fetch Fluid Merkl campaigns', e);
77
79
  return {};
@@ -22,7 +22,7 @@ import { getViemProvider, setViemBlockNumber } from '../services/viem';
22
22
  import { addMorphoBlueMerklOpportunitiesToMarketInfo, getMorphoBlueMerklOpportunities } from './merkl';
23
23
 
24
24
  export {
25
- MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES,
25
+ MorphoBlueMerklOpportunityType,
26
26
  addMorphoBlueMerklOpportunitiesToMarketInfo,
27
27
  getMorphoBlueMerklOpportunities,
28
28
  addMorphoBlueMerklRewardsToMarketInfo,
@@ -1,5 +1,5 @@
1
1
  import { aprToApy } from '../moneymarket';
2
- import { LONGER_TIMEOUT } from '../services/utils';
2
+ import { fetchAllMerklOpportunities } from '../services/merkl';
3
3
  import {
4
4
  IncentiveData,
5
5
  IncentiveKind,
@@ -23,7 +23,11 @@ import {
23
23
  * identifier (e.g. `0x54cf…c46WHITELIST_CAMPAIGN`); their rewards only accrue to whitelisted
24
24
  * addresses, so they are skipped rather than advertised to every user.
25
25
  */
26
- export const MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES = ['MORPHOSUPPLY', 'MORPHOCOLLATERAL', 'MORPHOBORROW'] as const;
26
+ export enum MorphoBlueMerklOpportunityType {
27
+ Supply = 'MORPHOSUPPLY',
28
+ Collateral = 'MORPHOCOLLATERAL',
29
+ Borrow = 'MORPHOBORROW',
30
+ }
27
31
 
28
32
  const MERKL_DESCRIPTION_MARKER = 'through Merkl';
29
33
 
@@ -61,16 +65,16 @@ export const addMorphoBlueMerklOpportunitiesToMarketInfo = (
61
65
 
62
66
  const relevant = opportunities.filter((o) => o.chainId === chainId
63
67
  && o.status === OpportunityStatus.LIVE
64
- && (MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES as readonly string[]).includes(o.type)
68
+ && Object.values(MorphoBlueMerklOpportunityType).includes(o.type as MorphoBlueMerklOpportunityType)
65
69
  // a suffixed identifier marks a whitelist-gated campaign — skip those
66
70
  && o.identifier?.length === MARKET_ID_PREFIX_LENGTH
67
71
  && o.identifier.toLowerCase() === marketIdPrefix);
68
72
  if (!relevant.length) return marketInfo;
69
73
 
70
- const incentivesByType = (type: string) => relevant.filter((o) => o.type === type).map(buildMerklIncentive);
71
- const collateralSupply = incentivesByType('MORPHOCOLLATERAL');
72
- const loanSupply = incentivesByType('MORPHOSUPPLY');
73
- const loanBorrow = incentivesByType('MORPHOBORROW');
74
+ const incentivesByType = (type: MorphoBlueMerklOpportunityType) => relevant.filter((o) => o.type === type).map(buildMerklIncentive);
75
+ const collateralSupply = incentivesByType(MorphoBlueMerklOpportunityType.Collateral);
76
+ const loanSupply = incentivesByType(MorphoBlueMerklOpportunityType.Supply);
77
+ const loanBorrow = incentivesByType(MorphoBlueMerklOpportunityType.Borrow);
74
78
 
75
79
  return {
76
80
  ...marketInfo,
@@ -91,18 +95,15 @@ export const addMorphoBlueMerklOpportunitiesToMarketInfo = (
91
95
 
92
96
  /**
93
97
  * Fetches live market-scoped Morpho Blue campaigns for all chains. Never throws — on any failure
94
- * it returns an empty list, so Merkl being down can't break market data. The query is narrowed by
95
- * `type=` and capped at `items=100` because Merkl paginates at 20 items by default — an
96
- * unfiltered protocol query would silently drop campaigns once Morpho has enough opportunities.
98
+ * it returns an empty list, so Merkl being down can't break market data.
97
99
  */
98
100
  export const getMorphoBlueMerklOpportunities = async (): Promise<MerklOpportunity[]> => {
99
101
  try {
100
- const res = await fetch(`https://fe.defisaver.com/api/merkl/opportunities?mainProtocolId=morpho&type=${MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES.join(',')}&status=LIVE&items=100`, {
101
- signal: AbortSignal.timeout(LONGER_TIMEOUT),
102
+ return await fetchAllMerklOpportunities({
103
+ mainProtocolId: 'morpho',
104
+ type: Object.values(MorphoBlueMerklOpportunityType).join(','),
105
+ status: OpportunityStatus.LIVE,
102
106
  });
103
- if (!res.ok) throw new Error('Failed to fetch Morpho Blue Merkl campaigns');
104
- const data = await res.json();
105
- return Array.isArray(data) ? data as MerklOpportunity[] : [];
106
107
  } catch (e) {
107
108
  console.error('Failed to fetch Morpho Blue Merkl campaigns', e);
108
109
  return [];
@@ -0,0 +1,31 @@
1
+ import { MerklOpportunity } from '../types';
2
+ import { LONGER_TIMEOUT } from './utils';
3
+
4
+ const MERKL_OPPORTUNITIES_URL = 'https://fe.defisaver.com/api/merkl/opportunities';
5
+
6
+ /**
7
+ * Fetches every page matching the query. Merkl returns 20 opportunities by default, so omitting
8
+ * `items` and advancing `page` avoids imposing a client-side maximum on the result set.
9
+ */
10
+ export const fetchAllMerklOpportunities = async (query: Record<string, string>): Promise<MerklOpportunity[]> => {
11
+ const opportunities: MerklOpportunity[] = [];
12
+ let page = 0;
13
+ let pageOpportunities: MerklOpportunity[];
14
+
15
+ do {
16
+ const searchParams = new URLSearchParams({ ...query, page: page.toString() });
17
+ const res = await fetch(`${MERKL_OPPORTUNITIES_URL}?${searchParams}`, { // eslint-disable-line no-await-in-loop
18
+ signal: AbortSignal.timeout(LONGER_TIMEOUT),
19
+ });
20
+ if (!res.ok) throw new Error(`Failed to fetch Merkl opportunities page ${page}`);
21
+
22
+ const data = await res.json(); // eslint-disable-line no-await-in-loop
23
+ if (!Array.isArray(data)) throw new Error(`Invalid Merkl opportunities response on page ${page}`);
24
+
25
+ pageOpportunities = data as MerklOpportunity[];
26
+ opportunities.push(...pageOpportunities);
27
+ page += 1;
28
+ } while (pageOpportunities.length > 0);
29
+
30
+ return opportunities;
31
+ };