@defisaver/positions-sdk 2.1.141-dev → 2.1.142-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.
- package/cjs/aaveV3/merkl.js +9 -5
- package/cjs/aaveV4/merkl.js +1 -1
- package/cjs/compoundV3/index.d.ts +1 -0
- package/cjs/compoundV3/index.js +8 -2
- package/cjs/compoundV3/merkl.d.ts +16 -0
- package/cjs/compoundV3/merkl.js +79 -0
- package/cjs/fluid/index.d.ts +1 -0
- package/cjs/fluid/index.js +28 -8
- package/cjs/fluid/merkl.d.ts +29 -0
- package/cjs/fluid/merkl.js +97 -0
- package/cjs/helpers/liquityV2Helpers/index.d.ts +2 -1
- package/cjs/helpers/liquityV2Helpers/index.js +3 -2
- package/cjs/liquityV2/index.js +3 -4
- package/cjs/morphoBlue/index.d.ts +1 -0
- package/cjs/morphoBlue/index.js +16 -10
- package/cjs/morphoBlue/merkl.d.ts +33 -0
- package/cjs/morphoBlue/merkl.js +101 -0
- package/cjs/staking/eligibility.d.ts +4 -0
- package/cjs/staking/eligibility.js +11 -1
- package/cjs/types/common.d.ts +2 -1
- package/cjs/types/common.js +1 -0
- package/cjs/types/liquityV2.d.ts +3 -0
- package/cjs/types/merkl.d.ts +8 -0
- package/esm/aaveV3/merkl.js +9 -5
- package/esm/aaveV4/merkl.js +1 -1
- package/esm/compoundV3/index.d.ts +1 -0
- package/esm/compoundV3/index.js +5 -1
- package/esm/compoundV3/merkl.d.ts +16 -0
- package/esm/compoundV3/merkl.js +74 -0
- package/esm/fluid/index.d.ts +1 -0
- package/esm/fluid/index.js +23 -7
- package/esm/fluid/merkl.d.ts +29 -0
- package/esm/fluid/merkl.js +91 -0
- package/esm/helpers/liquityV2Helpers/index.d.ts +2 -1
- package/esm/helpers/liquityV2Helpers/index.js +3 -2
- package/esm/liquityV2/index.js +3 -4
- package/esm/morphoBlue/index.d.ts +1 -0
- package/esm/morphoBlue/index.js +11 -9
- package/esm/morphoBlue/merkl.d.ts +33 -0
- package/esm/morphoBlue/merkl.js +95 -0
- package/esm/staking/eligibility.d.ts +4 -0
- package/esm/staking/eligibility.js +9 -0
- package/esm/types/common.d.ts +2 -1
- package/esm/types/common.js +1 -0
- package/esm/types/liquityV2.d.ts +3 -0
- package/esm/types/merkl.d.ts +8 -0
- package/package.json +1 -1
- package/src/aaveV3/merkl.ts +5 -2
- package/src/aaveV4/merkl.ts +1 -1
- package/src/compoundV3/index.ts +6 -1
- package/src/compoundV3/merkl.ts +77 -0
- package/src/fluid/index.ts +29 -7
- package/src/fluid/merkl.ts +97 -0
- package/src/helpers/liquityV2Helpers/index.ts +4 -1
- package/src/liquityV2/index.ts +4 -3
- package/src/morphoBlue/index.ts +24 -9
- package/src/morphoBlue/merkl.ts +120 -0
- package/src/staking/eligibility.ts +10 -0
- package/src/types/common.ts +1 -0
- package/src/types/liquityV2.ts +3 -0
- package/src/types/merkl.ts +6 -0
package/src/liquityV2/index.ts
CHANGED
|
@@ -425,7 +425,7 @@ export const _getLiquityV2TroveData = async (
|
|
|
425
425
|
const lastInterestRateAdjTime = data.lastInterestRateAdjTime.toString();
|
|
426
426
|
|
|
427
427
|
const hasInterestBatchManager = !compareAddresses(interestBatchManager, ZERO_ADDRESS);
|
|
428
|
-
const
|
|
428
|
+
const borrowLimitRatio = hasInterestBatchManager ? new Dec(minCollRatio).add(batchCollRatio).toString() : minCollRatio;
|
|
429
429
|
|
|
430
430
|
const payload: LiquityV2TroveData = {
|
|
431
431
|
usedAssets,
|
|
@@ -434,10 +434,11 @@ export const _getLiquityV2TroveData = async (
|
|
|
434
434
|
interestBatchManager,
|
|
435
435
|
debtInFront,
|
|
436
436
|
lastInterestRateAdjTime,
|
|
437
|
-
liqRatio,
|
|
437
|
+
liqRatio: minCollRatio,
|
|
438
|
+
borrowLimitRatio,
|
|
438
439
|
troveStatus: LIQUITY_V2_TROVE_STATUS_ENUM[parseInt(data.status.toString(), 10)],
|
|
439
440
|
...getLiquityV2AggregatedPositionData({
|
|
440
|
-
usedAssets, assetsData, minCollRatio:
|
|
441
|
+
usedAssets, assetsData, minCollRatio: borrowLimitRatio, interestRate, liqRatio: minCollRatio,
|
|
441
442
|
}),
|
|
442
443
|
collRatio,
|
|
443
444
|
};
|
package/src/morphoBlue/index.ts
CHANGED
|
@@ -19,6 +19,15 @@ import {
|
|
|
19
19
|
import { getChainlinkAssetAddress } from '../services/priceService';
|
|
20
20
|
import { getViemProvider, setViemBlockNumber } from '../services/viem';
|
|
21
21
|
|
|
22
|
+
import { addMorphoBlueMerklOpportunitiesToMarketInfo, getMorphoBlueMerklOpportunities } from './merkl';
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES,
|
|
26
|
+
addMorphoBlueMerklOpportunitiesToMarketInfo,
|
|
27
|
+
getMorphoBlueMerklOpportunities,
|
|
28
|
+
addMorphoBlueMerklRewardsToMarketInfo,
|
|
29
|
+
} from './merkl';
|
|
30
|
+
|
|
22
31
|
const HARDCODED_USD_STABLE_PRICE = '100000000'; // $1 with 8 decimals
|
|
23
32
|
|
|
24
33
|
const getMorphoRewardIncentives = (apy: string) => [{
|
|
@@ -151,15 +160,21 @@ async function getMorphoBlueMarketDataInternal(
|
|
|
151
160
|
}
|
|
152
161
|
|
|
153
162
|
export async function _getMorphoBlueMarketData(provider: Client, network: NetworkNumber, selectedMarket: MorphoBlueMarketData): Promise<MorphoBlueMarketInfo> {
|
|
154
|
-
const marketInfo = await
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
+
const [marketInfo, rewards, merklOpportunities] = await Promise.all([
|
|
164
|
+
getMorphoBlueMarketDataInternal(provider, network, selectedMarket),
|
|
165
|
+
getRewardsForMarket(selectedMarket.marketId, network).catch((error) => {
|
|
166
|
+
console.error(error);
|
|
167
|
+
return { supplyApy: '0', borrowApy: '0' };
|
|
168
|
+
}),
|
|
169
|
+
getMorphoBlueMerklOpportunities(),
|
|
170
|
+
]);
|
|
171
|
+
|
|
172
|
+
return addMorphoBlueMerklOpportunitiesToMarketInfo(
|
|
173
|
+
addMorphoBlueRewardsToMarketInfo(marketInfo, rewards),
|
|
174
|
+
selectedMarket,
|
|
175
|
+
network,
|
|
176
|
+
merklOpportunities,
|
|
177
|
+
);
|
|
163
178
|
}
|
|
164
179
|
|
|
165
180
|
export function _getMorphoBluePortfolioMarketData(provider: Client, network: NetworkNumber, selectedMarket: MorphoBlueMarketData): Promise<MorphoBlueMarketInfo> {
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { aprToApy } from '../moneymarket';
|
|
2
|
+
import { LONGER_TIMEOUT } from '../services/utils';
|
|
3
|
+
import {
|
|
4
|
+
IncentiveData,
|
|
5
|
+
IncentiveKind,
|
|
6
|
+
MerklOpportunity,
|
|
7
|
+
MorphoBlueMarketData,
|
|
8
|
+
MorphoBlueMarketInfo,
|
|
9
|
+
NetworkNumber,
|
|
10
|
+
OpportunityStatus,
|
|
11
|
+
} from '../types';
|
|
12
|
+
|
|
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 const MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES = ['MORPHOSUPPLY', 'MORPHOCOLLATERAL', 'MORPHOBORROW'] as const;
|
|
27
|
+
|
|
28
|
+
const MERKL_DESCRIPTION_MARKER = 'through Merkl';
|
|
29
|
+
|
|
30
|
+
// '0x' + first 20 bytes of the market id
|
|
31
|
+
const MARKET_ID_PREFIX_LENGTH = 42;
|
|
32
|
+
|
|
33
|
+
const buildMerklIncentive = (opportunity: MerklOpportunity): IncentiveData => {
|
|
34
|
+
const token = opportunity.rewardsRecord?.breakdowns?.[0]?.token?.symbol || opportunity.tokens?.[0]?.symbol || '';
|
|
35
|
+
return {
|
|
36
|
+
apy: aprToApy(opportunity.apr),
|
|
37
|
+
token,
|
|
38
|
+
incentiveKind: IncentiveKind.Reward,
|
|
39
|
+
description: `Eligible for ${token} rewards through Merkl.${opportunity.description ? `\n${opportunity.description}` : ''}`,
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const withoutMerklIncentives = (incentives: IncentiveData[] = []) => incentives
|
|
44
|
+
.filter(({ description }) => !description?.includes(MERKL_DESCRIPTION_MARKER));
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Appends the given live Merkl campaigns for the market to its `assetsData` incentive arrays
|
|
48
|
+
* (collateral supply / loan supply / loan borrow). Existing Merkl incentives are replaced rather
|
|
49
|
+
* than duplicated, so re-applying on refreshed data is safe.
|
|
50
|
+
*/
|
|
51
|
+
export const addMorphoBlueMerklOpportunitiesToMarketInfo = (
|
|
52
|
+
marketInfo: MorphoBlueMarketInfo,
|
|
53
|
+
selectedMarket: MorphoBlueMarketData,
|
|
54
|
+
chainId: NetworkNumber,
|
|
55
|
+
opportunities: MerklOpportunity[],
|
|
56
|
+
): MorphoBlueMarketInfo => {
|
|
57
|
+
const marketIdPrefix = (selectedMarket.marketId || '').slice(0, MARKET_ID_PREFIX_LENGTH).toLowerCase();
|
|
58
|
+
const collateralAsset = marketInfo.assetsData[marketInfo.collateralToken];
|
|
59
|
+
const loanAsset = marketInfo.assetsData[marketInfo.loanToken];
|
|
60
|
+
if (marketIdPrefix.length !== MARKET_ID_PREFIX_LENGTH || !collateralAsset || !loanAsset) return marketInfo;
|
|
61
|
+
|
|
62
|
+
const relevant = opportunities.filter((o) => o.chainId === chainId
|
|
63
|
+
&& o.status === OpportunityStatus.LIVE
|
|
64
|
+
&& (MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES as readonly string[]).includes(o.type)
|
|
65
|
+
// a suffixed identifier marks a whitelist-gated campaign — skip those
|
|
66
|
+
&& o.identifier?.length === MARKET_ID_PREFIX_LENGTH
|
|
67
|
+
&& o.identifier.toLowerCase() === marketIdPrefix);
|
|
68
|
+
if (!relevant.length) return marketInfo;
|
|
69
|
+
|
|
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
|
+
|
|
75
|
+
return {
|
|
76
|
+
...marketInfo,
|
|
77
|
+
assetsData: {
|
|
78
|
+
...marketInfo.assetsData,
|
|
79
|
+
[marketInfo.collateralToken]: {
|
|
80
|
+
...collateralAsset,
|
|
81
|
+
supplyIncentives: [...withoutMerklIncentives(collateralAsset.supplyIncentives), ...collateralSupply],
|
|
82
|
+
},
|
|
83
|
+
[marketInfo.loanToken]: {
|
|
84
|
+
...loanAsset,
|
|
85
|
+
supplyIncentives: [...withoutMerklIncentives(loanAsset.supplyIncentives), ...loanSupply],
|
|
86
|
+
borrowIncentives: [...withoutMerklIncentives(loanAsset.borrowIncentives), ...loanBorrow],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 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.
|
|
97
|
+
*/
|
|
98
|
+
export const getMorphoBlueMerklOpportunities = async (): Promise<MerklOpportunity[]> => {
|
|
99
|
+
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
|
+
});
|
|
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
|
+
} catch (e) {
|
|
107
|
+
console.error('Failed to fetch Morpho Blue Merkl campaigns', e);
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Fetches live Merkl campaigns and appends the ones for the market to its `assetsData` incentive
|
|
114
|
+
* arrays. Never throws — on any failure the market info is returned unchanged.
|
|
115
|
+
*/
|
|
116
|
+
export const addMorphoBlueMerklRewardsToMarketInfo = async (
|
|
117
|
+
marketInfo: MorphoBlueMarketInfo,
|
|
118
|
+
selectedMarket: MorphoBlueMarketData,
|
|
119
|
+
network: NetworkNumber,
|
|
120
|
+
): Promise<MorphoBlueMarketInfo> => addMorphoBlueMerklOpportunitiesToMarketInfo(marketInfo, selectedMarket, network, await getMorphoBlueMerklOpportunities());
|
|
@@ -97,10 +97,20 @@ export const isEligibleForAaveV3ArbitrumETHLSBorrow = (usedAssets: MMUsedAssets)
|
|
|
97
97
|
return { isEligible: true, eligibleUSDAmount: ETHAmountBorrowed };
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
+
// "Borrow WETH with pufETH collateral" — rewards are calculated as min(collateral * 80% LTV, borrow amount)
|
|
101
|
+
export const isEligibleForCompoundV3PufEthWethBorrow = (usedAssets: MMUsedAssets) => {
|
|
102
|
+
const collateralUsd = usedAssets.pufETH?.suppliedUsd || '0';
|
|
103
|
+
const borrowedUsd = usedAssets.ETH?.borrowedUsd || '0';
|
|
104
|
+
const eligibleUSDAmount = Dec.min(new Dec(collateralUsd).mul('0.8'), borrowedUsd).toString();
|
|
105
|
+
|
|
106
|
+
return { isEligible: new Dec(eligibleUSDAmount).gt(0), eligibleUSDAmount };
|
|
107
|
+
};
|
|
108
|
+
|
|
100
109
|
export const EligibilityMapping: { [key in IncentiveEligibilityId]: (usedAssets: MMUsedAssets, optionalData: any) => { isEligible: boolean; eligibleUSDAmount: string } } = {
|
|
101
110
|
[IncentiveEligibilityId.AaveV3EthenaLiquidLeverage]: isEligibleForEthenaUSDeRewards,
|
|
102
111
|
[IncentiveEligibilityId.AaveV3ArbitrumEthSupply]: isEligibleForAaveV3ArbitrumEthSupply,
|
|
103
112
|
[IncentiveEligibilityId.AaveV3ArbitrumETHLSBorrow]: isEligibleForAaveV3ArbitrumETHLSBorrow,
|
|
104
113
|
[IncentiveEligibilityId.AaveV3EthenaLiquidLeveragePlasma]: isEligibleForEthenaUSDeRewards,
|
|
105
114
|
[IncentiveEligibilityId.AaveV3EthenaLiquidLeveragePlasmaGHO]: isEligibleForEthenaGHORewards,
|
|
115
|
+
[IncentiveEligibilityId.CompoundV3PufEthWethBorrow]: isEligibleForCompoundV3PufEthWethBorrow,
|
|
106
116
|
};
|
package/src/types/common.ts
CHANGED
|
@@ -14,6 +14,7 @@ export enum IncentiveEligibilityId {
|
|
|
14
14
|
AaveV3ArbitrumETHLSBorrow = '0x0c84331e39d6658Cd6e6b9ba04736cC4c4734351',
|
|
15
15
|
AaveV3EthenaLiquidLeveragePlasma = '0x2E8f1FA9c73F9d975B46BDFe40C92b6dDEFA3f31BORROW_BL',
|
|
16
16
|
AaveV3EthenaLiquidLeveragePlasmaGHO = '2b2f6fefcd3df634',
|
|
17
|
+
CompoundV3PufEthWethBorrow = 'e6e49b79e83041f9',
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export enum LeverageType {
|
package/src/types/liquityV2.ts
CHANGED
|
@@ -94,6 +94,7 @@ export interface LiquityV2AggregatedTroveData {
|
|
|
94
94
|
suppliedUsd: string,
|
|
95
95
|
borrowedUsd: string,
|
|
96
96
|
borrowLimitUsd: string,
|
|
97
|
+
liquidationLimitUsd: string,
|
|
97
98
|
leftToBorrowUsd: string,
|
|
98
99
|
netApy: string,
|
|
99
100
|
incentiveUsd: string,
|
|
@@ -112,9 +113,11 @@ export interface LiquityV2TroveData {
|
|
|
112
113
|
ratio: string,
|
|
113
114
|
collRatio: string,
|
|
114
115
|
liqRatio: string,
|
|
116
|
+
borrowLimitRatio: string,
|
|
115
117
|
interestRate: string,
|
|
116
118
|
leftToBorrowUsd: string,
|
|
117
119
|
borrowLimitUsd: string,
|
|
120
|
+
liquidationLimitUsd: string,
|
|
118
121
|
suppliedUsd: string,
|
|
119
122
|
borrowedUsd: string,
|
|
120
123
|
netApy: string,
|
package/src/types/merkl.ts
CHANGED
|
@@ -72,6 +72,12 @@ export type MerkleRewardMap = Record<EthAddress, { supply?: MerkleRewardInfo; bo
|
|
|
72
72
|
|
|
73
73
|
export type AaveV4MerklScopedReward = { [side in IncentiveSide]?: IncentiveData };
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Fluid vault-scoped Merkl campaigns keyed by lowercase vault address — `supply` rewards apply to
|
|
77
|
+
* the vault's collateral side, `borrow` rewards to its debt side.
|
|
78
|
+
*/
|
|
79
|
+
export type FluidMerklRewardMap = Record<string, { supply: IncentiveData[], borrow: IncentiveData[] }>;
|
|
80
|
+
|
|
75
81
|
/**
|
|
76
82
|
* Aave V4 Merkl reward campaigns split by scope:
|
|
77
83
|
* - `hub`: keyed by `${hubAddress}_${underlyingAddress}` (both lowercase) — rewards for supplying/borrowing via a hub
|