From 02a61ce86855ae739c90311c32f72d0aaa268b6b Mon Sep 17 00:00:00 2001 From: MK Date: Wed, 11 Dec 2024 14:01:54 +0000 Subject: [PATCH 1/6] chore: change connect order method based on installed wallets --- packages/core/exports/index.ts | 5 +- .../src/controllers/ConnectorController.ts | 2 +- packages/core/src/utils/ConstantsUtil.ts | 1 - .../src/partials/w3m-connector-list/index.ts | 36 +--------- .../scaffold-ui/src/utils/ConnectorUtil.ts | 66 +++++++++++++++++++ .../scaffold-ui/src/utils/ConstantsUtil.ts | 3 + .../src/views/w3m-connect-view/index.ts | 33 +++++++--- 7 files changed, 101 insertions(+), 45 deletions(-) create mode 100644 packages/scaffold-ui/src/utils/ConnectorUtil.ts 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..8bb9465b98 100644 --- a/packages/core/src/utils/ConstantsUtil.ts +++ b/packages/core/src/utils/ConstantsUtil.ts @@ -236,7 +236,6 @@ export const ConstantsUtil = { legalCheckbox: false, smartSessions: false, collapseWallets: false, - connectMethodsOrder: ['email', 'social', 'wallet'], walletFeaturesOrder: ['onramp', 'swaps', 'receive', 'send'] } 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..74ffd68493 --- /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(undefined)) { + 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') From 8a54c11fed8ffbe2aa7e19af47934ca50847ecb1 Mon Sep 17 00:00:00 2001 From: MK Date: Wed, 11 Dec 2024 15:56:01 +0000 Subject: [PATCH 2/6] chore: start adding integration test --- .../test/views/w3m-connect-view.test.ts | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) 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..5b65b1a373 100644 --- a/packages/scaffold-ui/test/views/w3m-connect-view.test.ts +++ b/packages/scaffold-ui/test/views/w3m-connect-view.test.ts @@ -27,7 +27,7 @@ describe('W3mConnectView - Connection Methods', () => { }) }) - it('should render connection methods in specified order', async () => { + it('should render connection methods in specified order based on features.connectMethodsOrder option', async () => { const element: W3mConnectView = await fixture(html``) const children = Array.from( @@ -44,6 +44,32 @@ describe('W3mConnectView - Connection Methods', () => { expect(widgets.indexOf(EMAIL_LOGIN_WIDGET)).toBeLessThan(widgets.indexOf(SOCIAL_LOGIN_WIDGET)) }) + it('should render connection methods in specified order without features.connectMethodsOrder option', async () => { + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + enableWallets: true, + features: { + email: true, + socials: ['google', 'facebook'] + } + }) + + const element: W3mConnectView = await fixture(html``) + + const children = Array.from( + element.shadowRoot?.querySelector('.connect-methods')?.children ?? [] + ) + const widgets = children.map(child => child.tagName.toLowerCase()) + + expect(widgets).toContain(EMAIL_LOGIN_WIDGET) + expect(widgets).toContain(WALLET_LOGIN_LIST) + expect(widgets).toContain(SOCIAL_LOGIN_WIDGET) + + // Check order + expect(widgets.indexOf(EMAIL_LOGIN_WIDGET)).toBeLessThan(widgets.indexOf(SOCIAL_LOGIN_WIDGET)) + expect(widgets.indexOf(SOCIAL_LOGIN_WIDGET)).toBeLessThan(widgets.indexOf(WALLET_LOGIN_LIST)) + }) + it('should render "Continue with wallet" button when collapseWallets is true', async () => { vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ ...OptionsController.state, From 66a28d1c2fb45b750a61b804a1df5a157cdb99bf Mon Sep 17 00:00:00 2001 From: MK Date: Thu, 12 Dec 2024 11:44:51 +0000 Subject: [PATCH 3/6] chore: add test --- .../test/views/w3m-connect-view.test.ts | 51 +++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) 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 5b65b1a373..d5b14c7af5 100644 --- a/packages/scaffold-ui/test/views/w3m-connect-view.test.ts +++ b/packages/scaffold-ui/test/views/w3m-connect-view.test.ts @@ -1,9 +1,14 @@ import { W3mConnectView } from '../../src/views/w3m-connect-view/index' import { describe, it, expect, vi, beforeEach } from 'vitest' -import { fixture } from '@open-wc/testing' +import { elementUpdated, 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 based on features.connectMethodsOrder option', 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( @@ -40,20 +58,24 @@ describe('W3mConnectView - Connection Methods', () => { 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)) + 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 specified order without features.connectMethodsOrder option', async () => { + it('should render connection methods in the correct order based on if installed wallet exist', async () => { vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ ...OptionsController.state, - enableWallets: true, 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( @@ -61,13 +83,14 @@ describe('W3mConnectView - Connection Methods', () => { ) 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)) - expect(widgets.indexOf(SOCIAL_LOGIN_WIDGET)).toBeLessThan(widgets.indexOf(WALLET_LOGIN_LIST)) }) it('should render "Continue with wallet" button when collapseWallets is true', async () => { @@ -90,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) ?? []) From c34db2d1814eeca39056ac40bcdcd627c81cd7ba Mon Sep 17 00:00:00 2001 From: MK Date: Thu, 12 Dec 2024 11:49:30 +0000 Subject: [PATCH 4/6] chore: add default constant for connect method order --- packages/core/src/utils/ConstantsUtil.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/utils/ConstantsUtil.ts b/packages/core/src/utils/ConstantsUtil.ts index 8bb9465b98..2a65154ae4 100644 --- a/packages/core/src/utils/ConstantsUtil.ts +++ b/packages/core/src/utils/ConstantsUtil.ts @@ -236,6 +236,7 @@ export const ConstantsUtil = { legalCheckbox: false, smartSessions: false, collapseWallets: false, - walletFeaturesOrder: ['onramp', 'swaps', 'receive', 'send'] + walletFeaturesOrder: ['onramp', 'swaps', 'receive', 'send'], + connectMethodsOrder: undefined } satisfies Features } From 73e7713ed72ad208252d0225d4c436a42d8ea99e Mon Sep 17 00:00:00 2001 From: MK Date: Thu, 12 Dec 2024 12:02:42 +0000 Subject: [PATCH 5/6] chore: tweak --- packages/scaffold-ui/src/utils/ConnectorUtil.ts | 2 +- packages/scaffold-ui/test/views/w3m-connect-view.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/scaffold-ui/src/utils/ConnectorUtil.ts b/packages/scaffold-ui/src/utils/ConnectorUtil.ts index 74ffd68493..16909b498b 100644 --- a/packages/scaffold-ui/src/utils/ConnectorUtil.ts +++ b/packages/scaffold-ui/src/utils/ConnectorUtil.ts @@ -42,7 +42,7 @@ export const ConnectorUtil = { const walletRDNS = connector.info?.rdns - if (!walletRDNS && !ConnectionController.checkInstalled(undefined)) { + if (!walletRDNS && !ConnectionController.checkInstalled()) { return false } 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 d5b14c7af5..9e5929ec6e 100644 --- a/packages/scaffold-ui/test/views/w3m-connect-view.test.ts +++ b/packages/scaffold-ui/test/views/w3m-connect-view.test.ts @@ -62,7 +62,7 @@ describe('W3mConnectView - Connection Methods', () => { expect(widgets.indexOf(WALLET_LOGIN_LIST)).toBeLessThan(widgets.indexOf(SOCIAL_LOGIN_WIDGET)) }) - it('should render connection methods in the correct order based on if installed wallet exist', async () => { + 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: { From 3bbb5b1cf22bbc105c6b575f930f2d360fcae601 Mon Sep 17 00:00:00 2001 From: MK Date: Thu, 12 Dec 2024 12:19:06 +0000 Subject: [PATCH 6/6] chore: remove unused import --- packages/scaffold-ui/test/views/w3m-connect-view.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9e5929ec6e..00923c1d65 100644 --- a/packages/scaffold-ui/test/views/w3m-connect-view.test.ts +++ b/packages/scaffold-ui/test/views/w3m-connect-view.test.ts @@ -1,6 +1,6 @@ import { W3mConnectView } from '../../src/views/w3m-connect-view/index' import { describe, it, expect, vi, beforeEach } from 'vitest' -import { elementUpdated, fixture } from '@open-wc/testing' +import { fixture } from '@open-wc/testing' import { html } from 'lit' import { HelpersUtil } from '../utils/HelpersUtil' import {