diff --git a/packages/wallet-management/src/components/WalletMenuContent.tsx b/packages/wallet-management/src/components/WalletMenuContent.tsx index f364ea64a..b2a9791a0 100644 --- a/packages/wallet-management/src/components/WalletMenuContent.tsx +++ b/packages/wallet-management/src/components/WalletMenuContent.tsx @@ -24,6 +24,7 @@ import { ListItemButton } from './ListItemButton.js' import { ListItemText } from './ListItemText.js' import { SVMListItemButton } from './SVMListItemButton.js' import { UTXOListItemButton } from './UTXOListItemButton.js' +import { WalletMenuContentEmpty } from './WalletMenuContentEmpty.js' interface WalletMenuContentProps { onClose: () => void @@ -197,6 +198,9 @@ export const WalletMenuContent: React.FC = ({ ) })} + {/* TODO: show all connected wallets with 'Connected' badge + and have this empty screen only when there is no installed wallets at all */} + {!installedWallets.length ? : null} diff --git a/packages/wallet-management/src/components/WalletMenuContentEmpty.tsx b/packages/wallet-management/src/components/WalletMenuContentEmpty.tsx new file mode 100644 index 000000000..e8ce5e1bd --- /dev/null +++ b/packages/wallet-management/src/components/WalletMenuContentEmpty.tsx @@ -0,0 +1,34 @@ +import { Wallet } from '@mui/icons-material' +import { Box, Typography } from '@mui/material' +import { useTranslation } from 'react-i18next' + +export const WalletMenuContentEmpty: React.FC = () => { + const { t } = useTranslation() + return ( + + + + + + {t('title.availableWalletsNotFound')} + + + {t('message.availableWalletsNotFound')} + + + ) +} diff --git a/packages/wallet-management/src/hooks/useCombinedWallets.ts b/packages/wallet-management/src/hooks/useCombinedWallets.ts index 1044b94a7..0c0e39a01 100644 --- a/packages/wallet-management/src/hooks/useCombinedWallets.ts +++ b/packages/wallet-management/src/hooks/useCombinedWallets.ts @@ -141,24 +141,34 @@ export const useCombinedWallets = () => { ) } - const installedUTXOConnectors = bigmiConnectors.filter((connector) => { - const isInstalled = isWalletInstalled(connector.id) - const isConnected = bigmiAccount.connector?.id === connector.id - return isInstalled && !isConnected - }) - - const installedEVMConnectors = evmConnectors.filter((connector) => { - const isInstalled = isWalletInstalled(connector.id) - const isConnected = wagmiAccount.connector?.id === connector.id - return isInstalled && !isConnected - }) - - const installedSVMWallets = solanaWallets.filter((wallet) => { - const isInstalled = - wallet.adapter.readyState === WalletReadyState.Installed - const isConnected = wallet.adapter.connected - return isInstalled && !isConnected - }) + const includeEcosystem = (chainType: ChainType) => + !walletConfig.enabledChainTypes || + walletConfig.enabledChainTypes.includes(chainType) + + const installedUTXOConnectors = includeEcosystem(ChainType.UTXO) + ? bigmiConnectors.filter((connector) => { + const isInstalled = isWalletInstalled(connector.id) + const isConnected = bigmiAccount.connector?.id === connector.id + return isInstalled && !isConnected + }) + : [] + + const installedEVMConnectors = includeEcosystem(ChainType.EVM) + ? evmConnectors.filter((connector) => { + const isInstalled = isWalletInstalled(connector.id) + const isConnected = wagmiAccount.connector?.id === connector.id + return isInstalled && !isConnected + }) + : [] + + const installedSVMWallets = includeEcosystem(ChainType.SVM) + ? solanaWallets.filter((wallet) => { + const isInstalled = + wallet.adapter.readyState === WalletReadyState.Installed + const isConnected = wallet.adapter.connected + return isInstalled && !isConnected + }) + : [] const installedCombinedWallets = combineWalletLists( installedUTXOConnectors, diff --git a/packages/wallet-management/src/i18n/en.json b/packages/wallet-management/src/i18n/en.json index 397427eb4..02e479f63 100644 --- a/packages/wallet-management/src/i18n/en.json +++ b/packages/wallet-management/src/i18n/en.json @@ -3,10 +3,12 @@ "connectWallet": "Connect wallet", "connecting": "Connecting", "selectEcosystem": "Select ecosystem", - "waitingForWallet": "Waiting for {{walletName}}" + "waitingForWallet": "Waiting for {{walletName}}", + "availableWalletsNotFound": "Available wallets not found" }, "message": { "connecting": "Click connect in your wallet popup. Don't see your wallet? Check your other browser windows.", - "multipleEcosystems": "{{walletName}} supports multiple chain ecosystems. Select which chain ecosystem you'd like to connect." + "multipleEcosystems": "{{walletName}} supports multiple chain ecosystems. Select which chain ecosystem you'd like to connect.", + "availableWalletsNotFound": "No compatible wallet extensions detected. Please install a supported wallet and refresh the page. If already installed, ensure it's enabled and compatible, or contact support." } } diff --git a/packages/wallet-management/src/providers/WalletManagementProvider/types.ts b/packages/wallet-management/src/providers/WalletManagementProvider/types.ts index b6a8b6d22..a4223a204 100644 --- a/packages/wallet-management/src/providers/WalletManagementProvider/types.ts +++ b/packages/wallet-management/src/providers/WalletManagementProvider/types.ts @@ -1,8 +1,10 @@ +import type { ChainType } from '@lifi/sdk' import type { WalletConfig } from '../../types/walletConfig.js' import type { LanguageKey } from '../I18nProvider/types.js' export interface WalletManagementConfig extends WalletConfig { locale?: LanguageKey + enabledChainTypes?: ChainType[] } export interface WalletManagementProviderProps { diff --git a/packages/wallet-management/src/utils/isWalletInstalled.ts b/packages/wallet-management/src/utils/isWalletInstalled.ts index 8c51a04a4..3c4f313e4 100644 --- a/packages/wallet-management/src/utils/isWalletInstalled.ts +++ b/packages/wallet-management/src/utils/isWalletInstalled.ts @@ -21,6 +21,8 @@ export const isWalletInstalled = (id: string): boolean => { ) case 'app.phantom.bitcoin': return anyWindow.phantom?.bitcoin?.isPhantom + case 'com.okex.wallet.bitcoin': + return anyWindow.okxwallet?.bitcoin?.isOkxWallet case 'XverseProviders.BitcoinProvider': return anyWindow.XverseProviders?.BitcoinProvider case 'unisat': diff --git a/packages/widget-playground/src/components/DrawerControls/DesignControls/WalletManagementControl.tsx b/packages/widget-playground/src/components/DrawerControls/DesignControls/WalletManagementControl.tsx index 34b5bb8fc..c5d0017d6 100644 --- a/packages/widget-playground/src/components/DrawerControls/DesignControls/WalletManagementControl.tsx +++ b/packages/widget-playground/src/components/DrawerControls/DesignControls/WalletManagementControl.tsx @@ -1,3 +1,4 @@ +import { Collapse } from '@mui/material' import type * as React from 'react' import { useConfigActions } from '../../../store/widgetConfig/useConfigActions' import { useConfigWalletManagement } from '../../../store/widgetConfig/useConfigValues' @@ -10,18 +11,33 @@ import { import { Switch } from '../../Switch' export const WalletManagementControl = () => { - const { isExternalWalletManagement, replacementWalletConfig } = - useConfigWalletManagement() + const { + isExternalWalletManagement, + isPartialWalletManagement, + replacementWalletConfig, + } = useConfigWalletManagement() const { setWalletConfig } = useConfigActions() - const handleSwitchChange: ( + + const handleExternalWalletManagement = ( _: React.ChangeEvent, checked: boolean - ) => void = (_, checked) => { + ) => { const walletConfig = checked ? replacementWalletConfig : undefined setWalletConfig(walletConfig) } + const handlePartialWalletManagement = ( + _: React.ChangeEvent, + checked: boolean + ) => { + const walletConfig = checked + ? { ...replacementWalletConfig, usePartialWalletManagement: true } + : replacementWalletConfig + + setWalletConfig(walletConfig) + } + return ( @@ -30,10 +46,22 @@ export const WalletManagementControl = () => { + + + + Use partial wallet management + + + + ) } diff --git a/packages/widget-playground/src/defaultWidgetConfig.ts b/packages/widget-playground/src/defaultWidgetConfig.ts index eb50e9464..7958af7b7 100644 --- a/packages/widget-playground/src/defaultWidgetConfig.ts +++ b/packages/widget-playground/src/defaultWidgetConfig.ts @@ -38,6 +38,9 @@ export const widgetBaseConfig: WidgetConfig = { // disabledUI: ['toAddress', 'fromAmount', 'toToken', 'fromToken'], // requiredUI: ['toAddress'], // slippage: 0.003, + // walletConfig: { + // usePartialWalletManagement: true, + // }, sdkConfig: { apiUrl: 'https://li.quest/v1', rpcUrls: { diff --git a/packages/widget-playground/src/providers/ExternalWalletProvider/EVMProvider.tsx b/packages/widget-playground/src/providers/ExternalWalletProvider/EVMProvider.tsx index c97746f7f..0fe3119fe 100644 --- a/packages/widget-playground/src/providers/ExternalWalletProvider/EVMProvider.tsx +++ b/packages/widget-playground/src/providers/ExternalWalletProvider/EVMProvider.tsx @@ -6,13 +6,16 @@ import { darkTheme, getDefaultConfig, lightTheme, + useConnectModal, } from '@rainbow-me/rainbowkit' import '@rainbow-me/rainbowkit/styles.css' -import { type FC, type PropsWithChildren, useMemo } from 'react' +import { type FC, type PropsWithChildren, useEffect, useMemo } from 'react' import type { Chain } from 'viem' import { WagmiProvider } from 'wagmi' import { mainnet } from 'wagmi/chains' import { useThemeMode } from '../../hooks/useThemeMode' +import { useWidgetConfigStore } from '../../store/widgetConfig/WidgetConfigProvider' +import { useConfigActions } from '../../store/widgetConfig/useConfigActions' import { useEnvVariables } from '../EnvVariablesProvider' import { theme } from '../PlaygroundThemeProvider/theme' @@ -72,8 +75,36 @@ export const EVMProvider: FC = ({ children }) => { reconnectOnMount={Boolean(chains?.length)} > + {children} ) } +export const WidgetWalletConfigUpdater = () => { + const { openConnectModal } = useConnectModal() + const { setWalletConfig } = useConfigActions() + const walletConfig = useWidgetConfigStore( + (store) => store.config?.walletConfig + ) + + useEffect(() => { + // Due to provider constraints, we're unable to directly assign an onConnect function + // that opens the external wallet management directly from the widget playground settings component. + // To work around this limitation, we employ a temporary "hack" by initially setting an empty function. + // This allows us to later replace it with the intended functionality. + const onConnectStringified = walletConfig?.onConnect + ?.toString() + .replaceAll(' ', '') + if (onConnectStringified === '()=>{}') { + setWalletConfig({ + ...walletConfig, + onConnect: () => { + openConnectModal?.() + }, + }) + } + }, [openConnectModal, setWalletConfig, walletConfig]) + + return null +} diff --git a/packages/widget-playground/src/providers/ExternalWalletProvider/ExternalWalletProvider.tsx b/packages/widget-playground/src/providers/ExternalWalletProvider/ExternalWalletProvider.tsx index 2f1f04409..1636d4f49 100644 --- a/packages/widget-playground/src/providers/ExternalWalletProvider/ExternalWalletProvider.tsx +++ b/packages/widget-playground/src/providers/ExternalWalletProvider/ExternalWalletProvider.tsx @@ -1,6 +1,5 @@ import type { FC, PropsWithChildren } from 'react' import { EVMProvider } from './EVMProvider' -import { SVMProvider } from './SVMProvider' interface ExternalWalletProviderProps extends PropsWithChildren { isExternalProvider?: boolean @@ -9,11 +8,5 @@ export const ExternalWalletProvider: FC = ({ children, isExternalProvider, }) => { - return isExternalProvider ? ( - - {children} - - ) : ( - <>{children} - ) + return isExternalProvider ? {children} : children } diff --git a/packages/widget-playground/src/store/widgetConfig/createWidgetConfigStore.ts b/packages/widget-playground/src/store/widgetConfig/createWidgetConfigStore.ts index 47305d017..e7fb0ecd1 100644 --- a/packages/widget-playground/src/store/widgetConfig/createWidgetConfigStore.ts +++ b/packages/widget-playground/src/store/widgetConfig/createWidgetConfigStore.ts @@ -271,7 +271,7 @@ export const createWidgetConfigStore = ( if (state.config?.walletConfig) { const walletConfig = state.defaultConfig?.walletConfig ? state.defaultConfig?.walletConfig - : { async onConnect() {} } + : { onConnect: () => {} } state.setWalletConfig(walletConfig) } diff --git a/packages/widget-playground/src/store/widgetConfig/useConfigValues.ts b/packages/widget-playground/src/store/widgetConfig/useConfigValues.ts index 46ee4ed50..878e8e284 100644 --- a/packages/widget-playground/src/store/widgetConfig/useConfigValues.ts +++ b/packages/widget-playground/src/store/widgetConfig/useConfigValues.ts @@ -117,10 +117,11 @@ export const useConfigWalletManagement = () => { const replacementWalletConfig = defaultWalletConfig ? defaultWalletConfig - : { async onConnect() {} } + : { onConnect: () => {} } return { isExternalWalletManagement: !!walletConfig, + isPartialWalletManagement: !!walletConfig?.usePartialWalletManagement, replacementWalletConfig, } } diff --git a/packages/widget/src/components/ChainSelect/useChainSelect.ts b/packages/widget/src/components/ChainSelect/useChainSelect.ts index 873eeac91..5ae766000 100644 --- a/packages/widget/src/components/ChainSelect/useChainSelect.ts +++ b/packages/widget/src/components/ChainSelect/useChainSelect.ts @@ -2,7 +2,7 @@ import type { EVMChain } from '@lifi/sdk' import { useChains } from '../../hooks/useChains.js' import { useSwapOnly } from '../../hooks/useSwapOnly.js' import { useToAddressReset } from '../../hooks/useToAddressReset.js' -import { useHasExternalWalletProvider } from '../../providers/WalletProvider/useHasExternalWalletProvider.js' +import { useExternalWalletProvider } from '../../providers/WalletProvider/useExternalWalletProvider.js' import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js' import { useChainOrder } from '../../stores/chains/useChainOrder.js' import type { FormType } from '../../stores/form/types.js' @@ -16,11 +16,16 @@ export const useChainSelect = (formType: FormType) => { const chainKey = FormKeyHelper.getChainKey(formType) const { onChange } = useFieldController({ name: chainKey }) const { setFieldValue, getFieldValues } = useFieldActions() - const { hasExternalProvider, availableChainTypes } = - useHasExternalWalletProvider() + const { useExternalWalletProvidersOnly, externalChainTypes } = + useExternalWalletProvider() const { chains, isLoading, getChainById } = useChains( formType, - formType === 'from' && hasExternalProvider ? availableChainTypes : undefined + // If the integrator uses external wallet management and has not opted in for partial wallet management, + // restrict the displayed chains to those compatible with external wallet management. + // This ensures users only see chains for which they can sign transactions. + formType === 'from' && useExternalWalletProvidersOnly + ? externalChainTypes + : undefined ) const [chainOrder, setChainOrder] = useChainOrder(formType) diff --git a/packages/widget/src/components/Header/CloseDrawerButton.tsx b/packages/widget/src/components/Header/CloseDrawerButton.tsx index f0c666fbf..de41818dc 100644 --- a/packages/widget/src/components/Header/CloseDrawerButton.tsx +++ b/packages/widget/src/components/Header/CloseDrawerButton.tsx @@ -2,7 +2,7 @@ import { CloseRounded } from '@mui/icons-material' import { IconButton, Tooltip } from '@mui/material' import { useTranslation } from 'react-i18next' import { useDrawer } from '../../AppDrawerContext.js' -import { useHasExternalWalletProvider } from '../../providers/WalletProvider/useHasExternalWalletProvider.js' +import { useExternalWalletProvider } from '../../providers/WalletProvider/useExternalWalletProvider.js' import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js' interface CloseDrawerButtonProps { @@ -13,10 +13,11 @@ export const CloseDrawerButton = ({ header }: CloseDrawerButtonProps) => { const { t } = useTranslation() const { subvariant } = useWidgetConfig() const { closeDrawer } = useDrawer() - const { hasExternalProvider } = useHasExternalWalletProvider() + const { useExternalWalletProvidersOnly } = useExternalWalletProvider() const showInNavigationHeader = - header === 'navigation' && (hasExternalProvider || subvariant === 'split') + header === 'navigation' && + (useExternalWalletProvidersOnly || subvariant === 'split') const showInWalletHeader = header === 'wallet' && subvariant !== 'split' diff --git a/packages/widget/src/components/Header/WalletHeader.tsx b/packages/widget/src/components/Header/WalletHeader.tsx index 2739248b8..d07a4b781 100644 --- a/packages/widget/src/components/Header/WalletHeader.tsx +++ b/packages/widget/src/components/Header/WalletHeader.tsx @@ -9,7 +9,7 @@ import { Avatar, Badge } from '@mui/material' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useChain } from '../../hooks/useChain.js' -import { useHasExternalWalletProvider } from '../../providers/WalletProvider/useHasExternalWalletProvider.js' +import { useExternalWalletProvider } from '../../providers/WalletProvider/useExternalWalletProvider.js' import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js' import { useFieldValues } from '../../stores/form/useFieldValues.js' import { HiddenUI } from '../../types/widget.js' @@ -25,12 +25,23 @@ import { import { WalletMenu } from './WalletMenu.js' import { WalletMenuContainer } from './WalletMenu.style.js' -export const WalletHeader: React.FC = () => { +const useInternalWalletManagement = () => { const { subvariant, hiddenUI } = useWidgetConfig() - const { hasExternalProvider } = useHasExternalWalletProvider() - return !hasExternalProvider && - subvariant !== 'split' && - !hiddenUI?.includes(HiddenUI.WalletMenu) ? ( + const { useExternalWalletProvidersOnly } = useExternalWalletProvider() + + const isSplitVariant = subvariant === 'split' + const isWalletMenuHidden = hiddenUI?.includes(HiddenUI.WalletMenu) + + const shouldShowWalletMenu = + !useExternalWalletProvidersOnly && !isSplitVariant && !isWalletMenuHidden + + return shouldShowWalletMenu +} + +export const WalletHeader: React.FC = () => { + const shouldShowWalletMenu = useInternalWalletManagement() + + return shouldShowWalletMenu ? ( @@ -38,11 +49,8 @@ export const WalletHeader: React.FC = () => { } export const SplitWalletMenuButton: React.FC = () => { - const { hiddenUI } = useWidgetConfig() - const { hasExternalProvider } = useHasExternalWalletProvider() - return !hasExternalProvider && !hiddenUI?.includes(HiddenUI.WalletMenu) ? ( - - ) : null + const shouldShowWalletMenu = useInternalWalletManagement() + return shouldShowWalletMenu ? : null } export const WalletMenuButton: React.FC = () => { @@ -83,7 +91,7 @@ const ConnectButton = () => { const { walletConfig, subvariant, variant } = useWidgetConfig() const { openWalletMenu } = useWalletMenu() const connect = async () => { - if (walletConfig?.onConnect) { + if (!walletConfig?.usePartialWalletManagement && walletConfig?.onConnect) { walletConfig.onConnect() return } diff --git a/packages/widget/src/pages/SendToWallet/ConnectedWalletsPage.tsx b/packages/widget/src/pages/SendToWallet/ConnectedWalletsPage.tsx index 6964344a4..59458f577 100644 --- a/packages/widget/src/pages/SendToWallet/ConnectedWalletsPage.tsx +++ b/packages/widget/src/pages/SendToWallet/ConnectedWalletsPage.tsx @@ -4,7 +4,7 @@ import { ContentCopyRounded, MoreHoriz, OpenInNewRounded, - TurnedIn, + Wallet, } from '@mui/icons-material' import { ListItemAvatar, ListItemText, MenuItem } from '@mui/material' import { useId, useState } from 'react' @@ -139,7 +139,7 @@ export const ConnectedWalletsPage = () => { ) })} {!accounts.length && ( - }> + }> {t('sendToWallet.noConnectedWallets')} )} diff --git a/packages/widget/src/pages/SendToWallet/RecentWalletsPage.tsx b/packages/widget/src/pages/SendToWallet/RecentWalletsPage.tsx index 57161c798..355296e5c 100644 --- a/packages/widget/src/pages/SendToWallet/RecentWalletsPage.tsx +++ b/packages/widget/src/pages/SendToWallet/RecentWalletsPage.tsx @@ -1,10 +1,10 @@ import { ContentCopyRounded, DeleteOutline, + History, MoreHoriz, OpenInNewRounded, TurnedInNot, - Wallet, } from '@mui/icons-material' import { ListItemAvatar, ListItemText, MenuItem } from '@mui/material' import { useId, useRef, useState } from 'react' @@ -175,7 +175,7 @@ export const RecentWalletsPage = () => { ))} {!recentWallets.length && ( - }> + }> {t('sendToWallet.noRecentWallets')} )} diff --git a/packages/widget/src/providers/WalletProvider/WalletProvider.tsx b/packages/widget/src/providers/WalletProvider/WalletProvider.tsx index 85e0a440b..d58770c2b 100644 --- a/packages/widget/src/providers/WalletProvider/WalletProvider.tsx +++ b/packages/widget/src/providers/WalletProvider/WalletProvider.tsx @@ -7,6 +7,7 @@ import { EVMProvider } from './EVMProvider.js' import { SDKProviders } from './SDKProviders.js' import { SVMProvider } from './SVMProvider.js' import { UTXOProvider } from './UTXOProvider.js' +import { useExternalWalletProvider } from './useExternalWalletProvider.js' export const WalletProvider: FC = ({ children }) => { return ( @@ -24,10 +25,16 @@ export const WalletProvider: FC = ({ children }) => { export const WalletMenuProvider: FC = ({ children }) => { const { walletConfig } = useWidgetConfig() const { i18n } = useTranslation() + const { internalChainTypes } = useExternalWalletProvider() const config: WalletManagementConfig = useMemo(() => { - return { locale: i18n.resolvedLanguage as never, ...walletConfig } - }, [i18n.resolvedLanguage, walletConfig]) + return { + locale: i18n.resolvedLanguage as never, + enabledChainTypes: internalChainTypes, + ...walletConfig, + } + }, [i18n.resolvedLanguage, internalChainTypes, walletConfig]) + return ( {children} diff --git a/packages/widget/src/providers/WalletProvider/useExternalWalletProvider.ts b/packages/widget/src/providers/WalletProvider/useExternalWalletProvider.ts new file mode 100644 index 000000000..3ee67e97a --- /dev/null +++ b/packages/widget/src/providers/WalletProvider/useExternalWalletProvider.ts @@ -0,0 +1,53 @@ +import { ChainType } from '@lifi/sdk' +import { useContext, useMemo } from 'react' +import { useWidgetConfig } from '../WidgetProvider/WidgetProvider.js' +import { EVMExternalContext } from './EVMExternalContext.js' +import { SVMExternalContext } from './SVMExternalContext.js' +import { UTXOExternalContext } from './UTXOExternalContext.js' + +interface ExternalWalletProvider { + useExternalWalletProvidersOnly: boolean + externalChainTypes: ChainType[] + internalChainTypes: ChainType[] +} + +const internalChainTypes = [ChainType.EVM, ChainType.SVM, ChainType.UTXO] + +export function useExternalWalletProvider(): ExternalWalletProvider { + const { walletConfig } = useWidgetConfig() + const hasExternalEVMContext = useContext(EVMExternalContext) + const hasExternalSVMContext = useContext(SVMExternalContext) + const hasExternalUTXOContext = useContext(UTXOExternalContext) + + const data = useMemo(() => { + const providers: ChainType[] = [] + if (hasExternalEVMContext) { + providers.push(ChainType.EVM) + } + if (hasExternalSVMContext) { + providers.push(ChainType.SVM) + } + if (hasExternalUTXOContext) { + providers.push(ChainType.UTXO) + } + const hasExternalProvider = + hasExternalEVMContext || hasExternalSVMContext || hasExternalUTXOContext + + const useExternalWalletProvidersOnly = + hasExternalProvider && !walletConfig?.usePartialWalletManagement + return { + useExternalWalletProvidersOnly, + externalChainTypes: providers, + internalChainTypes: internalChainTypes.filter( + (chainType) => !providers.includes(chainType) + ), + } + }, [ + hasExternalEVMContext, + hasExternalSVMContext, + hasExternalUTXOContext, + walletConfig?.usePartialWalletManagement, + ]) + + return data +} diff --git a/packages/widget/src/providers/WalletProvider/useHasExternalWalletProvider.ts b/packages/widget/src/providers/WalletProvider/useHasExternalWalletProvider.ts deleted file mode 100644 index e92f40ac5..000000000 --- a/packages/widget/src/providers/WalletProvider/useHasExternalWalletProvider.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { ChainType } from '@lifi/sdk' -import { useContext, useMemo } from 'react' -import { EVMExternalContext } from './EVMExternalContext.js' -import { SVMExternalContext } from './SVMExternalContext.js' -import { UTXOExternalContext } from './UTXOExternalContext.js' - -interface ExternalWalletProvider { - hasExternalProvider: boolean - availableChainTypes: ChainType[] -} - -export function useHasExternalWalletProvider(): ExternalWalletProvider { - const hasExternalEVMContext = useContext(EVMExternalContext) - const hasExternalSVMContext = useContext(SVMExternalContext) - const hasExternalUTXOContext = useContext(UTXOExternalContext) - - const providers = useMemo(() => { - const providers: ChainType[] = [] - if (hasExternalEVMContext) { - providers.push(ChainType.EVM) - } - if (hasExternalSVMContext) { - providers.push(ChainType.SVM) - } - if (hasExternalUTXOContext) { - providers.push(ChainType.UTXO) - } - return providers - }, [hasExternalEVMContext, hasExternalSVMContext, hasExternalUTXOContext]) - - return { - hasExternalProvider: - hasExternalEVMContext || hasExternalSVMContext || hasExternalUTXOContext, - availableChainTypes: providers, - } -} diff --git a/packages/widget/src/stores/chains/ChainOrderStore.tsx b/packages/widget/src/stores/chains/ChainOrderStore.tsx index 118a43719..a49794b72 100644 --- a/packages/widget/src/stores/chains/ChainOrderStore.tsx +++ b/packages/widget/src/stores/chains/ChainOrderStore.tsx @@ -2,6 +2,7 @@ import { createContext, useContext, useEffect, useRef } from 'react' import type { StoreApi } from 'zustand' import type { UseBoundStoreWithEqualityFn } from 'zustand/traditional' import { useChains } from '../../hooks/useChains.js' +import { useExternalWalletProvider } from '../../providers/WalletProvider/useExternalWalletProvider.js' import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js' import { isItemAllowed } from '../../utils/item.js' import type { FormType } from '../form/types.js' @@ -22,10 +23,12 @@ export function ChainOrderStoreProvider({ children, ...props }: PersistStoreProviderProps) { - const { chains: configChains } = useWidgetConfig() + const { chains: chainsConfig } = useWidgetConfig() const storeRef = useRef() const { chains } = useChains() const { setFieldValue, getFieldValues } = useFieldActions() + const { externalChainTypes, useExternalWalletProvidersOnly } = + useExternalWalletProvider() if (!storeRef.current) { storeRef.current = createChainOrderStore(props) @@ -34,9 +37,22 @@ export function ChainOrderStoreProvider({ useEffect(() => { if (chains) { ;(['from', 'to'] as FormType[]).forEach((key) => { - const filteredChains = configChains?.[key] - ? chains.filter((chain) => isItemAllowed(chain.id, configChains[key])) - : chains + const configChainIds = chainsConfig?.[key] + const isFromKey = key === 'from' + + const filteredChains = chains.filter((chain) => { + const passesChainsConfigFilter = configChainIds + ? isItemAllowed(chain.id, configChainIds) + : true + // If the integrator uses external wallet management and has not opted in for partial wallet management, + // restrict the displayed chains to those compatible with external wallet management. + // This ensures users only see chains for which they can sign transactions. + const passesWalletConfigFilter = isFromKey + ? !useExternalWalletProvidersOnly || + externalChainTypes.includes(chain.chainType) + : true + return passesChainsConfigFilter && passesWalletConfigFilter + }) const chainOrder = storeRef.current?.getState().initializeChains( filteredChains.map((chain) => chain.id), key @@ -49,7 +65,14 @@ export function ChainOrderStoreProvider({ } }) } - }, [chains, configChains, getFieldValues, setFieldValue]) + }, [ + chains, + chainsConfig, + externalChainTypes, + getFieldValues, + setFieldValue, + useExternalWalletProvidersOnly, + ]) return ( diff --git a/packages/widget/src/types/widget.ts b/packages/widget/src/types/widget.ts index 9ef066396..304bb42af 100644 --- a/packages/widget/src/types/widget.ts +++ b/packages/widget/src/types/widget.ts @@ -108,6 +108,15 @@ export interface WidgetWalletConfig { walletConnect?: WalletConnectParameters coinbase?: CoinbaseWalletParameters metaMask?: MetaMaskParameters + /** + * Determines whether the widget should provide partial wallet management functionality. + * + * In partial mode, external wallet management will be used for "opt-out" providers, + * while the internal management is applied for any remaining providers that do not opt out. + * This allows a flexible balance between the integrator’s custom wallet menu and the widget’s native wallet menu. + * @default false + */ + usePartialWalletManagement?: boolean } export interface WidgetSDKConfig