Skip to content

Commit

Permalink
feat: add support for partial wallet management
Browse files Browse the repository at this point in the history
  • Loading branch information
chybisov committed Oct 29, 2024
1 parent 5106e81 commit b9e3ea8
Show file tree
Hide file tree
Showing 22 changed files with 282 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -197,6 +198,9 @@ export const WalletMenuContent: React.FC<WalletMenuContentProps> = ({
</ListItemButton>
)
})}
{/* TODO: show all connected wallets with 'Connected' badge
and have this empty screen only when there is no installed wallets at all */}
{!installedWallets.length ? <WalletMenuContentEmpty /> : null}
</List>
</Fade>
</Collapse>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<Box
sx={{
display: 'flex',
flex: 1,
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
paddingY: 8,
}}
>
<Typography fontSize={48}>
<Wallet fontSize="inherit" />
</Typography>
<Typography fontSize={18} fontWeight={700}>
{t('title.availableWalletsNotFound')}
</Typography>
<Typography
fontSize={14}
color="text.secondary"
textAlign="center"
mt={2}
>
{t('message.availableWalletsNotFound')}
</Typography>
</Box>
)
}
46 changes: 28 additions & 18 deletions packages/wallet-management/src/hooks/useCombinedWallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 4 additions & 2 deletions packages/wallet-management/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
}
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions packages/wallet-management/src/utils/isWalletInstalled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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<HTMLInputElement>,
checked: boolean
) => void = (_, checked) => {
) => {
const walletConfig = checked ? replacementWalletConfig : undefined

setWalletConfig(walletConfig)
}

const handlePartialWalletManagement = (
_: React.ChangeEvent<HTMLInputElement>,
checked: boolean
) => {
const walletConfig = checked
? { ...replacementWalletConfig, usePartialWalletManagement: true }
: replacementWalletConfig

setWalletConfig(walletConfig)
}

return (
<Card>
<CardRowContainer>
Expand All @@ -30,10 +46,22 @@ export const WalletManagementControl = () => {
</CardTitleContainer>
<Switch
checked={isExternalWalletManagement}
onChange={handleSwitchChange}
onChange={handleExternalWalletManagement}
aria-label="Enable external wallet management"
/>
</CardRowContainer>
<Collapse in={isExternalWalletManagement}>
<CardRowContainer>
<CardTitleContainer>
<CardValue>Use partial wallet management</CardValue>
</CardTitleContainer>
<Switch
checked={isPartialWalletManagement}
onChange={handlePartialWalletManagement}
aria-label="Use partial wallet management"
/>
</CardRowContainer>
</Collapse>
</Card>
)
}
3 changes: 3 additions & 0 deletions packages/widget-playground/src/defaultWidgetConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -72,8 +75,36 @@ export const EVMProvider: FC<PropsWithChildren> = ({ children }) => {
reconnectOnMount={Boolean(chains?.length)}
>
<RainbowKitProvider theme={RainbowKitModes[themeMode] as RainbowKitTheme}>
<WidgetWalletConfigUpdater />
{children}
</RainbowKitProvider>
</WagmiProvider>
)
}
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
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { FC, PropsWithChildren } from 'react'
import { EVMProvider } from './EVMProvider'
import { SVMProvider } from './SVMProvider'

interface ExternalWalletProviderProps extends PropsWithChildren {
isExternalProvider?: boolean
Expand All @@ -9,11 +8,5 @@ export const ExternalWalletProvider: FC<ExternalWalletProviderProps> = ({
children,
isExternalProvider,
}) => {
return isExternalProvider ? (
<EVMProvider>
<SVMProvider>{children}</SVMProvider>
</EVMProvider>
) : (
<>{children}</>
)
return isExternalProvider ? <EVMProvider>{children}</EVMProvider> : children
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,11 @@ export const useConfigWalletManagement = () => {

const replacementWalletConfig = defaultWalletConfig
? defaultWalletConfig
: { async onConnect() {} }
: { onConnect: () => {} }

return {
isExternalWalletManagement: !!walletConfig,
isPartialWalletManagement: !!walletConfig?.usePartialWalletManagement,
replacementWalletConfig,
}
}
13 changes: 9 additions & 4 deletions packages/widget/src/components/ChainSelect/useChainSelect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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)
Expand Down
7 changes: 4 additions & 3 deletions packages/widget/src/components/Header/CloseDrawerButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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'

Expand Down
Loading

0 comments on commit b9e3ea8

Please sign in to comment.