@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
|
@@ -0,0 +1,101 @@
|
|
|
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.addMorphoBlueMerklRewardsToMarketInfo = exports.getMorphoBlueMerklOpportunities = exports.addMorphoBlueMerklOpportunitiesToMarketInfo = exports.MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES = void 0;
|
|
13
|
+
const moneymarket_1 = require("../moneymarket");
|
|
14
|
+
const utils_1 = require("../services/utils");
|
|
15
|
+
const types_1 = require("../types");
|
|
16
|
+
/**
|
|
17
|
+
* Merkl tags Morpho Blue market-level reward campaigns via the opportunity `type` field:
|
|
18
|
+
* - MORPHOSUPPLY → reward for supplying the market's loan asset
|
|
19
|
+
* - MORPHOCOLLATERAL → reward for depositing the market's collateral asset
|
|
20
|
+
* - MORPHOBORROW → reward for borrowing the market's loan asset
|
|
21
|
+
* (MORPHOVAULT/ERC20LOGPROCESSOR are MetaMorpho vault campaigns and MORPHOSUPPLY_SINGLETOKEN is
|
|
22
|
+
* token-wide rather than market-scoped, so none of them belong on per-market data.)
|
|
23
|
+
*
|
|
24
|
+
* A campaign identifies its market via `identifier` — the first 20 bytes of the 32-byte Morpho
|
|
25
|
+
* market id, formatted as a checksummed address. Whitelist-gated campaigns append a marker to that
|
|
26
|
+
* identifier (e.g. `0x54cf…c46WHITELIST_CAMPAIGN`); their rewards only accrue to whitelisted
|
|
27
|
+
* addresses, so they are skipped rather than advertised to every user.
|
|
28
|
+
*/
|
|
29
|
+
exports.MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES = ['MORPHOSUPPLY', 'MORPHOCOLLATERAL', 'MORPHOBORROW'];
|
|
30
|
+
const MERKL_DESCRIPTION_MARKER = 'through Merkl';
|
|
31
|
+
// '0x' + first 20 bytes of the market id
|
|
32
|
+
const MARKET_ID_PREFIX_LENGTH = 42;
|
|
33
|
+
const buildMerklIncentive = (opportunity) => {
|
|
34
|
+
var _a, _b, _c, _d, _e, _f;
|
|
35
|
+
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) || '';
|
|
36
|
+
return {
|
|
37
|
+
apy: (0, moneymarket_1.aprToApy)(opportunity.apr),
|
|
38
|
+
token,
|
|
39
|
+
incentiveKind: types_1.IncentiveKind.Reward,
|
|
40
|
+
description: `Eligible for ${token} rewards through Merkl.${opportunity.description ? `\n${opportunity.description}` : ''}`,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
const withoutMerklIncentives = (incentives = []) => incentives
|
|
44
|
+
.filter(({ description }) => !(description === null || description === void 0 ? void 0 : description.includes(MERKL_DESCRIPTION_MARKER)));
|
|
45
|
+
/**
|
|
46
|
+
* Appends the given live Merkl campaigns for the market to its `assetsData` incentive arrays
|
|
47
|
+
* (collateral supply / loan supply / loan borrow). Existing Merkl incentives are replaced rather
|
|
48
|
+
* than duplicated, so re-applying on refreshed data is safe.
|
|
49
|
+
*/
|
|
50
|
+
const addMorphoBlueMerklOpportunitiesToMarketInfo = (marketInfo, selectedMarket, chainId, opportunities) => {
|
|
51
|
+
const marketIdPrefix = (selectedMarket.marketId || '').slice(0, MARKET_ID_PREFIX_LENGTH).toLowerCase();
|
|
52
|
+
const collateralAsset = marketInfo.assetsData[marketInfo.collateralToken];
|
|
53
|
+
const loanAsset = marketInfo.assetsData[marketInfo.loanToken];
|
|
54
|
+
if (marketIdPrefix.length !== MARKET_ID_PREFIX_LENGTH || !collateralAsset || !loanAsset)
|
|
55
|
+
return marketInfo;
|
|
56
|
+
const relevant = opportunities.filter((o) => {
|
|
57
|
+
var _a;
|
|
58
|
+
return o.chainId === chainId
|
|
59
|
+
&& o.status === types_1.OpportunityStatus.LIVE
|
|
60
|
+
&& exports.MORPHO_BLUE_MERKL_OPPORTUNITY_TYPES.includes(o.type)
|
|
61
|
+
// a suffixed identifier marks a whitelist-gated campaign — skip those
|
|
62
|
+
&& ((_a = o.identifier) === null || _a === void 0 ? void 0 : _a.length) === MARKET_ID_PREFIX_LENGTH
|
|
63
|
+
&& o.identifier.toLowerCase() === marketIdPrefix;
|
|
64
|
+
});
|
|
65
|
+
if (!relevant.length)
|
|
66
|
+
return marketInfo;
|
|
67
|
+
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');
|
|
71
|
+
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
|
+
};
|
|
73
|
+
exports.addMorphoBlueMerklOpportunitiesToMarketInfo = addMorphoBlueMerklOpportunitiesToMarketInfo;
|
|
74
|
+
/**
|
|
75
|
+
* 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.
|
|
79
|
+
*/
|
|
80
|
+
const getMorphoBlueMerklOpportunities = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
81
|
+
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),
|
|
84
|
+
});
|
|
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
|
+
catch (e) {
|
|
91
|
+
console.error('Failed to fetch Morpho Blue Merkl campaigns', e);
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
exports.getMorphoBlueMerklOpportunities = getMorphoBlueMerklOpportunities;
|
|
96
|
+
/**
|
|
97
|
+
* Fetches live Merkl campaigns and appends the ones for the market to its `assetsData` incentive
|
|
98
|
+
* arrays. Never throws — on any failure the market info is returned unchanged.
|
|
99
|
+
*/
|
|
100
|
+
const addMorphoBlueMerklRewardsToMarketInfo = (marketInfo, selectedMarket, network) => __awaiter(void 0, void 0, void 0, function* () { return (0, exports.addMorphoBlueMerklOpportunitiesToMarketInfo)(marketInfo, selectedMarket, network, yield (0, exports.getMorphoBlueMerklOpportunities)()); });
|
|
101
|
+
exports.addMorphoBlueMerklRewardsToMarketInfo = addMorphoBlueMerklRewardsToMarketInfo;
|
|
@@ -19,6 +19,10 @@ export declare const isEligibleForAaveV3ArbitrumETHLSBorrow: (usedAssets: MMUsed
|
|
|
19
19
|
isEligible: boolean;
|
|
20
20
|
eligibleUSDAmount: string;
|
|
21
21
|
};
|
|
22
|
+
export declare const isEligibleForCompoundV3PufEthWethBorrow: (usedAssets: MMUsedAssets) => {
|
|
23
|
+
isEligible: boolean;
|
|
24
|
+
eligibleUSDAmount: string;
|
|
25
|
+
};
|
|
22
26
|
export declare const EligibilityMapping: {
|
|
23
27
|
[key in IncentiveEligibilityId]: (usedAssets: MMUsedAssets, optionalData: any) => {
|
|
24
28
|
isEligible: boolean;
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.EligibilityMapping = exports.isEligibleForAaveV3ArbitrumETHLSBorrow = exports.isEligibleForAaveV3ArbitrumEthSupply = exports.isEligibleForEthenaGHORewards = exports.isEligibleForEthenaUSDeRewards = void 0;
|
|
6
|
+
exports.EligibilityMapping = exports.isEligibleForCompoundV3PufEthWethBorrow = exports.isEligibleForAaveV3ArbitrumETHLSBorrow = exports.isEligibleForAaveV3ArbitrumEthSupply = exports.isEligibleForEthenaGHORewards = exports.isEligibleForEthenaUSDeRewards = void 0;
|
|
7
7
|
const decimal_js_1 = __importDefault(require("decimal.js"));
|
|
8
8
|
const common_1 = require("../types/common");
|
|
9
9
|
const isEligibleForEthenaPairRewards = (usedAssets, { healthRatio }, { baseSymbol, pairSymbol, allowedBorrowAssets, maxHealthRatio, rewardSymbol, }) => {
|
|
@@ -70,10 +70,20 @@ const isEligibleForAaveV3ArbitrumETHLSBorrow = (usedAssets) => {
|
|
|
70
70
|
return { isEligible: true, eligibleUSDAmount: ETHAmountBorrowed };
|
|
71
71
|
};
|
|
72
72
|
exports.isEligibleForAaveV3ArbitrumETHLSBorrow = isEligibleForAaveV3ArbitrumETHLSBorrow;
|
|
73
|
+
// "Borrow WETH with pufETH collateral" — rewards are calculated as min(collateral * 80% LTV, borrow amount)
|
|
74
|
+
const isEligibleForCompoundV3PufEthWethBorrow = (usedAssets) => {
|
|
75
|
+
var _a, _b;
|
|
76
|
+
const collateralUsd = ((_a = usedAssets.pufETH) === null || _a === void 0 ? void 0 : _a.suppliedUsd) || '0';
|
|
77
|
+
const borrowedUsd = ((_b = usedAssets.ETH) === null || _b === void 0 ? void 0 : _b.borrowedUsd) || '0';
|
|
78
|
+
const eligibleUSDAmount = decimal_js_1.default.min(new decimal_js_1.default(collateralUsd).mul('0.8'), borrowedUsd).toString();
|
|
79
|
+
return { isEligible: new decimal_js_1.default(eligibleUSDAmount).gt(0), eligibleUSDAmount };
|
|
80
|
+
};
|
|
81
|
+
exports.isEligibleForCompoundV3PufEthWethBorrow = isEligibleForCompoundV3PufEthWethBorrow;
|
|
73
82
|
exports.EligibilityMapping = {
|
|
74
83
|
[common_1.IncentiveEligibilityId.AaveV3EthenaLiquidLeverage]: exports.isEligibleForEthenaUSDeRewards,
|
|
75
84
|
[common_1.IncentiveEligibilityId.AaveV3ArbitrumEthSupply]: exports.isEligibleForAaveV3ArbitrumEthSupply,
|
|
76
85
|
[common_1.IncentiveEligibilityId.AaveV3ArbitrumETHLSBorrow]: exports.isEligibleForAaveV3ArbitrumETHLSBorrow,
|
|
77
86
|
[common_1.IncentiveEligibilityId.AaveV3EthenaLiquidLeveragePlasma]: exports.isEligibleForEthenaUSDeRewards,
|
|
78
87
|
[common_1.IncentiveEligibilityId.AaveV3EthenaLiquidLeveragePlasmaGHO]: exports.isEligibleForEthenaGHORewards,
|
|
88
|
+
[common_1.IncentiveEligibilityId.CompoundV3PufEthWethBorrow]: exports.isEligibleForCompoundV3PufEthWethBorrow,
|
|
79
89
|
};
|
package/cjs/types/common.d.ts
CHANGED
|
@@ -11,7 +11,8 @@ export declare enum IncentiveEligibilityId {
|
|
|
11
11
|
AaveV3ArbitrumEthSupply = "0x5d16261c6715a653248269861bbacf68a9774cde",
|
|
12
12
|
AaveV3ArbitrumETHLSBorrow = "0x0c84331e39d6658Cd6e6b9ba04736cC4c4734351",
|
|
13
13
|
AaveV3EthenaLiquidLeveragePlasma = "0x2E8f1FA9c73F9d975B46BDFe40C92b6dDEFA3f31BORROW_BL",
|
|
14
|
-
AaveV3EthenaLiquidLeveragePlasmaGHO = "2b2f6fefcd3df634"
|
|
14
|
+
AaveV3EthenaLiquidLeveragePlasmaGHO = "2b2f6fefcd3df634",
|
|
15
|
+
CompoundV3PufEthWethBorrow = "e6e49b79e83041f9"
|
|
15
16
|
}
|
|
16
17
|
export declare enum LeverageType {
|
|
17
18
|
Long = "long",
|
package/cjs/types/common.js
CHANGED
|
@@ -18,6 +18,7 @@ var IncentiveEligibilityId;
|
|
|
18
18
|
IncentiveEligibilityId["AaveV3ArbitrumETHLSBorrow"] = "0x0c84331e39d6658Cd6e6b9ba04736cC4c4734351";
|
|
19
19
|
IncentiveEligibilityId["AaveV3EthenaLiquidLeveragePlasma"] = "0x2E8f1FA9c73F9d975B46BDFe40C92b6dDEFA3f31BORROW_BL";
|
|
20
20
|
IncentiveEligibilityId["AaveV3EthenaLiquidLeveragePlasmaGHO"] = "2b2f6fefcd3df634";
|
|
21
|
+
IncentiveEligibilityId["CompoundV3PufEthWethBorrow"] = "e6e49b79e83041f9";
|
|
21
22
|
})(IncentiveEligibilityId || (exports.IncentiveEligibilityId = IncentiveEligibilityId = {}));
|
|
22
23
|
var LeverageType;
|
|
23
24
|
(function (LeverageType) {
|
package/cjs/types/liquityV2.d.ts
CHANGED
|
@@ -84,6 +84,7 @@ export interface LiquityV2AggregatedTroveData {
|
|
|
84
84
|
suppliedUsd: string;
|
|
85
85
|
borrowedUsd: string;
|
|
86
86
|
borrowLimitUsd: string;
|
|
87
|
+
liquidationLimitUsd: string;
|
|
87
88
|
leftToBorrowUsd: string;
|
|
88
89
|
netApy: string;
|
|
89
90
|
incentiveUsd: string;
|
|
@@ -101,9 +102,11 @@ export interface LiquityV2TroveData {
|
|
|
101
102
|
ratio: string;
|
|
102
103
|
collRatio: string;
|
|
103
104
|
liqRatio: string;
|
|
105
|
+
borrowLimitRatio: string;
|
|
104
106
|
interestRate: string;
|
|
105
107
|
leftToBorrowUsd: string;
|
|
106
108
|
borrowLimitUsd: string;
|
|
109
|
+
liquidationLimitUsd: string;
|
|
107
110
|
suppliedUsd: string;
|
|
108
111
|
borrowedUsd: string;
|
|
109
112
|
netApy: string;
|
package/cjs/types/merkl.d.ts
CHANGED
|
@@ -76,6 +76,14 @@ export type MerkleRewardMap = Record<EthAddress, {
|
|
|
76
76
|
export type AaveV4MerklScopedReward = {
|
|
77
77
|
[side in IncentiveSide]?: IncentiveData;
|
|
78
78
|
};
|
|
79
|
+
/**
|
|
80
|
+
* Fluid vault-scoped Merkl campaigns keyed by lowercase vault address — `supply` rewards apply to
|
|
81
|
+
* the vault's collateral side, `borrow` rewards to its debt side.
|
|
82
|
+
*/
|
|
83
|
+
export type FluidMerklRewardMap = Record<string, {
|
|
84
|
+
supply: IncentiveData[];
|
|
85
|
+
borrow: IncentiveData[];
|
|
86
|
+
}>;
|
|
79
87
|
/**
|
|
80
88
|
* Aave V4 Merkl reward campaigns split by scope:
|
|
81
89
|
* - `hub`: keyed by `${hubAddress}_${underlyingAddress}` (both lowercase) — rewards for supplying/borrowing via a hub
|
package/esm/aaveV3/merkl.js
CHANGED
|
@@ -35,7 +35,9 @@ export const formatAaveAsset = (_symbol) => {
|
|
|
35
35
|
};
|
|
36
36
|
export const getMerkleCampaigns = (chainId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
37
37
|
try {
|
|
38
|
-
|
|
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', {
|
|
39
41
|
signal: AbortSignal.timeout(LONGER_TIMEOUT),
|
|
40
42
|
});
|
|
41
43
|
if (!res.ok)
|
|
@@ -45,11 +47,13 @@ export const getMerkleCampaigns = (chainId) => __awaiter(void 0, void 0, void 0,
|
|
|
45
47
|
.filter((o) => o.chainId === chainId)
|
|
46
48
|
.filter((o) => o.status === OpportunityStatus.LIVE);
|
|
47
49
|
return relevantOpportunities.reduce((acc, opportunity) => {
|
|
48
|
-
var _a, _b;
|
|
49
|
-
const rewardToken = opportunity.rewardsRecord.breakdowns[0].token;
|
|
50
|
+
var _a, _b, _c, _d, _e;
|
|
51
|
+
const rewardToken = (_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;
|
|
52
|
+
if (!rewardToken)
|
|
53
|
+
return acc;
|
|
50
54
|
const description = `Eligible for ${formatAaveAsset(rewardToken.symbol)} rewards through Merkl. ${opportunity.description ? `\n${opportunity.description}` : ''}`;
|
|
51
55
|
if (opportunity.action === OpportunityAction.LEND && opportunity.explorerAddress) {
|
|
52
|
-
const supplyAToken = (
|
|
56
|
+
const supplyAToken = (_d = opportunity.explorerAddress) === null || _d === void 0 ? void 0 : _d.toLowerCase();
|
|
53
57
|
if (!acc[supplyAToken])
|
|
54
58
|
acc[supplyAToken] = {};
|
|
55
59
|
acc[supplyAToken].supply = {
|
|
@@ -61,7 +65,7 @@ export const getMerkleCampaigns = (chainId) => __awaiter(void 0, void 0, void 0,
|
|
|
61
65
|
};
|
|
62
66
|
}
|
|
63
67
|
if (opportunity.action === OpportunityAction.BORROW && opportunity.explorerAddress) {
|
|
64
|
-
const borrowAToken = (
|
|
68
|
+
const borrowAToken = (_e = opportunity.explorerAddress) === null || _e === void 0 ? void 0 : _e.toLowerCase();
|
|
65
69
|
if (!acc[borrowAToken])
|
|
66
70
|
acc[borrowAToken] = {};
|
|
67
71
|
acc[borrowAToken].borrow = {
|
package/esm/aaveV4/merkl.js
CHANGED
|
@@ -63,7 +63,7 @@ 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', {
|
|
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
67
|
signal: AbortSignal.timeout(LONGER_TIMEOUT),
|
|
68
68
|
});
|
|
69
69
|
if (!res.ok)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Client } from 'viem';
|
|
2
2
|
import { CompoundMarketData, CompoundV3AssetsData, CompoundV3MarketsData, CompoundV3PositionData } from '../types';
|
|
3
3
|
import { Blockish, EthAddress, EthereumProvider, NetworkNumber, PositionBalances } from '../types/common';
|
|
4
|
+
export { getCompoundV3MerklOpportunities, attachCompoundV3MerklIncentives } from './merkl';
|
|
4
5
|
export declare const _getCompoundV3MarketsData: (provider: Client, network: NetworkNumber, selectedMarket: CompoundMarketData, defaultProvider: Client) => Promise<CompoundV3MarketsData>;
|
|
5
6
|
export declare const getCompoundV3MarketsData: (provider: EthereumProvider, network: NetworkNumber, selectedMarket: CompoundMarketData, defaultProvider: EthereumProvider) => Promise<CompoundV3MarketsData>;
|
|
6
7
|
export declare const EMPTY_COMPOUND_V3_DATA: {
|
package/esm/compoundV3/index.js
CHANGED
|
@@ -19,6 +19,8 @@ import { formatBaseData, formatMarketData, getCompoundV3AggregatedData, getIncen
|
|
|
19
19
|
import { CompoundMarkets } from '../markets/compound';
|
|
20
20
|
import { getEthPrice, getCompPrice, getUSDCPrice, getWstETHPrice, } from '../services/priceService';
|
|
21
21
|
import { getViemProvider, setViemBlockNumber } from '../services/viem';
|
|
22
|
+
import { attachCompoundV3MerklIncentives, getCompoundV3MerklOpportunities } from './merkl';
|
|
23
|
+
export { getCompoundV3MerklOpportunities, attachCompoundV3MerklIncentives } from './merkl';
|
|
22
24
|
const getSupportedAssetsAddressesForMarket = (selectedMarket, network) => selectedMarket.collAssets.map(asset => getAssetInfo(ethToWeth(asset), network)).map(addr => addr.address.toLowerCase());
|
|
23
25
|
const getBaseAssetPriceFunction = (asset) => {
|
|
24
26
|
switch (asset) {
|
|
@@ -32,12 +34,13 @@ const getBaseAssetPriceFunction = (asset) => {
|
|
|
32
34
|
};
|
|
33
35
|
export const _getCompoundV3MarketsData = (provider, network, selectedMarket, defaultProvider) => __awaiter(void 0, void 0, void 0, function* () {
|
|
34
36
|
const contract = CompV3ViewContractViem(provider, network);
|
|
35
|
-
const [baseAssetPrice, compPrice, baseTokenInfo, collInfos, govInfo] = yield Promise.all([
|
|
37
|
+
const [baseAssetPrice, compPrice, baseTokenInfo, collInfos, govInfo, merklOpportunities] = yield Promise.all([
|
|
36
38
|
getBaseAssetPriceFunction(selectedMarket.baseAsset)(defaultProvider),
|
|
37
39
|
getCompPrice(defaultProvider),
|
|
38
40
|
contract.read.getFullBaseTokenInfo([selectedMarket.baseMarketAddress]),
|
|
39
41
|
contract.read.getFullCollInfos([selectedMarket.baseMarketAddress]),
|
|
40
42
|
contract.read.getGovernanceInfoFull([selectedMarket.baseMarketAddress]),
|
|
43
|
+
getCompoundV3MerklOpportunities(),
|
|
41
44
|
]);
|
|
42
45
|
const { isSupplyPaused, isWithdrawPaused } = govInfo;
|
|
43
46
|
const supportedAssetsAddresses = getSupportedAssetsAddressesForMarket(selectedMarket, network);
|
|
@@ -57,6 +60,7 @@ export const _getCompoundV3MarketsData = (provider, network, selectedMarket, def
|
|
|
57
60
|
const base = formatBaseData(baseTokenInfo, network, baseAssetPrice, isSupplyPaused, isWithdrawPaused);
|
|
58
61
|
const payload = {};
|
|
59
62
|
const baseObj = Object.assign(Object.assign({}, base), (yield getIncentiveApys(base, compPrice)));
|
|
63
|
+
attachCompoundV3MerklIncentives(baseObj, colls, selectedMarket.baseMarketAddress, network, merklOpportunities);
|
|
60
64
|
const allAssets = [baseObj, ...colls];
|
|
61
65
|
allAssets
|
|
62
66
|
.sort((a, b) => {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CompoundV3AssetData, MerklOpportunity, NetworkNumber } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Merkl tags Compound V3 reward campaigns with COMPOUND_V3_* opportunity types (e.g.
|
|
4
|
+
* COMPOUND_V3_BORROW_FROM_COLLATERAL) and identifies the comet via `explorerAddress`. Campaign
|
|
5
|
+
* identifiers are opaque hashes, passed through as `eligibilityId` so conditional campaigns —
|
|
6
|
+
* like borrow-from-collateral ones, whose reward is min(collateral * LTV, borrow amount) — can
|
|
7
|
+
* be gated and sized by a curated check in EligibilityMapping; unknown ids apply
|
|
8
|
+
* unconditionally, mirroring Aave V3.
|
|
9
|
+
*/
|
|
10
|
+
export declare const getCompoundV3MerklOpportunities: () => Promise<MerklOpportunity[]>;
|
|
11
|
+
/**
|
|
12
|
+
* Appends the comet's Merkl campaigns to its assets' incentive arrays (mutating them, like the
|
|
13
|
+
* staking incentives are attached). BORROW campaigns land on the base asset — the only borrowable
|
|
14
|
+
* one — while LEND campaigns land on the asset the campaign's first token resolves to.
|
|
15
|
+
*/
|
|
16
|
+
export declare const attachCompoundV3MerklIncentives: (baseAsset: CompoundV3AssetData, collAssets: CompoundV3AssetData[], baseMarketAddress: string, chainId: NetworkNumber, opportunities: MerklOpportunity[]) => void;
|
|
@@ -0,0 +1,74 @@
|
|
|
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 { getAssetInfoByAddress } from '@defisaver/tokens';
|
|
11
|
+
import { aprToApy } from '../moneymarket';
|
|
12
|
+
import { compareAddresses, LONGER_TIMEOUT, wethToEth } from '../services/utils';
|
|
13
|
+
import { IncentiveKind, OpportunityAction, OpportunityStatus, } from '../types';
|
|
14
|
+
/**
|
|
15
|
+
* Merkl tags Compound V3 reward campaigns with COMPOUND_V3_* opportunity types (e.g.
|
|
16
|
+
* COMPOUND_V3_BORROW_FROM_COLLATERAL) and identifies the comet via `explorerAddress`. Campaign
|
|
17
|
+
* identifiers are opaque hashes, passed through as `eligibilityId` so conditional campaigns —
|
|
18
|
+
* like borrow-from-collateral ones, whose reward is min(collateral * LTV, borrow amount) — can
|
|
19
|
+
* be gated and sized by a curated check in EligibilityMapping; unknown ids apply
|
|
20
|
+
* unconditionally, mirroring Aave V3.
|
|
21
|
+
*/
|
|
22
|
+
export const getCompoundV3MerklOpportunities = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
23
|
+
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),
|
|
26
|
+
});
|
|
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
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
console.error('Failed to fetch Compound V3 Merkl campaigns', e);
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
const buildMerklIncentive = (opportunity) => {
|
|
38
|
+
var _a, _b, _c, _d, _e, _f;
|
|
39
|
+
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) || '';
|
|
40
|
+
return {
|
|
41
|
+
apy: aprToApy(opportunity.apr),
|
|
42
|
+
token,
|
|
43
|
+
incentiveKind: IncentiveKind.Reward,
|
|
44
|
+
description: `Eligible for ${token} rewards through Merkl.${opportunity.description ? `\n${opportunity.description}` : ''}`,
|
|
45
|
+
eligibilityId: opportunity.identifier,
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Appends the comet's Merkl campaigns to its assets' incentive arrays (mutating them, like the
|
|
50
|
+
* staking incentives are attached). BORROW campaigns land on the base asset — the only borrowable
|
|
51
|
+
* one — while LEND campaigns land on the asset the campaign's first token resolves to.
|
|
52
|
+
*/
|
|
53
|
+
export const attachCompoundV3MerklIncentives = (baseAsset, collAssets, baseMarketAddress, chainId, opportunities) => {
|
|
54
|
+
opportunities
|
|
55
|
+
.filter((o) => o.chainId === chainId
|
|
56
|
+
&& o.status === OpportunityStatus.LIVE
|
|
57
|
+
&& typeof o.type === 'string' && o.type.startsWith('COMPOUND_V3')
|
|
58
|
+
&& o.explorerAddress && compareAddresses(o.explorerAddress, baseMarketAddress))
|
|
59
|
+
.forEach((o) => {
|
|
60
|
+
var _a, _b;
|
|
61
|
+
const incentive = buildMerklIncentive(o);
|
|
62
|
+
if (o.action === OpportunityAction.BORROW) {
|
|
63
|
+
baseAsset.borrowIncentives.push(incentive);
|
|
64
|
+
}
|
|
65
|
+
else if (o.action === OpportunityAction.LEND) {
|
|
66
|
+
const campaignTokenAddress = (_b = (_a = o.tokens) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.address;
|
|
67
|
+
if (!campaignTokenAddress)
|
|
68
|
+
return;
|
|
69
|
+
const campaignSymbol = wethToEth(getAssetInfoByAddress(campaignTokenAddress, chainId).symbol);
|
|
70
|
+
const target = [baseAsset, ...collAssets].find((asset) => asset.symbol === campaignSymbol);
|
|
71
|
+
target === null || target === void 0 ? void 0 : target.supplyIncentives.push(incentive);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
};
|
package/esm/fluid/index.d.ts
CHANGED
|
@@ -1,6 +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
5
|
export declare const EMPTY_USED_ASSET: {
|
|
5
6
|
isSupplied: boolean;
|
|
6
7
|
isBorrowed: boolean;
|
package/esm/fluid/index.js
CHANGED
|
@@ -19,6 +19,8 @@ import { USD_QUOTE, ZERO_ADDRESS } from '../constants';
|
|
|
19
19
|
import { getChainlinkAssetAddress, getSyrupUSDTChainLinkPriceCalls, getSyrupUSDTPrice, getWeETHChainLinkPriceCalls, getWeETHPrice, getWsrETHChainLinkPriceCalls, getWsrETHPrice, getWstETHChainLinkPriceCalls, getWstETHPrice, getWstETHPriceFluid, getWstUSRChainLinkPriceCalls, getWstUSRPrice, parseSyrupUSDTPriceCalls, parseWeETHPriceCalls, parseWrsETHPriceCalls, parseWstETHPriceCalls, parseWstUSRPriceCalls, } from '../services/priceService';
|
|
20
20
|
import { getStakingApy, STAKING_ASSETS } from '../staking';
|
|
21
21
|
import { getViemProvider } from '../services/viem';
|
|
22
|
+
import { attachFluidMerklIncentives, getFluidMerklCampaigns } from './merkl';
|
|
23
|
+
export { FLUID_MERKL_OPPORTUNITY_TYPES, buildFluidMerklRewardMap, getFluidMerklCampaigns, attachFluidMerklIncentives, } from './merkl';
|
|
22
24
|
export const EMPTY_USED_ASSET = {
|
|
23
25
|
isSupplied: false,
|
|
24
26
|
isBorrowed: false,
|
|
@@ -1100,8 +1102,12 @@ const parseUserData = (userPositionData, vaultData) => {
|
|
|
1100
1102
|
};
|
|
1101
1103
|
export const _getFluidMarketData = (provider, network, market) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1102
1104
|
const view = FluidViewContractViem(provider, network);
|
|
1103
|
-
const data = yield
|
|
1104
|
-
|
|
1105
|
+
const [data, merklCampaigns] = yield Promise.all([
|
|
1106
|
+
view.read.getVaultData([market.marketAddress]),
|
|
1107
|
+
getFluidMerklCampaigns(network),
|
|
1108
|
+
]);
|
|
1109
|
+
const marketData = yield parseMarketData(provider, data, network);
|
|
1110
|
+
return marketData ? attachFluidMerklIncentives(marketData, merklCampaigns) : marketData;
|
|
1105
1111
|
});
|
|
1106
1112
|
export const getFluidMarketData = (provider, network, market) => __awaiter(void 0, void 0, void 0, function* () { return _getFluidMarketData(getViemProvider(provider, network), network, market); });
|
|
1107
1113
|
export const _getFluidVaultIdsForUser = (provider, network, user) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -1118,11 +1124,15 @@ export const _getFluidPosition = (provider, network, vaultId, extractedState) =>
|
|
|
1118
1124
|
export const getFluidPosition = (provider, network, vaultId, extractedState) => __awaiter(void 0, void 0, void 0, function* () { return _getFluidPosition(getViemProvider(provider, network), network, vaultId, extractedState); });
|
|
1119
1125
|
export const _getFluidPositionWithMarket = (provider, network, vaultId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1120
1126
|
const view = FluidViewContractViem(provider, network);
|
|
1121
|
-
const data = yield
|
|
1122
|
-
|
|
1123
|
-
|
|
1127
|
+
const [data, merklCampaigns] = yield Promise.all([
|
|
1128
|
+
view.read.getPositionByNftId([BigInt(vaultId)]),
|
|
1129
|
+
getFluidMerklCampaigns(network),
|
|
1130
|
+
]);
|
|
1131
|
+
const parsedMarketData = yield parseMarketData(provider, data[1], network);
|
|
1132
|
+
if (!parsedMarketData) {
|
|
1124
1133
|
return;
|
|
1125
1134
|
}
|
|
1135
|
+
const marketData = attachFluidMerklIncentives(parsedMarketData, merklCampaigns);
|
|
1126
1136
|
const userData = parseUserData(data[0], marketData);
|
|
1127
1137
|
return {
|
|
1128
1138
|
userData,
|
|
@@ -1133,8 +1143,14 @@ export const getFluidPositionWithMarket = (provider, network, vaultId) => __awai
|
|
|
1133
1143
|
export const _getAllFluidMarketDataChunked = (network, provider) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1134
1144
|
const versions = getFluidVersionsDataForNetwork(network);
|
|
1135
1145
|
const view = FluidViewContractViem(provider, network);
|
|
1136
|
-
const data = yield Promise.all(
|
|
1137
|
-
|
|
1146
|
+
const [data, merklCampaigns] = yield Promise.all([
|
|
1147
|
+
Promise.all(versions.map((version) => view.read.getVaultData([version.marketAddress]))),
|
|
1148
|
+
getFluidMerklCampaigns(network),
|
|
1149
|
+
]);
|
|
1150
|
+
return Promise.all(data.map((item) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1151
|
+
const marketData = yield parseMarketData(provider, item, network);
|
|
1152
|
+
return marketData ? attachFluidMerklIncentives(marketData, merklCampaigns) : marketData;
|
|
1153
|
+
})));
|
|
1138
1154
|
});
|
|
1139
1155
|
export const getAllFluidMarketDataChunked = (network, provider) => __awaiter(void 0, void 0, void 0, function* () { return _getAllFluidMarketDataChunked(network, getViemProvider(provider, network, { batch: { multicall: true } })); });
|
|
1140
1156
|
export const _getFluidTokenData = (provider, network, token) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FluidMarketData, FluidMerklRewardMap, MerklOpportunity, NetworkNumber } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Merkl tags Fluid vault-scoped reward campaigns via the opportunity `type` field:
|
|
4
|
+
* - FLUIDVAULT_COLLATERAL → reward for supplying collateral to the vault
|
|
5
|
+
* - FLUIDVAULT_BORROW → reward for borrowing from the vault
|
|
6
|
+
* (ERC20LOGPROCESSOR campaigns target fToken earn positions rather than vaults, so they don't
|
|
7
|
+
* belong on vault market data.)
|
|
8
|
+
*
|
|
9
|
+
* A campaign identifies its vault via `identifier` — the vault address. Conditional campaigns
|
|
10
|
+
* append a marker to it (e.g. `0x5668…eeaBORROW_BL`, excluding holders who borrow the reward
|
|
11
|
+
* asset elsewhere); those are still attached, with the full identifier passed through as
|
|
12
|
+
* `eligibilityId` so a curated check in EligibilityMapping can gate them in net APY — unknown
|
|
13
|
+
* ids apply unconditionally, mirroring Aave V3. Whitelist-gated campaigns only accrue to
|
|
14
|
+
* whitelisted addresses, so they are skipped rather than advertised to every user.
|
|
15
|
+
*/
|
|
16
|
+
export declare const FLUID_MERKL_OPPORTUNITY_TYPES: readonly ["FLUIDVAULT_COLLATERAL", "FLUIDVAULT_BORROW"];
|
|
17
|
+
export declare const buildFluidMerklRewardMap: (opportunities: MerklOpportunity[], chainId: NetworkNumber) => FluidMerklRewardMap;
|
|
18
|
+
/**
|
|
19
|
+
* 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.
|
|
22
|
+
*/
|
|
23
|
+
export declare const getFluidMerklCampaigns: (chainId: NetworkNumber) => Promise<FluidMerklRewardMap>;
|
|
24
|
+
/**
|
|
25
|
+
* Appends the vault's Merkl campaigns to its assets' incentive arrays — supply rewards on every
|
|
26
|
+
* suppliable asset and borrow rewards on every borrowable one. Smart collateral/debt vaults hold
|
|
27
|
+
* the position across both pair tokens, so the pro-rata reward APY applies to each side's value.
|
|
28
|
+
*/
|
|
29
|
+
export declare const attachFluidMerklIncentives: (marketData: FluidMarketData, campaigns: FluidMerklRewardMap) => FluidMarketData;
|
|
@@ -0,0 +1,91 @@
|
|
|
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 { LONGER_TIMEOUT } from '../services/utils';
|
|
12
|
+
import { IncentiveKind, OpportunityStatus, } from '../types';
|
|
13
|
+
/**
|
|
14
|
+
* Merkl tags Fluid vault-scoped reward campaigns via the opportunity `type` field:
|
|
15
|
+
* - FLUIDVAULT_COLLATERAL → reward for supplying collateral to the vault
|
|
16
|
+
* - FLUIDVAULT_BORROW → reward for borrowing from the vault
|
|
17
|
+
* (ERC20LOGPROCESSOR campaigns target fToken earn positions rather than vaults, so they don't
|
|
18
|
+
* belong on vault market data.)
|
|
19
|
+
*
|
|
20
|
+
* A campaign identifies its vault via `identifier` — the vault address. Conditional campaigns
|
|
21
|
+
* append a marker to it (e.g. `0x5668…eeaBORROW_BL`, excluding holders who borrow the reward
|
|
22
|
+
* asset elsewhere); those are still attached, with the full identifier passed through as
|
|
23
|
+
* `eligibilityId` so a curated check in EligibilityMapping can gate them in net APY — unknown
|
|
24
|
+
* ids apply unconditionally, mirroring Aave V3. Whitelist-gated campaigns only accrue to
|
|
25
|
+
* whitelisted addresses, so they are skipped rather than advertised to every user.
|
|
26
|
+
*/
|
|
27
|
+
export const FLUID_MERKL_OPPORTUNITY_TYPES = ['FLUIDVAULT_COLLATERAL', 'FLUIDVAULT_BORROW'];
|
|
28
|
+
// '0x' + 20-byte vault address; conditional campaigns append a marker after it
|
|
29
|
+
const VAULT_ADDRESS_LENGTH = 42;
|
|
30
|
+
const buildMerklIncentive = (opportunity) => {
|
|
31
|
+
var _a, _b, _c, _d, _e, _f;
|
|
32
|
+
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) || '';
|
|
33
|
+
return {
|
|
34
|
+
apy: aprToApy(opportunity.apr),
|
|
35
|
+
token,
|
|
36
|
+
incentiveKind: IncentiveKind.Reward,
|
|
37
|
+
description: `Eligible for ${token} rewards through Merkl.${opportunity.description ? `\n${opportunity.description}` : ''}`,
|
|
38
|
+
eligibilityId: opportunity.identifier,
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
export const buildFluidMerklRewardMap = (opportunities, chainId) => {
|
|
42
|
+
const result = {};
|
|
43
|
+
opportunities
|
|
44
|
+
.filter((o) => o.chainId === chainId)
|
|
45
|
+
.filter((o) => o.status === OpportunityStatus.LIVE)
|
|
46
|
+
.filter((o) => FLUID_MERKL_OPPORTUNITY_TYPES.includes(o.type))
|
|
47
|
+
.filter((o) => { var _a; return !((_a = o.identifier) === null || _a === void 0 ? void 0 : _a.includes('WHITELIST')); })
|
|
48
|
+
.forEach((o) => {
|
|
49
|
+
var _a;
|
|
50
|
+
const vaultAddress = (_a = o.identifier) === null || _a === void 0 ? void 0 : _a.slice(0, VAULT_ADDRESS_LENGTH).toLowerCase();
|
|
51
|
+
if (!vaultAddress || vaultAddress.length !== VAULT_ADDRESS_LENGTH)
|
|
52
|
+
return;
|
|
53
|
+
if (!result[vaultAddress])
|
|
54
|
+
result[vaultAddress] = { supply: [], borrow: [] };
|
|
55
|
+
result[vaultAddress][o.type === 'FLUIDVAULT_BORROW' ? 'borrow' : 'supply'].push(buildMerklIncentive(o));
|
|
56
|
+
});
|
|
57
|
+
return result;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* 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.
|
|
63
|
+
*/
|
|
64
|
+
export const getFluidMerklCampaigns = (chainId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
65
|
+
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),
|
|
68
|
+
});
|
|
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);
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
console.error('Failed to fetch Fluid Merkl campaigns', e);
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
/**
|
|
80
|
+
* Appends the vault's Merkl campaigns to its assets' incentive arrays — supply rewards on every
|
|
81
|
+
* suppliable asset and borrow rewards on every borrowable one. Smart collateral/debt vaults hold
|
|
82
|
+
* the position across both pair tokens, so the pro-rata reward APY applies to each side's value.
|
|
83
|
+
*/
|
|
84
|
+
export const attachFluidMerklIncentives = (marketData, campaigns) => {
|
|
85
|
+
var _a;
|
|
86
|
+
const vaultCampaigns = campaigns[(_a = marketData.marketData.marketAddress) === null || _a === void 0 ? void 0 : _a.toLowerCase()];
|
|
87
|
+
if (!vaultCampaigns || (!vaultCampaigns.supply.length && !vaultCampaigns.borrow.length))
|
|
88
|
+
return marketData;
|
|
89
|
+
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 })]));
|
|
90
|
+
return Object.assign(Object.assign({}, marketData), { assetsData });
|
|
91
|
+
};
|
|
@@ -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.
|
|
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;
|