Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f8fc2f6
feat: display strategy names instead of asset symbols in yield cards
gomesalexandre Jan 16, 2026
fca42a0
feat: add maintenance and deprecated warnings for yield opportunities
gomesalexandre Jan 16, 2026
e7b34e7
chore: add temporary monkey patch for testing maintenance/deprecated …
gomesalexandre Jan 16, 2026
ea4338b
chore: remove temporary monkey patch for maintenance/deprecated badges
gomesalexandre Jan 16, 2026
6ebcbc0
feat: add YieldExplainers component for consistent staking info
gomesalexandre Jan 16, 2026
1555437
feat: add documentation link to yield detail page
gomesalexandre Jan 16, 2026
3784581
feat: add yield explainers to YieldForm and improve docs link
gomesalexandre Jan 16, 2026
9f1e96a
feat: add Available to Earn tab and improve yield page navigation
gomesalexandre Jan 16, 2026
ec27e2b
feat: improve yield page UX with filter fixes and code cleanup
gomesalexandre Jan 16, 2026
f90fac7
feat: swap fiat/crypto display on yield detail page
gomesalexandre Jan 16, 2026
1e247f3
feat: improve yield detail page desktop layout with two-column design
gomesalexandre Jan 16, 2026
b18f7ab
feat: fix validator mismatch bug and add New badge to Yields menu
gomesalexandre Jan 16, 2026
eb59b8d
feat: add getYieldDisplayName utility for clean yield names
gomesalexandre Jan 16, 2026
3969437
feat: improve asset/chain/protocol display with icon + label format
gomesalexandre Jan 16, 2026
6abea3f
refactor: use named functions in memo() for better debugging
gomesalexandre Jan 16, 2026
b24cbd9
feat: commit best practices skill
gomesalexandre Jan 16, 2026
2d8bb8d
chore: add project-specific style preferences to react-best-practices…
gomesalexandre Jan 16, 2026
cccc386
refactor: improve yields code quality and patterns
gomesalexandre Jan 16, 2026
fa85b17
fix: add null coalescing to prevent runtime error in sort comparator
gomesalexandre Jan 16, 2026
e872c09
fix: guard claimable section against zero-amount balances
gomesalexandre Jan 16, 2026
8249484
feat: enable partial yield feature for production
gomesalexandre Jan 16, 2026
3a36943
fix: conditional back navigation based on YieldsPage flag
gomesalexandre Jan 16, 2026
1897e82
fix: Earn dropdown defaults to /tcy when YieldsPage disabled
gomesalexandre Jan 16, 2026
b45acc4
Merge origin/develop into feat_yield_flag_on
gomesalexandre Jan 19, 2026
5fa8965
Merge branch 'develop' into feat_yield_flag_on
gomesalexandre Jan 20, 2026
a47b059
Merge branch 'develop' into feat_yield_flag_on
gomesalexandre Jan 20, 2026
33f0aeb
Merge branch 'develop' into feat_yield_flag_on
gomesalexandre Jan 21, 2026
e768424
Merge branch 'develop' into feat_yield_flag_on
gomesalexandre Jan 21, 2026
3046588
Merge branch 'develop' into feat_yield_flag_on
premiumjibles Jan 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.production
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# feature flags
VITE_FEATURE_MIXPANEL=true
VITE_ENABLE_HYPELAB=true
VITE_FEATURE_ADDRESS_BOOK=true
VITE_FEATURE_THORCHAIN_TCY_ACTIVITY=false

# mixpanel
VITE_MIXPANEL_TOKEN=9d304465fc72224aead9e027e7c24356
VITE_FEATURE_YIELD_XYZ=true
VITE_FEATURE_YIELD_MULTI_ACCOUNT=true
16 changes: 16 additions & 0 deletions src/Routes/RoutesCommon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export const StakingPositionsByProvider: React.FC<StakingPositionsByProviderProp
if (walletDrawer.isOpen) {
walletDrawer.close()
}
navigate(`/yields/${opportunity.yieldId}`)
navigate(`/yield/${opportunity.yieldId}`)
return
}

