-
Notifications
You must be signed in to change notification settings - Fork 198
feat: big batch of yield improvements #11656
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
feat: big batch of yield improvements #11656
Conversation
- Remove unnecessary filteredYields dependency in YieldAssetDetails useMemo hooks - Add validator breakdown utility functions - Enhance yield action modal with additional transaction handling - Refactor yield filters component for better code organization - Update yields list component layout - Improve yield filters hook logic - Add yield transaction flow hooks - Add missing translation key Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
- Add APPROVAL type to TX_TYPE_TO_LABEL (button now shows "Approve" not "Approval") - Filter SKIPPED transactions in displaySteps preview - Use first CREATED transaction for button text - Harmonize button/row labels: SUPPLY/ENTER -> "Deposit", EXIT -> "Withdraw" - Fix useCallback missing dependencies in useYieldTransactionFlow - Add icon background color for yield detail hero - Add chainName to action transaction metadata Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Change invalidateQueries to refetchQueries with await - Ensures position data is fresh before modal closes - Fixes "My Positions" showing empty after deposit/withdraw Co-Authored-By: Claude Opus 4.5 <[email protected]>
Since dispatchNotification is called after waitForActionCompletion, the transaction is already confirmed. Dispatch with Complete status directly instead of relying on Unchained subscriber (which doesn't work for chains without Unchained support like Monad). Co-Authored-By: Claude Opus 4.5 <[email protected]>
After yield transaction completion, trigger portfolioApi.getAccount refetch for second class chains (Monad, Tron, etc.) that don't have Unchained support. This ensures wallet balances update immediately. Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Extract sortedRows from table to use as dependency - TanStack Table's table object reference is stable, so useMemo was not re-running when filtered data changed - Using sortedRows as dependency triggers proper re-memoization Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add searchString prop to YieldItem - Pass current filter params when navigating to asset detail page - Filters persist across navigation (e.g., network=ethereum) Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Set staleTime: Infinity to prevent refetching during modal lifecycle - Set gcTime: 0 to garbage collect cached quote when modal closes - Ensures fresh quote on re-entry, no stale data from previous tx Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Remove quote queries from cache when modal closes - Ensures fresh quote fetch on subsequent modal opens - Remove YIELD_POLISH.md Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Create YieldEnterModal component with Trust Wallet-inspired design
- Big centered input with fiat/crypto toggle
- Asset icon, percent buttons (25%, 50%, 75%, Max)
- Debounced quote fetching (500ms) for pre-loading transactions
- Auto-select default validator (ShapeShift DAO for Cosmos)
- Stats section with APY, validator/provider info, and min deposit
- Modify YieldAssetSection to open modal instead of navigate
- Add translation key for "Stake {asset}"
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add TransactionStepsList component for shared step display - Extract useConfetti hook to eliminate duplication - Export poll/waitForActionCompletion from useYieldTransactionFlow - Update YieldOpportunityCard with horizontal layout - Use Card variant='dashboard' in YieldAssetSection - Add tx hash links in success view Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.5 <[email protected]>
…ventions, style fixes Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- YieldFilters: Use MenuOptionGroup/MenuItemOption for proper theme styling (no more blue.500) - ValidatorBreakdown: Remove useColorModeValue, use direct color tokens - YieldEnterModal: Extract hover style constants outside component - YieldEnterExit: Extract style constants outside component, fix inline button handlers - YieldActionModal: Use STATUS_LABEL_KEYS map to avoid nested ternaries - useYieldTransactionFlow: Rename loadingMessage to statusLabel for consistency Co-Authored-By: Claude Opus 4.5 <[email protected]>
…a.50 Co-Authored-By: Claude Opus 4.5 <[email protected]>
- YieldValidatorSelectModal: gray.300, gray.500 -> text.subtle - YieldActionModal: whiteAlpha.50 -> background.surface.raised.base Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add SHAPESHIFT_VALIDATOR constant with fallback APR data - normalizeCosmosValidators ensures ShapeShift is always first - Only ShapeShift is marked as preferred for Cosmos SDK - Add COSMOS_NETWORK_FALLBACK_APR (15%) for validators missing APR - Remove redundant sortValidators call in modal Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Remove redundant ASSET column from YieldActivePositions table - Remove "Your Balance" label (already obvious from context) - Move ensureValidatorApr to utils for reuse - Fix React key warning in ReactTable (destructure key from spread props) Co-Authored-By: Claude Opus 4.5 <[email protected]>
…hide section without wallet - Extract TransactionStepStatus from anonymous IIFE in YieldActionModal - Reuse existing poll utility from lib/poll instead of custom implementation - Hide YieldAssetSection when wallet is not connected - Reorder TransactionStep type fields for cleaner grouping - Remove unnecessary curly braces in useYieldValidators Co-Authored-By: Claude Opus 4.5 <[email protected]>
…saction step - Reverted poll changes to use original inline implementation - Export waitForActionCompletion for YieldEnterModal usage - Fix enterButtonText to use getTransactionButtonText for proper labels (shows "Approve" instead of "Stake USDT" when approval is first step) - Add originalTitle and type to TransactionStep in YieldEnterModal - Ensure consistent use of loadingMessage property Co-Authored-By: Claude Opus 4.5 <[email protected]>
… model - Replace for-loop auto-execution with click-per-step model matching useYieldTransactionFlow - Add activeStepIndex, rawTransactions, currentActionId state for multi-step tracking - Extract executeSingleTransaction callback for single transaction execution - Export filterExecutableTransactions from hook for code reuse - Fix button text and steps display between transaction steps [skip ci] Co-Authored-By: Claude Opus 4.5 <[email protected]>
…dals [skip ci] Co-Authored-By: Claude Opus 4.5 <[email protected]>
Adds waitForTransactionConfirmation helper to poll backend until the submitted transaction is confirmed, preventing nonce conflicts when users quickly transition between Approve and Deposit transactions. This resolves "replacement transaction underpriced" errors by ensuring the on-chain nonce counter has incremented before constructing the next transaction. [skip ci] Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add responsive padding to Container components for breathing room on mobile - Fix YieldFilters to stack vertically on mobile with full-width controls - Fix ViewToggle to be full-width on mobile with equal flex buttons - Fix YieldAssetDetails filters layout for mobile responsiveness - Hide YieldOpportunityStats when wallet not connected (stale data bug) - Add isConnected guard to prevent showing stale portfolio balances - Add "myPositions" translation key for plural form in list tab Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.5 <[email protected]>
… and Potential Earnings
…ies based on user holdings
- Create YieldForm component for enter/exit/claim modal - Create YieldHero component with centered title and stacked icon design - Create YieldManager dialog wrapper for yield actions - Fix claim flow by passing passthrough and manageActionType from pendingActions - Use proper translation interpolation for modal titles - Add missing translations (stakeSymbol, unstakeSymbol, depositSymbol, etc.) - Remove multi-validator support - only use default validator (ShapeShift DAO) - Delete unused ValidatorBreakdown, YieldEnterExit, YieldValidatorSelectModal - Improve desktop layout with wider container - Simplify YieldStats and YieldPositionCard Co-Authored-By: Claude Opus 4.5 <[email protected]>
📝 WalkthroughWalkthroughAdds a gated "Earn" feature: new feature flags, routes and navigation, Earn UI (input/confirm), yield selection components, transaction flow enhancements (USDT reset, polling), many new Yields UI components and Redux slice, extensive translations and several Yields refactors/removals. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant UI as EarnInput/EarnConfirm
participant Selector as YieldSelector
participant API as YieldXYZ API
participant Wallet
participant TxFlow as useYieldTransactionFlow
participant Success as YieldSuccess
User->>UI: open Earn, pick asset & yield, enter amount
UI->>Selector: request provider/yields
Selector->>API: fetch providers & yields
API-->>Selector: returns providers & yields
Selector-->>UI: selected yield
UI->>API: request quote & validators
API-->>UI: returns quote & validators
User->>UI: confirm -> EarnConfirm
EarnConfirm->>Wallet: request sign / send tx
Wallet-->>TxFlow: tx submitted (may include allowance reset)
TxFlow->>API: poll action/tx status
API-->>TxFlow: status updates
TxFlow-->>EarnConfirm: final confirmation
EarnConfirm->>Success: render success UI
Success-->>User: show result (confetti, view position)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/pages/Yields/components/YieldsList.tsx (1)
146-149: Fix TS type usage:React.ChangeEventis referenced without importingReact.Proposed fix
-import { memo, useCallback, useMemo, useState } from 'react' +import { memo, useCallback, useMemo, useState } from 'react' +import type { ChangeEvent } from 'react' @@ - const handleSearchChange = useCallback( - (e: React.ChangeEvent<HTMLInputElement>) => setSearchQuery(e.target.value), + const handleSearchChange = useCallback( + (e: ChangeEvent<HTMLInputElement>) => setSearchQuery(e.target.value), [], )
🤖 Fix all issues with AI agents
In `@src/components/MultiHopTrade/components/Earn/EarnConfirm.tsx`:
- Around line 186-200: The SharedConfirm component type includes an unused prop
isLoading (in SharedConfirmProps) that should be removed; update the
SharedConfirmProps type to delete the isLoading field and then remove the
isLoading={...} prop from all SharedConfirm usages in EarnConfirm (the three
call sites that pass isLoading), ensuring you only delete the prop from the JSX
and not other props or logic—leave SharedConfirm, EarnConfirm, and related
handlers (e.g., handleBack) unchanged.
In `@src/components/MultiHopTrade/components/Earn/types.ts`:
- Around line 1-4: EarnRoutePaths currently defines Confirm as an absolute path
which mismatches the routing pattern used elsewhere; change the enum value of
EarnRoutePaths.Confirm from an absolute path to the relative segment "confirm"
so it aligns with EarnRoutePaths.Input and the Route usage in EarnTab.tsx (Route
path='confirm' element={earnConfirmElement}). Ensure only EarnRoutePaths.Confirm
is updated to the relative string while leaving EarnRoutePaths.Input as '/earn'.
In `@src/pages/Yields/components/YieldEnterModal.tsx`:
- Around line 312-316: The effect currently closes the modal immediately when
step === ModalStep.Success (useEffect watching [step, handleModalClose]), which
prevents the successContent (rendered when isSuccess is true) from ever being
seen; either remove this useEffect or change it to delay calling
handleModalClose (e.g., setTimeout) so users can view successContent, or if
immediate close is intended, remove the now-dead successContent/isSuccess
rendering blocks and any related success UI code; update references to
ModalStep.Success, handleModalClose, isSuccess, and successContent accordingly.
In `@src/pages/Yields/components/YieldForm.tsx`:
- Around line 99-105: The TSX type reference React.ChangeEvent in the
CryptoAmountInputProps type is used without importing React; fix by importing
the ChangeEvent type (or React) from 'react' and update the type signature to
use the imported symbol (e.g., ChangeEvent<HTMLInputElement>) so the compiler
can resolve React.ChangeEvent; ensure the file imports from 'react' and adjust
the onChange prop in CryptoAmountInputProps accordingly.
In `@src/pages/Yields/components/YieldItem.tsx`:
- Around line 219-222: GradientApy is a Chakra Text-based component and the prop
itemProp='medium' is a typo; replace the invalid prop with fontWeight='medium'
on the GradientApy element (look for the GradientApy usage that currently passes
itemProp, e.g., the instance rendering {apyFormatted}) so it uses a valid
TextProps property.
In `@src/pages/Yields/components/YieldsList.tsx`:
- Around line 268-314: The recommendedYields calculation currently falls back to
precision = assets[assetId]?.precision ?? 18 which can miscompute human-readable
balances; update the eligibleYields logic inside the useMemo (recommendedYields)
to skip assets when assets[assetId] or assets[assetId].precision is undefined
(use continue) instead of defaulting to 18 before calling
fromBaseUnit(baseBalance, precision), so the minimum-deposit check uses only
assets with known precision and avoids incorrect eligibility decisions.
In `@src/pages/Yields/hooks/useYieldTransactionFlow.ts`:
- Around line 86-103: In waitForTransactionConfirmation the poll completeness
predicate currently treats a missing tx as confirmed because tx?.status !==
TransactionStatus.Created is true when tx is undefined; update the predicate to
only return true when tx exists and its status is not Created (e.g., Boolean(tx
&& tx.status !== TransactionStatus.Created)). Also update the error/completion
checker (the third callback) to handle tx === undefined explicitly and return a
descriptive Error('Transaction not found') (or keep as not-found handling you
prefer) before checking tx.status === TransactionStatus.Failed, so missing
transactions don't prematurely stop polling as "confirmed."
- Around line 278-331: The allowance query is currently enabled for any 'enter'
action with an approvalSpender when the feature flag is on, but it should only
run for USDT on Ethereum mainnet; update the useAllowance call (and its
isDisabled logic) to also require isUsdtOnEthereumMainnet(inputTokenAssetId,
yieldChainId) before enabling polling (keep other guards: action==='enter',
isUsdtApprovalResetEnabled and approvalSpender), so change the isDisabled
expression in the useAllowance invocation to include
!isUsdtOnEthereumMainnet(inputTokenAssetId, yieldChainId).
In `@src/pages/Yields/YieldDetail.tsx`:
- Around line 91-100: Rename the returned object key `usd` to a clearer name
like `userCurrency` (or `displayCurrency`) in the useMemo block that computes
totalUsd (the block using totalUsd.times(userCurrencyToUsdRate)), so the value
reflects it's in the user's selected currency; then update all call sites that
access this memoized result (replace references to `.usd` with `.userCurrency`
or the chosen name). Ensure you update the return object in YieldDetail and any
destructurings or property accesses elsewhere to use the new key.
In `@src/state/slices/tradeEarnInputSlice/selectors.ts`:
- Around line 38-41: Remove the redundant selector export
selectEarnHasUserEnteredAmount from selectors.ts and replace its usages with the
base selector selectHasUserEnteredAmount: delete the createSelector block for
selectEarnHasUserEnteredAmount, update any imports (notably in EarnInput.tsx) to
import selectHasUserEnteredAmount instead of selectEarnHasUserEnteredAmount, and
update the selector reference in EarnInput.tsx to use selectHasUserEnteredAmount
so the shared implementation is used rather than a duplicate wrapper.
🧹 Nitpick comments (19)
src/pages/Yields/components/YieldViewHelpers.tsx (1)
22-37: Responsive layout looks good.The simplified ButtonGroup with responsive
widthandflexproperties correctly handles mobile (full-width buttons) and desktop (inline auto-width) layouts.Optional: The aria-labels on lines 24 and 31 are hardcoded English strings. For consistency with
ListHeaderwhich usestranslate()for all text, consider using translation keys here as well to support i18n for screen reader users.♻️ Optional: Use translation keys for aria-labels
<ButtonGroup size='md' isAttached variant='outline' width={{ base: 'full', md: 'auto' }}> <IconButton - aria-label='Grid View' + aria-label={translate('yieldXYZ.gridView')} icon={gridIcon} onClick={handleSetGridView} isActive={isGridActive} flex={{ base: 1, md: 'none' }} /> <IconButton - aria-label='List View' + aria-label={translate('yieldXYZ.listView')} icon={listIcon} onClick={handleSetListView} isActive={isListActive} flex={{ base: 1, md: 'none' }} /> </ButtonGroup>This would require adding translation keys and passing
translatefromuseTranslate().src/pages/Yields/components/YieldOpportunityStats.tsx (1)
218-271: Consider extracting repeated tooltip pattern.The info tooltip pattern is repeated three times with the same structure. Consider extracting into a small helper component for reduced duplication.
♻️ Optional: Extract InfoTooltip helper
const InfoTooltip = memo(function InfoTooltip({ label, color = 'purple.300' }: { label: string color?: string }) { return ( <Tooltip label={label} hasArrow> <Box as='span' cursor='help'> <Icon as={FaInfoCircle} boxSize={3} color={color} /> </Box> </Tooltip> ) })Usage:
<InfoTooltip label={translate('yieldXYZ.availableToEarnTooltip')} />src/pages/Yields/components/YieldHero.tsx (2)
55-56: Consider reading validator directly without useMemo.The
searchParamsobject fromuseSearchParamsshould be stable when unchanged, butsearchParams.get('validator')is a simple string lookup. You could simplify to:const validator = searchParams.get('validator')This is a minor optimization since the memoization overhead may exceed the getter cost.
227-240: Redundant opacity whenisDisabledis set.Chakra's
Buttoncomponent already applies reduced opacity whenisDisabled={true}. The manualopacityprop may conflict with or override the built-in disabled styling.Suggested simplification
<Button leftIcon={exitIcon} variant='outline' size='lg' height={14} borderRadius='xl' onClick={handleExit} flex={1} fontWeight='bold' isDisabled={!hasExitBalance} - opacity={!hasExitBalance ? 0.6 : 1} > {exitLabel} </Button>src/components/MultiHopTrade/components/Earn/components/EarnFooter.tsx (2)
76-129: Consider extracting icon elements to avoid recreating them on each call.The
getYieldExplainersfunction creates newIconandInfoIconJSX elements on every invocation. Since these are static and don't depend on props, consider defining them as constants outside the function.♻️ Suggested refactor
+const swapIcon = <Icon as={MdSwapHoriz} color='text.subtle' /> +const giftIcon = <Icon as={FaGift} color='text.subtle' /> +const infoIcon = <InfoIcon color='text.subtle' /> + const getYieldExplainers = (selectedYield: AugmentedYieldDto): ExplainerItem[] => { const yieldType = selectedYield.mechanics.type const outputTokenSymbol = selectedYield.outputToken?.symbol switch (yieldType) { case 'liquid-staking': return [ { - icon: <Icon as={MdSwapHoriz} color='text.subtle' />, + icon: swapIcon, textKey: outputTokenSymbol ? 'earn.explainers.liquidStakingReceive' : 'earn.explainers.liquidStakingTrade', }, { - icon: <Icon as={FaGift} color='text.subtle' />, + icon: giftIcon, textKey: 'earn.explainers.rewardsSchedule', }, { - icon: <InfoIcon color='text.subtle' />, + icon: infoIcon, textKey: 'earn.explainers.liquidStakingWithdraw', }, ] // ... apply similar changes to other cases
284-293: Potential empty string rendering for yield type.When
selectedYield.mechanics.typeis an empty string, this condition is truthy but the text will render an empty string after thereplace(). Consider adding a more explicit check.♻️ Suggested fix
- {selectedYield.mechanics.type && ( + {selectedYield.mechanics.type ? ( <Flex justify='space-between' align='center' mt={3}> <Text fontSize='sm' color='text.subtle'> {translate('earn.yieldType')} </Text> <Text fontSize='sm' fontWeight='medium' textTransform='capitalize'> {selectedYield.mechanics.type.replace(/-/g, ' ')} </Text> </Flex> - )} + ) : null}src/react-queries/queries/yieldxyz/useYieldValidators.ts (1)
33-36: Consider applying ensureValidatorApr consistently.When
existingShapeshift?.rewardRate?.totalis truthy (line 15-16), theshapeshiftValidatorbypassesensureValidatorApr. While this should be fine since it has a total, for defensive consistency you might consider applying the same normalization. That said, the current logic is functionally correct.♻️ Optional: Apply ensureValidatorApr uniformly
const normalizeCosmosValidators = (validators: ValidatorDto[]): ValidatorDto[] => { const existingShapeshift = validators.find(v => v.address === SHAPESHIFT_COSMOS_VALIDATOR_ADDRESS) - const shapeshiftValidator: ValidatorDto = existingShapeshift?.rewardRate?.total - ? { ...existingShapeshift, preferred: true } - : { ...SHAPESHIFT_VALIDATOR, ...existingShapeshift, preferred: true } + const baseShapeshiftValidator: ValidatorDto = existingShapeshift?.rewardRate?.total + ? existingShapeshift + : { ...SHAPESHIFT_VALIDATOR, ...existingShapeshift } + + const shapeshiftValidator = ensureValidatorApr({ ...baseShapeshiftValidator, preferred: true }) const otherValidators = validators .filter(v => v.address !== SHAPESHIFT_COSMOS_VALIDATOR_ADDRESS) .map(v => ensureValidatorApr({ ...v, preferred: false })) return [shapeshiftValidator, ...otherValidators] }src/pages/Yields/components/YieldManager.tsx (1)
17-29: Consider narrowing the action type more defensively.The type assertion on line 23 casts arbitrary URL input to a union type. If a user navigates directly with an invalid action parameter, TypeScript won't catch it at runtime, though line 69 provides a sensible fallback to
'enter'.This is a minor concern since the fallback handles the edge case gracefully.
♻️ Optional: Add runtime validation
+ const VALID_ACTIONS = ['enter', 'exit', 'claim'] as const + type YieldAction = (typeof VALID_ACTIONS)[number] + + const rawAction = searchParams.get('action') + const action: YieldAction | undefined = rawAction && VALID_ACTIONS.includes(rawAction as YieldAction) + ? (rawAction as YieldAction) + : undefined - const action = searchParams.get('action') as 'enter' | 'exit' | 'claim' | undefinedsrc/pages/Trade/tabs/EarnTab.tsx (1)
82-84: Consider removing unnecessary useMemo.The
titlecomputation is a simple translation lookup that doesn't benefit from memoization. Thetranslatefunction call is lightweight anduseMemoadds overhead here.Suggested simplification
- const title = useMemo(() => { - return translate('navBar.earn') - }, [translate]) + const title = translate('navBar.earn')src/components/MultiHopTrade/components/Earn/components/YieldSelector.tsx (1)
110-116: Consider using a local fallback asset instead of external URL.The hardcoded CoinGecko fallback URL (
https://assets.coingecko.com/...) creates an external dependency that could fail if the service is unavailable or changes the URL structure. Consider using a local placeholder asset from the app's assets.Also applies to: 225-231
src/pages/Yields/YieldAssetDetails.tsx (2)
45-45: Consider using Chakra's theme breakpoint instead of hardcoded pixel value.The hardcoded
'(max-width: 768px)'could diverge from Chakra'smdbreakpoint if the theme is customized. Consider usinguseBreakpointValuefor consistency with the design system.Alternative using useBreakpointValue
-import { - ... - useMediaQuery, -} from '@chakra-ui/react' +import { + ... + useBreakpointValue, +} from '@chakra-ui/react' - const [isMobile] = useMediaQuery('(max-width: 768px)') + const isMobile = useBreakpointValue({ base: true, md: false }, { ssr: false })
440-463: Mobile filter hiding could use responsive display object.The
display={isMobile ? 'none' : 'flex'}pattern works, but Chakra's responsive object syntax (display={{ base: 'none', md: 'flex' }}) would be more idiomatic and wouldn't require theuseMediaQueryhook if that's the only usage.src/components/MultiHopTrade/components/Earn/EarnInput.tsx (1)
133-140: URL navigation triggers on every input change.This effect navigates to a new URL on every change to
sellAmountCryptoBaseUnit, which includes each keystroke in the amount input. This could cause excessive history entries and performance overhead.Consider debouncing this navigation or only navigating on blur/submit.
Proposed debounced navigation
+ const debouncedSellAmountCryptoBaseUnit = useDebounce(sellAmountCryptoBaseUnit, 500) + useEffect(() => { if (!sellAsset.assetId || !selectedYieldId) return const encodedYieldId = encodeURIComponent(selectedYieldId) - const baseUnit = sellAmountCryptoBaseUnit ?? '0' + const baseUnit = debouncedSellAmountCryptoBaseUnit ?? '0' navigate(`/earn/${sellAsset.assetId}/${encodedYieldId}/${baseUnit}`, { replace: true }) - }, [sellAsset.assetId, selectedYieldId, sellAmountCryptoBaseUnit, navigate]) + }, [sellAsset.assetId, selectedYieldId, debouncedSellAmountCryptoBaseUnit, navigate])src/components/MultiHopTrade/components/Earn/EarnConfirm.tsx (1)
35-36: The empty object fallback is safe due to theisOpenguard, but the type assertion is unnecessary complexity.The
yieldItem: selectedYield ?? defaultYieldItempattern withisOpen: Boolean(selectedYield)ensures the hook never executes when the empty object is passed, since the query is only enabled whenisOpenis true (which correlates withselectedYieldbeing defined). The hook also guards against an incompleteyieldItemwith checks likeif (!yieldItem || !userAddress || !yieldChainId) return nullintxArguments.However, this pattern introduces unnecessary complexity. Since the hook already has proper guards and the
isOpenflag controls execution, consider whether the hook can acceptyieldItemas optional (AugmentedYieldDto | undefined) to eliminate the type assertion entirely.src/pages/Yields/components/YieldForm.tsx (1)
402-407: Remove the no-opuseEffect.It currently does nothing and can be dropped to reduce noise.
src/pages/Yields/components/YieldAssetSection.tsx (1)
44-129: Modal flow looks good; small state simplification is possible.
isEnterModalOpencould be derived fromselectedYield !== null(or vice-versa) to avoid two sources of truth, but current logic is fine.src/pages/Yields/components/YieldActionModal.tsx (3)
176-195: Variable shadowing:stepshadows theModalStepstate variable.On line 179, the local
const step = transactionSteps[activeStepIndex]shadows thestepvariable fromuseYieldTransactionFlow(line 61). While this doesn't cause a bug since the outerstepis of typeModalStepand the inner is a transaction step object, it can cause confusion during maintenance.♻️ Suggested fix: rename the local variable
if (activeStepIndex >= 0 && transactionSteps[activeStepIndex]) { - const step = transactionSteps[activeStepIndex] - return getTransactionButtonText(step.type, step.originalTitle) + const currentStep = transactionSteps[activeStepIndex] + return getTransactionButtonText(currentStep.type, currentStep.originalTitle) }
208-208: Consider memoizingassetFlowDirection.Per coding guidelines, conditional values should be wrapped in
useMemo. While this is a simple ternary, wrapping it maintains consistency with the rest of the component.♻️ Suggested fix
- const assetFlowDirection = action === 'exit' ? 'exit' : 'enter' + const assetFlowDirection = useMemo( + () => (action === 'exit' ? 'exit' : 'enter'), + [action], + )
340-343: Unreachable null branch:vaultMetadatais always truthy.The
vaultMetadatacomputed on lines 102-111 always returns an object (the final fallback is{ name: 'Vault', logoURI: yieldItem.metadata.logoURI }). The ternary's null branch is unreachable.♻️ Suggested simplification
const successProviderInfo = useMemo( - () => (vaultMetadata ? { name: vaultMetadata.name, logoURI: vaultMetadata.logoURI } : null), + () => ({ name: vaultMetadata.name, logoURI: vaultMetadata.logoURI }), [vaultMetadata], )
| const getYieldTypeName = (type: string): string => { | ||
| const typeMap: Record<string, string> = { | ||
| 'native-staking': 'Native Staking', | ||
| 'pooled-staking': 'Pooled Staking', | ||
| 'liquid-staking': 'Liquid Staking', | ||
| staking: 'Staking', | ||
| lending: 'Lending', | ||
| vault: 'Vault', | ||
| restaking: 'Restaking', | ||
| } | ||
| if (typeMap[type]) return typeMap[type] | ||
| return type | ||
| .split('-') | ||
| .filter((word): word is string => Boolean(word)) | ||
| .map(word => word.charAt(0).toUpperCase() + word.slice(1)) | ||
| .join(' ') | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded English strings should use translation keys.
The getYieldTypeName function contains hardcoded English display names. Per coding guidelines, all user-facing text must use translation keys for i18n support.
Suggested approach
-const getYieldTypeName = (type: string): string => {
- const typeMap: Record<string, string> = {
- 'native-staking': 'Native Staking',
- 'pooled-staking': 'Pooled Staking',
- 'liquid-staking': 'Liquid Staking',
- staking: 'Staking',
- lending: 'Lending',
- vault: 'Vault',
- restaking: 'Restaking',
- }
- if (typeMap[type]) return typeMap[type]
- return type
- .split('-')
- .filter((word): word is string => Boolean(word))
- .map(word => word.charAt(0).toUpperCase() + word.slice(1))
- .join(' ')
-}
+const getYieldTypeName = (type: string, translate: (key: string) => string): string => {
+ const typeKey = `yieldXYZ.types.${type.replace(/-/g, '_')}`
+ const translated = translate(typeKey)
+ if (translated !== typeKey) return translated
+ return type
+ .split('-')
+ .filter((word): word is string => Boolean(word))
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
+ .join(' ')
+}Then add corresponding translation keys in main.json.
| if (!selectedYield) { | ||
| return ( | ||
| <SharedConfirm | ||
| bodyContent={ | ||
| <VStack spacing={4} p={6} flex={1} justify='center'> | ||
| <Text>{translate('earn.selectYieldOpportunity')}</Text> | ||
| <Button onClick={handleBack}>{translate('common.goBack')}</Button> | ||
| </VStack> | ||
| } | ||
| footerContent={null} | ||
| isLoading={false} | ||
| onBack={handleBack} | ||
| headerTranslation='earn.confirmEarn' | ||
| /> | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find SharedConfirm component definition
fd -t f SharedConfirm.tsxRepository: shapeshift/web
Length of output: 129
🏁 Script executed:
#!/bin/bash
# Search for SharedConfirm type/interface definition across the codebase
rg 'type SharedConfirmProps|interface SharedConfirmProps|const SharedConfirm.*=|function SharedConfirm' -A 10 -t tsx -t tsRepository: shapeshift/web
Length of output: 85
🏁 Script executed:
#!/bin/bash
# Read the SharedConfirm component to see its props interface
cat -n src/components/MultiHopTrade/components/SharedConfirm/SharedConfirm.tsxRepository: shapeshift/web
Length of output: 2309
🏁 Script executed:
#!/bin/bash
# Check the specific lines in EarnConfirm.tsx where isLoading is allegedly passed
sed -n '186,223p' src/components/MultiHopTrade/components/Earn/EarnConfirm.tsx | cat -n
sed -n '340,350p' src/components/MultiHopTrade/components/Earn/EarnConfirm.tsx | cat -nRepository: shapeshift/web
Length of output: 1701
Remove unused isLoading prop from SharedConfirm.
The isLoading prop is defined in SharedConfirmProps but is not destructured or used in the component implementation. Remove it from the type definition on line 15 of SharedConfirm.tsx and the three call sites in EarnConfirm.tsx (lines 196, 219, 344).
🤖 Prompt for AI Agents
In `@src/components/MultiHopTrade/components/Earn/EarnConfirm.tsx` around lines
186 - 200, The SharedConfirm component type includes an unused prop isLoading
(in SharedConfirmProps) that should be removed; update the SharedConfirmProps
type to delete the isLoading field and then remove the isLoading={...} prop from
all SharedConfirm usages in EarnConfirm (the three call sites that pass
isLoading), ensuring you only delete the prop from the JSX and not other props
or logic—leave SharedConfirm, EarnConfirm, and related handlers (e.g.,
handleBack) unchanged.
| export enum EarnRoutePaths { | ||
| Input = '/earn', | ||
| Confirm = '/earn/confirm', | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find and examine route path enum definitions
echo "=== Searching for TradeRoutePaths ==="
rg -n "enum TradeRoutePaths" --type=ts -A 5
echo ""
echo "=== Searching for LimitOrderRoutePaths ==="
rg -n "enum LimitOrderRoutePaths" --type=ts -A 5
echo ""
echo "=== Searching for FiatRampRoutePaths ==="
rg -n "enum FiatRampRoutePaths" --type=ts -A 5
echo ""
echo "=== Searching for EarnRoutePaths usage in codebase ==="
rg "EarnRoutePaths\." --type=ts -B 2 -A 2 | head -50Repository: shapeshift/web
Length of output: 4507
🏁 Script executed:
#!/bin/bash
# Check routing configuration for Earn component
echo "=== Searching for Earn route definitions in routing config ==="
rg -n "EarnRoutePaths\|/earn" --type=ts src/components/MultiHopTrade -B 2 -A 2 | grep -E "(Route|path|navigate)" | head -50
echo ""
echo "=== Check how MultiHopTrade routes are structured ==="
fd ".*Route.*" --type ts src/components/MultiHopTrade | head -20
echo ""
echo "=== Examine MultiHopTrade routing structure ==="
find src/components/MultiHopTrade -name "*Route*" -o -name "*routes*" | head -20Repository: shapeshift/web
Length of output: 476
🏁 Script executed:
#!/bin/bash
# Search for route registration and usage patterns
echo "=== Search for route registration in MultiHopTrade ==="
rg "Route|path=" src/components/MultiHopTrade --type-list | head -5
fd . src/components/MultiHopTrade -name "*.tsx" -o -name "*.ts" | xargs rg -l "Route.*EarnRoutePaths|EarnRoutePaths.*Route" 2>/dev/null | head -10
echo ""
echo "=== Check MultiHopTrade main file ==="
cat -n src/components/MultiHopTrade/MultiHopTrade.tsx | head -100
echo ""
echo "=== Look for route configuration patterns ==="
rg "Routes\|<Route" src/components/MultiHopTrade/*.tsx 2>/dev/null | head -20Repository: shapeshift/web
Length of output: 5667
🏁 Script executed:
#!/bin/bash
# Search for route configuration
echo "=== Continue reading MultiHopTrade.tsx to see route setup ==="
cat -n src/components/MultiHopTrade/MultiHopTrade.tsx | tail -150 | head -100
echo ""
echo "=== Search for Route elements with paths ==="
rg "path=" src/components/MultiHopTrade/MultiHopTrade.tsx -A 1 -B 1
echo ""
echo "=== Check Earn routing setup ==="
rg "EarnRoutePaths|earn" src/components/MultiHopTrade/MultiHopTrade.tsx -B 2 -A 2Repository: shapeshift/web
Length of output: 5501
🏁 Script executed:
#!/bin/bash
# Search for Earn route configuration
echo "=== Search for Earn routes setup ==="
rg "Earn" src/components/MultiHopTrade/MultiHopTrade.tsx
echo ""
echo "=== Check RoutesCommon for Earn route definitions ==="
rg "earn" src/Routes/RoutesCommon.tsx -B 3 -A 3
echo ""
echo "=== Look for where Earn component routes are defined ==="
fd "Earn" src/components/MultiHopTrade --type f | xargs cat | grep -n "Route\|path" | head -30Repository: shapeshift/web
Length of output: 1019
🏁 Script executed:
#!/bin/bash
# Look for the actual route definitions for Earn
echo "=== Search for where Earn routes are defined in MultiHopTrade.tsx ==="
wc -l src/components/MultiHopTrade/MultiHopTrade.tsx
echo ""
echo "=== Look at the full Routes section ==="
rg "Routes" src/components/MultiHopTrade/MultiHopTrade.tsx -A 50 | head -100
echo ""
echo "=== Check if there's a separate Earn routing component ==="
find src/components/MultiHopTrade/components/Earn -type f -name "*.tsx" -o -name "*.ts" | xargs ls -laRepository: shapeshift/web
Length of output: 4930
🏁 Script executed:
#!/bin/bash
# Search for Earn route nesting
echo "=== Get the full MultiHopTrade.tsx file to see Routes setup ==="
cat src/components/MultiHopTrade/MultiHopTrade.tsx | tail -80
echo ""
echo "=== Search for Earn component integration ==="
rg "Earn" src/components/MultiHopTrade/MultiHopTrade.tsx -B 5 -A 5
echo ""
echo "=== Look for nested route setup in Earn ==="
fd . src/components/MultiHopTrade/components/Earn -type f | xargs rg "Route" -lRepository: shapeshift/web
Length of output: 25309
🏁 Script executed:
#!/bin/bash
# Check how Earn routes are actually used
echo "=== Search for Route definitions with EarnRoutePaths ==="
rg "path.*earn|EarnRoutePaths" src/components/MultiHopTrade/components/Earn -B 3 -A 3
echo ""
echo "=== Check EarnTab.tsx for routing ==="
rg "EarnRoutePaths\|Route\|Routes" src/pages/Trade/tabs/EarnTab.tsx -B 2 -A 2
echo ""
echo "=== Look for where Earn routes might be nested ==="
rg "Routes" src/pages/Trade/tabs/EarnTab.tsx -B 5 -A 15Repository: shapeshift/web
Length of output: 6065
Use relative path for EarnRoutePaths.Confirm to match the established pattern.
The Route definition in EarnTab.tsx uses relative paths (<Route path='confirm' element={earnConfirmElement} />), but EarnRoutePaths defines absolute paths. This diverges from the pattern established by TradeRoutePaths and LimitOrderRoutePaths, which use absolute paths only for Input routes and relative paths for sub-routes like Confirm. Update EarnRoutePaths.Confirm = 'confirm' to align with the consistent pattern.
🤖 Prompt for AI Agents
In `@src/components/MultiHopTrade/components/Earn/types.ts` around lines 1 - 4,
EarnRoutePaths currently defines Confirm as an absolute path which mismatches
the routing pattern used elsewhere; change the enum value of
EarnRoutePaths.Confirm from an absolute path to the relative segment "confirm"
so it aligns with EarnRoutePaths.Input and the Route usage in EarnTab.tsx (Route
path='confirm' element={earnConfirmElement}). Ensure only EarnRoutePaths.Confirm
is updated to the relative string while leaving EarnRoutePaths.Input as '/earn'.
| if (isFiat) { | ||
| const crypto = bnOrZero(values.value) | ||
| .div(marketData?.price ?? 1) | ||
| .toFixed() | ||
| setCryptoAmount(crypto) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Division by zero possible when price is 0.
marketData?.price ?? 1 only falls back to 1 when price is null or undefined. If price is 0, the nullish coalescing operator returns 0, causing division by zero.
Proposed fix
if (isFiat) {
const crypto = bnOrZero(values.value)
- .div(marketData?.price ?? 1)
+ .div(marketData?.price || 1)
.toFixed()
setCryptoAmount(crypto)| const recommendedYields = useMemo(() => { | ||
| if (!isConnected || !yields?.byInputAssetId || !userCurrencyBalances || !assetBalancesBaseUnit) | ||
| return [] | ||
|
|
||
| const recommendations: { | ||
| yield: AugmentedYieldDto | ||
| balanceFiat: ReturnType<typeof bnOrZero> | ||
| potentialEarnings: ReturnType<typeof bnOrZero> | ||
| }[] = [] | ||
|
|
||
| for (const [assetId, balanceFiat] of Object.entries(userCurrencyBalances)) { | ||
| const yieldsForAsset = yields.byInputAssetId[assetId] | ||
| if (!yieldsForAsset?.length) continue | ||
|
|
||
| const balance = bnOrZero(balanceFiat) | ||
| if (balance.lte(0)) continue | ||
|
|
||
| // Filter to only include yields where user meets the minimum requirement | ||
| const eligibleYields = yieldsForAsset.filter(y => { | ||
| const minDeposit = bnOrZero(y.mechanics?.entryLimits?.minimum) | ||
| if (minDeposit.lte(0)) return true | ||
|
|
||
| // minDeposit is human readable (e.g. 32), so convert base balance to human | ||
| const precision = assets[assetId]?.precision ?? 18 | ||
| const baseBalance = bnOrZero(assetBalancesBaseUnit[assetId]) | ||
| const balanceHuman = bnOrZero(fromBaseUnit(baseBalance, precision)) | ||
| return balanceHuman.gte(minDeposit) | ||
| }) | ||
|
|
||
| if (!eligibleYields.length) continue | ||
|
|
||
| const bestYield = eligibleYields.reduce((best, current) => | ||
| current.rewardRate.total > best.rewardRate.total ? current : best, | ||
| ) | ||
|
|
||
| recommendations.push({ | ||
| yield: bestYield, | ||
| balanceFiat: balance, | ||
| potentialEarnings: balance.times(bestYield.rewardRate.total), | ||
| }) | ||
| } | ||
|
|
||
| return recommendations | ||
| .sort((a, b) => b.potentialEarnings.minus(a.potentialEarnings).toNumber()) | ||
| .slice(0, 3) | ||
| }, [isConnected, yields?.byInputAssetId, userCurrencyBalances, assetBalancesBaseUnit, assets]) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find fromBaseUnit definition and check its expectations
rg -n "export const fromBaseUnit|function fromBaseUnit|fromBaseUnit.*=" src packages --type ts --type tsx -B2 -A5 | head -100Repository: shapeshift/web
Length of output: 85
🏁 Script executed:
#!/bin/bash
# Get callsites to understand typical usage patterns
rg -n "fromBaseUnit\(" src packages --type ts --type tsx -C2 | head -150Repository: shapeshift/web
Length of output: 85
🏁 Script executed:
#!/bin/bash
# Check what assets are used in yields context and how precision varies
rg -n "precision\s*\?" src/pages/Yields --type ts --type tsx -B3 -A3Repository: shapeshift/web
Length of output: 85
🏁 Script executed:
#!/bin/bash
# Look at how assets are loaded/defined in the Yields context
rg -n "useAppSelector.*assets|const assets.*=" src/pages/Yields --type ts --type tsx -B2 -A5 | head -100Repository: shapeshift/web
Length of output: 85
🏁 Script executed:
#!/bin/bash
# Find fromBaseUnit definition
rg "export const fromBaseUnit|function fromBaseUnit" -B2 -A8 src packagesRepository: shapeshift/web
Length of output: 1616
🏁 Script executed:
#!/bin/bash
# Find fromBaseUnit usage patterns
rg "fromBaseUnit\(" -B1 -A1 src packages | head -200Repository: shapeshift/web
Length of output: 15055
🏁 Script executed:
#!/bin/bash
# Check the specific file mentioned in the review
head -350 src/pages/Yields/components/YieldsList.tsx | tail -100Repository: shapeshift/web
Length of output: 3510
🏁 Script executed:
#!/bin/bash
# Look for asset precision patterns and how missing assets are handled
rg "assets\[.*\].*precision|precision.*\?" src/pages/Yields -B2 -A2Repository: shapeshift/web
Length of output: 561
🏁 Script executed:
#!/bin/bash
# Check how assets object is populated in yields context
rg "const assets.*=" src/pages/Yields -B5 -A5 | head -80Repository: shapeshift/web
Length of output: 923
🏁 Script executed:
#!/bin/bash
# Check if there are any patterns in the codebase where precision 18 is used as fallback
rg "precision.*\?\?\s*18|precision.*18\s*\?" src | head -50Repository: shapeshift/web
Length of output: 753
🏁 Script executed:
#!/bin/bash
# See if there are tests showing the impact of wrong precision
rg -n "fromBaseUnit.*18" packages/utils/src/baseUnits/ -B3 -A3Repository: shapeshift/web
Length of output: 1126
🏁 Script executed:
#!/bin/bash
# Check selectAssets to understand what data it contains and if it can have gaps
rg -n "selectAssets" src/state/slices -B5 -A10 | head -100Repository: shapeshift/web
Length of output: 8433
🏁 Script executed:
#!/bin/bash
# Look at asset precision in the codebase to see common values
rg "\.precision" src -o | sort | uniq -c | sort -rn | head -20Repository: shapeshift/web
Length of output: 1992
🏁 Script executed:
#!/bin/bash
# Check if there are any tests for precision fallback scenarios
rg "precision.*fallback|fallback.*precision" --type-listRepository: shapeshift/web
Length of output: 447
🏁 Script executed:
#!/bin/bash
# Check the common-selectors pattern to see how they handle missing precision
rg -A10 "if (precision === undefined)" src/state/slices/common-selectors.tsRepository: shapeshift/web
Length of output: 40
🏁 Script executed:
#!/bin/bash
# Look for how yields typically handle missing asset data
rg "assets\[.*\]" src/pages/Yields -B2 -A2 | head -100Repository: shapeshift/web
Length of output: 561
🏁 Script executed:
#!/bin/bash
# Check if selectAssets can return incomplete data
rg -A20 "export const selectAssets" src/state/slices/assetsSlice/selectors.tsRepository: shapeshift/web
Length of output: 2202
🏁 Script executed:
#!/bin/bash
# Look at how portfolio selectors safely handle missing assets
rg -B5 -A10 "if (asset)" src/state/slices/portfolioSlice/selectors.ts | head -80Repository: shapeshift/web
Length of output: 40
🏁 Script executed:
#!/bin/bash
# Check selectAssets definition to see if it can have missing entries
rg -B5 -A10 "selectAssetsById" src/state/slices/assetsSlice/selectors.ts | head -50Repository: shapeshift/web
Length of output: 1726
🏁 Script executed:
#!/bin/bash
# Look at other places where precision ?? 18 is used to understand if this pattern is widespread
rg "precision.*\?\?\s*18|precision.*??\s*18" -B3 -A3 src pagesRepository: shapeshift/web
Length of output: 18852
🏁 Script executed:
#!/bin/bash
# Check if assetBalancesBaseUnit can have keys that aren't in assets object
rg -B10 -A5 "assetBalancesBaseUnit" src/pages/Yields/components/YieldsList.tsxRepository: shapeshift/web
Length of output: 2304
Skip yield eligibility check when asset metadata is missing instead of defaulting precision to 18.
Using precision ?? 18 as a fallback can incorrectly mark yields as eligible or ineligible because fromBaseUnit(baseBalance, precision) depends critically on the correct precision value. If an asset is missing from the asset registry but present in the user's balance, defaulting to 18 assumes it's an EVM token—which may not be true. This produces incorrect human-readable balance calculations, breaking the minimum deposit eligibility check. Instead, skip the asset with continue when precision is unknown.
🤖 Prompt for AI Agents
In `@src/pages/Yields/components/YieldsList.tsx` around lines 268 - 314, The
recommendedYields calculation currently falls back to precision =
assets[assetId]?.precision ?? 18 which can miscompute human-readable balances;
update the eligibleYields logic inside the useMemo (recommendedYields) to skip
assets when assets[assetId] or assets[assetId].precision is undefined (use
continue) instead of defaulting to 18 before calling fromBaseUnit(baseBalance,
precision), so the minimum-deposit check uses only assets with known precision
and avoids incorrect eligibility decisions.
| export const waitForTransactionConfirmation = ( | ||
| actionId: string, | ||
| transactionId: string, | ||
| ): Promise<ActionDto> => | ||
| poll( | ||
| () => fetchAction(actionId), | ||
| action => { | ||
| const tx = action.transactions.find(t => t.id === transactionId) | ||
| return tx?.status !== TransactionStatus.Created | ||
| }, | ||
| action => { | ||
| if (action.status === YieldActionStatus.Failed) return new Error('Action failed') | ||
| if (action.status === YieldActionStatus.Canceled) return new Error('Action was canceled') | ||
| const tx = action.transactions.find(t => t.id === transactionId) | ||
| if (tx?.status === TransactionStatus.Failed) return new Error('Transaction failed') | ||
| return undefined | ||
| }, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix polling completion: treat “tx not found” as not-confirmed (or an error), not complete.
Right now tx?.status !== TransactionStatus.Created evaluates to true when tx is undefined, so polling can “confirm” a non-existent transaction.
Proposed fix
export const waitForTransactionConfirmation = (
actionId: string,
transactionId: string,
): Promise<ActionDto> =>
poll(
() => fetchAction(actionId),
action => {
const tx = action.transactions.find(t => t.id === transactionId)
- return tx?.status !== TransactionStatus.Created
+ return Boolean(tx && tx.status !== TransactionStatus.Created)
},
action => {
if (action.status === YieldActionStatus.Failed) return new Error('Action failed')
if (action.status === YieldActionStatus.Canceled) return new Error('Action was canceled')
const tx = action.transactions.find(t => t.id === transactionId)
if (tx?.status === TransactionStatus.Failed) return new Error('Transaction failed')
return undefined
},
)🤖 Prompt for AI Agents
In `@src/pages/Yields/hooks/useYieldTransactionFlow.ts` around lines 86 - 103, In
waitForTransactionConfirmation the poll completeness predicate currently treats
a missing tx as confirmed because tx?.status !== TransactionStatus.Created is
true when tx is undefined; update the predicate to only return true when tx
exists and its status is not Created (e.g., Boolean(tx && tx.status !==
TransactionStatus.Created)). Also update the error/completion checker (the third
callback) to handle tx === undefined explicitly and return a descriptive
Error('Transaction not found') (or keep as not-found handling you prefer) before
checking tx.status === TransactionStatus.Failed, so missing transactions don't
prematurely stop polling as "confirmed."
| // USDT reset logic - only for enter action on USDT/ETH | ||
| const approvalSpender = useMemo(() => { | ||
| if (action !== 'enter') return null | ||
| if (!quoteData?.transactions) return null | ||
| const createdTransactions = quoteData.transactions.filter( | ||
| tx => tx.status === TransactionStatus.Created, | ||
| ) | ||
| const approvalTx = createdTransactions.find(isApprovalTransaction) | ||
| if (!approvalTx) return null | ||
| return getSpenderFromApprovalTx(approvalTx) | ||
| }, [action, quoteData?.transactions]) | ||
|
|
||
| const allowanceQuery = useAllowance({ | ||
| assetId: inputTokenAssetId, | ||
| spender: approvalSpender ?? undefined, | ||
| from: userAddress || undefined, | ||
| isDisabled: !approvalSpender || !isUsdtApprovalResetEnabled || action !== 'enter', | ||
| isRefetchEnabled: true, | ||
| }) | ||
|
|
||
| const isUsdtResetRequired = useMemo(() => { | ||
| if (action !== 'enter') return false | ||
| if (!isUsdtApprovalResetEnabled) return false | ||
| if (!isUsdtOnEthereumMainnet(inputTokenAssetId, yieldChainId)) return false | ||
| if (!approvalSpender) return false | ||
| if (!allowanceQuery.data) return false | ||
| return bnOrZero(allowanceQuery.data).gt(0) | ||
| }, [ | ||
| action, | ||
| isUsdtApprovalResetEnabled, | ||
| inputTokenAssetId, | ||
| yieldChainId, | ||
| approvalSpender, | ||
| allowanceQuery.data, | ||
| ]) | ||
|
|
||
| // Check if we're waiting for USDT allowance check before we can determine reset requirement | ||
| const isAllowanceCheckPending = useMemo(() => { | ||
| if (action !== 'enter') return false | ||
| if (!isUsdtApprovalResetEnabled) return false | ||
| if (!isUsdtOnEthereumMainnet(inputTokenAssetId, yieldChainId)) return false | ||
| if (!approvalSpender) return false | ||
| // If we have an approval spender for USDT but allowance data hasn't loaded yet | ||
| return allowanceQuery.data === undefined && !allowanceQuery.isError | ||
| }, [ | ||
| action, | ||
| isUsdtApprovalResetEnabled, | ||
| inputTokenAssetId, | ||
| yieldChainId, | ||
| approvalSpender, | ||
| allowanceQuery.data, | ||
| allowanceQuery.isError, | ||
| ]) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Narrow allowance polling to the actual reset scenario (USDT on Ethereum mainnet).
useAllowance currently runs for any enter with an approval spender when the feature flag is on, even though the reset logic only applies to USDT on Ethereum mainnet.
Proposed fix
const allowanceQuery = useAllowance({
assetId: inputTokenAssetId,
spender: approvalSpender ?? undefined,
from: userAddress || undefined,
- isDisabled: !approvalSpender || !isUsdtApprovalResetEnabled || action !== 'enter',
+ isDisabled:
+ !approvalSpender ||
+ !isUsdtApprovalResetEnabled ||
+ action !== 'enter' ||
+ !isUsdtOnEthereumMainnet(inputTokenAssetId, yieldChainId),
isRefetchEnabled: true,
})🤖 Prompt for AI Agents
In `@src/pages/Yields/hooks/useYieldTransactionFlow.ts` around lines 278 - 331,
The allowance query is currently enabled for any 'enter' action with an
approvalSpender when the feature flag is on, but it should only run for USDT on
Ethereum mainnet; update the useAllowance call (and its isDisabled logic) to
also require isUsdtOnEthereumMainnet(inputTokenAssetId, yieldChainId) before
enabling polling (keep other guards: action==='enter',
isUsdtApprovalResetEnabled and approvalSpender), so change the isDisabled
expression in the useAllowance invocation to include
!isUsdtOnEthereumMainnet(inputTokenAssetId, yieldChainId).
| const totalUsd = [activeBalance, enteringBalance, exitingBalance, withdrawableBalance].reduce( | ||
| (sum, b) => sum.plus(bnOrZero(b?.aggregatedAmountUsd)), | ||
| bnOrZero(0), | ||
| ) | ||
|
|
||
| return { | ||
| usd: totalUsd.times(userCurrencyToUsdRate).toFixed(), | ||
| crypto: totalCrypto.toFixed(), | ||
| } | ||
| }, [balances, selectedValidatorAddress, userCurrencyToUsdRate]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find the definition of selectUserCurrencyToUsdRate and examine its implementation
rg -n 'selectUserCurrencyToUsdRate' --type=ts --type=tsx -B2 -A10 | head -100Repository: shapeshift/web
Length of output: 85
🏁 Script executed:
# Also search for where this selector is defined
rg -n 'export.*selectUserCurrencyToUsdRate|const selectUserCurrencyToUsdRate' --type=ts --type=tsx -A15Repository: shapeshift/web
Length of output: 85
🏁 Script executed:
# Check the actual YieldDetail.tsx file around the questionable code
cat -n src/pages/Yields/YieldDetail.tsx | sed -n '85,105p'Repository: shapeshift/web
Length of output: 832
🏁 Script executed:
# Search for selectUserCurrencyToUsdRate definition without file type issues
rg -n 'selectUserCurrencyToUsdRate' -A10 | head -80Repository: shapeshift/web
Length of output: 6867
🏁 Script executed:
# Find where this selector is defined
rg -n 'const selectUserCurrencyToUsdRate|export.*selectUserCurrencyToUsdRate' -A15 | head -100Repository: shapeshift/web
Length of output: 1488
🏁 Script executed:
# Look for the selector in the slices directory (common Redux pattern)
find . -name "*.ts" -o -name "*.tsx" | xargs grep -l "selectUserCurrencyToUsdRate" | head -10Repository: shapeshift/web
Length of output: 653
🏁 Script executed:
# Verify the fiatMarketData structure and confirm this pattern is consistent
rg -n 'fiatMarketData\[' --type=ts -A2 | head -40Repository: shapeshift/web
Length of output: 1332
Rename the usd key to reflect user currency denomination.
The value at line 97 is correctly calculated in user currency (USD * rate = user currency amount), but the return key usd is misleading. Rename it to userCurrency or displayCurrency to accurately reflect the denomination of the returned value.
🤖 Prompt for AI Agents
In `@src/pages/Yields/YieldDetail.tsx` around lines 91 - 100, Rename the returned
object key `usd` to a clearer name like `userCurrency` (or `displayCurrency`) in
the useMemo block that computes totalUsd (the block using
totalUsd.times(userCurrencyToUsdRate)), so the value reflects it's in the user's
selected currency; then update all call sites that access this memoized result
(replace references to `.usd` with `.userCurrency` or the chosen name). Ensure
you update the return object in YieldDetail and any destructurings or property
accesses elsewhere to use the new key.
| export const selectEarnHasUserEnteredAmount = createSelector( | ||
| selectInputSellAmountCryptoPrecision, | ||
| sellAmountCryptoPrecision => bnOrZero(sellAmountCryptoPrecision).gt(0), | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, let's see the structure of the selectors file
cat -n src/state/slices/tradeEarnInputSlice/selectors.tsRepository: shapeshift/web
Length of output: 1748
🏁 Script executed:
#!/bin/bash
# Find and examine the createTradeInputBaseSelectors file
find . -name 'createTradeInputBaseSelectors*' -type f | head -5Repository: shapeshift/web
Length of output: 131
🏁 Script executed:
#!/bin/bash
# Read the createTradeInputBaseSelectors file to see selectHasUserEnteredAmount implementation
cat -n ./src/state/slices/common/tradeInputBase/createTradeInputBaseSelectors.ts | head -100Repository: shapeshift/web
Length of output: 4332
🏁 Script executed:
#!/bin/bash
# Continue reading to find selectHasUserEnteredAmount
cat -n ./src/state/slices/common/tradeInputBase/createTradeInputBaseSelectors.ts | sed -n '100,250p'Repository: shapeshift/web
Length of output: 6324
🏁 Script executed:
#!/bin/bash
# Now let's check the usage of both selectors in the codebase
echo "=== selectEarnHasUserEnteredAmount usage ==="
rg -n 'selectEarnHasUserEnteredAmount' --type ts --type tsx -g '!*.test.*' -A 2
echo ""
echo "=== selectHasUserEnteredAmount usage in Earn/Yields context ==="
rg -n 'selectHasUserEnteredAmount' src/pages/Yields src/pages/Earn --type ts --type tsx 2>/dev/null || echo "No matches in Earn/Yields pages"
echo ""
echo "=== selectHasUserEnteredAmount usage overall ==="
rg -n 'selectHasUserEnteredAmount' --type ts --type tsx -g '!*.test.*' | head -20Repository: shapeshift/web
Length of output: 322
🏁 Script executed:
#!/bin/bash
# Try with different approach - search for both selectors without type restrictions
echo "=== selectEarnHasUserEnteredAmount usage ==="
rg 'selectEarnHasUserEnteredAmount' -g '!*.test.ts' -g '!*.test.tsx' -A 2
echo ""
echo "=== selectHasUserEnteredAmount usage in tradeEarnInputSlice ==="
rg 'selectHasUserEnteredAmount' src/state/slices/tradeEarnInputSlice/ -g '!*.test.*' -A 2
echo ""
echo "=== selectHasUserEnteredAmount in other trade slices ==="
rg 'selectHasUserEnteredAmount' src/state/slices/tradeInputSlice/ -g '!*.test.*' -A 2Repository: shapeshift/web
Length of output: 1688
🏁 Script executed:
#!/bin/bash
# Check if selectHasUserEnteredAmount is actually exported/used from tradeEarnInputSlice
echo "=== Check tradeEarnInputSlice/selectors.ts exports ==="
cat src/state/slices/tradeEarnInputSlice/selectors.ts | grep -A 30 'export const'
echo ""
echo "=== Check if selectHasUserEnteredAmount is ever used from tradeEarnInputSlice ==="
rg 'from.*tradeEarnInputSlice.*selectHasUserEnteredAmount' -g '!*.test.*'
echo ""
echo "=== Check if selectHasUserEnteredAmount is used in tradeInputSlice context ==="
rg 'selectHasUserEnteredAmount' src/components/MultiHopTrade/ -B 3 -A 3 | head -40Repository: shapeshift/web
Length of output: 5982
🏁 Script executed:
#!/bin/bash
# Check the pattern - are there other Earn-specific selectors or is this unique?
echo "=== All exported selectors from tradeEarnInputSlice ==="
cat src/state/slices/tradeEarnInputSlice/selectors.ts | grep 'export const select'
echo ""
echo "=== Check if EarnInput.tsx uses any of the base selectHasUserEnteredAmount ==="
rg 'selectHasUserEnteredAmount' src/components/MultiHopTrade/components/Earn/Repository: shapeshift/web
Length of output: 308
🏁 Script executed:
#!/bin/bash
# Let's check if there's a pattern of domain-specific selector wrappers in other slices
echo "=== Check TradeInputSlice for similar pattern ==="
cat src/state/slices/tradeInputSlice/selectors.ts | grep -A 3 'selectHasUserEnteredAmount'
echo ""
echo "=== Check LimitOrderSlice for similar pattern ==="
find . -path ./node_modules -prune -o -name '*limitOrder*selectors*' -type f -print | head -5Repository: shapeshift/web
Length of output: 295
🏁 Script executed:
#!/bin/bash
# Let's verify if selectHasUserEnteredAmount from the destructured export is actually exported
echo "=== Verify exports from tradeEarnInputSlice/selectors.ts ==="
node -e "
const content = require('fs').readFileSync('src/state/slices/tradeEarnInputSlice/selectors.ts', 'utf-8');
const hasDirectExport = content.includes('export const selectHasUserEnteredAmount');
const hasDestructuredExport = content.match(/export const \{[\s\S]*selectHasUserEnteredAmount[\s\S]*\}/);
console.log('Direct export of selectHasUserEnteredAmount:', !!hasDirectExport);
console.log('Destructured export includes selectHasUserEnteredAmount:', !!hasDestructuredExport);
"
echo ""
echo "=== Check if selectHasUserEnteredAmount is imported from tradeEarnInputSlice anywhere ==="
rg 'from.*tradeEarnInputSlice.*selectHasUserEnteredAmount|import.*selectHasUserEnteredAmount.*from.*tradeEarnInputSlice'Repository: shapeshift/web
Length of output: 322
Remove duplicate selector and use the base selectHasUserEnteredAmount.
selectEarnHasUserEnteredAmount duplicates selectHasUserEnteredAmount from the base selectors without adding any distinct logic or behavior—both perform an identical bnOrZero(sellAmountCryptoPrecision).gt(0) check. Use the imported selectHasUserEnteredAmount in EarnInput.tsx instead of maintaining a separate wrapper selector.
🤖 Prompt for AI Agents
In `@src/state/slices/tradeEarnInputSlice/selectors.ts` around lines 38 - 41,
Remove the redundant selector export selectEarnHasUserEnteredAmount from
selectors.ts and replace its usages with the base selector
selectHasUserEnteredAmount: delete the createSelector block for
selectEarnHasUserEnteredAmount, update any imports (notably in EarnInput.tsx) to
import selectHasUserEnteredAmount instead of selectEarnHasUserEnteredAmount, and
update the selector reference in EarnInput.tsx to use selectHasUserEnteredAmount
so the shared implementation is used rather than a duplicate wrapper.
Removed the useEffect that auto-closed the modal on success, allowing users to see the success screen with transaction details before dismissing. Co-Authored-By: Claude Opus 4.5 <[email protected]>
The YieldSuccess component already has its own Close button, so the additional DialogFooter with Close button was redundant. Fixes: #11650 Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Cards/mobile: show Available (green) when user has wallet balance but no staked position - List view: show both Balance and Available when user has both - Earn button now shows individual yields instead of filtered groups - Pass correct position balance vs wallet balance to YieldItem Co-Authored-By: Claude Opus 4.5 <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/pages/Yields/components/YieldsList.tsx`:
- Around line 331-338: The eligibility check in yieldsForAsset.filter (used to
compute eligibleYields) uses a hardcoded precision fallback
(assets[assetId]?.precision ?? 18) which can produce incorrect comparisons;
mirror the fix used in recommendedYields by replacing that hardcoded fallback
with the same precision resolution logic (use the shared DEFAULT/utility
precision or the same assets lookup used in recommendedYields), then compute
baseBalance/fromBaseUnit with that resolved precision before comparing to
bnOrZero(y.mechanics?.entryLimits?.minimum) so the minimum deposit check uses
the correct precision.
♻️ Duplicate comments (2)
src/pages/Yields/components/YieldsList.tsx (1)
268-314: Skip yield eligibility check when asset metadata is missing instead of defaulting precision to 18.Using
precision ?? 18as a fallback can incorrectly mark yields as eligible or ineligible becausefromBaseUnit(baseBalance, precision)depends critically on the correct precision value. If an asset is missing from the asset registry but present in the user's balance, defaulting to 18 assumes it's an EVM token—which may not be true (e.g., USDC has 6 decimals). This produces incorrect human-readable balance calculations, breaking the minimum deposit eligibility check.🐛 Proposed fix
const eligibleYields = yieldsForAsset.filter(y => { const minDeposit = bnOrZero(y.mechanics?.entryLimits?.minimum) if (minDeposit.lte(0)) return true - // minDeposit is human readable (e.g. 32), so convert base balance to human - const precision = assets[assetId]?.precision ?? 18 + const asset = assets[assetId] + if (!asset) return false // Skip if asset metadata unknown + + const precision = asset.precision const baseBalance = bnOrZero(assetBalancesBaseUnit[assetId]) const balanceHuman = bnOrZero(fromBaseUnit(baseBalance, precision)) return balanceHuman.gte(minDeposit) })src/pages/Yields/components/YieldItem.tsx (1)
358-361: Invalid propitemProponGradientApycomponent.
GradientApyextends Chakra'sTextcomponent. The propitemProp='medium'appears to be a typo—it should likely befontWeight='medium'based on usage elsewhere in the file.🐛 Proposed fix
- <GradientApy itemProp='medium' justifyContent='flex-end'> + <GradientApy fontWeight='medium' justifyContent='flex-end'>
🧹 Nitpick comments (1)
src/pages/Yields/components/YieldsList.tsx (1)
63-63: Consider using a breakpoint token instead of hardcoded pixel value.The hardcoded
768pxbreakpoint may drift from Chakra's theme breakpoints. Consider using the theme'smdbreakpoint for consistency with the design system.Suggested improvement
- const [isMobile] = useMediaQuery('(max-width: 768px)') + const [isMobile] = useMediaQuery('(max-width: 48em)', { ssr: false })Or better, use Chakra's
useBreakpointValuefor theme-aware responsive logic:const isMobile = useBreakpointValue({ base: true, md: false }) ?? false
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
src/pages/Yields/components/YieldItem.tsxsrc/pages/Yields/components/YieldsList.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,js,jsx}: Never assume a library is available - always check imports/package.json first
Prefer composition over inheritance
Write self-documenting code with clear variable and function names
Keep functions small and focused on a single responsibility
Avoid deep nesting - use early returns instead
Prefer procedural and easy to understand code
Never expose, log, or commit secrets, API keys, or credentials
Validate all inputs, especially user inputs
Handle errors gracefully with meaningful messages
Don't silently catch and ignore exceptions
Log errors appropriately for debugging
Provide fallback behavior when possible
Use appropriate data structures for the task
Never add code comments unless explicitly requested
When modifying code, do not add comments that reference previous implementations or explain what changed. Comments should only describe the current logic and functionality.
Use meaningful names for branches, variables, and functions
Always runyarn lint --fixandyarn type-checkafter making changes
Avoidletvariable assignments - preferconstwith inline IIFE switch statements or extract to functions for conditional logic
Files:
src/pages/Yields/components/YieldItem.tsxsrc/pages/Yields/components/YieldsList.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Avoid useEffect where practical - use it only when necessary and following best practices
Avoid 'any' types - use specific type annotations instead
For default values with user overrides, use computed values (useMemo) instead of useEffect - pattern:userSelected ?? smartDefault ?? fallback
When function parameters are unused due to interface requirements, refactor the interface or implementation to remove them rather than prefixing with underscore
Sanitize data before displaying to prevent XSS
Memoize aggressively - wrap component variables inuseMemoand callbacks inuseCallbackwhere possible
For static JSX icon elements (e.g.,<TbCopy />) that don't depend on state/props, define them as constants outside the component to avoid re-renders instead of using useMemo
Account for light/dark mode usinguseColorModeValuehook
Account for responsive mobile designs in all UI components
When applying styles, use the existing standards and conventions of the codebase
Use Chakra UI components and conventions
All copy/text must use translation keys - never hardcode strings
Use the translation hook:useTranslate()fromreact-polyglot
UseuseFeatureFlag('FlagName')hook to access feature flag values in components
Prefertypeoverinterfacefor type definitions
Use strict typing - avoidany
UseNominaltypes for domain identifiers (e.g.,WalletId,AccountId)
Import types from@shapeshiftoss/caipfor chain/account/asset IDs
UseuseAppSelectorfor Redux state
UseuseAppDispatchfor Redux actions
Memoize expensive computations withuseMemo
Memoize callbacks withuseCallback
**/*.{ts,tsx}: UseResult<T, E>pattern for error handling in swappers and APIs; ALWAYS useOk()andErr()from@sniptt/monads; AVOID throwing within swapper API implementations
ALWAYS use custom error classes from@shapeshiftoss/errorswith meaningful error codes for internationalization and relevant details in error objects
ALWAYS wrap async op...
Files:
src/pages/Yields/components/YieldItem.tsxsrc/pages/Yields/components/YieldsList.tsx
**/*.{tsx,jsx}
📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)
**/*.{tsx,jsx}: ALWAYS wrap React components in error boundaries and provide user-friendly fallback components with error logging
ALWAYS useuseErrorToasthook for displaying errors with translated error messages and handle different error types appropriatelyUse PascalCase for React component names and match the component name to the file name
Files:
src/pages/Yields/components/YieldItem.tsxsrc/pages/Yields/components/YieldsList.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/naming-conventions.mdc)
**/*.{js,jsx,ts,tsx}: Use camelCase for variables, functions, and methods with descriptive names that explain the purpose
Use verb prefixes for functions that perform actions (e.g., fetch, validate, execute, update, calculate)
Use UPPER_SNAKE_CASE for constants and configuration values with descriptive names
Usehandleprefix for event handlers with descriptive names in camelCase
Use descriptive boolean variable names withis,has,can,shouldprefixes
Use named exports for components, functions, and utilities instead of default exports
Use descriptive import names and avoid renaming imports unless necessary
Avoid non-descriptive variable names likedata,item,obj, and single-letter variable names except in loops
Avoid abbreviations in names unless they are widely understood
Avoid generic function names likefn,func, orcallback
Files:
src/pages/Yields/components/YieldItem.tsxsrc/pages/Yields/components/YieldsList.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)
**/*.{jsx,tsx}: ALWAYS useuseMemofor expensive computations, object/array creations, and filtered data
ALWAYS useuseMemofor derived values and computed properties
ALWAYS useuseMemofor conditional values and simple transformations
ALWAYS useuseCallbackfor event handlers and functions passed as props
ALWAYS useuseCallbackfor any function that could be passed as a prop or dependency
ALWAYS include all dependencies inuseEffect,useMemo,useCallbackdependency arrays
NEVER use// eslint-disable-next-line react-hooks/exhaustive-depsunless absolutely necessary, and ALWAYS explain why dependencies are excluded if using eslint disable
ALWAYS use named exports for components; NEVER use default exports for components
KEEP component files under 200 lines when possible; BREAK DOWN large components into smaller, reusable pieces
EXTRACT complex logic into custom hooks
ALWAYS wrap components in error boundaries for production
ALWAYS handle async errors properly in async operations
ALWAYS provide user-friendly error messages in error handling
ALWAYS use virtualization for lists with 100+ items
ALWAYS implement proper key props for list items
ALWAYS lazy load heavy components using React.lazy for code splitting
ALWAYS use Suspense wrapper for lazy loaded components
USE local state for component-level state; LIFT state up when needed across multiple components; USE Context for avoiding prop drilling; USE Redux only for global state shared across multiple places
Wrap components receiving props withmemofor performance optimization
Files:
src/pages/Yields/components/YieldItem.tsxsrc/pages/Yields/components/YieldsList.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)
Ensure TypeScript types are explicit and proper; avoid use of
anytype
Files:
src/pages/Yields/components/YieldItem.tsxsrc/pages/Yields/components/YieldsList.tsx
🧠 Learnings (31)
📓 Common learnings
Learnt from: NeOMakinG
Repo: shapeshift/web PR: 10231
File: src/components/AssetSearch/components/AssetList.tsx:2-2
Timestamp: 2025-08-08T15:00:49.887Z
Learning: Project shapeshift/web: NeOMakinG prefers avoiding minor a11y/UI nitpicks (e.g., adding aria-hidden to decorative icons in empty states like src/components/AssetSearch/components/AssetList.tsx) within feature PRs; defer such suggestions to a follow-up instead of blocking the PR.
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10569
File: src/plugins/walletConnectToDapps/components/WalletConnectSigningModal/WalletConnectModalSigningFooter.tsx:121-129
Timestamp: 2025-09-17T22:40:30.149Z
Learning: gomesalexandre maintains strict scope discipline even for style/UI PRs in shapeshift/web, declining functionally correct UX improvements (like keeping Cancel button enabled during gas simulation loading) when they fall outside the PR's stated styling objectives, demonstrating his consistent pattern of deferring valid but tangential improvements to separate efforts.
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10418
File: src/plugins/walletConnectToDapps/components/header/WalletConnectToDappsHeaderButton.tsx:0-0
Timestamp: 2025-09-08T22:00:48.005Z
Learning: gomesalexandre dismissed an aria-label accessibility suggestion with "meh" in PR `#10418` for WalletConnectToDappsHeaderButton.tsx, consistent with the team's pattern of deferring minor a11y improvements to follow-up PRs rather than expanding feature PR scope.
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: To add a new feature flag: (1) Add to `FeatureFlags` type in `src/state/slices/preferencesSlice/preferencesSlice.ts`, (2) Add environment variable validation in `src/config.ts`, (3) Add to initial state in `preferencesSlice.ts`, (4) Add to test mock in `src/test/mocks/store.ts`, (5) Set appropriate values in `.env`, `.env.development`, and `.env.production`
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10206
File: src/config.ts:127-128
Timestamp: 2025-08-07T11:20:44.614Z
Learning: gomesalexandre prefers required environment variables without default values in the config file (src/config.ts). They want explicit configuration and fail-fast behavior when environment variables are missing, rather than having fallback defaults.
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10461
File: src/plugins/walletConnectToDapps/components/modals/ContractInteractionBreakdown.tsx:0-0
Timestamp: 2025-09-13T16:45:18.813Z
Learning: gomesalexandre prefers aggressively deleting unused/obsolete code files ("ramboing") rather than fixing technical issues in code that won't be used, demonstrating his preference for keeping codebases clean and PR scope focused.
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10458
File: src/plugins/walletConnectToDapps/types.ts:7-7
Timestamp: 2025-09-10T15:34:29.604Z
Learning: gomesalexandre is comfortable relying on transitive dependencies (like abitype through ethers/viem) rather than explicitly declaring them in package.json, preferring to avoid package.json bloat when the transitive dependency approach works reliably in practice.
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10503
File: .env:56-56
Timestamp: 2025-09-16T13:17:02.938Z
Learning: gomesalexandre prefers to enable feature flags globally in the base .env file when the intent is to activate features everywhere, even when there are known issues like crashes, demonstrating his preference for intentional global feature rollouts over cautious per-environment enablement.
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10249
File: src/pages/ThorChainLP/components/ReusableLpStatus/TransactionRow.tsx:447-503
Timestamp: 2025-08-13T17:07:10.763Z
Learning: gomesalexandre prefers relying on TypeScript's type system for validation rather than adding defensive runtime null checks when types are properly defined. They favor a TypeScript-first approach over defensive programming with runtime validations.
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10276
File: src/hooks/useActionCenterSubscribers/useThorchainLpDepositActionSubscriber.tsx:61-66
Timestamp: 2025-08-14T17:51:47.556Z
Learning: gomesalexandre is not concerned about structured logging and prefers to keep console.error usage as-is rather than implementing structured logging patterns, even when project guidelines suggest otherwise.
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10413
File: src/components/Modals/FiatRamps/fiatRampProviders/onramper/utils.ts:29-55
Timestamp: 2025-09-02T14:26:19.028Z
Learning: gomesalexandre prefers to keep preparatory/reference code simple until it's actively consumed, rather than implementing comprehensive error handling, validation, and robustness improvements upfront. They prefer to add these improvements when the code is actually being used in production.
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10783
File: src/context/ModalStackProvider/useModalRegistration.ts:30-41
Timestamp: 2025-10-16T11:14:40.657Z
Learning: gomesalexandre prefers to add lint rules (like typescript-eslint/strict-boolean-expressions for truthiness checks on numbers) to catch common issues project-wide rather than relying on code review to catch them.
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10276
File: src/pages/ThorChainLP/components/ReusableLpStatus/TransactionRow.tsx:396-402
Timestamp: 2025-08-14T17:55:57.490Z
Learning: gomesalexandre is comfortable with functions/variables that return undefined or true (tri-state) when only the truthy case matters, preferring to rely on JavaScript's truthy/falsy behavior rather than explicitly returning boolean values.
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10206
File: src/lib/moralis.ts:47-85
Timestamp: 2025-08-07T11:22:16.983Z
Learning: gomesalexandre prefers console.error over structured logging for Moralis API integration debugging, as they find it more conventional and prefer to examine XHR requests directly rather than rely on structured logs for troubleshooting.
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: Applies to **/*.{ts,tsx} : Account for responsive mobile designs in all UI components
Applied to files:
src/pages/Yields/components/YieldItem.tsxsrc/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-08-08T15:00:49.887Z
Learnt from: NeOMakinG
Repo: shapeshift/web PR: 10231
File: src/components/AssetSearch/components/AssetList.tsx:2-2
Timestamp: 2025-08-08T15:00:49.887Z
Learning: Project shapeshift/web: NeOMakinG prefers avoiding minor a11y/UI nitpicks (e.g., adding aria-hidden to decorative icons in empty states like src/components/AssetSearch/components/AssetList.tsx) within feature PRs; defer such suggestions to a follow-up instead of blocking the PR.
Applied to files:
src/pages/Yields/components/YieldItem.tsxsrc/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-08-08T14:59:40.422Z
Learnt from: NeOMakinG
Repo: shapeshift/web PR: 10231
File: src/pages/Explore/ExploreCategory.tsx:231-238
Timestamp: 2025-08-08T14:59:40.422Z
Learning: In src/pages/Explore/ExploreCategory.tsx, for the PageHeader filter trigger, NeOMakinG considers changing a clickable Chakra Icon to IconButton too nitpicky for this PR and prefers to keep the current Icon-based trigger; such minor a11y/UI nitpicks should be deferred to a follow-up if needed.
Applied to files:
src/pages/Yields/components/YieldItem.tsx
📚 Learning: 2025-09-08T15:53:09.362Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10442
File: src/components/TradeAssetSearch/components/GroupedAssetList/GroupedAssetList.tsx:34-35
Timestamp: 2025-09-08T15:53:09.362Z
Learning: In DefaultAssetList.tsx, the GroupedAssetList component already receives the activeChainId prop correctly on line ~58, contrary to automated analysis that may flag it as missing.
Applied to files:
src/pages/Yields/components/YieldItem.tsxsrc/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-09-16T09:32:21.333Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10490
File: src/components/Layout/Header/NavBar/NavigationDropdown.tsx:96-103
Timestamp: 2025-09-16T09:32:21.333Z
Learning: In the shapeshift/web codebase, gomesalexandre confirms that using `item.icon && <Icon as={item.icon} boxSize={4} />` for Chakra UI MenuItem.icon prop is valid and acceptable - the boolean short-circuit pattern works fine with Chakra components and doesn't need to be changed to explicit undefined.
Applied to files:
src/pages/Yields/components/YieldItem.tsx
📚 Learning: 2025-11-24T21:20:44.637Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: .cursor/rules/react-best-practices.mdc:0-0
Timestamp: 2025-11-24T21:20:44.637Z
Learning: Applies to **/*.{jsx,tsx} : ALWAYS implement proper key props for list items
Applied to files:
src/pages/Yields/components/YieldItem.tsx
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: Applies to **/*.{ts,tsx} : Use Chakra UI components and conventions
Applied to files:
src/pages/Yields/components/YieldItem.tsxsrc/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-10-07T03:44:27.350Z
Learnt from: 0xApotheosis
Repo: shapeshift/web PR: 10760
File: src/components/ManageHiddenAssets/ManageHiddenAssetsList.tsx:78-84
Timestamp: 2025-10-07T03:44:27.350Z
Learning: In the ShapeShift web codebase, the following are stable references and do not need to be included in useCallback/useMemo dependency arrays:
- `navigate` from `useBrowserRouter()` hook
- Modal control objects (like `walletDrawer`) from `useModal()` hook (including their `isOpen`, `close`, and `open` methods)
- These are backed by stable context providers
Applied to files:
src/pages/Yields/components/YieldItem.tsxsrc/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: Applies to **/*.{ts,tsx} : For static JSX icon elements (e.g., `<TbCopy />`) that don't depend on state/props, define them as constants outside the component to avoid re-renders instead of using useMemo
Applied to files:
src/pages/Yields/components/YieldItem.tsxsrc/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-08-14T17:54:32.563Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10276
File: src/pages/ThorChainLP/components/ReusableLpStatus/ReusableLpStatus.tsx:97-108
Timestamp: 2025-08-14T17:54:32.563Z
Learning: In ReusableLpStatus component (src/pages/ThorChainLP/components/ReusableLpStatus/ReusableLpStatus.tsx), the txAssets dependency is stable from first render because poolAsset, baseAsset, actionSide, and action are all defined first render, making the current txAssetsStatuses initialization pattern safe without needing useEffect synchronization.
Applied to files:
src/pages/Yields/components/YieldItem.tsxsrc/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: Applies to **/*.{ts,tsx} : Import types from `shapeshiftoss/caip` for chain/account/asset IDs
Applied to files:
src/pages/Yields/components/YieldItem.tsxsrc/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-12-27T16:02:52.792Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 11536
File: src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeExecution.tsx:252-265
Timestamp: 2025-12-27T16:02:52.792Z
Learning: When reviewing bug fixes, especially in shapeshift/web, prefer minimal changes that fix correctness over introducing broader refactors or quality-of-life improvements (e.g., extracting duplicated logic) unless such improvements are essential to the fix. Apply this guideline broadly to TSX files and related components, not just the specific location, to keep changes focused and maintainable.
Applied to files:
src/pages/Yields/components/YieldItem.tsxsrc/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-07-30T20:57:48.176Z
Learnt from: NeOMakinG
Repo: shapeshift/web PR: 10148
File: src/components/MarketTableVirtualized/MarketsTableVirtualized.tsx:88-91
Timestamp: 2025-07-30T20:57:48.176Z
Learning: In src/components/MarketTableVirtualized/MarketsTableVirtualized.tsx, NeOMakinG prefers keeping the hardcoded overscan value (40) over dynamic calculation based on viewport height, prioritizing code simplicity over precision when the current approach works effectively.
Applied to files:
src/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-08-06T09:47:29.865Z
Learnt from: NeOMakinG
Repo: shapeshift/web PR: 10191
File: src/pages/Explore/Explore.tsx:174-178
Timestamp: 2025-08-06T09:47:29.865Z
Learning: In src/pages/Explore/Explore.tsx, NeOMakinG prefers keeping complex height calculations inline rather than extracting them to useMemo, prioritizing perceived performance/execution speed over code readability, even when the calculation would only run when the dependency (isSearching) changes.
Applied to files:
src/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-08-17T21:53:03.806Z
Learnt from: 0xApotheosis
Repo: shapeshift/web PR: 10290
File: scripts/generateAssetData/color-map.json:41-47
Timestamp: 2025-08-17T21:53:03.806Z
Learning: In the ShapeShift web codebase, native assets (using CAIP-19 slip44 namespace like eip155:1/slip44:60, bip122:.../slip44:..., cosmos:.../slip44:...) are manually hardcoded and not generated via the automated asset generation script. Only ERC20/BEP20 tokens go through the asset generation process. The validation scripts should only validate generated assets, not manually added native assets.
Applied to files:
src/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-10-21T17:11:18.087Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10871
File: src/components/Modals/Send/hooks/useSendDetails/useSendDetails.tsx:426-428
Timestamp: 2025-10-21T17:11:18.087Z
Learning: In src/components/Modals/Send/hooks/useSendDetails/useSendDetails.tsx, within the handleInputChange function, use .toFixed() without arguments (not .toString()) when converting BigNumber amounts for input field synchronization. This avoids exponential notation in the input while preserving precision for presentational components like <Amount.Crypto /> and <Amount.Fiat /> to format appropriately.
Applied to files:
src/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-08-10T21:09:25.643Z
Learnt from: premiumjibles
Repo: shapeshift/web PR: 10215
File: src/components/MultiHopTrade/hooks/useGetTradeRateInput.ts:65-67
Timestamp: 2025-08-10T21:09:25.643Z
Learning: In the MultiHopTrade components, `selectInputBuyAsset` and `selectInputSellAsset` selectors from `tradeInputSlice` always return defined values because they have default values in the initial state (BTC for buyAsset, ETH for sellAsset, with fallback to defaultAsset). Null checks for these assets are unnecessary when using these selectors.
Applied to files:
src/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-11-03T22:31:30.786Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10985
File: packages/swapper/src/swappers/PortalsSwapper/getPortalsTradeQuote/getPortalsTradeQuote.ts:0-0
Timestamp: 2025-11-03T22:31:30.786Z
Learning: In packages/swapper/src/swappers/PortalsSwapper, the rate and quote files intentionally use different approaches for calculating buyAmountBeforeSlippageCryptoBaseUnit: getPortalsTradeRate.tsx uses minOutputAmount / (1 - buffer) for conservative estimates, while getPortalsTradeQuote.ts uses outputAmount / (1 - buffer) for final quote display. This difference is validated by on-chain simulation testing and is intentional.
Applied to files:
src/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-07-24T11:07:20.536Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10073
File: src/features/defi/providers/fox-farming/components/FoxFarmingManager/Claim/Claim.tsx:77-84
Timestamp: 2025-07-24T11:07:20.536Z
Learning: In fox farming components, the `opportunity?.rewardsCryptoBaseUnit?.amounts` property has a well-defined type signature that is always an array (never undefined), but can be empty: `readonly [] | readonly [string, string, string] | readonly [string, string] | readonly [string]`. Using optional chaining on the `amounts` property itself is unnecessary since it's always defined, though accessing `amounts[0]` on an empty array returns undefined which bnOrZero() handles safely.
Applied to files:
src/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-12-26T15:45:47.558Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 11515
File: scripts/generateAssetData/generateRelatedAssetIndex/generateRelatedAssetIndex.ts:100-122
Timestamp: 2025-12-26T15:45:47.558Z
Learning: In scripts/generateAssetData/generateRelatedAssetIndex/generateRelatedAssetIndex.ts, gomesalexandre is comfortable with the fetchBridgedCategoryMappings function lacking try-catch error handling for CoinGecko API calls. He prefers letting the script crash on API failures rather than adding defensive error handling, consistent with his fail-fast approach for asset generation scripts.
Applied to files:
src/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-10-15T15:57:39.956Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10810
File: src/plugins/walletConnectToDapps/utils/tenderly/index.ts:212-0
Timestamp: 2025-10-15T15:57:39.956Z
Learning: gomesalexandre uses discriminated union patterns (e.g., `isEIP1559 ? { max_fee_per_gas, max_priority_fee_per_gas } : { gas_price }`) in WalletConnect flows without additional validation guards, trusting that the runtime data structure ensures mutual exclusivity between EIP-1559 and legacy gas pricing fields.
Applied to files:
src/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-08-15T07:51:16.374Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10278
File: src/components/AssetHeader/hooks/useQuickBuy.ts:97-99
Timestamp: 2025-08-15T07:51:16.374Z
Learning: The selectPortfolioUserCurrencyBalanceByAssetId selector in src/state/slices/portfolioSlice/selectors.ts expects a filter object with an assetId property, not a raw AssetId string. The selector signature is (state: ReduxState, filter) where filter should have an assetId property. This pattern is consistent across portfolio selectors that use selectAssetIdParamFromFilter. Passing a filter object like { assetId: someAssetId } is the correct usage pattern.
Applied to files:
src/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-08-22T12:59:01.702Z
Learnt from: NeOMakinG
Repo: shapeshift/web PR: 10323
File: src/components/Layout/Header/ActionCenter/components/RewardDistributionActionCard.tsx:37-53
Timestamp: 2025-08-22T12:59:01.702Z
Learning: In RewardDistributionActionCard component (src/components/Layout/Header/ActionCenter/components/RewardDistributionActionCard.tsx), NeOMakinG confirmed that runeAsset is expected to always be defined when the component renders, so defensive guards against undefined runeAsset are not needed.
Applied to files:
src/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: Applies to **/*.{ts,tsx} : Account for light/dark mode using `useColorModeValue` hook
Applied to files:
src/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-10-17T07:51:58.374Z
Learnt from: NeOMakinG
Repo: shapeshift/web PR: 10783
File: src/context/WalletProvider/NewWalletViews/NewWalletViewsSwitch.tsx:344-349
Timestamp: 2025-10-17T07:51:58.374Z
Learning: In the shapeshift/web codebase, Chakra UI's ModalContent component supports the containerProps prop. When using the useModalRegistration hook from ModalStackProvider, spreading {...modalContentProps} directly onto ModalContent is correct and properly applies z-index and pointer-events through containerProps.sx. Do not suggest extracting sx from modalContentProps.containerProps.sx.
Applied to files:
src/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-09-12T12:04:59.556Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10461
File: src/plugins/walletConnectToDapps/components/WalletConnectSigningModal/content/SendTransactionContent.tsx:0-0
Timestamp: 2025-09-12T12:04:59.556Z
Learning: The ShapeShift codebase's fromBaseUnit function correctly handles hex strings (like WalletConnect transaction.value) without manual conversion because bnOrZero -> bn -> new BigNumber() automatically detects and parses hex strings starting with "0x". gomesalexandre confirmed this with concrete evidence showing hex value 0x176d1c49189db correctly converts to 0.000412118294825435 ETH.
Applied to files:
src/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-08-11T09:46:41.060Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10219
File: src/components/MultiHopTrade/components/TradeInput/TradeInput.tsx:167-172
Timestamp: 2025-08-11T09:46:41.060Z
Learning: In the shapeshift/web repository, the display cache logic for trade quotes (using `selectUserAvailableTradeQuotes` and `selectUserUnavailableTradeQuotes`) is intentionally kept the same between `TradeInput.tsx` and `TradeQuotes.tsx` components. The `hasQuotes` computation in `TradeInput.tsx` uses these display cache selectors by design, matching the pattern used in `TradeQuotes.tsx`.
Applied to files:
src/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-09-04T17:29:59.479Z
Learnt from: NeOMakinG
Repo: shapeshift/web PR: 10380
File: src/components/TradeAssetSearch/hooks/useGetPopularAssetsQuery.tsx:28-33
Timestamp: 2025-09-04T17:29:59.479Z
Learning: In shapeshift/web, the useGetPopularAssetsQuery function in src/components/TradeAssetSearch/hooks/useGetPopularAssetsQuery.tsx intentionally uses primaryAssets[assetId] instead of falling back to assets[assetId]. The design distributes primary assets across chains by iterating through their related assets and adding the primary asset to each related asset's chain. This ensures primary assets appear in all chains where they have related assets, supporting the grouped asset system.
Applied to files:
src/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-11-24T21:20:44.637Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: .cursor/rules/react-best-practices.mdc:0-0
Timestamp: 2025-11-24T21:20:44.637Z
Learning: Applies to **/*.{jsx,tsx} : ALWAYS use virtualization for lists with 100+ items
Applied to files:
src/pages/Yields/components/YieldsList.tsx
📚 Learning: 2025-11-05T23:37:30.632Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10912
File: src/context/WalletProvider/NewWalletViews/NewWalletViewsSwitch.tsx:290-290
Timestamp: 2025-11-05T23:37:30.632Z
Learning: In the ShapeShift web codebase, `isMobile` imported from '@/lib/globals' is a module-level constant (defined as `export const isMobile = Boolean(window?.isShapeShiftMobile)`) that is evaluated once at module load time. It is a stable reference that does not need to be included in useCallback/useMemo/useEffect dependency arrays.
Applied to files:
src/pages/Yields/components/YieldsList.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Call / Static
🔇 Additional comments (12)
src/pages/Yields/components/YieldsList.tsx (5)
56-56: LGTM!Clean extraction of
tabSelectedSxas a constant outside the component, avoiding unnecessary re-renders as per coding guidelines.
175-182: LGTM!Clean derivation of unique yield types with proper capitalization and formatting. The Set-based deduplication is efficient.
607-691: LGTM!The grid element rendering logic correctly handles both single yields and groups, with proper mobile variant switching and all necessary dependencies in the useMemo array.
820-854: LGTM!The recommended strip element is well-structured with proper conditional rendering based on connection state and data availability. Mobile variant is correctly applied.
856-876: LGTM!The content element correctly handles the empty state check for both
isMyOpportunitiesand regular view, with proper mobile-aware view mode fallback.src/pages/Yields/components/YieldItem.tsx (7)
47-55: LGTM!Clean type definitions with proper optional props. The variant union type correctly includes the new 'mobile' option.
75-107: LGTM!The stats computation correctly handles both single and group yields with proper memoization. The dependency array includes all necessary values.
124-135: LGTM!The click handler correctly differentiates between single and group yields, with proper URL encoding for asset symbols and search string preservation.
299-366: LGTM on mobile variant structure!The mobile variant layout is well-structured with a compact 3-column grid for balance/TVL/APY. The conditional APY label (maxApy vs apy) correctly reflects single vs group context.
368-429: LGTM!The row variant correctly implements responsive column visibility with proper display breakpoints and handles both single and group provider rendering appropriately.
431-504: LGTM!The card variant maintains visual consistency with proper hover effects, gradient APY styling, and flexible stat positioning. The cursor correctly reflects whether entering is allowed.
506-524: LGTM!The mobile skeleton variant provides appropriate loading state UI with the correct structure matching the mobile variant layout.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| const eligibleYields = yieldsForAsset.filter(y => { | ||
| const minDeposit = bnOrZero(y.mechanics?.entryLimits?.minimum) | ||
| if (minDeposit.gt(0)) { | ||
| const precision = assets[assetId]?.precision ?? 18 | ||
| const baseBalance = bnOrZero(assetBalancesBaseUnit[assetId]) | ||
| const balanceHuman = bnOrZero(fromBaseUnit(baseBalance, precision)) | ||
| if (balanceHuman.lt(minDeposit)) return false | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same precision fallback issue in availableYields.
This mirrors the issue in recommendedYields. When precision is unknown, the eligibility check will be incorrect.
🐛 Proposed fix
const eligibleYields = yieldsForAsset.filter(y => {
const minDeposit = bnOrZero(y.mechanics?.entryLimits?.minimum)
if (minDeposit.gt(0)) {
- const precision = assets[assetId]?.precision ?? 18
+ const asset = assets[assetId]
+ if (!asset) return false
+ const precision = asset.precision
const baseBalance = bnOrZero(assetBalancesBaseUnit[assetId])
const balanceHuman = bnOrZero(fromBaseUnit(baseBalance, precision))
if (balanceHuman.lt(minDeposit)) return false
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const eligibleYields = yieldsForAsset.filter(y => { | |
| const minDeposit = bnOrZero(y.mechanics?.entryLimits?.minimum) | |
| if (minDeposit.gt(0)) { | |
| const precision = assets[assetId]?.precision ?? 18 | |
| const baseBalance = bnOrZero(assetBalancesBaseUnit[assetId]) | |
| const balanceHuman = bnOrZero(fromBaseUnit(baseBalance, precision)) | |
| if (balanceHuman.lt(minDeposit)) return false | |
| } | |
| const eligibleYields = yieldsForAsset.filter(y => { | |
| const minDeposit = bnOrZero(y.mechanics?.entryLimits?.minimum) | |
| if (minDeposit.gt(0)) { | |
| const asset = assets[assetId] | |
| if (!asset) return false | |
| const precision = asset.precision | |
| const baseBalance = bnOrZero(assetBalancesBaseUnit[assetId]) | |
| const balanceHuman = bnOrZero(fromBaseUnit(baseBalance, precision)) | |
| if (balanceHuman.lt(minDeposit)) return false | |
| } |
🤖 Prompt for AI Agents
In `@src/pages/Yields/components/YieldsList.tsx` around lines 331 - 338, The
eligibility check in yieldsForAsset.filter (used to compute eligibleYields) uses
a hardcoded precision fallback (assets[assetId]?.precision ?? 18) which can
produce incorrect comparisons; mirror the fix used in recommendedYields by
replacing that hardcoded fallback with the same precision resolution logic (use
the shared DEFAULT/utility precision or the same assets lookup used in
recommendedYields), then compute baseBalance/fromBaseUnit with that resolved
precision before comparing to bnOrZero(y.mechanics?.entryLimits?.minimum) so the
minimum deposit check uses the correct precision.
| {!isMobile && ( | ||
| <> | ||
| <YieldFilters | ||
| networks={networks} | ||
| selectedNetwork={selectedNetwork} | ||
| onSelectNetwork={handleNetworkChange} | ||
| providers={providers} | ||
| selectedProvider={selectedProvider} | ||
| onSelectProvider={handleProviderChange} | ||
| types={types} | ||
| selectedType={selectedType} | ||
| onSelectType={handleTypeChange} | ||
| sortOption={sortOption} | ||
| onSortChange={handleSortChange} | ||
| mb={0} | ||
| /> | ||
| <ViewToggle viewMode={viewMode} setViewMode={setViewMode} /> | ||
| </> | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the full context of the YieldsList component
cat -n src/pages/Yields/components/YieldsList.tsx | head -100Repository: shapeshift/web
Length of output: 4544
🏁 Script executed:
# Check file size and then examine the section in question
wc -l src/pages/Yields/components/YieldsList.tsxRepository: shapeshift/web
Length of output: 105
🏁 Script executed:
# Look at the specific lines and surrounding context (1000-1060)
sed -n '1000,1060p' src/pages/Yields/components/YieldsList.tsxRepository: shapeshift/web
Length of output: 2014
🏁 Script executed:
# Search for mobile filter implementations in the Yields directory
fd . src/pages/Yields/components -type f -name "*.tsx" | xargs grep -l "isMobile\|mobile.*filter" -iRepository: shapeshift/web
Length of output: 230
🏁 Script executed:
# Check if there's a filter modal or drawer implementation
rg "filter.*modal\|modal.*filter\|filter.*drawer\|drawer.*filter" -i src/pages/YieldsRepository: shapeshift/web
Length of output: 40
🏁 Script executed:
# Let me search correctly for files in Yields components
find src/pages/Yields/components -name "*.tsx" -o -name "*.ts"Repository: shapeshift/web
Length of output: 997
🏁 Script executed:
# Check the entire section around the isMobile filters to see if there's a mobile alternative
sed -n '1000,1056p' src/pages/Yields/components/YieldsList.tsxRepository: shapeshift/web
Length of output: 2014
🏁 Script executed:
# Search for any mobile filter modal or drawer implementation
rg "Modal\|Drawer\|Popover" src/pages/Yields/components -A 2 -B 2Repository: shapeshift/web
Length of output: 40
🏁 Script executed:
# Check if YieldFilters component has mobile-specific logic
cat -n src/pages/Yields/components/YieldFilters.tsx 2>/dev/null | head -50Repository: shapeshift/web
Length of output: 1586
🏁 Script executed:
# Let's search for any mobile filter implementations in the entire Yields directory
rg "isMobile.*filter\|mobile.*filter" src/pages/Yields -iRepository: shapeshift/web
Length of output: 40
🏁 Script executed:
# Check git history for the isMobile filters section
git log -p --follow -S "!isMobile && (" -- src/pages/Yields/components/YieldsList.tsx | head -200Repository: shapeshift/web
Length of output: 40
🏁 Script executed:
# Look at useYieldFilters hook to understand how filters work
cat -n src/pages/Yields/hooks/useYieldFilters.tsx 2>/dev/null | head -100Repository: shapeshift/web
Length of output: 40
🏁 Script executed:
# Check if there's URL params logic that maintains filters on mobile
rg "searchParams\|setSearchParams" src/pages/Yields/components/YieldsList.tsx -B 2 -A 2Repository: shapeshift/web
Length of output: 40
🏁 Script executed:
# Compare with Explore component mentioned in learnings
cat -n src/pages/Explore/ExploreCategory.tsx | grep -A 10 -B 10 "isMobile\|mobile" | head -50Repository: shapeshift/web
Length of output: 1069
🏁 Script executed:
# Let me check the full YieldsList component more thoroughly for any mobile filter logic
sed -n '1,1056p' src/pages/Yields/components/YieldsList.tsx | tail -200Repository: shapeshift/web
Length of output: 6354
🏁 Script executed:
# Check if there's a mobile filters implementation elsewhere or commented out
rg "mobile\|isMobile" src/pages/Yields/components/YieldsList.tsx -B 3 -A 3Repository: shapeshift/web
Length of output: 40
🏁 Script executed:
# Look for any drawer or modal patterns in the entire Yields directory
rg "Drawer\|Modal\|Popover" src/pages/Yields --files-with-matchesRepository: shapeshift/web
Length of output: 40
🏁 Script executed:
# Check how filters persist in the URL and if they work on mobile
sed -n '60,100p' src/pages/Yields/components/YieldsList.tsxRepository: shapeshift/web
Length of output: 1576
🏁 Script executed:
# Check if there's filter functionality in related components
cat -n src/pages/Yields/components/YieldViewHelpers.tsx | head -100Repository: shapeshift/web
Length of output: 3285
🏁 Script executed:
# Let me check if there's any Jira ticket or issue reference in the code
rg "TODO|FIXME|XXX.*mobile.*filter|XXX.*filter.*mobile" src/pages/YieldsRepository: shapeshift/web
Length of output: 324
🏁 Script executed:
# Check the useYieldFilters hook implementation to confirm it uses setSearchParams
find src -name "useYieldFilters*" -type fRepository: shapeshift/web
Length of output: 99
🏁 Script executed:
# Look at the complete filter change handlers to confirm they work via URL params
fd useYieldFilters -type f -name "*.tsx" -o -name "*.ts" | head -5Repository: shapeshift/web
Length of output: 228
🏁 Script executed:
# Search for any alternate filter implementation or drawer pattern in Yields
rg "Modal|Drawer" src/pages/Yields/components/YieldFilters.tsx -A 5Repository: shapeshift/web
Length of output: 40
🏁 Script executed:
# Check the useYieldFilters hook implementation
cat -n src/pages/Yields/hooks/useYieldFilters.tsRepository: shapeshift/web
Length of output: 3572
🏁 Script executed:
# Verify that filter handlers use setSearchParams
rg "setSearchParams\|useSearchParams" src/pages/Yields/hooks/useYieldFilters.ts -B 2 -A 2Repository: shapeshift/web
Length of output: 40
🏁 Script executed:
# Check if YieldFilters component itself has any mobile awareness
grep -n "isMobile\|mobile" src/pages/Yields/components/YieldFilters.tsxRepository: shapeshift/web
Length of output: 40
Add mobile-friendly filters or justify the design decision.
Mobile users cannot access any filters (network, provider, type) or change view modes—these are completely hidden. The coding guidelines require accounting for responsive mobile designs in all UI components. Either implement a condensed filter UI, filter modal/drawer, or filter menu for mobile, or document why mobile filtering is intentionally unsupported. Currently, filters work via URL parameters only on mobile, not through the UI.
Description
This PR consolidates the following PRs into a single review-friendly PR targeting
develop:All above PRs will be closed in favor of this consolidated PR.
What's included:
Fixes:
/yields/asset/GHO)passthroughandmanageActionTypefrom pendingActionsFeatures:
UX/UI Improvements:
Cleanup:
Issue (if applicable)
Risk
Low - all changes are scoped to the yield feature which is behind a feature flag. No changes to transaction logic beyond fixing the claim flow passthrough.
Yield.xyz integrations, yield enter/exit/claim actions on all supported yield protocols.
Testing
Engineering
/yieldsand apply network/provider/type filters - verify persistenceOperations
Screenshots (if applicable)
Yield detail page refactor:
https://jam.dev/c/2ff6b47e-d2dc-4147-a0d9-52c10a703069
Mobile improvements:




Type filter & recommendations:
https://jam.dev/c/8bae16b6-87fe-41a8-a480-f918a0b70679
Earn tab:
https://jam.dev/c/c9e81000-60cb-4430-95d0-89a51225629f
https://jam.dev/c/4fabf2af-06af-488f-8e5b-e93f513c278b
One-click enter flow - Mobile:
https://jam.dev/c/1ff8f48c-ca5e-4b79-992c-cc5bf9109ffb
One-click enter flow - Desktop:
https://jam.dev/c/1f93ce52-a66f-410c-a0ae-c92eb47d3ba9
USDT reset + approval flows:
https://jam.dev/c/2f4fe73b-095f-45f0-8e5d-7f11b654b9cf
https://jam.dev/c/c8a11f4e-aabb-4485-bab9-63a9276193c9
Initial fixes:
https://jam.dev/c/9f77b0f6-7185-4383-ac98-6dcd8f32517e
https://jam.dev/c/71010588-6804-4cf5-8974-b31cd602e5cd
Success step & duplicate close button fixes:
Summary by CodeRabbit
New Features
Improvements
Chores
✏️ Tip: You can customize this high-level summary in your review settings.