From 5e300f7966b2aeaee9df98758105402f224e0ade Mon Sep 17 00:00:00 2001 From: Eugene Chybisov Date: Tue, 6 Aug 2024 17:30:26 +0200 Subject: [PATCH] feat: add useWallets hook --- packages/widget/src/hooks/useWallets.ts | 147 ++++++++++++++++++ packages/widget/src/index.ts | 1 + .../SelectWalletPage/SelectWalletPage.tsx | 115 +------------- .../src/pages/SelectWalletPage/utils.ts | 27 ---- packages/widget/src/types/widget.ts | 32 ++-- 5 files changed, 171 insertions(+), 151 deletions(-) create mode 100644 packages/widget/src/hooks/useWallets.ts delete mode 100644 packages/widget/src/pages/SelectWalletPage/utils.ts diff --git a/packages/widget/src/hooks/useWallets.ts b/packages/widget/src/hooks/useWallets.ts new file mode 100644 index 000000000..4c492d7a0 --- /dev/null +++ b/packages/widget/src/hooks/useWallets.ts @@ -0,0 +1,147 @@ +import { ChainType } from '@lifi/sdk'; +import type { CreateConnectorFnExtended } from '@lifi/wallet-management'; +import { + createCoinbaseConnector, + createMetaMaskConnector, + createWalletConnectConnector, + getWalletPriority, + isWalletInstalled, +} from '@lifi/wallet-management'; +import type { Theme } from '@mui/material'; +import { useMediaQuery } from '@mui/material'; +import { WalletReadyState } from '@solana/wallet-adapter-base'; +import type { Wallet } from '@solana/wallet-adapter-react'; +import { useWallet } from '@solana/wallet-adapter-react'; +import { useMemo } from 'react'; +import type { Connector } from 'wagmi'; +import { useConnect, useAccount as useWagmiAccount } from 'wagmi'; +import { defaultCoinbaseConfig } from '../config/coinbase.js'; +import { defaultMetaMaskConfig } from '../config/metaMask.js'; +import { defaultWalletConnectConfig } from '../config/walletConnect.js'; +import type { WidgetChains, WidgetWalletConfig } from '../types/widget.js'; +import { isItemAllowed } from '../utils/item.js'; + +export const useWallets = ( + walletConfig?: WidgetWalletConfig, + chains?: WidgetChains, +) => { + const account = useWagmiAccount(); + const { connectors } = useConnect(); + const { wallets: solanaWallets } = useWallet(); + + const isDesktopView = useMediaQuery((theme: Theme) => + theme.breakpoints.up('sm'), + ); + + const wallets = useMemo(() => { + const evmConnectors: (CreateConnectorFnExtended | Connector)[] = + Array.from(connectors); + if ( + !connectors.some((connector) => + connector.id.toLowerCase().includes('walletconnect'), + ) + ) { + evmConnectors.unshift( + createWalletConnectConnector( + walletConfig?.walletConnect ?? defaultWalletConnectConfig, + ), + ); + } + if ( + !connectors.some((connector) => + connector.id.toLowerCase().includes('coinbase'), + ) && + !isWalletInstalled('coinbase') + ) { + evmConnectors.unshift( + createCoinbaseConnector( + walletConfig?.coinbase ?? defaultCoinbaseConfig, + ), + ); + } + if ( + !connectors.some((connector) => + connector.id.toLowerCase().includes('metamask'), + ) && + !isWalletInstalled('metaMask') + ) { + evmConnectors.unshift( + createMetaMaskConnector( + walletConfig?.metaMask ?? defaultMetaMaskConfig, + ), + ); + } + const evmInstalled = isItemAllowed(ChainType.EVM, chains?.types) + ? evmConnectors.filter( + (connector) => + isWalletInstalled(connector.id!) && + // We should not show already connected connectors + account.connector?.id !== connector.id, + ) + : []; + const evmNotDetected = isItemAllowed(ChainType.EVM, chains?.types) + ? evmConnectors.filter((connector) => !isWalletInstalled(connector.id!)) + : []; + const svmInstalled = isItemAllowed(ChainType.SVM, chains?.types) + ? solanaWallets?.filter( + (connector) => + connector.adapter.readyState === WalletReadyState.Installed && + // We should not show already connected connectors + !connector.adapter.connected, + ) + : []; + const svmNotDetected = isItemAllowed(ChainType.SVM, chains?.types) + ? solanaWallets?.filter( + (connector) => + connector.adapter.readyState !== WalletReadyState.Installed, + ) + : []; + + const installedWallets = [...evmInstalled, ...svmInstalled].sort( + walletComparator, + ); + + if (isDesktopView) { + const notDetectedWallets = [...evmNotDetected, ...svmNotDetected].sort( + walletComparator, + ); + installedWallets.push(...notDetectedWallets); + } + + return installedWallets; + }, [ + account.connector?.id, + chains?.types, + connectors, + isDesktopView, + solanaWallets, + walletConfig?.coinbase, + walletConfig?.metaMask, + walletConfig?.walletConnect, + ]); + + return wallets; +}; + +export const walletComparator = ( + a: CreateConnectorFnExtended | Connector | Wallet, + b: CreateConnectorFnExtended | Connector | Wallet, +) => { + let aId = (a as Connector).id || (a as Wallet).adapter?.name; + let bId = (b as Connector).id || (b as Wallet).adapter?.name; + + const priorityA = getWalletPriority(aId); + const priorityB = getWalletPriority(bId); + + if (priorityA !== priorityB) { + return priorityA - priorityB; + } + + if (aId < bId) { + return -1; + } + if (aId > bId) { + return 1; + } + return 0; +}; diff --git a/packages/widget/src/index.ts b/packages/widget/src/index.ts index 74b54b423..490512ba4 100644 --- a/packages/widget/src/index.ts +++ b/packages/widget/src/index.ts @@ -9,6 +9,7 @@ export * from './components/Skeleton/WidgetSkeleton.js'; export * from './config/version.js'; export { useAccount } from './hooks/useAccount.js'; export { useAvailableChains } from './hooks/useAvailableChains.js'; +export { useWallets, walletComparator } from './hooks/useWallets.js'; export { useWidgetEvents, widgetEvents } from './hooks/useWidgetEvents.js'; export * from './stores/form/types.js'; export { useFieldActions } from './stores/form/useFieldActions.js'; diff --git a/packages/widget/src/pages/SelectWalletPage/SelectWalletPage.tsx b/packages/widget/src/pages/SelectWalletPage/SelectWalletPage.tsx index 9f9042049..1f4f972ea 100644 --- a/packages/widget/src/pages/SelectWalletPage/SelectWalletPage.tsx +++ b/packages/widget/src/pages/SelectWalletPage/SelectWalletPage.tsx @@ -1,56 +1,34 @@ -import { ChainType } from '@lifi/sdk'; -import type { CreateConnectorFnExtended } from '@lifi/wallet-management'; -import { - createCoinbaseConnector, - createMetaMaskConnector, - createWalletConnectConnector, - isWalletInstalled, -} from '@lifi/wallet-management'; -import type { Theme } from '@mui/material'; import { Button, DialogActions, DialogContent, DialogContentText, List, - useMediaQuery, } from '@mui/material'; -import { WalletReadyState } from '@solana/wallet-adapter-base'; import type { Wallet } from '@solana/wallet-adapter-react'; -import { useWallet } from '@solana/wallet-adapter-react'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import type { Connector } from 'wagmi'; -import { useConnect, useAccount as useWagmiAccount } from 'wagmi'; +import { useAccount as useWagmiAccount } from 'wagmi'; import { Dialog } from '../../components/Dialog.js'; import { PageContainer } from '../../components/PageContainer.js'; -import { defaultCoinbaseConfig } from '../../config/coinbase.js'; -import { defaultMetaMaskConfig } from '../../config/metaMask.js'; -import { defaultWalletConnectConfig } from '../../config/walletConnect.js'; import { useHeader } from '../../hooks/useHeader.js'; +import { useWallets } from '../../hooks/useWallets.js'; import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'; -import { isItemAllowed } from '../../utils/item.js'; import { EVMListItemButton } from './EVMListItemButton.js'; import { SVMListItemButton } from './SVMListItemButton.js'; -import { walletComparator } from './utils.js'; export const SelectWalletPage = () => { const { t } = useTranslation(); const { chains, walletConfig } = useWidgetConfig(); const account = useWagmiAccount(); - const { connectors } = useConnect(); const [walletIdentity, setWalletIdentity] = useState<{ show: boolean; connector?: Connector; }>({ show: false }); - const { wallets: solanaWallets } = useWallet(); useHeader(t(`header.selectWallet`)); - const isDesktopView = useMediaQuery((theme: Theme) => - theme.breakpoints.up('sm'), - ); - const closeDialog = () => { setWalletIdentity((state) => ({ ...state, @@ -65,92 +43,7 @@ export const SelectWalletPage = () => { }); }, []); - const wallets = useMemo(() => { - const evmConnectors: (CreateConnectorFnExtended | Connector)[] = - Array.from(connectors); - if ( - !connectors.some((connector) => - connector.id.toLowerCase().includes('walletconnect'), - ) - ) { - evmConnectors.unshift( - createWalletConnectConnector( - walletConfig?.walletConnect ?? defaultWalletConnectConfig, - ), - ); - } - if ( - !connectors.some((connector) => - connector.id.toLowerCase().includes('coinbase'), - ) && - !isWalletInstalled('coinbase') - ) { - evmConnectors.unshift( - createCoinbaseConnector( - walletConfig?.coinbase ?? defaultCoinbaseConfig, - ), - ); - } - if ( - !connectors.some((connector) => - connector.id.toLowerCase().includes('metamask'), - ) && - !isWalletInstalled('metaMask') - ) { - evmConnectors.unshift( - createMetaMaskConnector( - walletConfig?.metaMask ?? defaultMetaMaskConfig, - ), - ); - } - const evmInstalled = isItemAllowed(ChainType.EVM, chains?.types) - ? evmConnectors.filter( - (connector) => - isWalletInstalled(connector.id!) && - // We should not show already connected connectors - account.connector?.id !== connector.id, - ) - : []; - const evmNotDetected = isItemAllowed(ChainType.EVM, chains?.types) - ? evmConnectors.filter((connector) => !isWalletInstalled(connector.id!)) - : []; - const svmInstalled = isItemAllowed(ChainType.SVM, chains?.types) - ? solanaWallets?.filter( - (connector) => - connector.adapter.readyState === WalletReadyState.Installed && - // We should not show already connected connectors - !connector.adapter.connected, - ) - : []; - const svmNotDetected = isItemAllowed(ChainType.SVM, chains?.types) - ? solanaWallets?.filter( - (connector) => - connector.adapter.readyState !== WalletReadyState.Installed, - ) - : []; - - const installedWallets = [...evmInstalled, ...svmInstalled].sort( - walletComparator, - ); - - if (isDesktopView) { - const notDetectedWallets = [...evmNotDetected, ...svmNotDetected].sort( - walletComparator, - ); - installedWallets.push(...notDetectedWallets); - } - - return installedWallets; - }, [ - account.connector?.id, - chains?.types, - connectors, - isDesktopView, - solanaWallets, - walletConfig?.coinbase, - walletConfig?.metaMask, - walletConfig?.walletConnect, - ]); + const wallets = useWallets(walletConfig, chains); return ( diff --git a/packages/widget/src/pages/SelectWalletPage/utils.ts b/packages/widget/src/pages/SelectWalletPage/utils.ts deleted file mode 100644 index d75041ff0..000000000 --- a/packages/widget/src/pages/SelectWalletPage/utils.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { CreateConnectorFnExtended } from '@lifi/wallet-management'; -import { getWalletPriority } from '@lifi/wallet-management'; -import type { Wallet } from '@solana/wallet-adapter-react'; -import type { Connector } from 'wagmi'; - -export const walletComparator = ( - a: CreateConnectorFnExtended | Connector | Wallet, - b: CreateConnectorFnExtended | Connector | Wallet, -) => { - let aId = (a as Connector).id || (a as Wallet).adapter?.name; - let bId = (b as Connector).id || (b as Wallet).adapter?.name; - - const priorityA = getWalletPriority(aId); - const priorityB = getWalletPriority(bId); - - if (priorityA !== priorityB) { - return priorityA - priorityB; - } - - if (aId < bId) { - return -1; - } - if (aId > bId) { - return 1; - } - return 0; -}; diff --git a/packages/widget/src/types/widget.ts b/packages/widget/src/types/widget.ts index b6e7ae602..a8905e313 100644 --- a/packages/widget/src/types/widget.ts +++ b/packages/widget/src/types/widget.ts @@ -134,6 +134,22 @@ export interface AllowDeny { deny?: T[]; } +export type WidgetChains = { + from?: AllowDeny; + to?: AllowDeny; + types?: AllowDeny; +} & AllowDeny; + +export type WidgetTokens = { + featured?: StaticToken[]; + include?: Token[]; + popular?: StaticToken[]; +} & AllowDeny; + +export type WidgetLanguages = { + default?: LanguageKey; +} & AllowDeny; + export interface WidgetConfig { fromChain?: number; toChain?: number; @@ -179,19 +195,9 @@ export interface WidgetConfig { bridges?: AllowDeny; exchanges?: AllowDeny; - chains?: { - from?: AllowDeny; - to?: AllowDeny; - types?: AllowDeny; - } & AllowDeny; - tokens?: { - featured?: StaticToken[]; - include?: Token[]; - popular?: StaticToken[]; - } & AllowDeny; - languages?: { - default?: LanguageKey; - } & AllowDeny; + chains?: WidgetChains; + tokens?: WidgetTokens; + languages?: WidgetLanguages; languageResources?: LanguageResources; }