From 114328fb5f5f563b9d8f6593a4df298ffa4d3bce Mon Sep 17 00:00:00 2001 From: Alejandro <95312462+AGMASO@users.noreply.github.com> Date: Fri, 26 Sep 2025 16:05:32 +0200 Subject: [PATCH 01/39] refactor: sdk integrated in market page --- .../incentives/IncentivesTooltipContent.tsx | 5 + .../incentives/incentives.helper.ts | 76 ++ src/components/lists/ListMobileItem.tsx | 4 +- .../app-data-provider/useAppDataProvider.tsx | 67 +- src/hooks/app-data-provider/useMarketsData.ts | 38 + src/hooks/useMeritIncentives.ts | 50 +- src/hooks/useMerklIncentives.ts | 37 +- .../dashboard/lists/ListMobileItemWrapper.tsx | 3 + src/modules/markets/MarketAssetsList.tsx | 49 +- .../markets/MarketAssetsListContainer.tsx | 41 +- src/modules/markets/MarketAssetsListItem.tsx | 105 ++- .../markets/MarketAssetsListMobileItem.tsx | 86 +- src/modules/markets/MarketsTopPanel.tsx | 23 +- .../reserve-overview/TokenLinkDropdown.tsx | 2 +- src/ui-config/marketsConfig.tsx | 2 +- src/ui-config/marketsConfig_original.tsx | 803 ++++++++++++++++++ 16 files changed, 1258 insertions(+), 133 deletions(-) create mode 100644 src/components/incentives/incentives.helper.ts create mode 100644 src/hooks/app-data-provider/useMarketsData.ts create mode 100644 src/ui-config/marketsConfig_original.tsx diff --git a/src/components/incentives/IncentivesTooltipContent.tsx b/src/components/incentives/IncentivesTooltipContent.tsx index 6ba55667cf..7e1a68c24b 100644 --- a/src/components/incentives/IncentivesTooltipContent.tsx +++ b/src/components/incentives/IncentivesTooltipContent.tsx @@ -172,6 +172,11 @@ const IncentivesSymbolMap: { symbol: 'aSCR', aToken: true, }, + aPlaUSDe: { + tokenIconSymbol: 'USDe', + symbol: 'aUSDe', + aToken: true, + }, }; interface IncentivesTooltipContentProps { diff --git a/src/components/incentives/incentives.helper.ts b/src/components/incentives/incentives.helper.ts new file mode 100644 index 0000000000..d263564fbb --- /dev/null +++ b/src/components/incentives/incentives.helper.ts @@ -0,0 +1,76 @@ +import type { AaveBorrowIncentive, AaveSupplyIncentive, ReserveIncentive } from '@aave/graphql'; +import type { ReserveIncentiveResponse } from '@aave/math-utils/dist/esm/formatters/incentive/calculate-reserve-incentives'; + +const isAaveSupplyIncentive = (incentive: ReserveIncentive): incentive is AaveSupplyIncentive => { + return incentive.__typename === 'AaveSupplyIncentive'; +}; + +const isAaveBorrowIncentive = (incentive: ReserveIncentive): incentive is AaveBorrowIncentive => { + return incentive.__typename === 'AaveBorrowIncentive'; +}; + +// Guard combinado para incentivos de protocolo Aave +export const isAaveProtocolIncentive = (incentive: ReserveIncentive): boolean => { + return isAaveSupplyIncentive(incentive) || isAaveBorrowIncentive(incentive); +}; + +export const getIncentiveAPR = (incentive: ReserveIncentive): string => { + // Para AaveSupplyIncentive + if ('extraSupplyApr' in incentive && incentive.extraSupplyApr?.value) { + return incentive.extraSupplyApr.value.toString(); + } + + // Para AaveBorrowIncentive + if ('borrowAprDiscount' in incentive && incentive.borrowAprDiscount?.value) { + return incentive.borrowAprDiscount.value.toString(); + } + + // Fallback para estructura anterior (por compatibilidad) + if ('incentiveAPR' in incentive) { + return String(incentive.incentiveAPR); + } + + return '0'; +}; + +export const calculateProtocolIncentivesAPR = ( + incentives: ReserveIncentive[] | undefined +): number | 'Infinity' => { + return ( + incentives?.filter(isAaveProtocolIncentive)?.reduce((sum, inc) => { + const aprString = getIncentiveAPR(inc); + + if (aprString === 'Infinity' || sum === 'Infinity') { + return 'Infinity'; + } + + const aprValue = parseFloat(aprString); + + if (aprValue === Infinity || Number.isNaN(aprValue)) { + return sum; + } + + return sum + aprValue; + }, 0 as number | 'Infinity') || 0 + ); +}; + +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, + })); +}; diff --git a/src/components/lists/ListMobileItem.tsx b/src/components/lists/ListMobileItem.tsx index 045e2f565c..c16f6102b8 100644 --- a/src/components/lists/ListMobileItem.tsx +++ b/src/components/lists/ListMobileItem.tsx @@ -20,6 +20,7 @@ interface ListMobileItemProps { showBorrowCapTooltips?: boolean; showDebtCeilingTooltips?: boolean; isIsolated: boolean; + onIconError?: () => void; } export const ListMobileItem = ({ @@ -35,6 +36,7 @@ export const ListMobileItem = ({ showBorrowCapTooltips = false, showDebtCeilingTooltips = false, isIsolated, + onIconError, }: ListMobileItemProps) => { const { supplyCap, borrowCap, debtCeiling } = useAssetCaps(); return ( @@ -59,7 +61,7 @@ export const ListMobileItem = ({ href={ROUTES.reserveOverview(underlyingAsset, currentMarket)} sx={{ display: 'inline-flex', alignItems: 'center' }} > - + {name} diff --git a/src/hooks/app-data-provider/useAppDataProvider.tsx b/src/hooks/app-data-provider/useAppDataProvider.tsx index 0c930901ab..3dc230854d 100644 --- a/src/hooks/app-data-provider/useAppDataProvider.tsx +++ b/src/hooks/app-data-provider/useAppDataProvider.tsx @@ -1,5 +1,7 @@ +import type { EmodeMarketCategory, Market, MarketUserState, Reserve } from '@aave/graphql'; import { UserReserveData } from '@aave/math-utils'; -import React, { PropsWithChildren, useContext } from 'react'; +import { client } from 'pages/_app.page'; +import React, { PropsWithChildren, useContext, useMemo } from 'react'; import { EmodeCategory } from 'src/helpers/types'; import { useWeb3Context } from 'src/libs/hooks/useWeb3Context'; import { useRootStore } from 'src/store/root'; @@ -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 @@ -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; user?: ExtendedFormattedUser; @@ -62,6 +73,47 @@ export const AppDataProvider: React.FC = ({ children }) => { const currentMarketData = useRootStore((state) => state.currentMarketData); + const { data, isPending } = useMarketsData({ + client, + marketData: currentMarketData, + account: currentAccount, + }); + + const marketAddress = currentMarketData.addresses.LENDING_POOL.toLowerCase(); + const marketsList = useMemo(() => { + if (!data) { + return [] as Market[]; + } + const withItems = (data as { items?: Market[] }).items; + if (Array.isArray(withItems)) { + return withItems; + } + if (Array.isArray(data)) { + return data as Market[]; + } + return [] as Market[]; + }, [data]); + + const sdkMarket = marketsList.find((item) => item.address.toLowerCase() === marketAddress); + console.log('sdkMarket', sdkMarket); + 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 } = @@ -81,10 +133,19 @@ export const AppDataProvider: React.FC = ({ children }) => { const isReservesLoading = reservesDataLoading || formattedPoolReservesLoading; const isUserDataLoading = userReservesDataLoading || userSummaryLoading; + const loading = isPending || isReservesLoading || (!!currentAccount && isUserDataLoading); + return ( { + 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; + }, + }); +}; diff --git a/src/hooks/useMeritIncentives.ts b/src/hooks/useMeritIncentives.ts index 1d58be2f62..95d7d4b9ee 100644 --- a/src/hooks/useMeritIncentives.ts +++ b/src/hooks/useMeritIncentives.ts @@ -55,6 +55,7 @@ export enum MeritAction { AVALANCHE_SUPPLY_AUSD = 'avalanche-supply-ausd', AVALANCHE_SUPPLY_GHO = 'avalanche-supply-gho', AVALANCHE_BORROW_USDC = 'avalanche-borrow-usdc', + AVALANCHE_BORROW_EURC = 'avalanche-borrow-eurc', SONIC_SUPPLY_USDCE = 'sonic-supply-usdce', SONIC_SUPPLY_STS_BORROW_WS = 'sonic-supply-sts-borrow-ws', GNOSIS_BORROW_EURE = 'gnosis-borrow-eure', @@ -114,7 +115,8 @@ const antiLoopMessage = const antiLoopBorrowMessage = 'Supplying of some assets or holding of some token may impact the amount of rewards you are eligible for. Please check the forum post for the full eligibility criteria.'; - +const masivBorrowUsdcMessage = + 'Only new debt created since the campaign start will be rewarded. Supplying of some assets or holding of some token may impact the amount of rewards you are eligible for.'; const lbtcCbbtcCampaignMessage = 'You must supply LBTC and borrow cbBTC, while maintaining a health factor of 1.5 or below, in order to receive merit rewards. Please check the forum post for the full eligibility criteria.'; @@ -527,7 +529,7 @@ export const MERIT_DATA_MAP: Record 0) { + totalAmountIncentivesCampaigns.push(incentive.action); + } if (standardAPR == null) continue; if (totalMeritAPR === null) totalMeritAPR = 0; @@ -782,25 +798,33 @@ export const useMeritIncentives = ({ }, 0); const isBorrow = protocolAction === ProtocolAction.borrow; + const totalAPY = isBorrow ? protocolAPY - protocolIncentivesAPR - meritIncentivesAPY - (selfIncentivesAPY ?? 0) : protocolAPY + protocolIncentivesAPR + meritIncentivesAPY + (selfIncentivesAPY ?? 0); + let finalAction: MeritAction | undefined = undefined; + if (totalAmountIncentivesCampaigns.length >= 1) { + finalAction = totalAmountIncentivesCampaigns[0]; + } + + const actionMessages = incentives.reduce((acc, incentive) => { + acc[incentive.action] = { + customMessage: incentive.customMessage, + customForumLink: incentive.customForumLink, + }; + return acc; + }, {} as Record); + return { incentiveAPR: meritIncentivesAPY.toString(), rewardTokenAddress: incentives[0].rewardTokenAddress, rewardTokenSymbol: incentives[0].rewardTokenSymbol, - activeActions: incentives.map((incentive) => incentive.action), - actionMessages: incentives.reduce((acc, incentive) => { - acc[incentive.action] = { - customMessage: incentive.customMessage, - customForumLink: incentive.customForumLink, - }; - return acc; - }, {} as Record), - action: incentives[0].action, - customMessage: incentives[0].customMessage, - customForumLink: incentives[0].customForumLink, + activeActions: totalAmountIncentivesCampaigns, + actionMessages: actionMessages, + action: finalAction, + customMessage: finalAction ? actionMessages[finalAction]?.customMessage : undefined, + customForumLink: finalAction ? actionMessages[finalAction]?.customForumLink : undefined, variants: { selfAPY: selfIncentivesAPY }, breakdown: { diff --git a/src/hooks/useMerklIncentives.ts b/src/hooks/useMerklIncentives.ts index 77fb115f21..ba33429ea1 100644 --- a/src/hooks/useMerklIncentives.ts +++ b/src/hooks/useMerklIncentives.ts @@ -1,5 +1,6 @@ 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'; @@ -99,7 +100,27 @@ type WhitelistApiResponse = { additionalIncentiveInfo: Record; }; -const hardcodedIncentives: Record = {}; +const hardcodedIncentives: Record = { + [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 @@ -158,7 +179,19 @@ export const useMerklIncentives = ({ const hardcodedIncentive = rewardedAsset ? hardcodedIncentives[rewardedAsset] : undefined; if (hardcodedIncentive) { - return hardcodedIncentive; + const protocolIncentivesAPR = protocolIncentives.reduce((sum, inc) => { + return sum + (inc.incentiveAPR === 'Infinity' ? 0 : +inc.incentiveAPR); + }, 0); + const merklIncentivesAPY = convertAprToApy(0.1); + return { + ...hardcodedIncentive, + breakdown: { + protocolAPY, + protocolIncentivesAPR, + merklIncentivesAPR: merklIncentivesAPY, + totalAPY: protocolAPY + protocolIncentivesAPR + merklIncentivesAPY, + } as MerklIncentivesBreakdown, + } as ExtendedReserveIncentiveResponse; } const opportunities = merklOpportunities.filter( diff --git a/src/modules/dashboard/lists/ListMobileItemWrapper.tsx b/src/modules/dashboard/lists/ListMobileItemWrapper.tsx index 11df134304..3b138559fb 100644 --- a/src/modules/dashboard/lists/ListMobileItemWrapper.tsx +++ b/src/modules/dashboard/lists/ListMobileItemWrapper.tsx @@ -32,6 +32,7 @@ interface ListMobileItemWrapperProps { showDebtCeilingTooltips?: boolean; isIsolated?: boolean; showExternalIncentivesTooltips?: ExternalIncentivesTooltipsConfig; + onIconError?: () => void; } export const ListMobileItemWrapper = ({ @@ -54,6 +55,7 @@ export const ListMobileItemWrapper = ({ spkAirdrop: false, kernelPoints: false, }, + onIconError, }: ListMobileItemWrapperProps) => { const WarningComponent: React.FC = () => { const showFrozenTooltip = frozen && symbol !== 'renFIL'; @@ -94,6 +96,7 @@ export const ListMobileItemWrapper = ({ showSupplyCapTooltips={showSupplyCapTooltips} showBorrowCapTooltips={showBorrowCapTooltips} showDebtCeilingTooltips={showDebtCeilingTooltips} + onIconError={onIconError} > {children} diff --git a/src/modules/markets/MarketAssetsList.tsx b/src/modules/markets/MarketAssetsList.tsx index 3b3e8ac07b..72e7ba80f8 100644 --- a/src/modules/markets/MarketAssetsList.tsx +++ b/src/modules/markets/MarketAssetsList.tsx @@ -5,8 +5,8 @@ import { VariableAPYTooltip } from 'src/components/infoTooltips/VariableAPYToolt import { ListColumn } from 'src/components/lists/ListColumn'; import { ListHeaderTitle } from 'src/components/lists/ListHeaderTitle'; import { ListHeaderWrapper } from 'src/components/lists/ListHeaderWrapper'; -import { ComputedReserveData } from 'src/hooks/app-data-provider/useAppDataProvider'; +import { ReserveWithId } from '../../hooks/app-data-provider/useAppDataProvider'; import { MarketAssetsListItem } from './MarketAssetsListItem'; import { MarketAssetsListItemLoader } from './MarketAssetsListItemLoader'; import { MarketAssetsListMobileItem } from './MarketAssetsListMobileItem'; @@ -15,19 +15,19 @@ import { MarketAssetsListMobileItemLoader } from './MarketAssetsListMobileItemLo const listHeaders = [ { title: Asset, - sortKey: 'symbol', + sortKey: 'underlyingToken.symbol', // Cambiado de 'symbol' }, { title: Total supplied, - sortKey: 'totalLiquidityUSD', + sortKey: 'size.usd', // Cambiado de 'totalLiquidityUSD' }, { title: Supply APY, - sortKey: 'supplyAPY', + sortKey: 'supplyInfo.apy.value', // Cambiado de 'supplyAPY' }, { title: Total borrowed, - sortKey: 'totalDebtUSD', + sortKey: 'borrowInfo.total.usd', // Cambiado de 'totalDebtUSD' }, { title: ( @@ -37,12 +37,12 @@ const listHeaders = [ variant="subheader2" /> ), - sortKey: 'variableBorrowAPY', + sortKey: 'borrowInfo.apy.value', }, ]; type MarketAssetsListProps = { - reserves: ComputedReserveData[]; + reserves: ReserveWithId[]; loading: boolean; }; @@ -50,21 +50,40 @@ export default function MarketAssetsList({ reserves, loading }: MarketAssetsList const isTableChangedToCards = useMediaQuery('(max-width:1125px)'); const [sortName, setSortName] = useState(''); const [sortDesc, setSortDesc] = useState(false); + const getValue = (obj: ReserveWithId, path: string): unknown => { + return path.split('.').reduce((current: unknown, key: string) => { + return current && typeof current === 'object' && key in current + ? (current as Record)[key] + : undefined; + }, obj); + }; if (sortDesc) { - if (sortName === 'symbol') { - reserves.sort((a, b) => (a.symbol.toUpperCase() < b.symbol.toUpperCase() ? -1 : 1)); + if (sortName === 'underlyingToken.symbol') { + reserves.sort((a, b) => + a.underlyingToken.symbol.toUpperCase() < b.underlyingToken.symbol.toUpperCase() ? -1 : 1 + ); } else { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - reserves.sort((a, b) => a[sortName] - b[sortName]); + reserves.sort((a, b) => { + const aValue = Number(getValue(a, sortName)) || 0; + const bValue = Number(getValue(b, sortName)) || 0; + return aValue - bValue; + }); } } else { - if (sortName === 'symbol') { - reserves.sort((a, b) => (b.symbol.toUpperCase() < a.symbol.toUpperCase() ? -1 : 1)); + if (sortName === 'underlyingToken.symbol') { + reserves.sort((a, b) => + b.underlyingToken.symbol.toUpperCase() < a.underlyingToken.symbol.toUpperCase() ? -1 : 1 + ); } else { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - reserves.sort((a, b) => b[sortName] - a[sortName]); + reserves.sort((a, b) => { + const aValue = Number(getValue(a, sortName)) || 0; + const bValue = Number(getValue(b, sortName)) || 0; + return bValue - aValue; + }); } } @@ -95,8 +114,8 @@ export default function MarketAssetsList({ reserves, loading }: MarketAssetsList {listHeaders.map((col) => ( { const { data, isLoading, error } = useCoingeckoCategories(); - const { reserves, loading } = useAppDataContext(); + const { supplyReserves, loading } = useAppDataContext(); + const [trackEvent, currentMarket, currentMarketData, currentNetworkConfig] = useRootStore( useShallow((store) => [ store.trackEvent, @@ -60,19 +59,19 @@ export const MarketAssetsListContainer = () => { const displayGhoBanner = shouldDisplayGhoBanner(currentMarket, searchTerm); - const filteredData = reserves + const filteredData = supplyReserves // Filter out any non-active reserves - .filter((res) => res.isActive) + .filter((res) => !res.isPaused) // Filter out any hidden assets - .filter((res) => !isAssetHidden(currentMarketData.market, res.underlyingAsset)) + .filter((res) => !isAssetHidden(currentMarketData.market, res.underlyingToken.address)) // filter out any that don't meet search term criteria .filter((res) => { if (!searchTerm) return true; const term = searchTerm.toLowerCase().trim(); return ( - res.symbol.toLowerCase().includes(term) || - res.name.toLowerCase().includes(term) || - res.underlyingAsset.toLowerCase().includes(term) + res.underlyingToken.symbol.toLowerCase().includes(term) || + res.underlyingToken.name.toLowerCase().includes(term) || + res.underlyingToken.address.toLowerCase().includes(term) ); }) // Filter by category @@ -81,27 +80,39 @@ export const MarketAssetsListContainer = () => { selectedCategories.length === 0 || selectedCategories.some((category) => isAssetInCategoryDynamic( - res.symbol, + res.underlyingToken.symbol, category, data?.stablecoinSymbols, data?.ethCorrelatedSymbols ) ) ) + // Add initial sorting by total supplied in USD descending + .sort((a, b) => { + const aValue = Number(a.size.usd) || 0; + const bValue = Number(b.size.usd) || 0; + return bValue - aValue; + }) // Transform the object for list to consume it .map((reserve) => ({ ...reserve, - ...(reserve.isWrappedBaseAsset - ? fetchIconSymbolAndName({ - symbol: currentNetworkConfig.baseAssetSymbol, - underlyingAsset: API_ETH_MOCK_ADDRESS.toLowerCase(), - }) + ...(reserve.acceptsNative + ? { + underlyingToken: { + ...reserve.underlyingToken, + name: currentNetworkConfig.baseAssetSymbol, // e.g., "Ethereum" + symbol: currentNetworkConfig.baseAssetSymbol, // e.g., "ETH" + imageUrl: currentNetworkConfig.baseAssetSymbol.toLowerCase(), // This might need adjustment based on your icon system + }, + } : {}), })); + // const marketFrozen = !reserves.some((reserve) => !reserve.isFrozen); // const showFrozenMarketWarning = // marketFrozen && ['Fantom', 'Ethereum AMM'].includes(currentMarketData.marketTitle); const unfrozenReserves = filteredData.filter((r) => !r.isFrozen && !r.isPaused); + const [showFrozenMarketsToggle, setShowFrozenMarketsToggle] = useState(false); const handleChange = () => { diff --git a/src/modules/markets/MarketAssetsListItem.tsx b/src/modules/markets/MarketAssetsListItem.tsx index 9fbf9a1ca9..ddde2b6364 100644 --- a/src/modules/markets/MarketAssetsListItem.tsx +++ b/src/modules/markets/MarketAssetsListItem.tsx @@ -2,6 +2,7 @@ import { ProtocolAction } from '@aave/contract-helpers'; import { Trans } from '@lingui/macro'; import { Box, Button, Typography } from '@mui/material'; import { useRouter } from 'next/router'; +import { useState } from 'react'; import { KernelAirdropTooltip } from 'src/components/infoTooltips/KernelAirdropTooltip'; import { OffboardingTooltip } from 'src/components/infoTooltips/OffboardingToolTip'; import { RenFILToolTip } from 'src/components/infoTooltips/RenFILToolTip'; @@ -12,10 +13,12 @@ import { NoData } from 'src/components/primitives/NoData'; import { ReserveSubheader } from 'src/components/ReserveSubheader'; import { AssetsBeingOffboarded } from 'src/components/Warnings/OffboardingWarning'; import { useRootStore } from 'src/store/root'; +import { fetchIconSymbolAndName } from 'src/ui-config/reservePatches'; import { MARKETS } from 'src/utils/events'; import { showExternalIncentivesTooltip } from 'src/utils/utils'; import { useShallow } from 'zustand/shallow'; +import { mapAaveProtocolIncentives } from '../../components/incentives/incentives.helper'; import { IncentivesCard } from '../../components/incentives/IncentivesCard'; import { AMPLToolTip } from '../../components/infoTooltips/AMPLToolTip'; import { ListColumn } from '../../components/lists/ListColumn'; @@ -23,25 +26,34 @@ import { ListItem } from '../../components/lists/ListItem'; import { FormattedNumber } from '../../components/primitives/FormattedNumber'; import { Link, ROUTES } from '../../components/primitives/Link'; import { TokenIcon } from '../../components/primitives/TokenIcon'; -import { ComputedReserveData } from '../../hooks/app-data-provider/useAppDataProvider'; +import { ReserveWithId } from '../../hooks/app-data-provider/useAppDataProvider'; -export const MarketAssetsListItem = ({ ...reserve }: ComputedReserveData) => { +export const MarketAssetsListItem = ({ ...reserve }: ReserveWithId) => { const router = useRouter(); const [trackEvent, currentMarket] = useRootStore( useShallow((store) => [store.trackEvent, store.currentMarket]) ); - - const offboardingDiscussion = AssetsBeingOffboarded[currentMarket]?.[reserve.symbol]; + const [useFetchIcon, setUseFetchIcon] = useState(false); + const offboardingDiscussion = + AssetsBeingOffboarded[currentMarket]?.[reserve.underlyingToken.symbol]; const externalIncentivesTooltipsSupplySide = showExternalIncentivesTooltip( - reserve.symbol, + reserve.underlyingToken.symbol, currentMarket, ProtocolAction.supply ); const externalIncentivesTooltipsBorrowSide = showExternalIncentivesTooltip( - reserve.symbol, + reserve.underlyingToken.symbol, currentMarket, ProtocolAction.borrow ); + const { iconSymbol } = fetchIconSymbolAndName({ + underlyingAsset: reserve.underlyingToken.address, + symbol: reserve.underlyingToken.symbol, + name: reserve.underlyingToken.name, + }); + + const supplyProtocolIncentives = mapAaveProtocolIncentives(reserve.incentives, 'supply'); + const borrowProtocolIncentives = mapAaveProtocolIncentives(reserve.incentives, 'borrow'); return ( { onClick={() => { trackEvent(MARKETS.DETAILS_NAVIGATION, { type: 'Row', - assetName: reserve.name, - asset: reserve.underlyingAsset, + assetName: reserve.underlyingToken.name, + asset: reserve.underlyingToken.address.toLowerCase(), market: currentMarket, }); - router.push(ROUTES.reserveOverview(reserve.underlyingAsset, currentMarket)); + router.push( + ROUTES.reserveOverview(reserve.underlyingToken.address.toLowerCase(), currentMarket) + ); }} sx={{ cursor: 'pointer' }} button - data-cy={`marketListItemListItem_${reserve.symbol.toUpperCase()}`} + data-cy={`marketListItemListItem_${reserve.underlyingToken.symbol.toUpperCase()}`} > - + {!useFetchIcon ? ( + { + setUseFetchIcon(true); + }} + /> + ) : ( + + )} - {reserve.name} + {reserve.underlyingToken.name} { }} > - {reserve.symbol} - {reserve.isIsolated && ( + {reserve.underlyingToken.symbol} + {reserve.isolationModeConfig?.canBeCollateral && ( @@ -82,22 +106,22 @@ export const MarketAssetsListItem = ({ ...reserve }: ComputedReserveData) => { - {reserve.symbol === 'AMPL' && } - {reserve.symbol === 'renFIL' && } + {reserve.underlyingToken.symbol === 'AMPL' && } + {reserve.underlyingToken.symbol === 'renFIL' && } {offboardingDiscussion && } - - + + { - {reserve.borrowingEnabled || Number(reserve.totalDebt) > 0 ? ( + {reserve.borrowInfo ? ( <> - {' '} - + {' '} + ) : ( @@ -125,10 +153,14 @@ export const MarketAssetsListItem = ({ ...reserve }: ComputedReserveData) => { 0 ? reserve.variableBorrowAPY : '-1'} - incentives={reserve.vIncentivesData || []} - address={reserve.variableDebtTokenAddress} - symbol={reserve.symbol} + value={ + Number(reserve.borrowInfo?.total.amount.value) > 0 + ? String(reserve.borrowInfo?.apy.value) + : '-1' + } + incentives={borrowProtocolIncentives} + address={reserve.vToken.address} + symbol={reserve.underlyingToken.symbol} variant="main16" symbolsVariant="secondary16" tooltip={ @@ -140,21 +172,24 @@ export const MarketAssetsListItem = ({ ...reserve }: ComputedReserveData) => { market={currentMarket} protocolAction={ProtocolAction.borrow} /> - {!reserve.borrowingEnabled && - Number(reserve.totalVariableDebt) > 0 && - !reserve.isFrozen && } + {reserve.borrowInfo?.borrowingState === 'DISABLED' && !reserve.isFrozen && ( + + )}