@defisaver/positions-sdk 2.1.127-midnight-14-dev → 2.1.127-midnight-15-dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/cjs/aaveV3/merkl.js +10 -9
  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/index.d.ts +1 -0
  6. package/cjs/compoundV3/index.js +8 -2
  7. package/cjs/compoundV3/merkl.d.ts +16 -0
  8. package/cjs/compoundV3/merkl.js +77 -0
  9. package/cjs/fluid/index.d.ts +9 -0
  10. package/cjs/fluid/index.js +36 -10
  11. package/cjs/fluid/merkl.d.ts +32 -0
  12. package/cjs/fluid/merkl.js +103 -0
  13. package/cjs/helpers/fluidHelpers/index.d.ts +3 -1
  14. package/cjs/helpers/fluidHelpers/index.js +37 -4
  15. package/cjs/helpers/liquityV2Helpers/index.d.ts +2 -1
  16. package/cjs/helpers/liquityV2Helpers/index.js +3 -2
  17. package/cjs/helpers/morphoMidnightHelpers/headroom.d.ts +48 -0
  18. package/cjs/helpers/morphoMidnightHelpers/headroom.js +52 -0
  19. package/cjs/helpers/morphoMidnightHelpers/index.d.ts +2 -0
  20. package/cjs/helpers/morphoMidnightHelpers/index.js +10 -4
  21. package/cjs/liquityV2/index.js +3 -4
  22. package/cjs/moneymarket/moneymarketCommonService.js +2 -0
  23. package/cjs/morphoBlue/index.d.ts +1 -0
  24. package/cjs/morphoBlue/index.js +16 -10
  25. package/cjs/morphoBlue/merkl.d.ts +35 -0
  26. package/cjs/morphoBlue/merkl.js +102 -0
  27. package/cjs/portfolio/index.js +5 -5
  28. package/cjs/services/merkl.d.ts +6 -0
  29. package/cjs/services/merkl.js +39 -0
  30. package/cjs/staking/eligibility.d.ts +4 -0
  31. package/cjs/staking/eligibility.js +11 -1
  32. package/cjs/types/claiming.d.ts +12 -1
  33. package/cjs/types/claiming.js +2 -0
  34. package/cjs/types/common.d.ts +6 -1
  35. package/cjs/types/common.js +6 -1
  36. package/cjs/types/fluid.d.ts +2 -0
  37. package/cjs/types/liquityV2.d.ts +3 -0
  38. package/cjs/types/merkl.d.ts +8 -0
  39. package/esm/aaveV3/merkl.js +11 -10
  40. package/esm/aaveV4/merkl.js +5 -6
  41. package/esm/claiming/aaveV3.d.ts +2 -1
  42. package/esm/claiming/aaveV3.js +12 -4
  43. package/esm/compoundV3/index.d.ts +1 -0
  44. package/esm/compoundV3/index.js +5 -1
  45. package/esm/compoundV3/merkl.d.ts +16 -0
  46. package/esm/compoundV3/merkl.js +72 -0
  47. package/esm/fluid/index.d.ts +9 -0
  48. package/esm/fluid/index.js +31 -9
  49. package/esm/fluid/merkl.d.ts +32 -0
  50. package/esm/fluid/merkl.js +95 -0
  51. package/esm/helpers/fluidHelpers/index.d.ts +3 -1
  52. package/esm/helpers/fluidHelpers/index.js +38 -6
  53. package/esm/helpers/liquityV2Helpers/index.d.ts +2 -1
  54. package/esm/helpers/liquityV2Helpers/index.js +3 -2
  55. package/esm/helpers/morphoMidnightHelpers/headroom.d.ts +48 -0
  56. package/esm/helpers/morphoMidnightHelpers/headroom.js +45 -0
  57. package/esm/helpers/morphoMidnightHelpers/index.d.ts +2 -0
  58. package/esm/helpers/morphoMidnightHelpers/index.js +7 -3
  59. package/esm/liquityV2/index.js +3 -4
  60. package/esm/moneymarket/moneymarketCommonService.js +2 -0
  61. package/esm/morphoBlue/index.d.ts +1 -0
  62. package/esm/morphoBlue/index.js +11 -9
  63. package/esm/morphoBlue/merkl.d.ts +35 -0
  64. package/esm/morphoBlue/merkl.js +96 -0
  65. package/esm/portfolio/index.js +6 -6
  66. package/esm/services/merkl.d.ts +6 -0
  67. package/esm/services/merkl.js +35 -0
  68. package/esm/staking/eligibility.d.ts +4 -0
  69. package/esm/staking/eligibility.js +9 -0
  70. package/esm/types/claiming.d.ts +12 -1
  71. package/esm/types/claiming.js +2 -0
  72. package/esm/types/common.d.ts +6 -1
  73. package/esm/types/common.js +5 -0
  74. package/esm/types/fluid.d.ts +2 -0
  75. package/esm/types/liquityV2.d.ts +3 -0
  76. package/esm/types/merkl.d.ts +8 -0
  77. package/package.json +1 -1
  78. package/src/aaveV3/merkl.ts +7 -7
  79. package/src/aaveV4/merkl.ts +5 -5
  80. package/src/claiming/aaveV3.ts +19 -7
  81. package/src/compoundV3/index.ts +6 -1
  82. package/src/compoundV3/merkl.ts +76 -0
  83. package/src/fluid/index.ts +37 -9
  84. package/src/fluid/merkl.ts +102 -0
  85. package/src/helpers/fluidHelpers/index.ts +68 -6
  86. package/src/helpers/liquityV2Helpers/index.ts +4 -1
  87. package/src/helpers/morphoMidnightHelpers/headroom.ts +82 -0
  88. package/src/helpers/morphoMidnightHelpers/index.ts +11 -3
  89. package/src/liquityV2/index.ts +4 -3
  90. package/src/moneymarket/moneymarketCommonService.ts +2 -0
  91. package/src/morphoBlue/index.ts +24 -9
  92. package/src/morphoBlue/merkl.ts +121 -0
  93. package/src/portfolio/index.ts +6 -6
  94. package/src/services/merkl.ts +31 -0
  95. package/src/staking/eligibility.ts +10 -0
  96. package/src/types/claiming.ts +13 -0
  97. package/src/types/common.ts +6 -0
  98. package/src/types/fluid.ts +3 -1
  99. package/src/types/liquityV2.ts +3 -0
  100. package/src/types/merkl.ts +6 -0
