diff --git a/.changeset/quick-moons-fall.md b/.changeset/quick-moons-fall.md new file mode 100644 index 0000000000..d0a5f93ede --- /dev/null +++ b/.changeset/quick-moons-fall.md @@ -0,0 +1,29 @@ +--- +'@reown/appkit-controllers': patch +'@reown/appkit-scaffold-ui': patch +'pay-test-exchange': patch +'@reown/appkit-adapter-bitcoin': patch +'@reown/appkit-adapter-ethers': patch +'@reown/appkit-adapter-ethers5': patch +'@reown/appkit-adapter-solana': patch +'@reown/appkit-adapter-wagmi': patch +'@reown/appkit': patch +'@reown/appkit-utils': patch +'@reown/appkit-cdn': patch +'@reown/appkit-cli': patch +'@reown/appkit-codemod': patch +'@reown/appkit-common': patch +'@reown/appkit-core': patch +'@reown/appkit-experimental': patch +'@reown/appkit-pay': patch +'@reown/appkit-polyfills': patch +'@reown/appkit-siwe': patch +'@reown/appkit-siwx': patch +'@reown/appkit-testing': patch +'@reown/appkit-ui': patch +'@reown/appkit-universal-connector': patch +'@reown/appkit-wallet': patch +'@reown/appkit-wallet-button': patch +--- + +Improved wallet search to filter out non-walletconnect and walletconnect wallets based on platform and SDK type diff --git a/packages/appkit-utils/src/ConstantsUtil.ts b/packages/appkit-utils/src/ConstantsUtil.ts index 65a284e644..a63e988749 100644 --- a/packages/appkit-utils/src/ConstantsUtil.ts +++ b/packages/appkit-utils/src/ConstantsUtil.ts @@ -14,6 +14,7 @@ export const ConstantsUtil = { XVERSE_CONNECTOR_NAME: 'Xverse Wallet', LEATHER_CONNECTOR_NAME: 'Leather', OKX_CONNECTOR_NAME: 'OKX Wallet', + BINANCE_CONNECTOR_NAME: 'Binance Wallet', EIP155: CommonConstantsUtil.CHAIN.EVM, ADD_CHAIN_METHOD: 'wallet_addEthereumChain', diff --git a/packages/appkit-utils/src/PresetsUtil.ts b/packages/appkit-utils/src/PresetsUtil.ts index 23657c8ab6..ae47e16763 100644 --- a/packages/appkit-utils/src/PresetsUtil.ts +++ b/packages/appkit-utils/src/PresetsUtil.ts @@ -42,7 +42,9 @@ export const PresetsUtil = { [ConstantsUtil.LEATHER_CONNECTOR_NAME]: '483afe1df1df63daf313109971ff3ef8356ddf1cc4e45877d205eee0b7893a13', [ConstantsUtil.OKX_CONNECTOR_NAME]: - '971e689d0a5be527bac79629b4ee9b925e82208e5168b733496a09c0faed0709' + '971e689d0a5be527bac79629b4ee9b925e82208e5168b733496a09c0faed0709', + [ConstantsUtil.BINANCE_CONNECTOR_NAME]: + '2fafea35bb471d22889ccb49c08d99dd0a18a37982602c33f696a5723934ba25' } as Record, NetworkImageIds: { // Ethereum diff --git a/packages/controllers/src/utils/TypeUtil.ts b/packages/controllers/src/utils/TypeUtil.ts index 47eb8965f7..080c9bd1f0 100644 --- a/packages/controllers/src/utils/TypeUtil.ts +++ b/packages/controllers/src/utils/TypeUtil.ts @@ -172,6 +172,7 @@ export interface WcWallet { }[] | null display_index?: number + supports_wc?: boolean } export interface ApiGetWalletsRequest { diff --git a/packages/scaffold-ui/src/partials/w3m-all-wallets-list/index.ts b/packages/scaffold-ui/src/partials/w3m-all-wallets-list/index.ts index e8931a6cc5..cd37f172a7 100644 --- a/packages/scaffold-ui/src/partials/w3m-all-wallets-list/index.ts +++ b/packages/scaffold-ui/src/partials/w3m-all-wallets-list/index.ts @@ -123,8 +123,9 @@ export class W3mAllWalletsList extends LitElement { const uniqueWallets = CoreHelperUtil.uniqueBy(wallets, 'id') const walletsWithInstalled = WalletUtil.markWalletsAsInstalled(uniqueWallets) + const walletsByWcSupport = WalletUtil.filterWalletsByWcSupport(walletsWithInstalled) - return WalletUtil.markWalletsWithDisplayIndex(walletsWithInstalled) + return WalletUtil.markWalletsWithDisplayIndex(walletsByWcSupport) } private walletsTemplate() { diff --git a/packages/scaffold-ui/src/partials/w3m-all-wallets-search/index.ts b/packages/scaffold-ui/src/partials/w3m-all-wallets-search/index.ts index 9f4273a166..48afa5a5eb 100644 --- a/packages/scaffold-ui/src/partials/w3m-all-wallets-search/index.ts +++ b/packages/scaffold-ui/src/partials/w3m-all-wallets-search/index.ts @@ -58,9 +58,10 @@ export class W3mAllWalletsSearch extends LitElement { private walletsTemplate() { const { search } = ApiController.state - const wallets = WalletUtil.markWalletsAsInstalled(search) + const markedInstalledWallets = WalletUtil.markWalletsAsInstalled(search) + const walletsByWcSupport = WalletUtil.filterWalletsByWcSupport(markedInstalledWallets) - if (!search.length) { + if (!walletsByWcSupport.length) { return html` - ${wallets.map( + ${walletsByWcSupport.map( (wallet, index) => html` this.onConnectWallet(wallet)} diff --git a/packages/scaffold-ui/src/utils/WalletUtil.ts b/packages/scaffold-ui/src/utils/WalletUtil.ts index f651c3c17d..d55471107d 100644 --- a/packages/scaffold-ui/src/utils/WalletUtil.ts +++ b/packages/scaffold-ui/src/utils/WalletUtil.ts @@ -1,5 +1,7 @@ +import { ConstantsUtil as CommonConstantsUtil } from '@reown/appkit-common' import { ApiController, + ConnectionController, ConnectorController, CoreHelperUtil, OptionsController, @@ -7,10 +9,20 @@ import { } from '@reown/appkit-controllers' import type { ConnectMethod, Connector, Features, WcWallet } from '@reown/appkit-controllers' import { HelpersUtil } from '@reown/appkit-utils' +import { ConstantsUtil as AppKitConstantsUtil, PresetsUtil } from '@reown/appkit-utils' import { ConnectorUtil } from './ConnectorUtil.js' import { ConstantsUtil } from './ConstantsUtil.js' +const MANDATORY_WALLET_IDS_ON_MOBILE = [ + PresetsUtil.ConnectorExplorerIds[CommonConstantsUtil.CONNECTOR_ID.COINBASE], + PresetsUtil.ConnectorExplorerIds[CommonConstantsUtil.CONNECTOR_ID.COINBASE_SDK], + PresetsUtil.ConnectorExplorerIds[CommonConstantsUtil.CONNECTOR_ID.BASE_ACCOUNT], + PresetsUtil.ConnectorExplorerIds[AppKitConstantsUtil.SOLFLARE_CONNECTOR_NAME], + PresetsUtil.ConnectorExplorerIds[AppKitConstantsUtil.PHANTOM_CONNECTOR_NAME], + PresetsUtil.ConnectorExplorerIds[AppKitConstantsUtil.BINANCE_CONNECTOR_NAME] +] + interface AppKitWallet extends WcWallet { installed: boolean } @@ -176,5 +188,29 @@ export const WalletUtil = { }, markWalletsWithDisplayIndex(wallets: WcWallet[]) { return wallets.map((w, index) => ({ ...w, display_index: index })) + }, + + /** + * Filters wallets based on WalletConnect support and platform requirements. + * + * On mobile only wallets with WalletConnect support and some mandatory wallets are shown. + * On desktop with Appkit Core only wallets with WalletConnect support are shown. + * On desktop with Appkit all wallets are shown. + * + * @param wallets - Array of wallets to filter + * @returns Filtered array of wallets based on WalletConnect support and platform + */ + filterWalletsByWcSupport(wallets: WcWallet[]) { + if (ConnectionController.state.wcBasic) { + return wallets.filter(wallet => wallet.supports_wc) + } + + if (CoreHelperUtil.isMobile()) { + return wallets.filter( + wallet => wallet.supports_wc || MANDATORY_WALLET_IDS_ON_MOBILE.includes(wallet.id) + ) + } + + return wallets } } diff --git a/packages/scaffold-ui/test/WalletUtil.test.ts b/packages/scaffold-ui/test/WalletUtil.test.ts index e53c21255a..80f77ad4cf 100644 --- a/packages/scaffold-ui/test/WalletUtil.test.ts +++ b/packages/scaffold-ui/test/WalletUtil.test.ts @@ -1,6 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { + ConnectionController, ConnectorController, CoreHelperUtil, OptionsController, @@ -324,4 +325,52 @@ describe('WalletUtil', () => { ]) }) }) + + describe('filterWalletsByWcSupport', () => { + const walletsWithWcSupport: WcWallet[] = [ + { id: '1', name: 'Wallet 1', supports_wc: true }, + { id: '2', name: 'Wallet 2', supports_wc: false }, + { id: '3', name: 'Wallet 3', supports_wc: true }, + { id: '4', name: 'Wallet 4' } // undefined supports_wc + ] + + beforeEach(() => { + vi.restoreAllMocks() + OptionsController.state.manualWCControl = false + ConnectionController.state.wcBasic = false + }) + + it('should filter out wallets without WC support on mobile', () => { + vi.spyOn(CoreHelperUtil, 'isMobile').mockReturnValue(true) + + const result = WalletUtil.filterWalletsByWcSupport(walletsWithWcSupport) + + expect(result).toEqual([ + { id: '1', name: 'Wallet 1', supports_wc: true }, + { id: '3', name: 'Wallet 3', supports_wc: true } + ]) + }) + + it('should filter out wallets without WC support when using Appkit Core (wcBasic)', () => { + vi.spyOn(CoreHelperUtil, 'isMobile').mockReturnValue(false) + ConnectionController.state.wcBasic = true + + const result = WalletUtil.filterWalletsByWcSupport(walletsWithWcSupport) + + expect(result).toEqual([ + { id: '1', name: 'Wallet 1', supports_wc: true }, + { id: '3', name: 'Wallet 3', supports_wc: true } + ]) + }) + + it('should show all wallets on desktop with Appkit (not Appkit Core)', () => { + vi.spyOn(CoreHelperUtil, 'isMobile').mockReturnValue(false) + OptionsController.state.manualWCControl = false + ConnectionController.state.wcBasic = false + + const result = WalletUtil.filterWalletsByWcSupport(walletsWithWcSupport) + + expect(result).toEqual(walletsWithWcSupport) + }) + }) }) diff --git a/packages/scaffold-ui/test/partials/w3m-all-wallets-list.test.ts b/packages/scaffold-ui/test/partials/w3m-all-wallets-list.test.ts index 53d0bc31e9..2444e9932b 100644 --- a/packages/scaffold-ui/test/partials/w3m-all-wallets-list.test.ts +++ b/packages/scaffold-ui/test/partials/w3m-all-wallets-list.test.ts @@ -6,7 +6,9 @@ import { html } from 'lit' import { ApiController, ChainController, + ConnectionController, ConnectorController, + CoreHelperUtil, OptionsController, RouterController } from '@reown/appkit-controllers' @@ -248,4 +250,68 @@ describe('W3mAllWalletsList', () => { expect(disconnectSpy).toHaveBeenCalled() expect(unsubscribeSpy).toHaveBeenCalled() }) + + it('filters wallets by WC support on mobile', async () => { + const walletsWithMixedSupport: WcWallet[] = [ + { id: '1', name: 'Wallet 1', supports_wc: true }, + { id: '2', name: 'Wallet 2', supports_wc: false }, + { id: '3', name: 'Wallet 3', supports_wc: true } + ] + + vi.spyOn(ApiController, 'state', 'get').mockReturnValue({ + ...ApiController.state, + wallets: walletsWithMixedSupport, + recommended: [], + featured: [], + count: 3, + page: 1 + }) + + vi.spyOn(CoreHelperUtil, 'isMobile').mockReturnValue(true) + vi.spyOn(ConnectorController.state, 'connectors', 'get').mockReturnValue([]) + + const element: W3mAllWalletsList = await fixture( + html`` + ) + await elementUpdated(element) + + const walletItems = element.shadowRoot?.querySelectorAll('w3m-all-wallets-list-item') + expect(walletItems?.length).toBe(2) + }) + + it('shows all wallets on desktop with Appkit', async () => { + const walletsWithMixedSupport: WcWallet[] = [ + { id: '1', name: 'Wallet 1', supports_wc: true }, + { id: '2', name: 'Wallet 2', supports_wc: false }, + { id: '3', name: 'Wallet 3', supports_wc: true } + ] + + vi.spyOn(ApiController, 'state', 'get').mockReturnValue({ + ...ApiController.state, + wallets: walletsWithMixedSupport, + recommended: [], + featured: [], + count: 3, + page: 1 + }) + + vi.spyOn(CoreHelperUtil, 'isMobile').mockReturnValue(false) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + manualWCControl: false + }) + vi.spyOn(ConnectionController, 'state', 'get').mockReturnValue({ + ...ConnectionController.state, + wcBasic: false + }) + vi.spyOn(ConnectorController.state, 'connectors', 'get').mockReturnValue([]) + + const element: W3mAllWalletsList = await fixture( + html`` + ) + await elementUpdated(element) + + const walletItems = element.shadowRoot?.querySelectorAll('w3m-all-wallets-list-item') + expect(walletItems?.length).toBe(3) + }) }) diff --git a/packages/scaffold-ui/test/partials/w3m-all-wallets-search.test.ts b/packages/scaffold-ui/test/partials/w3m-all-wallets-search.test.ts index eb85e6d7f8..5c29227d05 100644 --- a/packages/scaffold-ui/test/partials/w3m-all-wallets-search.test.ts +++ b/packages/scaffold-ui/test/partials/w3m-all-wallets-search.test.ts @@ -7,8 +7,10 @@ import { ApiController, type ApiControllerState, type BadgeType, + ConnectionController, ConnectorController, type ConnectorWithProviders, + CoreHelperUtil, OptionsController, RouterController, type WcWallet @@ -221,4 +223,56 @@ describe('W3mAllWalletsSearch', () => { expect(el.getAttribute('data-mobile-fullscreen')).toBeNull() }) + + it('should filter search results by WC support on mobile', async () => { + const mockSearchResults: WcWallet[] = [ + { id: '1', name: 'Mobile Wallet', supports_wc: true }, + { id: '2', name: 'Desktop Only Wallet', supports_wc: false }, + { id: '3', name: 'Universal Wallet', supports_wc: true } + ] + + vi.spyOn(ApiController, 'state', 'get').mockReturnValue({ + ...ApiController.state, + search: mockSearchResults + }) + vi.spyOn(CoreHelperUtil, 'isMobile').mockReturnValue(true) + vi.spyOn(ConnectorController.state, 'connectors', 'get').mockReturnValue([]) + + // @ts-expect-error - Accessing private property for testing + element.query = 'wallet' + await elementUpdated(element) + + const walletItems = element.shadowRoot?.querySelectorAll('[data-testid^="wallet-search-item"]') + expect(walletItems?.length).toBe(2) + }) + + it('should show all search results on desktop with Appkit', async () => { + const mockSearchResults: WcWallet[] = [ + { id: '1', name: 'Mobile Wallet', supports_wc: true }, + { id: '2', name: 'Desktop Only Wallet', supports_wc: false }, + { id: '3', name: 'Universal Wallet', supports_wc: true } + ] + + vi.spyOn(ApiController, 'state', 'get').mockReturnValue({ + ...ApiController.state, + search: mockSearchResults + }) + vi.spyOn(CoreHelperUtil, 'isMobile').mockReturnValue(false) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + manualWCControl: false + }) + vi.spyOn(ConnectionController, 'state', 'get').mockReturnValue({ + ...ConnectionController.state, + wcBasic: false + }) + vi.spyOn(ConnectorController.state, 'connectors', 'get').mockReturnValue([]) + + // @ts-expect-error - Accessing private property for testing + element.query = 'wallet' + await elementUpdated(element) + + const walletItems = element.shadowRoot?.querySelectorAll('[data-testid^="wallet-search-item"]') + expect(walletItems?.length).toBe(3) + }) })