Skip to content
55 changes: 55 additions & 0 deletions src/components/incentives/incentives.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { AaveBorrowIncentive, AaveSupplyIncentive, ReserveIncentive } from '@aave/graphql';
import type { ReserveIncentiveResponse } from '@aave/math-utils/dist/esm/formatters/incentive/calculate-reserve-incentives';

//Typescript GUARDS
const isAaveSupplyIncentive = (incentive: ReserveIncentive): incentive is AaveSupplyIncentive => {
return incentive.__typename === 'AaveSupplyIncentive';
};

const isAaveBorrowIncentive = (incentive: ReserveIncentive): incentive is AaveBorrowIncentive => {
return incentive.__typename === 'AaveBorrowIncentive';
};

export const isAaveProtocolIncentive = (incentive: ReserveIncentive): boolean => {
return isAaveSupplyIncentive(incentive) || isAaveBorrowIncentive(incentive);
};

export const getIncentiveAPR = (incentive: ReserveIncentive): string => {
// For AaveSupplyIncentive
if (isAaveSupplyIncentive(incentive) && incentive.extraSupplyApr?.value) {
return incentive.extraSupplyApr.value.toString();
}

// For AaveBorrowIncentive
if (isAaveBorrowIncentive(incentive) && incentive.borrowAprDiscount?.value) {
return incentive.borrowAprDiscount.value.toString();
}

// Fallback for previous structure)
if ('incentiveAPR' in incentive) {
return String(incentive.incentiveAPR);
}

return '0';
};

// Mapping sdk structure to legacy structure used in incentives card logic
export const mapAaveProtocolIncentives = (
incentives: ReserveIncentive[] | undefined,
direction: 'supply' | 'borrow'
): ReserveIncentiveResponse[] => {
if (!incentives || incentives.length === 0) {
return [];
}

const typedIncentives =
direction === 'supply'
? incentives.filter(isAaveSupplyIncentive)
: incentives.filter(isAaveBorrowIncentive);

return typedIncentives.map((incentive) => ({
incentiveAPR: getIncentiveAPR(incentive),
rewardTokenAddress: incentive.rewardTokenAddress,
rewardTokenSymbol: incentive.rewardTokenSymbol,
}));
};
4 changes: 3 additions & 1 deletion src/components/lists/ListMobileItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface ListMobileItemProps {
showBorrowCapTooltips?: boolean;
showDebtCeilingTooltips?: boolean;
isIsolated: boolean;
onIconError?: () => void;
}

