From eee87aa1b5e029ff104f38a9598178094c7cd0cd Mon Sep 17 00:00:00 2001 From: Eugene Chybisov <18644653+chybisov@users.noreply.github.com> Date: Fri, 9 Aug 2024 13:34:04 +0200 Subject: [PATCH] feat: optimize wallet sdks handling (#283) --- .../src/connectors/coinbase.ts | 8 +- .../src/connectors/metaMask.ts | 7 + .../wallet-management/src/connectors/types.ts | 6 + .../wallet-management/src/connectors/utils.ts | 13 ++ .../src/connectors/walletConnect.ts | 23 +-- .../src/createDefaultWagmiConfig.ts | 47 +++++- packages/wallet-management/src/icons.ts | 1 + packages/wallet-management/src/index.ts | 3 + .../src/utils/getWalletPriority.ts | 17 +- .../src/utils/isWalletInstalled.ts | 15 ++ .../src/utils/isWalletInstalledAsync.ts | 4 +- packages/widget/src/config/coinbase.ts | 7 + packages/widget/src/config/metaMask.ts | 13 ++ packages/widget/src/config/walletConnect.ts | 6 +- packages/widget/src/hooks/useWallets.ts | 147 ++++++++++++++++++ packages/widget/src/index.ts | 1 + .../SelectWalletPage/EVMListItemButton.tsx | 23 ++- .../SelectWalletPage/SelectWalletPage.tsx | 72 +-------- .../src/pages/SelectWalletPage/utils.ts | 26 ---- .../WalletProvider/EVMBaseProvider.tsx | 16 +- packages/widget/src/types/widget.ts | 34 ++-- 21 files changed, 341 insertions(+), 148 deletions(-) create mode 100644 packages/wallet-management/src/connectors/metaMask.ts create mode 100644 packages/wallet-management/src/connectors/types.ts create mode 100644 packages/wallet-management/src/connectors/utils.ts create mode 100644 packages/widget/src/config/coinbase.ts create mode 100644 packages/widget/src/config/metaMask.ts create mode 100644 packages/widget/src/hooks/useWallets.ts delete mode 100644 packages/widget/src/pages/SelectWalletPage/utils.ts diff --git a/packages/wallet-management/src/connectors/coinbase.ts b/packages/wallet-management/src/connectors/coinbase.ts index 6659cfe28..04a0d8beb 100644 --- a/packages/wallet-management/src/connectors/coinbase.ts +++ b/packages/wallet-management/src/connectors/coinbase.ts @@ -1,6 +1,12 @@ import type { CoinbaseWalletParameters } from '@wagmi/connectors'; import { coinbaseWallet } from '@wagmi/connectors'; +import { extendConnector } from './utils.js'; export const createCoinbaseConnector = /*#__PURE__*/ ( params: CoinbaseWalletParameters, -) => coinbaseWallet(params); +) => + extendConnector( + coinbaseWallet(params), + 'coinbaseWalletSDK', + 'Coinbase Smart Wallet', + ); diff --git a/packages/wallet-management/src/connectors/metaMask.ts b/packages/wallet-management/src/connectors/metaMask.ts new file mode 100644 index 000000000..7339b0bac --- /dev/null +++ b/packages/wallet-management/src/connectors/metaMask.ts @@ -0,0 +1,7 @@ +import type { MetaMaskParameters } from '@wagmi/connectors'; +import { metaMask } from '@wagmi/connectors'; +import { extendConnector } from './utils.js'; + +export const createMetaMaskConnector = /*#__PURE__*/ ( + params: MetaMaskParameters, +) => extendConnector(metaMask(params), 'metaMaskSDK', 'MetaMask'); diff --git a/packages/wallet-management/src/connectors/types.ts b/packages/wallet-management/src/connectors/types.ts new file mode 100644 index 000000000..d68f02d47 --- /dev/null +++ b/packages/wallet-management/src/connectors/types.ts @@ -0,0 +1,6 @@ +import type { CreateConnectorFn } from 'wagmi'; + +export interface CreateConnectorFnExtended extends CreateConnectorFn { + id: string; + displayName: string; +} diff --git a/packages/wallet-management/src/connectors/utils.ts b/packages/wallet-management/src/connectors/utils.ts new file mode 100644 index 000000000..e49581216 --- /dev/null +++ b/packages/wallet-management/src/connectors/utils.ts @@ -0,0 +1,13 @@ +import type { CreateConnectorFn } from 'wagmi'; +import type { CreateConnectorFnExtended } from './types.js'; + +export const extendConnector = ( + connector: CreateConnectorFn, + id: string, + name: string, +): CreateConnectorFnExtended => { + const extendedConnector = connector as CreateConnectorFnExtended; + extendedConnector.id = id; + extendedConnector.displayName = name; + return extendedConnector; +}; diff --git a/packages/wallet-management/src/connectors/walletConnect.ts b/packages/wallet-management/src/connectors/walletConnect.ts index 7fbb66319..1705fcb6e 100644 --- a/packages/wallet-management/src/connectors/walletConnect.ts +++ b/packages/wallet-management/src/connectors/walletConnect.ts @@ -1,15 +1,20 @@ import type { WalletConnectParameters } from '@wagmi/connectors'; -import { walletConnect as _walletConnect } from '@wagmi/connectors'; +import { walletConnect } from '@wagmi/connectors'; +import { extendConnector } from './utils.js'; export const createWalletConnectConnector = /*#__PURE__*/ ( params: WalletConnectParameters, ) => - _walletConnect({ - showQrModal: true, - qrModalOptions: { - themeVariables: { - '--wcm-z-index': '3000', + extendConnector( + walletConnect({ + showQrModal: true, + qrModalOptions: { + themeVariables: { + '--wcm-z-index': '3000', + }, }, - }, - ...params, - }); + ...params, + }), + 'walletConnect', + 'WalletConnect', + ); diff --git a/packages/wallet-management/src/createDefaultWagmiConfig.ts b/packages/wallet-management/src/createDefaultWagmiConfig.ts index f9364e6dc..bb5e22a72 100644 --- a/packages/wallet-management/src/createDefaultWagmiConfig.ts +++ b/packages/wallet-management/src/createDefaultWagmiConfig.ts @@ -1,5 +1,6 @@ import type { CoinbaseWalletParameters, + MetaMaskParameters, WalletConnectParameters, } from '@wagmi/connectors'; import type { Chain, Transport } from 'viem'; @@ -34,7 +35,9 @@ import { trust, xdefi, } from './connectors/connectors.js'; +import { createMetaMaskConnector } from './connectors/metaMask.js'; import { createWalletConnectConnector } from './connectors/walletConnect.js'; +import { isWalletInstalled } from './utils/isWalletInstalled.js'; export type _chains = readonly [Chain, ...Chain[]]; export type _transports = Record<_chains[number]['id'], Transport>; @@ -42,10 +45,15 @@ export type _transports = Record<_chains[number]['id'], Transport>; export interface DefaultWagmiConfigProps { walletConnect?: WalletConnectParameters; coinbase?: CoinbaseWalletParameters; + metaMask?: MetaMaskParameters; wagmiConfig?: { ssr?: boolean; }; connectors?: CreateConnectorFn[]; + /** + * Load Wallet SDKs only if the wallet is the most recently connected wallet + */ + lazy?: boolean; } export interface DefaultWagmiConfigResult { @@ -74,7 +82,6 @@ export interface DefaultWagmiConfigResult { * ); * }; */ - export function createDefaultWagmiConfig( props?: DefaultWagmiConfigProps, ): DefaultWagmiConfigResult { @@ -106,13 +113,6 @@ export function createDefaultWagmiConfig( ...(props?.connectors ?? []), ]; - if (props?.coinbase) { - connectors.unshift(createCoinbaseConnector(props.coinbase)); - } - if (props?.walletConnect) { - connectors.unshift(createWalletConnectConnector(props.walletConnect)); - } - const config = createConfig({ chains: [mainnet], client({ chain }) { @@ -121,6 +121,37 @@ export function createDefaultWagmiConfig( ...props?.wagmiConfig, }); + // Check if WalletConnect properties exist in the props + if (props?.walletConnect) { + // Retrieve the ID of the most recently connected wallet connector from storage + const recentConnectorId = (window as any)?.localStorage.getItem( + `${config.storage?.key}.recentConnectorId`, + ); + // If WalletConnect is the most recently connected wallet or lazy loading is disabled, + // add the WalletConnect connector to the beginning of the connectors list + if (recentConnectorId?.includes?.('walletConnect') || !props.lazy) { + connectors.unshift(createWalletConnectConnector(props.walletConnect)); + } + } + + if (!props?.lazy && props?.coinbase && !isWalletInstalled('coinbase')) { + const recentConnectorId = (window as any)?.localStorage.getItem( + `${config.storage?.key}.recentConnectorId`, + ); + if (recentConnectorId?.includes?.('coinbaseWalletSDK') || !props.lazy) { + connectors.unshift(createCoinbaseConnector(props.coinbase)); + } + } + + if (props?.metaMask && !isWalletInstalled('metaMask')) { + const recentConnectorId = (window as any)?.localStorage.getItem( + `${config.storage?.key}.recentConnectorId`, + ); + if (recentConnectorId?.includes?.('metaMaskSDK') || !props.lazy) { + connectors.unshift(createMetaMaskConnector(props.metaMask)); + } + } + return { config, connectors, diff --git a/packages/wallet-management/src/icons.ts b/packages/wallet-management/src/icons.ts index e27e8eb89..2e49b394d 100644 --- a/packages/wallet-management/src/icons.ts +++ b/packages/wallet-management/src/icons.ts @@ -6,6 +6,7 @@ export const getWalletIcon = (id: string): string | undefined => { return "data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 40 40'%3e %3cpath fill='%231652F0' d='M20 40c11.046 0 20-8.954 20-20S31.046 0 20 0 0 8.954 0 20s8.954 20 20 20Z'/%3e %3cpath fill='white' fill-rule='evenodd' d='M5.455 20c0 8.034 6.512 14.546 14.546 14.546 8.033 0 14.545-6.512 14.545-14.545 0-8.034-6.512-14.546-14.545-14.546-8.034 0-14.546 6.512-14.546 14.546Zm11.859-4.685a2 2 0 0 0-2 2v5.373a2 2 0 0 0 2 2h5.373a2 2 0 0 0 2-2v-5.373a2 2 0 0 0-2-2h-5.373Z' clip-rule='evenodd'/%3e %3c/svg%3e"; case 'safe': return "data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 661.62 661.47'%3e %3cpath fill='%2312ff80' d='M531.98 330.7h-49.42c-14.76 0-26.72 11.96-26.72 26.72v71.73c0 14.76-11.96 26.72-26.72 26.72H232.51c-14.76 0-26.72 11.96-26.72 26.72v49.42c0 14.76 11.96 26.72 26.72 26.72H440.5c14.76 0 26.55-11.96 26.55-26.72v-39.65c0-14.76 11.96-25.23 26.72-25.23h38.2c14.76 0 26.72-11.96 26.72-26.72v-83.3c0-14.76-11.96-26.41-26.72-26.41Zm-326.2-98.18c0-14.76 11.96-26.72 26.72-26.72h196.49c14.76 0 26.72-11.96 26.72-26.72v-49.42c0-14.76-11.96-26.72-26.72-26.72H221.11c-14.76 0-26.72 11.96-26.72 26.72v38.08c0 14.76-11.96 26.72-26.72 26.72h-38.03c-14.76 0-26.72 11.96-26.72 26.72v83.39c0 14.76 12.01 26.12 26.77 26.12h49.42c14.76 0 26.72-11.96 26.72-26.72l-.05-71.44Zm101.77 46.23h47.47c15.47 0 28.02 12.56 28.02 28.02v47.47c0 15.47-12.56 28.02-28.02 28.02h-47.47c-15.47 0-28.02-12.56-28.02-28.02v-47.47c0-15.47 12.56-28.02 28.02-28.02Z'/%3e %3c/svg%3e"; + case 'metaMaskSDK': case 'io.metamask': return "data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' fill='none'%3e %3cpath fill='%23E17726' stroke='%23E17726' stroke-linecap='round' stroke-linejoin='round' stroke-width='.303' d='m26.815 5-9.38 6.939 1.749-4.09z'/%3e %3cpath fill='%23E27625' stroke='%23E27625' stroke-linecap='round' stroke-linejoin='round' stroke-width='.303' d='m5.185 5 9.295 6.996-1.664-4.14zM23.44 21.083l-2.499 3.812 5.347 1.47 1.527-5.196zm-19.246.086 1.52 5.197 5.333-1.47-2.484-3.813z'/%3e %3cpath fill='%23E27625' stroke='%23E27625' stroke-linecap='round' stroke-linejoin='round' stroke-width='.303' d='m10.76 14.645-1.484 2.241 5.282.243-.171-5.69-3.626 3.213zm10.48.007-3.684-3.284-.12 5.761 5.282-.243zM11.046 24.896l3.205-1.542-2.755-2.142zm6.71-1.55 3.184 1.55-.428-3.69z'/%3e %3cpath fill='%23D5BFB2' stroke='%23D5BFB2' stroke-linecap='round' stroke-linejoin='round' stroke-width='.303' d='m20.94 24.896-3.184-1.543.257 2.07-.029.879zm-9.895 0 2.97 1.406-.021-.878.257-2.07z'/%3e %3cpath fill='%23233447' stroke='%23233447' stroke-linecap='round' stroke-linejoin='round' stroke-width='.303' d='m14.065 19.834-2.64-.771 1.87-.857.777 1.628zm3.862 0 .786-1.635 1.877.857-2.663.785z'/%3e %3cpath fill='%23CC6228' stroke='%23CC6228' stroke-linecap='round' stroke-linejoin='round' stroke-width='.303' d='m11.046 24.895.464-3.812-2.948.086zm9.444-3.812.45 3.812 2.499-3.726zm2.235-4.197-5.283.242.486 2.713.785-1.642 1.877.857zm-11.3 2.177 1.87-.857.778 1.628.492-2.713-5.282-.235z'/%3e %3cpath fill='%23E27525' stroke='%23E27525' stroke-linecap='round' stroke-linejoin='round' stroke-width='.303' d='m9.283 16.886 2.213 4.319-.072-2.142zm11.307 2.177-.085 2.142 2.213-4.32zm-6.025-1.934-.5 2.712.629 3.198.143-4.219zm2.87 0-.257 1.684.128 4.226.622-3.205-.493-2.713z'/%3e %3cpath fill='%23F5841F' stroke='%23F5841F' stroke-linecap='round' stroke-linejoin='round' stroke-width='.303' d='m17.928 19.834-.621 3.206.45.314 2.748-2.142.085-2.148zm-6.504-.77.072 2.141 2.755 2.142.443-.307-.621-3.206-2.656-.77z'/%3e %3cpath fill='%23C0AC9D' stroke='%23C0AC9D' stroke-linecap='round' stroke-linejoin='round' stroke-width='.303' d='m17.991 26.302.022-.878-.243-.2h-3.54l-.236.2.021.878-2.97-1.407 1.043.857 2.106 1.45h3.605l2.113-1.457 1.027-.85-2.955 1.407z'/%3e %3cpath fill='%23161616' stroke='%23161616' stroke-linecap='round' stroke-linejoin='round' stroke-width='.303' d='m17.756 23.345-.45-.307h-2.613l-.442.314-.257 2.07.236-.2h3.54l.243.2-.257-2.07z'/%3e %3cpath fill='%23763E1A' stroke='%23763E1A' stroke-linecap='round' stroke-linejoin='round' stroke-width='.303' d='M27.215 12.389 28 8.569 26.815 5l-9.066 6.71 3.49 2.934 4.926 1.435 1.085-1.263-.47-.343.749-.685-.571-.443.75-.571-.5-.385zM4 8.562l.8 3.827-.514.378.763.57-.57.45.742.686-.471.343 1.085 1.263 4.925-1.428 3.491-2.948L5.185 5z'/%3e %3cpath fill='%23F5841F' stroke='%23F5841F' stroke-linecap='round' stroke-linejoin='round' stroke-width='.303' d='m26.166 16.078-4.926-1.427 1.485 2.234-2.213 4.319 2.927-.036h4.375zM10.76 14.644l-4.925 1.434-1.642 5.09h4.376l2.927.036-2.213-4.319 1.485-2.241zm6.675 2.484.321-5.425 1.428-3.855h-6.368l1.428 3.855.321 5.425.122 1.699v4.212h2.62l.014-4.212z'/%3e %3c/svg%3e"; default: diff --git a/packages/wallet-management/src/index.ts b/packages/wallet-management/src/index.ts index d40b4eee1..9074adc2a 100644 --- a/packages/wallet-management/src/index.ts +++ b/packages/wallet-management/src/index.ts @@ -1,6 +1,9 @@ export * from './connectors/coinbase.js'; export * from './connectors/connectors.js'; +export * from './connectors/metaMask.js'; export * from './connectors/safe.js'; +export * from './connectors/types.js'; +export * from './connectors/utils.js'; export * from './connectors/walletConnect.js'; export * from './createDefaultWagmiConfig.js'; export * from './icons.js'; diff --git a/packages/wallet-management/src/utils/getWalletPriority.ts b/packages/wallet-management/src/utils/getWalletPriority.ts index 61517055c..1596c5bda 100644 --- a/packages/wallet-management/src/utils/getWalletPriority.ts +++ b/packages/wallet-management/src/utils/getWalletPriority.ts @@ -1,12 +1,15 @@ const walletPriority: Record = { + metaMaskSDK: 1, 'io.metamask': 1, - walletConnect: 2, - tokenpocket: 3, - safepal: 4, - '1inch': 5, - safe: 6, - okx: 7, - coinbaseWalletSDK: 8, + 'io.metamask.mobile': 1, + coinbaseWalletSDK: 2, + 'com.coinbase.wallet': 2, + walletConnect: 3, + tokenpocket: 4, + safepal: 5, + '1inch': 6, + safe: 7, + okx: 8, bitget: 9, }; diff --git a/packages/wallet-management/src/utils/isWalletInstalled.ts b/packages/wallet-management/src/utils/isWalletInstalled.ts index 2ec217f2f..0d507dd86 100644 --- a/packages/wallet-management/src/utils/isWalletInstalled.ts +++ b/packages/wallet-management/src/utils/isWalletInstalled.ts @@ -1,5 +1,20 @@ export const isWalletInstalled = (id: string): boolean => { switch (id) { + case 'metaMask': + return ( + (window as any)?.ethereum?.isMetaMask || + (window as any)?.ethereum?.providers?.some( + (provider: any) => provider.isMetaMask, + ) + ); + case 'coinbase': + return ( + (window as any)?.ethereum?.isCoinbaseWallet || + (window as any)?.coinbaseWalletExtension?.isCoinbaseWallet || + (window as any)?.ethereum?.providers?.some( + (provider: any) => provider.isCoinbaseWallet, + ) + ); case 'gate': return (window as any)?.gatewallet; case 'bitget': diff --git a/packages/wallet-management/src/utils/isWalletInstalledAsync.ts b/packages/wallet-management/src/utils/isWalletInstalledAsync.ts index e3b626de0..dc7fa637d 100644 --- a/packages/wallet-management/src/utils/isWalletInstalledAsync.ts +++ b/packages/wallet-management/src/utils/isWalletInstalledAsync.ts @@ -3,7 +3,7 @@ import { isWalletInstalled } from './isWalletInstalled.js'; export const isWalletInstalledAsync = async (id: string): Promise => { switch (id) { case 'safe': { - // in Multisig env, window.parent is not equal to window + // In Safe iframe env, window.parent is not equal to window const isIFrameEnvironment = window?.parent !== window; if (!isIFrameEnvironment) { @@ -17,7 +17,7 @@ export const isWalletInstalledAsync = async (id: string): Promise => { try { const accountInfo = await Promise.race([ sdk.safe.getInfo(), - new Promise((resolve) => setTimeout(resolve, 200)), + new Promise((resolve) => setTimeout(resolve, 500)), ]); return !!accountInfo?.safeAddress; diff --git a/packages/widget/src/config/coinbase.ts b/packages/widget/src/config/coinbase.ts new file mode 100644 index 000000000..5f3865910 --- /dev/null +++ b/packages/widget/src/config/coinbase.ts @@ -0,0 +1,7 @@ +import type { CoinbaseWalletParameters } from 'wagmi/connectors'; +import { LiFiToolLogo } from '../icons/lifi.js'; + +export const defaultCoinbaseConfig: CoinbaseWalletParameters = { + appName: 'LI.FI', + appLogoUrl: LiFiToolLogo, +}; diff --git a/packages/widget/src/config/metaMask.ts b/packages/widget/src/config/metaMask.ts new file mode 100644 index 000000000..97c30b534 --- /dev/null +++ b/packages/widget/src/config/metaMask.ts @@ -0,0 +1,13 @@ +import type { MetaMaskParameters } from 'wagmi/connectors'; +import { LiFiToolLogo } from '../icons/lifi.js'; + +export const defaultMetaMaskConfig: MetaMaskParameters = { + dappMetadata: { + name: 'LI.FI', + url: + typeof window !== 'undefined' + ? (window as any)?.location.href + : 'https://li.fi/', + base64Icon: LiFiToolLogo, + }, +}; diff --git a/packages/widget/src/config/walletConnect.ts b/packages/widget/src/config/walletConnect.ts index 8f0643382..375c34b0e 100644 --- a/packages/widget/src/config/walletConnect.ts +++ b/packages/widget/src/config/walletConnect.ts @@ -1 +1,5 @@ -export const defaultWalletConnectProjectId = '5432e3507d41270bee46b7b85bbc2ef8'; +import type { WalletConnectParameters } from 'wagmi/connectors'; + +export const defaultWalletConnectConfig: WalletConnectParameters = { + projectId: '5432e3507d41270bee46b7b85bbc2ef8', +}; 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/EVMListItemButton.tsx b/packages/widget/src/pages/SelectWalletPage/EVMListItemButton.tsx index a594a174a..57472700a 100644 --- a/packages/widget/src/pages/SelectWalletPage/EVMListItemButton.tsx +++ b/packages/widget/src/pages/SelectWalletPage/EVMListItemButton.tsx @@ -1,4 +1,5 @@ import { ChainType } from '@lifi/sdk'; +import type { CreateConnectorFnExtended } from '@lifi/wallet-management'; import { getConnectorIcon, isWalletInstalledAsync, @@ -14,7 +15,7 @@ import { WidgetEvent } from '../../types/events.js'; interface EVMListItemButtonProps { connectedConnector?: Connector; - connector: Connector; + connector: CreateConnectorFnExtended | Connector; onNotInstalled(connector: Connector): void; } @@ -29,9 +30,11 @@ export const EVMListItemButton = ({ const { disconnectAsync } = useDisconnect(); const handleEVMConnect = async () => { - const identityCheckPassed = await isWalletInstalledAsync(connector.id); + const identityCheckPassed = await isWalletInstalledAsync( + (connector as Connector).id, + ); if (!identityCheckPassed) { - onNotInstalled(connector); + onNotInstalled(connector as Connector); return; } if (connectedConnector) { @@ -52,14 +55,20 @@ export const EVMListItemButton = ({ navigateBack(); }; + const connectorName: string = + (connector as CreateConnectorFnExtended).displayName || connector.name; + return ( - + - - {connector.name[0]} + + {connectorName?.[0]} - + ); }; diff --git a/packages/widget/src/pages/SelectWalletPage/SelectWalletPage.tsx b/packages/widget/src/pages/SelectWalletPage/SelectWalletPage.tsx index 487ff5aec..1f4f972ea 100644 --- a/packages/widget/src/pages/SelectWalletPage/SelectWalletPage.tsx +++ b/packages/widget/src/pages/SelectWalletPage/SelectWalletPage.tsx @@ -1,47 +1,34 @@ -import { ChainType } from '@lifi/sdk'; -import { 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 { 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 } = useWidgetConfig(); + 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, @@ -56,52 +43,7 @@ export const SelectWalletPage = () => { }); }, []); - const wallets = useMemo(() => { - const evmInstalled = isItemAllowed(ChainType.EVM, chains?.types) - ? connectors.filter( - (connector) => - isWalletInstalled(connector.id) && - // We should not show already connected connectors - account.connector?.id !== connector.id, - ) - : []; - const evmNotDetected = isItemAllowed(ChainType.EVM, chains?.types) - ? connectors.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, - ]); + const wallets = useWallets(walletConfig, chains); return ( @@ -114,9 +56,9 @@ export const SelectWalletPage = () => { }} > {wallets?.map((connector) => - (connector as Connector).uid ? ( + (connector as Connector).id ? ( { - 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/providers/WalletProvider/EVMBaseProvider.tsx b/packages/widget/src/providers/WalletProvider/EVMBaseProvider.tsx index f6c70c96f..00e9bd0c0 100644 --- a/packages/widget/src/providers/WalletProvider/EVMBaseProvider.tsx +++ b/packages/widget/src/providers/WalletProvider/EVMBaseProvider.tsx @@ -5,9 +5,10 @@ import { } from '@lifi/wallet-management'; import { useRef, type FC, type PropsWithChildren } from 'react'; import { WagmiProvider } from 'wagmi'; -import { defaultWalletConnectProjectId } from '../../config/walletConnect.js'; +import { defaultCoinbaseConfig } from '../../config/coinbase.js'; +import { defaultMetaMaskConfig } from '../../config/metaMask.js'; +import { defaultWalletConnectConfig } from '../../config/walletConnect.js'; import { useAvailableChains } from '../../hooks/useAvailableChains.js'; -import { LiFiToolLogo } from '../../icons/lifi.js'; import { useWidgetConfig } from '../WidgetProvider/WidgetProvider.js'; export const EVMBaseProvider: FC = ({ children }) => { @@ -17,16 +18,13 @@ export const EVMBaseProvider: FC = ({ children }) => { if (!wagmi.current) { wagmi.current = createDefaultWagmiConfig({ - walletConnect: walletConfig?.walletConnect ?? { - projectId: defaultWalletConnectProjectId, - }, - coinbase: walletConfig?.coinbase ?? { - appName: 'LI.FI', - appLogoUrl: LiFiToolLogo, - }, + coinbase: walletConfig?.coinbase ?? defaultCoinbaseConfig, + metaMask: walletConfig?.metaMask ?? defaultMetaMaskConfig, + walletConnect: walletConfig?.walletConnect ?? defaultWalletConnectConfig, wagmiConfig: { ssr: true, }, + lazy: true, }); } diff --git a/packages/widget/src/types/widget.ts b/packages/widget/src/types/widget.ts index 7ff4e5898..c838da701 100644 --- a/packages/widget/src/types/widget.ts +++ b/packages/widget/src/types/widget.ts @@ -18,6 +18,7 @@ import type { import type { TypographyOptions } from '@mui/material/styles/createTypography.js'; import type { CoinbaseWalletParameters, + MetaMaskParameters, WalletConnectParameters, } from '@wagmi/connectors'; import type { CSSProperties, ReactNode, RefObject } from 'react'; @@ -96,6 +97,7 @@ export interface WidgetWalletConfig { onConnect?(): void; walletConnect?: WalletConnectParameters; coinbase?: CoinbaseWalletParameters; + metaMask?: MetaMaskParameters; } export interface WidgetSDKConfig @@ -153,6 +155,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; @@ -201,19 +219,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; }