diff --git a/.changeset/eager-carpets-obey.md b/.changeset/eager-carpets-obey.md new file mode 100644 index 0000000000..a089420242 --- /dev/null +++ b/.changeset/eager-carpets-obey.md @@ -0,0 +1,7 @@ +--- +'@reown/appkit-controllers': patch +'@reown/appkit-scaffold-ui': patch +'@reown/appkit': patch +--- + +Fixes issue causing infinite loading after switching from solana to evm using embedded wallet SIWX default diff --git a/apps/laboratory/package.json b/apps/laboratory/package.json index 23ed1ebd5a..672715c8d1 100644 --- a/apps/laboratory/package.json +++ b/apps/laboratory/package.json @@ -14,6 +14,7 @@ "playwright:install-deps": "pnpm playwright install-deps", "synpress": "synpress ./tests/wallet-setup", "playwright:test": "HEADLESS=true playwright test", + "playwright:test:siwx-email": "playwright test --grep 'siwx-email.spec.ts'", "playwright:test:mobile-wallet-features": "playwright test --grep 'mobile-wallet-features.spec.ts'", "playwright:test:basic": "playwright test --grep 'basic-tests.spec.ts'", "playwright:test:flag-enable-reconnect": "HEADLESS=true playwright test --grep 'flag-enable-reconnect.spec.ts'", @@ -61,6 +62,7 @@ "playwright:test:core": "playwright test --grep core.spec.ts", "playwright:debug:core": "pnpm playwright:test:core --debug", "playwright:test:core-universal-provider": "playwright test --grep universal-provider.spec.ts", + "playwright:debug:siwx-email": "pnpm playwright:test:siwx-email --debug", "playwright:debug:core-universal-provider": "pnpm playwright:test:core-universal-provider --debug", "playwright:debug": "pnpm playwright:test --debug", "playwright:debug:mobile-wallet-features": "pnpm playwright:test:mobile-wallet-features --debug", diff --git a/apps/laboratory/tests/shared/pages/ModalWalletPage.ts b/apps/laboratory/tests/shared/pages/ModalWalletPage.ts index 0807890228..88f35b5369 100644 --- a/apps/laboratory/tests/shared/pages/ModalWalletPage.ts +++ b/apps/laboratory/tests/shared/pages/ModalWalletPage.ts @@ -6,12 +6,14 @@ import { setupNetworkListener } from '@/src/utils/NetworkUtil' import { ModalPage } from './ModalPage' +type ModalWalletFlavor = 'default' | 'all' | 'siwe' | 'siwx' + export class ModalWalletPage extends ModalPage { public override readonly page: Page public override readonly library: string - public override readonly flavor: 'default' | 'all' | 'siwe' + public override readonly flavor: ModalWalletFlavor - constructor(page: Page, library: string, flavor: 'default' | 'all' | 'siwe') { + constructor(page: Page, library: string, flavor: ModalWalletFlavor) { super(page, library, flavor) setupNetworkListener(page) this.page = page diff --git a/apps/laboratory/tests/siwx-email.spec.ts b/apps/laboratory/tests/siwx-email.spec.ts new file mode 100644 index 0000000000..462ef86c85 --- /dev/null +++ b/apps/laboratory/tests/siwx-email.spec.ts @@ -0,0 +1,99 @@ +import { timingFixture } from './shared/fixtures/timing-fixture' +import { ModalWalletPage } from './shared/pages/ModalWalletPage' +import { Email } from './shared/utils/email' +import { getTestnet2ByLibrary, getTestnetByLibrary } from './shared/utils/namespace' +import { ModalValidator } from './shared/validators/ModalValidator' + +/* eslint-disable init-declarations */ +let modalPage: ModalWalletPage +let modalValidator: ModalValidator + +const targetLibraries = ['ethers', 'solana'] + +// -- Setup -------------------------------------------------------------------- +const siwxEmailTest = timingFixture.extend<{ library: string }>({ + library: ['ethers', { option: true }] +}) + +siwxEmailTest.describe.configure({ mode: 'serial' }) + +siwxEmailTest.beforeAll(async ({ library, browser }) => { + if (!targetLibraries.includes(library)) { + return + } + const context = await browser.newContext() + const browserPage = await context.newPage() + + modalPage = new ModalWalletPage(browserPage, library, 'siwx') + modalValidator = new ModalValidator(browserPage) + + await modalPage.load() + + siwxEmailTest.setTimeout(300000) + + const mailsacApiKey = process.env['MAILSAC_API_KEY'] + if (!mailsacApiKey) { + throw new Error('MAILSAC_API_KEY is not set') + } + const email = new Email(mailsacApiKey) + + const emailAddress = await email.getEmailAddressToUse() + await modalPage.emailFlow({ + emailAddress, + context, + mailsacApiKey + }) + + // Should do 1CA, so no need to approve prompt siwe + await modalValidator.expectConnected() +}) + +siwxEmailTest( + 'it should require request signature when switching networks', + async ({ library }) => { + if (!targetLibraries.includes(library)) { + return + } + const network = getTestnetByLibrary(library) + + await modalPage.switchNetwork(network) + await modalPage.promptSiwe() + await modalPage.approveSign() + await modalValidator.expectConnected() + } +) + +siwxEmailTest( + 'it should fallback to the last session when cancel siwe from AppKit', + async ({ library }) => { + if (!targetLibraries.includes(library)) { + return + } + const newNetwork = getTestnet2ByLibrary(library) + const prevNetwork = getTestnetByLibrary(library) + + await modalPage.switchNetwork(newNetwork) + await modalPage.cancelSiwe() + await modalValidator.expectNetworkButton(prevNetwork) + await modalValidator.expectConnected() + } +) + +siwxEmailTest( + 'it should be connected after connecting and refreshing the page', + async ({ library }) => { + if (!targetLibraries.includes(library)) { + return + } + await modalPage.page.reload() + await modalValidator.expectConnected() + } +) + +siwxEmailTest('it should disconnect', async ({ library }) => { + if (!targetLibraries.includes(library)) { + return + } + await modalPage.disconnectWithHook() + await modalValidator.expectDisconnected() +}) diff --git a/packages/appkit/exports/constants.ts b/packages/appkit/exports/constants.ts index c1359f435b..f8939537b8 100644 --- a/packages/appkit/exports/constants.ts +++ b/packages/appkit/exports/constants.ts @@ -1 +1 @@ -export const PACKAGE_VERSION = '1.7.13' +export const PACKAGE_VERSION = '1.7.15' diff --git a/packages/controllers/src/controllers/ChainController.ts b/packages/controllers/src/controllers/ChainController.ts index 8896496cf7..824324db25 100644 --- a/packages/controllers/src/controllers/ChainController.ts +++ b/packages/controllers/src/controllers/ChainController.ts @@ -600,15 +600,15 @@ const controller = { }) }, - checkIfSupportedNetwork(namespace: ChainNamespace, caipNetwork?: CaipNetwork) { - const activeCaipNetwork = caipNetwork || state.activeCaipNetwork + checkIfSupportedNetwork(namespace: ChainNamespace, caipNetworkId?: CaipNetworkId) { + const activeCaipNetworkId = caipNetworkId || state.activeCaipNetwork?.caipNetworkId const requestedCaipNetworks = ChainController.getRequestedCaipNetworks(namespace) if (!requestedCaipNetworks.length) { return true } - return requestedCaipNetworks?.some(network => network.id === activeCaipNetwork?.id) + return requestedCaipNetworks?.some(network => network.caipNetworkId === activeCaipNetworkId) }, checkIfSupportedChainId(chainId: number | string) { diff --git a/packages/controllers/src/utils/SIWXUtil.ts b/packages/controllers/src/utils/SIWXUtil.ts index 91105c1451..74f5993b61 100644 --- a/packages/controllers/src/utils/SIWXUtil.ts +++ b/packages/controllers/src/utils/SIWXUtil.ts @@ -28,16 +28,15 @@ export const SIWXUtil = { return OptionsController.state.siwx }, - async initializeIfEnabled() { + async initializeIfEnabled(caipAddress = ChainController.getActiveCaipAddress()) { const siwx = OptionsController.state.siwx - const caipAddress = ChainController.getActiveCaipAddress() if (!(siwx && caipAddress)) { return } const [namespace, chainId, address] = caipAddress.split(':') as [ChainNamespace, string, string] - if (!ChainController.checkIfSupportedNetwork(namespace)) { + if (!ChainController.checkIfSupportedNetwork(namespace, `${namespace}:${chainId}`)) { return } @@ -260,8 +259,10 @@ export const SIWXUtil = { } } + const caipNetwork = `${chainNamespace}:${chainId}` as CaipNetworkId + const siwxMessage = await siwx.createMessage({ - chainId: ChainController.getActiveCaipNetwork()?.caipNetworkId || ('' as CaipNetworkId), + chainId: caipNetwork, accountAddress: '<>' }) diff --git a/packages/scaffold-ui/src/modal/w3m-account-button/index.ts b/packages/scaffold-ui/src/modal/w3m-account-button/index.ts index 0bdc148294..c55870a750 100644 --- a/packages/scaffold-ui/src/modal/w3m-account-button/index.ts +++ b/packages/scaffold-ui/src/modal/w3m-account-button/index.ts @@ -77,7 +77,10 @@ class W3mAccountButtonBase extends LitElement { 'networkState', val => { this.setNetworkData(val) - this.isSupported = ChainController.checkIfSupportedNetwork(namespace, val?.caipNetwork) + this.isSupported = ChainController.checkIfSupportedNetwork( + namespace, + val?.caipNetwork?.caipNetworkId + ) }, namespace ) diff --git a/packages/scaffold-ui/src/modal/w3m-modal/index.ts b/packages/scaffold-ui/src/modal/w3m-modal/index.ts index 4302986620..c557e125e6 100644 --- a/packages/scaffold-ui/src/modal/w3m-modal/index.ts +++ b/packages/scaffold-ui/src/modal/w3m-modal/index.ts @@ -239,7 +239,7 @@ export class W3mModalBase extends LitElement { ModalController.close() } - await SIWXUtil.initializeIfEnabled() + await SIWXUtil.initializeIfEnabled(caipAddress) this.caipAddress = caipAddress ChainController.setIsSwitchingNamespace(false) } diff --git a/packages/scaffold-ui/src/views/w3m-network-switch-view/index.ts b/packages/scaffold-ui/src/views/w3m-network-switch-view/index.ts index cf56a6cc9a..8bb434a2a9 100644 --- a/packages/scaffold-ui/src/views/w3m-network-switch-view/index.ts +++ b/packages/scaffold-ui/src/views/w3m-network-switch-view/index.ts @@ -136,14 +136,14 @@ export class W3mNetworkSwitchView extends LitElement { } } - private onSwitchNetwork() { + private async onSwitchNetwork() { try { this.error = false if (ChainController.state.activeChain !== this.network?.chainNamespace) { ChainController.setIsSwitchingNamespace(true) } if (this.network) { - ChainController.switchActiveNetwork(this.network) + await ChainController.switchActiveNetwork(this.network) } } catch (error) { this.error = true