Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b849a44
refactor: extract SelectTokenWidget controller
fairlighteth Nov 17, 2025
259c318
docs: describe recent token storage schema
fairlighteth Nov 17, 2025
d64ef3c
fix: make SelectTokenModal onSelectChain optional
fairlighteth Nov 17, 2025
a60856c
fix: guard optional chain selector handlers
fairlighteth Nov 17, 2025
3f443a2
Merge branch 'feat/token-selector-8' into feat/token-selector-9
fairlighteth Nov 18, 2025
bcc0496
Merge branch 'feat/token-selector-8' into feat/token-selector-9
fairlighteth Nov 18, 2025
d675a2e
Merge remote-tracking branch 'origin/feat/token-selector-8' into feat…
fairlighteth Nov 25, 2025
a1380a0
Merge branch 'feat/token-selector-8' into feat/token-selector-9
fairlighteth Nov 28, 2025
4f46d1f
fix: improve error logging for network switch failure in token selection
fairlighteth Nov 28, 2025
9f68f8a
refactor: streamline onSelectChain handling in SelectTokenWidget
fairlighteth Nov 28, 2025
2389dee
Merge branch 'feat/token-selector-8' into feat/token-selector-9
fairlighteth Dec 4, 2025
a5328d9
Merge branch 'feat/token-selector-8' into feat/token-selector-9
fairlighteth Dec 4, 2025
fe91216
Merge remote-tracking branch 'origin/feat/token-selector-8' into feat…
fairlighteth Dec 4, 2025
ef97c76
Merge branch 'feat/token-selector-8' into feat/token-selector-9
fairlighteth Dec 5, 2025
fdb5371
Merge branch 'feat/token-selector-8' into feat/token-selector-9
fairlighteth Dec 9, 2025
8c022f3
Merge branch 'feat/token-selector-8' into feat/token-selector-9
fairlighteth Dec 10, 2025
69f8317
refactor: remove SelectTokenWidget helper functions and types to stre…
fairlighteth Dec 10, 2025
512d1c8
Merge branch 'feat/token-selector-8' into feat/token-selector-9
fairlighteth Dec 10, 2025
a600182
Merge branch 'feat/token-selector-8' into feat/token-selector-9
fairlighteth Dec 11, 2025
a316ba2
Merge branch 'feat/token-selector-8' into feat/token-selector-9
fairlighteth Dec 11, 2025
f506eb2
Merge remote-tracking branch 'origin/feat/token-selector-8' into feat…
fairlighteth Dec 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { useIsBridgingEnabled } from '@cowprotocol/common-hooks'
import { useWalletInfo } from '@cowprotocol/wallet'

import { Field } from 'legacy/state/types'

import { useLpTokensWithBalances } from 'modules/yield/shared'

import { SelectTokenWidgetViewProps } from './controllerProps'
import {
useManageWidgetVisibility,
useTokenAdminActions,
useTokenDataSources,
useWidgetMetadata,
} from './controllerState'
import { useSelectTokenWidgetViewState } from './controllerViewState'

import { useChainsToSelect } from '../../hooks/useChainsToSelect'
import { useCloseTokenSelectWidget } from '../../hooks/useCloseTokenSelectWidget'
import { useOnSelectChain } from '../../hooks/useOnSelectChain'
import { useOnTokenListAddingError } from '../../hooks/useOnTokenListAddingError'
import { useSelectTokenWidgetState } from '../../hooks/useSelectTokenWidgetState'
import { useUpdateSelectTokenWidgetState } from '../../hooks/useUpdateSelectTokenWidgetState'

export interface SelectTokenWidgetProps {
displayLpTokenLists?: boolean
standalone?: boolean
}

export interface SelectTokenWidgetController {
shouldRender: boolean
hasChainPanel: boolean
viewProps: SelectTokenWidgetViewProps
}

