@defisaver/positions-sdk 2.1.141 → 2.1.142
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/aaveV3/merkl.js +10 -9
- package/cjs/aaveV4/merkl.js +5 -6
- 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 +77 -0
- package/cjs/fluid/index.d.ts +1 -0
- package/cjs/fluid/index.js +28 -8
- package/cjs/fluid/merkl.d.ts +31 -0
- package/cjs/fluid/merkl.js +99 -0
- package/cjs/morphoBlue/index.d.ts +1 -0
- package/cjs/morphoBlue/index.js +16 -10
- package/cjs/morphoBlue/merkl.d.ts +35 -0
- package/cjs/morphoBlue/merkl.js +102 -0
- package/cjs/services/merkl.d.ts +6 -0
- package/cjs/services/merkl.js +39 -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/merkl.d.ts +8 -0
- package/esm/aaveV3/merkl.js +11 -10
- package/esm/aaveV4/merkl.js +5 -6
- 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 +72 -0
- package/esm/fluid/index.d.ts +1 -0
- package/esm/fluid/index.js +23 -7
- package/esm/fluid/merkl.d.ts +31 -0
- package/esm/fluid/merkl.js +93 -0
- package/esm/morphoBlue/index.d.ts +1 -0
- package/esm/morphoBlue/index.js +11 -9
- package/esm/morphoBlue/merkl.d.ts +35 -0
- package/esm/morphoBlue/merkl.js +96 -0
- package/esm/services/merkl.d.ts +6 -0
- package/esm/services/merkl.js +35 -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/merkl.d.ts +8 -0
- package/package.json +1 -1
- package/src/aaveV3/merkl.ts +7 -7
- package/src/aaveV4/merkl.ts +5 -5
- package/src/compoundV3/index.ts +6 -1
- package/src/compoundV3/merkl.ts +76 -0
- package/src/fluid/index.ts +29 -7
- package/src/fluid/merkl.ts +99 -0
- package/src/morphoBlue/index.ts +24 -9
- package/src/morphoBlue/merkl.ts +121 -0
- package/src/services/merkl.ts +31 -0
- package/src/staking/eligibility.ts +10 -0
- package/src/types/common.ts +1 -0
- package/src/types/merkl.ts +6 -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>;
|
package/esm/morphoBlue/index.js
CHANGED
|
@@ -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
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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()); });
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { MerklOpportunity } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Fetches every page matching the query. Merkl returns 20 opportunities by default, so omitting
|
|
4
|
+
* `items` and advancing `page` avoids imposing a client-side maximum on the result set.
|
|
5
|
+
*/
|
|
6
|
+
export declare const fetchAllMerklOpportunities: (query: Record<string, string>) => Promise<MerklOpportunity[]>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { LONGER_TIMEOUT } from './utils';
|
|
11
|
+
const MERKL_OPPORTUNITIES_URL = 'https://fe.defisaver.com/api/merkl/opportunities';
|
|
12
|
+
/**
|
|
13
|
+
* Fetches every page matching the query. Merkl returns 20 opportunities by default, so omitting
|
|
14
|
+
* `items` and advancing `page` avoids imposing a client-side maximum on the result set.
|
|
15
|
+
*/
|
|
16
|
+
export const fetchAllMerklOpportunities = (query) => __awaiter(void 0, void 0, void 0, function* () {
|
|
17
|
+
const opportunities = [];
|
|
18
|
+
let page = 0;
|
|
19
|
+
let pageOpportunities;
|
|
20
|
+
do {
|
|
21
|
+
const searchParams = new URLSearchParams(Object.assign(Object.assign({}, query), { page: page.toString() }));
|
|
22
|
+
const res = yield fetch(`${MERKL_OPPORTUNITIES_URL}?${searchParams}`, {
|
|
23
|
+
signal: AbortSignal.timeout(LONGER_TIMEOUT),
|
|
24
|
+
});
|
|
25
|
+
if (!res.ok)
|
|
26
|
+
throw new Error(`Failed to fetch Merkl opportunities page ${page}`);
|
|
27
|
+
const data = yield res.json(); // eslint-disable-line no-await-in-loop
|
|
28
|
+
if (!Array.isArray(data))
|
|
29
|
+
throw new Error(`Invalid Merkl opportunities response on page ${page}`);
|
|
30
|
+
pageOpportunities = data;
|
|
31
|
+
opportunities.push(...pageOpportunities);
|
|
32
|
+
page += 1;
|
|
33
|
+
} while (pageOpportunities.length > 0);
|
|
34
|
+
return opportunities;
|
|
35
|
+
});
|
|
@@ -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;
|
|
@@ -60,10 +60,19 @@ export const isEligibleForAaveV3ArbitrumETHLSBorrow = (usedAssets) => {
|
|
|
60
60
|
const ETHAmountBorrowed = ((_a = usedAssets.ETH) === null || _a === void 0 ? void 0 : _a.borrowedUsd) || '0';
|
|
61
61
|
return { isEligible: true, eligibleUSDAmount: ETHAmountBorrowed };
|
|
62
62
|
};
|
|
63
|
+
// "Borrow WETH with pufETH collateral" — rewards are calculated as min(collateral * 80% LTV, borrow amount)
|
|
64
|
+
export const isEligibleForCompoundV3PufEthWethBorrow = (usedAssets) => {
|
|
65
|
+
var _a, _b;
|
|
66
|
+
const collateralUsd = ((_a = usedAssets.pufETH) === null || _a === void 0 ? void 0 : _a.suppliedUsd) || '0';
|
|
67
|
+
const borrowedUsd = ((_b = usedAssets.ETH) === null || _b === void 0 ? void 0 : _b.borrowedUsd) || '0';
|
|
68
|
+
const eligibleUSDAmount = Dec.min(new Dec(collateralUsd).mul('0.8'), borrowedUsd).toString();
|
|
69
|
+
return { isEligible: new Dec(eligibleUSDAmount).gt(0), eligibleUSDAmount };
|
|
70
|
+
};
|
|
63
71
|
export const EligibilityMapping = {
|
|
64
72
|
[IncentiveEligibilityId.AaveV3EthenaLiquidLeverage]: isEligibleForEthenaUSDeRewards,
|
|
65
73
|
[IncentiveEligibilityId.AaveV3ArbitrumEthSupply]: isEligibleForAaveV3ArbitrumEthSupply,
|
|
66
74
|
[IncentiveEligibilityId.AaveV3ArbitrumETHLSBorrow]: isEligibleForAaveV3ArbitrumETHLSBorrow,
|
|
67
75
|
[IncentiveEligibilityId.AaveV3EthenaLiquidLeveragePlasma]: isEligibleForEthenaUSDeRewards,
|
|
68
76
|
[IncentiveEligibilityId.AaveV3EthenaLiquidLeveragePlasmaGHO]: isEligibleForEthenaGHORewards,
|
|
77
|
+
[IncentiveEligibilityId.CompoundV3PufEthWethBorrow]: isEligibleForCompoundV3PufEthWethBorrow,
|
|
69
78
|
};
|
package/esm/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/esm/types/common.js
CHANGED
|
@@ -15,6 +15,7 @@ export var IncentiveEligibilityId;
|
|
|
15
15
|
IncentiveEligibilityId["AaveV3ArbitrumETHLSBorrow"] = "0x0c84331e39d6658Cd6e6b9ba04736cC4c4734351";
|
|
16
16
|
IncentiveEligibilityId["AaveV3EthenaLiquidLeveragePlasma"] = "0x2E8f1FA9c73F9d975B46BDFe40C92b6dDEFA3f31BORROW_BL";
|
|
17
17
|
IncentiveEligibilityId["AaveV3EthenaLiquidLeveragePlasmaGHO"] = "2b2f6fefcd3df634";
|
|
18
|
+
IncentiveEligibilityId["CompoundV3PufEthWethBorrow"] = "e6e49b79e83041f9";
|
|
18
19
|
})(IncentiveEligibilityId || (IncentiveEligibilityId = {}));
|
|
19
20
|
export var LeverageType;
|
|
20
21
|
(function (LeverageType) {
|
package/esm/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/package.json
CHANGED
package/src/aaveV3/merkl.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { aprToApy } from '../moneymarket';
|
|
2
|
-
import {
|
|
2
|
+
import { fetchAllMerklOpportunities } from '../services/merkl';
|
|
3
|
+
import { wethToEth } from '../services/utils';
|
|
3
4
|
import {
|
|
4
5
|
MerkleRewardMap, MerklOpportunity, OpportunityAction, OpportunityStatus,
|
|
5
6
|
EthAddress, NetworkNumber,
|
|
@@ -31,16 +32,16 @@ export const formatAaveAsset = (_symbol: string) => {
|
|
|
31
32
|
|
|
32
33
|
export const getMerkleCampaigns = async (chainId: NetworkNumber): Promise<MerkleRewardMap> => {
|
|
33
34
|
try {
|
|
34
|
-
const
|
|
35
|
-
|
|
35
|
+
const opportunities = await fetchAllMerklOpportunities({
|
|
36
|
+
mainProtocolId: 'aave',
|
|
37
|
+
status: OpportunityStatus.LIVE,
|
|
36
38
|
});
|
|
37
|
-
if (!res.ok) throw new Error('Failed to fetch Merkle campaigns');
|
|
38
|
-
const opportunities = await res.json() as MerklOpportunity[];
|
|
39
39
|
const relevantOpportunities = opportunities
|
|
40
40
|
.filter((o: MerklOpportunity) => o.chainId === chainId)
|
|
41
41
|
.filter((o: MerklOpportunity) => o.status === OpportunityStatus.LIVE);
|
|
42
42
|
return relevantOpportunities.reduce((acc, opportunity) => {
|
|
43
|
-
const rewardToken = opportunity.rewardsRecord
|
|
43
|
+
const rewardToken = opportunity.rewardsRecord?.breakdowns?.[0]?.token;
|
|
44
|
+
if (!rewardToken) return acc;
|
|
44
45
|
const description = `Eligible for ${formatAaveAsset(rewardToken.symbol)} rewards through Merkl. ${opportunity.description ? `\n${opportunity.description}` : ''}`;
|
|
45
46
|
if (opportunity.action === OpportunityAction.LEND && opportunity.explorerAddress) {
|
|
46
47
|
const supplyAToken = opportunity.explorerAddress?.toLowerCase() as EthAddress;
|
|
@@ -71,4 +72,3 @@ export const getMerkleCampaigns = async (chainId: NetworkNumber): Promise<Merkle
|
|
|
71
72
|
return {};
|
|
72
73
|
}
|
|
73
74
|
};
|
|
74
|
-
|
package/src/aaveV4/merkl.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { aprToApy } from '../moneymarket';
|
|
2
|
-
import {
|
|
2
|
+
import { fetchAllMerklOpportunities } from '../services/merkl';
|
|
3
3
|
import {
|
|
4
4
|
AaveV4MerklRewardMap,
|
|
5
5
|
AaveV4ReserveAssetData,
|
|
@@ -67,11 +67,11 @@ export const buildAaveV4MerklRewardMap = (opportunities: MerklOpportunity[], cha
|
|
|
67
67
|
|
|
68
68
|
export const getAaveV4MerkleCampaigns = async (chainId: NetworkNumber): Promise<AaveV4MerklRewardMap> => {
|
|
69
69
|
try {
|
|
70
|
-
const
|
|
71
|
-
|
|
70
|
+
const opportunities = await fetchAllMerklOpportunities({
|
|
71
|
+
mainProtocolId: 'aave',
|
|
72
|
+
type: 'AAVE_V4_HUB_SUPPLY,AAVE_V4_HUB_BORROW,AAVE_V4_SPOKE_SUPPLY,AAVE_V4_SPOKE_BORROW',
|
|
73
|
+
status: OpportunityStatus.LIVE,
|
|
72
74
|
});
|
|
73
|
-
if (!res.ok) throw new Error('Failed to fetch Aave V4 Merkle campaigns');
|
|
74
|
-
const opportunities = await res.json() as MerklOpportunity[];
|
|
75
75
|
return buildAaveV4MerklRewardMap(opportunities, chainId);
|
|
76
76
|
} catch (e) {
|
|
77
77
|
console.error('Failed to fetch Aave V4 Merkle campaigns', e);
|
package/src/compoundV3/index.ts
CHANGED
|
@@ -24,6 +24,9 @@ import {
|
|
|
24
24
|
getEthPrice, getCompPrice, getUSDCPrice, getWstETHPrice,
|
|
25
25
|
} from '../services/priceService';
|
|
26
26
|
import { getViemProvider, setViemBlockNumber } from '../services/viem';
|
|
27
|
+
import { attachCompoundV3MerklIncentives, getCompoundV3MerklOpportunities } from './merkl';
|
|
28
|
+
|
|
29
|
+
export { getCompoundV3MerklOpportunities, attachCompoundV3MerklIncentives } from './merkl';
|
|
27
30
|
|
|
28
31
|
const getSupportedAssetsAddressesForMarket = (selectedMarket: CompoundMarketData, network: NetworkNumber) => selectedMarket.collAssets.map(asset => getAssetInfo(ethToWeth(asset), network)).map(addr => addr.address.toLowerCase());
|
|
29
32
|
|
|
@@ -41,12 +44,13 @@ const getBaseAssetPriceFunction = (asset: string) => {
|
|
|
41
44
|
export const _getCompoundV3MarketsData = async (provider: Client, network: NetworkNumber, selectedMarket: CompoundMarketData, defaultProvider: Client): Promise<CompoundV3MarketsData> => {
|
|
42
45
|
const contract = CompV3ViewContractViem(provider, network);
|
|
43
46
|
|
|
44
|
-
const [baseAssetPrice, compPrice, baseTokenInfo, collInfos, govInfo] = await Promise.all([
|
|
47
|
+
const [baseAssetPrice, compPrice, baseTokenInfo, collInfos, govInfo, merklOpportunities] = await Promise.all([
|
|
45
48
|
getBaseAssetPriceFunction(selectedMarket.baseAsset)(defaultProvider),
|
|
46
49
|
getCompPrice(defaultProvider),
|
|
47
50
|
contract.read.getFullBaseTokenInfo([selectedMarket.baseMarketAddress]),
|
|
48
51
|
contract.read.getFullCollInfos([selectedMarket.baseMarketAddress]),
|
|
49
52
|
contract.read.getGovernanceInfoFull([selectedMarket.baseMarketAddress]),
|
|
53
|
+
getCompoundV3MerklOpportunities(),
|
|
50
54
|
]);
|
|
51
55
|
|
|
52
56
|
const { isSupplyPaused, isWithdrawPaused } = govInfo;
|
|
@@ -72,6 +76,7 @@ export const _getCompoundV3MarketsData = async (provider: Client, network: Netwo
|
|
|
72
76
|
const payload: CompoundV3AssetsData = {};
|
|
73
77
|
|
|
74
78
|
const baseObj = { ...base, ...(await getIncentiveApys(base, compPrice)) };
|
|
79
|
+
attachCompoundV3MerklIncentives(baseObj, colls, selectedMarket.baseMarketAddress, network, merklOpportunities);
|
|
75
80
|
const allAssets = [baseObj, ...colls];
|
|
76
81
|
|
|
77
82
|
allAssets
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { getAssetInfoByAddress } from '@defisaver/tokens';
|
|
2
|
+
import { aprToApy } from '../moneymarket';
|
|
3
|
+
import { fetchAllMerklOpportunities } from '../services/merkl';
|
|
4
|
+
import { compareAddresses, wethToEth } from '../services/utils';
|
|
5
|
+
import {
|
|
6
|
+
CompoundV3AssetData,
|
|
7
|
+
IncentiveData,
|
|
8
|
+
IncentiveEligibilityId,
|
|
9
|
+
IncentiveKind,
|
|
10
|
+
MerklOpportunity,
|
|
11
|
+
NetworkNumber,
|
|
12
|
+
OpportunityAction,
|
|
13
|
+
OpportunityStatus,
|
|
14
|
+
} from '../types';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Merkl tags Compound V3 reward campaigns with COMPOUND_V3_* opportunity types (e.g.
|
|
18
|
+
* COMPOUND_V3_BORROW_FROM_COLLATERAL) and identifies the comet via `explorerAddress`. Campaign
|
|
19
|
+
* identifiers are opaque hashes, passed through as `eligibilityId` so conditional campaigns —
|
|
20
|
+
* like borrow-from-collateral ones, whose reward is min(collateral * LTV, borrow amount) — can
|
|
21
|
+
* be gated and sized by a curated check in EligibilityMapping; unknown ids apply
|
|
22
|
+
* unconditionally, mirroring Aave V3.
|
|
23
|
+
*/
|
|
24
|
+
export const getCompoundV3MerklOpportunities = async (): Promise<MerklOpportunity[]> => {
|
|
25
|
+
try {
|
|
26
|
+
return await fetchAllMerklOpportunities({
|
|
27
|
+
mainProtocolId: 'compound-v3',
|
|
28
|
+
status: OpportunityStatus.LIVE,
|
|
29
|
+
});
|
|
30
|
+
} catch (e) {
|
|
31
|
+
console.error('Failed to fetch Compound V3 Merkl campaigns', e);
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const buildMerklIncentive = (opportunity: MerklOpportunity): IncentiveData => {
|
|
37
|
+
const token = opportunity.rewardsRecord?.breakdowns?.[0]?.token?.symbol || opportunity.tokens?.[0]?.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
|
+
eligibilityId: opportunity.identifier as IncentiveEligibilityId,
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Appends the comet's Merkl campaigns to its assets' incentive arrays (mutating them, like the
|
|
49
|
+
* staking incentives are attached). BORROW campaigns land on the base asset — the only borrowable
|
|
50
|
+
* one — while LEND campaigns land on the asset the campaign's first token resolves to.
|
|
51
|
+
*/
|
|
52
|
+
export const attachCompoundV3MerklIncentives = (
|
|
53
|
+
baseAsset: CompoundV3AssetData,
|
|
54
|
+
collAssets: CompoundV3AssetData[],
|
|
55
|
+
baseMarketAddress: string,
|
|
56
|
+
chainId: NetworkNumber,
|
|
57
|
+
opportunities: MerklOpportunity[],
|
|
58
|
+
): void => {
|
|
59
|
+
opportunities
|
|
60
|
+
.filter((o) => o.chainId === chainId
|
|
61
|
+
&& o.status === OpportunityStatus.LIVE
|
|
62
|
+
&& typeof o.type === 'string' && o.type.startsWith('COMPOUND_V3')
|
|
63
|
+
&& o.explorerAddress && compareAddresses(o.explorerAddress, baseMarketAddress))
|
|
64
|
+
.forEach((o) => {
|
|
65
|
+
const incentive = buildMerklIncentive(o);
|
|
66
|
+
if (o.action === OpportunityAction.BORROW) {
|
|
67
|
+
baseAsset.borrowIncentives.push(incentive);
|
|
68
|
+
} else if (o.action === OpportunityAction.LEND) {
|
|
69
|
+
const campaignTokenAddress = o.tokens?.[0]?.address;
|
|
70
|
+
if (!campaignTokenAddress) return;
|
|
71
|
+
const campaignSymbol = wethToEth(getAssetInfoByAddress(campaignTokenAddress, chainId).symbol);
|
|
72
|
+
const target = [baseAsset, ...collAssets].find((asset) => asset.symbol === campaignSymbol);
|
|
73
|
+
target?.supplyIncentives.push(incentive);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
};
|
package/src/fluid/index.ts
CHANGED
|
@@ -66,6 +66,14 @@ import {
|
|
|
66
66
|
} from '../services/priceService';
|
|
67
67
|
import { getStakingApy, STAKING_ASSETS } from '../staking';
|
|
68
68
|
import { getViemProvider } from '../services/viem';
|
|
69
|
+
import { attachFluidMerklIncentives, getFluidMerklCampaigns } from './merkl';
|
|
70
|
+
|
|
71
|
+
export {
|
|
72
|
+
FluidMerklOpportunityType,
|
|
73
|
+
buildFluidMerklRewardMap,
|
|
74
|
+
getFluidMerklCampaigns,
|
|
75
|
+
attachFluidMerklIncentives,
|
|
76
|
+
} from './merkl';
|
|
69
77
|
|
|
70
78
|
export const EMPTY_USED_ASSET = {
|
|
71
79
|
isSupplied: false,
|
|
@@ -1442,9 +1450,13 @@ const parseUserData = (userPositionData: FluidUserPositionStructOutputStruct, va
|
|
|
1442
1450
|
export const _getFluidMarketData = async (provider: PublicClient, network: NetworkNumber, market: FluidMarketInfo) => {
|
|
1443
1451
|
const view = FluidViewContractViem(provider, network);
|
|
1444
1452
|
|
|
1445
|
-
const data = await
|
|
1453
|
+
const [data, merklCampaigns] = await Promise.all([
|
|
1454
|
+
view.read.getVaultData([market.marketAddress]),
|
|
1455
|
+
getFluidMerklCampaigns(network),
|
|
1456
|
+
]);
|
|
1446
1457
|
|
|
1447
|
-
|
|
1458
|
+
const marketData = await parseMarketData(provider, data, network);
|
|
1459
|
+
return marketData ? attachFluidMerklIncentives(marketData, merklCampaigns) : marketData;
|
|
1448
1460
|
};
|
|
1449
1461
|
|
|
1450
1462
|
export const getFluidMarketData = async (
|
|
@@ -1500,11 +1512,15 @@ export const getFluidPosition = async (
|
|
|
1500
1512
|
|
|
1501
1513
|
export const _getFluidPositionWithMarket = async (provider: PublicClient, network: NetworkNumber, vaultId: string) => {
|
|
1502
1514
|
const view = FluidViewContractViem(provider, network);
|
|
1503
|
-
const data = await
|
|
1504
|
-
|
|
1505
|
-
|
|
1515
|
+
const [data, merklCampaigns] = await Promise.all([
|
|
1516
|
+
view.read.getPositionByNftId([BigInt(vaultId)]),
|
|
1517
|
+
getFluidMerklCampaigns(network),
|
|
1518
|
+
]);
|
|
1519
|
+
const parsedMarketData = await parseMarketData(provider, data[1], network);
|
|
1520
|
+
if (!parsedMarketData) {
|
|
1506
1521
|
return;
|
|
1507
1522
|
}
|
|
1523
|
+
const marketData = attachFluidMerklIncentives(parsedMarketData, merklCampaigns);
|
|
1508
1524
|
const userData = parseUserData(data[0], marketData);
|
|
1509
1525
|
|
|
1510
1526
|
return {
|
|
@@ -1522,8 +1538,14 @@ export const getFluidPositionWithMarket = async (
|
|
|
1522
1538
|
export const _getAllFluidMarketDataChunked = async (network: NetworkNumber, provider: PublicClient) => {
|
|
1523
1539
|
const versions = getFluidVersionsDataForNetwork(network);
|
|
1524
1540
|
const view = FluidViewContractViem(provider, network);
|
|
1525
|
-
const data = await Promise.all(
|
|
1526
|
-
|
|
1541
|
+
const [data, merklCampaigns] = await Promise.all([
|
|
1542
|
+
Promise.all(versions.map((version) => view.read.getVaultData([version.marketAddress]))),
|
|
1543
|
+
getFluidMerklCampaigns(network),
|
|
1544
|
+
]);
|
|
1545
|
+
return Promise.all(data.map(async (item) => {
|
|
1546
|
+
const marketData = await parseMarketData(provider, item, network);
|
|
1547
|
+
return marketData ? attachFluidMerklIncentives(marketData, merklCampaigns) : marketData;
|
|
1548
|
+
}));
|
|
1527
1549
|
};
|
|
1528
1550
|
|
|
1529
1551
|
export const getAllFluidMarketDataChunked = async (
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { aprToApy } from '../moneymarket';
|
|
2
|
+
import { fetchAllMerklOpportunities } from '../services/merkl';
|
|
3
|
+
import {
|
|
4
|
+
FluidMarketData,
|
|
5
|
+
FluidMerklRewardMap,
|
|
6
|
+
IncentiveData,
|
|
7
|
+
IncentiveEligibilityId,
|
|
8
|
+
IncentiveKind,
|
|
9
|
+
MerklOpportunity,
|
|
10
|
+
NetworkNumber,
|
|
11
|
+
OpportunityStatus,
|
|
12
|
+
} from '../types';
|
|
13
|
+
|
|
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 enum FluidMerklOpportunityType {
|
|
29
|
+
Collateral = 'FLUIDVAULT_COLLATERAL',
|
|
30
|
+
Borrow = 'FLUIDVAULT_BORROW',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// '0x' + 20-byte vault address; conditional campaigns append a marker after it
|
|
34
|
+
const VAULT_ADDRESS_LENGTH = 42;
|
|
35
|
+
|
|
36
|
+
const buildMerklIncentive = (opportunity: MerklOpportunity): IncentiveData => {
|
|
37
|
+
const token = opportunity.rewardsRecord?.breakdowns?.[0]?.token?.symbol || opportunity.tokens?.[0]?.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
|
+
eligibilityId: opportunity.identifier as IncentiveEligibilityId,
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const buildFluidMerklRewardMap = (opportunities: MerklOpportunity[], chainId: NetworkNumber): FluidMerklRewardMap => {
|
|
48
|
+
const result: FluidMerklRewardMap = {};
|
|
49
|
+
|
|
50
|
+
opportunities
|
|
51
|
+
.filter((o) => o.chainId === chainId)
|
|
52
|
+
.filter((o) => o.status === OpportunityStatus.LIVE)
|
|
53
|
+
.filter((o) => Object.values(FluidMerklOpportunityType).includes(o.type as FluidMerklOpportunityType))
|
|
54
|
+
.filter((o) => !o.identifier?.includes('WHITELIST'))
|
|
55
|
+
.forEach((o) => {
|
|
56
|
+
const vaultAddress = o.identifier?.slice(0, VAULT_ADDRESS_LENGTH).toLowerCase();
|
|
57
|
+
if (!vaultAddress || vaultAddress.length !== VAULT_ADDRESS_LENGTH) return;
|
|
58
|
+
if (!result[vaultAddress]) result[vaultAddress] = { supply: [], borrow: [] };
|
|
59
|
+
result[vaultAddress][o.type === FluidMerklOpportunityType.Borrow ? 'borrow' : 'supply'].push(buildMerklIncentive(o));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return result;
|
|
63
|
+
};
|
|
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 = async (chainId: NetworkNumber): Promise<FluidMerklRewardMap> => {
|
|
70
|
+
try {
|
|
71
|
+
const opportunities = await fetchAllMerklOpportunities({
|
|
72
|
+
mainProtocolId: 'fluid',
|
|
73
|
+
type: Object.values(FluidMerklOpportunityType).join(','),
|
|
74
|
+
status: OpportunityStatus.LIVE,
|
|
75
|
+
});
|
|
76
|
+
return buildFluidMerklRewardMap(opportunities, chainId);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.error('Failed to fetch Fluid Merkl campaigns', e);
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
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: FluidMarketData, campaigns: FluidMerklRewardMap): FluidMarketData => {
|
|
89
|
+
const vaultCampaigns = campaigns[marketData.marketData.marketAddress?.toLowerCase()];
|
|
90
|
+
if (!vaultCampaigns || (!vaultCampaigns.supply.length && !vaultCampaigns.borrow.length)) return marketData;
|
|
91
|
+
|
|
92
|
+
const assetsData = Object.fromEntries(Object.entries(marketData.assetsData).map(([symbol, asset]) => [symbol, {
|
|
93
|
+
...asset,
|
|
94
|
+
supplyIncentives: asset.canBeSupplied ? [...asset.supplyIncentives, ...vaultCampaigns.supply] : asset.supplyIncentives,
|
|
95
|
+
borrowIncentives: asset.canBeBorrowed ? [...asset.borrowIncentives, ...vaultCampaigns.borrow] : asset.borrowIncentives,
|
|
96
|
+
}]));
|
|
97
|
+
|
|
98
|
+
return { ...marketData, assetsData };
|
|
99
|
+
};
|