Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c28d657
feat(incentives): centralize all reward sources via aave-v3-backend
mgrabina Apr 23, 2026
8bcafb0
fix(incentives): align hook inputs + harden react-query data shapes
mgrabina Apr 24, 2026
9e5ff90
fix(incentives): address codex review feedback
mgrabina Apr 24, 2026
5ffad30
Merge branch 'main' into SDK-779-interface-incentives
mgrabina Apr 24, 2026
2b35623
fix: payout token not displayed
AGMASO Apr 27, 2026
2eed8cf
fix: merit payouttoken not displayed and customMessages not displayed
AGMASO Apr 27, 2026
65c8d2d
fix: poitns campaigns not displaying points and messages
AGMASO Apr 27, 2026
6bb1cbb
fix: gho displaying wrong apys in for different protocol actions
AGMASO Apr 27, 2026
b308608
fix: missing symbol token mapping
AGMASO Apr 27, 2026
202a8e9
fix(incentives): consume Ethena/EtherFi/Sonic as SupplyPointsIncentive
mgrabina Apr 27, 2026
f1f0d1d
fix(incentives): rename Merkl extraApy/discountApy to *Apr to match v…
mgrabina Apr 28, 2026
165bffd
feat: refactored to use new endpoint SavingsGhoIncentive (#2963)
AGMASO May 6, 2026
34d4227
Merge branch 'main' into SDK-779-interface-incentives
AGMASO May 7, 2026
3eec2b4
fix: sghoincentives using sdk react hook & add PartnerIncentive to hook
AGMASO May 7, 2026
2453760
fix: revert the migration to new hook sdk because sdk not in place yet
AGMASO May 7, 2026
363763d
fix(incentives): switch Partner types to PointsProgramKind discriminator
mgrabina May 28, 2026
e9b3223
Merge remote-tracking branch 'origin/main' into SDK-779-interface-inc…
mgrabina May 28, 2026
d92557f
chore(i18n): refresh message catalog after main merge
mgrabina May 28, 2026
4dd1ddc
fix: needed adjustments for points campaigns (#3000)
AGMASO May 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 70 additions & 12 deletions src/components/incentives/IncentivesButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ import { Box, Typography } from '@mui/material';
import { useState } from 'react';
import { useEthenaIncentives } from 'src/hooks/useEthenaIncentives';
import { useEtherfiIncentives } from 'src/hooks/useEtherfiIncentives';
import { useMeritIncentives } from 'src/hooks/useMeritIncentives';
import {
ExtendedReserveIncentiveResponse as MeritReserveIncentiveResponse,
MeritAction,
useMeritIncentives,
} from 'src/hooks/useMeritIncentives';
import { ExtendedReserveIncentiveResponse, useMerklIncentives } from 'src/hooks/useMerklIncentives';
import { useMerklPointsIncentives } from 'src/hooks/useMerklPointsIncentives';
import { useSavingsGhoIncentive } from 'src/hooks/useSavingsGhoIncentive';
import { useSonicIncentives } from 'src/hooks/useSonicIncentives';
import { useRootStore } from 'src/store/root';
import { DASHBOARD } from 'src/utils/events';
Expand Down Expand Up @@ -128,23 +133,23 @@ const BlankIncentives = () => {
);
};

export const MeritIncentivesButton = (params: {
type MeritIncentivesButtonParams = {
symbol: string;
market: string;
protocolAction?: ProtocolAction;
protocolAPY?: number;
protocolIncentives?: ReserveIncentiveResponse[];
hideValue?: boolean;
};

const MeritIncentivesButtonContent = ({
meritIncentives,
hideValue,
}: {
meritIncentives: MeritReserveIncentiveResponse;
hideValue?: boolean;
}) => {
const [open, setOpen] = useState(false);
const { data: meritIncentives } = useMeritIncentives(params);

if (!meritIncentives) {
return null;
}

// Show only merit incentives APR
const displayValue = +meritIncentives.incentiveAPR;

return (
<ContentWithTooltip
Expand All @@ -160,13 +165,66 @@ export const MeritIncentivesButton = (params: {
>
<Content
incentives={[meritIncentives]}
incentivesNetAPR={displayValue}
hideValue={params.hideValue}
incentivesNetAPR={+meritIncentives.incentiveAPR}
hideValue={hideValue}
/>
</ContentWithTooltip>
);
};

export const SavingsGhoIncentivesButton = ({ hideValue }: { hideValue?: boolean }) => {
const { data: savingsGhoIncentive } = useSavingsGhoIncentive();

if (!savingsGhoIncentive) {
return null;
}

const meritIncentivesAPY = convertAprToApy(parseFloat(savingsGhoIncentive.aprDecimal));
const action = savingsGhoIncentive.actionKey || MeritAction.ETHEREUM_SGHO;
const meritIncentives: MeritReserveIncentiveResponse = {
incentiveAPR: meritIncentivesAPY.toString(),
rewardTokenAddress: savingsGhoIncentive.rewardTokenAddress || '',
rewardTokenSymbol: savingsGhoIncentive.rewardTokenSymbol || 'sGHO',
activeActions: [action],
actionMessages: {
[action]: {
customMessage: savingsGhoIncentive.customMessage ?? undefined,
customForumLink: savingsGhoIncentive.customForumLink ?? undefined,
},
},
action,
customMessage: savingsGhoIncentive.customMessage ?? undefined,
customForumLink: savingsGhoIncentive.customForumLink ?? undefined,
variants: { selfAPY: null },
breakdown: {
protocolAPY: 0,
protocolIncentivesAPR: 0,
meritIncentivesAPR: meritIncentivesAPY,
totalAPY: meritIncentivesAPY,
isBorrow: false,
breakdown: {
protocol: 0,
protocolIncentives: 0,
meritIncentives: meritIncentivesAPY,
},
},
};

return <MeritIncentivesButtonContent meritIncentives={meritIncentives} hideValue={hideValue} />;
};

export const MeritIncentivesButton = (params: MeritIncentivesButtonParams) => {
const { data: meritIncentives } = useMeritIncentives(params);

if (!meritIncentives) {
return null;
}

return (
<MeritIncentivesButtonContent meritIncentives={meritIncentives} hideValue={params.hideValue} />
);
};

export const MerklIncentivesButton = (params: {
market: string;
rewardedAsset?: string;
Expand Down
1 change: 1 addition & 0 deletions src/components/incentives/IncentivesCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ interface IncentivesCardProps {
symbol: string;
value: string | number;
incentives?: ReserveIncentiveResponse[];
/** aToken / vToken address (legacy; hook resolves underlying internally). */
address?: string;
variant?: 'main14' | 'main16' | 'secondary14';
symbolsVariant?: 'secondary14' | 'secondary16';
Expand Down
5 changes: 5 additions & 0 deletions src/components/incentives/IncentivesTooltipContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ const IncentivesSymbolMap: {
symbol: 'aUSDm',
aToken: true,
},
aCelUSDT: {
tokenIconSymbol: 'USDT',
symbol: 'aUSDT',
aToken: true,
},
aGnoEURe: {
tokenIconSymbol: 'EURe',
symbol: 'aEURe',
Expand Down
33 changes: 19 additions & 14 deletions src/components/incentives/MeritIncentivesTooltipContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,19 @@ interface CampaignConfig {
}

const isCeloAction = (action: MeritAction): boolean => {
return [
MeritAction.CELO_SUPPLY_CELO,
MeritAction.CELO_SUPPLY_USDT,
MeritAction.CELO_SUPPLY_USDC,
MeritAction.CELO_SUPPLY_WETH,
MeritAction.CELO_SUPPLY_MULTIPLE_BORROW_USDT,
MeritAction.CELO_BORROW_CELO,
MeritAction.CELO_BORROW_USDT,
MeritAction.CELO_BORROW_USDC,
MeritAction.CELO_BORROW_WETH,
].includes(action);
return (
[
MeritAction.CELO_SUPPLY_CELO,
MeritAction.CELO_SUPPLY_USDT,
MeritAction.CELO_SUPPLY_USDC,
MeritAction.CELO_SUPPLY_WETH,
MeritAction.CELO_SUPPLY_MULTIPLE_BORROW_USDT,
MeritAction.CELO_BORROW_CELO,
MeritAction.CELO_BORROW_USDT,
MeritAction.CELO_BORROW_USDC,
MeritAction.CELO_BORROW_WETH,
] as string[]
).includes(action);
};

const selfCampaignConfig: Map<MeritAction, { limit: string; token: string }> = new Map([
Expand Down Expand Up @@ -122,12 +124,15 @@ export const MeritIncentivesTooltipContent = ({
};
const meritIncentivesFormatted = getSymbolMap(meritIncentives);
const isCombinedMeritIncentives: boolean = meritIncentives.activeActions.length > 1;
const campaignConfig = getCampaignConfig(meritIncentives.action);
const selfConfig = selfCampaignConfig.get(meritIncentives.action);
// `action` is now optional (backend-driven). Fall back to an empty string
// so the switch/lookup helpers match their STANDARD branch.
const primaryAction = meritIncentives.action ?? '';
const campaignConfig = getCampaignConfig(primaryAction);
const selfConfig = selfCampaignConfig.get(primaryAction);

const remainingCustomMessage = getRemainingMessagesWhenCombined(
meritIncentives.activeActions,
meritIncentives.action,
primaryAction,
isCombinedMeritIncentives,
meritIncentives.actionMessages
);
Expand Down
52 changes: 4 additions & 48 deletions src/components/incentives/MerklIncentivesTooltipContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,55 +185,11 @@ export const MerklIncentivesTooltipContent = ({
</Typography>
</Box>
</Row>
) : merklIncentives.rewardsTokensMappedApys &&
merklIncentives.rewardsTokensMappedApys.length > 1 ? (
<>
{merklIncentives.rewardsTokensMappedApys.map((reward, index) => {
const { tokenIconSymbol, symbol, aToken } = getSymbolMap({
rewardTokenSymbol: reward.token.symbol,
rewardTokenAddress: reward.token.address,
incentiveAPR: reward.apy.toString(),
});
return (
<Row
key={index}
height={32}
caption={
<Box
sx={{
display: 'flex',
alignItems: 'center',
mb: 0,
}}
>
<TokenIcon
symbol={tokenIconSymbol}
aToken={aToken}
sx={{ fontSize: '20px', mr: 1 }}
/>
<Typography variant={typographyVariant}>{symbol}</Typography>
<Typography variant={typographyVariant} sx={{ ml: 0.5 }}>
{merklIncentives.breakdown.isBorrow ? '(-)' : '(+)'}
</Typography>
</Box>
}
width="100%"
>
<Box sx={{ display: 'inline-flex', alignItems: 'center' }}>
<FormattedNumber
value={merklIncentives.breakdown.isBorrow ? -reward.apy : reward.apy}
percent
variant={typographyVariant}
/>
<Typography variant={typographyVariant} sx={{ ml: 1 }}>
<Trans>APY</Trans>
</Typography>
</Box>
</Row>
);
})}
</>
) : (
// Note: legacy multi-reward-token rendering (`rewardsTokensMappedApys`)
// is gone. The V3 backend returns one `MerklSupply/Borrow`
// variant per reserve per direction with a single `payoutToken`,
// so the single-row render below covers all live campaigns.
<Row
height={32}
caption={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import { Typography } from '@mui/material';
import React, { useRef, useState } from 'react';
import { useGeneralStakeUiData } from 'src/hooks/stake/useGeneralStakeUiData';
import { useUserStakeUiData } from 'src/hooks/stake/useUserStakeUiData';
import { useMeritIncentives } from 'src/hooks/useMeritIncentives';
import { useModalContext } from 'src/hooks/useModal';
import { useSavingsGhoIncentive } from 'src/hooks/useSavingsGhoIncentive';
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
import { useRootStore } from 'src/store/root';
import { stakeAssetNameFormatted, stakeConfig } from 'src/ui-config/stakeConfig';
import { STAKE } from 'src/utils/events';
import { GHO_SYMBOL } from 'src/utils/ghoUtilities';
import { convertAprToApy } from 'src/utils/utils';
import { useShallow } from 'zustand/shallow';

import { AssetInput } from '../AssetInput';
Expand All @@ -35,10 +36,10 @@ export const SavingsGhoModalDepositContent = () => {
store.currentChainId,
])
);
const { data: meritIncentives } = useMeritIncentives({
symbol: 'GHO',
market: currentMarketData.market,
});
const { data: savingsGhoIncentive } = useSavingsGhoIncentive();
const savingsGhoAPY = savingsGhoIncentive?.aprDecimal
? convertAprToApy(Number(savingsGhoIncentive.aprDecimal))
: 0;
const [_amount, setAmount] = useState('');
const amountRef = useRef<string>();

Expand Down Expand Up @@ -118,11 +119,7 @@ export const SavingsGhoModalDepositContent = () => {
</Typography>
)}
<TxModalDetails gasLimit={gasLimit} chainId={ChainId.mainnet}>
<DetailsNumberLine
description={<Trans>APR</Trans>}
value={meritIncentives?.incentiveAPR || '0'}
percent
/>
<DetailsNumberLine description={<Trans>APY</Trans>} value={savingsGhoAPY} percent />
</TxModalDetails>

{txError && <GasEstimationError txError={txError} />}
Expand Down
7 changes: 6 additions & 1 deletion src/hooks/app-data-provider/useAppDataProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,12 @@ export const AppDataProvider: React.FC<PropsWithChildren> = ({ children }) => {

const marketAddress = currentMarketData.addresses.LENDING_POOL.toLowerCase();

const sdkMarket = data?.find((item) => item.address.toLowerCase() === marketAddress);
// react-query's structural sharing can replace our Market[] with a
// structurally-similar plain object on refetch when it encounters
// non-POJO values (e.g. bigint-ish strings wrapped by the SDK). Guard
// before calling Array.prototype methods.
const marketsList = Array.isArray(data) ? data : [];
const sdkMarket = marketsList.find((item) => item.address.toLowerCase() === marketAddress);

const totalBorrows = sdkMarket?.borrowReserves.reduce((acc, reserve) => {
const value = reserve.borrowInfo?.total?.usd ?? 0;
Expand Down
Loading