diff --git a/apps/laboratory/src/pages/library/ethers-verify-domain-mismatch.tsx b/apps/laboratory/src/pages/library/ethers-verify-domain-mismatch.tsx new file mode 100644 index 0000000000..f38a100abf --- /dev/null +++ b/apps/laboratory/src/pages/library/ethers-verify-domain-mismatch.tsx @@ -0,0 +1,39 @@ +import { createWeb3Modal, defaultConfig } from '@web3modal/ethers/react' +import { EthersTests } from '../../components/Ethers/EthersTests' +import { AppKitButtons } from '../../components/AppKitButtons' +import { ThemeStore } from '../../utils/StoreUtil' +import { EthersConstants } from '../../utils/EthersConstants' +import { ConstantsUtil } from '../../utils/ConstantsUtil' +import { EthersModalInfo } from '../../components/Ethers/EthersModalInfo' + +// Special project ID with verify enabled on localhost +const projectId = 'e4eae1aad4503db9966a04fd045a7e4d' + +const modal = createWeb3Modal({ + ethersConfig: defaultConfig({ + metadata: ConstantsUtil.Metadata, + defaultChainId: 1, + chains: EthersConstants.chains, + coinbasePreference: 'smartWalletOnly' + }), + chains: EthersConstants.chains, + defaultChain: EthersConstants.chains[1], + projectId, + enableAnalytics: true, + metadata: ConstantsUtil.Metadata, + termsConditionsUrl: 'https://walletconnect.com/terms', + privacyPolicyUrl: 'https://walletconnect.com/privacy', + customWallets: ConstantsUtil.CustomWallets +}) + +ThemeStore.setModal(modal) + +export default function Ethers() { + return ( + <> + + + + + ) +} diff --git a/apps/laboratory/src/pages/library/ethers-verify-evil.tsx b/apps/laboratory/src/pages/library/ethers-verify-evil.tsx new file mode 100644 index 0000000000..2b24288d57 --- /dev/null +++ b/apps/laboratory/src/pages/library/ethers-verify-evil.tsx @@ -0,0 +1,47 @@ +import { createWeb3Modal, defaultConfig } from '@web3modal/ethers/react' +import { EthersTests } from '../../components/Ethers/EthersTests' +import { AppKitButtons } from '../../components/AppKitButtons' +import { ThemeStore } from '../../utils/StoreUtil' +import { EthersConstants } from '../../utils/EthersConstants' +import { ConstantsUtil } from '../../utils/ConstantsUtil' +import { EthersModalInfo } from '../../components/Ethers/EthersModalInfo' + +const metadata = { + name: 'Evil Web3Modal', + description: 'Evil Web3Modal Laboratory', + url: 'https://malicious-app-verify-simulation.vercel.app/', + icons: ['https://avatars.githubusercontent.com/u/37784886'], + verifyUrl: '' +} + +// Special project ID with https://malicious-app-verify-simulation.vercel.app/ as the verified domain and this domain is marked as a scam +const projectId = '9d176efa3150a1df0a76c8c138b6b657' + +const modal = createWeb3Modal({ + ethersConfig: defaultConfig({ + metadata, + defaultChainId: 1, + chains: EthersConstants.chains, + coinbasePreference: 'smartWalletOnly' + }), + chains: EthersConstants.chains, + defaultChain: EthersConstants.chains[1], + projectId, + enableAnalytics: true, + metadata, + termsConditionsUrl: 'https://walletconnect.com/terms', + privacyPolicyUrl: 'https://walletconnect.com/privacy', + customWallets: ConstantsUtil.CustomWallets +}) + +ThemeStore.setModal(modal) + +export default function Ethers() { + return ( + <> + + + + + ) +} diff --git a/apps/laboratory/src/pages/library/ethers-verify-valid.tsx b/apps/laboratory/src/pages/library/ethers-verify-valid.tsx new file mode 100644 index 0000000000..614aead757 --- /dev/null +++ b/apps/laboratory/src/pages/library/ethers-verify-valid.tsx @@ -0,0 +1,48 @@ +import { createWeb3Modal, defaultConfig } from '@web3modal/ethers/react' +import { EthersTests } from '../../components/Ethers/EthersTests' +import { AppKitButtons } from '../../components/AppKitButtons' +import { ThemeStore } from '../../utils/StoreUtil' +import { EthersConstants } from '../../utils/EthersConstants' +import { ConstantsUtil } from '../../utils/ConstantsUtil' +import { EthersModalInfo } from '../../components/Ethers/EthersModalInfo' + +const metadata = { + name: 'Web3Modal', + description: 'Web3Modal Laboratory', + // Allow localhost + url: 'http://localhost:3000', + icons: ['https://avatars.githubusercontent.com/u/37784886'], + verifyUrl: '' +} + +// Special project ID with verify enabled on localhost +const projectId = 'e4eae1aad4503db9966a04fd045a7e4d' + +const modal = createWeb3Modal({ + ethersConfig: defaultConfig({ + metadata, + defaultChainId: 1, + chains: EthersConstants.chains, + coinbasePreference: 'smartWalletOnly' + }), + chains: EthersConstants.chains, + defaultChain: EthersConstants.chains[1], + projectId, + enableAnalytics: true, + metadata, + termsConditionsUrl: 'https://walletconnect.com/terms', + privacyPolicyUrl: 'https://walletconnect.com/privacy', + customWallets: ConstantsUtil.CustomWallets +}) + +ThemeStore.setModal(modal) + +export default function Ethers() { + return ( + <> + + + + + ) +} diff --git a/apps/laboratory/src/pages/library/verify-domain-mismatch.tsx b/apps/laboratory/src/pages/library/wagmi-verify-domain-mismatch.tsx similarity index 100% rename from apps/laboratory/src/pages/library/verify-domain-mismatch.tsx rename to apps/laboratory/src/pages/library/wagmi-verify-domain-mismatch.tsx diff --git a/apps/laboratory/src/pages/library/verify-evil.tsx b/apps/laboratory/src/pages/library/wagmi-verify-evil.tsx similarity index 100% rename from apps/laboratory/src/pages/library/verify-evil.tsx rename to apps/laboratory/src/pages/library/wagmi-verify-evil.tsx diff --git a/apps/laboratory/src/pages/library/verify-valid.tsx b/apps/laboratory/src/pages/library/wagmi-verify-valid.tsx similarity index 100% rename from apps/laboratory/src/pages/library/verify-valid.tsx rename to apps/laboratory/src/pages/library/wagmi-verify-valid.tsx diff --git a/apps/laboratory/tests/shared/fixtures/w3m-ethers-verify-domain-mismatch-fixture.ts b/apps/laboratory/tests/shared/fixtures/w3m-ethers-verify-domain-mismatch-fixture.ts new file mode 100644 index 0000000000..f673489a0f --- /dev/null +++ b/apps/laboratory/tests/shared/fixtures/w3m-ethers-verify-domain-mismatch-fixture.ts @@ -0,0 +1,12 @@ +import type { ModalFixture } from './w3m-fixture' +import { ModalPage } from '../pages/ModalPage' +import { timingFixture } from './timing-fixture' + +export const testMEthersVerifyDomainMismatch = timingFixture.extend({ + library: ['ethers', { option: true }], + modalPage: async ({ page, library }, use) => { + const modalPage = new ModalPage(page, library, 'ethers-verify-domain-mismatch') + await modalPage.load() + await use(modalPage) + } +}) diff --git a/apps/laboratory/tests/shared/fixtures/w3m-ethers-verify-evil-fixture.ts b/apps/laboratory/tests/shared/fixtures/w3m-ethers-verify-evil-fixture.ts new file mode 100644 index 0000000000..44bbd88634 --- /dev/null +++ b/apps/laboratory/tests/shared/fixtures/w3m-ethers-verify-evil-fixture.ts @@ -0,0 +1,12 @@ +import type { ModalFixture } from './w3m-fixture' +import { ModalPage } from '../pages/ModalPage' +import { timingFixture } from './timing-fixture' + +export const testMEthersVerifyEvil = timingFixture.extend({ + library: ['ethers', { option: true }], + modalPage: async ({ page, library }, use) => { + const modalPage = new ModalPage(page, library, 'ethers-verify-evil') + await modalPage.load() + await use(modalPage) + } +}) diff --git a/apps/laboratory/tests/shared/fixtures/w3m-ethers-verify-valid-fixture.ts b/apps/laboratory/tests/shared/fixtures/w3m-ethers-verify-valid-fixture.ts new file mode 100644 index 0000000000..a141d4a5e9 --- /dev/null +++ b/apps/laboratory/tests/shared/fixtures/w3m-ethers-verify-valid-fixture.ts @@ -0,0 +1,12 @@ +import type { ModalFixture } from './w3m-fixture' +import { ModalPage } from '../pages/ModalPage' +import { timingFixture } from './timing-fixture' + +export const testMEthersVerifyValid = timingFixture.extend({ + library: ['ethers', { option: true }], + modalPage: async ({ page, library }, use) => { + const modalPage = new ModalPage(page, library, 'ethers-verify-valid') + await modalPage.load() + await use(modalPage) + } +}) diff --git a/apps/laboratory/tests/shared/fixtures/w3m-fixture.ts b/apps/laboratory/tests/shared/fixtures/w3m-fixture.ts index 886b56f0c5..9e0358fe90 100644 --- a/apps/laboratory/tests/shared/fixtures/w3m-fixture.ts +++ b/apps/laboratory/tests/shared/fixtures/w3m-fixture.ts @@ -23,6 +23,18 @@ export const testM = timingFixture.extend({ await use(modalPage) } }) +export const testMEthers = timingFixture.extend({ + library: ['ethers', { option: true }], + modalPage: async ({ page, library }, use) => { + timeStart('new ModalPage') + const modalPage = new ModalPage(page, library, 'default') + timeEnd('new ModalPage') + timeStart('modalPage.load') + await modalPage.load() + timeEnd('modalPage.load') + await use(modalPage) + } +}) export const testMSiwe = timingFixture.extend({ library: ['wagmi', { option: true }], diff --git a/apps/laboratory/tests/shared/fixtures/w3m-verify-domain-mismatch-fixture.ts b/apps/laboratory/tests/shared/fixtures/w3m-wagmi-verify-domain-mismatch-fixture.ts similarity index 63% rename from apps/laboratory/tests/shared/fixtures/w3m-verify-domain-mismatch-fixture.ts rename to apps/laboratory/tests/shared/fixtures/w3m-wagmi-verify-domain-mismatch-fixture.ts index e87f4a3707..539e19650d 100644 --- a/apps/laboratory/tests/shared/fixtures/w3m-verify-domain-mismatch-fixture.ts +++ b/apps/laboratory/tests/shared/fixtures/w3m-wagmi-verify-domain-mismatch-fixture.ts @@ -2,10 +2,10 @@ import type { ModalFixture } from './w3m-fixture' import { ModalPage } from '../pages/ModalPage' import { timingFixture } from './timing-fixture' -export const testMVerifyDomainMismatch = timingFixture.extend({ +export const testMWagmiVerifyDomainMismatch = timingFixture.extend({ library: ['wagmi', { option: true }], modalPage: async ({ page, library }, use) => { - const modalPage = new ModalPage(page, library, 'verify-domain-mismatch') + const modalPage = new ModalPage(page, library, 'wagmi-verify-domain-mismatch') await modalPage.load() await use(modalPage) } diff --git a/apps/laboratory/tests/shared/fixtures/w3m-verify-evil-fixture.ts b/apps/laboratory/tests/shared/fixtures/w3m-wagmi-verify-evil-fixture.ts similarity index 67% rename from apps/laboratory/tests/shared/fixtures/w3m-verify-evil-fixture.ts rename to apps/laboratory/tests/shared/fixtures/w3m-wagmi-verify-evil-fixture.ts index 9e65c6e300..db479b3df3 100644 --- a/apps/laboratory/tests/shared/fixtures/w3m-verify-evil-fixture.ts +++ b/apps/laboratory/tests/shared/fixtures/w3m-wagmi-verify-evil-fixture.ts @@ -2,10 +2,10 @@ import type { ModalFixture } from './w3m-fixture' import { ModalPage } from '../pages/ModalPage' import { timingFixture } from './timing-fixture' -export const testMVerifyEvil = timingFixture.extend({ +export const testMWagmiVerifyEvil = timingFixture.extend({ library: ['wagmi', { option: true }], modalPage: async ({ page, library }, use) => { - const modalPage = new ModalPage(page, library, 'verify-evil') + const modalPage = new ModalPage(page, library, 'wagmi-verify-evil') await modalPage.load() await use(modalPage) } diff --git a/apps/laboratory/tests/shared/fixtures/w3m-verify-valid-fixture.ts b/apps/laboratory/tests/shared/fixtures/w3m-wagmi-verify-valid-fixture.ts similarity index 66% rename from apps/laboratory/tests/shared/fixtures/w3m-verify-valid-fixture.ts rename to apps/laboratory/tests/shared/fixtures/w3m-wagmi-verify-valid-fixture.ts index 8b42c63bb0..749573f5ff 100644 --- a/apps/laboratory/tests/shared/fixtures/w3m-verify-valid-fixture.ts +++ b/apps/laboratory/tests/shared/fixtures/w3m-wagmi-verify-valid-fixture.ts @@ -2,10 +2,10 @@ import type { ModalFixture } from './w3m-fixture' import { ModalPage } from '../pages/ModalPage' import { timingFixture } from './timing-fixture' -export const testMVerifyValid = timingFixture.extend({ +export const testMWagmiVerifyValid = timingFixture.extend({ library: ['wagmi', { option: true }], modalPage: async ({ page, library }, use) => { - const modalPage = new ModalPage(page, library, 'verify-valid') + const modalPage = new ModalPage(page, library, 'wagmi-verify-valid') await modalPage.load() await use(modalPage) } diff --git a/apps/laboratory/tests/shared/pages/ModalPage.ts b/apps/laboratory/tests/shared/pages/ModalPage.ts index 05c1567997..23519042d3 100644 --- a/apps/laboratory/tests/shared/pages/ModalPage.ts +++ b/apps/laboratory/tests/shared/pages/ModalPage.ts @@ -15,9 +15,12 @@ const maliciousUrl = 'https://malicious-app-verify-simulation.vercel.app' export type ModalFlavor = | 'default' | 'external' - | 'verify-valid' - | 'verify-domain-mismatch' - | 'verify-evil' + | 'wagmi-verify-valid' + | 'wagmi-verify-domain-mismatch' + | 'wagmi-verify-evil' + | 'ethers-verify-valid' + | 'ethers-verify-domain-mismatch' + | 'ethers-verify-evil' | 'no-email' | 'no-socials' | 'all' @@ -26,9 +29,12 @@ function getUrlByFlavor(baseUrl: string, library: string, flavor: ModalFlavor) { const urlsByFlavor: Partial> = { default: `${baseUrl}library/${library}/`, external: `${baseUrl}library/external/`, - 'verify-valid': `${baseUrl}library/verify-valid/`, - 'verify-domain-mismatch': `${baseUrl}library/verify-domain-mismatch/`, - 'verify-evil': maliciousUrl + 'wagmi-verify-valid': `${baseUrl}library/wagmi-verify-valid/`, + 'wagmi-verify-domain-mismatch': `${baseUrl}library/wagmi-verify-domain-mismatch/`, + 'wagmi-verify-evil': maliciousUrl, + 'ethers-verify-valid': `${baseUrl}library/ethers-verify-valid/`, + 'ethers-verify-domain-mismatch': `${baseUrl}library/ethers-verify-domain-mismatch/`, + 'ethers-verify-evil': maliciousUrl } return urlsByFlavor[flavor] || `${baseUrl}library/${library}-${flavor}/` @@ -51,8 +57,11 @@ export class ModalPage { } async load() { - if (this.flavor === 'verify-evil') { - await routeInterceptUrl(this.page, maliciousUrl, this.baseURL, '/library/verify-evil/') + if (this.flavor === 'wagmi-verify-evil') { + await routeInterceptUrl(this.page, maliciousUrl, this.baseURL, '/library/wagmi-verify-evil/') + } + if (this.flavor === 'ethers-verify-evil') { + await routeInterceptUrl(this.page, maliciousUrl, this.baseURL, '/library/ethers-verify-evil/') } await this.page.goto(this.url) diff --git a/apps/laboratory/tests/verify.spec.ts b/apps/laboratory/tests/verify.spec.ts index 7f68b5f3af..fd819e0f60 100644 --- a/apps/laboratory/tests/verify.spec.ts +++ b/apps/laboratory/tests/verify.spec.ts @@ -1,15 +1,19 @@ import { DEFAULT_CHAIN_NAME, DEFAULT_SESSION_PARAMS } from './shared/constants' -import { testM } from './shared/fixtures/w3m-fixture' -import { testMVerifyDomainMismatch } from './shared/fixtures/w3m-verify-domain-mismatch-fixture' -import { testMVerifyEvil } from './shared/fixtures/w3m-verify-evil-fixture' -import { testMVerifyValid } from './shared/fixtures/w3m-verify-valid-fixture' +import { testM as testMWagmi } from './shared/fixtures/w3m-fixture' +import { testMWagmiVerifyDomainMismatch } from './shared/fixtures/w3m-wagmi-verify-domain-mismatch-fixture' +import { testMWagmiVerifyEvil } from './shared/fixtures/w3m-wagmi-verify-evil-fixture' +import { testMWagmiVerifyValid } from './shared/fixtures/w3m-wagmi-verify-valid-fixture' +import { testMEthers } from './shared/fixtures/w3m-fixture' +import { testMEthersVerifyDomainMismatch } from './shared/fixtures/w3m-ethers-verify-domain-mismatch-fixture' +import { testMEthersVerifyEvil } from './shared/fixtures/w3m-ethers-verify-evil-fixture' +import { testMEthersVerifyValid } from './shared/fixtures/w3m-ethers-verify-valid-fixture' import { WalletPage } from './shared/pages/WalletPage' import { ModalValidator } from './shared/validators/ModalValidator' import { WalletValidator } from './shared/validators/WalletValidator' import { expect } from '@playwright/test' -testM( - 'connection and signature requests from non-verified project should show as cannot verify', +testMWagmi( + 'wagmi: connection and signature requests from non-verified project should show as cannot verify', async ({ modalPage, context }) => { if (modalPage.library === 'solana') { return @@ -40,8 +44,8 @@ testM( } ) -testMVerifyValid( - 'connection and signature requests from non-scam verified domain should show as domain match', +testMWagmiVerifyValid( + 'wagmi: connection and signature requests from non-scam verified domain should show as domain match', async ({ modalPage, context }) => { if (modalPage.library === 'solana') { return @@ -72,8 +76,8 @@ testMVerifyValid( } ) -testMVerifyDomainMismatch( - 'connection and signature requests from non-scam verified domain but on localhost should show as invalid domain', +testMWagmiVerifyDomainMismatch( + 'wagmi: connection and signature requests from non-scam verified domain but on localhost should show as invalid domain', async ({ modalPage, context }) => { if (modalPage.library === 'solana') { return @@ -104,8 +108,140 @@ testMVerifyDomainMismatch( } ) -testMVerifyEvil( - 'connection and signature requests from scam verified domain should show as scam domain', +testMWagmiVerifyEvil( + 'wagmi: connection and signature requests from scam verified domain should show as scam domain', + async ({ modalPage, context }) => { + if (modalPage.library === 'solana') { + return + } + + const modalValidator = new ModalValidator(modalPage.page) + const walletPage = new WalletPage(await context.newPage()) + await walletPage.load() + const walletValidator = new WalletValidator(walletPage.page) + + const uri = await modalPage.getConnectUri() + await walletPage.connectWithUri(uri) + await expect(walletPage.page.getByText('Website flagged')).toBeVisible() + await walletPage.page.getByText('Proceed anyway').click() + await expect(walletPage.page.getByText('Potential threat')).toBeVisible() + await walletPage.handleSessionProposal(DEFAULT_SESSION_PARAMS) + await modalValidator.expectConnected() + await walletValidator.expectConnected() + + await modalPage.sign() + const chainName = modalPage.library === 'solana' ? 'Solana' : DEFAULT_CHAIN_NAME + await expect(walletPage.page.getByText('Website flagged')).toBeVisible() + await walletPage.page.getByText('Proceed anyway').click() + await walletValidator.expectReceivedSign({ chainName }) + await expect(walletPage.page.getByText('Potential threat')).toBeVisible() + await walletPage.handleRequest({ accept: true }) + await modalValidator.expectAcceptedSign() + + await modalPage.disconnect() + await modalValidator.expectDisconnected() + await walletValidator.expectDisconnected() + } +) + +testMEthers( + 'ethers: connection and signature requests from non-verified project should show as cannot verify', + async ({ modalPage, context }) => { + if (modalPage.library === 'solana') { + return + } + + const modalValidator = new ModalValidator(modalPage.page) + const walletPage = new WalletPage(await context.newPage()) + await walletPage.load() + const walletValidator = new WalletValidator(walletPage.page) + + const uri = await modalPage.getConnectUri() + await walletPage.connectWithUri(uri) + await expect(walletPage.page.getByText('Cannot Verify')).toBeVisible() + await walletPage.handleSessionProposal(DEFAULT_SESSION_PARAMS) + await modalValidator.expectConnected() + await walletValidator.expectConnected() + + await modalPage.sign() + const chainName = modalPage.library === 'solana' ? 'Solana' : DEFAULT_CHAIN_NAME + await walletValidator.expectReceivedSign({ chainName }) + await expect(walletPage.page.getByText('Cannot Verify')).toBeVisible() + await walletPage.handleRequest({ accept: true }) + await modalValidator.expectAcceptedSign() + + await modalPage.disconnect() + await modalValidator.expectDisconnected() + await walletValidator.expectDisconnected() + } +) + +testMEthersVerifyValid( + 'ethers: connection and signature requests from non-scam verified domain should show as domain match', + async ({ modalPage, context }) => { + if (modalPage.library === 'solana') { + return + } + + const modalValidator = new ModalValidator(modalPage.page) + const walletPage = new WalletPage(await context.newPage()) + await walletPage.load() + const walletValidator = new WalletValidator(walletPage.page) + + const uri = await modalPage.getConnectUri() + await walletPage.connectWithUri(uri) + await expect(walletPage.page.getByTestId('session-info-verified')).toBeVisible() + await walletPage.handleSessionProposal(DEFAULT_SESSION_PARAMS) + await modalValidator.expectConnected() + await walletValidator.expectConnected() + + await modalPage.sign() + const chainName = modalPage.library === 'solana' ? 'Solana' : DEFAULT_CHAIN_NAME + await walletValidator.expectReceivedSign({ chainName }) + await expect(walletPage.page.getByTestId('session-info-verified')).toBeVisible() + await walletPage.handleRequest({ accept: true }) + await modalValidator.expectAcceptedSign() + + await modalPage.disconnect() + await modalValidator.expectDisconnected() + await walletValidator.expectDisconnected() + } +) + +testMEthersVerifyDomainMismatch( + 'ethers: connection and signature requests from non-scam verified domain but on localhost should show as invalid domain', + async ({ modalPage, context }) => { + if (modalPage.library === 'solana') { + return + } + + const modalValidator = new ModalValidator(modalPage.page) + const walletPage = new WalletPage(await context.newPage()) + await walletPage.load() + const walletValidator = new WalletValidator(walletPage.page) + + const uri = await modalPage.getConnectUri() + await walletPage.connectWithUri(uri) + await expect(walletPage.page.getByText('Invalid Domain')).toBeVisible() + await walletPage.handleSessionProposal(DEFAULT_SESSION_PARAMS) + await modalValidator.expectConnected() + await walletValidator.expectConnected() + + await modalPage.sign() + const chainName = modalPage.library === 'solana' ? 'Solana' : DEFAULT_CHAIN_NAME + await walletValidator.expectReceivedSign({ chainName }) + await expect(walletPage.page.getByText('Invalid Domain')).toBeVisible() + await walletPage.handleRequest({ accept: true }) + await modalValidator.expectAcceptedSign() + + await modalPage.disconnect() + await modalValidator.expectDisconnected() + await walletValidator.expectDisconnected() + } +) + +testMEthersVerifyEvil( + 'ethers: connection and signature requests from scam verified domain should show as scam domain', async ({ modalPage, context }) => { if (modalPage.library === 'solana') { return