diff --git a/packages/core/exports/index.ts b/packages/core/exports/index.ts index 98b14a9e0b..547b5fd49a 100644 --- a/packages/core/exports/index.ts +++ b/packages/core/exports/index.ts @@ -24,7 +24,10 @@ export type { } from '../src/controllers/ConnectionController.js' export { ConnectorController } from '../src/controllers/ConnectorController.js' -export type { ConnectorControllerState } from '../src/controllers/ConnectorController.js' +export type { + ConnectorControllerState, + ConnectorWithProviders +} from '../src/controllers/ConnectorController.js' export { SnackController } from '../src/controllers/SnackController.js' export type { SnackControllerState } from '../src/controllers/SnackController.js' diff --git a/packages/core/src/controllers/ConnectorController.ts b/packages/core/src/controllers/ConnectorController.ts index f83deb0f54..fef906384d 100644 --- a/packages/core/src/controllers/ConnectorController.ts +++ b/packages/core/src/controllers/ConnectorController.ts @@ -7,7 +7,7 @@ import { ThemeController } from './ThemeController.js' import { ChainController } from './ChainController.js' // -- Types --------------------------------------------- // -interface ConnectorWithProviders extends Connector { +export interface ConnectorWithProviders extends Connector { connectors?: Connector[] } export interface ConnectorControllerState { diff --git a/packages/core/src/utils/ConstantsUtil.ts b/packages/core/src/utils/ConstantsUtil.ts index e5d094eafe..2a65154ae4 100644 --- a/packages/core/src/utils/ConstantsUtil.ts +++ b/packages/core/src/utils/ConstantsUtil.ts @@ -236,7 +236,7 @@ export const ConstantsUtil = { legalCheckbox: false, smartSessions: false, collapseWallets: false, - connectMethodsOrder: ['email', 'social', 'wallet'], - walletFeaturesOrder: ['onramp', 'swaps', 'receive', 'send'] + walletFeaturesOrder: ['onramp', 'swaps', 'receive', 'send'], + connectMethodsOrder: undefined } satisfies Features } diff --git a/packages/scaffold-ui/src/partials/w3m-connector-list/index.ts b/packages/scaffold-ui/src/partials/w3m-connector-list/index.ts index a3fc959e50..68c7214404 100644 --- a/packages/scaffold-ui/src/partials/w3m-connector-list/index.ts +++ b/packages/scaffold-ui/src/partials/w3m-connector-list/index.ts @@ -2,15 +2,10 @@ import { customElement } from '@reown/appkit-ui' import { LitElement, html } from 'lit' import styles from './styles.js' -import { - ApiController, - ConnectorController, - OptionsController, - StorageUtil -} from '@reown/appkit-core' +import { ConnectorController, OptionsController } from '@reown/appkit-core' import { property, state } from 'lit/decorators.js' -import { WalletUtil } from '../../utils/WalletUtil.js' import { ifDefined } from 'lit/directives/if-defined.js' +import { ConnectorUtil } from '../../utils/ConnectorUtil.js' @customElement('w3m-connector-list') export class W3mConnectorList extends LitElement { public static override styles = styles @@ -37,7 +32,7 @@ export class W3mConnectorList extends LitElement { // -- Render -------------------------------------------- // public override render() { const { custom, recent, announced, injected, multiChain, recommended, featured, external } = - this.getConnectorsByType() + ConnectorUtil.getConnectorsByType(this.connectors) const enableWalletConnect = OptionsController.state.enableWalletConnect @@ -91,31 +86,6 @@ export class W3mConnectorList extends LitElement { ` } - - private getConnectorsByType() { - const { featured, recommended } = ApiController.state - const { customWallets: custom } = OptionsController.state - const recent = StorageUtil.getRecentWallets() - - const filteredRecommended = WalletUtil.filterOutDuplicateWallets(recommended) - const filteredFeatured = WalletUtil.filterOutDuplicateWallets(featured) - - const multiChain = this.connectors.filter(connector => connector.type === 'MULTI_CHAIN') - const announced = this.connectors.filter(connector => connector.type === 'ANNOUNCED') - const injected = this.connectors.filter(connector => connector.type === 'INJECTED') - const external = this.connectors.filter(connector => connector.type === 'EXTERNAL') - - return { - custom, - recent, - external, - multiChain, - announced, - injected, - recommended: filteredRecommended, - featured: filteredFeatured - } - } } declare global { diff --git a/packages/scaffold-ui/src/utils/ConnectorUtil.ts b/packages/scaffold-ui/src/utils/ConnectorUtil.ts new file mode 100644 index 0000000000..16909b498b --- /dev/null +++ b/packages/scaffold-ui/src/utils/ConnectorUtil.ts @@ -0,0 +1,66 @@ +import { + ApiController, + OptionsController, + StorageUtil, + type ConnectorWithProviders, + CoreHelperUtil, + ConnectionController +} from '@reown/appkit-core' +import { WalletUtil } from './WalletUtil.js' + +export const ConnectorUtil = { + getConnectorsByType(connectors: ConnectorWithProviders[]) { + const { featured, recommended } = ApiController.state + const { customWallets: custom } = OptionsController.state + const recent = StorageUtil.getRecentWallets() + + const filteredRecommended = WalletUtil.filterOutDuplicateWallets(recommended) + const filteredFeatured = WalletUtil.filterOutDuplicateWallets(featured) + + const multiChain = connectors.filter(connector => connector.type === 'MULTI_CHAIN') + const announced = connectors.filter(connector => connector.type === 'ANNOUNCED') + const injected = connectors.filter(connector => connector.type === 'INJECTED') + + const external = connectors.filter(connector => connector.type === 'EXTERNAL') + + return { + custom, + recent, + external, + multiChain, + announced, + injected, + recommended: filteredRecommended, + featured: filteredFeatured + } + }, + showConnector(connector: ConnectorWithProviders) { + if (connector.type === 'INJECTED') { + if (!CoreHelperUtil.isMobile() && connector.name === 'Browser Wallet') { + return false + } + + const walletRDNS = connector.info?.rdns + + if (!walletRDNS && !ConnectionController.checkInstalled()) { + return false + } + + if (walletRDNS && ApiController.state.excludedRDNS) { + if (ApiController.state.excludedRDNS.includes(walletRDNS)) { + return false + } + } + } + + if (connector.type === 'ANNOUNCED') { + const rdns = connector.info?.rdns + + if (rdns && ApiController.state.excludedRDNS.includes(rdns)) { + return false + } + } + + return true + } +} diff --git a/packages/scaffold-ui/src/utils/ConstantsUtil.ts b/packages/scaffold-ui/src/utils/ConstantsUtil.ts index 7e6ba337ba..4f6d4d27e9 100644 --- a/packages/scaffold-ui/src/utils/ConstantsUtil.ts +++ b/packages/scaffold-ui/src/utils/ConstantsUtil.ts @@ -1,3 +1,5 @@ +import type { ConnectMethod } from '@reown/appkit-core' + export const ConstantsUtil = { ACCOUNT_TABS: [{ label: 'Tokens' }, { label: 'NFTs' }, { label: 'Activity' }], SECURE_SITE_ORIGIN: @@ -6,6 +8,7 @@ export const ConstantsUtil = { Next: 'next', Prev: 'prev' }, + DEFAULT_CONNECT_METHOD_ORDER: ['email', 'social', 'wallet'] as ConnectMethod[], ANIMATION_DURATIONS: { HeaderText: 120, ModalHeight: 150, diff --git a/packages/scaffold-ui/src/views/w3m-connect-view/index.ts b/packages/scaffold-ui/src/views/w3m-connect-view/index.ts index 88c075c8e5..5868f41b91 100644 --- a/packages/scaffold-ui/src/views/w3m-connect-view/index.ts +++ b/packages/scaffold-ui/src/views/w3m-connect-view/index.ts @@ -7,15 +7,15 @@ import { CoreHelperUtil, OptionsController, RouterController, + type ConnectMethod, type WalletGuideType } from '@reown/appkit-core' import { state } from 'lit/decorators/state.js' import { property } from 'lit/decorators.js' import { classMap } from 'lit/directives/class-map.js' import { ifDefined } from 'lit/directives/if-defined.js' -import { ConstantsUtil } from '@reown/appkit-core' - -const defaultConnectMethodsOrder = ConstantsUtil.DEFAULT_FEATURES.connectMethodsOrder +import { ConnectorUtil } from '../../utils/ConnectorUtil.js' +import { ConstantsUtil } from '../../utils/ConstantsUtil.js' @customElement('w3m-connect-view') export class W3mConnectView extends LitElement { @@ -119,11 +119,7 @@ export class W3mConnectView extends LitElement { // -- Private ------------------------------------------- // private renderConnectMethod(tabIndex?: number) { - const connectMethodsOrder = this.features?.connectMethodsOrder || defaultConnectMethodsOrder - - if (!connectMethodsOrder) { - return null - } + const connectMethodsOrder = this.getConnectOrderMethod() return html`${connectMethodsOrder.map((method, index) => { switch (method) { @@ -155,7 +151,7 @@ export class W3mConnectView extends LitElement { } private checkIsThereNextMethod(currentIndex: number): string | undefined { - const connectMethodsOrder = this.features?.connectMethodsOrder || defaultConnectMethodsOrder + const connectMethodsOrder = this.getConnectOrderMethod() const nextMethod = connectMethodsOrder[currentIndex + 1] as | 'wallet' @@ -349,6 +345,25 @@ export class W3mConnectView extends LitElement { ) } + private getConnectOrderMethod() { + const connectMethodOrder = this.features?.connectMethodsOrder + + if (connectMethodOrder) { + return connectMethodOrder + } + + const { injected, announced } = ConnectorUtil.getConnectorsByType(this.connectors) + + const shownInjected = injected.filter(ConnectorUtil.showConnector) + const shownAnnounced = announced.filter(ConnectorUtil.showConnector) + + if (shownInjected.length || shownAnnounced.length) { + return ['wallet', 'email', 'social'] as ConnectMethod[] + } + + return ConstantsUtil.DEFAULT_CONNECT_METHOD_ORDER + } + // -- Private Methods ----------------------------------- // private onContinueWalletClick() { RouterController.push('ConnectWallets') diff --git a/packages/scaffold-ui/test/views/w3m-connect-view.test.ts b/packages/scaffold-ui/test/views/w3m-connect-view.test.ts index 1cb4c15dd9..00923c1d65 100644 --- a/packages/scaffold-ui/test/views/w3m-connect-view.test.ts +++ b/packages/scaffold-ui/test/views/w3m-connect-view.test.ts @@ -3,7 +3,12 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' import { fixture } from '@open-wc/testing' import { html } from 'lit' import { HelpersUtil } from '../utils/HelpersUtil' -import { OptionsController } from '@reown/appkit-core' +import { + OptionsController, + type ConnectorWithProviders, + ConnectorController, + CoreHelperUtil +} from '@reown/appkit-core' // --- Constants ---------------------------------------------------- // const EMAIL_LOGIN_WIDGET = 'w3m-email-login-widget' @@ -13,21 +18,34 @@ const COLLAPSE_WALLETS_BUTTON = 'w3m-collapse-wallets-button' const SEPARATOR = 'wui-separator' const EMAIL_SEPARATOR = 'w3m-email-login-or-separator' +const INSTALLED_WALLET = { + id: 'metamask', + name: 'MetaMask', + type: 'ANNOUNCED' +} as ConnectorWithProviders + describe('W3mConnectView - Connection Methods', () => { beforeEach(() => { + vi.spyOn(CoreHelperUtil, 'isMobile').mockReturnValue(false) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ ...OptionsController.state, enableWallets: true, features: { email: true, socials: ['google', 'facebook'], - connectMethodsOrder: ['wallet', 'email', 'social'], + connectMethodsOrder: ['email', 'wallet', 'social'], collapseWallets: false } }) }) - it('should render connection methods in specified order', async () => { + it('should render connection methods in specified order based on connectMethodsOrder option', async () => { + vi.spyOn(ConnectorController, 'state', 'get').mockReturnValue({ + ...ConnectorController.state, + connectors: [INSTALLED_WALLET] + }) + const element: W3mConnectView = await fixture(html``) const children = Array.from( @@ -39,6 +57,37 @@ describe('W3mConnectView - Connection Methods', () => { expect(widgets).toContain(EMAIL_LOGIN_WIDGET) expect(widgets).toContain(SOCIAL_LOGIN_WIDGET) + // Check order + expect(widgets.indexOf(EMAIL_LOGIN_WIDGET)).toBeLessThan(widgets.indexOf(WALLET_LOGIN_LIST)) + expect(widgets.indexOf(WALLET_LOGIN_LIST)).toBeLessThan(widgets.indexOf(SOCIAL_LOGIN_WIDGET)) + }) + + it('should render connection methods in the correct order based on if there are installed wallets', async () => { + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + features: { + email: true, + socials: ['google', 'facebook'] + } + }) + + vi.spyOn(ConnectorController, 'state', 'get').mockReturnValue({ + ...ConnectorController.state, + connectors: [INSTALLED_WALLET] + }) + + const element: W3mConnectView = await fixture(html``) + + const children = Array.from( + element.shadowRoot?.querySelector('.connect-methods')?.children ?? [] + ) + const widgets = children.map(child => child.tagName.toLowerCase()) + + // Assertions + expect(widgets).toContain(EMAIL_LOGIN_WIDGET) + expect(widgets).toContain(WALLET_LOGIN_LIST) + expect(widgets).toContain(SOCIAL_LOGIN_WIDGET) + // Check order expect(widgets.indexOf(WALLET_LOGIN_LIST)).toBeLessThan(widgets.indexOf(EMAIL_LOGIN_WIDGET)) expect(widgets.indexOf(EMAIL_LOGIN_WIDGET)).toBeLessThan(widgets.indexOf(SOCIAL_LOGIN_WIDGET)) @@ -64,6 +113,16 @@ describe('W3mConnectView - Connection Methods', () => { }) it('should render one separator between wallet and email/social group', async () => { + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + enableWallets: true, + features: { + email: true, + socials: ['google'], + connectMethodsOrder: ['wallet', 'email', 'social'] + } + }) + const element: W3mConnectView = await fixture(html``) const separators = Array.from(element.shadowRoot?.querySelectorAll(SEPARATOR) ?? [])