export const ListMobileItem = ({
Expand All @@ -35,6 +36,7 @@ export const ListMobileItem = ({
showBorrowCapTooltips = false,
showDebtCeilingTooltips = false,
isIsolated,
onIconError,
}: ListMobileItemProps) => {
const { supplyCap, borrowCap, debtCeiling } = useAssetCaps();
return (
Expand All @@ -59,7 +61,7 @@ export const ListMobileItem = ({
href={ROUTES.reserveOverview(underlyingAsset, currentMarket)}
sx={{ display: 'inline-flex', alignItems: 'center' }}
>
<TokenIcon symbol={iconSymbol} sx={{ fontSize: '40px' }} />
<TokenIcon symbol={iconSymbol} sx={{ fontSize: '40px' }} onError={onIconError} />
<Box sx={{ ml: 2 }}>
<Typography variant="h4">{name}</Typography>
<Box display="flex" alignItems="center">
Expand Down
52 changes: 50 additions & 2 deletions src/hooks/app-data-provider/useAppDataProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { EmodeMarketCategory, Market, MarketUserState, Reserve } from '@aave/graphql';
import { UserReserveData } from '@aave/math-utils';
import { client } from 'pages/_app.page';
import React, { PropsWithChildren, useContext } from 'react';
import { EmodeCategory } from 'src/helpers/types';
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
Expand All @@ -16,6 +18,7 @@ import {
import { usePoolReservesHumanized } from '../pool/usePoolReserves';
import { useUserPoolReservesHumanized } from '../pool/useUserPoolReserves';
import { FormattedUserReserves } from '../pool/useUserSummaryAndIncentives';
import { useMarketsData } from './useMarketsData';

/**
* removes the marketPrefix from a symbol
Expand All @@ -40,9 +43,17 @@ export type ComputedUserReserveData = FormattedUserReserves;
* @deprecated Use ExtendedFormattedUser type from useExtendedUserSummaryAndIncentives hook
*/
export type ExtendedFormattedUser = _ExtendedFormattedUser;

export type ReserveWithId = Reserve & { id: string };
export interface AppDataContextType {
loading: boolean;
/** SDK market snapshot */
market?: Market;
totalBorrows?: number;
supplyReserves: ReserveWithId[];
borrowReserves: ReserveWithId[];
eModeCategories: EmodeMarketCategory[];
userState?: MarketUserState;
/** Legacy fields (deprecated) kept temporarily for incremental migration */
reserves: ComputedReserveData[];
eModes: Record<number, EmodeCategory>;
user?: ExtendedFormattedUser;
Expand All @@ -62,6 +73,34 @@ export const AppDataProvider: React.FC<PropsWithChildren> = ({ children }) => {

const currentMarketData = useRootStore((state) => state.currentMarketData);

const { data, isPending } = useMarketsData({
client,
marketData: currentMarketData,
account: currentAccount,
});

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

const sdkMarket = data?.find((item) => item.address.toLowerCase() === marketAddress);

const totalBorrows = sdkMarket?.borrowReserves.reduce((acc, reserve) => {
const value = reserve.borrowInfo?.total?.usd ?? 0;
return acc + Number(value);
}, 0);

const supplyReserves = (sdkMarket?.supplyReserves ?? []).map((reserve) => ({
...reserve,
id: `${sdkMarket?.address}-${reserve.underlyingToken.address}`,
}));

const borrowReserves = (sdkMarket?.borrowReserves ?? []).map((reserve) => ({
...reserve,
id: `${sdkMarket?.address}-${reserve.underlyingToken.address}`,
}));

const eModeCategories = sdkMarket?.eModeCategories ?? [];
const marketUserState = sdkMarket?.userState ?? undefined;

const { data: reservesData, isPending: reservesDataLoading } =
usePoolReservesHumanized(currentMarketData);
const { data: formattedPoolReserves, isPending: formattedPoolReservesLoading } =
Expand All @@ -81,10 +120,19 @@ export const AppDataProvider: React.FC<PropsWithChildren> = ({ children }) => {
const isReservesLoading = reservesDataLoading || formattedPoolReservesLoading;
const isUserDataLoading = userReservesDataLoading || userSummaryLoading;

const loading = isPending || isReservesLoading || (!!currentAccount && isUserDataLoading);

return (
<AppDataContext.Provider
value={{
loading: isReservesLoading || (!!currentAccount && isUserDataLoading),
loading,
market: sdkMarket,
totalBorrows,
supplyReserves,
borrowReserves,
eModeCategories,
userState: marketUserState,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this one used?

Copy link
Collaborator Author

@AGMASO AGMASO Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually not yet, but I have a feeling I’ll need it for another part of the Aave/interface refactoring. I can delete it for now if you’d like.

// Legacy fields (to be removed once consumers migrate)
reserves: formattedPoolReserves || [],
eModes,
user: userSummary,
Expand Down
38 changes: 38 additions & 0 deletions src/hooks/app-data-provider/useMarketsData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { AaveClient, chainId, evmAddress, OrderDirection } from '@aave/client';
import { markets } from '@aave/client/actions';
import { useQuery } from '@tanstack/react-query';
import { MarketDataType } from 'src/ui-config/marketsConfig';
import { queryKeysFactory } from 'src/ui-config/queries';

type UseMarketsDataParams = {
client: AaveClient;
marketData: MarketDataType;
account?: string | null;
};

export const useMarketsData = ({ client, marketData, account }: UseMarketsDataParams) => {
const userAddress = account ? evmAddress(account) : undefined;
const marketKey = [
...queryKeysFactory.market(marketData),
...queryKeysFactory.user(userAddress ?? 'anonymous'),
];

return useQuery({
queryKey: marketKey,
enabled: !!client,
queryFn: async () => {
const response = await markets(client, {
chainIds: [chainId(marketData.chainId)],
user: userAddress,
suppliesOrderBy: { tokenName: OrderDirection.Asc },
borrowsOrderBy: { tokenName: OrderDirection.Asc },
});

if (response.isErr()) {
throw response.error;
}

return response.value;
},
});
};
41 changes: 0 additions & 41 deletions src/hooks/useMerklIncentives.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ProtocolAction } from '@aave/contract-helpers';
import { ReserveIncentiveResponse } from '@aave/math-utils/dist/esm/formatters/incentive/calculate-reserve-incentives';
import { AaveV3Plasma } from '@bgd-labs/aave-address-book';
import { useQuery } from '@tanstack/react-query';
import { useRootStore } from 'src/store/root';
import { convertAprToApy } from 'src/utils/utils';
Expand Down Expand Up @@ -100,28 +99,6 @@ type WhitelistApiResponse = {
additionalIncentiveInfo: Record<string, ReserveIncentiveAdditionalData>;
};

const hardcodedIncentives: Record<string, ExtendedReserveIncentiveResponse> = {
[AaveV3Plasma.ASSETS.USDe.A_TOKEN]: {
incentiveAPR: '0.12',
rewardTokenAddress: AaveV3Plasma.ASSETS.USDe.A_TOKEN,
rewardTokenSymbol: 'aPlaUSDe',
customMessage:
'You must supply USDe and hold an equal or greater amount of sUSDe (by USD value) to receive the incentives. To be eligible, your assets supplied must be at least 2x your account equity, and you must not be borrowing any USDe. The rate provided to eligible users will change week by week, but will be roughly in line with the sUSDe rate for the forseeable future.',
breakdown: {
protocolAPY: 0,
protocolIncentivesAPR: 0,
merklIncentivesAPR: 0,
totalAPY: 0,
isBorrow: false,
breakdown: {
protocol: 0,
protocolIncentives: 0,
merklIncentives: 0,
},
},
},
};

const MERKL_ENDPOINT = 'https://api.merkl.xyz/v4/opportunities?mainProtocolId=aave'; // Merkl API
const WHITELIST_ENDPOINT = 'https://apps.aavechan.com/api/aave/merkl/whitelist-token-list'; // Endpoint to fetch whitelisted tokens
const checkOpportunityAction = (
Expand Down Expand Up @@ -176,24 +153,6 @@ export const useMerklIncentives = ({
queryKey: ['merklIncentives', market],
staleTime: 1000 * 60 * 5,
select: (merklOpportunities) => {
const hardcodedIncentive = rewardedAsset ? hardcodedIncentives[rewardedAsset] : undefined;

if (hardcodedIncentive) {
const protocolIncentivesAPR = protocolIncentives.reduce((sum, inc) => {
return sum + (inc.incentiveAPR === 'Infinity' ? 0 : +inc.incentiveAPR);
}, 0);
const merklIncentivesAPY = convertAprToApy(0.06);
return {
...hardcodedIncentive,
breakdown: {
protocolAPY,
protocolIncentivesAPR,
merklIncentivesAPR: merklIncentivesAPY,
totalAPY: protocolAPY + protocolIncentivesAPR + merklIncentivesAPY,
} as MerklIncentivesBreakdown,
} as ExtendedReserveIncentiveResponse;
}

const opportunities = merklOpportunities.filter(
(opportunitiy) =>
rewardedAsset &&
Expand Down
3 changes: 3 additions & 0 deletions src/modules/dashboard/lists/ListMobileItemWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ interface ListMobileItemWrapperProps {
showDebtCeilingTooltips?: boolean;
isIsolated?: boolean;
showExternalIncentivesTooltips?: ExternalIncentivesTooltipsConfig;
onIconError?: () => void;
}

export const ListMobileItemWrapper = ({
Expand All @@ -54,6 +55,7 @@ export const ListMobileItemWrapper = ({
spkAirdrop: false,
kernelPoints: false,
},
onIconError,
}: ListMobileItemWrapperProps) => {
const WarningComponent: React.FC = () => {
const showFrozenTooltip = frozen && symbol !== 'renFIL';
Expand Down Expand Up @@ -94,6 +96,7 @@ export const ListMobileItemWrapper = ({
showSupplyCapTooltips={showSupplyCapTooltips}
showBorrowCapTooltips={showBorrowCapTooltips}
showDebtCeilingTooltips={showDebtCeilingTooltips}
onIconError={onIconError}
>
{children}
</ListMobileItem>
Expand Down
Loading
Loading