diff --git a/apps/main/src/dex/components/MonadBannerAlert.tsx b/apps/main/src/dex/components/MonadBannerAlert.tsx deleted file mode 100644 index 7ab1161475..0000000000 --- a/apps/main/src/dex/components/MonadBannerAlert.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Stack } from '@mui/material' -import { t } from '@ui-kit/lib/i18n' -import { Banner } from '@ui-kit/shared/ui/Banner' -import { Chain } from '@ui-kit/utils' -import { usePoolIdByAddressOrId } from '../hooks/usePoolIdByAddressOrId' - -const MonadBannerAlert = ({ chainId, poolIdOrAddress }: { chainId: number; poolIdOrAddress: string }) => { - const poolId = usePoolIdByAddressOrId({ chainId, poolIdOrAddress }) - const showFactoryStableNg11Banner = poolId === 'factory-stable-ng-11' && chainId === Chain.Monad - - if (showFactoryStableNg11Banner) - return ( - - - {t`This pool has been misconfigured. It has been set to withdraw only. To minimize impact withdraw in balanced proportion instead of single sided.`} - - - ) -} - -export default MonadBannerAlert diff --git a/apps/main/src/dex/components/PagePool/index.tsx b/apps/main/src/dex/components/PagePool/index.tsx index 7f40d4d304..76d69cf8ac 100644 --- a/apps/main/src/dex/components/PagePool/index.tsx +++ b/apps/main/src/dex/components/PagePool/index.tsx @@ -21,6 +21,7 @@ import useStore from '@/dex/store/useStore' import { getChainPoolIdActiveKey } from '@/dex/utils' import { getPath } from '@/dex/utils/utilsRouter' import { ManageGauge } from '@/dex/widgets/manage-gauge' +import { notFalsy } from '@curvefi/prices-api/objects.util' import Stack from '@mui/material/Stack' import AlertBox from '@ui/AlertBox' import { AppFormContentWrapper } from '@ui/AppForm' @@ -45,7 +46,7 @@ import { t } from '@ui-kit/lib/i18n' import { REFRESH_INTERVAL } from '@ui-kit/lib/model' import { type TabOption, TabsSwitcher } from '@ui-kit/shared/ui/TabsSwitcher' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' -import MonadBannerAlert from '../MonadBannerAlert' +import { PoolAlertBanner } from '../PoolAlertBanner' const DEFAULT_SEED: Seed = { isSeed: null, loaded: false } const { MaxWidth } = SizesAndSpaces @@ -204,10 +205,15 @@ const Transfer = (pageTransferProps: PageTransferProps) => { ) - return ( <> - + {poolAlert?.banner && ( + + )} = { + error: 'alert', + danger: 'alert', + warning: 'warning', + info: 'info', + '': 'info', +} + +export const PoolAlertBanner = ({ + banner, + poolAlertBannerKey, + alertType, +}: { + banner: NonNullable + poolAlertBannerKey: string + alertType: AlertType +}) => { + const { shouldShowBanner, dismissBanner } = useDismissBanner(poolAlertBannerKey, ONE_DAY_MS) + const isDesktop = useIsDesktop() + // eslint-disable-next-line react-hooks/exhaustive-deps -- isDesktop triggers re-query when header changes (desktop ↔ mobile) + const portalTarget = useMemo(() => document.getElementsByTagName('header')[0], [isDesktop]) + const severity = alertTypeToBannerSeverity[alertType] + + return ( + shouldShowBanner && + portalTarget && + createPortal( + + {banner.title} + , + portalTarget, + ) + ) +} diff --git a/apps/main/src/dex/components/pool-alert-messages.tsx b/apps/main/src/dex/components/pool-alert-messages.tsx new file mode 100644 index 0000000000..667dfdeb69 --- /dev/null +++ b/apps/main/src/dex/components/pool-alert-messages.tsx @@ -0,0 +1,49 @@ +import Button from '@mui/material/Button' +import Link from '@mui/material/Link' +import Stack from '@mui/material/Stack' +import { ArrowTopRightIcon } from '@ui-kit/shared/icons/ArrowTopRightIcon' +import { RouterLink } from '@ui-kit/shared/ui/RouterLink' +import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' + +const { Spacing } = SizesAndSpaces + +export const PoolAlertMessage = ({ children }: { children: React.ReactNode }) => ( + + {children} + +) + +export const ExternalLink = ({ href, children }: { href: string; children: React.ReactNode }) => ( + +) + +export const InternalLink = ({ href, children }: { href: string; children: React.ReactNode }) => ( + +) diff --git a/apps/main/src/dex/hooks/usePoolAlert.tsx b/apps/main/src/dex/hooks/usePoolAlert.tsx index eef576711d..8f3953e5f3 100644 --- a/apps/main/src/dex/hooks/usePoolAlert.tsx +++ b/apps/main/src/dex/hooks/usePoolAlert.tsx @@ -1,13 +1,10 @@ import { useMemo } from 'react' -import { styled } from 'styled-components' import { ROUTE } from '@/dex/constants' import { PoolAlert, PoolData, PoolDataCache, type UrlParams } from '@/dex/types/main.types' -import { getPath } from '@/dex/utils/utilsRouter' -import Box from '@ui/Box' -import { ExternalLink, InternalLink } from '@ui/Link' -import { breakpoints } from '@ui/utils' import { useParams } from '@ui-kit/hooks/router' -import { shortenAddress } from '@ui-kit/utils' +import { t, Trans } from '@ui-kit/lib/i18n' +import { getInternalUrl } from '@ui-kit/shared/routes' +import { ExternalLink, InternalLink, PoolAlertMessage } from '../components/pool-alert-messages' const usePoolAlert = (poolData?: PoolData | PoolDataCache) => { const params = useParams() @@ -22,16 +19,16 @@ const usePoolAlert = (poolData?: PoolData | PoolDataCache) => { isDisableDeposit: true, isInformationOnly: true, isCloseOnTooltipOnly: true, + banner: { + title: t`zStables Attack`, + subtitle: t`zStables (zETH, UZD) have encountered an attack. `, + learnMoreUrl: 'https://twitter.com/ZunamiProtocol/status/1690863406079696896?s=20', + }, + /* TODO: use Typography component instead of p tag */ message: ( - -
- zStables (zETH, UZD) have encountered an attack. The collateral remain secure, we delve into the ongoing - investigation. —Zunami Protocol{' '} - - https://twitter.com/ZunamiProtocol/status/1690863406079696896?s=20 - -
-
+ +

{t`Deposit disabled.`}

+
), }) const geistFinanceAlert = (): PoolAlert => ({ @@ -40,41 +37,42 @@ const usePoolAlert = (poolData?: PoolData | PoolDataCache) => { isDisableSwap: true, isInformationOnly: true, isCloseOnTooltipOnly: true, + banner: { + title: t`Geist Finance Disabled`, + subtitle: t`Deposit and swap are disabled.`, + learnMoreUrl: 'https://twitter.com/geistfinance', + }, message: ( - -
- Deposit and swap are disabled, see{' '} - - https://twitter.com/geistfinance - {' '} - for additional information. -
-
+ +

{t`This pool is in withdraw only mode.`}

+
), }) const yPrismaAlert = (): PoolAlert => { - const redirectPathname = params && getPath(params, `${ROUTE.PAGE_POOLS}/factory-v2-372${ROUTE.PAGE_POOL_DEPOSIT}`) - const redirectText = `PRISMA/yPRISMA pool (${shortenAddress('0x69833361991ed76f9e8dbbcdf9ea1520febfb4a7')})` + const prismaPoolHref = getInternalUrl( + 'dex', + params.network, + `${ROUTE.PAGE_POOLS}/factory-v2-372${ROUTE.PAGE_POOL_DEPOSIT}`, + ) return { isDisableDeposit: true, isInformationOnly: true, isCloseOnTooltipOnly: true, alertType: 'warning', message: ( - -
- This pool has been deprecated. Please use the{' '} - {redirectPathname ? ( - - {redirectText} - - ) : ( - {redirectText} - )}{' '} - instead. -
-
+ +

{t`Deposit disabled.`}

+
), + banner: { + title: t`Deprecated Pool`, + subtitle: ( + + This pool has been deprecated. Please use the{' '} + PRISMA/yPRISMA pool instead. + + ), + }, } } const synthetixAlert = (): PoolAlert => ({ @@ -82,39 +80,49 @@ const usePoolAlert = (poolData?: PoolData | PoolDataCache) => { isInformationOnly: true, isCloseOnTooltipOnly: true, minWidth: '350px', + banner: { + title: t`Synthetix Synths Disabled`, + subtitle: t`Exchanges on Synthetix synths are expected to be disabled. Users should withdraw liquidity or redeem synths to sUSD. Exit pools to avoid value dilution.`, + learnMoreUrl: 'https://gov.curve.finance/t/kill-gauges-on-all-non-susd-curve-pools-on-ethereum/10033/2', + }, message: ( - - -

+ +

+ Please note that exchanges on synthetix synths are expected to be disabled and users can either withdraw liquidity from the underlying token, or redeem their synths to sUSD on{' '} - - https://staking.synthetix.io/wallet/balances/ - -

-

+ synthetix.io + +

+

+ Users are encouraged to exit the pools in order to avoid getting their holdings‘ value diluted with the discountRate For more information please refer to{' '} - - https://gov.curve.finance/t/kill-gauges-on-all-non-susd-curve-pools-on-ethereum/10033/2 + + gov.curve.finance -

-

Please note that sUSD is not involved, so these would be on the other pools sETH, sBTC, sForex ...

-
-
+ +

+

+ + Please note that sUSD is not involved, so these would be on the other pools sETH, sBTC, sForex ... + +

+ ), }) const ironbankAlert = (): PoolAlert => ({ alertType: 'warning', isInformationOnlyAndShowInForm: true, isDisableDeposit: true, + banner: { + title: t`Ironbank Protocol Deprecated`, + subtitle: t`The Ironbank protocol is deprecated. Please do not supply liquidity to this pool.`, + }, message: ( - -
Ironbank protocol is deprecated. Please do not supply liquidity to this pool.
-
+ +

{t`Deposit disabled.`}

+
), }) const synthetixUsdAlert = (): PoolAlert => ({ @@ -123,16 +131,15 @@ const usePoolAlert = (poolData?: PoolData | PoolDataCache) => { isDisableSwap: true, isInformationOnly: true, isCloseOnTooltipOnly: true, + banner: { + title: t`Synthetix USD Deprecated`, + subtitle: t`Pool is deprecated. Deposit and swap are disabled.`, + learnMoreUrl: 'https://x.com/synthetix_io/status/1953054538610688198', + }, message: ( - -
- Deposit and swap are disabled, see{' '} - - https://x.com/synthetix_io/status/1953054538610688198 - {' '} - for additional information. -
-
+ +

{t`This pool is in withdraw only mode.`}

+
), }) const yieldbasisAlert = (): PoolAlert => ({ @@ -140,17 +147,23 @@ const usePoolAlert = (poolData?: PoolData | PoolDataCache) => { isDisableDeposit: true, isInformationOnly: true, isCloseOnTooltipOnly: true, + banner: { + title: t`YieldBasis Managed Pool`, + subtitle: ( + + This pool is managed by YieldBasis. Only deposits made on the YieldBasis UI earn fees and rewards. + + ), + learnMoreUrl: 'https://yieldbasis.com', + }, message: ( - -
- This pool is managed by YieldBasis. Only deposits made on the YieldBasis UI earn fees and rewards.{' '} -
Go to{' '} - - yieldbasis.com - {' '} - to deposit. -
-
+ +

+ + Deposit on yieldbasis.com + +

+
), }) @@ -159,10 +172,14 @@ const usePoolAlert = (poolData?: PoolData | PoolDataCache) => { isDisableDeposit: true, isInformationOnly: true, isCloseOnTooltipOnly: true, + banner: { + title: t`USPD Exploited`, + subtitle: t`USPD has been exploited. This pool has been disabled to prevent new users from loss of funds.`, + }, message: ( - -
USPD has been exploited. This pool has been disabled to prevent new users from loss of funds.
-
+ +

{t`Deposit disabled. We recommend exiting this pool.`}

+
), }) @@ -172,14 +189,14 @@ const usePoolAlert = (poolData?: PoolData | PoolDataCache) => { isCloseOnTooltipOnly: true, isInformationOnlyAndShowInForm: true, message: ( - -
- Deposit and Swap with wBTC.e will return an error due to an Aave community decision to freeze this asset.{' '} - - More details - -
-
+ +

+ + Deposit and Swap with wBTC.e will return an error due to an Aave community decision to freeze this asset.{' '} + More details + +

+
), }) @@ -189,33 +206,32 @@ const usePoolAlert = (poolData?: PoolData | PoolDataCache) => { isDisableDeposit: true, isInformationOnly: true, isCloseOnTooltipOnly: true, + banner: { + title: t`Vyper Vulnerability Exploit`, + subtitle: t`This pool has been exploited due to a vulnerability found in Vyper versions v0.2.15, v0.2.16, or v0.3.0.`, + learnMoreUrl: 'https://hackmd.io/@LlamaRisk/BJzSKHNjn', + }, message: ( - -
- This pool has been exploited due to a vulnerability found in Vyper versions v0.2.15, v0.2.16, or v0.3.0. For - additional information, please click on the post-mortem link:{' '} - - https://hackmd.io/@LlamaRisk/BJzSKHNjn - -
-
+ +

{t`Deposit disabled. We recommend exiting this pool.`}

+
), }) + const possibleVyperExploitedAlert = (): PoolAlert => ({ alertType: 'danger', isDisableDeposit: true, isInformationOnly: true, isCloseOnTooltipOnly: true, + banner: { + title: t`Potential Vulnerability Exploit`, + subtitle: t`This pool might be at risk of being exploited. We recommend exiting this pool.`, + learnMoreUrl: 'https://twitter.com/CurveFinance/status/1685925429041917952', + }, message: ( - -
- This pool might be at risk of being exploited. While security researchers have not identified a profitable - exploit, we recommend exiting this pool.{' '} - - https://twitter.com/CurveFinance/status/1685925429041917952 - -
-
+ +

{t`Deposit disabled. We recommend exiting this pool.`}

+
), }) @@ -227,10 +243,14 @@ const usePoolAlert = (poolData?: PoolData | PoolDataCache) => { isInformationOnly: true, isInformationOnlyAndShowInForm: true, isCloseOnTooltipOnly: true, + banner: { + title: t`Misconfigured Pool`, + subtitle: t`This pool has been misconfigured. It has been set to withdraw only. To minimize impact withdraw in balanced proportion instead of single sided.`, + }, message: ( - -
This pool has been badly configured and is in withdraw only mode.
-
+ +

{t`This pool is in withdraw only mode.`}

+
), }) @@ -258,7 +278,6 @@ const usePoolAlert = (poolData?: PoolData | PoolDataCache) => { '0xd9ff8396554a0d18b2cfbec53e1979b7ecce8373': yieldbasisAlert(), '0x06cf5f9b93e9fcfdb33d6b3791eb152567cd8d36': uspdioAlert(), - // arbitrum '0x960ea3e3c7fb317332d990873d354e18d7645590': possibleVyperExploitedAlert(), // tricrypto @@ -280,19 +299,4 @@ const usePoolAlert = (poolData?: PoolData | PoolDataCache) => { }, [poolAddress, params, hasVyperVulnerability]) } -const MessageWrapper = styled.div` - align-items: flex-start; - display: flex; - flex-direction: column; - - a { - word-break: break-word; - } - - @media (min-width: ${breakpoints.sm}rem) { - align-items: center; - flex-direction: row; - } -` - export default usePoolAlert diff --git a/apps/main/src/dex/types/main.types.ts b/apps/main/src/dex/types/main.types.ts index baa09985e7..67528c347b 100644 --- a/apps/main/src/dex/types/main.types.ts +++ b/apps/main/src/dex/types/main.types.ts @@ -5,6 +5,7 @@ import type { IChainId, IDict, INetworkName } from '@curvefi/api/lib/interfaces' import type { PoolTemplate } from '@curvefi/api/lib/pools' import type { TooltipProps } from '@ui/Tooltip/types' import type { BaseConfig } from '@ui/utils' +import { BannerProps } from '@ui-kit/shared/ui/Banner' export type { CurveApi, Wallet } from '@ui-kit/features/connect-wallet' @@ -273,9 +274,12 @@ export interface PoolAlert extends TooltipProps { isInformationOnly?: boolean isInformationOnlyAndShowInForm?: boolean isCloseOnTooltipOnly?: boolean + // banner message, related to the market situation + banner?: Omit & { title: string } isPoolPageOnly?: boolean // Don't show the pools overview table address?: string - message: ReactNode + // action card message, related to action of user + message?: ReactNode } export type EstimatedGas = number | number[] | null diff --git a/packages/curve-ui-kit/src/shared/ui/Banner.tsx b/packages/curve-ui-kit/src/shared/ui/Banner.tsx index c07063b6d5..d7a6cc3378 100644 --- a/packages/curve-ui-kit/src/shared/ui/Banner.tsx +++ b/packages/curve-ui-kit/src/shared/ui/Banner.tsx @@ -92,7 +92,7 @@ export const Banner = ({ }} data-testid={testId} > - + {BannerIcons[icon]} {children} diff --git a/packages/ui/src/AlertBox/AlertBox.tsx b/packages/ui/src/AlertBox/AlertBox.tsx index be5f833918..20430ffb94 100644 --- a/packages/ui/src/AlertBox/AlertBox.tsx +++ b/packages/ui/src/AlertBox/AlertBox.tsx @@ -33,45 +33,47 @@ const AlertBox = ({ className, alertType, children, title, limitHeight, handleBt }, [children, limitHeight]) return ( - - {title ? ( - <> -
- {IconComp} {title}{' '} -
- {showFullHeight ? children : cutAlert} - - ) : ( - - {IconComp} - + (title || children) && ( + + {title ? ( + <> +
+ {IconComp} {title}{' '} +
{showFullHeight ? children : cutAlert} -
- {handleBtnClose !== undefined && ( - - - - )} -
- )} - {limitHeight && enabledHeightToggle && ( - - - setShowFullHeight(!showFullHeight)}> - {showFullHeight ? 'Minimize' : 'Expand'} - {showFullHeight ? : } - - - - )} -
+ + ) : ( + + {IconComp} + + {showFullHeight ? children : cutAlert} + + {handleBtnClose !== undefined && ( + + + + )} + + )} + {limitHeight && enabledHeightToggle && ( + + + setShowFullHeight(!showFullHeight)}> + {showFullHeight ? 'Minimize' : 'Expand'} + {showFullHeight ? : } + + + + )} + + ) ) }