export function useSelectTokenWidgetController({
displayLpTokenLists,
standalone,
}: SelectTokenWidgetProps): SelectTokenWidgetController {
const widgetState = useSelectTokenWidgetState()
const { count: lpTokensWithBalancesCount } = useLpTokensWithBalances()
const resolvedField = widgetState.field ?? Field.INPUT
const chainsToSelect = useChainsToSelect()
const onSelectChain = useOnSelectChain()
const isBridgeFeatureEnabled = useIsBridgingEnabled()
const manageWidget = useManageWidgetVisibility()
const updateSelectTokenWidget = useUpdateSelectTokenWidgetState()
const { account, chainId: walletChainId } = useWalletInfo()
const closeTokenSelectWidget = useCloseTokenSelectWidget()
const tokenData = useTokenDataSources()
const onTokenListAddingError = useOnTokenListAddingError()
const tokenAdminActions = useTokenAdminActions()
const widgetMetadata = useWidgetMetadata(
resolvedField,
widgetState.tradeType,
displayLpTokenLists,
widgetState.oppositeToken,
lpTokensWithBalancesCount,
)

const { isChainPanelEnabled, viewProps } = useSelectTokenWidgetViewState({
displayLpTokenLists,
standalone,
widgetState,
chainsToSelect,
onSelectChain,
manageWidget,
updateSelectTokenWidget,
account,
closeTokenSelectWidget,
tokenData,
onTokenListAddingError,
tokenAdminActions,
widgetMetadata,
walletChainId,
isBridgeFeatureEnabled,
})

return {
shouldRender: Boolean(widgetState.onSelectToken && (widgetState.open || widgetState.forceOpen)),
hasChainPanel: isChainPanelEnabled,
viewProps,
}
}

export type { SelectTokenWidgetViewProps } from './controllerProps'
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {
useDismissHandler,
useImportFlowCallbacks,
useManageWidgetVisibility,
usePoolPageHandlers,
useRecentTokenSection,
useTokenAdminActions,
useTokenDataSources,
useTokenSelectionHandler,
} from './controllerState'

import { useCloseTokenSelectWidget } from '../../hooks/useCloseTokenSelectWidget'
import { useOnTokenListAddingError } from '../../hooks/useOnTokenListAddingError'
import { useSelectTokenWidgetState } from '../../hooks/useSelectTokenWidgetState'
import { useUpdateSelectTokenWidgetState } from '../../hooks/useUpdateSelectTokenWidgetState'

export interface WidgetViewDependenciesResult {
isManageWidgetOpen: boolean
openManageWidget: ReturnType<typeof useManageWidgetVisibility>['openManageWidget']
closeManageWidget: ReturnType<typeof useManageWidgetVisibility>['closeManageWidget']
onDismiss(): void
openPoolPage: ReturnType<typeof usePoolPageHandlers>['openPoolPage']
closePoolPage: ReturnType<typeof usePoolPageHandlers>['closePoolPage']
recentTokens: ReturnType<typeof useRecentTokenSection>['recentTokens']
handleTokenListItemClick: ReturnType<typeof useRecentTokenSection>['handleTokenListItemClick']
clearRecentTokens: ReturnType<typeof useRecentTokenSection>['clearRecentTokens']
handleSelectToken: ReturnType<typeof useTokenSelectionHandler>
importFlows: ReturnType<typeof useImportFlowCallbacks>
}

interface WidgetViewDependenciesArgs {
manageWidget: ReturnType<typeof useManageWidgetVisibility>
closeTokenSelectWidget: ReturnType<typeof useCloseTokenSelectWidget>
updateSelectTokenWidget: ReturnType<typeof useUpdateSelectTokenWidgetState>
tokenData: ReturnType<typeof useTokenDataSources>
tokenAdminActions: ReturnType<typeof useTokenAdminActions>
onTokenListAddingError: ReturnType<typeof useOnTokenListAddingError>
widgetState: ReturnType<typeof useSelectTokenWidgetState>
activeChainId: number | undefined
}