@@ -0,0 +1,95 @@
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 memoize from 'memoizee';
11
+ import { aprToApy } from '../moneymarket';
12
+ import { fetchAllMerklOpportunities } from '../services/merkl';
13
+ import { IncentiveKind, IncentiveSource, OpportunityStatus, } from '../types';
14
+ /**
15
+ * Merkl tags Fluid vault-scoped reward campaigns via the opportunity `type` field:
16
+ * - FLUIDVAULT_COLLATERAL → reward for supplying collateral to the vault
17
+ * - FLUIDVAULT_BORROW → reward for borrowing from the vault
18
+ * (ERC20LOGPROCESSOR campaigns target fToken earn positions rather than vaults, so they don't
19
+ * belong on vault market data.)
20
+ *
21
+ * A campaign identifies its vault via `identifier` — the vault address. Conditional campaigns
22
+ * append a marker to it (e.g. `0x5668…eeaBORROW_BL`, excluding holders who borrow the reward
23
+ * asset elsewhere); those are still attached, with the full identifier passed through as
24
+ * `eligibilityId` so a curated check in EligibilityMapping can gate them in net APY — unknown
25
+ * ids apply unconditionally, mirroring Aave V3. Whitelist-gated campaigns only accrue to
26
+ * whitelisted addresses, so they are skipped rather than advertised to every user.
27
+ */
28
+ export var FluidMerklOpportunityType;
29
+ (function (FluidMerklOpportunityType) {
30
+ FluidMerklOpportunityType["Collateral"] = "FLUIDVAULT_COLLATERAL";
31
+ FluidMerklOpportunityType["Borrow"] = "FLUIDVAULT_BORROW";
32
+ })(FluidMerklOpportunityType || (FluidMerklOpportunityType = {}));
33
+ // '0x' + 20-byte vault address; conditional campaigns append a marker after it
34
+ const VAULT_ADDRESS_LENGTH = 42;
35
+ const buildMerklIncentive = (opportunity) => {
36
+ var _a, _b, _c, _d, _e, _f;
37
+ const token = ((_d = (_c = (_b = (_a = opportunity.rewardsRecord) === null || _a === void 0 ? void 0 : _a.breakdowns) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.token) === null || _d === void 0 ? void 0 : _d.symbol) || ((_f = (_e = opportunity.tokens) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.symbol) || '';
38
+ return {
39
+ apy: aprToApy(opportunity.apr),
40
+ token,
41
+ incentiveKind: IncentiveKind.Reward,
42
+ source: IncentiveSource.Merkl,
43
+ description: `Eligible for ${token} rewards through Merkl.${opportunity.description ? `\n${opportunity.description}` : ''}`,
44
+ eligibilityId: opportunity.identifier,
45
+ };
46
+ };
47
+ export const buildFluidMerklRewardMap = (opportunities, chainId) => {
48
+ const result = {};
49
+ opportunities
50
+ .filter((o) => o.chainId === chainId)
51
+ .filter((o) => o.status === OpportunityStatus.LIVE)
52
+ .filter((o) => Object.values(FluidMerklOpportunityType).includes(o.type))
53
+ .filter((o) => { var _a; return !((_a = o.identifier) === null || _a === void 0 ? void 0 : _a.includes('WHITELIST')); })
54
+ .forEach((o) => {
55
+ var _a;
56
+ const vaultAddress = (_a = o.identifier) === null || _a === void 0 ? void 0 : _a.slice(0, VAULT_ADDRESS_LENGTH).toLowerCase();
57
+ if (!vaultAddress || vaultAddress.length !== VAULT_ADDRESS_LENGTH)
58
+ return;
59
+ if (!result[vaultAddress])
60
+ result[vaultAddress] = { supply: [], borrow: [] };
61
+ result[vaultAddress][o.type === FluidMerklOpportunityType.Borrow ? 'borrow' : 'supply'].push(buildMerklIncentive(o));
62
+ });
63
+ return result;
64
+ };
65
+ /**
66
+ * Fetches live vault-scoped Fluid campaigns for the chain, keyed by vault address. Never throws —
67
+ * on any failure it returns an empty map, so Merkl being down can't break market data.
68
+ */
69
+ export const getFluidMerklCampaigns = memoize((chainId) => __awaiter(void 0, void 0, void 0, function* () {
70
+ try {
71
+ const opportunities = yield fetchAllMerklOpportunities({
72
+ mainProtocolId: 'fluid',
73
+ type: Object.values(FluidMerklOpportunityType).join(','),
74
+ status: OpportunityStatus.LIVE,
75
+ });
76
+ return buildFluidMerklRewardMap(opportunities, chainId);
77
+ }
78
+ catch (e) {
79
+ console.error('Failed to fetch Fluid Merkl campaigns', e);
80
+ return {};
81
+ }
82
+ }), { promise: true, maxAge: 60 * 1000 });
83
+ /**
84
+ * Appends the vault's Merkl campaigns to its assets' incentive arrays — supply rewards on every
85
+ * suppliable asset and borrow rewards on every borrowable one. Smart collateral/debt vaults hold
86
+ * the position across both pair tokens, so the pro-rata reward APY applies to each side's value.
87
+ */
88
+ export const attachFluidMerklIncentives = (marketData, campaigns) => {
89
+ var _a;
90
+ const vaultCampaigns = campaigns[(_a = marketData.marketData.marketAddress) === null || _a === void 0 ? void 0 : _a.toLowerCase()];
91
+ if (!vaultCampaigns || (!vaultCampaigns.supply.length && !vaultCampaigns.borrow.length))
92
+ return marketData;
93
+ const assetsData = Object.fromEntries(Object.entries(marketData.assetsData).map(([symbol, asset]) => [symbol, Object.assign(Object.assign({}, asset), { supplyIncentives: asset.canBeSupplied ? [...asset.supplyIncentives, ...vaultCampaigns.supply] : asset.supplyIncentives, borrowIncentives: asset.canBeBorrowed ? [...asset.borrowIncentives, ...vaultCampaigns.borrow] : asset.borrowIncentives })]));
94
+ return Object.assign(Object.assign({}, marketData), { assetsData });
95
+ };
@@ -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;
@@ -4,9 +4,10 @@ export declare const calculateNetApyLiquityV2: (usedAssets: LiquityV2UsedAssets,
4
4
  totalInterestUsd: string;
5
5
  incentiveUsd: string;
6
6
  };
7
- export declare const getLiquityV2AggregatedPositionData: ({ usedAssets, assetsData, minCollRatio, interestRate, }: {
7
+ export declare const getLiquityV2AggregatedPositionData: ({ usedAssets, assetsData, minCollRatio, interestRate, liqRatio, }: {
8
8
  usedAssets: LiquityV2UsedAssets;
9
9
  assetsData: LiquityV2AssetsData;
10
10
  minCollRatio: string;
11
11
  interestRate: string;
12
+ liqRatio?: string;
12
13
  }) => LiquityV2AggregatedTroveData;
@@ -31,11 +31,12 @@ export const calculateNetApyLiquityV2 = (usedAssets, assetsData, interestRate) =
31
31
  const netApy = new Dec(totalInterestUsd).div(balance).times(100).toString();
32
32
  return { netApy, totalInterestUsd, incentiveUsd };
33
33
  };
34
- export const getLiquityV2AggregatedPositionData = ({ usedAssets, assetsData, minCollRatio, interestRate, }) => {
34
+ export const getLiquityV2AggregatedPositionData = ({ usedAssets, assetsData, minCollRatio, interestRate, liqRatio, }) => {
35
35
  const payload = {};
36
36
  payload.suppliedUsd = getAssetsTotal(usedAssets, (usedAsset) => usedAsset, ({ suppliedUsd }) => suppliedUsd);
37
37
  payload.borrowedUsd = getAssetsTotal(usedAssets, (usedAsset) => usedAsset, ({ borrowedUsd }) => borrowedUsd);
38
38
  payload.borrowLimitUsd = new Dec(payload.suppliedUsd).div(minCollRatio).mul(100).toString();
39
+ payload.liquidationLimitUsd = new Dec(payload.suppliedUsd).div(liqRatio !== null && liqRatio !== void 0 ? liqRatio : minCollRatio).mul(100).toString();
39
40
  const leftToBorrowUsd = new Dec(payload.borrowLimitUsd).sub(payload.borrowedUsd);
40
41
  payload.leftToBorrowUsd = leftToBorrowUsd.lte('0') ? '0' : leftToBorrowUsd.toString();
41
42
  payload.ratio = (+payload.suppliedUsd && +payload.borrowedUsd) ? new Dec(payload.borrowLimitUsd).div(payload.borrowedUsd).mul(100).toString() : '0';
@@ -50,7 +51,7 @@ export const getLiquityV2AggregatedPositionData = ({ usedAssets, assetsData, min
50
51
  payload.liquidationPrice = '';
51
52
  if (leveragedType !== '') {
52
53
  const assetPrice = assetsData[leveragedAsset].price;
53
- payload.liquidationPrice = calcLeverageLiqPrice(leveragedType, assetPrice, payload.borrowedUsd, payload.borrowLimitUsd);
54
+ payload.liquidationPrice = calcLeverageLiqPrice(leveragedType, assetPrice, payload.borrowedUsd, payload.liquidationLimitUsd);
54
55
  }
55
56
  payload.exposure = getExposure(payload.borrowedUsd, payload.suppliedUsd);
56
57
  return payload;
@@ -0,0 +1,48 @@
1
+ import Dec from 'decimal.js';
2
+ /**
3
+ * Safety margin every Midnight position is held at. On Midnight the lltv is *both* the borrow cap and the
4
+ * liquidation threshold (`liquidationLimitUsd === borrowLimitUsd` in the aggregate), so a position borrowed
5
+ * to its full limit opens at a health ratio of exactly 1 — immediately liquidatable. This is what holds it
6
+ * off that line, and what absorbs swap price impact on a boost.
7
+ */
8
+ export declare const MIDNIGHT_MIN_HEALTH_RATIO = 1.025;
9
+ export interface MidnightBorrowHeadroomParams {
10
+ borrowLimitUsd: Dec.Value;
11
+ borrowedUsd: Dec.Value;
12
+ /** Principal a unit of borrow power is worth (loan-per-unit price, ≤ 1), off a book rate. */
13
+ principalPerUnit?: Dec.Value;
14
+ /** Depth the book can fill, in loan tokens, from the same quote. Omitted leaves the answer uncapped. */
15
+ availableAssets?: Dec.Value;
16
+ loanTokenPrice?: Dec.Value;
17
+ /** Loan token decimals. Omitted skips the quantisation, which only a caller ignoring `leftToBorrow` wants. */
18
+ loanTokenDecimals?: number;
19
+ isMatured?: boolean;
20
+ minHealthRatio?: Dec.Value;
21
+ }
22
+ export interface MidnightBorrowHeadroom {
23
+ /** Face-value headroom, before the order-book discount. What a *boost* solve scales itself. */
24
+ headroomUsd: string;
25
+ /** The borrowable amount in loan tokens — the Borrow action's Max, quantised exactly as it is typed in. */
26
+ leftToBorrow: string;
27
+ /** `leftToBorrow · price`. Derived FROM the token amount, never computed alongside it — see below. */
28
+ leftToBorrowUsd: string;
29
+ borrowPowerUsed: string;
30
+ }
31
+ /**
32
+ * The one answer to "how much more can this position borrow". Every surface that sizes a Midnight borrow
33
+ * derives from here — the max-borrow getter, the position overview's headroom rows, and the max-boost
34
+ * solve, which scales `headroomUsd` by its own leverage factors — so a max and the figure shown next to it
35
+ * cannot drift into two different formulas. They did: the overview used to price
36
+ * the whole headroom off the top-of-book offer (often dust) with no rate pad and no depth cap, while the
37
+ * max walked the book for its real size, which is what put the two 1–2% apart.
38
+ *
39
+ * Three corrections separate this from a plain `limit − debt`, and dropping any one overstates the answer:
40
+ * `minHealthRatio` reserves the margin, `principalPerUnit` turns face-value headroom into the principal a
41
+ * borrow actually receives (a correction that grows with the term, past 7% on a one-year market), and
42
+ * `availableAssets` caps it at what the book can currently fill.
43
+ *
44
+ * `leftToBorrowUsd` is deliberately `leftToBorrow · price` rather than the USD figure the token amount was
45
+ * derived from: the two differ by the round-down to token decimals, and a UI showing one next to an input
46
+ * holding the other would be reporting a difference the user cannot spend. Everything quantises once, here.
47
+ */
48
+ export declare const getMorphoMidnightBorrowHeadroom: ({ borrowLimitUsd, borrowedUsd, principalPerUnit, availableAssets, loanTokenPrice, loanTokenDecimals, isMatured, minHealthRatio, }: MidnightBorrowHeadroomParams) => MidnightBorrowHeadroom;
@@ -0,0 +1,45 @@
1
+ import Dec from 'decimal.js';
2
+ /**
3
+ * Safety margin every Midnight position is held at. On Midnight the lltv is *both* the borrow cap and the
4
+ * liquidation threshold (`liquidationLimitUsd === borrowLimitUsd` in the aggregate), so a position borrowed
5
+ * to its full limit opens at a health ratio of exactly 1 — immediately liquidatable. This is what holds it
6
+ * off that line, and what absorbs swap price impact on a boost.
7
+ */
8
+ export const MIDNIGHT_MIN_HEALTH_RATIO = 1.025;
9
+ /**
10
+ * The one answer to "how much more can this position borrow". Every surface that sizes a Midnight borrow
11
+ * derives from here — the max-borrow getter, the position overview's headroom rows, and the max-boost
12
+ * solve, which scales `headroomUsd` by its own leverage factors — so a max and the figure shown next to it
13
+ * cannot drift into two different formulas. They did: the overview used to price
14
+ * the whole headroom off the top-of-book offer (often dust) with no rate pad and no depth cap, while the
15
+ * max walked the book for its real size, which is what put the two 1–2% apart.
16
+ *
17
+ * Three corrections separate this from a plain `limit − debt`, and dropping any one overstates the answer:
18
+ * `minHealthRatio` reserves the margin, `principalPerUnit` turns face-value headroom into the principal a
19
+ * borrow actually receives (a correction that grows with the term, past 7% on a one-year market), and
20
+ * `availableAssets` caps it at what the book can currently fill.
21
+ *
22
+ * `leftToBorrowUsd` is deliberately `leftToBorrow · price` rather than the USD figure the token amount was
23
+ * derived from: the two differ by the round-down to token decimals, and a UI showing one next to an input
24
+ * holding the other would be reporting a difference the user cannot spend. Everything quantises once, here.
25
+ */
26
+ export const getMorphoMidnightBorrowHeadroom = ({ borrowLimitUsd, borrowedUsd, principalPerUnit = 1, availableAssets, loanTokenPrice = 0, loanTokenDecimals, isMatured = false, minHealthRatio = MIDNIGHT_MIN_HEALTH_RATIO, }) => {
27
+ const usableLimitUsd = new Dec(borrowLimitUsd).div(minHealthRatio);
28
+ const headroomUsd = isMatured ? new Dec(0) : Dec.max(0, usableLimitUsd.sub(borrowedUsd));
29
+ const price = new Dec(loanTokenPrice);
30
+ // Capped in loan tokens rather than in USD: `availableAssets` is quoted in tokens, and converting it to
31
+ // USD only to divide the result back out again would re-round the one number that has to land exactly.
32
+ const payout = price.lte(0) ? new Dec(0) : headroomUsd.mul(principalPerUnit).div(price);
33
+ const capped = availableAssets === undefined ? payout : Dec.min(payout, availableAssets);
34
+ // Round down: rounding the last decimal up would eat into the margin we just reserved.
35
+ const leftToBorrow = loanTokenDecimals === undefined ? capped : capped.toDP(loanTokenDecimals, Dec.ROUND_DOWN);
36
+ return {
37
+ headroomUsd: headroomUsd.toString(),
38
+ leftToBorrow: leftToBorrow.toString(),
39
+ leftToBorrowUsd: leftToBorrow.mul(price).toString(),
40
+ // Measured against the margined limit rather than the raw one, so it reads 100% at max borrow instead
41
+ // of 97.56%, and can exceed 100% inside the reserved band. Deliberately left uncapped by book depth:
42
+ // how much a position has borrowed is a property of the position, not of what the book can fill today.
43
+ borrowPowerUsed: usableLimitUsd.lte(0) ? '0' : new Dec(borrowedUsd).div(usableLimitUsd).mul(100).toString(),
44
+ };
45
+ };
@@ -2,6 +2,8 @@ import Dec from 'decimal.js';
2
2
  import { MMUsedAssets, NetworkNumber } from '../../types/common';
3
3
  import { MorphoMidnightAggregatedPositionData, MorphoMidnightAssetsData, MorphoMidnightBookSide, MorphoMidnightMarketData, MorphoMidnightMarketInfo, MorphoMidnightParsedBook } from '../../types';
4
4
  export { buildMidnightParsedBook, midnightApyFromPrice, midnightBookBestFirst, midnightBoundPrice, midnightPriceFromApy, midnightTimeToMaturityDays, MIDNIGHT_DEFAULT_RATE_SLIPPAGE, } from './rate';
5
+ export { getMorphoMidnightBorrowHeadroom, MIDNIGHT_MIN_HEALTH_RATIO, } from './headroom';
6
+ export type { MidnightBorrowHeadroom, MidnightBorrowHeadroomParams } from './headroom';
5
7
  export { tenorBookKeyFor, tenorBookRateToApyPercent, tenorOfferFillToApiFill, tenorOfferToApiOffer, } from './tenor';
6
8
  /**
7
9
  * Aggregate a Morpho Midnight position. Midnight markets are multi-collateral, so the borrow limit is
@@ -18,6 +18,7 @@ import { isTenorMidnightMarket } from '../../markets/morphoMidnight';
18
18
  import { buildMidnightParsedBook, midnightApyFromPrice, midnightBoundPrice, midnightTimeToMaturityDays, } from './rate';
19
19
  import { getTenorBorrowQuote, getTenorMarketBook, getTenorPaybackQuote, getTenorPaybackUnitsQuote, } from './tenor';
20
20
  export { buildMidnightParsedBook, midnightApyFromPrice, midnightBookBestFirst, midnightBoundPrice, midnightPriceFromApy, midnightTimeToMaturityDays, MIDNIGHT_DEFAULT_RATE_SLIPPAGE, } from './rate';
21
+ export { getMorphoMidnightBorrowHeadroom, MIDNIGHT_MIN_HEALTH_RATIO, } from './headroom';
21
22
  export { tenorBookKeyFor, tenorBookRateToApyPercent, tenorOfferFillToApiFill, tenorOfferToApiOffer, } from './tenor';
22
23
  /**
23
24
  * Aggregate a Morpho Midnight position. Midnight markets are multi-collateral, so the borrow limit is
@@ -40,9 +41,12 @@ export const getMorphoMidnightAggregatedPositionData = ({ usedAssets, assetsData
40
41
  // Same subtraction every other money market uses, but it does NOT mean the same thing here. Elsewhere
41
42
  // `borrowedUsd` is debt at present value, so the remainder is what a borrow would pay out. Midnight
42
43
  // records debt at its face value at maturity, so this is face-value headroom: borrowing it would add
43
- // more debt than the number says, by the market's discount. Anything surfacing this as "what you can
44
- // borrow" has to scale it by the loan-per-unit price first (`midnightPriceFromApy` off a book rate) —
45
- // a correction that grows with the term, past 7% on a one-year market.
44
+ // more debt than the number says, by the market's discount. It also reserves no safety margin, and this
45
+ // aggregate is synchronous so it has no book rate to discount by.
46
+ //
47
+ // DO NOT SURFACE THIS AS "what you can borrow" — use `getMorphoMidnightBorrowHeadroom` instead, which
48
+ // applies both corrections (the margin, and the loan-per-unit price off a book rate) and caps on the
49
+ // depth the book can fill. The discount alone grows past 7% on a one-year market.
46
50
  const leftToBorrowUsd = new Dec(payload.borrowLimitUsd).sub(payload.borrowedUsd);
47
51
  payload.leftToBorrowUsd = leftToBorrowUsd.lte('0') ? '0' : leftToBorrowUsd.toString();
48
52
  const loanTokenPrice = ((_a = assetsData[marketInfo.loanToken]) === null || _a === void 0 ? void 0 : _a.price) || '0';
@@ -309,15 +309,14 @@ export const _getLiquityV2TroveData = (provider_1, network_1, _a, ...args_1) =>
309
309
  const interestBatchManager = data.interestBatchManager;
310
310
  const lastInterestRateAdjTime = data.lastInterestRateAdjTime.toString();
311
311
  const hasInterestBatchManager = !compareAddresses(interestBatchManager, ZERO_ADDRESS);
312
- const liqRatio = hasInterestBatchManager ? new Dec(minCollRatio).add(batchCollRatio).toString() : minCollRatio;
312
+ const borrowLimitRatio = hasInterestBatchManager ? new Dec(minCollRatio).add(batchCollRatio).toString() : minCollRatio;
313
313
  const payload = Object.assign(Object.assign({ usedAssets,
314
314
  troveId,
315
315
  interestRate,
316
316
  interestBatchManager,
317
317
  debtInFront,
318
- lastInterestRateAdjTime,
319
- liqRatio, troveStatus: LIQUITY_V2_TROVE_STATUS_ENUM[parseInt(data.status.toString(), 10)] }, getLiquityV2AggregatedPositionData({
320
- usedAssets, assetsData, minCollRatio: liqRatio, interestRate,
318
+ lastInterestRateAdjTime, liqRatio: minCollRatio, borrowLimitRatio, troveStatus: LIQUITY_V2_TROVE_STATUS_ENUM[parseInt(data.status.toString(), 10)] }, getLiquityV2AggregatedPositionData({
319
+ usedAssets, assetsData, minCollRatio: borrowLimitRatio, interestRate, liqRatio: minCollRatio,
321
320
  })), { collRatio });
322
321
  return payload;
323
322
  });
@@ -23,6 +23,8 @@ export const STABLE_ASSETS = [
23
23
  'USDtb', 'eUSDe', 'PT USDe July', 'PT eUSDe Aug', 'PT sUSDe Sep', 'PT USDe Sep', 'PT sUSDe Nov', 'PT USDe Nov', 'PT sUSDe Jan', 'PT USDe Jan',
24
24
  'PT sUSDe Feb', 'PT USDe Feb', 'PT sUSDe Apr', 'PT USDe Apr', 'PT sUSDe May', 'PT USDe May', 'PT srUSDe Apr', 'frxUSD', 'PT sUSDe June',
25
25
  'PT USDe June', 'PT srUSDe Jun', 'PT srUSDe Oct', 'PT USDG May', 'PT sUSDS Nov', 'PT sUSDe Oct', 'PT USDG Sep',
26
+ 'USDG', 'mUSD', 'PYUSD', 'USDbC', 'MAI', 'sGHO', 'USD3', 'stUSDS', 'PRIME',
27
+ 'crvUSD', 'syrupUSDC', 'syrupUSDT', 'syrupUSDG', 'PT syrupUSDC Aug',
26
28
  ];
27
29
  export const isLeveragedPos = (usedAssets, dustLimit = 5) => {
28
30
  let borrowUnstable = 0;
@@ -1,6 +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 { MorphoBlueMerklOpportunityType, addMorphoBlueMerklOpportunitiesToMarketInfo, getMorphoBlueMerklOpportunities, addMorphoBlueMerklRewardsToMarketInfo, } from './merkl';
4
5
  export declare const addMorphoBlueRewardsToMarketInfo: (marketInfo: MorphoBlueMarketInfo, rewards: MorphoBlueMarketRewards) => MorphoBlueMarketInfo;
5
6
  export declare function _getMorphoBlueMarketData(provider: Client, network: NetworkNumber, selectedMarket: MorphoBlueMarketData): Promise<MorphoBlueMarketInfo>;
6
7
  export declare function _getMorphoBluePortfolioMarketData(provider: Client, network: NetworkNumber, selectedMarket: MorphoBlueMarketData): Promise<MorphoBlueMarketInfo>;
@@ -17,6 +17,8 @@ import { isMainnetNetwork, wethToEth } from '../services/utils';
17
17
  import { getBorrowRate, getMorphoBlueAggregatedPositionData, getRewardsForMarket, getSupplyRate, } from '../helpers/morphoBlueHelpers';
18
18
  import { getChainlinkAssetAddress } from '../services/priceService';
19
19
  import { getViemProvider, setViemBlockNumber } from '../services/viem';
20
+ import { addMorphoBlueMerklOpportunitiesToMarketInfo, getMorphoBlueMerklOpportunities } from './merkl';
21
+ export { MorphoBlueMerklOpportunityType, addMorphoBlueMerklOpportunitiesToMarketInfo, getMorphoBlueMerklOpportunities, addMorphoBlueMerklRewardsToMarketInfo, } from './merkl';
20
22
  const HARDCODED_USD_STABLE_PRICE = '100000000'; // $1 with 8 decimals
21
23
  const getMorphoRewardIncentives = (apy) => [{
22
24
  token: 'MORPHO',
@@ -119,15 +121,15 @@ function getMorphoBlueMarketDataInternal(provider, network, selectedMarket) {
119
121
  }
120
122
  export function _getMorphoBlueMarketData(provider, network, selectedMarket) {
121
123
  return __awaiter(this, void 0, void 0, function* () {
122
- const marketInfo = yield getMorphoBlueMarketDataInternal(provider, network, selectedMarket);
123
- try {
124
- const rewards = yield getRewardsForMarket(selectedMarket.marketId, network);
125
- return addMorphoBlueRewardsToMarketInfo(marketInfo, rewards);
126
- }
127
- catch (error) {
128
- console.error(error);
129
- return addMorphoBlueRewardsToMarketInfo(marketInfo, { supplyApy: '0', borrowApy: '0' });
130
- }
124
+ const [marketInfo, rewards, merklOpportunities] = yield Promise.all([
125
+ getMorphoBlueMarketDataInternal(provider, network, selectedMarket),
126
+ getRewardsForMarket(selectedMarket.marketId, network).catch((error) => {
127
+ console.error(error);
128
+ return { supplyApy: '0', borrowApy: '0' };
129
+ }),
130
+ getMorphoBlueMerklOpportunities(),
131
+ ]);
132
+ return addMorphoBlueMerklOpportunitiesToMarketInfo(addMorphoBlueRewardsToMarketInfo(marketInfo, rewards), selectedMarket, network, merklOpportunities);
131
133
  });
132
134
  }
133
135
  export function _getMorphoBluePortfolioMarketData(provider, network, selectedMarket) {
@@ -0,0 +1,35 @@
1
+ import { MerklOpportunity, MorphoBlueMarketData, MorphoBlueMarketInfo, NetworkNumber } from '../types';
2
+ /**
3
+ * Merkl tags Morpho Blue market-level reward campaigns via the opportunity `type` field:
4
+ * - MORPHOSUPPLY → reward for supplying the market's loan asset
5
+ * - MORPHOCOLLATERAL → reward for depositing the market's collateral asset
6
+ * - MORPHOBORROW → reward for borrowing the market's loan asset
7
+ * (MORPHOVAULT/ERC20LOGPROCESSOR are MetaMorpho vault campaigns and MORPHOSUPPLY_SINGLETOKEN is
8
+ * token-wide rather than market-scoped, so none of them belong on per-market data.)
9
+ *
10
+ * A campaign identifies its market via `identifier` — the first 20 bytes of the 32-byte Morpho
11
+ * market id, formatted as a checksummed address. Whitelist-gated campaigns append a marker to that
12
+ * identifier (e.g. `0x54cf…c46WHITELIST_CAMPAIGN`); their rewards only accrue to whitelisted
13
+ * addresses, so they are skipped rather than advertised to every user.
14
+ */
15
+ export declare enum MorphoBlueMerklOpportunityType {
16
+ Supply = "MORPHOSUPPLY",
17
+ Collateral = "MORPHOCOLLATERAL",
18
+ Borrow = "MORPHOBORROW"
19
+ }
20
+ /**
21
+ * Appends the given live Merkl campaigns for the market to its `assetsData` incentive arrays
22
+ * (collateral supply / loan supply / loan borrow). Existing Merkl incentives are replaced rather
23
+ * than duplicated, so re-applying on refreshed data is safe.
24
+ */
25
+ export declare const addMorphoBlueMerklOpportunitiesToMarketInfo: (marketInfo: MorphoBlueMarketInfo, selectedMarket: MorphoBlueMarketData, chainId: NetworkNumber, opportunities: MerklOpportunity[]) => MorphoBlueMarketInfo;
26
+ /**
27
+ * Fetches live market-scoped Morpho Blue campaigns for all chains. Never throws — on any failure
28
+ * it returns an empty list, so Merkl being down can't break market data.
29
+ */
30
+ export declare const getMorphoBlueMerklOpportunities: () => Promise<MerklOpportunity[]>;
31
+ /**
32
+ * Fetches live Merkl campaigns and appends the ones for the market to its `assetsData` incentive
33
+ * arrays. Never throws — on any failure the market info is returned unchanged.
34
+ */
35
+ export declare const addMorphoBlueMerklRewardsToMarketInfo: (marketInfo: MorphoBlueMarketInfo, selectedMarket: MorphoBlueMarketData, network: NetworkNumber) => Promise<MorphoBlueMarketInfo>;
@@ -0,0 +1,96 @@
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 { aprToApy } from '../moneymarket';
11
+ import { fetchAllMerklOpportunities } from '../services/merkl';
12
+ import { IncentiveKind, OpportunityStatus, } from '../types';
13
+ /**
14
+ * Merkl tags Morpho Blue market-level reward campaigns via the opportunity `type` field:
15
+ * - MORPHOSUPPLY → reward for supplying the market's loan asset
16
+ * - MORPHOCOLLATERAL → reward for depositing the market's collateral asset
17
+ * - MORPHOBORROW → reward for borrowing the market's loan asset
18
+ * (MORPHOVAULT/ERC20LOGPROCESSOR are MetaMorpho vault campaigns and MORPHOSUPPLY_SINGLETOKEN is
19
+ * token-wide rather than market-scoped, so none of them belong on per-market data.)
20
+ *
21
+ * A campaign identifies its market via `identifier` — the first 20 bytes of the 32-byte Morpho
22
+ * market id, formatted as a checksummed address. Whitelist-gated campaigns append a marker to that
23
+ * identifier (e.g. `0x54cf…c46WHITELIST_CAMPAIGN`); their rewards only accrue to whitelisted
24
+ * addresses, so they are skipped rather than advertised to every user.
25
+ */
26
+ export var MorphoBlueMerklOpportunityType;
27
+ (function (MorphoBlueMerklOpportunityType) {
28
+ MorphoBlueMerklOpportunityType["Supply"] = "MORPHOSUPPLY";
29
+ MorphoBlueMerklOpportunityType["Collateral"] = "MORPHOCOLLATERAL";
30
+ MorphoBlueMerklOpportunityType["Borrow"] = "MORPHOBORROW";
31
+ })(MorphoBlueMerklOpportunityType || (MorphoBlueMerklOpportunityType = {}));
32
+ const MERKL_DESCRIPTION_MARKER = 'through Merkl';
33
+ // '0x' + first 20 bytes of the market id
34
+ const MARKET_ID_PREFIX_LENGTH = 42;
35
+ const buildMerklIncentive = (opportunity) => {
36
+ var _a, _b, _c, _d, _e, _f;
37
+ const token = ((_d = (_c = (_b = (_a = opportunity.rewardsRecord) === null || _a === void 0 ? void 0 : _a.breakdowns) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.token) === null || _d === void 0 ? void 0 : _d.symbol) || ((_f = (_e = opportunity.tokens) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.symbol) || '';
38
+ return {
39
+ apy: aprToApy(opportunity.apr),
40
+ token,
41
+ incentiveKind: IncentiveKind.Reward,
42
+ description: `Eligible for ${token} rewards through Merkl.${opportunity.description ? `\n${opportunity.description}` : ''}`,
43
+ };
44
+ };
45
+ const withoutMerklIncentives = (incentives = []) => incentives
46
+ .filter(({ description }) => !(description === null || description === void 0 ? void 0 : description.includes(MERKL_DESCRIPTION_MARKER)));
47
+ /**
48
+ * Appends the given live Merkl campaigns for the market to its `assetsData` incentive arrays
49
+ * (collateral supply / loan supply / loan borrow). Existing Merkl incentives are replaced rather
50
+ * than duplicated, so re-applying on refreshed data is safe.
51
+ */
52
+ export const addMorphoBlueMerklOpportunitiesToMarketInfo = (marketInfo, selectedMarket, chainId, opportunities) => {
53
+ const marketIdPrefix = (selectedMarket.marketId || '').slice(0, MARKET_ID_PREFIX_LENGTH).toLowerCase();
54
+ const collateralAsset = marketInfo.assetsData[marketInfo.collateralToken];
55
+ const loanAsset = marketInfo.assetsData[marketInfo.loanToken];
56
+ if (marketIdPrefix.length !== MARKET_ID_PREFIX_LENGTH || !collateralAsset || !loanAsset)
57
+ return marketInfo;
58
+ const relevant = opportunities.filter((o) => {
59
+ var _a;
60
+ return o.chainId === chainId
61
+ && o.status === OpportunityStatus.LIVE
62
+ && Object.values(MorphoBlueMerklOpportunityType).includes(o.type)
63
+ // a suffixed identifier marks a whitelist-gated campaign — skip those
64
+ && ((_a = o.identifier) === null || _a === void 0 ? void 0 : _a.length) === MARKET_ID_PREFIX_LENGTH
65
+ && o.identifier.toLowerCase() === marketIdPrefix;
66
+ });
67
+ if (!relevant.length)
68
+ return marketInfo;
69
+ const incentivesByType = (type) => relevant.filter((o) => o.type === type).map(buildMerklIncentive);
70
+ const collateralSupply = incentivesByType(MorphoBlueMerklOpportunityType.Collateral);
71
+ const loanSupply = incentivesByType(MorphoBlueMerklOpportunityType.Supply);
72
+ const loanBorrow = incentivesByType(MorphoBlueMerklOpportunityType.Borrow);
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] }) }) });
74
+ };
75
+ /**
76
+ * Fetches live market-scoped Morpho Blue campaigns for all chains. Never throws — on any failure
77
+ * it returns an empty list, so Merkl being down can't break market data.
78
+ */
79
+ export const getMorphoBlueMerklOpportunities = () => __awaiter(void 0, void 0, void 0, function* () {
80
+ try {
81
+ return yield fetchAllMerklOpportunities({
82
+ mainProtocolId: 'morpho',
83
+ type: Object.values(MorphoBlueMerklOpportunityType).join(','),
84
+ status: OpportunityStatus.LIVE,
85
+ });
86
+ }
87
+ catch (e) {
88
+ console.error('Failed to fetch Morpho Blue Merkl campaigns', e);
89
+ return [];
90
+ }
91
+ });
92
+ /**
93
+ * Fetches live Merkl campaigns and appends the ones for the market to its `assetsData` incentive
94
+ * arrays. Never throws — on any failure the market info is returned unchanged.
95
+ */
96
+ export const addMorphoBlueMerklRewardsToMarketInfo = (marketInfo, selectedMarket, network) => __awaiter(void 0, void 0, void 0, function* () { return addMorphoBlueMerklOpportunitiesToMarketInfo(marketInfo, selectedMarket, network, yield getMorphoBlueMerklOpportunities()); });
@@ -27,7 +27,7 @@ import { _getLiquityTroveInfo, getLiquityStakingData } from '../liquity';
27
27
  import { _getLiquityV2MarketData, getLiquitySAndYBold, getLiquityV2Staking } from '../liquityV2';
28
28
  import { _getAllUserEarnPositionsWithFTokens, _getUserPositionsPortfolio } from '../fluid';
29
29
  import { getUmbrellaData } from '../umbrella';
30
- import { getMeritUnclaimedRewards, getUnclaimedRewardsForAllMarkets } from '../claiming/aaveV3';
30
+ import { getMerklUnclaimedRewards, getUnclaimedRewardsForAllMarkets } from '../claiming/aaveV3';
31
31
  import { getCompoundV3Rewards } from '../claiming/compV3';
32
32
  import { fetchSparkAirdropRewards, fetchSparkRewards } from '../claiming/spark';
33
33
  import { getKingRewards } from '../claiming/king';
@@ -120,7 +120,7 @@ export function getPortfolioData(provider_1, network_1, defaultProvider_1, addre
120
120
  },
121
121
  };
122
122
  rewardsData[address.toLowerCase()] = {
123
- aaveV3merit: {},
123
+ merkl: {},
124
124
  aaveV3: {},
125
125
  compV3: {},
126
126
  spark: {},
@@ -345,12 +345,12 @@ export function getPortfolioData(provider_1, network_1, defaultProvider_1, addre
345
345
  }))).flat(),
