Skip to content

Commit 08b1677

Browse files
authored
Refactor Maket Page to use Aave-sdk (#2686)
1 parent cddb844 commit 08b1677

File tree

13 files changed

+371
-131
lines changed

13 files changed

+371
-131
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"@aave/math-utils": "1.36.1",
3636
"@aave/react": "0.6.1",
3737
"@amplitude/analytics-browser": "^2.13.0",
38-
"@bgd-labs/aave-address-book": "^4.31.0",
38+
"@bgd-labs/aave-address-book": "^4.34.1",
3939
"@cowprotocol/app-data": "^3.1.0",
4040
"@cowprotocol/cow-sdk": "6.3.3",
4141
"@emotion/cache": "11.10.3",
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type { AaveBorrowIncentive, AaveSupplyIncentive, ReserveIncentive } from '@aave/graphql';
2+
import type { ReserveIncentiveResponse } from '@aave/math-utils/dist/esm/formatters/incentive/calculate-reserve-incentives';
3+
4+
//Typescript GUARDS
5+
const isAaveSupplyIncentive = (incentive: ReserveIncentive): incentive is AaveSupplyIncentive => {
6+
return incentive.__typename === 'AaveSupplyIncentive';
7+
};
8+
9+
const isAaveBorrowIncentive = (incentive: ReserveIncentive): incentive is AaveBorrowIncentive => {
10+
return incentive.__typename === 'AaveBorrowIncentive';
11+
};
12+
13+
export const isAaveProtocolIncentive = (incentive: ReserveIncentive): boolean => {
14+
return isAaveSupplyIncentive(incentive) || isAaveBorrowIncentive(incentive);
15+
};
16+
17+
export const getIncentiveAPR = (incentive: ReserveIncentive): string => {
18+
// For AaveSupplyIncentive
19+
if (isAaveSupplyIncentive(incentive) && incentive.extraSupplyApr?.value) {
20+
return incentive.extraSupplyApr.value.toString();
21+
}
22+
23+
// For AaveBorrowIncentive
24+
if (isAaveBorrowIncentive(incentive) && incentive.borrowAprDiscount?.value) {
25+
return incentive.borrowAprDiscount.value.toString();
26+
}
27+
28+
// Fallback for previous structure)
29+
if ('incentiveAPR' in incentive) {
30+
return String(incentive.incentiveAPR);
31+
}
32+
33+
return '0';
34+
};
35+
36+
// Mapping sdk structure to legacy structure used in incentives card logic
37+
export const mapAaveProtocolIncentives = (
38+
incentives: ReserveIncentive[] | undefined,
39+
direction: 'supply' | 'borrow'
40+
): ReserveIncentiveResponse[] => {
41+
if (!incentives || incentives.length === 0) {
42+
return [];
43+
}
44+
45+
const typedIncentives =
46+
direction === 'supply'
47+
? incentives.filter(isAaveSupplyIncentive)
48+
: incentives.filter(isAaveBorrowIncentive);
49+
50+
return typedIncentives.map((incentive) => ({
51+
incentiveAPR: getIncentiveAPR(incentive),
52+
rewardTokenAddress: incentive.rewardTokenAddress,
53+
rewardTokenSymbol: incentive.rewardTokenSymbol,
54+
}));
55+
};

src/components/lists/ListMobileItem.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ interface ListMobileItemProps {
2020
showBorrowCapTooltips?: boolean;
2121
showDebtCeilingTooltips?: boolean;
2222
isIsolated: boolean;
23+
onIconError?: () => void;
2324
}
2425

2526
export const ListMobileItem = ({
@@ -35,6 +36,7 @@ export const ListMobileItem = ({
3536
showBorrowCapTooltips = false,
3637
showDebtCeilingTooltips = false,
3738
isIsolated,
39+
onIconError,
3840
}: ListMobileItemProps) => {
3941
const { supplyCap, borrowCap, debtCeiling } = useAssetCaps();
4042
return (
@@ -59,7 +61,7 @@ export const ListMobileItem = ({
5961
href={ROUTES.reserveOverview(underlyingAsset, currentMarket)}
6062
sx={{ display: 'inline-flex', alignItems: 'center' }}
6163
>
62-
<TokenIcon symbol={iconSymbol} sx={{ fontSize: '40px' }} />
64+
<TokenIcon symbol={iconSymbol} sx={{ fontSize: '40px' }} onError={onIconError} />
6365
<Box sx={{ ml: 2 }}>
6466
<Typography variant="h4">{name}</Typography>
6567
<Box display="flex" alignItems="center">

src/hooks/app-data-provider/useAppDataProvider.tsx

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import type { EmodeMarketCategory, Market, MarketUserState, Reserve } from '@aave/graphql';
12
import { UserReserveData } from '@aave/math-utils';
3+
import { client } from 'pages/_app.page';
24
import React, { PropsWithChildren, useContext } from 'react';
35
import { EmodeCategory } from 'src/helpers/types';
46
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
@@ -16,6 +18,7 @@ import {
1618
import { usePoolReservesHumanized } from '../pool/usePoolReserves';
1719
import { useUserPoolReservesHumanized } from '../pool/useUserPoolReserves';
1820
import { FormattedUserReserves } from '../pool/useUserSummaryAndIncentives';
21+
import { useMarketsData } from './useMarketsData';
1922

2023
/**
2124
* removes the marketPrefix from a symbol
@@ -40,9 +43,17 @@ export type ComputedUserReserveData = FormattedUserReserves;
4043
* @deprecated Use ExtendedFormattedUser type from useExtendedUserSummaryAndIncentives hook
4144
*/
4245
export type ExtendedFormattedUser = _ExtendedFormattedUser;
43-
46+
export type ReserveWithId = Reserve & { id: string };
4447
export interface AppDataContextType {
4548
loading: boolean;
49+
/** SDK market snapshot */
50+
market?: Market;
51+
totalBorrows?: number;
52+
supplyReserves: ReserveWithId[];
53+
borrowReserves: ReserveWithId[];
54+
eModeCategories: EmodeMarketCategory[];
55+
userState?: MarketUserState;
56+
/** Legacy fields (deprecated) kept temporarily for incremental migration */
4657
reserves: ComputedReserveData[];
4758
eModes: Record<number, EmodeCategory>;
4859
user?: ExtendedFormattedUser;
@@ -62,6 +73,34 @@ export const AppDataProvider: React.FC<PropsWithChildren> = ({ children }) => {
6273

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

76+
const { data, isPending } = useMarketsData({
77+
client,
78+
marketData: currentMarketData,
79+
account: currentAccount,
80+
});
81+
82+
const marketAddress = currentMarketData.addresses.LENDING_POOL.toLowerCase();
83+
84+
const sdkMarket = data?.find((item) => item.address.toLowerCase() === marketAddress);
85+
86+
const totalBorrows = sdkMarket?.borrowReserves.reduce((acc, reserve) => {
87+
const value = reserve.borrowInfo?.total?.usd ?? 0;
88+
return acc + Number(value);
89+
}, 0);
90+
91+
const supplyReserves = (sdkMarket?.supplyReserves ?? []).map((reserve) => ({
92+
...reserve,
93+
id: `${sdkMarket?.address}-${reserve.underlyingToken.address}`,
94+
}));
95+
96+
const borrowReserves = (sdkMarket?.borrowReserves ?? []).map((reserve) => ({
97+
...reserve,
98+
id: `${sdkMarket?.address}-${reserve.underlyingToken.address}`,
99+
}));
100+
101+
const eModeCategories = sdkMarket?.eModeCategories ?? [];
102+
const marketUserState = sdkMarket?.userState ?? undefined;
103+
65104
const { data: reservesData, isPending: reservesDataLoading } =
66105
usePoolReservesHumanized(currentMarketData);
67106
const { data: formattedPoolReserves, isPending: formattedPoolReservesLoading } =
@@ -81,10 +120,19 @@ export const AppDataProvider: React.FC<PropsWithChildren> = ({ children }) => {
81120
const isReservesLoading = reservesDataLoading || formattedPoolReservesLoading;
82121
const isUserDataLoading = userReservesDataLoading || userSummaryLoading;
83122

123+
const loading = isPending || isReservesLoading || (!!currentAccount && isUserDataLoading);
124+
84125
return (
85126
<AppDataContext.Provider
86127
value={{
87-
loading: isReservesLoading || (!!currentAccount && isUserDataLoading),
128+
loading,
129+
market: sdkMarket,
130+
totalBorrows,
131+
supplyReserves,
132+
borrowReserves,
133+
eModeCategories,
134+
userState: marketUserState,
135+
// Legacy fields (to be removed once consumers migrate)
88136
reserves: formattedPoolReserves || [],
89137
eModes,
90138
user: userSummary,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { AaveClient, chainId, evmAddress, OrderDirection } from '@aave/client';
2+
import { markets } from '@aave/client/actions';
3+
import { useQuery } from '@tanstack/react-query';
4+
import { MarketDataType } from 'src/ui-config/marketsConfig';
5+
import { queryKeysFactory } from 'src/ui-config/queries';
6+
7+
type UseMarketsDataParams = {
8+
client: AaveClient;
9+
marketData: MarketDataType;
10+
account?: string | null;
11+
};
12+
13+
export const useMarketsData = ({ client, marketData, account }: UseMarketsDataParams) => {
14+
const userAddress = account ? evmAddress(account) : undefined;
15+
const marketKey = [
16+
...queryKeysFactory.market(marketData),
17+
...queryKeysFactory.user(userAddress ?? 'anonymous'),
18+
];
19+
20+
return useQuery({
21+
queryKey: marketKey,
22+
enabled: !!client,
23+
queryFn: async () => {
24+
const response = await markets(client, {
25+
chainIds: [chainId(marketData.chainId)],
26+
user: userAddress,
27+
suppliesOrderBy: { tokenName: OrderDirection.Asc },
28+
borrowsOrderBy: { tokenName: OrderDirection.Asc },
29+
});
30+
31+
if (response.isErr()) {
32+
throw response.error;
33+
}
34+
35+
return response.value;
36+
},
37+
});
38+
};

src/modules/dashboard/lists/ListMobileItemWrapper.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ interface ListMobileItemWrapperProps {
3232
showDebtCeilingTooltips?: boolean;
3333
isIsolated?: boolean;
3434
showExternalIncentivesTooltips?: ExternalIncentivesTooltipsConfig;
35+
onIconError?: () => void;
3536
}
3637

3738
export const ListMobileItemWrapper = ({
@@ -54,6 +55,7 @@ export const ListMobileItemWrapper = ({
5455
spkAirdrop: false,
5556
kernelPoints: false,
5657
},
58+
onIconError,
5759
}: ListMobileItemWrapperProps) => {
5860
const WarningComponent: React.FC = () => {
5961
const showFrozenTooltip = frozen && symbol !== 'renFIL';
@@ -94,6 +96,7 @@ export const ListMobileItemWrapper = ({
9496
showSupplyCapTooltips={showSupplyCapTooltips}
9597
showBorrowCapTooltips={showBorrowCapTooltips}
9698
showDebtCeilingTooltips={showDebtCeilingTooltips}
99+
onIconError={onIconError}
97100
>
98101
{children}
99102
</ListMobileItem>

src/modules/markets/MarketAssetsList.tsx

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { Trans } from '@lingui/macro';
22
import { useMediaQuery } from '@mui/material';
33
import { useState } from 'react';
4+
import { mapAaveProtocolIncentives } from 'src/components/incentives/incentives.helper';
45
import { VariableAPYTooltip } from 'src/components/infoTooltips/VariableAPYTooltip';
56
import { ListColumn } from 'src/components/lists/ListColumn';
67
import { ListHeaderTitle } from 'src/components/lists/ListHeaderTitle';
78
import { ListHeaderWrapper } from 'src/components/lists/ListHeaderWrapper';
8-
import { ComputedReserveData } from 'src/hooks/app-data-provider/useAppDataProvider';
99

10+
import { ReserveWithId } from '../../hooks/app-data-provider/useAppDataProvider';
1011
import { MarketAssetsListItem } from './MarketAssetsListItem';
1112
import { MarketAssetsListItemLoader } from './MarketAssetsListItemLoader';
1213
import { MarketAssetsListMobileItem } from './MarketAssetsListMobileItem';
@@ -15,19 +16,19 @@ import { MarketAssetsListMobileItemLoader } from './MarketAssetsListMobileItemLo
1516
const listHeaders = [
1617
{
1718
title: <Trans>Asset</Trans>,
18-
sortKey: 'symbol',
19+
sortKey: 'underlyingToken.symbol',
1920
},
2021
{
2122
title: <Trans>Total supplied</Trans>,
22-
sortKey: 'totalLiquidityUSD',
23+
sortKey: 'size.usd',
2324
},
2425
{
2526
title: <Trans>Supply APY</Trans>,
26-
sortKey: 'supplyAPY',
27+
sortKey: 'supplyInfo.apy.value',
2728
},
2829
{
2930
title: <Trans>Total borrowed</Trans>,
30-
sortKey: 'totalDebtUSD',
31+
sortKey: 'borrowInfo.total.usd',
3132
},
3233
{
3334
title: (
@@ -37,37 +38,71 @@ const listHeaders = [
3738
variant="subheader2"
3839
/>
3940
),
40-
sortKey: 'variableBorrowAPY',
41+
sortKey: 'borrowInfo.apy.value',
4142
},
4243
];
4344

4445
type MarketAssetsListProps = {
45-
reserves: ComputedReserveData[];
46+
reserves: ReserveWithId[];
4647
loading: boolean;
4748
};
49+
export type ReserveWithProtocolIncentives = ReserveWithId & {
50+
supplyProtocolIncentives: ReturnType<typeof mapAaveProtocolIncentives>;
51+
borrowProtocolIncentives: ReturnType<typeof mapAaveProtocolIncentives>;
52+
};
4853

4954
export default function MarketAssetsList({ reserves, loading }: MarketAssetsListProps) {
5055
const isTableChangedToCards = useMediaQuery('(max-width:1125px)');
5156
const [sortName, setSortName] = useState('');
5257
const [sortDesc, setSortDesc] = useState(false);
53-
if (sortDesc) {
54-
if (sortName === 'symbol') {
55-
reserves.sort((a, b) => (a.symbol.toUpperCase() < b.symbol.toUpperCase() ? -1 : 1));
56-
} else {
57-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
58-
// @ts-ignore
59-
reserves.sort((a, b) => a[sortName] - b[sortName]);
60-
}
61-
} else {
62-
if (sortName === 'symbol') {
63-
reserves.sort((a, b) => (b.symbol.toUpperCase() < a.symbol.toUpperCase() ? -1 : 1));
64-
} else {
65-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
66-
// @ts-ignore
67-
reserves.sort((a, b) => b[sortName] - a[sortName]);
58+
const sortedReserves = [...reserves].sort((a, b) => {
59+
if (!sortName) return 0;
60+
61+
let aValue: number | string;
62+
let bValue: number | string;
63+
64+
switch (sortName) {
65+
case 'underlyingToken.symbol':
66+
aValue = a.underlyingToken.symbol.toUpperCase();
67+
bValue = b.underlyingToken.symbol.toUpperCase();
68+
if (sortDesc) {
69+
return aValue < bValue ? -1 : 1;
70+
}
71+
return bValue < aValue ? -1 : 1;
72+
73+
case 'size.usd':
74+
aValue = Number(a.size.usd) || 0;
75+
bValue = Number(b.size.usd) || 0;
76+
break;
77+
78+
case 'supplyInfo.apy.value':
79+
aValue = Number(a.supplyInfo.apy.value) || 0;
80+
bValue = Number(b.supplyInfo.apy.value) || 0;
81+
break;
82+
83+
case 'borrowInfo.total.usd':
84+
aValue = Number(a.borrowInfo?.total.usd) || 0;
85+
bValue = Number(b.borrowInfo?.total.usd) || 0;
86+
break;
87+
88+
case 'borrowInfo.apy.value':
89+
aValue = Number(a.borrowInfo?.apy.value) || 0;
90+
bValue = Number(b.borrowInfo?.apy.value) || 0;
91+
break;
92+
93+
default:
94+
return 0;
6895
}
69-
}
7096

97+
return sortDesc
98+
? (aValue as number) - (bValue as number)
99+
: (bValue as number) - (aValue as number);
100+
});
101+
const reservesWithIncentives: ReserveWithProtocolIncentives[] = sortedReserves.map((reserve) => ({
102+
...reserve,
103+
supplyProtocolIncentives: mapAaveProtocolIncentives(reserve.incentives, 'supply'),
104+
borrowProtocolIncentives: mapAaveProtocolIncentives(reserve.incentives, 'borrow'),
105+
}));
71106
// Show loading state when loading
72107
if (loading) {
73108
return isTableChangedToCards ? (
@@ -95,8 +130,8 @@ export default function MarketAssetsList({ reserves, loading }: MarketAssetsList
95130
<ListHeaderWrapper px={6}>
96131
{listHeaders.map((col) => (
97132
<ListColumn
98-
isRow={col.sortKey === 'symbol'}
99-
maxWidth={col.sortKey === 'symbol' ? 280 : undefined}
133+
isRow={col.sortKey === 'underlyingToken.symbol'}
134+
maxWidth={col.sortKey === 'underlyingToken.symbol' ? 280 : undefined}
100135
key={col.sortKey}
101136
>
102137
<ListHeaderTitle
@@ -115,7 +150,7 @@ export default function MarketAssetsList({ reserves, loading }: MarketAssetsList
115150
</ListHeaderWrapper>
116151
)}
117152

118-
{reserves.map((reserve) =>
153+
{reservesWithIncentives.map((reserve) =>
119154
isTableChangedToCards ? (
120155
<MarketAssetsListMobileItem {...reserve} key={reserve.id} />
121156
) : (

0 commit comments

Comments
 (0)