export function useWidgetViewDependencies({
manageWidget,
closeTokenSelectWidget,
updateSelectTokenWidget,
tokenData,
tokenAdminActions,
onTokenListAddingError,
widgetState,
activeChainId,
}: WidgetViewDependenciesArgs): WidgetViewDependenciesResult {
const { isManageWidgetOpen, openManageWidget, closeManageWidget } = manageWidget
const onDismiss = useDismissHandler(closeManageWidget, closeTokenSelectWidget)
const { openPoolPage, closePoolPage } = usePoolPageHandlers(updateSelectTokenWidget)
const { recentTokens, handleTokenListItemClick, clearRecentTokens } = useRecentTokenSection(
tokenData.allTokens,
tokenData.favoriteTokens,
activeChainId,
)
const handleSelectToken = useTokenSelectionHandler(widgetState.onSelectToken, widgetState)
const importFlows = useImportFlowCallbacks(
tokenAdminActions.importTokenCallback,
handleSelectToken,
onDismiss,
tokenAdminActions.addCustomTokenLists,
onTokenListAddingError,
updateSelectTokenWidget,
tokenData.favoriteTokens,
)

return {
isManageWidgetOpen,
openManageWidget,
closeManageWidget,
onDismiss,
openPoolPage,
closePoolPage,
recentTokens,
handleTokenListItemClick,
clearRecentTokens,
handleSelectToken,
importFlows,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import { TokenWithLogo } from '@cowprotocol/common-const'

import { buildSelectTokenModalPropsInput, buildSelectTokenWidgetViewProps, useSelectTokenModalPropsMemo } from './controllerProps'
import {
useManageWidgetVisibility,
usePoolPageHandlers,
useRecentTokenSection,
useTokenDataSources,
useTokenSelectionHandler,
useWidgetMetadata,
} from './controllerState'

import { useChainsToSelect } from '../../hooks/useChainsToSelect'
import { useOnSelectChain } from '../../hooks/useOnSelectChain'
import { useSelectTokenWidgetState } from '../../hooks/useSelectTokenWidgetState'

import type { WidgetViewDependenciesResult } from './controllerDependencies'
import type { SelectTokenModalProps } from '../../pure/SelectTokenModal'

const EMPTY_FAV_TOKENS: TokenWithLogo[] = []

interface WidgetModalPropsArgs {
account: string | undefined
chainsToSelect: ReturnType<typeof useChainsToSelect>
displayLpTokenLists?: boolean
widgetDeps: WidgetViewDependenciesResult
hasChainPanel: boolean
onSelectChain: ReturnType<typeof useOnSelectChain>
recentTokens: ReturnType<typeof useRecentTokenSection>['recentTokens']
standalone?: boolean
tokenData: ReturnType<typeof useTokenDataSources>
widgetMetadata: ReturnType<typeof useWidgetMetadata>
widgetState: ReturnType<typeof useSelectTokenWidgetState>
isInjectedWidgetMode: boolean
}

export function useWidgetModalProps({
account,
chainsToSelect,
displayLpTokenLists,
widgetDeps,
hasChainPanel,
onSelectChain,
recentTokens,
standalone,
tokenData,
widgetMetadata,
widgetState,
isInjectedWidgetMode,
}: WidgetModalPropsArgs): SelectTokenModalProps {
const favoriteTokens = standalone ? EMPTY_FAV_TOKENS : tokenData.favoriteTokens

return useSelectTokenModalPropsMemo(
createSelectTokenModalProps({
account,
chainsPanelTitle: widgetMetadata.chainsPanelTitle,
chainsState: chainsToSelect,
disableErc20: widgetMetadata.disableErc20,
displayLpTokenLists,
favoriteTokens,
handleSelectToken: widgetDeps.handleSelectToken,
hasChainPanel,
isInjectedWidgetMode,
modalTitle: widgetMetadata.modalTitle,
onDismiss: widgetDeps.onDismiss,
onSelectChain,
onTokenListItemClick: widgetDeps.handleTokenListItemClick,
onClearRecentTokens: widgetDeps.clearRecentTokens,
onOpenManageWidget: widgetDeps.openManageWidget,
openPoolPage: widgetDeps.openPoolPage,
recentTokens,
standalone,
tokenData,
tokenListCategoryState: widgetMetadata.tokenListCategoryState,
widgetState,
}),
)
}

interface BuildViewPropsArgs {
allTokenLists: ReturnType<typeof useTokenDataSources>['allTokenLists']
chainsPanelTitle: string
chainsToSelect: ReturnType<typeof useChainsToSelect>
closeManageWidget: ReturnType<typeof useManageWidgetVisibility>['closeManageWidget']
closePoolPage: ReturnType<typeof usePoolPageHandlers>['closePoolPage']
importFlows: WidgetViewDependenciesResult['importFlows']
isChainPanelEnabled: boolean
onDismiss: () => void
onSelectChain: ReturnType<typeof useOnSelectChain>
selectTokenModalProps: ReturnType<typeof useSelectTokenModalPropsMemo>
selectedPoolAddress: ReturnType<typeof useSelectTokenWidgetState>['selectedPoolAddress']
standalone: boolean | undefined
tokenToImport: ReturnType<typeof useSelectTokenWidgetState>['tokenToImport']
listToImport: ReturnType<typeof useSelectTokenWidgetState>['listToImport']
isManageWidgetOpen: ReturnType<typeof useManageWidgetVisibility>['isManageWidgetOpen']
userAddedTokens: ReturnType<typeof useTokenDataSources>['userAddedTokens']
handleSelectToken: ReturnType<typeof useTokenSelectionHandler>
}

type BuildViewPropsInput = Parameters<typeof buildSelectTokenWidgetViewProps>[0]

export function getSelectTokenWidgetViewPropsArgs(args: BuildViewPropsArgs): BuildViewPropsInput {
const {
standalone,
tokenToImport,
listToImport,
isManageWidgetOpen,
selectedPoolAddress,
isChainPanelEnabled,
chainsPanelTitle,
chainsToSelect,
onSelectChain,
onDismiss,
importFlows,
allTokenLists,
userAddedTokens,
closeManageWidget,
closePoolPage,
selectTokenModalProps,
handleSelectToken,
} = args

return {
standalone,
tokenToImport,
listToImport,
isManageWidgetOpen,
selectedPoolAddress,
isChainPanelEnabled,
chainsPanelTitle,
chainsToSelect,
onSelectChain,
onDismiss,
onBackFromImport: importFlows.resetTokenImport,
onImportTokens: importFlows.importTokenAndClose,
onImportList: importFlows.importListAndBack,
allTokenLists,
userAddedTokens,
onCloseManageWidget: closeManageWidget,
onClosePoolPage: closePoolPage,
selectTokenModalProps,
onSelectToken: handleSelectToken,
}
}

function createSelectTokenModalProps({
account,
chainsPanelTitle,
chainsState,
disableErc20,
displayLpTokenLists,
favoriteTokens,
handleSelectToken,
hasChainPanel,
isInjectedWidgetMode,
modalTitle,
onDismiss,
onSelectChain,
onTokenListItemClick,
onClearRecentTokens,
onOpenManageWidget,
openPoolPage,
recentTokens,
standalone,
tokenData,
tokenListCategoryState,
widgetState,
}: {
account: string | undefined
chainsPanelTitle: string
chainsState: ReturnType<typeof useChainsToSelect>
disableErc20: boolean
displayLpTokenLists: boolean | undefined
favoriteTokens: TokenWithLogo[]
handleSelectToken: ReturnType<typeof useTokenSelectionHandler>
hasChainPanel: boolean
isInjectedWidgetMode: boolean
modalTitle: string
onDismiss: () => void
onSelectChain: ReturnType<typeof useOnSelectChain>
onTokenListItemClick: ReturnType<typeof useRecentTokenSection>['handleTokenListItemClick']
onClearRecentTokens: ReturnType<typeof useRecentTokenSection>['clearRecentTokens']
onOpenManageWidget: ReturnType<typeof useManageWidgetVisibility>['openManageWidget']
openPoolPage: ReturnType<typeof usePoolPageHandlers>['openPoolPage']
recentTokens: ReturnType<typeof useRecentTokenSection>['recentTokens']
standalone: boolean | undefined
tokenData: ReturnType<typeof useTokenDataSources>
tokenListCategoryState: ReturnType<typeof useWidgetMetadata>['tokenListCategoryState']
widgetState: ReturnType<typeof useSelectTokenWidgetState>
}): SelectTokenModalProps {
return buildSelectTokenModalPropsInput({
standalone,
displayLpTokenLists,
tokenData,
widgetState,
favoriteTokens,
recentTokens,
handleSelectToken,
onTokenListItemClick,
onClearRecentTokens,
onDismiss,
onOpenManageWidget,
openPoolPage,
tokenListCategoryState,
disableErc20,
account,
hasChainPanel,
chainsState,
chainsPanelTitle,
onSelectChain,
isInjectedWidgetMode,
modalTitle,
})
}
Loading