diff --git a/.env.production b/.env.production index bde42eb0c3a..26f596e2335 100644 --- a/.env.production +++ b/.env.production @@ -1,4 +1,3 @@ -# feature flags VITE_FEATURE_MIXPANEL=true VITE_ENABLE_HYPELAB=true VITE_FEATURE_ADDRESS_BOOK=true @@ -6,3 +5,5 @@ VITE_FEATURE_THORCHAIN_TCY_ACTIVITY=false # mixpanel VITE_MIXPANEL_TOKEN=9d304465fc72224aead9e027e7c24356 +VITE_FEATURE_YIELD_XYZ=true +VITE_FEATURE_YIELD_MULTI_ACCOUNT=true diff --git a/src/Routes/RoutesCommon.tsx b/src/Routes/RoutesCommon.tsx index bcfeb725ed0..2227ff9248f 100644 --- a/src/Routes/RoutesCommon.tsx +++ b/src/Routes/RoutesCommon.tsx @@ -141,6 +141,16 @@ const YieldsPage = makeSuspenseful( true, ) +const YieldDetailPage = makeSuspenseful( + lazy(() => + import('@/pages/Yields/YieldDetailPage').then(({ YieldDetailPage }) => ({ + default: YieldDetailPage, + })), + ), + {}, + true, +) + const WalletConnectDeepLink = makeSuspenseful( lazy(() => import('@/pages/WalletConnectDeepLink/WalletConnectDeepLink').then( @@ -241,6 +251,12 @@ export const routes: Route[] = [ mobileNav: false, disable: !getConfig().VITE_FEATURE_MARKETS, }, + { + path: '/yield/:yieldId/*', + main: YieldDetailPage, + hide: true, + disable: !getConfig().VITE_FEATURE_YIELD_XYZ, + }, { path: '/yields/*', label: 'navBar.yields', diff --git a/src/components/EarnDashboard/components/PositionDetails/StakingPositionsByProvider.tsx b/src/components/EarnDashboard/components/PositionDetails/StakingPositionsByProvider.tsx index f7550a58c4a..f769271fb00 100644 --- a/src/components/EarnDashboard/components/PositionDetails/StakingPositionsByProvider.tsx +++ b/src/components/EarnDashboard/components/PositionDetails/StakingPositionsByProvider.tsx @@ -158,7 +158,7 @@ export const StakingPositionsByProvider: React.FC { const translate = useTranslate() const [isLargerThanMd] = useMediaQuery(`(min-width: ${breakpoints['md']})`) const isYieldXyzEnabled = useFeatureFlag('YieldXyz') + const isYieldsPageEnabled = useFeatureFlag('YieldsPage') const navigate = useNavigate() const { @@ -116,14 +117,14 @@ export const Header = memo(() => { const hasWallet = Boolean(walletInfo?.deviceId) const earnSubMenuItems = useMemo( () => [ - ...(isYieldXyzEnabled + ...(isYieldsPageEnabled ? [{ label: 'navBar.yields', path: '/yields', icon: TbTrendingUp, isNew: true }] : []), { label: 'navBar.tcy', path: '/tcy', icon: TCYIcon }, { label: 'navBar.pools', path: '/pools', icon: TbPool }, { label: 'navBar.lending', path: '/lending', icon: TbBuildingBank }, ], - [isYieldXyzEnabled], + [isYieldsPageEnabled], ) /** @@ -183,7 +184,7 @@ export const Header = memo(() => { = { + morpho: { + description: + 'Morpho is a money market and vault infrastructure protocol, multiply audited by top-tier security firms, live since 2022, with $2.5M in bug bounty incentives.', + }, + 'morpho-aave': { + description: + 'Morpho is a money market and vault infrastructure protocol, multiply audited by top-tier security firms, live since 2022, with $2.5M in bug bounty incentives.', + }, + 'morpho-compound': { + description: + 'Morpho is a money market and vault infrastructure protocol, multiply audited by top-tier security firms, live since 2022, with $2.5M in bug bounty incentives.', + }, + lido: { + description: + 'Lido is a liquid staking protocol that lets users stake ETH while keeping liquidity via stETH. Multiply audited by top-tier security firms, live since 2020, with $2M in bug bounty incentives.', + }, + aave: { + description: + 'Aave is a multi-chain lending marketplace enabling users to lend, borrow, and build advanced strategies. Multiply audited by top-tier security firms, live since 2017, with $1M in bug bounty incentives.', + }, + compound: { + description: + 'Compound is a foundational DeFi money market with algorithmic interest rates. Multiply audited by top-tier security firms, live since 2018, with $1M in bug bounty incentives.', + }, + kamino: { + description: + 'Kamino is a Solana DeFi suite unifying lending, liquidity, and leverage into one platform. Runs an Immunefi program with up to $1.5M maximum bounty.', + }, + fluid: { + description: + 'Fluid is a liquidity layer built by the Instadapp team, connecting lending, DEX, borrowing, and stablecoin markets into one efficient system. Multiply audited by top-tier security firms, live since 2024, with $0.5M in bug bounty incentives.', + }, + venus: { + description: + 'Venus is a lending and borrowing protocol focused on BNB Chain. Emphasizes security through third-party audits and an ongoing bug bounty program.', + }, + gearbox: { + description: + 'Gearbox is a composable leverage protocol enabling credit accounts that plug into DeFi strategies. Multiply audited by top-tier security firms, live since 2021, with $0.2M in bug bounty incentives.', + }, +} diff --git a/src/pages/Yields/YieldDetail.tsx b/src/pages/Yields/YieldDetail.tsx index 2264dd99ac7..0c4998b4400 100644 --- a/src/pages/Yields/YieldDetail.tsx +++ b/src/pages/Yields/YieldDetail.tsx @@ -68,7 +68,13 @@ export const YieldDetail = memo(() => { selectMarketDataByAssetIdUserCurrency(state, inputTokenAssetId), ) - const handleBack = useCallback(() => navigate('/yields'), [navigate]) + const isYieldMultiAccountEnabled = useFeatureFlag('YieldMultiAccount') + const isYieldsPageEnabled = useFeatureFlag('YieldsPage') + + const handleBack = useCallback( + () => (isYieldsPageEnabled ? navigate('/yields') : navigate(-1)), + [navigate, isYieldsPageEnabled], + ) const availableAccounts = useAppSelector(state => selectorAssetId @@ -76,8 +82,6 @@ export const YieldDetail = memo(() => { : [], ) - const isYieldMultiAccountEnabled = useFeatureFlag('YieldMultiAccount') - const { selectedAccountId, handleAccountChange } = useYieldAccountSync({ availableAccountIds: availableAccounts, }) @@ -158,7 +162,7 @@ export const YieldDetail = memo(() => { return getYieldDisplayName(yieldItem) }, [yieldItem, translate]) - const userBalances = useMemo(() => { + const userBalances = (() => { if (!balances) return { userCurrency: '0', crypto: '0' } const balancesByType = selectedValidatorAddress @@ -186,44 +190,42 @@ export const YieldDetail = memo(() => { userCurrency: totalUsd.times(userCurrencyToUsdRate).toFixed(), crypto: totalCrypto.toFixed(), } - }, [balances, selectedValidatorAddress, userCurrencyToUsdRate]) + })() useEffect(() => { - if (!yieldId) navigate('/yields') - }, [yieldId, navigate]) + if (!yieldId) navigate(isYieldsPageEnabled ? '/yields' : '/') + }, [yieldId, navigate, isYieldsPageEnabled]) const isModalOpen = searchParams.get('modal') === 'yield' - const loadingElement = useMemo( - () => ( - - - - {translate('common.loadingText')} - - - - ), - [translate], + const loadingElement = ( + + + + {translate('common.loadingText')} + + + ) - const errorElement = useMemo( - () => ( - - - - {translate('common.error')} - - - {error ? String(error) : translate('common.noResultsFound')} - - - - - ), - [error, navigate, translate], + const errorElement = ( + + + + {translate('common.error')} + + + {error ? String(error) : translate('common.noResultsFound')} + + + + ) if (isLoading) return loadingElement diff --git a/src/pages/Yields/YieldDetailPage.tsx b/src/pages/Yields/YieldDetailPage.tsx new file mode 100644 index 00000000000..173262434e6 --- /dev/null +++ b/src/pages/Yields/YieldDetailPage.tsx @@ -0,0 +1,13 @@ +import { memo } from 'react' +import { Route, Routes } from 'react-router-dom' + +import { YieldAccountProvider } from '@/pages/Yields/YieldAccountContext' +import { YieldDetailRouter } from '@/pages/Yields/Yields' + +export const YieldDetailPage = memo(() => ( + + + } /> + + +)) diff --git a/src/pages/Yields/Yields.tsx b/src/pages/Yields/Yields.tsx index 0ade7c454e9..23af556fab4 100644 --- a/src/pages/Yields/Yields.tsx +++ b/src/pages/Yields/Yields.tsx @@ -17,3 +17,11 @@ export const Yields = memo(() => ( )) + +export const YieldDetailRouter = memo(() => ( + + } /> + } /> + } /> + +)) diff --git a/src/pages/Yields/components/YieldActivePositions.tsx b/src/pages/Yields/components/YieldActivePositions.tsx index c3dcede91b2..ffbd0f80e46 100644 --- a/src/pages/Yields/components/YieldActivePositions.tsx +++ b/src/pages/Yields/components/YieldActivePositions.tsx @@ -176,7 +176,7 @@ export const YieldActivePositions = memo( const params = new URLSearchParams() params.set('accountId', accountId) if (validatorAddress) params.set('validator', validatorAddress) - navigate(`/yields/${yieldId}?${params.toString()}`) + navigate(`/yield/${yieldId}?${params.toString()}`) }, [navigate], ) diff --git a/src/pages/Yields/components/YieldItem.tsx b/src/pages/Yields/components/YieldItem.tsx index a404684e4c6..dc85998f473 100644 --- a/src/pages/Yields/components/YieldItem.tsx +++ b/src/pages/Yields/components/YieldItem.tsx @@ -78,7 +78,7 @@ export const YieldItem = memo( const isSingle = data.type === 'single' const isGroup = data.type === 'group' - const stats = useMemo(() => { + const stats = (() => { if (isSingle) { const y = data.yieldItem return { @@ -110,7 +110,7 @@ export const YieldItem = memo( name: data.assetName, canEnter: true, } - }, [data, isSingle, yieldProviders]) + })() const apyFormatted = `${(stats.apy * 100).toFixed(2)}%` const tvlUserCurrency = bnOrZero(stats.tvlUsd).times(userCurrencyToUsdRate).toFixed() @@ -126,7 +126,7 @@ export const YieldItem = memo( if (stats.canEnter && onEnter) { onEnter(data.yieldItem) } else { - navigate(`/yields/${data.yieldItem.id}`) + navigate(`/yield/${data.yieldItem.id}`) } } else { const suffix = searchString ? `?${searchString}` : '' @@ -134,7 +134,7 @@ export const YieldItem = memo( } }, [data, isSingle, navigate, onEnter, searchString, stats.canEnter]) - const iconElement = useMemo(() => { + const iconElement = (() => { if (isSingle) { const iconSource = resolveYieldInputAssetIcon(data.yieldItem) const size = variant === 'card' ? 'md' : 'sm' @@ -148,7 +148,7 @@ export const YieldItem = memo( return } return - }, [data, isSingle, variant]) + })() const subtitle = isSingle ? data.providerName ?? data.yieldItem.providerId @@ -197,7 +197,7 @@ export const YieldItem = memo( const showAvailable = isSingle && hasAvailable && !hasBalance - const cardStatElement = useMemo(() => { + const cardStatElement = (() => { if (hasBalance) { return ( <> @@ -232,24 +232,17 @@ export const YieldItem = memo( ) - }, [ - hasBalance, - showAvailable, - userBalanceUserCurrency, - availableBalanceUserCurrency, - tvlUserCurrency, - translate, - ]) + })() const showAvailableInRow = isSingle && hasAvailable - const mobileBalanceLabelKey = useMemo(() => { + const mobileBalanceLabelKey = (() => { if (hasBalance) return 'yieldXYZ.balance' if (showAvailable) return 'common.available' return 'yieldXYZ.balance' - }, [hasBalance, showAvailable]) + })() - const mobileBalanceElement = useMemo(() => { + const mobileBalanceElement = (() => { if (hasBalance) { return ( @@ -269,9 +262,9 @@ export const YieldItem = memo( — ) - }, [hasBalance, showAvailable, userBalanceUserCurrency, availableBalanceUserCurrency]) + })() - const rowBalanceElement = useMemo(() => { + const rowBalanceElement = (() => { if (hasBalance && showAvailableInRow) { return ( @@ -318,13 +311,7 @@ export const YieldItem = memo( — ) - }, [ - hasBalance, - showAvailableInRow, - userBalanceUserCurrency, - availableBalanceUserCurrency, - translate, - ]) + })() if (variant === 'mobile') { return ( diff --git a/src/pages/Yields/components/YieldOpportunityStats.tsx b/src/pages/Yields/components/YieldOpportunityStats.tsx index 672f76d2aad..9a1e7abca93 100644 --- a/src/pages/Yields/components/YieldOpportunityStats.tsx +++ b/src/pages/Yields/components/YieldOpportunityStats.tsx @@ -66,7 +66,7 @@ export const YieldOpportunityStats = memo(function YieldOpportunityStats({ [positions, balances], ) - const idleValueUsd = useMemo(() => { + const idleValueUsd = (() => { if (!isConnected || !allYields) return bnOrZero(0) const yieldableAssetIds = new Set( @@ -82,7 +82,7 @@ export const YieldOpportunityStats = memo(function YieldOpportunityStats({ const bal = portfolioBalances[assetId] return bal ? totalIdle.plus(bnOrZero(bal)) : totalIdle }, bnOrZero(0)) - }, [isConnected, allYields, portfolioBalances]) + })() const { weightedApy, potentialEarningsValue } = useMemo(() => { if (!isConnected || !yields?.byInputAssetId || !portfolioBalances) { diff --git a/src/pages/Yields/components/YieldStats.tsx b/src/pages/Yields/components/YieldStats.tsx index 9f497dcd269..bc3b84adb14 100644 --- a/src/pages/Yields/components/YieldStats.tsx +++ b/src/pages/Yields/components/YieldStats.tsx @@ -44,7 +44,7 @@ export const YieldStats = memo(({ yieldItem, balances }: YieldStatsProps) => { const enforced = getDefaultValidatorForYield(yieldItem.id) if (enforced) return enforced return validatorParam || defaultValidator - }, [yieldItem.id, validatorParam, defaultValidator]) + }, [yieldItem.id, defaultValidator, validatorParam]) const selectedValidator = useMemo(() => { if (!selectedValidatorAddress) return undefined @@ -56,11 +56,9 @@ export const YieldStats = memo(({ yieldItem, balances }: YieldStatsProps) => { return undefined }, [selectedValidatorAddress, validators, balances?.raw]) - const tvl = useMemo(() => { - const validatorTvl = - selectedValidator && 'tvl' in selectedValidator ? selectedValidator.tvl : undefined - return bnOrZero(yieldItem.statistics?.tvl ?? validatorTvl).toNumber() - }, [selectedValidator, yieldItem.statistics?.tvl]) + const validatorTvl = + selectedValidator && 'tvl' in selectedValidator ? selectedValidator.tvl : undefined + const tvl = bnOrZero(yieldItem.statistics?.tvl ?? validatorTvl).toNumber() const tvlUserCurrency = useMemo( () => diff --git a/src/pages/Yields/components/YieldSuccess.tsx b/src/pages/Yields/components/YieldSuccess.tsx index 3a1cd81fe92..d65cc662777 100644 --- a/src/pages/Yields/components/YieldSuccess.tsx +++ b/src/pages/Yields/components/YieldSuccess.tsx @@ -54,7 +54,7 @@ export const YieldSuccess = memo( const params = new URLSearchParams() if (accountId) params.set('accountId', accountId) const queryString = params.toString() - navigate(queryString ? `/yields/${yieldId}?${queryString}` : `/yields/${yieldId}`) + navigate(queryString ? `/yield/${yieldId}?${queryString}` : `/yield/${yieldId}`) }, [yieldId, accountId, navigate]) const providerPillProps = useMemo(