Expand Down
7 changes: 4 additions & 3 deletions src/components/Layout/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export const Header = memo(() => {
const translate = useTranslate()
const [isLargerThanMd] = useMediaQuery(`(min-width: ${breakpoints['md']})`)
const isYieldXyzEnabled = useFeatureFlag('YieldXyz')
const isYieldsPageEnabled = useFeatureFlag('YieldsPage')

const navigate = useNavigate()
const {
Expand Down Expand Up @@ -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],
)

/**
Expand Down Expand Up @@ -183,7 +184,7 @@ export const Header = memo(() => {
<NavigationDropdown
label='defi.earn'
items={earnSubMenuItems}
defaultPath={isYieldXyzEnabled ? '/yields' : '/tcy'}
defaultPath={isYieldXyzEnabled && isYieldsPageEnabled ? '/yields' : '/tcy'}
/>
<Link
as={ReactRouterLink}
Expand Down
47 changes: 47 additions & 0 deletions src/lib/yieldxyz/providerDescriptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export const PROVIDER_DESCRIPTIONS: Record<
string,
{
description: string
}
> = {
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.',
},
}
72 changes: 37 additions & 35 deletions src/pages/Yields/YieldDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,20 @@ 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
? selectPortfolioAccountIdsByAssetIdFilter(state, { assetId: selectorAssetId })
: [],
)

const isYieldMultiAccountEnabled = useFeatureFlag('YieldMultiAccount')

const { selectedAccountId, handleAccountChange } = useYieldAccountSync({
availableAccountIds: availableAccounts,
})
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
() => (
<Container maxW={{ base: 'full', md: 'container.md' }} py={20}>
<Flex direction='column' gap={8} alignItems='center'>
<Text color='text.subtle' fontSize='lg'>
{translate('common.loadingText')}
</Text>
</Flex>
</Container>
),
[translate],
const loadingElement = (
<Container maxW={{ base: 'full', md: 'container.md' }} py={20}>
<Flex direction='column' gap={8} alignItems='center'>
<Text color='text.subtle' fontSize='lg'>
{translate('common.loadingText')}
</Text>
</Flex>
</Container>
)

const errorElement = useMemo(
() => (
<Container maxW={{ base: 'full', md: 'container.md' }} py={20}>
<Box textAlign='center' py={16} bg='background.surface.raised.base' borderRadius='2xl'>
<Heading as='h2' size='xl' mb={4}>
{translate('common.error')}
</Heading>
<Text color='text.subtle'>
{error ? String(error) : translate('common.noResultsFound')}
</Text>
<Button mt={8} onClick={() => navigate('/yields')} size='lg'>
{translate('common.back')}
</Button>
</Box>
</Container>
),
[error, navigate, translate],
const errorElement = (
<Container maxW={{ base: 'full', md: 'container.md' }} py={20}>
<Box textAlign='center' py={16} bg='background.surface.raised.base' borderRadius='2xl'>
<Heading as='h2' size='xl' mb={4}>
{translate('common.error')}
</Heading>
<Text color='text.subtle'>
{error ? String(error) : translate('common.noResultsFound')}
</Text>
<Button
mt={8}
onClick={() => (isYieldsPageEnabled ? navigate('/yields') : navigate(-1))}
size='lg'
>
{translate('common.back')}
</Button>
</Box>
</Container>
)

if (isLoading) return loadingElement
Expand Down
13 changes: 13 additions & 0 deletions src/pages/Yields/YieldDetailPage.tsx
Original file line number Diff line number Diff line change
@@ -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(() => (
<YieldAccountProvider>
<Routes>
<Route path='*' element={<YieldDetailRouter />} />
</Routes>
</YieldAccountProvider>
))
8 changes: 8 additions & 0 deletions src/pages/Yields/Yields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@ export const Yields = memo(() => (
</Routes>
</YieldAccountProvider>
))

export const YieldDetailRouter = memo(() => (
<Routes>
<Route path='enter' element={<YieldDetail />} />
<Route path='exit' element={<YieldDetail />} />
<Route index element={<YieldDetail />} />
</Routes>
))
2 changes: 1 addition & 1 deletion src/pages/Yields/components/YieldActivePositions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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],
)
Expand Down
39 changes: 13 additions & 26 deletions src/pages/Yields/components/YieldItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
Expand All @@ -126,15 +126,15 @@ 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}` : ''
navigate(`/yields/asset/${encodeURIComponent(data.assetSymbol)}${suffix}`)
}
}, [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'
Expand All @@ -148,7 +148,7 @@ export const YieldItem = memo(
return <AssetIcon assetId={data.assetId} size={size} showNetworkIcon={false} />
}
return <AssetIcon src={data.assetIcon} size={size} />
}, [data, isSingle, variant])
})()

const subtitle = isSingle
? data.providerName ?? data.yieldItem.providerId
Expand Down Expand Up @@ -197,7 +197,7 @@ export const YieldItem = memo(

const showAvailable = isSingle && hasAvailable && !hasBalance

const cardStatElement = useMemo(() => {
const cardStatElement = (() => {
if (hasBalance) {
return (
<>
Expand Down Expand Up @@ -232,24 +232,17 @@ export const YieldItem = memo(
</StatNumber>
</>
)
}, [
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 (
<Text fontWeight='medium' color='blue.400'>
Expand All @@ -269,9 +262,9 @@ export const YieldItem = memo(
</Text>
)
}, [hasBalance, showAvailable, userBalanceUserCurrency, availableBalanceUserCurrency])
})()

const rowBalanceElement = useMemo(() => {
const rowBalanceElement = (() => {
if (hasBalance && showAvailableInRow) {
return (
<Box>
Expand Down Expand Up @@ -318,13 +311,7 @@ export const YieldItem = memo(
</Text>
)
}, [
hasBalance,
showAvailableInRow,
userBalanceUserCurrency,
availableBalanceUserCurrency,
translate,
])
})()

if (variant === 'mobile') {
return (
Expand Down
Loading