diff --git a/.github/workflows/ui_tests.yml b/.github/workflows/ui_tests.yml index f5931c8ddd..70dc541399 100644 --- a/.github/workflows/ui_tests.yml +++ b/.github/workflows/ui_tests.yml @@ -40,6 +40,10 @@ on: required: false TESTS_MAILSEC_API_KEY: required: false + TESTS_SOCIAL_EMAIL: + required: false + TESTS_SOCIAL_PASSWORD: + required: false jobs: ui_tests: @@ -81,6 +85,8 @@ jobs: NEXTAUTH_SECRET: ${{ secrets.TESTS_NEXTAUTH_SECRET }} MAILSAC_API_KEY: ${{ secrets.TESTS_MAILSEC_API_KEY }} NEXT_PUBLIC_SECURE_SITE_SDK_URL: ${{ inputs.secure-site-url }} + SOCIAL_TEST_EMAIL: ${{ secrets.TESTS_SOCIAL_EMAIL }} + SOCIAL_TEST_PASSWORD: ${{ secrets.TESTS_SOCIAL_PASSWORD }} - name: Install Playwright Browsers if: steps.playwright-cache.outputs.cache-hit != 'true' working-directory: ./apps/laboratory/ @@ -94,6 +100,8 @@ jobs: NEXTAUTH_SECRET: ${{ secrets.TESTS_NEXTAUTH_SECRET }} MAILSAC_API_KEY: ${{ secrets.TESTS_MAILSEC_API_KEY }} NEXT_PUBLIC_SECURE_SITE_SDK_URL: ${{ inputs.secure-site-url }} + SOCIAL_TEST_EMAIL: ${{ secrets.TESTS_SOCIAL_EMAIL }} + SOCIAL_TEST_PASSWORD: ${{ secrets.TESTS_SOCIAL_PASSWORD }} CI: true working-directory: ./apps/laboratory/ run: npm run ${{ inputs.command }} diff --git a/.gitignore b/.gitignore index 86b91b276d..dd61f89ead 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ screenshots/ coverage test-results .vscode/* +apps/laboratory/playwright/.auth/user.json diff --git a/apps/laboratory/.env.example b/apps/laboratory/.env.example index 1b63c86782..58aad67f24 100644 --- a/apps/laboratory/.env.example +++ b/apps/laboratory/.env.example @@ -7,3 +7,5 @@ MAILSAC_API_KEY="" # Only needed when overriding default next-auth URL # NEXTAUTH_URL="" NEXT_PUBLIC_SECURE_SITE_SDK_URL="" +SOCIAL_TEST_EMAIL="" +SOCIAL_TEST_PASSWORD="" diff --git a/apps/laboratory/package.json b/apps/laboratory/package.json index 96be7ac21c..31b6f657ae 100644 --- a/apps/laboratory/package.json +++ b/apps/laboratory/package.json @@ -12,6 +12,7 @@ "playwright:test": "playwright test", "playwright:test:wallet": "playwright test --grep 'connect-qr.spec.ts|wallet.spec.ts'", "playwright:test:email": "playwright test --grep 'email.spec.ts'", + "playwright:test:social": "playwright test --grep 'social.spec.ts'", "playwright:test:siwe": "playwright test --grep siwe.spec.ts", "playwright:test:siwe-email": "playwright test --grep siwe-email.spec.ts", "playwright:test:siwe-sa": "playwright test --grep siwe-smart-account.spec.ts", @@ -20,6 +21,7 @@ "playwright:debug": "npm run playwright:test -- --debug", "playwright:debug:wallet": "npm run playwright:test:wallet -- --debug", "playwright:debug:email": "npm run playwright:test:email -- --debug", + "playwright:debug:social": "npm run playwright:test:social -- --debug", "playwright:debug:siwe": "npm run playwright:test:siwe -- --debug", "playwright:debug:siwe-email": "npm run playwright:test:siwe-email -- --debug", "playwright:debug:siwe-sa": "npm run playwright:test:siwe-smart-account -- --debug", diff --git a/apps/laboratory/src/pages/library/ethers-email.tsx b/apps/laboratory/src/pages/library/ethers-email.tsx index 5e8c01243e..a6089822aa 100644 --- a/apps/laboratory/src/pages/library/ethers-email.tsx +++ b/apps/laboratory/src/pages/library/ethers-email.tsx @@ -11,7 +11,10 @@ const modal = createWeb3Modal({ metadata: ConstantsUtil.Metadata, defaultChainId: 1, rpcUrl: 'https://cloudflare-eth.com', - enableEmail: true + enableEmail: true, + auth: { + socials: ['google', 'x', 'discord', 'apple', 'github'] + } }), chains: EthersConstants.chains, projectId: ConstantsUtil.ProjectId, diff --git a/apps/laboratory/src/pages/library/ethers-wallet.tsx b/apps/laboratory/src/pages/library/ethers-wallet.tsx index c7b727b640..6a3501bb8e 100644 --- a/apps/laboratory/src/pages/library/ethers-wallet.tsx +++ b/apps/laboratory/src/pages/library/ethers-wallet.tsx @@ -11,7 +11,10 @@ const modal = createWeb3Modal({ metadata: ConstantsUtil.Metadata, defaultChainId: 1, rpcUrl: 'https://cloudflare-eth.com', - enableEmail: true + enableEmail: true, + auth: { + socials: ['google', 'x', 'discord', 'apple', 'github'] + } }), chains: EthersConstants.chains, projectId: ConstantsUtil.ProjectId, diff --git a/apps/laboratory/src/pages/library/wagmi-email.tsx b/apps/laboratory/src/pages/library/wagmi-email.tsx index 4d76560f39..4a69bc4d06 100644 --- a/apps/laboratory/src/pages/library/wagmi-email.tsx +++ b/apps/laboratory/src/pages/library/wagmi-email.tsx @@ -17,6 +17,9 @@ export const wagmiConfig = defaultWagmiConfig({ projectId: ConstantsUtil.ProjectId, metadata: ConstantsUtil.Metadata, enableEmail: true, + auth: { + socials: ['google', 'x', 'discord', 'apple', 'github'] + }, ssr: true }) diff --git a/apps/laboratory/src/pages/library/wagmi-wallet.tsx b/apps/laboratory/src/pages/library/wagmi-wallet.tsx index 01ea596e48..a8cd2bfb52 100644 --- a/apps/laboratory/src/pages/library/wagmi-wallet.tsx +++ b/apps/laboratory/src/pages/library/wagmi-wallet.tsx @@ -17,7 +17,10 @@ export const wagmiConfig = defaultWagmiConfig({ projectId: ConstantsUtil.ProjectId, metadata: ConstantsUtil.Metadata, enableEmail: true, - ssr: true + ssr: true, + auth: { + socials: ['google', 'x', 'discord', 'apple', 'github'] + } }) const modal = createWeb3Modal({ diff --git a/apps/laboratory/src/utils/DataUtil.ts b/apps/laboratory/src/utils/DataUtil.ts index 61d8a24c3a..c9ec810586 100644 --- a/apps/laboratory/src/utils/DataUtil.ts +++ b/apps/laboratory/src/utils/DataUtil.ts @@ -37,7 +37,7 @@ export const wagmiSdkOptions = [ { title: 'Wallet', link: '/library/wagmi-wallet/', - description: 'Configuration using wagmi and implementing email wallet' + description: 'Configuration using wagmi and implementing social wallet' } ] @@ -60,7 +60,7 @@ export const ethersSdkOptions = [ { title: 'Wallet', link: '/library/ethers-wallet/', - description: 'Configuration using ethers and implementing email wallet' + description: 'Configuration using ethers and implementing social wallet' } ] diff --git a/apps/laboratory/tests/shared/fixtures/w3m-social-fixture.ts b/apps/laboratory/tests/shared/fixtures/w3m-social-fixture.ts new file mode 100644 index 0000000000..e7a867deff --- /dev/null +++ b/apps/laboratory/tests/shared/fixtures/w3m-social-fixture.ts @@ -0,0 +1,28 @@ +import type { ModalFixture } from './w3m-fixture' +import { ModalPage } from '../pages/ModalPage' +import { ModalValidator } from '../validators/ModalValidator' +import { timingFixture } from './timing-fixture' + +export const testMSocial = timingFixture.extend({ + library: ['wagmi', { option: true }], + modalPage: async ({ page, library }, use) => { + const modalPage = new ModalPage(page, library, 'email') + await modalPage.load() + + const socialMail = process.env['SOCIAL_TEST_EMAIL'] + if (!socialMail) { + throw new Error('SOCIAL_TEST_MAIL is not set') + } + const socialPass = process.env['SOCIAL_TEST_PASSWORD'] + if (!socialPass) { + throw new Error('SOCIAL_TEST_PASSWORD is not set') + } + + await modalPage.loginWithSocial(socialMail, socialPass) + await use(modalPage) + }, + modalValidator: async ({ modalPage }, use) => { + const modalValidator = new ModalValidator(modalPage.page) + await use(modalValidator) + } +}) diff --git a/apps/laboratory/tests/shared/pages/ModalPage.ts b/apps/laboratory/tests/shared/pages/ModalPage.ts index c3200da2b3..8e816ca8e6 100644 --- a/apps/laboratory/tests/shared/pages/ModalPage.ts +++ b/apps/laboratory/tests/shared/pages/ModalPage.ts @@ -127,6 +127,23 @@ export class ModalPage { }) } + async loginWithSocial(socialMail: string, socialPass: string) { + const authFile = 'playwright/.auth/user.json' + await this.page + .getByTestId('connect-button') + .getByRole('button', { name: 'Connect Wallet' }) + .click() + const discordPopupPromise = this.page.waitForEvent('popup') + await this.page.getByTestId('social-selector-discord').click() + const discordPopup = await discordPopupPromise + await discordPopup.fill('#uid_8', socialMail) + await discordPopup.fill('#uid_10', socialPass) + await discordPopup.locator('[type=submit]').click() + await discordPopup.locator('.footer_b96583 button:nth-child(2)').click() + await discordPopup.context().storageState({ path: authFile }) + await discordPopup.waitForEvent('close') + } + async enterOTP(otp: string, headerTitle = 'Confirm Email') { await expect(this.page.getByText(headerTitle)).toBeVisible({ timeout: 10_000 @@ -184,6 +201,7 @@ export class ModalPage { }) await this.page.waitForTimeout(2000) } + async clickSignatureRequestButton(name: string) { await this.page.frameLocator('#w3m-iframe').getByRole('button', { name, exact: true }).click() } diff --git a/apps/laboratory/tests/shared/utils/project.ts b/apps/laboratory/tests/shared/utils/project.ts index 0717e0a8a5..8b9cc0f896 100644 --- a/apps/laboratory/tests/shared/utils/project.ts +++ b/apps/laboratory/tests/shared/utils/project.ts @@ -35,36 +35,42 @@ const braveOptions: UseOptions = { } const customProjectProperties: CustomProjectProperties = { + 'Desktop Chrome/ethers': { + testIgnore: /(?:social\.spec\.ts).*$/u + }, 'Desktop Brave/ethers': { - testIgnore: /(?:email\.spec\.ts|smart-account\.spec\.ts).*$/u, + testIgnore: /(?:email\.spec\.ts|smart-account\.spec\.ts|social\.spec\.ts).*$/u, useOptions: braveOptions }, + 'Desktop Firefox/ethers': { + testIgnore: /(?:social\.spec\.ts).*$/u + }, 'Desktop Brave/wagmi': { testIgnore: - /(?:email\.spec\.ts|smart-account\.spec\.ts|siwe-email\.spec\.ts|siwe-smart-account\.spec\.ts).*$/u, + /(?:email\.spec\.ts|smart-account\.spec\.ts|siwe-email\.spec\.ts|siwe-smart-account\.spec\.ts|social\.spec\.ts).*$/u, useOptions: braveOptions }, 'Desktop Chrome/wagmi': { testIgnore: - /(?:email\.spec\.ts|smart-account\.spec\.ts|siwe-email\.spec\.ts|siwe-smart-account\.spec\.ts).*$/u + /(?:email\.spec\.ts|smart-account\.spec\.ts|siwe-email\.spec\.ts|siwe-smart-account\.spec\.ts|social\.spec\.ts).*$/u }, 'Desktop Firefox/wagmi': { testIgnore: - /(?:email\.spec\.ts|smart-account\.spec\.ts|siwe-email\.spec\.ts|siwe-smart-account\.spec\.ts).*$/u + /(?:email\.spec\.ts|smart-account\.spec\.ts|siwe-email\.spec\.ts|siwe-smart-account\.spec\.ts|social\.spec\.ts).*$/u }, - // Exclude email.spec.ts, siwe.spec.ts, and canary.spec.ts from solana, not yet implemented + // Exclude social.spec.ts, email.spec.ts, siwe.spec.ts, and canary.spec.ts from solana, not yet implemented 'Desktop Chrome/solana': { - grep: /^(?!.*(?:email\.spec\.ts|siwe\.spec\.ts|canary\.spec\.ts|smart-account\.spec\.ts)).*$/u + grep: /^(?!.*(?:email\.spec\.ts|siwe\.spec\.ts|canary\.spec\.ts|smart-account\.spec\.ts|social\.spec\.ts)).*$/u }, 'Desktop Brave/solana': { useOptions: braveOptions, - grep: /^(?!.*(?:email\.spec\.ts|siwe\.spec\.ts|canary\.spec\.ts|smart-account\.spec\.ts)).*$/u + grep: /^(?!.*(?:email\.spec\.ts|siwe\.spec\.ts|canary\.spec\.ts|smart-account\.spec\.ts|social\.spec\.ts)).*$/u }, 'Desktop Firefox/solana': { - grep: /^(?!.*(?:email\.spec\.ts|siwe\.spec\.ts|canary\.spec\.ts|smart-account\.spec\.ts)).*$/u + grep: /^(?!.*(?:email\.spec\.ts|siwe\.spec\.ts|canary\.spec\.ts|smart-account\.spec\.ts|social\.spec\.ts)).*$/u }, 'Desktop Safari/solana': { - grep: /^(?!.*(?:email\.spec\.ts|siwe\.spec\.ts|canary\.spec\.ts|smart-account\.spec\.ts)).*$/u + grep: /^(?!.*(?:email\.spec\.ts|siwe\.spec\.ts|canary\.spec\.ts|smart-account\.spec\.ts|social\.spec\.ts)).*$/u } } @@ -78,7 +84,8 @@ export function getProjects() { const deviceName = device === 'Desktop Brave' ? 'Desktop Chrome' : device let project = { name: `${device}/${library}`, - use: { ...devices[deviceName], library } + use: { ...devices[deviceName], library }, + storageState: 'playwright/.auth/user.json' } const props = customProjectProperties[project.name] if (props) { diff --git a/apps/laboratory/tests/social.spec.ts b/apps/laboratory/tests/social.spec.ts new file mode 100644 index 0000000000..539e4a54be --- /dev/null +++ b/apps/laboratory/tests/social.spec.ts @@ -0,0 +1,40 @@ +import { testMSocial } from './shared/fixtures/w3m-social-fixture' + +testMSocial.beforeEach(async ({ modalValidator }) => { + await modalValidator.expectConnected() +}) + +testMSocial('it should sign', async ({ modalPage, modalValidator }) => { + await modalPage.sign() + await modalPage.approveSign() + await modalValidator.expectAcceptedSign() +}) + +testMSocial('it should reject sign', async ({ modalPage, modalValidator }) => { + await modalPage.sign() + await modalPage.rejectSign() + await modalValidator.expectRejectedSign() +}) + +testMSocial('it should switch network and sign', async ({ modalPage, modalValidator }) => { + let targetChain = 'Polygon' + await modalPage.switchNetwork(targetChain) + await modalValidator.expectSwitchedNetwork(targetChain) + await modalPage.closeModal() + await modalPage.sign() + await modalPage.approveSign() + await modalValidator.expectAcceptedSign() + + targetChain = 'Ethereum' + await modalPage.switchNetwork(targetChain) + await modalValidator.expectSwitchedNetwork(targetChain) + await modalPage.closeModal() + await modalPage.sign() + await modalPage.approveSign() + await modalValidator.expectAcceptedSign() +}) + +testMSocial('it should disconnect correctly', async ({ modalPage, modalValidator }) => { + await modalPage.disconnect() + await modalValidator.expectDisconnected() +}) diff --git a/packages/core/src/controllers/AccountController.ts b/packages/core/src/controllers/AccountController.ts index 0e07e92e58..ce2952ee17 100644 --- a/packages/core/src/controllers/AccountController.ts +++ b/packages/core/src/controllers/AccountController.ts @@ -1,7 +1,7 @@ import { subscribeKey as subKey } from 'valtio/vanilla/utils' import { proxy, ref, subscribe as sub } from 'valtio/vanilla' import { CoreHelperUtil } from '../utils/CoreHelperUtil.js' -import type { CaipAddress, ConnectedWalletInfo } from '../utils/TypeUtil.js' +import type { CaipAddress, ConnectedWalletInfo, SocialProvider } from '../utils/TypeUtil.js' import type { Balance } from '@web3modal/common' import { BlockchainApiController } from './BlockchainApiController.js' import { SnackController } from './SnackController.js' @@ -22,6 +22,7 @@ export interface AccountControllerState { profileImage?: string | null addressExplorerUrl?: string smartAccountDeployed?: boolean + socialProvider?: SocialProvider tokenBalance?: Balance[] connectedWalletInfo?: ConnectedWalletInfo preferredAccountType?: W3mFrameTypes.AccountType @@ -100,6 +101,12 @@ export const AccountController = { state.preferredAccountType = preferredAccountType }, + setSocialProvider(socialProvider: AccountControllerState['socialProvider']) { + if (socialProvider) { + state.socialProvider = socialProvider + } + }, + async fetchTokenBalance() { const chainId = NetworkController.state.caipNetwork?.id @@ -129,5 +136,6 @@ export const AccountController = { state.tokenBalance = [] state.connectedWalletInfo = undefined state.preferredAccountType = undefined + state.socialProvider = undefined } } diff --git a/packages/core/src/controllers/RouterController.ts b/packages/core/src/controllers/RouterController.ts index b2694e0db7..20a7c4027e 100644 --- a/packages/core/src/controllers/RouterController.ts +++ b/packages/core/src/controllers/RouterController.ts @@ -24,6 +24,9 @@ export interface RouterControllerState { | 'ConnectingExternal' | 'ConnectingWalletConnect' | 'ConnectingSiwe' + | 'ConnectingSocial' + | 'ConnectSocials' + | 'ConnectWallets' | 'Downloads' | 'EmailVerifyOtp' | 'EmailVerifyDevice' diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index 0d84794fc5..5bbaf2dff2 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -57,6 +57,7 @@ export type Connector = { provider?: unknown email?: boolean socials?: SocialProvider[] + showWallets?: boolean } export interface AuthConnector extends Connector { diff --git a/packages/ethers/src/client.ts b/packages/ethers/src/client.ts index f4fb3d22cf..27315d8ba2 100644 --- a/packages/ethers/src/client.ts +++ b/packages/ethers/src/client.ts @@ -1360,7 +1360,8 @@ export class Web3Modal extends Web3ModalScaffold { name: 'Auth', provider: this.authProvider, email, - socials: auth?.socials + socials: auth?.socials, + showWallets: auth?.showWallets === undefined ? true : auth.showWallets }) super.setLoading(true) diff --git a/packages/ethers/src/utils/defaultConfig.ts b/packages/ethers/src/utils/defaultConfig.ts index 97788c3c47..82fc81391e 100644 --- a/packages/ethers/src/utils/defaultConfig.ts +++ b/packages/ethers/src/utils/defaultConfig.ts @@ -9,6 +9,7 @@ export interface ConfigOptions { enableEmail?: boolean auth?: { socials?: SocialProvider[] + showWallets?: boolean } enableInjected?: boolean rpcUrl?: string diff --git a/packages/scaffold-utils/src/EthersTypesUtil.ts b/packages/scaffold-utils/src/EthersTypesUtil.ts index cdd3372293..b9dfe9b410 100644 --- a/packages/scaffold-utils/src/EthersTypesUtil.ts +++ b/packages/scaffold-utils/src/EthersTypesUtil.ts @@ -15,6 +15,7 @@ export type ProviderType = { email?: boolean auth?: { socials?: SocialProvider[] + showWallets?: boolean } EIP6963?: boolean metadata: Metadata diff --git a/packages/scaffold/index.ts b/packages/scaffold/index.ts index 8c2a9c3232..981e94bcce 100644 --- a/packages/scaffold/index.ts +++ b/packages/scaffold/index.ts @@ -43,6 +43,9 @@ export * from './src/views/w3m-wallet-compatible-networks-view/index.js' export * from './src/views/w3m-wallet-send-view/index.js' export * from './src/views/w3m-wallet-send-select-token-view/index.js' export * from './src/views/w3m-wallet-send-preview-view/index.js' +export * from './src/views/w3m-connect-wallets-view/index.js' +export * from './src/views/w3m-connect-socials-view/index.js' +export * from './src/views/w3m-connecting-social-view/index.js' export * from './src/partials/w3m-all-wallets-list/index.js' export * from './src/partials/w3m-all-wallets-search/index.js' @@ -75,6 +78,18 @@ export * from './src/partials/w3m-input-address/index.js' export * from './src/partials/w3m-wallet-send-details/index.js' export * from './src/partials/w3m-tooltip/index.js' export * from './src/partials/w3m-tooltip-trigger/index.js' +export * from './src/partials/w3m-social-login-widget/index.js' +export * from './src/partials/w3m-wallet-login-list/index.js' +export * from './src/partials/w3m-social-login-list/index.js' +export * from './src/partials/w3m-connect-announced-widget/index.js' +export * from './src/partials/w3m-connect-custom-widget/index.js' +export * from './src/partials/w3m-connect-external-widget/index.js' +export * from './src/partials/w3m-connect-featured-widget/index.js' +export * from './src/partials/w3m-connect-injected-widget/index.js' +export * from './src/partials/w3m-connect-recent-widget/index.js' +export * from './src/partials/w3m-connect-recommended-widget/index.js' +export * from './src/partials/w3m-connect-walletconnect-widget/index.js' +export * from './src/partials/w3m-all-wallets-widget/index.js' export { Web3ModalScaffold } from './src/client.js' export type { LibraryOptions, ScaffoldOptions } from './src/client.js' diff --git a/packages/scaffold/src/modal/w3m-router/index.ts b/packages/scaffold/src/modal/w3m-router/index.ts index 9e7d1a9778..c76fb39591 100644 --- a/packages/scaffold/src/modal/w3m-router/index.ts +++ b/packages/scaffold/src/modal/w3m-router/index.ts @@ -129,6 +129,12 @@ export class W3mRouter extends LitElement { return html`` case 'WalletSendPreview': return html`` + case 'ConnectWallets': + return html`` + case 'ConnectSocials': + return html`` + case 'ConnectingSocial': + return html`` default: return html`` } diff --git a/packages/scaffold/src/partials/w3m-all-wallets-widget/index.ts b/packages/scaffold/src/partials/w3m-all-wallets-widget/index.ts new file mode 100644 index 0000000000..d8cb927126 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-all-wallets-widget/index.ts @@ -0,0 +1,76 @@ +import { + ApiController, + ConnectorController, + CoreHelperUtil, + EventsController, + OptionsController, + RouterController +} from '@web3modal/core' +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' +import { state } from 'lit/decorators.js' + +@customElement('w3m-all-wallets-widget') +export class W3mAllWalletsWidget extends LitElement { + // -- Members ------------------------------------------- // + private unsubscribe: (() => void)[] = [] + + // -- State & Properties -------------------------------- // + @state() private connectors = ConnectorController.state.connectors + @state() private count = ApiController.state.count + + public constructor() { + super() + this.unsubscribe.push( + ConnectorController.subscribeKey('connectors', val => (this.connectors = val)), + ApiController.subscribeKey('count', val => (this.count = val)) + ) + } + + public override disconnectedCallback() { + this.unsubscribe.forEach(unsubscribe => unsubscribe()) + } + + // -- Render -------------------------------------------- // + public override render() { + const connector = this.connectors.find(c => c.type === 'WALLET_CONNECT') + const { allWallets } = OptionsController.state + + if (!connector || allWallets === 'HIDE') { + return null + } + + if (allWallets === 'ONLY_MOBILE' && !CoreHelperUtil.isMobile()) { + return null + } + + const featuredCount = ApiController.state.featured.length + const rawCount = this.count + featuredCount + const roundedCount = rawCount < 10 ? rawCount : Math.floor(rawCount / 10) * 10 + const tagLabel = roundedCount < rawCount ? `${roundedCount}+` : `${roundedCount}` + + return html` + + ` + } + + // -- Private ------------------------------------------- // + private onAllWallets() { + EventsController.sendEvent({ type: 'track', event: 'CLICK_ALL_WALLETS' }) + RouterController.push('AllWallets') + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-all-wallets-widget': W3mAllWalletsWidget + } +} diff --git a/packages/scaffold/src/partials/w3m-connect-announced-widget/index.ts b/packages/scaffold/src/partials/w3m-connect-announced-widget/index.ts new file mode 100644 index 0000000000..90c4d3ebf5 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-connect-announced-widget/index.ts @@ -0,0 +1,75 @@ +import type { Connector } from '@web3modal/core' +import { AssetUtil, ConnectorController, CoreHelperUtil, RouterController } from '@web3modal/core' +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' +import { state } from 'lit/decorators.js' +import { ifDefined } from 'lit/directives/if-defined.js' + +@customElement('w3m-connect-announced-widget') +export class W3mConnectAnnouncedWidget extends LitElement { + // -- Members ------------------------------------------- // + private unsubscribe: (() => void)[] = [] + + // -- State & Properties -------------------------------- // + @state() private connectors = ConnectorController.state.connectors + + public constructor() { + super() + this.unsubscribe.push( + ConnectorController.subscribeKey('connectors', val => (this.connectors = val)) + ) + } + + public override disconnectedCallback() { + this.unsubscribe.forEach(unsubscribe => unsubscribe()) + } + + // -- Render -------------------------------------------- // + public override render() { + const announcedConnectors = this.connectors.filter(connector => connector.type === 'ANNOUNCED') + + if (!announcedConnectors?.length) { + this.style.cssText = `display: none` + + return null + } + + return html` + + ${announcedConnectors.map(connector => { + return html` + this.onConnector(connector)} + tagVariant="success" + tagLabel="installed" + data-testid=${`wallet-selector-${connector.id}`} + .installed=${true} + > + + ` + })} + + ` + } + + // -- Private Methods ----------------------------------- // + private onConnector(connector: Connector) { + if (connector.type === 'WALLET_CONNECT') { + if (CoreHelperUtil.isMobile()) { + RouterController.push('AllWallets') + } else { + RouterController.push('ConnectingWalletConnect') + } + } else { + RouterController.push('ConnectingExternal', { connector }) + } + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-connect-announced-widget': W3mConnectAnnouncedWidget + } +} diff --git a/packages/scaffold/src/partials/w3m-connect-custom-widget/index.ts b/packages/scaffold/src/partials/w3m-connect-custom-widget/index.ts new file mode 100644 index 0000000000..588a6ffc9e --- /dev/null +++ b/packages/scaffold/src/partials/w3m-connect-custom-widget/index.ts @@ -0,0 +1,88 @@ +import type { WcWallet } from '@web3modal/core' +import { + AssetUtil, + ConnectorController, + CoreHelperUtil, + OptionsController, + RouterController, + StorageUtil +} from '@web3modal/core' +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' +import { state } from 'lit/decorators.js' +import { ifDefined } from 'lit/directives/if-defined.js' + +@customElement('w3m-connect-custom-widget') +export class W3mConnectCustomWidget extends LitElement { + // -- Members ------------------------------------------- // + private unsubscribe: (() => void)[] = [] + + // -- State & Properties -------------------------------- // + @state() private connectors = ConnectorController.state.connectors + + public constructor() { + super() + this.unsubscribe.push( + ConnectorController.subscribeKey('connectors', val => (this.connectors = val)) + ) + } + + public override disconnectedCallback() { + this.unsubscribe.forEach(unsubscribe => unsubscribe()) + } + + // -- Render -------------------------------------------- // + public override render() { + const { customWallets } = OptionsController.state + if (!customWallets?.length) { + this.style.cssText = `display: none` + + return null + } + + const wallets = this.filterOutDuplicateWallets(customWallets) + + return html` + ${wallets.map(wallet => { + return html` + this.onConnectWallet(wallet)} + data-testid=${`wallet-selector-${wallet.id}`} + > + + ` + })} + ` + } + + // -- Private Methods ----------------------------------- // + private filterOutDuplicateWallets(wallets: WcWallet[]) { + const recent = StorageUtil.getRecentWallets() + + const connectorRDNSs = this.connectors + .map(connector => connector.info?.rdns) + .filter(Boolean) as string[] + + const recentRDNSs = recent.map(wallet => wallet.rdns).filter(Boolean) as string[] + const allRDNSs = connectorRDNSs.concat(recentRDNSs) + if (allRDNSs.includes('io.metamask.mobile') && CoreHelperUtil.isMobile()) { + const index = allRDNSs.indexOf('io.metamask.mobile') + allRDNSs[index] = 'io.metamask' + } + const filtered = wallets.filter(wallet => !allRDNSs.includes(String(wallet?.rdns))) + + return filtered + } + + private onConnectWallet(wallet: WcWallet) { + RouterController.push('ConnectingWalletConnect', { wallet }) + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-connect-custom-widget': W3mConnectCustomWidget + } +} diff --git a/packages/scaffold/src/partials/w3m-connect-external-widget/index.ts b/packages/scaffold/src/partials/w3m-connect-external-widget/index.ts new file mode 100644 index 0000000000..47fadf52ee --- /dev/null +++ b/packages/scaffold/src/partials/w3m-connect-external-widget/index.ts @@ -0,0 +1,59 @@ +import type { Connector } from '@web3modal/core' +import { AssetUtil, ConnectorController, RouterController } from '@web3modal/core' +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' +import { state } from 'lit/decorators.js' +import { ifDefined } from 'lit/directives/if-defined.js' + +@customElement('w3m-connect-external-widget') +export class W3mConnectExternalWidget extends LitElement { + // -- Members ------------------------------------------- // + private unsubscribe: (() => void)[] = [] + + // -- State & Properties -------------------------------- // + @state() private connectors = ConnectorController.state.connectors + + public constructor() { + super() + this.unsubscribe.push( + ConnectorController.subscribeKey('connectors', val => (this.connectors = val)) + ) + } + + public override disconnectedCallback() { + this.unsubscribe.forEach(unsubscribe => unsubscribe()) + } + + // -- Render -------------------------------------------- // + public override render() { + const externalConnectors = this.connectors.filter( + connector => !['WALLET_CONNECT', 'INJECTED', 'ANNOUNCED', 'AUTH'].includes(connector.type) + ) + + if (!externalConnectors?.length) { + this.style.cssText = `display: none` + + return null + } + + return html` + + ${externalConnectors.map( + connector => html` + this.onConnector(connector)} + > + + ` + )} + + ` + } + + // -- Private Methods ----------------------------------- // + private onConnector(connector: Connector) { + RouterController.push('ConnectingExternal', { connector }) + } +} diff --git a/packages/scaffold/src/partials/w3m-connect-featured-widget/index.ts b/packages/scaffold/src/partials/w3m-connect-featured-widget/index.ts new file mode 100644 index 0000000000..35946ee992 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-connect-featured-widget/index.ts @@ -0,0 +1,89 @@ +import type { WcWallet } from '@web3modal/core' +import { + ApiController, + AssetUtil, + ConnectorController, + CoreHelperUtil, + RouterController, + StorageUtil +} from '@web3modal/core' +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' +import { state } from 'lit/decorators.js' +import { ifDefined } from 'lit/directives/if-defined.js' + +@customElement('w3m-connect-featured-widget') +export class W3mConnectFeaturedWidget extends LitElement { + // -- Members ------------------------------------------- // + private unsubscribe: (() => void)[] = [] + + // -- State & Properties -------------------------------- // + @state() private connectors = ConnectorController.state.connectors + + public constructor() { + super() + this.unsubscribe.push( + ConnectorController.subscribeKey('connectors', val => (this.connectors = val)) + ) + } + + public override disconnectedCallback() { + this.unsubscribe.forEach(unsubscribe => unsubscribe()) + } + + // -- Render -------------------------------------------- // + public override render() { + const { featured } = ApiController.state + if (!featured.length) { + this.style.cssText = `display: none` + + return null + } + + const wallets = this.filterOutDuplicateWallets(featured) + + return html` + + ${wallets.map( + wallet => html` + this.onConnectWallet(wallet)} + > + + ` + )} + + ` + } + + // -- Private Methods ----------------------------------- // + private filterOutDuplicateWallets(wallets: WcWallet[]) { + const recent = StorageUtil.getRecentWallets() + + const connectorRDNSs = this.connectors + .map(connector => connector.info?.rdns) + .filter(Boolean) as string[] + + const recentRDNSs = recent.map(wallet => wallet.rdns).filter(Boolean) as string[] + const allRDNSs = connectorRDNSs.concat(recentRDNSs) + if (allRDNSs.includes('io.metamask.mobile') && CoreHelperUtil.isMobile()) { + const index = allRDNSs.indexOf('io.metamask.mobile') + allRDNSs[index] = 'io.metamask' + } + const filtered = wallets.filter(wallet => !allRDNSs.includes(String(wallet?.rdns))) + + return filtered + } + + private onConnectWallet(wallet: WcWallet) { + RouterController.push('ConnectingWalletConnect', { wallet }) + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-connect-featured-widget': W3mConnectFeaturedWidget + } +} diff --git a/packages/scaffold/src/partials/w3m-connect-injected-widget/index.ts b/packages/scaffold/src/partials/w3m-connect-injected-widget/index.ts new file mode 100644 index 0000000000..799f9371a7 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-connect-injected-widget/index.ts @@ -0,0 +1,86 @@ +import type { Connector } from '@web3modal/core' +import { + AssetUtil, + ConnectionController, + ConnectorController, + CoreHelperUtil, + RouterController +} from '@web3modal/core' +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' +import { state } from 'lit/decorators.js' +import { ifDefined } from 'lit/directives/if-defined.js' + +@customElement('w3m-connect-injected-widget') +export class W3mConnectInjectedWidget extends LitElement { + // -- Members ------------------------------------------- // + private unsubscribe: (() => void)[] = [] + + // -- State & Properties -------------------------------- // + @state() private connectors = ConnectorController.state.connectors + + public constructor() { + super() + this.unsubscribe.push( + ConnectorController.subscribeKey('connectors', val => (this.connectors = val)) + ) + } + + public override disconnectedCallback() { + this.unsubscribe.forEach(unsubscribe => unsubscribe()) + } + + // -- Render -------------------------------------------- // + public override render() { + const injectedConnectors = this.connectors.filter(connector => connector.type === 'INJECTED') + + if ( + !injectedConnectors?.length || + (injectedConnectors.length === 1 && + injectedConnectors[0]?.name === 'Browser Wallet' && + !CoreHelperUtil.isMobile()) + ) { + this.style.cssText = `display: none` + + return null + } + + return html` + + ${injectedConnectors.map(connector => { + if (!CoreHelperUtil.isMobile() && connector.name === 'Browser Wallet') { + return null + } + + if (!ConnectionController.checkInstalled()) { + return null + } + + return html` + this.onConnector(connector)} + > + + ` + })} + + ` + } + + // -- Private Methods ----------------------------------- // + private onConnector(connector: Connector) { + RouterController.push('ConnectingExternal', { connector }) + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-connect-injected-widget': W3mConnectInjectedWidget + } +} diff --git a/packages/scaffold/src/partials/w3m-connect-recent-widget/index.ts b/packages/scaffold/src/partials/w3m-connect-recent-widget/index.ts new file mode 100644 index 0000000000..18d01554c4 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-connect-recent-widget/index.ts @@ -0,0 +1,47 @@ +import type { WcWallet } from '@web3modal/core' +import { AssetUtil, RouterController, StorageUtil } from '@web3modal/core' +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' +import { ifDefined } from 'lit/directives/if-defined.js' + +@customElement('w3m-connect-recent-widget') +export class W3mConnectRecentWidget extends LitElement { + // -- Render -------------------------------------------- // + public override render() { + const recent = StorageUtil.getRecentWallets() + + if (!recent?.length) { + this.style.cssText = `display: none` + + return null + } + + return html` + + ${recent.map(wallet => { + return html` + this.onConnectWallet(wallet)} + tagLabel="recent" + tagVariant="shade" + > + + ` + })} + + ` + } + + // -- Private Methods ----------------------------------- // + private onConnectWallet(wallet: WcWallet) { + RouterController.push('ConnectingWalletConnect', { wallet }) + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-connect-recent-widget': W3mConnectRecentWidget + } +} diff --git a/packages/scaffold/src/partials/w3m-connect-recommended-widget/index.ts b/packages/scaffold/src/partials/w3m-connect-recommended-widget/index.ts new file mode 100644 index 0000000000..ec7e20051e --- /dev/null +++ b/packages/scaffold/src/partials/w3m-connect-recommended-widget/index.ts @@ -0,0 +1,111 @@ +import type { WcWallet } from '@web3modal/core' +import { + ApiController, + AssetUtil, + ConnectorController, + CoreHelperUtil, + OptionsController, + RouterController, + StorageUtil +} from '@web3modal/core' +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' +import { state } from 'lit/decorators.js' +import { ifDefined } from 'lit/directives/if-defined.js' + +@customElement('w3m-connect-recommended-widget') +export class W3mConnectRecommendedWidget extends LitElement { + // -- Members ------------------------------------------- // + private unsubscribe: (() => void)[] = [] + + // -- State & Properties -------------------------------- // + @state() private connectors = ConnectorController.state.connectors + + public constructor() { + super() + this.unsubscribe.push( + ConnectorController.subscribeKey('connectors', val => (this.connectors = val)) + ) + } + + public override disconnectedCallback() { + this.unsubscribe.forEach(unsubscribe => unsubscribe()) + } + + // -- Render -------------------------------------------- // + public override render() { + const connector = this.connectors.find(c => c.type === 'WALLET_CONNECT') + if (!connector) { + return null + } + const { recommended } = ApiController.state + const { customWallets, featuredWalletIds } = OptionsController.state + const { connectors } = ConnectorController.state + const recent = StorageUtil.getRecentWallets() + + const injected = connectors.filter(c => c.type === 'INJECTED' || c.type === 'ANNOUNCED') + const injectedWallets = injected.filter(i => i.name !== 'Browser Wallet') + + if (featuredWalletIds || customWallets || !recommended.length) { + this.style.cssText = `display: none` + + return null + } + + const overrideLength = injectedWallets.length + recent.length + + const maxRecommended = Math.max(0, 2 - overrideLength) + + const wallets = this.filterOutDuplicateWallets(recommended).slice(0, maxRecommended) + + if (!wallets.length) { + this.style.cssText = `display: none` + + return null + } + + return html` + + ${wallets.map( + wallet => html` + this.onConnectWallet(wallet)} + > + + ` + )} + + ` + } + + // -- Private Methods ----------------------------------- // + private filterOutDuplicateWallets(wallets: WcWallet[]) { + const recent = StorageUtil.getRecentWallets() + + const connectorRDNSs = this.connectors + .map(connector => connector.info?.rdns) + .filter(Boolean) as string[] + + const recentRDNSs = recent.map(wallet => wallet.rdns).filter(Boolean) as string[] + const allRDNSs = connectorRDNSs.concat(recentRDNSs) + if (allRDNSs.includes('io.metamask.mobile') && CoreHelperUtil.isMobile()) { + const index = allRDNSs.indexOf('io.metamask.mobile') + allRDNSs[index] = 'io.metamask' + } + const filtered = wallets.filter(wallet => !allRDNSs.includes(String(wallet?.rdns))) + + return filtered + } + + private onConnectWallet(wallet: WcWallet) { + RouterController.push('ConnectingWalletConnect', { wallet }) + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-connect-recommended-widget': W3mConnectRecommendedWidget + } +} diff --git a/packages/scaffold/src/partials/w3m-connect-walletconnect-widget/index.ts b/packages/scaffold/src/partials/w3m-connect-walletconnect-widget/index.ts new file mode 100644 index 0000000000..f817c34e7b --- /dev/null +++ b/packages/scaffold/src/partials/w3m-connect-walletconnect-widget/index.ts @@ -0,0 +1,73 @@ +import type { Connector } from '@web3modal/core' +import { AssetUtil, ConnectorController, CoreHelperUtil, RouterController } from '@web3modal/core' +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' +import { state } from 'lit/decorators.js' +import { ifDefined } from 'lit/directives/if-defined.js' + +@customElement('w3m-connect-walletconnect-widget') +export class W3mConnectWalletConnectWidget extends LitElement { + // -- Members ------------------------------------------- // + private unsubscribe: (() => void)[] = [] + + // -- State & Properties -------------------------------- // + @state() private connectors = ConnectorController.state.connectors + + public constructor() { + super() + this.unsubscribe.push( + ConnectorController.subscribeKey('connectors', val => (this.connectors = val)) + ) + } + + public override disconnectedCallback() { + this.unsubscribe.forEach(unsubscribe => unsubscribe()) + } + + // -- Render -------------------------------------------- // + public override render() { + if (CoreHelperUtil.isMobile()) { + this.style.cssText = `display: none` + + return null + } + + const connector = this.connectors.find(c => c.type === 'WALLET_CONNECT') + if (!connector) { + this.style.cssText = `display: none` + + return null + } + + return html` + this.onConnector(connector)} + tagLabel="qr code" + tagVariant="main" + data-testid="wallet-selector-walletconnect" + > + + ` + } + + // -- Private Methods ----------------------------------- // + private onConnector(connector: Connector) { + if (connector.type === 'WALLET_CONNECT') { + if (CoreHelperUtil.isMobile()) { + RouterController.push('AllWallets') + } else { + RouterController.push('ConnectingWalletConnect') + } + } else { + RouterController.push('ConnectingExternal', { connector }) + } + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-connect-walletconnect-widget': W3mConnectWalletConnectWidget + } +} diff --git a/packages/scaffold/src/partials/w3m-email-login-widget/index.ts b/packages/scaffold/src/partials/w3m-email-login-widget/index.ts index ffd84b50c1..f76442886a 100644 --- a/packages/scaffold/src/partials/w3m-email-login-widget/index.ts +++ b/packages/scaffold/src/partials/w3m-email-login-widget/index.ts @@ -46,8 +46,8 @@ export class W3mEmailLoginWidget extends LitElement { // -- Render -------------------------------------------- // public override render() { - const multipleConnectors = this.connectors.length > 1 const connector = this.connectors.find(c => c.type === 'AUTH') + const multipleConnectors = this.connectors.length > 1 if (!connector?.email) { return null @@ -67,7 +67,11 @@ export class W3mEmailLoginWidget extends LitElement { - ${multipleConnectors ? html`` : null} + ${connector.socials || !multipleConnectors + ? null + : html` + + `} ` } diff --git a/packages/scaffold/src/partials/w3m-header/index.ts b/packages/scaffold/src/partials/w3m-header/index.ts index d75f640997..07d6b5121b 100644 --- a/packages/scaffold/src/partials/w3m-header/index.ts +++ b/packages/scaffold/src/partials/w3m-header/index.ts @@ -1,5 +1,6 @@ import type { RouterControllerState } from '@web3modal/core' import { + AccountController, ConnectionController, ConnectorController, EventsController, @@ -58,7 +59,12 @@ function headings() { SwapPreview: 'Preview swap', WalletSend: 'Send', WalletSendPreview: 'Review send', - WalletSendSelectToken: 'Select Token' + WalletSendSelectToken: 'Select Token', + ConnectWallets: 'Connect wallet', + ConnectSocials: 'All socials', + ConnectingSocial: AccountController.state.socialProvider + ? AccountController.state.socialProvider + : 'Connect Social' } } diff --git a/packages/scaffold/src/partials/w3m-header/styles.ts b/packages/scaffold/src/partials/w3m-header/styles.ts index 929e2456db..f95c496dec 100644 --- a/packages/scaffold/src/partials/w3m-header/styles.ts +++ b/packages/scaffold/src/partials/w3m-header/styles.ts @@ -5,6 +5,10 @@ export default css` height: 64px; } + wui-text { + text-transform: capitalize; + } + wui-icon-link[data-hidden='true'] { opacity: 0 !important; pointer-events: none; diff --git a/packages/scaffold/src/partials/w3m-social-login-list/index.ts b/packages/scaffold/src/partials/w3m-social-login-list/index.ts new file mode 100644 index 0000000000..83ca3b0636 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-social-login-list/index.ts @@ -0,0 +1,52 @@ +import { ConnectorController } from '@web3modal/core' +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' +import { state } from 'lit/decorators.js' +import styles from './styles.js' +@customElement('w3m-social-login-list') +export class W3mSocialLoginList extends LitElement { + public static override styles = styles + + // -- Members ------------------------------------------- // + private unsubscribe: (() => void)[] = [] + + // -- State & Properties -------------------------------- // + @state() private connectors = ConnectorController.state.connectors + + private connector = this.connectors.find(c => c.type === 'AUTH') + + public constructor() { + super() + this.unsubscribe.push( + ConnectorController.subscribeKey('connectors', val => { + this.connectors = val + this.connector = this.connectors.find(c => c.type === 'AUTH') + }) + ) + } + + public override disconnectedCallback() { + this.unsubscribe.forEach(unsubscribe => unsubscribe()) + } + + // -- Render -------------------------------------------- // + public override render() { + if (!this.connector?.socials) { + return null + } + + return html` + ${this.connector.socials.map( + social => html`` + )} + ` + } + + // -- Private ------------------------------------------- // +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-social-login-list': W3mSocialLoginList + } +} diff --git a/packages/scaffold/src/partials/w3m-social-login-list/styles.ts b/packages/scaffold/src/partials/w3m-social-login-list/styles.ts new file mode 100644 index 0000000000..ac7e429975 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-social-login-list/styles.ts @@ -0,0 +1,12 @@ +import { css } from 'lit' + +export default css` + :host { + margin-top: var(--wui-spacing-3xs); + } + wui-separator { + margin: var(--wui-spacing-m) calc(var(--wui-spacing-m) * -1) var(--wui-spacing-xs) + calc(var(--wui-spacing-m) * -1); + width: calc(100% + var(--wui-spacing-s) * 2); + } +` diff --git a/packages/scaffold/src/partials/w3m-social-login-widget/index.ts b/packages/scaffold/src/partials/w3m-social-login-widget/index.ts new file mode 100644 index 0000000000..db74337c8b --- /dev/null +++ b/packages/scaffold/src/partials/w3m-social-login-widget/index.ts @@ -0,0 +1,160 @@ +import { + AccountController, + ConnectorController, + CoreHelperUtil, + RouterController, + SnackController, + type SocialProvider +} from '@web3modal/core' +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' +import { state } from 'lit/decorators.js' + +import styles from './styles.js' +import { ifDefined } from 'lit/directives/if-defined.js' + +const MAX_TOP_VIEW = 2 +const MAXIMUM_LENGTH = 6 + +@customElement('w3m-social-login-widget') +export class W3mSocialLoginWidget extends LitElement { + public static override styles = styles + + // -- Members ------------------------------------------- // + private unsubscribe: (() => void)[] = [] + + // -- State & Properties -------------------------------- // + @state() private connectors = ConnectorController.state.connectors + + private connector = this.connectors.find(c => c.type === 'AUTH') + + public constructor() { + super() + this.unsubscribe.push( + ConnectorController.subscribeKey('connectors', val => { + this.connectors = val + this.connector = this.connectors.find(c => c.type === 'AUTH') + }) + ) + } + + public override disconnectedCallback() { + this.unsubscribe.forEach(unsubscribe => unsubscribe()) + } + + // -- Render -------------------------------------------- // + public override render() { + if (!this.connector?.socials) { + return null + } + + return html` + + ${this.topViewTemplate()}${this.bottomViewTemplate()} + + + ` + } + + // -- Private ------------------------------------------- // + private topViewTemplate() { + if (!this.connector?.socials) { + return null + } + + if (this.connector.socials.length === 2) { + return html` + ${this.connector.socials.slice(0, MAX_TOP_VIEW).map( + social => + html` { + this.onSocialClick(social) + }} + logo=${social} + >` + )} + ` + } + + return html` { + this.onSocialClick(this.connector?.socials?.[0]) + }} + logo=${ifDefined(this.connector.socials[0])} + align="center" + name=${`Continue with ${this.connector.socials[0]}`} + >` + } + + private bottomViewTemplate() { + if (!this.connector?.socials) { + return null + } + + if (this.connector?.socials.length <= MAX_TOP_VIEW) { + return null + } + + if (this.connector?.socials.length > MAXIMUM_LENGTH) { + return html` + ${this.connector.socials.slice(1, MAXIMUM_LENGTH - 1).map( + social => + html` { + this.onSocialClick(social) + }} + logo=${social} + >` + )} + + ` + } + + return html` + ${this.connector.socials.slice(1, this.connector.socials.length).map( + social => + html` { + this.onSocialClick(social) + }} + logo=${social} + >` + )} + ` + } + + // -- Private Methods ----------------------------------- // + onMoreSocialsClick() { + RouterController.push('ConnectSocials') + } + + async onSocialClick(socialProvider?: SocialProvider) { + const authConnector = ConnectorController.getAuthConnector() + try { + if (authConnector && socialProvider) { + const { uri } = await authConnector.provider.getSocialRedirectUri({ + provider: socialProvider + }) + AccountController.setSocialProvider(socialProvider) + // Window.open doesn't work on ios withing an async function, wrapping it in a setTimeout fixes this + setTimeout(() => { + CoreHelperUtil.openHref(uri, 'popupWindow', 'width=600,height=800,scrollbars=yes') + }) + + RouterController.push('ConnectingSocial') + } + } catch (error) { + SnackController.showError('Something went wrong') + } + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-social-login-widget': W3mSocialLoginWidget + } +} diff --git a/packages/scaffold/src/partials/w3m-social-login-widget/styles.ts b/packages/scaffold/src/partials/w3m-social-login-widget/styles.ts new file mode 100644 index 0000000000..6bd2b524a6 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-social-login-widget/styles.ts @@ -0,0 +1,12 @@ +import { css } from 'lit' + +export default css` + wui-flex:first-child { + margin-top: var(--wui-spacing-s); + } + wui-separator { + margin: var(--wui-spacing-m) calc(var(--wui-spacing-m) * -1) var(--wui-spacing-m) + calc(var(--wui-spacing-m) * -1); + width: calc(100% + var(--wui-spacing-s) * 2); + } +` diff --git a/packages/scaffold/src/partials/w3m-wallet-login-list/index.ts b/packages/scaffold/src/partials/w3m-wallet-login-list/index.ts new file mode 100644 index 0000000000..f1ec8f72a0 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-wallet-login-list/index.ts @@ -0,0 +1,30 @@ +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' + +import styles from './styles.js' +@customElement('w3m-wallet-login-list') +export class W3mWalletLoginList extends LitElement { + public static override styles = styles + + // -- Render -------------------------------------------- // + public override render() { + return html` + + + + + + + + + + + ` + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-wallet-login-list': W3mWalletLoginList + } +} diff --git a/packages/scaffold/src/partials/w3m-wallet-login-list/styles.ts b/packages/scaffold/src/partials/w3m-wallet-login-list/styles.ts new file mode 100644 index 0000000000..ac7e429975 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-wallet-login-list/styles.ts @@ -0,0 +1,12 @@ +import { css } from 'lit' + +export default css` + :host { + margin-top: var(--wui-spacing-3xs); + } + wui-separator { + margin: var(--wui-spacing-m) calc(var(--wui-spacing-m) * -1) var(--wui-spacing-xs) + calc(var(--wui-spacing-m) * -1); + width: calc(100% + var(--wui-spacing-s) * 2); + } +` diff --git a/packages/scaffold/src/utils/ConstantsUtil.ts b/packages/scaffold/src/utils/ConstantsUtil.ts index 62a6e66b03..88c7eaee31 100644 --- a/packages/scaffold/src/utils/ConstantsUtil.ts +++ b/packages/scaffold/src/utils/ConstantsUtil.ts @@ -1,3 +1,5 @@ export const ConstantsUtil = { - ACCOUNT_TABS: [{ label: 'Tokens' }, { label: 'NFTs' }, { label: 'Activity' }] + ACCOUNT_TABS: [{ label: 'Tokens' }, { label: 'NFTs' }, { label: 'Activity' }], + SECURE_SITE_ORIGIN: + process.env['NEXT_PUBLIC_SECURE_SITE_ORIGIN'] || 'https://secure.walletconnect.com' } diff --git a/packages/scaffold/src/views/w3m-connect-socials-view/index.ts b/packages/scaffold/src/views/w3m-connect-socials-view/index.ts new file mode 100644 index 0000000000..0ec8560ce5 --- /dev/null +++ b/packages/scaffold/src/views/w3m-connect-socials-view/index.ts @@ -0,0 +1,25 @@ +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' + +import styles from './styles.js' + +@customElement('w3m-connect-socials-view') +export class W3mConnectSocialsView extends LitElement { + public static override styles = styles + + // -- Render -------------------------------------------- // + public override render() { + return html` + + + + + ` + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-connect-socials-view': W3mConnectSocialsView + } +} diff --git a/packages/scaffold/src/views/w3m-connect-socials-view/styles.ts b/packages/scaffold/src/views/w3m-connect-socials-view/styles.ts new file mode 100644 index 0000000000..a7b23cd611 --- /dev/null +++ b/packages/scaffold/src/views/w3m-connect-socials-view/styles.ts @@ -0,0 +1,12 @@ +import { css } from 'lit' + +export default css` + wui-flex { + max-height: clamp(360px, 540px, 80vh); + overflow: scroll; + scrollbar-width: none; + } + wui-flex::-webkit-scrollbar { + display: none; + } +` diff --git a/packages/scaffold/src/views/w3m-connect-view/index.ts b/packages/scaffold/src/views/w3m-connect-view/index.ts index 505b1590f1..97e2eea80a 100644 --- a/packages/scaffold/src/views/w3m-connect-view/index.ts +++ b/packages/scaffold/src/views/w3m-connect-view/index.ts @@ -1,21 +1,8 @@ -import type { Connector, WcWallet } from '@web3modal/core' -import { - ApiController, - AssetUtil, - ConnectionController, - ConnectorController, - CoreHelperUtil, - EventsController, - OptionsController, - RouterController, - StorageUtil -} from '@web3modal/core' import { customElement } from '@web3modal/ui' import { LitElement, html } from 'lit' -import { state } from 'lit/decorators.js' -import { ifDefined } from 'lit/directives/if-defined.js' import styles from './styles.js' -import { ConstantsUtil } from '@web3modal/scaffold-utils' +import { ConnectorController, RouterController } from '@web3modal/core' +import { state } from 'lit/decorators/state.js' @customElement('w3m-connect-view') export class W3mConnectView extends LitElement { @@ -26,13 +13,11 @@ export class W3mConnectView extends LitElement { // -- State & Properties -------------------------------- // @state() private connectors = ConnectorController.state.connectors - @state() private count = ApiController.state.count public constructor() { super() this.unsubscribe.push( - ConnectorController.subscribeKey('connectors', val => (this.connectors = val)), - ApiController.subscribeKey('count', val => (this.count = val)) + ConnectorController.subscribeKey('connectors', val => (this.connectors = val)) ) } @@ -45,281 +30,46 @@ export class W3mConnectView extends LitElement { return html` - - ${this.walletConnectConnectorTemplate()} ${this.recentTemplate()} - ${this.announcedTemplate()} ${this.injectedTemplate()} ${this.featuredTemplate()} - ${this.customTemplate()} ${this.recommendedTemplate()} ${this.externalTemplate()} - ${this.allWalletsTemplate()} - + + ${this.walletListTemplate()} ` } // -- Private ------------------------------------------- // - private walletConnectConnectorTemplate() { - if (CoreHelperUtil.isMobile()) { - return null - } - - const connector = this.connectors.find(c => c.type === 'WALLET_CONNECT') - if (!connector) { - return null - } - - return html` - this.onConnector(connector)} - tagLabel="qr code" - tagVariant="main" - data-testid="wallet-selector-walletconnect" - > - - ` - } - - private customTemplate() { - const { customWallets } = OptionsController.state - - if (!customWallets?.length) { - return null - } - - const wallets = this.filterOutDuplicateWallets(customWallets) - - return wallets.map( - wallet => html` - this.onConnectWallet(wallet)} - data-testid=${`wallet-selector-${wallet.id}`} - > - - ` - ) - } - - private featuredTemplate() { - const connector = this.connectors.find(c => c.type === 'WALLET_CONNECT') - if (!connector) { - return null - } - - const { featured } = ApiController.state - if (!featured.length) { - return null - } - - const wallets = this.filterOutDuplicateWallets(featured) - - return wallets.map( - wallet => html` - this.onConnectWallet(wallet)} - > - - ` - ) - } - - private recentTemplate() { - const recent = StorageUtil.getRecentWallets() - - return recent.map( - wallet => html` - this.onConnectWallet(wallet)} - tagLabel="recent" - tagVariant="shade" - > - - ` - ) - } - - private announcedTemplate() { - return this.connectors.map(connector => { - if (connector.type !== 'ANNOUNCED') { - return null - } - - return html` - this.onConnector(connector)} - tagVariant="success" - tagLabel="installed" - data-testid=${`wallet-selector-${connector.id}`} - .installed=${true} - > - - ` - }) - } - - private injectedTemplate() { - return this.connectors.map(connector => { - if (connector.type !== 'INJECTED') { - return null - } - - if (!CoreHelperUtil.isMobile() && connector.name === 'Browser Wallet') { - return null + private walletListTemplate() { + const authConnector = this.connectors.find(c => c.type === 'AUTH') + + if (authConnector?.socials) { + if (authConnector?.showWallets) { + return html` + + + + + + + + + + + + ` } - if (!ConnectionController.checkInstalled()) { - return null - } - - return html` - this.onConnector(connector)} - > - - ` - }) - } - - private externalTemplate() { - const announcedRdns = ConnectorController.getAnnouncedConnectorRdns() - - return this.connectors.map(connector => { - if (['WALLET_CONNECT', 'INJECTED', 'ANNOUNCED', 'AUTH'].includes(connector.type)) { - return null - } - - if (announcedRdns.includes(ConstantsUtil.CONNECTOR_RDNS_MAP[connector.id])) { - return null - } - - return html` - this.onConnector(connector)} - > - - ` - }) - } - - private allWalletsTemplate() { - const connector = this.connectors.find(c => c.type === 'WALLET_CONNECT') - const { allWallets } = OptionsController.state - - if (!connector || allWallets === 'HIDE') { - return null - } - - if (allWallets === 'ONLY_MOBILE' && !CoreHelperUtil.isMobile()) { - return null - } - - const featuredCount = ApiController.state.featured.length - const rawCount = this.count + featuredCount - const roundedCount = rawCount < 10 ? rawCount : Math.floor(rawCount / 10) * 10 - const tagLabel = roundedCount < rawCount ? `${roundedCount}+` : `${roundedCount}` - - return html` - - ` - } - - private recommendedTemplate() { - const connector = this.connectors.find(c => c.type === 'WALLET_CONNECT') - if (!connector) { - return null - } - const { recommended } = ApiController.state - const { customWallets, featuredWalletIds } = OptionsController.state - const { connectors } = ConnectorController.state - const recent = StorageUtil.getRecentWallets() - - const injected = connectors.filter(c => c.type === 'INJECTED' || c.type === 'ANNOUNCED') - const injectedWallets = injected.filter(i => i.name !== 'Browser Wallet') - - if (featuredWalletIds || customWallets || !recommended.length) { - return null + return html` ` } - const overrideLength = injectedWallets.length + recent.length - - const maxRecommended = Math.max(0, 2 - overrideLength) - - const wallets = this.filterOutDuplicateWallets(recommended).slice(0, maxRecommended) - - return wallets.map( - wallet => html` - this.onConnectWallet(wallet)} - > - - ` - ) + return html`` } // -- Private Methods ----------------------------------- // - private onConnector(connector: Connector) { - if (connector.type === 'WALLET_CONNECT') { - if (CoreHelperUtil.isMobile()) { - RouterController.push('AllWallets') - } else { - RouterController.push('ConnectingWalletConnect') - } - } else { - RouterController.push('ConnectingExternal', { connector }) - } - } - - private filterOutDuplicateWallets(wallets: WcWallet[]) { - const recent = StorageUtil.getRecentWallets() - - const connectorRDNSs = this.connectors - .map(connector => connector.info?.rdns) - .filter(Boolean) as string[] - - const recentRDNSs = recent.map(wallet => wallet.rdns).filter(Boolean) as string[] - const allRDNSs = connectorRDNSs.concat(recentRDNSs) - if (allRDNSs.includes('io.metamask.mobile') && CoreHelperUtil.isMobile()) { - const index = allRDNSs.indexOf('io.metamask.mobile') - allRDNSs[index] = 'io.metamask' - } - const filtered = wallets.filter(wallet => !allRDNSs.includes(String(wallet?.rdns))) - - return filtered - } - - private onAllWallets() { - EventsController.sendEvent({ type: 'track', event: 'CLICK_ALL_WALLETS' }) - RouterController.push('AllWallets') - } - - private onConnectWallet(wallet: WcWallet) { - RouterController.push('ConnectingWalletConnect', { wallet }) + private onContinueWalletClick() { + RouterController.push('ConnectWallets') } } diff --git a/packages/scaffold/src/views/w3m-connect-view/styles.ts b/packages/scaffold/src/views/w3m-connect-view/styles.ts index 4e6807dd4d..007fa2dc1d 100644 --- a/packages/scaffold/src/views/w3m-connect-view/styles.ts +++ b/packages/scaffold/src/views/w3m-connect-view/styles.ts @@ -11,4 +11,8 @@ export default css` :host > wui-flex::-webkit-scrollbar { display: none; } + + .all-wallets { + flex-flow: column; + } ` diff --git a/packages/scaffold/src/views/w3m-connect-wallets-view/index.ts b/packages/scaffold/src/views/w3m-connect-wallets-view/index.ts new file mode 100644 index 0000000000..dc3435d649 --- /dev/null +++ b/packages/scaffold/src/views/w3m-connect-wallets-view/index.ts @@ -0,0 +1,25 @@ +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' + +import styles from './styles.js' + +@customElement('w3m-connect-wallets-view') +export class W3mConnectWalletsView extends LitElement { + public static override styles = styles + + // -- Render -------------------------------------------- // + public override render() { + return html` + + + + + ` + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-connect-wallets-view': W3mConnectWalletsView + } +} diff --git a/packages/scaffold/src/views/w3m-connect-wallets-view/styles.ts b/packages/scaffold/src/views/w3m-connect-wallets-view/styles.ts new file mode 100644 index 0000000000..a7b23cd611 --- /dev/null +++ b/packages/scaffold/src/views/w3m-connect-wallets-view/styles.ts @@ -0,0 +1,12 @@ +import { css } from 'lit' + +export default css` + wui-flex { + max-height: clamp(360px, 540px, 80vh); + overflow: scroll; + scrollbar-width: none; + } + wui-flex::-webkit-scrollbar { + display: none; + } +` diff --git a/packages/scaffold/src/views/w3m-connecting-social-view/index.ts b/packages/scaffold/src/views/w3m-connecting-social-view/index.ts new file mode 100644 index 0000000000..33903054eb --- /dev/null +++ b/packages/scaffold/src/views/w3m-connecting-social-view/index.ts @@ -0,0 +1,135 @@ +import { + AccountController, + ConnectionController, + ConnectorController, + ModalController, + RouterController, + SnackController, + ThemeController +} from '@web3modal/core' +import { customElement } from '@web3modal/ui' +import { LitElement, html } from 'lit' +import { state } from 'lit/decorators.js' +import { ifDefined } from 'lit/directives/if-defined.js' +import styles from './styles.js' +import { ConstantsUtil } from '../../utils/ConstantsUtil.js' + +@customElement('w3m-connecting-social-view') +export class W3mConnectingSocialView extends LitElement { + public static override styles = styles + + // -- Members ------------------------------------------- // + private unsubscribe: (() => void)[] = [] + + // -- State & Properties -------------------------------- // + @state() private socialProvider = AccountController.state.socialProvider + + @state() protected error = false + + @state() protected connecting = false + + public authConnector = ConnectorController.getAuthConnector() + + public constructor() { + super() + this.unsubscribe.push( + ...[ + AccountController.subscribe(val => { + if (val.socialProvider) { + this.socialProvider = val.socialProvider + } + if (val.address) { + if (ModalController.state.open) { + ModalController.close() + } + } + }) + ] + ) + if (this.authConnector) { + this.connectSocial() + } + } + + public override disconnectedCallback() { + this.unsubscribe.forEach(unsubscribe => unsubscribe()) + + window.removeEventListener('message', this.handleSocialConnection, false) + } + + // -- Render -------------------------------------------- // + public override render() { + return html` + + + + ${this.error ? null : this.loaderTemplate()} + + + + Log in with + ${this.socialProvider ?? 'Social'} + ${this.error ? 'Something went wrong' : 'Connect in the provider window'} + + ` + } + + // -- Private ------------------------------------------- // + private loaderTemplate() { + const borderRadiusMaster = ThemeController.state.themeVariables['--w3m-border-radius-master'] + const radius = borderRadiusMaster ? parseInt(borderRadiusMaster.replace('px', ''), 10) : 4 + + return html`` + } + + private handleSocialConnection = async (event: MessageEvent) => { + if (event.data?.resultUri) { + if (event.origin === ConstantsUtil.SECURE_SITE_ORIGIN) { + window.removeEventListener('message', this.handleSocialConnection, false) + try { + if (this.authConnector && !this.connecting) { + this.connecting = true + const uri = event.data.resultUri as string + + await this.authConnector.provider.connectSocial(uri) + await ConnectionController.connectExternal(this.authConnector) + } + } catch (error) { + this.error = true + } + } else { + RouterController.goBack() + SnackController.showError('Untrusted Origin') + } + } + } + + private connectSocial() { + window.addEventListener('message', this.handleSocialConnection, false) + } +} + +declare global { + interface HTMLElementTagNameMap { + 'w3m-connecting-social-view': W3mConnectingSocialView + } +} diff --git a/packages/scaffold/src/views/w3m-connecting-social-view/styles.ts b/packages/scaffold/src/views/w3m-connecting-social-view/styles.ts new file mode 100644 index 0000000000..78409d8ef6 --- /dev/null +++ b/packages/scaffold/src/views/w3m-connecting-social-view/styles.ts @@ -0,0 +1,54 @@ +import { css } from 'lit' + +export default css` + wui-logo { + width: 80px; + height: 80px; + border-radius: var(--wui-border-radius-m); + } + @keyframes shake { + 0% { + transform: translateX(0); + } + 25% { + transform: translateX(3px); + } + 50% { + transform: translateX(-3px); + } + 75% { + transform: translateX(3px); + } + 100% { + transform: translateX(0); + } + } + wui-flex:first-child:not(:only-child) { + position: relative; + } + wui-loading-thumbnail { + position: absolute; + } + wui-icon-box { + position: absolute; + right: calc(var(--wui-spacing-3xs) * -1); + bottom: calc(var(--wui-spacing-3xs) * -1); + opacity: 0; + transform: scale(0.5); + transition: all var(--wui-ease-out-power-2) var(--wui-duration-lg); + } + wui-text[align='center'] { + width: 100%; + padding: 0px var(--wui-spacing-l); + } + [data-error='true'] wui-icon-box { + opacity: 1; + transform: scale(1); + } + [data-error='true'] > wui-flex:first-child { + animation: shake 250ms cubic-bezier(0.36, 0.07, 0.19, 0.97) both; + } + .capitalize { + text-transform: capitalize; + } +` diff --git a/packages/wagmi/src/client.ts b/packages/wagmi/src/client.ts index b0a0288565..63f82b7fa6 100644 --- a/packages/wagmi/src/client.ts +++ b/packages/wagmi/src/client.ts @@ -557,6 +557,7 @@ export class Web3Modal extends Web3ModalScaffold { ) as unknown as Web3ModalClientOptions['wagmiConfig']['connectors'][0] & { email: boolean socials: SocialProvider[] + showWallets?: boolean } if (authConnector) { const provider = await authConnector.getProvider() @@ -566,7 +567,8 @@ export class Web3Modal extends Web3ModalScaffold { name: 'Auth', provider, email: authConnector.email, - socials: authConnector.socials + socials: authConnector.socials, + showWallets: authConnector?.showWallets === undefined ? true : authConnector.showWallets }) } } diff --git a/packages/wagmi/src/connectors/AuthConnector.ts b/packages/wagmi/src/connectors/AuthConnector.ts index bf90b2c88e..0e88670617 100644 --- a/packages/wagmi/src/connectors/AuthConnector.ts +++ b/packages/wagmi/src/connectors/AuthConnector.ts @@ -18,6 +18,7 @@ export type AuthParameters = { options: W3mFrameProviderOptions socials?: SocialProvider[] email?: boolean + showWallets?: boolean } // -- Connector ------------------------------------------------------------------------------------ @@ -32,6 +33,7 @@ export function authConnector(parameters: AuthParameters) { type: 'w3mAuth', socials: parameters.socials, email: parameters.email, + showWallets: parameters.showWallets, async connect(options: ConnectOptions = {}) { const provider = await this.getProvider() diff --git a/packages/wagmi/src/utils/defaultWagmiCoreConfig.ts b/packages/wagmi/src/utils/defaultWagmiCoreConfig.ts index bcaf32fc50..f51e632d7d 100644 --- a/packages/wagmi/src/utils/defaultWagmiCoreConfig.ts +++ b/packages/wagmi/src/utils/defaultWagmiCoreConfig.ts @@ -15,6 +15,7 @@ export type ConfigOptions = Partial & { enableEmail?: boolean auth?: { socials?: SocialProvider[] + showWallets?: boolean } enableInjected?: boolean enableWalletConnect?: boolean @@ -33,7 +34,9 @@ export function defaultWagmiConfig({ enableCoinbase, enableEmail, enableInjected, - auth, + auth = { + showWallets: true + }, enableWalletConnect, enableEIP6963, ...wagmiConfig @@ -71,7 +74,8 @@ export function defaultWagmiConfig({ chains: [...chains], options: { projectId }, socials: auth?.socials, - email: enableEmail + email: enableEmail, + showWallets: auth.showWallets }) ) } diff --git a/packages/wagmi/src/utils/defaultWagmiReactConfig.ts b/packages/wagmi/src/utils/defaultWagmiReactConfig.ts index 170bebebdb..b82bf0d185 100644 --- a/packages/wagmi/src/utils/defaultWagmiReactConfig.ts +++ b/packages/wagmi/src/utils/defaultWagmiReactConfig.ts @@ -15,6 +15,7 @@ export type ConfigOptions = Partial & { enableEmail?: boolean auth?: { socials?: SocialProvider[] + showWallets?: boolean } enableInjected?: boolean enableWalletConnect?: boolean @@ -33,7 +34,9 @@ export function defaultWagmiConfig({ enableCoinbase, enableEmail, enableInjected, - auth, + auth = { + showWallets: true + }, enableWalletConnect, enableEIP6963, ...wagmiConfig @@ -71,7 +74,8 @@ export function defaultWagmiConfig({ chains: [...chains], options: { projectId }, socials: auth?.socials, - email: enableEmail + email: enableEmail, + showWallets: auth.showWallets }) ) }