diff --git a/.changeset/fix-injected-eip6963-filtering.md b/.changeset/fix-injected-eip6963-filtering.md new file mode 100644 index 0000000000..ac94ecfe02 --- /dev/null +++ b/.changeset/fix-injected-eip6963-filtering.md @@ -0,0 +1,7 @@ +--- +'@reown/appkit': patch +'@reown/appkit-controllers': patch +'@reown/appkit-adapter-wagmi': patch +--- + +Fix injected and EIP6963 wallets showing despite being disabled and appearing duplicated. `enableInjected` is now wired through to the controller state, the wagmi adapter no longer conflates the basic injected connector with EIP6963-discovered ones, featured/recommended wallets are deduped against connectors regardless of the `enableEIP6963` flag, and `includeWalletIds`/`excludeWalletIds` now also filter injected and EIP6963 connectors in the connect view. diff --git a/packages/adapters/wagmi/src/client.ts b/packages/adapters/wagmi/src/client.ts index 745f07a8bd..1e6989d9ca 100644 --- a/packages/adapters/wagmi/src/client.ts +++ b/packages/adapters/wagmi/src/client.ts @@ -502,11 +502,18 @@ export class WagmiAdapter extends AdapterBlueprint { * from wagmi since we already set it in chain adapter blueprint */ - const { enableEIP6963: isEIP6963Enabled } = OptionsController.state || {} - if ( - connector.type === CommonConstantsUtil.CONNECTOR_ID.INJECTED && - isEIP6963Enabled === false - ) { + const { enableEIP6963: isEIP6963Enabled, enableInjected: isInjectedEnabled } = + OptionsController.state || {} + + const isInjectedType = connector.type === CommonConstantsUtil.CONNECTOR_ID.INJECTED + const isBasicInjected = connector.id === CommonConstantsUtil.CONNECTOR_ID.INJECTED + const isEip6963Connector = isInjectedType && !isBasicInjected + + if (isBasicInjected && isInjectedEnabled === false) { + return + } + + if (isEip6963Connector && isEIP6963Enabled === false) { return } diff --git a/packages/adapters/wagmi/src/tests/client.test.ts b/packages/adapters/wagmi/src/tests/client.test.ts index 3345fd07f6..02c587fbdb 100644 --- a/packages/adapters/wagmi/src/tests/client.test.ts +++ b/packages/adapters/wagmi/src/tests/client.test.ts @@ -452,6 +452,76 @@ describe('WagmiAdapter', () => { }) ) }) + + it('should skip basic injected connector when enableInjected is false', async () => { + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + enableInjected: false, + enableEIP6963: true + }) + + const addConnectorSpy = vi.spyOn(adapter as any, 'addConnector') + + const basicInjected = { + id: 'injected', + name: 'Browser Wallet', + type: 'injected', + getProvider() { + return Promise.resolve({ connect: vi.fn(), request: vi.fn() }) + } + } as unknown as wagmiCore.Connector + + await (adapter as any).addWagmiConnector(basicInjected) + + expect(addConnectorSpy).not.toHaveBeenCalled() + }) + + it('should keep basic injected connector when enableEIP6963 is false but enableInjected is true', async () => { + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + enableInjected: true, + enableEIP6963: false + }) + + const addConnectorSpy = vi.spyOn(adapter as any, 'addConnector') + + const basicInjected = { + id: 'injected', + name: 'Browser Wallet', + type: 'injected', + getProvider() { + return Promise.resolve({ connect: vi.fn(), request: vi.fn() }) + } + } as unknown as wagmiCore.Connector + + await (adapter as any).addWagmiConnector(basicInjected) + + expect(addConnectorSpy).toHaveBeenCalled() + }) + + it('should skip EIP6963-discovered connector when enableEIP6963 is false', async () => { + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + enableInjected: true, + enableEIP6963: false + }) + + const addConnectorSpy = vi.spyOn(adapter as any, 'addConnector') + + const eip6963Connector = { + id: 'io.metamask', + name: 'MetaMask', + type: 'injected', + info: { rdns: 'io.metamask' }, + getProvider() { + return Promise.resolve({ connect: vi.fn(), request: vi.fn() }) + } + } as unknown as wagmiCore.Connector + + await (adapter as any).addWagmiConnector(eip6963Connector) + + expect(addConnectorSpy).not.toHaveBeenCalled() + }) }) describe('WagmiAdapter - signMessage', () => { diff --git a/packages/appkit/src/client/appkit-base-client.ts b/packages/appkit/src/client/appkit-base-client.ts index 5e8f855f9a..ab92f14e9e 100644 --- a/packages/appkit/src/client/appkit-base-client.ts +++ b/packages/appkit/src/client/appkit-base-client.ts @@ -432,6 +432,7 @@ export abstract class AppKitBaseClient { OptionsController.setEnableWalletGuide(options.enableWalletGuide !== false) OptionsController.setEnableWallets(options.enableWallets !== false) OptionsController.setEIP6963Enabled(options.enableEIP6963 !== false) + OptionsController.setEnableInjected(options.enableInjected !== false) OptionsController.setEnableCoinbase(options.enableCoinbase !== false) OptionsController.setEnableNetworkSwitch(options.enableNetworkSwitch !== false) OptionsController.setEnableReconnect(options.enableReconnect !== false) diff --git a/packages/controllers/src/controllers/OptionsController.ts b/packages/controllers/src/controllers/OptionsController.ts index f718204e1a..67e12743ac 100644 --- a/packages/controllers/src/controllers/OptionsController.ts +++ b/packages/controllers/src/controllers/OptionsController.ts @@ -348,6 +348,10 @@ export const OptionsController = { state.enableEIP6963 = enableEIP6963 }, + setEnableInjected(enableInjected: OptionsControllerState['enableInjected']) { + state.enableInjected = enableInjected + }, + setEnableCoinbase(enableCoinbase: OptionsControllerState['enableCoinbase']) { state.enableCoinbase = enableCoinbase }, diff --git a/packages/controllers/src/utils/ConnectorUtil.ts b/packages/controllers/src/utils/ConnectorUtil.ts index d4cee20502..914829457b 100644 --- a/packages/controllers/src/utils/ConnectorUtil.ts +++ b/packages/controllers/src/utils/ConnectorUtil.ts @@ -101,6 +101,31 @@ export const ConnectorUtil = { return false } + const { includeWalletIds, excludeWalletIds } = OptionsController.state + const isFilterableConnectorType = + connector.type === 'INJECTED' || + connector.type === 'ANNOUNCED' || + connector.type === 'MULTI_CHAIN' + + if (isFilterableConnectorType) { + const connectorWalletId = connector.explorerId || connector.explorerWallet?.id + + if ( + excludeWalletIds?.length && + connectorWalletId && + excludeWalletIds.includes(connectorWalletId) + ) { + return false + } + + if ( + includeWalletIds?.length && + (!connectorWalletId || !includeWalletIds.includes(connectorWalletId)) + ) { + return false + } + } + return true }, diff --git a/packages/controllers/src/utils/WalletUtil.ts b/packages/controllers/src/utils/WalletUtil.ts index fe5e79aef0..df5a9359a3 100644 --- a/packages/controllers/src/utils/WalletUtil.ts +++ b/packages/controllers/src/utils/WalletUtil.ts @@ -16,9 +16,7 @@ interface AppKitWallet extends WcWallet { export const WalletUtil = { filterOutDuplicatesByRDNS(wallets: WcWallet[]) { - const connectors = OptionsController.state.enableEIP6963 - ? ConnectorController.state.connectors - : [] + const connectors = ConnectorController.state.connectors const recent = StorageUtil.getRecentWallets() const connectorRDNSs = connectors diff --git a/packages/controllers/tests/controllers/OptionsController.test.ts b/packages/controllers/tests/controllers/OptionsController.test.ts index e94785fe4f..bb313c2b56 100644 --- a/packages/controllers/tests/controllers/OptionsController.test.ts +++ b/packages/controllers/tests/controllers/OptionsController.test.ts @@ -128,4 +128,12 @@ describe('OptionsController', () => { expect(OptionsController.state.enableMobileFullScreen).toBe(true) spy.mockRestore() }) + + it('should update state correctly on setEnableInjected()', () => { + OptionsController.setEnableInjected(false) + expect(OptionsController.state.enableInjected).toBe(false) + + OptionsController.setEnableInjected(true) + expect(OptionsController.state.enableInjected).toBe(true) + }) }) diff --git a/packages/controllers/tests/utils/ControllerUtil.test.ts b/packages/controllers/tests/utils/ControllerUtil.test.ts index f63b508452..87f8c0cd4f 100644 --- a/packages/controllers/tests/utils/ControllerUtil.test.ts +++ b/packages/controllers/tests/utils/ControllerUtil.test.ts @@ -197,6 +197,85 @@ describe('ConnectorUtil', () => { expect(ConnectorUtil.showConnector(ANNOUNCED_CONNECTOR)).toBe(true) }) + + it('should hide connector whose explorerId is in excludeWalletIds', () => { + ApiControllerRelative.state.excludedWallets = [] + const originalExclude = OptionsControllerRelative.state.excludeWalletIds + OptionsControllerRelative.state.excludeWalletIds = ['phantom-id'] + + const phantom = { + ...ANNOUNCED_CONNECTOR, + explorerId: 'phantom-id' + } as unknown as ConnectorWithProviders + + expect(ConnectorUtil.showConnector(phantom)).toBe(false) + + OptionsControllerRelative.state.excludeWalletIds = originalExclude + }) + + it('should hide connector when includeWalletIds is set and connector id not included', () => { + ApiControllerRelative.state.excludedWallets = [] + const originalInclude = OptionsControllerRelative.state.includeWalletIds + OptionsControllerRelative.state.includeWalletIds = ['metamask-id'] + + const phantom = { + ...ANNOUNCED_CONNECTOR, + explorerId: 'phantom-id' + } as unknown as ConnectorWithProviders + + expect(ConnectorUtil.showConnector(phantom)).toBe(false) + + OptionsControllerRelative.state.includeWalletIds = originalInclude + }) + + it('should show connector when its explorerId is in includeWalletIds', () => { + ApiControllerRelative.state.excludedWallets = [] + const originalInclude = OptionsControllerRelative.state.includeWalletIds + OptionsControllerRelative.state.includeWalletIds = ['metamask-id'] + + const metamask = { + ...ANNOUNCED_CONNECTOR, + explorerId: 'metamask-id' + } as unknown as ConnectorWithProviders + + expect(ConnectorUtil.showConnector(metamask)).toBe(true) + + OptionsControllerRelative.state.includeWalletIds = originalInclude + }) + + it('should fall back to explorerWallet.id when explorerId is not set', () => { + ApiControllerRelative.state.excludedWallets = [] + const originalExclude = OptionsControllerRelative.state.excludeWalletIds + OptionsControllerRelative.state.excludeWalletIds = ['phantom-id'] + + const phantom = { + ...ANNOUNCED_CONNECTOR, + explorerWallet: { id: 'phantom-id' } + } as unknown as ConnectorWithProviders + + expect(ConnectorUtil.showConnector(phantom)).toBe(false) + + OptionsControllerRelative.state.excludeWalletIds = originalExclude + }) + + it('should not filter EXTERNAL connectors by include/excludeWalletIds', () => { + ApiControllerRelative.state.excludedWallets = [] + const originalInclude = OptionsControllerRelative.state.includeWalletIds + OptionsControllerRelative.state.includeWalletIds = ['metamask-id'] + + const external = { + id: 'external', + type: 'EXTERNAL', + info: { rdns: 'external.wallet' }, + name: 'External Wallet', + chain: { id: 'eip155:1' }, + explorerId: 'external-id' + } as unknown as ConnectorWithProviders + + expect(ConnectorUtil.showConnector(external)).toBe(true) + + OptionsControllerRelative.state.includeWalletIds = originalInclude + }) }) describe('getAuthName', () => { diff --git a/packages/scaffold-ui/test/WalletUtil.test.ts b/packages/scaffold-ui/test/WalletUtil.test.ts index 9b8d6b10f8..7fb6e4ab6d 100644 --- a/packages/scaffold-ui/test/WalletUtil.test.ts +++ b/packages/scaffold-ui/test/WalletUtil.test.ts @@ -125,6 +125,19 @@ describe('WalletUtil', () => { expect(filteredWallets).toEqual(mockWallets) }) + + it('should still dedup against connectors when enableEIP6963 is false', () => { + OptionsController.state.enableEIP6963 = false + + const mockConnectors = [mockMetamaskConnector] + vi.spyOn(ConnectorController.state, 'connectors', 'get').mockReturnValue(mockConnectors) + vi.spyOn(StorageUtil, 'getRecentWallets').mockReturnValue([]) + vi.spyOn(CoreHelperUtil, 'isMobile').mockReturnValue(false) + + const filteredWallets = WalletUtil.filterOutDuplicatesByRDNS(mockWallets) + + expect(filteredWallets).not.toContain(mockMetamaskWallet) + }) }) describe('filterOutDuplicatesByIds', () => {