346
346
  ...addresses.map((address) => __awaiter(this, void 0, void 0, function* () {
347
347
  try {
348
- const aaveMeritData = yield getMeritUnclaimedRewards(address, network);
349
- rewardsData[address.toLowerCase()].aaveV3merit = { error: '', data: aaveMeritData };
348
+ const merklData = yield getMerklUnclaimedRewards(address, network);
349
+ rewardsData[address.toLowerCase()].merkl = { error: '', data: merklData };
350
350
  }
351
351
  catch (error) {
352
- console.error(`Error fetching Aave V3 Merit rewards data for address ${address}:`, error);
353
- rewardsData[address.toLowerCase()].aaveV3merit = { error: `Error fetching Aave V3 Merit rewards data for address ${address}`, data: null };
352
+ console.error(`Error fetching Merkl rewards data for address ${address}:`, error);
353
+ rewardsData[address.toLowerCase()].merkl = { error: `Error fetching Merkl rewards data for address ${address}`, data: null };
354
354
  }
355
355
  })),
356
356
  ...aaveV3Markets.map(market => addresses.map((address) => __awaiter(this, void 0, void 0, function* () {
@@ -0,0 +1,6 @@
1
+ import { MerklOpportunity } from '../types';
2
+ /**
3
+ * Fetches every page matching the query. Merkl returns 20 opportunities by default, so omitting
4
+ * `items` and advancing `page` avoids imposing a client-side maximum on the result set.
5
+ */
6
+ export declare const fetchAllMerklOpportunities: (query: Record<string, string>) => Promise<MerklOpportunity[]>;