diff --git a/.github/workflows/crowdin-download.yml b/.github/workflows/crowdin-download.yml index d1187147a5..dd42b77687 100644 --- a/.github/workflows/crowdin-download.yml +++ b/.github/workflows/crowdin-download.yml @@ -13,6 +13,7 @@ jobs: download-from-crowdin: name: Download sources from Crowdin runs-on: ubuntu-latest + if: github.repository == 'aave/interface' steps: - name: Checkout diff --git a/package.json b/package.json index 7f5aac95e3..bb56609835 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,8 @@ "test:coverage": "jest --coverage" }, "dependencies": { - "@aave/contract-helpers": "1.30.5", - "@aave/math-utils": "1.30.5", + "@aave/contract-helpers": "1.32.1", + "@aave/math-utils": "1.32.1", "@bgd-labs/aave-address-book": "4.10.0", "@emotion/cache": "11.10.3", "@emotion/react": "11.10.4", diff --git a/public/icons/other/ethena.svg b/public/icons/other/ethena.svg new file mode 100644 index 0000000000..d16afab082 --- /dev/null +++ b/public/icons/other/ethena.svg @@ -0,0 +1 @@ + diff --git a/public/icons/tokens/rez.svg b/public/icons/tokens/rez.svg new file mode 100644 index 0000000000..820ff2fe74 --- /dev/null +++ b/public/icons/tokens/rez.svg @@ -0,0 +1 @@ + diff --git a/src/components/UserDisplay.tsx b/src/components/UserDisplay.tsx index a2273a27c1..efe15fc752 100644 --- a/src/components/UserDisplay.tsx +++ b/src/components/UserDisplay.tsx @@ -42,6 +42,7 @@ export const UserDisplay: React.FC = ({ } invisibleBadge={!readOnlyMode} diff --git a/src/components/incentives/EthenaIncentivesTooltipContent.tsx b/src/components/incentives/EthenaIncentivesTooltipContent.tsx new file mode 100644 index 0000000000..ea9aa346c6 --- /dev/null +++ b/src/components/incentives/EthenaIncentivesTooltipContent.tsx @@ -0,0 +1,29 @@ +import { Trans } from '@lingui/macro'; +import { Box } from '@mui/material'; + +import { Link } from '../primitives/Link'; + +export const EthenaAirdropTooltipContent = ({ points }: { points: number }) => { + return ( + + {`This asset is eligible for ${({points}x)} Ethena Rewards.\n`} +
+ {'Learn more about Ethena Rewards program'}{' '} + + {'here'} + + {'.'} +
+
+ + {`Aave Labs does not + guarantee the program and accepts no liability.\n`} + +
+ ); +}; diff --git a/src/components/incentives/IncentivesButton.tsx b/src/components/incentives/IncentivesButton.tsx index 71c163ef62..4f8149ea7e 100644 --- a/src/components/incentives/IncentivesButton.tsx +++ b/src/components/incentives/IncentivesButton.tsx @@ -4,6 +4,7 @@ import { ReserveIncentiveResponse } from '@aave/math-utils/dist/esm/formatters/i import { DotsHorizontalIcon } from '@heroicons/react/solid'; import { Box, SvgIcon, Typography } from '@mui/material'; import { useState } from 'react'; +import { useEthenaIncentives } from 'src/hooks/useEthenaIncentives'; import { useMeritIncentives } from 'src/hooks/useMeritIncentives'; import { useZkSyncIgniteIncentives } from 'src/hooks/useZkSyncIgniteIncentives'; import { useRootStore } from 'src/store/root'; @@ -12,6 +13,7 @@ import { DASHBOARD } from 'src/utils/mixPanelEvents'; import { ContentWithTooltip } from '../ContentWithTooltip'; import { FormattedNumber } from '../primitives/FormattedNumber'; import { TokenIcon } from '../primitives/TokenIcon'; +import { EthenaAirdropTooltipContent } from './EthenaIncentivesTooltipContent'; import { getSymbolMap, IncentivesTooltipContent } from './IncentivesTooltipContent'; import { MeritIncentivesTooltipContent } from './MeritIncentivesTooltipContent'; import { ZkSyncIgniteIncentivesTooltipContent } from './ZkSyncIgniteIncentivesTooltipContent'; @@ -92,6 +94,26 @@ export const ZkIgniteIncentivesButton = (params: { ); }; +export const EthenaIncentivesButton = ({ rewardedAsset }: { rewardedAsset?: string }) => { + const [open, setOpen] = useState(false); + const points = useEthenaIncentives(rewardedAsset); + + if (!points) { + return null; + } + + return ( + } + withoutHover + setOpen={setOpen} + open={open} + > + + + ); +}; + export const IncentivesButton = ({ incentives, symbol, displayBlank }: IncentivesButtonProps) => { const [open, setOpen] = useState(false); @@ -271,3 +293,41 @@ const Content = ({
); }; + +const ContentEthenaButton = ({ points }: { points: number }) => { + const [open, setOpen] = useState(false); + const trackEvent = useRootStore((store) => store.trackEvent); + + return ( + ({ + p: { xs: '0 4px', xsm: '2px 4px' }, + border: `1px solid ${open ? theme.palette.action.disabled : theme.palette.divider}`, + borderRadius: '4px', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + transition: 'opacity 0.2s ease', + bgcolor: open ? 'action.hover' : 'transparent', + '&:hover': { + bgcolor: 'action.hover', + borderColor: 'action.disabled', + }, + })} + onClick={() => { + trackEvent(DASHBOARD.VIEW_LM_DETAILS_DASHBOARD, {}); + setOpen(!open); + }} + > + + + {`${points}x`} + + + + ethena-icon + + + ); +}; diff --git a/src/components/incentives/IncentivesCard.tsx b/src/components/incentives/IncentivesCard.tsx index d6bda0c6af..2129d2a6bf 100644 --- a/src/components/incentives/IncentivesCard.tsx +++ b/src/components/incentives/IncentivesCard.tsx @@ -6,6 +6,7 @@ import { ReactNode } from 'react'; import { FormattedNumber } from '../primitives/FormattedNumber'; import { NoData } from '../primitives/NoData'; import { + EthenaIncentivesButton, IncentivesButton, MeritIncentivesButton, ZkIgniteIncentivesButton, @@ -85,6 +86,7 @@ export const IncentivesCard = ({ rewardedAsset={address} protocolAction={protocolAction} /> + ); diff --git a/src/components/incentives/IncentivesTooltipContent.tsx b/src/components/incentives/IncentivesTooltipContent.tsx index 3a46bcc9d7..738a2aa65f 100644 --- a/src/components/incentives/IncentivesTooltipContent.tsx +++ b/src/components/incentives/IncentivesTooltipContent.tsx @@ -48,6 +48,21 @@ const IncentivesSymbolMap: { symbol: 'aPYUSD', aToken: true, }, + aArbWETH: { + tokenIconSymbol: 'WETH', + symbol: 'aWETH', + aToken: true, + }, + aArbwstETH: { + tokenIconSymbol: 'wstETH', + symbol: 'awstETH', + aToken: true, + }, + aBaswstETH: { + tokenIconSymbol: 'wstETH', + symbol: 'awstETH', + aToken: true, + }, aAvaSAVAX: { tokenIconSymbol: 'sAVAX', symbol: 'asAVAX', diff --git a/src/hooks/app-data-provider/useAppDataProvider.tsx b/src/hooks/app-data-provider/useAppDataProvider.tsx index d9b613ceea..8f7bd65206 100644 --- a/src/hooks/app-data-provider/useAppDataProvider.tsx +++ b/src/hooks/app-data-provider/useAppDataProvider.tsx @@ -1,10 +1,12 @@ import { + ComputedUserReserve, FormattedGhoReserveData, FormattedGhoUserData, formatUserSummaryWithDiscount, USD_DECIMALS, UserReserveData, } from '@aave/math-utils'; +import { AaveV3Ethereum } from '@bgd-labs/aave-address-book'; import { formatUnits } from 'ethers/lib/utils'; import React, { PropsWithChildren, useContext } from 'react'; import { EmodeCategory } from 'src/helpers/types'; @@ -140,9 +142,42 @@ export const AppDataProvider: React.FC = ({ children }) => { formatUnits(baseCurrencyData.marketReferenceCurrencyPriceInUsd, USD_DECIMALS) ), }); + + const userGhoReserve = user.userReservesData.find( + (r) => r.reserve.underlyingAsset === AaveV3Ethereum.ASSETS.GHO.UNDERLYING.toLowerCase() + ); + + if (!userGhoReserve) { + throw new Error('GHO reserve not found in user reserves data'); + } + + const mergeUserReserves = (reserve: ComputedUserReserve) => { + if (reserve.underlyingAsset !== AaveV3Ethereum.ASSETS.GHO.UNDERLYING.toLowerCase()) { + return reserve; + } + + if (reserve.variableBorrows === '0') { + return reserve; + } + + // This amount takes into account the discount applied on the accrued interest. + const userGhoDebtBalance = formattedGhoUserData.userGhoBorrowBalance.toString(); + + // Merge with the user reserves so the correct debt balance can be shown throughout the app. + return { + ...reserve, + variableBorrows: userGhoDebtBalance, + variableBorrowsUSD: userGhoDebtBalance, + totalBorrowsUSD: userGhoDebtBalance, + totalBorrows: userGhoDebtBalance, + totalBorrowsMarketReferenceCurrency: userGhoDebtBalance, + }; + }; + user = { ...user, ...userSummaryWithDiscount, + userReservesData: user.userReservesData.map(mergeUserReserves), }; } } diff --git a/src/hooks/pool/useUserYield.ts b/src/hooks/pool/useUserYield.ts index 6cc2e91d1d..e8fb7c3170 100644 --- a/src/hooks/pool/useUserYield.ts +++ b/src/hooks/pool/useUserYield.ts @@ -155,7 +155,7 @@ export const useUserYields = ( ) => { return formatUserYield(formattedPoolReserves, undefined, undefined, user, marketData.market); }; - if (GHO_MINTING_MARKETS.includes(marketData.marketTitle)) + if (GHO_MINTING_MARKETS.includes(marketData.market)) return combineQueries( [ elem, diff --git a/src/hooks/useEthenaIncentives.ts b/src/hooks/useEthenaIncentives.ts new file mode 100644 index 0000000000..e431a68264 --- /dev/null +++ b/src/hooks/useEthenaIncentives.ts @@ -0,0 +1,18 @@ +import { AaveV3Ethereum, AaveV3EthereumLido } from '@bgd-labs/aave-address-book'; + +const getEthenaData = (assetAddress: string): number | undefined => + ETHENA_DATA_MAP.get(assetAddress); + +const ETHENA_DATA_MAP: Map = new Map([ + [AaveV3Ethereum.ASSETS.USDe.A_TOKEN, 25], + [AaveV3Ethereum.ASSETS.sUSDe.A_TOKEN, 5], + [AaveV3EthereumLido.ASSETS.sUSDe.A_TOKEN, 5], +]); + +export const useEthenaIncentives = (rewardedAsset?: string) => { + if (!rewardedAsset) { + return undefined; + } + + return getEthenaData(rewardedAsset); +}; diff --git a/src/hooks/useMeritIncentives.ts b/src/hooks/useMeritIncentives.ts index 1f28f6ed50..01ddbd6b98 100644 --- a/src/hooks/useMeritIncentives.ts +++ b/src/hooks/useMeritIncentives.ts @@ -1,6 +1,12 @@ import { ProtocolAction } from '@aave/contract-helpers'; import { ReserveIncentiveResponse } from '@aave/math-utils/dist/esm/formatters/incentive/calculate-reserve-incentives'; -import { AaveV3Avalanche, AaveV3Base, AaveV3Ethereum } from '@bgd-labs/aave-address-book'; +import { + AaveV3Arbitrum, + AaveV3Avalanche, + AaveV3Base, + AaveV3Ethereum, + AaveV3EthereumLido, +} from '@bgd-labs/aave-address-book'; import { useQuery } from '@tanstack/react-query'; import { CustomMarket } from 'src/ui-config/marketsConfig'; @@ -8,10 +14,17 @@ export enum MeritAction { ETHEREUM_STKGHO = 'ethereum-stkgho', ETHEREUM_SUPPLY_PYUSD = 'ethereum-supply-pyusd', ETHEREUM_SUPPLY_ETHX = 'ethereum-supply-ethx', + ETHEREUM_PRIME_SUPPLY_ETH = 'ethereum-prime-supply-weth', + ETHEREUM_PRIME_SUPPLY_EZETH = 'ethereum-prime-supply-ezeth', SUPPLY_CBBTC_BORROW_USDC = 'ethereum-supply-cbbtc-borrow-usdc', SUPPLY_WBTC_BORROW_USDT = 'ethereum-supply-wbtc-borrow-usdt', + ARBITRUM_SUPPLY_ETH = 'arbitrum-supply-weth', + ARBITRUM_SUPPLY_WSTETH = 'arbitrum-supply-wsteth', + ARBITRUM_SUPPLY_EZETH = 'arbitrum-supply-ezeth', BASE_SUPPLY_CBBTC = 'base-supply-cbbtc', BASE_SUPPLY_USDC = 'base-supply-usdc', + BASE_SUPPLY_WSTETH = 'base-supply-wsteth', + BASE_SUPPLY_EZETH = 'base-supply-ezeth', BASE_BORROW_USDC = 'base-borrow-usdc', AVALANCHE_SUPPLY_BTCB = 'avalanche-supply-btcb', AVALANCHE_SUPPLY_USDC = 'avalanche-supply-usdc', @@ -44,6 +57,11 @@ export type MeritReserveIncentiveData = Omit MERIT_DATA_MAP[market]?.[symbol]; +const antiLoopMessage = + 'Borrowing of some assets may impact the amount of rewards you are eligible for. Please check the forum post for the full eligibility criteria.'; +const joinedEthCorrelatedIncentiveForumLink = + 'https://governance.aave.com/t/arfc-set-aci-as-emission-manager-for-liquidity-mining-programs/17898/56'; + const MERIT_DATA_MAP: Record> = { [CustomMarket.proto_mainnet_v3]: { GHO: [ @@ -99,8 +117,7 @@ const MERIT_DATA_MAP: Record protocolAction: ProtocolAction.supply, customForumLink: 'https://governance.aave.com/t/arfc-pyusd-reserve-configuration-update-incentive-campaign/19573', - customMessage: - 'Borrowing of some assets may impact the amount of rewards you are eligible for. Please check the forum post for the full eligibility criteria.', + customMessage: antiLoopMessage, }, ], ETHx: [ @@ -112,6 +129,80 @@ const MERIT_DATA_MAP: Record }, ], }, + [CustomMarket.proto_lido_v3]: { + ETH: [ + { + action: MeritAction.ETHEREUM_PRIME_SUPPLY_ETH, + rewardTokenAddress: AaveV3EthereumLido.ASSETS.WETH.A_TOKEN, + rewardTokenSymbol: 'aEthLidoWETH', + protocolAction: ProtocolAction.supply, + customMessage: antiLoopMessage, + customForumLink: joinedEthCorrelatedIncentiveForumLink, + }, + ], + WETH: [ + { + action: MeritAction.ETHEREUM_PRIME_SUPPLY_ETH, + rewardTokenAddress: AaveV3EthereumLido.ASSETS.WETH.A_TOKEN, + rewardTokenSymbol: 'aEthLidoWETH', + protocolAction: ProtocolAction.supply, + customMessage: antiLoopMessage, + customForumLink: joinedEthCorrelatedIncentiveForumLink, + }, + ], + ezETH: [ + { + action: MeritAction.ETHEREUM_PRIME_SUPPLY_EZETH, + rewardTokenAddress: '0x3B50805453023a91a8bf641e279401a0b23FA6F9', // Renzo (REZ) + rewardTokenSymbol: 'REZ', + protocolAction: ProtocolAction.supply, + customMessage: antiLoopMessage, + customForumLink: joinedEthCorrelatedIncentiveForumLink, + }, + ], + }, + [CustomMarket.proto_arbitrum_v3]: { + ETH: [ + { + action: MeritAction.ARBITRUM_SUPPLY_ETH, + rewardTokenAddress: AaveV3Arbitrum.ASSETS.WETH.A_TOKEN, + rewardTokenSymbol: 'aArbWETH', + protocolAction: ProtocolAction.supply, + customMessage: antiLoopMessage, + customForumLink: joinedEthCorrelatedIncentiveForumLink, + }, + ], + WETH: [ + { + action: MeritAction.ARBITRUM_SUPPLY_ETH, + rewardTokenAddress: AaveV3Arbitrum.ASSETS.WETH.A_TOKEN, + rewardTokenSymbol: 'aArbWETH', + protocolAction: ProtocolAction.supply, + customMessage: antiLoopMessage, + customForumLink: joinedEthCorrelatedIncentiveForumLink, + }, + ], + wstETH: [ + { + action: MeritAction.ARBITRUM_SUPPLY_WSTETH, + rewardTokenAddress: AaveV3Ethereum.ASSETS.wstETH.UNDERLYING, + rewardTokenSymbol: 'aArbwstETH', + protocolAction: ProtocolAction.supply, + customMessage: antiLoopMessage, + customForumLink: joinedEthCorrelatedIncentiveForumLink, + }, + ], + ezETH: [ + { + action: MeritAction.ARBITRUM_SUPPLY_EZETH, + rewardTokenAddress: '0x3B50805453023a91a8bf641e279401a0b23FA6F9', // Renzo (REZ) + rewardTokenSymbol: 'REZ', + protocolAction: ProtocolAction.supply, + customMessage: antiLoopMessage, + customForumLink: joinedEthCorrelatedIncentiveForumLink, + }, + ], + }, [CustomMarket.proto_base_v3]: { cbBTC: [ { @@ -135,6 +226,26 @@ const MERIT_DATA_MAP: Record protocolAction: ProtocolAction.borrow, }, ], + wstETH: [ + { + action: MeritAction.BASE_SUPPLY_WSTETH, + rewardTokenAddress: AaveV3Base.ASSETS.wstETH.UNDERLYING, + rewardTokenSymbol: 'aBaswstETH', + protocolAction: ProtocolAction.supply, + customMessage: antiLoopMessage, + customForumLink: joinedEthCorrelatedIncentiveForumLink, + }, + ], + ezETH: [ + { + action: MeritAction.BASE_SUPPLY_EZETH, + rewardTokenAddress: '0x3B50805453023a91a8bf641e279401a0b23FA6F9', // Renzo (REZ) + rewardTokenSymbol: 'REZ', + protocolAction: ProtocolAction.supply, + customMessage: antiLoopMessage, + customForumLink: joinedEthCorrelatedIncentiveForumLink, + }, + ], }, [CustomMarket.proto_avalanche_v3]: { ['BTC.b']: [ diff --git a/src/layouts/MainLayout.tsx b/src/layouts/MainLayout.tsx index 7228d00326..6b3e8f94bf 100644 --- a/src/layouts/MainLayout.tsx +++ b/src/layouts/MainLayout.tsx @@ -2,23 +2,43 @@ import { Box } from '@mui/material'; import React, { ReactNode } from 'react'; import AnalyticsConsent from 'src/components/Analytics/AnalyticsConsent'; import { FeedbackModal } from 'src/layouts/FeedbackDialog'; +import { useRootStore } from 'src/store/root'; import { FORK_ENABLED } from 'src/utils/marketsAndNetworksConfig'; import { AppFooter } from './AppFooter'; import { AppHeader } from './AppHeader'; -// import TopBarNotify from './TopBarNotify'; +import TopBarNotify from './TopBarNotify'; +const SwitchIcon = () => ( + + + +); export function MainLayout({ children }: { children: ReactNode }) { - // const APP_BANNER_VERSION = '5.0.0'; - + const APP_BANNER_VERSION = '6.0.0'; + const currentMarket = useRootStore((state) => state.currentMarket); return ( <> - {/* */} + {currentMarket === 'proto_base_v3' && ( + } + /> + )} + {children} diff --git a/src/layouts/TopBarNotify.tsx b/src/layouts/TopBarNotify.tsx index e9322acc1e..6149460470 100644 --- a/src/layouts/TopBarNotify.tsx +++ b/src/layouts/TopBarNotify.tsx @@ -17,6 +17,7 @@ interface TopBarNotifyProps { buttonText?: string; bannerVersion: string; icon?: string; + customIcon?: ReactNode; } export default function TopBarNotify({ @@ -25,6 +26,7 @@ export default function TopBarNotify({ buttonText, bannerVersion, icon, + customIcon, }: TopBarNotifyProps) { const { breakpoints } = useTheme(); const md = useMediaQuery(breakpoints.down('md')); @@ -86,6 +88,8 @@ export default function TopBarNotify({ > {notifyText} + {customIcon ? customIcon : null} + {icon && !sm ? : ''} {learnMoreLink && md ? ( @@ -99,6 +103,7 @@ export default function TopBarNotify({ ) : null} + {!md && learnMoreLink ? (