From 6584fd5b42b7f2dba0f64177f75dbc9c94fde61c Mon Sep 17 00:00:00 2001 From: Sven Date: Thu, 12 Dec 2024 09:51:17 +0100 Subject: [PATCH 1/4] fix unsupportedChain param --- apps/laboratory/src/pages/library/wagmi.tsx | 8 +--- packages/appkit/src/client.ts | 4 ++ packages/appkit/src/tests/appkit.test.ts | 47 +++++++++++++++++++ .../core/src/controllers/ChainController.ts | 3 +- .../core/src/controllers/OptionsController.ts | 9 ++++ .../controllers/OptionsController.test.ts | 5 ++ .../src/modal/w3m-account-button/index.ts | 9 ++-- .../src/modal/w3m-network-button/index.ts | 7 +-- 8 files changed, 79 insertions(+), 13 deletions(-) diff --git a/apps/laboratory/src/pages/library/wagmi.tsx b/apps/laboratory/src/pages/library/wagmi.tsx index 035dfd6df2..1972e7f61d 100644 --- a/apps/laboratory/src/pages/library/wagmi.tsx +++ b/apps/laboratory/src/pages/library/wagmi.tsx @@ -1,5 +1,5 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { fallback, http, WagmiProvider } from 'wagmi' +import { WagmiProvider } from 'wagmi' import { AppKitButtons } from '../../components/AppKitButtons' import { WagmiTests } from '../../components/Wagmi/WagmiTests' import { WagmiModalInfo } from '../../components/Wagmi/WagmiModalInfo' @@ -7,17 +7,13 @@ import { WagmiAdapter } from '@reown/appkit-adapter-wagmi' import { createAppKit } from '@reown/appkit/react' import { ConstantsUtil } from '../../utils/ConstantsUtil' import { ThemeStore } from '../../utils/StoreUtil' -import { mainnet } from 'viem/chains' const queryClient = new QueryClient() const wagmiAdapter = new WagmiAdapter({ ssr: true, networks: ConstantsUtil.EvmNetworks, - projectId: ConstantsUtil.ProjectId, - transports: { - [mainnet.id]: http('https://foo-bar-baz.quiknode.pro') - } + projectId: ConstantsUtil.ProjectId }) const modal = createAppKit({ diff --git a/packages/appkit/src/client.ts b/packages/appkit/src/client.ts index 558f5f6e79..a6d9ef784f 100644 --- a/packages/appkit/src/client.ts +++ b/packages/appkit/src/client.ts @@ -653,6 +653,10 @@ export class AppKit { OptionsController.setSdkVersion(options.sdkVersion) OptionsController.setEnableEmbedded(options.enableEmbedded) + if (options.allowUnsupportedChain) { + OptionsController.setAllowUnsupportedChain(options.allowUnsupportedChain) + } + if (!options.projectId) { AlertController.open(ErrorUtil.ALERT_ERRORS.PROJECT_ID_NOT_CONFIGURED, 'error') diff --git a/packages/appkit/src/tests/appkit.test.ts b/packages/appkit/src/tests/appkit.test.ts index 49e32d7bd3..01814144a4 100644 --- a/packages/appkit/src/tests/appkit.test.ts +++ b/packages/appkit/src/tests/appkit.test.ts @@ -635,6 +635,53 @@ describe('Base', () => { expect(isClientSpy).toHaveBeenCalled() }) + it('should not show unsupported chain UI when allowUnsupportedChain is true', async () => { + vi.mocked(ChainController).state = { + chains: new Map([['eip155', { namespace: 'eip155' }]]), + activeChain: 'eip155' + } as any + ;(appKit as any).caipNetworks = [{ id: 'eip155:1', chainNamespace: 'eip155' }] + + vi.mocked(OptionsController).state = { + allowUnsupportedChain: true + } as any + + const mockAdapter = { + getAccounts: vi.fn().mockResolvedValue([]), + syncConnection: vi.fn().mockResolvedValue({ + chainId: 'eip155:999', // Unsupported chain + address: '0x123' + }), + getBalance: vi.fn().mockResolvedValue({ balance: '0', symbol: 'ETH' }), + getProfile: vi.fn().mockResolvedValue({}), + on: vi.fn(), + off: vi.fn(), + emit: vi.fn() + } + + vi.spyOn(appKit as any, 'getAdapter').mockReturnValue(mockAdapter) + + vi.spyOn(StorageUtil, 'setConnectedConnector').mockImplementation(vi.fn()) + + vi.spyOn(appKit as any, 'setUnsupportedNetwork').mockImplementation(vi.fn()) + + vi.spyOn(SafeLocalStorage, 'getItem').mockImplementation((key: string) => { + if (key === SafeLocalStorageKeys.CONNECTED_CONNECTOR) { + return 'test-wallet' + } + if (key === SafeLocalStorageKeys.ACTIVE_CAIP_NETWORK_ID) { + return 'eip155:1' + } + return undefined + }) + + vi.mocked(ChainController.showUnsupportedChainUI).mockImplementation(vi.fn()) + + await (appKit as any).syncExistingConnection() + + expect(ChainController.showUnsupportedChainUI).not.toHaveBeenCalled() + }) + it('should subscribe to providers', () => { const callback = vi.fn() const providers = { diff --git a/packages/core/src/controllers/ChainController.ts b/packages/core/src/controllers/ChainController.ts index c38e44f279..940afdacf9 100644 --- a/packages/core/src/controllers/ChainController.ts +++ b/packages/core/src/controllers/ChainController.ts @@ -22,6 +22,7 @@ import { ModalController } from './ModalController.js' import { EventsController } from './EventsController.js' import { RouterController } from './RouterController.js' import { StorageUtil } from '../utils/StorageUtil.js' +import { OptionsController } from './OptionsController.js' // -- Constants ----------------------------------------- // const accountState: AccountControllerState = { @@ -233,7 +234,7 @@ export const ChainController = { const isSupported = this.checkIfSupportedNetwork(caipNetwork.chainNamespace) - if (!isSupported) { + if (!isSupported && !OptionsController.state.allowUnsupportedChain) { this.showUnsupportedChainUI() } }, diff --git a/packages/core/src/controllers/OptionsController.ts b/packages/core/src/controllers/OptionsController.ts index ed3dd2d6b8..59428c3b97 100644 --- a/packages/core/src/controllers/OptionsController.ts +++ b/packages/core/src/controllers/OptionsController.ts @@ -130,6 +130,11 @@ export interface OptionsControllerStatePublic { * @default false */ enableEmbedded?: boolean + /** + * Allow users to switch to an unsupported chain. + * @default false + */ + allowUnsupportedChain?: boolean } export interface OptionsControllerStateInternal { @@ -292,5 +297,9 @@ export const OptionsController = { setEnableEmbedded(enableEmbedded: OptionsControllerState['enableEmbedded']) { state.enableEmbedded = enableEmbedded + }, + + setAllowUnsupportedChain(allowUnsupportedChain: OptionsControllerState['allowUnsupportedChain']) { + state.allowUnsupportedChain = allowUnsupportedChain } } diff --git a/packages/core/tests/controllers/OptionsController.test.ts b/packages/core/tests/controllers/OptionsController.test.ts index 741f41188a..5de9895b1d 100644 --- a/packages/core/tests/controllers/OptionsController.test.ts +++ b/packages/core/tests/controllers/OptionsController.test.ts @@ -22,4 +22,9 @@ describe('OptionsController', () => { OptionsController.setSdkVersion('react-wagmi-3.0.0') expect(OptionsController.state.sdkVersion).toEqual('react-wagmi-3.0.0') }) + + it('should update state correctly on setAllowUnsupportedChain()', () => { + OptionsController.setAllowUnsupportedChain(true) + expect(OptionsController.state.allowUnsupportedChain).toEqual(true) + }) }) 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 b08e0704d8..6b22240f40 100644 --- a/packages/scaffold-ui/src/modal/w3m-account-button/index.ts +++ b/packages/scaffold-ui/src/modal/w3m-account-button/index.ts @@ -5,7 +5,8 @@ import { AssetUtil, ChainController, CoreHelperUtil, - ModalController + ModalController, + OptionsController } from '@reown/appkit-core' import type { WuiAccountButton } from '@reown/appkit-ui' import { LitElement, html } from 'lit' @@ -82,7 +83,9 @@ class W3mAccountButtonBase extends LitElement { return html` @@ -82,7 +83,7 @@ class W3mNetworkButtonBase extends LitElement { // -- Private ------------------------------------------- // private getLabel() { if (this.network) { - if (!this.isSupported) { + if (!this.isSupported && !OptionsController.state.allowUnsupportedChain) { return 'Switch Network' } From c2ebc4d8659a2552cab6f244cca15b8554d4cfae Mon Sep 17 00:00:00 2001 From: Sven Date: Thu, 12 Dec 2024 09:56:33 +0100 Subject: [PATCH 2/4] add changeset --- .changeset/hip-students-move.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .changeset/hip-students-move.md diff --git a/.changeset/hip-students-move.md b/.changeset/hip-students-move.md new file mode 100644 index 0000000000..cc9ac148ba --- /dev/null +++ b/.changeset/hip-students-move.md @@ -0,0 +1,22 @@ +--- +'@reown/appkit-scaffold-ui': patch +'@reown/appkit': patch +'@reown/appkit-core': patch +'@reown/appkit-adapter-ethers': patch +'@reown/appkit-adapter-ethers5': patch +'@reown/appkit-adapter-solana': patch +'@reown/appkit-adapter-wagmi': patch +'@reown/appkit-utils': patch +'@reown/appkit-cdn': patch +'@reown/appkit-cli': patch +'@reown/appkit-common': patch +'@reown/appkit-experimental': patch +'@reown/appkit-polyfills': patch +'@reown/appkit-siwe': patch +'@reown/appkit-siwx': patch +'@reown/appkit-ui': patch +'@reown/appkit-wallet': patch +'@reown/appkit-wallet-button': patch +--- + +Fix allowUnsupportedChain param to work correctly From bd01d2c7d72eabbc78ceb0bc154dbeb972c4e1fd Mon Sep 17 00:00:00 2001 From: Sven Date: Thu, 12 Dec 2024 12:03:59 +0100 Subject: [PATCH 3/4] add ui tests --- .../src/modal/w3m-account-button/index.ts | 7 +- .../src/modal/w3m-network-button/index.ts | 7 +- .../test/modal/w3m-account-button.test.ts | 128 ++++++++++++++++++ .../test/modal/w3m-network-button.test.ts | 86 ++++++++++++ 4 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 packages/scaffold-ui/test/modal/w3m-account-button.test.ts create mode 100644 packages/scaffold-ui/test/modal/w3m-network-button.test.ts 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 6b22240f40..8bebf69544 100644 --- a/packages/scaffold-ui/src/modal/w3m-account-button/index.ts +++ b/packages/scaffold-ui/src/modal/w3m-account-button/index.ts @@ -40,7 +40,12 @@ class W3mAccountButtonBase extends LitElement { @state() private networkImage = AssetUtil.getNetworkImage(this.network) - @state() private isSupported = true + // eslint-disable-next-line no-nested-ternary + @state() private isSupported = OptionsController.state.allowUnsupportedChain + ? true + : ChainController.state.activeChain + ? ChainController.checkIfSupportedNetwork(ChainController.state.activeChain) + : true // -- Lifecycle ----------------------------------------- // public constructor() { diff --git a/packages/scaffold-ui/src/modal/w3m-network-button/index.ts b/packages/scaffold-ui/src/modal/w3m-network-button/index.ts index e10cee8cbf..f6e947fc36 100644 --- a/packages/scaffold-ui/src/modal/w3m-network-button/index.ts +++ b/packages/scaffold-ui/src/modal/w3m-network-button/index.ts @@ -32,7 +32,12 @@ class W3mNetworkButtonBase extends LitElement { @state() private loading = ModalController.state.loading - @state() private isSupported = true + // eslint-disable-next-line no-nested-ternary + @state() private isSupported = OptionsController.state.allowUnsupportedChain + ? true + : ChainController.state.activeChain + ? ChainController.checkIfSupportedNetwork(ChainController.state.activeChain) + : true // -- Lifecycle ----------------------------------------- // public constructor() { diff --git a/packages/scaffold-ui/test/modal/w3m-account-button.test.ts b/packages/scaffold-ui/test/modal/w3m-account-button.test.ts new file mode 100644 index 0000000000..4a090f2c7e --- /dev/null +++ b/packages/scaffold-ui/test/modal/w3m-account-button.test.ts @@ -0,0 +1,128 @@ +import { expect, html, fixture } from '@open-wc/testing' + +import { + AccountController, + ChainController, + OptionsController, + ModalController, + RouterController +} from '@reown/appkit-core' +import { W3mAccountButton } from '../../src/modal/w3m-account-button' +import { describe, it, afterEach, vi } from 'vitest' +import type { CaipNetwork } from '@reown/appkit-common' + +const mockCaipNetwork: CaipNetwork = { + chainNamespace: 'eip155', + caipNetworkId: 'eip155:1', + id: 1, + name: '', + nativeCurrency: { name: '', symbol: '', decimals: 0 }, + rpcUrls: { default: { http: [], webSocket: undefined } } +} +describe('W3mAccountButton', () => { + afterEach(() => { + vi.clearAllMocks() + }) + + it('should set isUnsupportedChain to false when allowUnsupportedChain is true', async () => { + vi.spyOn(ChainController.state, 'activeChain', 'get').mockReturnValue('eip155') + vi.spyOn(ChainController, 'checkIfSupportedNetwork').mockReturnValue(true) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + allowUnsupportedChain: true + }) + + vi.spyOn(AccountController, 'state', 'get').mockReturnValue({ + ...AccountController.state, + profileName: 'test' + }) + + const button = (await fixture( + html`` + )) as W3mAccountButton + + const accountButton = button.shadowRoot?.querySelector('wui-account-button') + expect(accountButton).to.exist + + expect(accountButton?.isUnsupportedChain).to.equal(false) + }) + + it('should set isUnsupportedChain to true when allowUnsupportedChain is false and chain is unsupported', async () => { + vi.spyOn(ChainController.state, 'activeChain', 'get').mockReturnValue('eip155') + vi.spyOn(ChainController.state, 'activeCaipNetwork', 'get').mockReturnValue(mockCaipNetwork) + vi.spyOn(ChainController, 'checkIfSupportedNetwork').mockReturnValue(false) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + allowUnsupportedChain: false + }) + + const button = (await fixture( + html`` + )) as W3mAccountButton + + const accountButton = button.shadowRoot?.querySelector('wui-account-button') + expect(accountButton).to.exist + expect(accountButton?.isUnsupportedChain).to.equal(true) + }) + + describe('onClick behavior', () => { + it('should open modal normally when chain is supported', async () => { + vi.spyOn(ChainController.state, 'activeChain', 'get').mockReturnValue('eip155') + vi.spyOn(ChainController, 'checkIfSupportedNetwork').mockReturnValue(true) + vi.spyOn(ChainController.state, 'activeCaipAddress', 'get').mockReturnValue( + 'eip155:1:0x0000000000000000000000000000000000000000' + ) + + vi.spyOn(ModalController, 'open') + + const button = await fixture(html``) + const accountButton = button.shadowRoot?.querySelector('wui-account-button') + + await accountButton?.click() + + expect(RouterController.state.view).to.equal('Account') + }) + + it('should open modal normally when chain is not supported and allowUnsupportedChain is true', async () => { + vi.spyOn(ChainController.state, 'activeChain', 'get').mockReturnValue('eip155') + vi.spyOn(ChainController, 'checkIfSupportedNetwork').mockReturnValue(false) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + allowUnsupportedChain: true + }) + vi.spyOn(ChainController.state, 'activeCaipAddress', 'get').mockReturnValue( + 'eip155:1:0x0000000000000000000000000000000000000000' + ) + + vi.spyOn(ModalController, 'open') + + const button = await fixture(html``) + const accountButton = button.shadowRoot?.querySelector('wui-account-button') + + await accountButton?.click() + + expect(RouterController.state.view).to.equal('Account') + }) + + it('should open modal in UnsupportedChain view when chain is not supported and allowUnsupportedChain is false', async () => { + vi.spyOn(ChainController.state, 'activeChain', 'get').mockReturnValue('eip155') + vi.spyOn(ChainController, 'checkIfSupportedNetwork').mockReturnValue(false) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + allowUnsupportedChain: false + }) + vi.spyOn(ChainController.state, 'activeCaipAddress', 'get').mockReturnValue( + 'eip155:1:0x0000000000000000000000000000000000000000' + ) + + vi.spyOn(ModalController, 'open') + + const button = await fixture(html``) + const accountButton = button.shadowRoot?.querySelector('wui-account-button') + + await accountButton?.click() + + expect(RouterController.state.view).to.equal('UnsupportedChain') + }) + }) +}) diff --git a/packages/scaffold-ui/test/modal/w3m-network-button.test.ts b/packages/scaffold-ui/test/modal/w3m-network-button.test.ts new file mode 100644 index 0000000000..e71987daf3 --- /dev/null +++ b/packages/scaffold-ui/test/modal/w3m-network-button.test.ts @@ -0,0 +1,86 @@ +import { expect, html, fixture } from '@open-wc/testing' +import { ChainController, OptionsController } from '@reown/appkit-core' +import { W3mNetworkButton } from '../../src/modal/w3m-network-button' +import { describe, it, afterEach, vi } from 'vitest' +import type { CaipNetwork } from '@reown/appkit-common' + +const mockCaipNetwork: CaipNetwork = { + chainNamespace: 'eip155', + caipNetworkId: 'eip155:1', + id: 1, + name: 'Test Network', + nativeCurrency: { name: '', symbol: '', decimals: 0 }, + rpcUrls: { default: { http: [], webSocket: undefined } } +} + +describe('W3mNetworkButton', () => { + afterEach(() => { + vi.clearAllMocks() + }) + + it('should set isUnsupportedChain to false when allowUnsupportedChain is true', async () => { + vi.spyOn(ChainController.state, 'activeChain', 'get').mockReturnValue('eip155') + vi.spyOn(ChainController, 'checkIfSupportedNetwork').mockReturnValue(false) + vi.spyOn(ChainController.state, 'activeCaipNetwork', 'get').mockReturnValue(mockCaipNetwork) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + allowUnsupportedChain: true + }) + + const button = (await fixture( + html`` + )) as W3mNetworkButton + + const networkButton = button.shadowRoot?.querySelector('wui-network-button') + expect(networkButton).to.exist + expect(networkButton?.isUnsupportedChain).to.equal(false) + }) + + it('should set isUnsupportedChain to true when allowUnsupportedChain is false and chain is unsupported', async () => { + vi.spyOn(ChainController.state, 'activeChain', 'get').mockReturnValue('eip155') + vi.spyOn(ChainController, 'checkIfSupportedNetwork').mockReturnValue(false) + vi.spyOn(ChainController.state, 'activeCaipNetwork', 'get').mockReturnValue(mockCaipNetwork) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + allowUnsupportedChain: false + }) + + const button = (await fixture( + html`` + )) as W3mNetworkButton + + const networkButton = button.shadowRoot?.querySelector('wui-network-button') + expect(networkButton).to.exist + expect(networkButton?.isUnsupportedChain).to.equal(true) + }) + + it('should show "Switch Network" label when chain is unsupported and allowUnsupportedChain is false', async () => { + vi.spyOn(ChainController.state, 'activeChain', 'get').mockReturnValue('eip155') + vi.spyOn(ChainController, 'checkIfSupportedNetwork').mockReturnValue(false) + vi.spyOn(ChainController.state, 'activeCaipNetwork', 'get').mockReturnValue(mockCaipNetwork) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + allowUnsupportedChain: false + }) + + const button = await fixture(html``) + const networkButton = button.shadowRoot?.querySelector('wui-network-button') + + expect(networkButton?.textContent?.trim()).to.equal('Switch Network') + }) + + it('should show network name when allowUnsupportedChain is true even if chain is unsupported', async () => { + vi.spyOn(ChainController.state, 'activeChain', 'get').mockReturnValue('eip155') + vi.spyOn(ChainController, 'checkIfSupportedNetwork').mockReturnValue(false) + vi.spyOn(ChainController.state, 'activeCaipNetwork', 'get').mockReturnValue(mockCaipNetwork) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + ...OptionsController.state, + allowUnsupportedChain: true + }) + + const button = await fixture(html``) + const networkButton = button.shadowRoot?.querySelector('wui-network-button') + + expect(networkButton?.textContent?.trim()).to.equal('Test Network') + }) +}) From b9c44e89c4085882ea09eabc1e98ae6007b4dec8 Mon Sep 17 00:00:00 2001 From: Sven Date: Thu, 12 Dec 2024 12:05:49 +0100 Subject: [PATCH 4/4] add consstant --- .../test/modal/w3m-account-button.test.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/scaffold-ui/test/modal/w3m-account-button.test.ts b/packages/scaffold-ui/test/modal/w3m-account-button.test.ts index 4a090f2c7e..d65255574c 100644 --- a/packages/scaffold-ui/test/modal/w3m-account-button.test.ts +++ b/packages/scaffold-ui/test/modal/w3m-account-button.test.ts @@ -19,6 +19,8 @@ const mockCaipNetwork: CaipNetwork = { nativeCurrency: { name: '', symbol: '', decimals: 0 }, rpcUrls: { default: { http: [], webSocket: undefined } } } + +const mockCaipAddress = 'eip155:1:0x0000000000000000000000000000000000000000' describe('W3mAccountButton', () => { afterEach(() => { vi.clearAllMocks() @@ -69,9 +71,7 @@ describe('W3mAccountButton', () => { it('should open modal normally when chain is supported', async () => { vi.spyOn(ChainController.state, 'activeChain', 'get').mockReturnValue('eip155') vi.spyOn(ChainController, 'checkIfSupportedNetwork').mockReturnValue(true) - vi.spyOn(ChainController.state, 'activeCaipAddress', 'get').mockReturnValue( - 'eip155:1:0x0000000000000000000000000000000000000000' - ) + vi.spyOn(ChainController.state, 'activeCaipAddress', 'get').mockReturnValue(mockCaipAddress) vi.spyOn(ModalController, 'open') @@ -90,9 +90,7 @@ describe('W3mAccountButton', () => { ...OptionsController.state, allowUnsupportedChain: true }) - vi.spyOn(ChainController.state, 'activeCaipAddress', 'get').mockReturnValue( - 'eip155:1:0x0000000000000000000000000000000000000000' - ) + vi.spyOn(ChainController.state, 'activeCaipAddress', 'get').mockReturnValue(mockCaipAddress) vi.spyOn(ModalController, 'open') @@ -111,9 +109,7 @@ describe('W3mAccountButton', () => { ...OptionsController.state, allowUnsupportedChain: false }) - vi.spyOn(ChainController.state, 'activeCaipAddress', 'get').mockReturnValue( - 'eip155:1:0x0000000000000000000000000000000000000000' - ) + vi.spyOn(ChainController.state, 'activeCaipAddress', 'get').mockReturnValue(mockCaipAddress) vi.spyOn(ModalController, 'open')