Skip to content

Commit

Permalink
:fix allowUnsupportedChain param to work correctly (#3447)
Browse files Browse the repository at this point in the history
  • Loading branch information
svenvoskamp authored Dec 12, 2024
1 parent c1a641f commit 85c858f
Show file tree
Hide file tree
Showing 10 changed files with 321 additions and 9 deletions.
22 changes: 22 additions & 0 deletions .changeset/hip-students-move.md
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions packages/appkit/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down
47 changes: 47 additions & 0 deletions packages/appkit/src/tests/appkit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/controllers/ChainController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -233,7 +234,7 @@ export const ChainController = {

const isSupported = this.checkIfSupportedNetwork(caipNetwork.chainNamespace)

if (!isSupported) {
if (!isSupported && !OptionsController.state.allowUnsupportedChain) {
this.showUnsupportedChainUI()
}
},
Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/controllers/OptionsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -292,5 +297,9 @@ export const OptionsController = {

setEnableEmbedded(enableEmbedded: OptionsControllerState['enableEmbedded']) {
state.enableEmbedded = enableEmbedded
},

setAllowUnsupportedChain(allowUnsupportedChain: OptionsControllerState['allowUnsupportedChain']) {
state.allowUnsupportedChain = allowUnsupportedChain
}
}
5 changes: 5 additions & 0 deletions packages/core/tests/controllers/OptionsController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
})
16 changes: 12 additions & 4 deletions packages/scaffold-ui/src/modal/w3m-account-button/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -39,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() {
Expand Down Expand Up @@ -82,7 +88,9 @@ class W3mAccountButtonBase extends LitElement {
return html`
<wui-account-button
.disabled=${Boolean(this.disabled)}
.isUnsupportedChain=${!this.isSupported}
.isUnsupportedChain=${OptionsController.state.allowUnsupportedChain
? false
: !this.isSupported}
address=${ifDefined(CoreHelperUtil.getPlainAddress(this.caipAddress))}
profileName=${ifDefined(this.profileName)}
networkSrc=${ifDefined(this.networkImage)}
Expand All @@ -101,7 +109,7 @@ class W3mAccountButtonBase extends LitElement {

// -- Private ------------------------------------------- //
private onClick() {
if (this.isSupported) {
if (this.isSupported || OptionsController.state.allowUnsupportedChain) {
ModalController.open()
} else {
ModalController.open({ view: 'UnsupportedChain' })
Expand Down
14 changes: 10 additions & 4 deletions packages/scaffold-ui/src/modal/w3m-network-button/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
AssetUtil,
ChainController,
EventsController,
ModalController
ModalController,
OptionsController
} from '@reown/appkit-core'
import type { WuiNetworkButton } from '@reown/appkit-ui'
import { LitElement, html } from 'lit'
Expand All @@ -31,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() {
Expand Down Expand Up @@ -69,7 +75,7 @@ class W3mNetworkButtonBase extends LitElement {
return html`
<wui-network-button
.disabled=${Boolean(this.disabled || this.loading)}
.isUnsupportedChain=${!isSupported}
.isUnsupportedChain=${OptionsController.state.allowUnsupportedChain ? false : !isSupported}
imageSrc=${ifDefined(this.networkImage)}
@click=${this.onClick.bind(this)}
>
Expand All @@ -82,7 +88,7 @@ class W3mNetworkButtonBase extends LitElement {
// -- Private ------------------------------------------- //
private getLabel() {
if (this.network) {
if (!this.isSupported) {
if (!this.isSupported && !OptionsController.state.allowUnsupportedChain) {
return 'Switch Network'
}

Expand Down
124 changes: 124 additions & 0 deletions packages/scaffold-ui/test/modal/w3m-account-button.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
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 } }
}

const mockCaipAddress = 'eip155:1:0x0000000000000000000000000000000000000000'
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`<w3m-account-button></w3m-account-button>`
)) 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`<w3m-account-button></w3m-account-button>`
)) 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(mockCaipAddress)

vi.spyOn(ModalController, 'open')

const button = await fixture(html`<w3m-account-button></w3m-account-button>`)
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(mockCaipAddress)

vi.spyOn(ModalController, 'open')

const button = await fixture(html`<w3m-account-button></w3m-account-button>`)
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(mockCaipAddress)

vi.spyOn(ModalController, 'open')

const button = await fixture(html`<w3m-account-button></w3m-account-button>`)
const accountButton = button.shadowRoot?.querySelector('wui-account-button')

await accountButton?.click()

expect(RouterController.state.view).to.equal('UnsupportedChain')
})
})
})
Loading

0 comments on commit 85c858f

Please sign in to comment.