Skip to content

Commit 6c97df6

Browse files
authored
feat: added support for multi incentives campaigns for a token (#2728)
1 parent 2035dd2 commit 6c97df6

File tree

4 files changed

+155
-58
lines changed

4 files changed

+155
-58
lines changed

public/icons/tokens/usdtb.svg

Lines changed: 1 addition & 1 deletion
Loading

src/components/incentives/MerklIncentivesTooltipContent.tsx

Lines changed: 105 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,22 @@ export const MerklIncentivesTooltipContent = ({
7979
Learn more
8080
</Link>
8181
</Typography>
82-
) : null}
82+
) : (
83+
<Typography variant="caption" color="text.strong" mb={3}>
84+
<Trans>{merklIncentives.description}</Trans>{' '}
85+
<Link
86+
href={
87+
merklIncentives.customForumLink
88+
? merklIncentives.customForumLink
89+
: 'https://governance.aave.com/t/arfc-set-aci-as-emission-manager-for-liquidity-mining-programs/17898'
90+
}
91+
sx={{ textDecoration: 'underline' }}
92+
variant="caption"
93+
>
94+
Learn more
95+
</Link>
96+
</Typography>
97+
)}
8398

8499
<Box sx={{ width: '100%' }}>
85100
{merklIncentives.breakdown ? (
@@ -130,48 +145,98 @@ export const MerklIncentivesTooltipContent = ({
130145
</Row>
131146
)}
132147

133-
{/* Merit Incentives */}
134-
<Row
135-
height={32}
136-
caption={
137-
<Box
138-
sx={{
139-
display: 'flex',
140-
alignItems: 'center',
141-
mb: 0,
142-
}}
143-
>
144-
<TokenIcon
145-
aToken={merklIncentivesFormatted.aToken}
146-
symbol={merklIncentivesFormatted.tokenIconSymbol}
147-
sx={{ fontSize: '20px', mr: 1 }}
148+
{/* Merkl Incentives */}
149+
{merklIncentives.allOpportunities && merklIncentives.allOpportunities.length > 1 ? (
150+
<>
151+
{merklIncentives.allOpportunities.map((opportunity, index) => {
152+
const { tokenIconSymbol, symbol, aToken } = getSymbolMap({
153+
rewardTokenSymbol: opportunity.rewardToken.symbol,
154+
rewardTokenAddress: opportunity.rewardToken.address,
155+
incentiveAPR: opportunity.apy.toString(),
156+
});
157+
return (
158+
<Row
159+
key={index}
160+
height={32}
161+
caption={
162+
<Box
163+
sx={{
164+
display: 'flex',
165+
alignItems: 'center',
166+
mb: 0,
167+
}}
168+
>
169+
<TokenIcon
170+
symbol={tokenIconSymbol}
171+
aToken={aToken}
172+
sx={{ fontSize: '20px', mr: 1 }}
173+
/>
174+
<Typography variant={typographyVariant}>{symbol}</Typography>
175+
<Typography variant={typographyVariant} sx={{ ml: 0.5 }}>
176+
{merklIncentives.breakdown.isBorrow ? '(-)' : '(+)'}
177+
</Typography>
178+
</Box>
179+
}
180+
width="100%"
181+
>
182+
<Box sx={{ display: 'inline-flex', alignItems: 'center' }}>
183+
<FormattedNumber
184+
value={
185+
merklIncentives.breakdown.isBorrow ? -opportunity.apy : opportunity.apy
186+
}
187+
percent
188+
variant={typographyVariant}
189+
/>
190+
<Typography variant={typographyVariant} sx={{ ml: 1 }}>
191+
<Trans>APY</Trans>
192+
</Typography>
193+
</Box>
194+
</Row>
195+
);
196+
})}
197+
</>
198+
) : (
199+
<Row
200+
height={32}
201+
caption={
202+
<Box
203+
sx={{
204+
display: 'flex',
205+
alignItems: 'center',
206+
mb: 0,
207+
}}
208+
>
209+
<TokenIcon
210+
aToken={merklIncentivesFormatted.aToken}
211+
symbol={merklIncentivesFormatted.tokenIconSymbol}
212+
sx={{ fontSize: '20px', mr: 1 }}
213+
/>
214+
<Typography variant={typographyVariant}>
215+
{merklIncentivesFormatted.symbol}
216+
</Typography>
217+
<Typography variant={typographyVariant} sx={{ ml: 0.5 }}>
218+
{merklIncentives.breakdown.isBorrow ? '(-)' : '(+)'}
219+
</Typography>
220+
</Box>
221+
}
222+
width="100%"
223+
>
224+
<Box sx={{ display: 'inline-flex', alignItems: 'center' }}>
225+
<FormattedNumber
226+
value={
227+
merklIncentives.breakdown.isBorrow
228+
? -merklIncentives.breakdown.merklIncentivesAPR
229+
: merklIncentives.breakdown.merklIncentivesAPR
230+
}
231+
percent
232+
variant={typographyVariant}
148233
/>
149-
<Typography variant={typographyVariant}>
150-
{merklIncentivesFormatted.symbol}
151-
</Typography>
152-
<Typography variant={typographyVariant} sx={{ ml: 0.5 }}>
153-
{merklIncentives.breakdown.isBorrow ? '(-)' : '(+)'}
234+
<Typography variant={typographyVariant} sx={{ ml: 1 }}>
235+
<Trans>APY</Trans>
154236
</Typography>
155237
</Box>
156-
}
157-
width="100%"
158-
>
159-
<Box sx={{ display: 'inline-flex', alignItems: 'center' }}>
160-
<FormattedNumber
161-
value={
162-
merklIncentives.breakdown.isBorrow
163-
? -merklIncentives.breakdown.merklIncentivesAPR
164-
: merklIncentives.breakdown.merklIncentivesAPR
165-
}
166-
percent
167-
variant={typographyVariant}
168-
/>
169-
<Typography variant={typographyVariant} sx={{ ml: 1 }}>
170-
<Trans>APY</Trans>
171-
</Typography>
172-
</Box>
173-
</Row>
174-
238+
</Row>
239+
)}
175240
{/* Total APY */}
176241
<Box sx={{ mt: 2, pt: 2, borderTop: 1, borderColor: 'divider' }}>
177242
<Row

src/hooks/useMerklIncentives.ts

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ enum OpportunityStatus {
1919
type MerklOpportunity = {
2020
chainId: number;
2121
type: string;
22+
description?: string;
2223
identifier: Address;
2324
name: string;
2425
status: OpportunityStatus;
@@ -80,6 +81,17 @@ type ReserveIncentiveAdditionalData = {
8081
export type ExtendedReserveIncentiveResponse = ReserveIncentiveResponse &
8182
ReserveIncentiveAdditionalData & {
8283
breakdown: MerklIncentivesBreakdown;
84+
description?: string;
85+
allOpportunities?: {
86+
name: string;
87+
apy: number;
88+
rewardToken: {
89+
address: string;
90+
symbol: string;
91+
icon: string;
92+
price: number;
93+
};
94+
}[];
8395
};
8496

8597
export type MerklIncentivesBreakdown = {
@@ -98,7 +110,7 @@ type WhitelistApiResponse = {
98110
whitelistedRewardTokens: string[];
99111
additionalIncentiveInfo: Record<string, ReserveIncentiveAdditionalData>;
100112
};
101-
113+
const addressETHFI = '0xFe0c30065B384F05761f15d0CC899D4F9F9Cc0eB';
102114
const MERKL_ENDPOINT = 'https://api.merkl.xyz/v4/opportunities?mainProtocolId=aave'; // Merkl API
103115
const WHITELIST_ENDPOINT = 'https://apps.aavechan.com/api/aave/merkl/whitelist-token-list'; // Endpoint to fetch whitelisted tokens
104116
const checkOpportunityAction = (
@@ -121,7 +133,14 @@ const useWhitelistedTokens = () => {
121133
if (!response.ok) {
122134
throw new Error('Failed to fetch whitelisted tokens');
123135
}
124-
return response.json();
136+
const data = await response.json();
137+
138+
// TODO: Remove hardcoded addition once we have ETHFI in the whitelist API
139+
if (!data.whitelistedRewardTokens.includes(addressETHFI.toLowerCase())) {
140+
data.whitelistedRewardTokens.push(addressETHFI.toLowerCase());
141+
}
142+
143+
return data;
125144
},
126145
queryKey: ['whitelistedTokens'],
127146
staleTime: 1000 * 60 * 5, // 5 minutes
@@ -148,6 +167,7 @@ export const useMerklIncentives = ({
148167
queryFn: async () => {
149168
const response = await fetch(`${MERKL_ENDPOINT}`);
150169
const merklOpportunities: MerklOpportunity[] = await response.json();
170+
151171
return merklOpportunities;
152172
},
153173
queryKey: ['merklIncentives', market],
@@ -167,20 +187,9 @@ export const useMerklIncentives = ({
167187
return null;
168188
}
169189

170-
const opportunity = opportunities[0];
171-
172-
if (opportunity.status !== OpportunityStatus.LIVE) {
173-
return null;
174-
}
175-
176-
if (opportunity.apr <= 0) {
177-
return null;
178-
}
179-
180-
const merklIncentivesAPR = opportunity.apr / 100;
181-
const merklIncentivesAPY = convertAprToApy(merklIncentivesAPR);
182-
183-
const rewardToken = opportunity.rewardsRecord.breakdowns[0].token;
190+
const validOpportunities = opportunities.filter(
191+
(opp) => opp.status === OpportunityStatus.LIVE && opp.apr > 0
192+
);
184193

185194
if (!whitelistData?.whitelistedRewardTokens) {
186195
return null;
@@ -190,10 +199,25 @@ export const useMerklIncentives = ({
190199
whitelistData.whitelistedRewardTokens.map((token) => token.toLowerCase())
191200
);
192201

193-
if (!whitelistedTokensSet.has(rewardToken.address.toLowerCase())) {
202+
const whitelistedOpportunities = validOpportunities.filter((opp) => {
203+
const rewardToken = opp.rewardsRecord.breakdowns[0]?.token;
204+
return rewardToken && whitelistedTokensSet.has(rewardToken.address.toLowerCase());
205+
});
206+
207+
if (whitelistedOpportunities.length === 0) {
194208
return null;
195209
}
196210

211+
const totalMerklAPR = whitelistedOpportunities.reduce((sum, opp) => {
212+
return sum + opp.apr / 100;
213+
}, 0);
214+
215+
const merklIncentivesAPY = convertAprToApy(totalMerklAPR);
216+
217+
const primaryOpportunity = whitelistedOpportunities[0];
218+
const rewardToken = primaryOpportunity.rewardsRecord.breakdowns[0].token;
219+
const description = primaryOpportunity.description;
220+
197221
const protocolIncentivesAPR = protocolIncentives.reduce((sum, inc) => {
198222
return sum + (inc.incentiveAPR === 'Infinity' ? 0 : +inc.incentiveAPR);
199223
}, 0);
@@ -210,7 +234,13 @@ export const useMerklIncentives = ({
210234
incentiveAPR: merklIncentivesAPY.toString(),
211235
rewardTokenAddress: rewardToken.address,
212236
rewardTokenSymbol: rewardToken.symbol,
237+
description: description,
213238
...incentiveAdditionalData,
239+
allOpportunities: whitelistedOpportunities.map((opp) => ({
240+
name: opp.name,
241+
apy: convertAprToApy(opp.apr / 100),
242+
rewardToken: opp.rewardsRecord.breakdowns[0].token,
243+
})),
214244
breakdown: {
215245
protocolAPY,
216246
protocolIncentivesAPR,

src/locales/en/messages.po

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,6 +1233,7 @@ msgstr "deposited"
12331233
#: src/components/incentives/MeritIncentivesTooltipContent.tsx
12341234
#: src/components/incentives/MerklIncentivesTooltipContent.tsx
12351235
#: src/components/incentives/MerklIncentivesTooltipContent.tsx
1236+
#: src/components/incentives/MerklIncentivesTooltipContent.tsx
12361237
#: src/components/MarketSwitcher.tsx
12371238
#: src/components/transactions/DelegationTxsWrapper.tsx
12381239
#: src/components/transactions/DelegationTxsWrapper.tsx
@@ -3434,6 +3435,7 @@ msgstr "Selected supply assets"
34343435
#: src/components/incentives/MerklIncentivesTooltipContent.tsx
34353436
#: src/components/incentives/MerklIncentivesTooltipContent.tsx
34363437
#: src/components/incentives/MerklIncentivesTooltipContent.tsx
3438+
#: src/components/incentives/MerklIncentivesTooltipContent.tsx
34373439
#: src/modules/dashboard/lists/BorrowedPositionsList/BorrowedPositionsList.tsx
34383440
#: src/modules/dashboard/lists/BorrowedPositionsList/BorrowedPositionsList.tsx
34393441
#: src/modules/dashboard/lists/BorrowedPositionsList/BorrowedPositionsListItem.tsx

0 commit comments

Comments
 (0)