diff --git a/apps/native/tests/shared/pages/ModalPage.ts b/apps/native/tests/shared/pages/ModalPage.ts index b7e6f1e7..aeef6495 100644 --- a/apps/native/tests/shared/pages/ModalPage.ts +++ b/apps/native/tests/shared/pages/ModalPage.ts @@ -32,7 +32,7 @@ export class ModalPage { const qrLoadInitiatedTime = new Date(); const qrCode = this.page.getByTestId('qr-code'); - await expect(qrCode).toBeVisible(); + await expect(qrCode).toBeVisible({ timeout: 20000 }); const uri = await this.clickCopyLink(); const qrLoadedTime = new Date(); @@ -51,7 +51,7 @@ export class ModalPage { const qrLoadInitiatedTime = new Date(); const qrCode = this.page.getByTestId('qr-code'); - await expect(qrCode).toBeVisible(); + await expect(qrCode).toBeVisible({ timeout: 20000 }); const uri = await this.clickCopyLink(); const qrLoadedTime = new Date(); if (timingRecords) { diff --git a/apps/native/tests/shared/pages/WalletPage.ts b/apps/native/tests/shared/pages/WalletPage.ts index 44a48135..1eb43990 100644 --- a/apps/native/tests/shared/pages/WalletPage.ts +++ b/apps/native/tests/shared/pages/WalletPage.ts @@ -81,8 +81,8 @@ export class WalletPage { timeout: 30000 }); await expect(btn).toBeEnabled(); - await btn.focus(); - await this.page.keyboard.press('Space'); + await this.page.waitForTimeout(1000); + await btn.click(); } /** diff --git a/apps/native/tests/shared/validators/WalletValidator.ts b/apps/native/tests/shared/validators/WalletValidator.ts index c6e292e5..5fb20305 100644 --- a/apps/native/tests/shared/validators/WalletValidator.ts +++ b/apps/native/tests/shared/validators/WalletValidator.ts @@ -28,14 +28,14 @@ export class WalletValidator { async expectSessionCard({ visible = true }: { visible?: boolean }) { if (visible) { await expect( - this.page.getByTestId('session-card'), + this.page.getByTestId('session-card').first(), 'Session card should be visible' ).toBeVisible({ timeout: MAX_WAIT }); } else { await expect( - this.page.getByTestId('session-card'), + this.page.getByTestId('session-card').first(), 'Session card should not be visible' ).not.toBeVisible({ timeout: MAX_WAIT @@ -46,7 +46,7 @@ export class WalletValidator { async expectDisconnected() { await this.gotoSessions.click(); await expect( - this.page.getByTestId('session-card'), + this.page.getByTestId('session-card').first(), 'Session card should not be visible' ).not.toBeVisible({ timeout: MAX_WAIT diff --git a/apps/native/tests/wallet.spec.ts b/apps/native/tests/wallet.spec.ts index c1bc69de..446eb929 100644 --- a/apps/native/tests/wallet.spec.ts +++ b/apps/native/tests/wallet.spec.ts @@ -137,7 +137,7 @@ sampleWalletTest('it should disconnect using hook', async () => { await modalValidator.expectDisconnected(); }); -sampleWalletTest('it should disconnect and close modal when connecting from wallet', async () => { +sampleWalletTest('it should disconnect and close modal when disconnecting from wallet', async () => { await modalValidator.expectDisconnected(); await modalPage.qrCodeFlow(modalPage, walletPage); await modalValidator.expectConnected(); diff --git a/packages/core/src/__tests__/controllers/NetworkController.test.ts b/packages/core/src/__tests__/controllers/NetworkController.test.ts index 9202383c..314b0357 100644 --- a/packages/core/src/__tests__/controllers/NetworkController.test.ts +++ b/packages/core/src/__tests__/controllers/NetworkController.test.ts @@ -19,7 +19,6 @@ const client: NetworkControllerClient = { const initialState = { _client: client, supportsAllNetworks: true, - isDefaultCaipNetwork: false, smartAccountEnabledNetworks: [] }; @@ -65,7 +64,7 @@ describe('NetworkController', () => { it('should update state correctly on setDefaultCaipNetwork()', () => { NetworkController.setDefaultCaipNetwork(caipNetwork); expect(NetworkController.state.caipNetwork).toEqual(caipNetwork); - expect(NetworkController.state.isDefaultCaipNetwork).toEqual(true); + expect(NetworkController.state.defaultCaipNetwork).toEqual(caipNetwork); }); it('should reset state correctly when default caip network is true', () => { diff --git a/packages/core/src/controllers/ConnectionController.ts b/packages/core/src/controllers/ConnectionController.ts index d36182b7..b3d2538d 100644 --- a/packages/core/src/controllers/ConnectionController.ts +++ b/packages/core/src/controllers/ConnectionController.ts @@ -93,7 +93,7 @@ export const ConnectionController = { async connectExternal(options: ConnectExternalOptions) { await this._getClient().connectExternal?.(options); - ConnectorController.setConnectedConnector(options.type); + await ConnectorController.setConnectedConnector(options.type); }, async signMessage(message: string) { diff --git a/packages/core/src/controllers/ConnectorController.ts b/packages/core/src/controllers/ConnectorController.ts index 7ff77664..798aadd5 100644 --- a/packages/core/src/controllers/ConnectorController.ts +++ b/packages/core/src/controllers/ConnectorController.ts @@ -42,7 +42,7 @@ export const ConnectorController = { return state.connectors.find(c => c.type === 'AUTH'); }, - setConnectedConnector( + async setConnectedConnector( connectorType: ConnectorControllerState['connectedConnector'], saveStorage = true ) { @@ -50,9 +50,9 @@ export const ConnectorController = { if (saveStorage) { if (connectorType) { - StorageUtil.setConnectedConnector(connectorType); + await StorageUtil.setConnectedConnector(connectorType); } else { - StorageUtil.removeConnectedConnector(); + await StorageUtil.removeConnectedConnector(); } } }, diff --git a/packages/core/src/controllers/NetworkController.ts b/packages/core/src/controllers/NetworkController.ts index f1023c95..60a8957f 100644 --- a/packages/core/src/controllers/NetworkController.ts +++ b/packages/core/src/controllers/NetworkController.ts @@ -15,9 +15,9 @@ export interface NetworkControllerClient { export interface NetworkControllerState { supportsAllNetworks: boolean; - isDefaultCaipNetwork: boolean; _client?: NetworkControllerClient; caipNetwork?: CaipNetwork; + defaultCaipNetwork?: CaipNetwork; requestedCaipNetworks?: CaipNetwork[]; approvedCaipNetworkIds?: CaipNetworkId[]; smartAccountEnabledNetworks: number[]; @@ -26,7 +26,7 @@ export interface NetworkControllerState { // -- State --------------------------------------------- // const state = proxy({ supportsAllNetworks: true, - isDefaultCaipNetwork: false, + defaultCaipNetwork: undefined, smartAccountEnabledNetworks: [] }); @@ -53,7 +53,7 @@ export const NetworkController = { setDefaultCaipNetwork(caipNetwork: NetworkControllerState['caipNetwork']) { state.caipNetwork = caipNetwork; - state.isDefaultCaipNetwork = true; + state.defaultCaipNetwork = caipNetwork; PublicStateController.set({ selectedNetworkId: caipNetwork?.id }); }, @@ -84,9 +84,11 @@ export const NetworkController = { }, getApprovedCaipNetworks() { - return state.approvedCaipNetworkIds + const networks = state.approvedCaipNetworkIds ?.map(id => state.requestedCaipNetworks?.find(network => network.id === id)) .filter(Boolean) as CaipNetwork[]; + + return networks ?? []; }, getSmartAccountEnabledNetworks() { @@ -110,9 +112,7 @@ export const NetworkController = { }, resetNetwork() { - if (!state.isDefaultCaipNetwork) { - state.caipNetwork = undefined; - } + state.caipNetwork = state.defaultCaipNetwork || undefined; state.approvedCaipNetworkIds = undefined; state.supportsAllNetworks = true; state.smartAccountEnabledNetworks = []; diff --git a/packages/core/src/utils/NetworkUtil.ts b/packages/core/src/utils/NetworkUtil.ts index 4ab2b5b4..89082870 100644 --- a/packages/core/src/utils/NetworkUtil.ts +++ b/packages/core/src/utils/NetworkUtil.ts @@ -7,18 +7,20 @@ import { SwapController } from '../controllers/SwapController'; import type { CaipNetwork } from '../utils/TypeUtil'; export const NetworkUtil = { - async handleNetworkSwitch(network: CaipNetwork) { + async handleNetworkSwitch(network: CaipNetwork, navigate: boolean = true) { const { isConnected } = AccountController.state; const { caipNetwork, approvedCaipNetworkIds, supportsAllNetworks } = NetworkController.state; - const isAuthConnected = ConnectorController.state.connectedConnector === 'AUTH'; + const isAuthConnector = ConnectorController.state.connectedConnector === 'AUTH'; let eventData = null; if (isConnected && caipNetwork?.id !== network.id) { - if (approvedCaipNetworkIds?.includes(network.id) && !isAuthConnected) { + if (approvedCaipNetworkIds?.includes(network.id) && !isAuthConnector) { await NetworkController.switchActiveNetwork(network); - RouterUtil.navigateAfterNetworkSwitch(['ConnectingSiwe']); + if (navigate) { + RouterUtil.goBackOrCloseModal(); + } eventData = { type: 'SWITCH_NETWORK', networkId: network.id }; - } else if (supportsAllNetworks || isAuthConnected) { + } else if (supportsAllNetworks || isAuthConnector) { RouterController.push('SwitchNetwork', { network }); } } else if (!isConnected) { diff --git a/packages/core/src/utils/RouterUtil.ts b/packages/core/src/utils/RouterUtil.ts index c0e61b62..d52b844b 100644 --- a/packages/core/src/utils/RouterUtil.ts +++ b/packages/core/src/utils/RouterUtil.ts @@ -1,19 +1,10 @@ -import { RouterController, type RouterControllerState } from '../controllers/RouterController'; +import { RouterController } from '../controllers/RouterController'; import { ModalController } from '../controllers/ModalController'; export const RouterUtil = { - navigateAfterNetworkSwitch(excludeViews: RouterControllerState['view'][] = []) { - if (excludeViews.includes(RouterController.state.view)) { - return; - } - - const { history } = RouterController.state; - const networkSelectIndex = history.findIndex( - name => name === 'Networks' || name === 'UnsupportedChain' - ); - - if (networkSelectIndex >= 1) { - RouterController.goBackToIndex(networkSelectIndex - 1); + goBackOrCloseModal() { + if (RouterController.state.history.length > 1) { + RouterController.goBack(); } else { ModalController.close(); } diff --git a/packages/ethers/src/client.ts b/packages/ethers/src/client.ts index c6204de2..729d4824 100644 --- a/packages/ethers/src/client.ts +++ b/packages/ethers/src/client.ts @@ -103,6 +103,12 @@ export class AppKit extends AppKitScaffold { private authProvider?: AppKitFrameProvider; + private providerHandlers: { + disconnect: () => void; + accountsChanged: (accounts: string[]) => void; + chainChanged: (chainId: string) => void; + } | null = null; + public constructor(options: AppKitClientOptions) { const { config, @@ -144,7 +150,7 @@ export class AppKit extends AppKitScaffold { const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!; if (walletChoice?.includes(walletConnectType)) { const provider = await this.getWalletConnectProvider(); - const result = getWalletConnectCaipNetworks(provider); + const result = await getWalletConnectCaipNetworks(provider); resolve(result); } else if (walletChoice?.includes(authType)) { @@ -248,6 +254,7 @@ export class AppKit extends AppKitScaffold { await this.setCoinbaseProvider(coinbaseProvider as Provider); } catch (error) { EthersStoreUtil.setError(error); + throw error; } } else if (id === ConstantsUtil.AUTH_CONNECTOR_ID) { await this.setAuthProvider(); @@ -452,7 +459,7 @@ export class AppKit extends AppKitScaffold { }); EthersStoreUtil.subscribeKey('chainId', () => { - this.syncNetwork(chainImages); + this.syncNetwork(); }); EthersStoreUtil.subscribeKey('provider', provider => { @@ -609,7 +616,6 @@ export class AppKit extends AppKitScaffold { if (walletId === ConstantsUtil.COINBASE_CONNECTOR_ID) { if (CoinbaseProvider.address) { await this.setCoinbaseProvider(provider); - await this.watchCoinbase(provider); } else { await StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); EthersStoreUtil.reset(); @@ -623,12 +629,12 @@ export class AppKit extends AppKitScaffold { const WalletConnectProvider = await this.getWalletConnectProvider(); if (WalletConnectProvider) { const providerType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]; - EthersStoreUtil.setChainId(WalletConnectProvider.chainId); EthersStoreUtil.setProviderType(providerType); EthersStoreUtil.setProvider(WalletConnectProvider as unknown as Provider); EthersStoreUtil.setIsConnected(true); + EthersStoreUtil.setChainId(WalletConnectProvider.chainId); this.setAddress(WalletConnectProvider.accounts?.[0]); - await this.watchWalletConnect(); + this.listenProviderEvents(WalletConnectProvider as unknown as Provider); } } @@ -639,12 +645,12 @@ export class AppKit extends AppKitScaffold { const { address, chainId } = await EthersHelpersUtil.getUserInfo(provider); if (address && chainId) { const providerType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.COINBASE_CONNECTOR_ID]; - EthersStoreUtil.setChainId(chainId); EthersStoreUtil.setProviderType(providerType); EthersStoreUtil.setProvider(provider); EthersStoreUtil.setIsConnected(true); this.setAddress(address); - await this.watchCoinbase(provider); + EthersStoreUtil.setChainId(chainId); + this.listenProviderEvents(provider); } } } @@ -656,102 +662,80 @@ export class AppKit extends AppKitScaffold { const { address, chainId } = await this.authProvider.connect(); super.setLoading(false); if (address && chainId) { - EthersStoreUtil.setChainId(chainId); EthersStoreUtil.setProviderType( PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID] ); EthersStoreUtil.setProvider(this.authProvider as CombinedProviderType); EthersStoreUtil.setIsConnected(true); EthersStoreUtil.setAddress(address as Address); + EthersStoreUtil.setChainId(chainId); } } } - private async watchWalletConnect() { - const WalletConnectProvider = await this.getWalletConnectProvider(); - - function disconnectHandler() { + private listenProviderEvents(provider: Provider) { + const disconnectHandler = () => { + this.removeProviderListeners(provider); StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); EthersStoreUtil.reset(); - - WalletConnectProvider?.removeListener('disconnect', disconnectHandler); - WalletConnectProvider?.removeListener('accountsChanged', accountsChangedHandler); - WalletConnectProvider?.removeListener('chainChanged', chainChangedHandler); - } - - function chainChangedHandler(chainId: string) { - if (chainId) { - const chain = EthersHelpersUtil.hexStringToNumber(chainId); - EthersStoreUtil.setChainId(chain); - } - } - - const accountsChangedHandler = async (accounts: string[]) => { - if (accounts.length > 0) { - await this.setWalletConnectProvider(); - } + this.setClientId(null); }; - if (WalletConnectProvider) { - WalletConnectProvider.on('disconnect', disconnectHandler); - WalletConnectProvider.on('accountsChanged', accountsChangedHandler); - WalletConnectProvider.on('chainChanged', chainChangedHandler); - } - } - - private async watchCoinbase(provider: Provider) { - const walletId = await StorageUtil.getItem(EthersConstantsUtil.WALLET_ID); - - function disconnectHandler() { - StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); - EthersStoreUtil.reset(); - - provider?.removeListener('disconnect', disconnectHandler); - provider?.removeListener('accountsChanged', accountsChangedHandler); - provider?.removeListener('chainChanged', chainChangedHandler); - } - - function accountsChangedHandler(accounts: string[]) { + const accountsChangedHandler = (accounts: string[]) => { if (accounts.length === 0) { - StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); - EthersStoreUtil.reset(); + disconnectHandler(); } else { EthersStoreUtil.setAddress(accounts[0] as Address); } - } + }; - function chainChangedHandler(chainId: string) { - if (chainId && walletId === ConstantsUtil.COINBASE_CONNECTOR_ID) { - const chain = Number(chainId); + const chainChangedHandler = (chainId: string) => { + if (chainId) { + const chain = EthersHelpersUtil.hexStringToNumber(chainId); EthersStoreUtil.setChainId(chain); } - } + }; - if (provider) { - provider.on('disconnect', disconnectHandler); - provider.on('accountsChanged', accountsChangedHandler); - provider.on('chainChanged', chainChangedHandler); + provider.on('disconnect', disconnectHandler); + provider.on('accountsChanged', accountsChangedHandler); + provider.on('chainChanged', chainChangedHandler); + + this.providerHandlers = { + disconnect: disconnectHandler, + accountsChanged: accountsChangedHandler, + chainChanged: chainChangedHandler + }; + } + + private removeProviderListeners(provider: Provider) { + if (this.providerHandlers) { + provider.removeListener('disconnect', this.providerHandlers.disconnect); + provider.removeListener('accountsChanged', this.providerHandlers.accountsChanged); + provider.removeListener('chainChanged', this.providerHandlers.chainChanged); + this.providerHandlers = null; } } private async syncAccount({ address }: { address?: Address }) { const chainId = EthersStoreUtil.state.chainId; - const isConnected = EthersStoreUtil.state.isConnected; + const isSiweEnabled = this.options?.siweConfig?.options?.enabled; - if (isConnected && address && chainId) { + if (address && chainId) { const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; - this.setIsConnected(isConnected); + EthersStoreUtil.setIsConnected(true); + this.setIsConnected(true); this.setCaipAddress(caipAddress); + this.resetTransactions(); - await Promise.all([ - this.syncProfile(address), - this.syncBalance(address), - this.getApprovedCaipNetworksData() - ]); + await Promise.all([this.syncProfile(address), this.syncBalance(address)]); this.hasSyncedConnectedAccount = true; - } else if (!isConnected && this.hasSyncedConnectedAccount) { + + if (isSiweEnabled) { + this.handleSiweChange({ isNetworkChange: false, isAccountChange: true }); + } + } else if (!address && this.hasSyncedConnectedAccount) { this.close(); this.resetAccount(); this.resetWcConnection(); @@ -759,37 +743,74 @@ export class AppKit extends AppKitScaffold { } } - private async syncNetwork(chainImages?: AppKitClientOptions['chainImages']) { + private async checkNetworkSupport(params: { chainId?: number; isConnected?: boolean }) { + // wait until the session is set + await new Promise(resolve => setTimeout(resolve, 500)); + + const { isConnected = false, chainId } = params; + const chain = this.chains.find((c: Chain) => c.chainId === chainId); + + if (chain) { + const caipChainId: CaipNetworkId = `${ConstantsUtil.EIP155}:${chain.chainId}`; + this.setCaipNetwork({ + id: caipChainId, + name: chain.name, + imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId], + imageUrl: this.options?.chainImages?.[chain.chainId] + }); + } else if (params.chainId) { + this.setCaipNetwork({ + id: `${ConstantsUtil.EIP155}:${params.chainId}`, + name: 'Unsupported Network' + }); + this.setAddressExplorerUrl(undefined); + this.setBalance(undefined, undefined); + } + + if (isConnected) { + await this.getApprovedCaipNetworksData(); + const isApproved = this.getApprovedCaipNetworks().some( + network => network.id === `${ConstantsUtil.EIP155}:${params.chainId}` + ); + + const isSupported = !!chain && isApproved; + + this.openUnsupportedNetworkView(isSupported); + + return isSupported; + } + + return false; + } + + private async syncNetwork() { const address = EthersStoreUtil.state.address; const chainId = EthersStoreUtil.state.chainId; const isConnected = EthersStoreUtil.state.isConnected; if (this.chains) { const chain = this.chains.find(c => c.chainId === chainId); + const isSupported = await this.checkNetworkSupport({ chainId, isConnected }); + const isSiweEnabled = this.options?.siweConfig?.options?.enabled; + + if (chain && isConnected && address) { + const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; + this.setCaipAddress(caipAddress); + this.resetTransactions(); + if (chain.explorerUrl) { + const url = `${chain.explorerUrl}/address/${address}`; + this.setAddressExplorerUrl(url); + } else { + this.setAddressExplorerUrl(undefined); + } - if (chain) { - const caipChainId: CaipNetworkId = `${ConstantsUtil.EIP155}:${chain.chainId}`; - - this.setCaipNetwork({ - id: caipChainId, - name: chain.name, - imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId], - imageUrl: chainImages?.[chain.chainId] - }); - if (isConnected && address) { - const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; - this.setCaipAddress(caipAddress); - if (chain.explorerUrl) { - const url = `${chain.explorerUrl}/address/${address}`; - this.setAddressExplorerUrl(url); - } else { - this.setAddressExplorerUrl(undefined); - } - - if (this.hasSyncedConnectedAccount) { - await this.syncBalance(address); - } + if (this.hasSyncedConnectedAccount) { + await this.syncBalance(address); } } + + if (isConnected && isSupported && isSiweEnabled) { + this.handleSiweChange({ isNetworkChange: true, isAccountChange: false }); + } } } diff --git a/packages/ethers5/src/client.ts b/packages/ethers5/src/client.ts index c36e0c81..f17c5673 100644 --- a/packages/ethers5/src/client.ts +++ b/packages/ethers5/src/client.ts @@ -90,6 +90,12 @@ export class AppKit extends AppKitScaffold { private authProvider?: AppKitFrameProvider; + private providerHandlers: { + disconnect: () => void; + accountsChanged: (accounts: string[]) => void; + chainChanged: (chainId: string) => void; + } | null = null; + public constructor(options: AppKitClientOptions) { const { config, @@ -131,7 +137,7 @@ export class AppKit extends AppKitScaffold { const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!; if (walletChoice?.includes(walletConnectType)) { const provider = await this.getWalletConnectProvider(); - const result = getWalletConnectCaipNetworks(provider); + const result = await getWalletConnectCaipNetworks(provider); resolve(result); } else if (walletChoice?.includes(authType)) { @@ -235,6 +241,7 @@ export class AppKit extends AppKitScaffold { await this.setCoinbaseProvider(coinbaseProvider as Provider); } catch (error) { EthersStoreUtil.setError(error); + throw error; } } else if (id === ConstantsUtil.AUTH_CONNECTOR_ID) { await this.setAuthProvider(); @@ -434,7 +441,7 @@ export class AppKit extends AppKitScaffold { }); EthersStoreUtil.subscribeKey('chainId', () => { - this.syncNetwork(chainImages); + this.syncNetwork(); }); EthersStoreUtil.subscribeKey('provider', provider => { @@ -509,7 +516,7 @@ export class AppKit extends AppKitScaffold { EthersStoreUtil.reset(); this.setClientId(null); - await (provider as unknown as EthereumProvider).disconnect(); + await (provider as unknown as EthereumProvider)?.disconnect?.(); } // -- Private ----------------------------------------------------------------- @@ -589,7 +596,6 @@ export class AppKit extends AppKitScaffold { if (walletId === ConstantsUtil.COINBASE_CONNECTOR_ID) { if (CoinbaseProvider.address) { await this.setCoinbaseProvider(provider); - await this.watchCoinbase(provider); } else { await StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); EthersStoreUtil.reset(); @@ -603,12 +609,12 @@ export class AppKit extends AppKitScaffold { const WalletConnectProvider = await this.getWalletConnectProvider(); if (WalletConnectProvider) { const providerType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]; - EthersStoreUtil.setChainId(WalletConnectProvider.chainId); EthersStoreUtil.setProviderType(providerType); EthersStoreUtil.setProvider(WalletConnectProvider as unknown as Provider); EthersStoreUtil.setIsConnected(true); this.setAddress(WalletConnectProvider.accounts?.[0]); - await this.watchWalletConnect(); + EthersStoreUtil.setChainId(WalletConnectProvider.chainId); + this.listenProviderEvents(WalletConnectProvider as unknown as Provider); } } @@ -619,12 +625,12 @@ export class AppKit extends AppKitScaffold { const { address, chainId } = await EthersHelpersUtil.getUserInfo(provider); if (address && chainId) { const providerType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.COINBASE_CONNECTOR_ID]; - EthersStoreUtil.setChainId(chainId); EthersStoreUtil.setProviderType(providerType); EthersStoreUtil.setProvider(provider); EthersStoreUtil.setIsConnected(true); this.setAddress(address); - await this.watchCoinbase(provider); + EthersStoreUtil.setChainId(chainId); + this.listenProviderEvents(provider); } } } @@ -647,91 +653,69 @@ export class AppKit extends AppKitScaffold { } } - private async watchWalletConnect() { - const WalletConnectProvider = await this.getWalletConnectProvider(); - - function disconnectHandler() { + private listenProviderEvents(provider: Provider) { + const disconnectHandler = () => { + this.removeProviderListeners(provider); StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); EthersStoreUtil.reset(); - - WalletConnectProvider?.removeListener('disconnect', disconnectHandler); - WalletConnectProvider?.removeListener('accountsChanged', accountsChangedHandler); - WalletConnectProvider?.removeListener('chainChanged', chainChangedHandler); - } - - function chainChangedHandler(chainId: string) { - if (chainId) { - const chain = EthersHelpersUtil.hexStringToNumber(chainId); - EthersStoreUtil.setChainId(chain); - } - } - - const accountsChangedHandler = async (accounts: string[]) => { - if (accounts.length > 0) { - await this.setWalletConnectProvider(); - } + this.setClientId(null); }; - if (WalletConnectProvider) { - WalletConnectProvider.on('disconnect', disconnectHandler); - WalletConnectProvider.on('accountsChanged', accountsChangedHandler); - WalletConnectProvider.on('chainChanged', chainChangedHandler); - } - } - - private async watchCoinbase(provider: Provider) { - const walletId = await StorageUtil.getItem(EthersConstantsUtil.WALLET_ID); - - function disconnectHandler() { - StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); - EthersStoreUtil.reset(); - - provider?.removeListener('disconnect', disconnectHandler); - provider?.removeListener('accountsChanged', accountsChangedHandler); - provider?.removeListener('chainChanged', chainChangedHandler); - } - - function accountsChangedHandler(accounts: string[]) { + const accountsChangedHandler = (accounts: string[]) => { if (accounts.length === 0) { - StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); - EthersStoreUtil.reset(); + disconnectHandler(); } else { EthersStoreUtil.setAddress(accounts[0] as Address); } - } + }; - function chainChangedHandler(chainId: string) { - if (chainId && walletId === ConstantsUtil.COINBASE_CONNECTOR_ID) { - const chain = Number(chainId); + const chainChangedHandler = (chainId: string) => { + if (chainId) { + const chain = EthersHelpersUtil.hexStringToNumber(chainId); EthersStoreUtil.setChainId(chain); } - } + }; - if (provider) { - provider.on('disconnect', disconnectHandler); - provider.on('accountsChanged', accountsChangedHandler); - provider.on('chainChanged', chainChangedHandler); + provider.on('disconnect', disconnectHandler); + provider.on('accountsChanged', accountsChangedHandler); + provider.on('chainChanged', chainChangedHandler); + + this.providerHandlers = { + disconnect: disconnectHandler, + accountsChanged: accountsChangedHandler, + chainChanged: chainChangedHandler + }; + } + + private removeProviderListeners(provider: Provider) { + if (this.providerHandlers) { + provider.removeListener('disconnect', this.providerHandlers.disconnect); + provider.removeListener('accountsChanged', this.providerHandlers.accountsChanged); + provider.removeListener('chainChanged', this.providerHandlers.chainChanged); + this.providerHandlers = null; } } private async syncAccount({ address }: { address?: Address }) { const chainId = EthersStoreUtil.state.chainId; - const isConnected = EthersStoreUtil.state.isConnected; + const isSiweEnabled = this.options?.siweConfig?.options?.enabled; - if (isConnected && address && chainId) { + if (address && chainId) { const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; - this.setIsConnected(isConnected); + EthersStoreUtil.setIsConnected(true); + this.setIsConnected(true); this.setCaipAddress(caipAddress); + this.resetTransactions(); - await Promise.all([ - this.syncProfile(address), - this.syncBalance(address), - this.getApprovedCaipNetworksData() - ]); + await Promise.all([this.syncProfile(address), this.syncBalance(address)]); this.hasSyncedConnectedAccount = true; - } else if (!isConnected && this.hasSyncedConnectedAccount) { + + if (isSiweEnabled) { + this.handleSiweChange({ isNetworkChange: false, isAccountChange: true }); + } + } else if (!address && this.hasSyncedConnectedAccount) { this.close(); this.resetAccount(); this.resetWcConnection(); @@ -739,37 +723,74 @@ export class AppKit extends AppKitScaffold { } } - private async syncNetwork(chainImages?: AppKitClientOptions['chainImages']) { + private async checkNetworkSupport(params: { chainId?: number; isConnected?: boolean }) { + // wait until the session is set + await new Promise(resolve => setTimeout(resolve, 500)); + + const { isConnected = false, chainId } = params; + const chain = this.chains.find((c: Chain) => c.chainId === chainId); + + if (chain) { + const caipChainId: CaipNetworkId = `${ConstantsUtil.EIP155}:${chain.chainId}`; + this.setCaipNetwork({ + id: caipChainId, + name: chain.name, + imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId], + imageUrl: this.options?.chainImages?.[chain.chainId] + }); + } else if (params.chainId) { + this.setCaipNetwork({ + id: `${ConstantsUtil.EIP155}:${params.chainId}`, + name: 'Unsupported Network' + }); + this.setAddressExplorerUrl(undefined); + this.setBalance(undefined, undefined); + } + + if (isConnected) { + await this.getApprovedCaipNetworksData(); + const isApproved = this.getApprovedCaipNetworks().some( + network => network.id === `${ConstantsUtil.EIP155}:${params.chainId}` + ); + + const isSupported = !!chain && isApproved; + + this.openUnsupportedNetworkView(isSupported); + + return isSupported; + } + + return false; + } + + private async syncNetwork() { const address = EthersStoreUtil.state.address; const chainId = EthersStoreUtil.state.chainId; const isConnected = EthersStoreUtil.state.isConnected; if (this.chains) { const chain = this.chains.find(c => c.chainId === chainId); + const isSupported = await this.checkNetworkSupport({ chainId, isConnected }); + const isSiweEnabled = this.options?.siweConfig?.options?.enabled; + + if (chain && isConnected && address) { + const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; + this.setCaipAddress(caipAddress); + this.resetTransactions(); + if (chain.explorerUrl) { + const url = `${chain.explorerUrl}/address/${address}`; + this.setAddressExplorerUrl(url); + } else { + this.setAddressExplorerUrl(undefined); + } - if (chain) { - const caipChainId: CaipNetworkId = `${ConstantsUtil.EIP155}:${chain.chainId}`; - - this.setCaipNetwork({ - id: caipChainId, - name: chain.name, - imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId], - imageUrl: chainImages?.[chain.chainId] - }); - if (isConnected && address) { - const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; - this.setCaipAddress(caipAddress); - if (chain.explorerUrl) { - const url = `${chain.explorerUrl}/address/${address}`; - this.setAddressExplorerUrl(url); - } else { - this.setAddressExplorerUrl(undefined); - } - - if (this.hasSyncedConnectedAccount) { - await this.syncBalance(address); - } + if (this.hasSyncedConnectedAccount) { + await this.syncBalance(address); } } + + if (isConnected && isSupported && isSiweEnabled) { + this.handleSiweChange({ isNetworkChange: true, isAccountChange: false }); + } } } diff --git a/packages/scaffold/src/client.ts b/packages/scaffold/src/client.ts index 60daf796..b43fae22 100644 --- a/packages/scaffold/src/client.ts +++ b/packages/scaffold/src/client.ts @@ -27,6 +27,8 @@ import { NetworkController, OptionsController, PublicStateController, + RouterController, + RouterUtil, SnackController, StorageUtil, ThemeController, @@ -200,6 +202,9 @@ export class AppKitScaffold { protected getApprovedCaipNetworksData: (typeof NetworkController)['getApprovedCaipNetworksData'] = () => NetworkController.getApprovedCaipNetworksData(); + protected getApprovedCaipNetworks: (typeof NetworkController)['getApprovedCaipNetworks'] = () => + NetworkController.getApprovedCaipNetworks(); + protected resetNetwork: (typeof NetworkController)['resetNetwork'] = () => { NetworkController.resetNetwork(); }; @@ -274,6 +279,55 @@ export class AppKitScaffold { } } + protected onSiweNavigation = () => { + if (ModalController.state.open) { + RouterController.push('ConnectingSiwe'); + } else { + ModalController.open({ view: 'ConnectingSiwe' }); + } + }; + + protected openUnsupportedNetworkView(isSupported: boolean) { + if (isSupported && RouterController.state.view === 'UnsupportedChain') { + return RouterUtil.goBackOrCloseModal(); + } else if (!isSupported) { + if (ModalController.state.open) { + RouterController.push('UnsupportedChain'); + } else { + ModalController.open({ view: 'UnsupportedChain' }); + } + } + } + + protected async handleSiweChange(params: { + isNetworkChange?: boolean; + isAccountChange?: boolean; + }) { + const { isNetworkChange, isAccountChange } = params; + const { enabled, signOutOnAccountChange, signOutOnNetworkChange } = + SIWEController.state._client?.options ?? {}; + + if (enabled) { + const session = await SIWEController.getSession(); + if (session && isAccountChange && signOutOnAccountChange) { + // If the address has changed and signOnAccountChange is enabled, sign out + await SIWEController.signOut(); + this.onSiweNavigation(); + } else if (isNetworkChange && signOutOnNetworkChange) { + // If the network has changed and signOnNetworkChange is enabled, sign out + await SIWEController.signOut(); + this.onSiweNavigation(); + } else if (!session) { + // If it's connected but there's no session, show sign view + this.onSiweNavigation(); + } + } + } + + protected resetTransactions() { + TransactionsController.resetTransactions(); + } + // -- Private ------------------------------------------------------------------ private async initControllers(options: ScaffoldOptions) { this.initAsyncValues(options); @@ -356,7 +410,7 @@ export class AppKitScaffold { private async initConnectedConnector() { const connectedConnector = await StorageUtil.getConnectedConnector(); if (connectedConnector) { - ConnectorController.setConnectedConnector(connectedConnector, false); + await ConnectorController.setConnectedConnector(connectedConnector, false); } } diff --git a/packages/scaffold/src/modal/w3m-modal/index.tsx b/packages/scaffold/src/modal/w3m-modal/index.tsx index 7a39c491..eaf80675 100644 --- a/packages/scaffold/src/modal/w3m-modal/index.tsx +++ b/packages/scaffold/src/modal/w3m-modal/index.tsx @@ -1,5 +1,5 @@ import { useSnapshot } from 'valtio'; -import { useCallback, useEffect } from 'react'; +import { useEffect } from 'react'; import { useWindowDimensions, StatusBar } from 'react-native'; import Modal from 'react-native-modal'; import { Card, ThemeProvider } from '@reown/appkit-ui-react-native'; @@ -8,13 +8,10 @@ import { ApiController, ConnectionController, ConnectorController, - CoreHelperUtil, EventsController, ModalController, OptionsController, RouterController, - TransactionsController, - type CaipAddress, type AppKitFrameProvider, ThemeController } from '@reown/appkit-core-react-native'; @@ -26,10 +23,12 @@ import { Snackbar } from '../../partials/w3m-snackbar'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import styles from './styles'; +const disableCloseViews = ['UnsupportedChain', 'ConnectingSiwe']; + export function AppKit() { - const { open, loading } = useSnapshot(ModalController.state); + const { open } = useSnapshot(ModalController.state); const { connectors, connectedConnector } = useSnapshot(ConnectorController.state); - const { caipAddress, isConnected } = useSnapshot(AccountController.state); + const { themeMode, themeVariables } = useSnapshot(ThemeController.state); const { height } = useWindowDimensions(); const { isLandscape } = useCustomDimensions(); @@ -40,7 +39,19 @@ export function AppKit() { const SocialView = authProvider?.Webview; const showAuth = !connectedConnector || connectedConnector === 'AUTH'; + const onBackdropPress = () => { + if (disableCloseViews.includes(RouterController.state.view)) { + return; + } + + return ModalController.close(); + }; + const onBackButtonPress = () => { + if (disableCloseViews.includes(RouterController.state.view)) { + return; + } + if (RouterController.state.history.length > 1) { return RouterController.goBack(); } @@ -61,59 +72,10 @@ export function AppKit() { } }; - const onNewAddress = useCallback( - async (address?: CaipAddress) => { - if (!isConnected || loading) { - return; - } - - const newAddress = CoreHelperUtil.getPlainAddress(address); - TransactionsController.resetTransactions(); - - if (OptionsController.state.isSiweEnabled) { - const newNetworkId = CoreHelperUtil.getNetworkId(address); - - const { signOutOnAccountChange, signOutOnNetworkChange } = - SIWEController.state._client?.options ?? {}; - const session = await SIWEController.getSession(); - - if (session && newAddress && signOutOnAccountChange) { - // If the address has changed and signOnAccountChange is enabled, sign out - await SIWEController.signOut(); - onSiweNavigation(); - } else if ( - newNetworkId && - session?.chainId.toString() !== newNetworkId && - signOutOnNetworkChange - ) { - // If the network has changed and signOnNetworkChange is enabled, sign out - await SIWEController.signOut(); - onSiweNavigation(); - } else if (!session) { - // If it's connected but there's no session, show sign view - onSiweNavigation(); - } - } - }, - [isConnected, loading] - ); - - const onSiweNavigation = () => { - if (ModalController.state.open) { - RouterController.push('ConnectingSiwe'); - } else { - ModalController.open({ view: 'ConnectingSiwe' }); - } - }; - useEffect(() => { prefetch(); }, []); - useEffect(() => { - onNewAddress(caipAddress); - }, [caipAddress, onNewAddress]); - return ( <> @@ -127,7 +89,7 @@ export function AppKit() { hideModalContentWhileAnimating propagateSwipe onModalHide={handleClose} - onBackdropPress={ModalController.close} + onBackdropPress={onBackdropPress} onBackButtonPress={onBackButtonPress} testID="w3m-modal" > diff --git a/packages/scaffold/src/modal/w3m-network-button/index.tsx b/packages/scaffold/src/modal/w3m-network-button/index.tsx index 353a1804..40109342 100644 --- a/packages/scaffold/src/modal/w3m-network-button/index.tsx +++ b/packages/scaffold/src/modal/w3m-network-button/index.tsx @@ -10,6 +10,7 @@ import { ThemeController } from '@reown/appkit-core-react-native'; import { NetworkButton as NetworkButtonUI, ThemeProvider } from '@reown/appkit-ui-react-native'; +import { UiUtil } from '../../utils/UiUtil'; export interface NetworkButtonProps { disabled?: boolean; @@ -17,9 +18,9 @@ export interface NetworkButtonProps { } export function NetworkButton({ disabled, style }: NetworkButtonProps) { - const { isConnected } = useSnapshot(AccountController.state); - const { caipNetwork } = useSnapshot(NetworkController.state); const { loading } = useSnapshot(ModalController.state); + const { caipNetwork } = useSnapshot(NetworkController.state); + const { isConnected } = useSnapshot(AccountController.state); const { themeMode, themeVariables } = useSnapshot(ThemeController.state); const onNetworkPress = () => { @@ -30,10 +31,12 @@ export function NetworkButton({ disabled, style }: NetworkButtonProps) { }); }; + const buttonText = UiUtil.getNetworkButtonText(isConnected, caipNetwork); + return ( - {caipNetwork?.name ?? (isConnected ? 'Unknown Network' : 'Select Network')} + {buttonText} ); diff --git a/packages/scaffold/src/partials/w3m-header/index.tsx b/packages/scaffold/src/partials/w3m-header/index.tsx index 33d0b88f..d352df5b 100644 --- a/packages/scaffold/src/partials/w3m-header/index.tsx +++ b/packages/scaffold/src/partials/w3m-header/index.tsx @@ -19,6 +19,7 @@ export function Header() { RouterController.push('WhatIsAWallet'); EventsController.sendEvent({ type: 'track', event: 'CLICK_WALLET_HELP' }); }; + const showButtons = !['ConnectingSiwe', 'UnsupportedChain'].includes(view); const headings = (_data: RouterControllerState['data'], _view: RouterControllerState['view']) => { const connectorName = _data?.connector?.name; @@ -100,9 +101,7 @@ export function Header() { }; const dynamicButtonTemplate = () => { - const noButtonViews = ['ConnectingSiwe']; - - if (noButtonViews.includes(RouterController.state.view)) { + if (!showButtons) { return ; } @@ -130,7 +129,11 @@ export function Header() { {header} - + {showButtons ? ( + + ) : ( + + )} ); } diff --git a/packages/scaffold/src/utils/UiUtil.ts b/packages/scaffold/src/utils/UiUtil.ts index 65e1f9d9..21d7a493 100644 --- a/packages/scaffold/src/utils/UiUtil.ts +++ b/packages/scaffold/src/utils/UiUtil.ts @@ -2,7 +2,8 @@ import { AssetUtil, ConnectionController, StorageUtil, - type WcWallet + type WcWallet, + type CaipNetwork } from '@reown/appkit-core-react-native'; import { LayoutAnimation } from 'react-native'; @@ -27,5 +28,25 @@ export const UiUtil = { const url = AssetUtil.getWalletImage(pressedWallet); ConnectionController.setConnectedWalletImageUrl(url); } + }, + + getNetworkButtonText(isConnected: boolean, caipNetwork: CaipNetwork | undefined): string { + let buttonText: string; + + if (!isConnected) { + if (caipNetwork) { + buttonText = caipNetwork.name ?? 'Unknown Network'; + } else { + buttonText = 'Select Network'; + } + } else { + if (caipNetwork) { + buttonText = caipNetwork.name ?? 'Unknown Network'; + } else { + buttonText = 'Select Network'; + } + } + + return buttonText; } }; diff --git a/packages/scaffold/src/views/w3m-account-default-view/index.tsx b/packages/scaffold/src/views/w3m-account-default-view/index.tsx index f3982ed9..08893358 100644 --- a/packages/scaffold/src/views/w3m-account-default-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-default-view/index.tsx @@ -30,6 +30,7 @@ import { ListItem } from '@reown/appkit-ui-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { UiUtil as ScaffoldUiUtil } from '../../utils/UiUtil'; import styles from './styles'; import { AuthButtons } from './components/auth-buttons'; @@ -42,7 +43,8 @@ export function AccountDefaultView() { balance, balanceSymbol, addressExplorerUrl, - preferredAccountType + preferredAccountType, + isConnected } = useSnapshot(AccountController.state); const { loading } = useSnapshot(ModalController.state); const [disconnecting, setDisconnecting] = useState(false); @@ -238,17 +240,17 @@ export function AccountDefaultView() { )} - - {caipNetwork?.name} + + {ScaffoldUiUtil.getNetworkButtonText(isConnected, caipNetwork)} diff --git a/packages/scaffold/src/views/w3m-connecting-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-view/index.tsx index 44ee537c..d43de6e2 100644 --- a/packages/scaffold/src/views/w3m-connecting-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connecting-view/index.tsx @@ -50,7 +50,7 @@ export function ConnectingView() { ConnectionController.setWcError(false); ConnectionController.connectWalletConnect(routeData?.wallet?.link_mode ?? undefined); await ConnectionController.state.wcPromise; - ConnectorController.setConnectedConnector('WALLET_CONNECT'); + await ConnectorController.setConnectedConnector('WALLET_CONNECT'); AccountController.setIsConnected(true); if (OptionsController.state.isSiweEnabled) { diff --git a/packages/scaffold/src/views/w3m-network-switch-view/index.tsx b/packages/scaffold/src/views/w3m-network-switch-view/index.tsx index 8adf0455..8a810da6 100644 --- a/packages/scaffold/src/views/w3m-network-switch-view/index.tsx +++ b/packages/scaffold/src/views/w3m-network-switch-view/index.tsx @@ -56,7 +56,7 @@ export function NetworkSwitchView() { useEffect(() => { // Go back if network is already switched if (caipNetwork?.id === network?.id) { - RouterUtil.navigateAfterNetworkSwitch(); + RouterUtil.goBackOrCloseModal(); } }, [caipNetwork?.id, network?.id]); diff --git a/packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx b/packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx index f2074e50..9c07f2f0 100644 --- a/packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx +++ b/packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx @@ -11,7 +11,8 @@ import { NetworkController, NetworkUtil, type CaipNetwork, - type NetworkControllerState + type NetworkControllerState, + ModalController } from '@reown/appkit-core-react-native'; import styles from './styles'; @@ -24,7 +25,11 @@ export function UnsupportedChainView() { const imageHeaders = ApiController._getApiHeaders(); const onNetworkPress = async (network: CaipNetwork) => { - const result = await NetworkUtil.handleNetworkSwitch(network); + if (NetworkController.state.caipNetwork?.id === network.id) { + return ModalController.close(); + } + + const result = await NetworkUtil.handleNetworkSwitch(network, false); if (result?.type === 'SWITCH_NETWORK') { EventsController.sendEvent({ type: 'track', @@ -49,8 +54,8 @@ export function UnsupportedChainView() { ListHeaderComponentStyle={styles.header} ListHeaderComponent={ - The swap feature doesn't support your current network. Switch to an available option to - continue. + The current network is not supported by this application. Please switch to an available + option to continue. } contentContainerStyle={styles.contentContainer} diff --git a/packages/siwe/src/client.ts b/packages/siwe/src/client.ts index c0c28441..b893e252 100644 --- a/packages/siwe/src/client.ts +++ b/packages/siwe/src/client.ts @@ -2,7 +2,7 @@ import { AccountController, NetworkController, ConnectionController, - RouterUtil + ModalController } from '@reown/appkit-core-react-native'; import { NetworkUtil } from '@reown/appkit-common-react-native'; @@ -120,7 +120,7 @@ export class AppKitSIWEClient { this.methods.onSignIn(session); } - RouterUtil.navigateAfterNetworkSwitch(); + ModalController.close(); return session; } diff --git a/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx b/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx index e5a56f95..b940f089 100644 --- a/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx +++ b/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx @@ -1,5 +1,5 @@ import { useSnapshot } from 'valtio'; -import { Button, FlexView, IconLink, Text } from '@reown/appkit-ui-react-native'; +import { Button, FlexView, Text } from '@reown/appkit-ui-react-native'; import { AccountController, ConnectionController, @@ -86,13 +86,6 @@ export function ConnectingSiweView() { return ( - Sign in diff --git a/packages/ui/src/utils/TypesUtil.ts b/packages/ui/src/utils/TypesUtil.ts index 151cc8e5..a4334534 100644 --- a/packages/ui/src/utils/TypesUtil.ts +++ b/packages/ui/src/utils/TypesUtil.ts @@ -113,6 +113,10 @@ export type ColorType = | 'gray-glass-010' | 'gray-glass-005' | 'gray-glass-002' + | 'error-glass-020' + | 'error-glass-015' + | 'error-glass-010' + | 'error-glass-005' | 'inverse-000' | 'inverse-100' | 'success-100' diff --git a/packages/wagmi/src/client.ts b/packages/wagmi/src/client.ts index 5c4a4aac..a35dcd8c 100644 --- a/packages/wagmi/src/client.ts +++ b/packages/wagmi/src/client.ts @@ -124,7 +124,7 @@ export class AppKit extends AppKitScaffold { ); return getWalletConnectCaipNetworks(connector); - } else if (authType) { + } else if (walletChoice?.includes(authType)) { return getAuthCaipNetworks(); } @@ -157,7 +157,8 @@ export class AppKit extends AppKitScaffold { this.setClientId(clientId); } - const chainId = NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id); + const caipNetwork = this.getCaipNetwork(); + const chainId = NetworkUtil.caipNetworkIdToNumber(caipNetwork?.id); // SIWE const siweParams = await siweConfig?.getMessageParams?.(); @@ -198,7 +199,7 @@ export class AppKit extends AppKitScaffold { cacao: signedCacao }); - if (address && chainId) { + if (address && cacaoChainId) { const session = { address, chainId: parseInt(cacaoChainId, 10) @@ -246,10 +247,6 @@ export class AppKit extends AppKitScaffold { disconnect: async () => { await disconnect(this.wagmiConfig); this.setClientId(null); - - if (siweConfig?.options?.signOutOnDisconnect) { - await SIWEController.signOut(); - } }, sendTransaction: async (data: SendTransactionArgs) => { @@ -322,9 +319,7 @@ export class AppKit extends AppKitScaffold { getEnsAddress: async (value: string) => { try { if (!this.wagmiConfig) { - throw new Error( - 'networkControllerClient:getApprovedCaipNetworksData - wagmiConfig is undefined' - ); + throw new Error('WagmiAdapter:getEnsAddress - wagmiConfig is undefined'); } const chainId = Number(NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id)); let ensName: boolean | GetEnsAddressReturnType = false; @@ -380,9 +375,14 @@ export class AppKit extends AppKitScaffold { watchAccount(wagmiConfig, { onChange: (accountData, prevAccountData) => { - this.syncAccount({ ...accountData }); + this.syncAccount({ + ...accountData, + isNetworkChange: accountData.chainId !== prevAccountData.chainId, + isAccountChange: accountData.address !== prevAccountData.address + }); if (accountData.status === 'disconnected' && prevAccountData.status === 'connected') { + this.onSiweDisconnect(); this.close(); } } @@ -431,23 +431,25 @@ export class AppKit extends AppKitScaffold { chainId, connector, isConnecting, - isReconnecting + isReconnecting, + isNetworkChange, + isAccountChange }: Pick< GetAccountReturnType, 'address' | 'isConnected' | 'chainId' | 'connector' | 'isConnecting' | 'isReconnecting' - >) { - this.syncNetwork(address, chainId, isConnected); + > & { isNetworkChange?: boolean; isAccountChange?: boolean }) { + this.syncNetwork(address, chainId, isConnected, isNetworkChange, isAccountChange); this.setLoading(!!connector && (isConnecting || isReconnecting)); if (isConnected && address && chainId) { const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; this.setIsConnected(isConnected); this.setCaipAddress(caipAddress); + this.resetTransactions(); await Promise.all([ this.syncProfile(address, chainId), this.syncBalance(address, chainId), - this.syncConnectedWalletInfo(connector), - this.getApprovedCaipNetworksData() + this.syncConnectedWalletInfo(connector) ]); this.hasSyncedConnectedAccount = true; } else if (!isConnected && !isConnecting && !isReconnecting && this.hasSyncedConnectedAccount) { @@ -457,22 +459,60 @@ export class AppKit extends AppKitScaffold { } } - private async syncNetwork(address?: Hex, chainId?: number, isConnected?: boolean) { + private async checkNetworkSupport(params: { chainId?: number; isConnected?: boolean }) { + // wait until the session is set + await new Promise(resolve => setTimeout(resolve, 500)); + + const { isConnected = false, chainId } = params; const chain = this.wagmiConfig.chains.find((c: Chain) => c.id === chainId); - if (chain || chainId) { - const name = chain?.name ?? chainId?.toString(); - const id = Number(chain?.id ?? chainId); + if (chain) { + const id = Number(chain.id); const caipChainId: CaipNetworkId = `${ConstantsUtil.EIP155}:${id}`; this.setCaipNetwork({ id: caipChainId, - name, + name: chain.name, imageId: PresetsUtil.EIP155NetworkImageIds[id], imageUrl: this.options?.chainImages?.[id] }); + } else if (params.chainId) { + this.setCaipNetwork({ + id: `${ConstantsUtil.EIP155}:${params.chainId}`, + name: 'Unsupported Network' + }); + } + + if (isConnected) { + await this.getApprovedCaipNetworksData(); + const isApproved = this.getApprovedCaipNetworks().some( + network => network.id === `${ConstantsUtil.EIP155}:${params.chainId}` + ); + + const isSupported = !!chain && isApproved; + this.openUnsupportedNetworkView(isSupported); + + return isSupported; + } + + return false; + } + + private async syncNetwork( + address?: Hex, + chainId?: number, + isConnected?: boolean, + isNetworkChange?: boolean, + isAccountChange?: boolean + ) { + const chain = this.wagmiConfig.chains.find((c: Chain) => c.id === chainId); + const isSiweEnabled = this.options?.siweConfig?.options?.enabled; + + if (chain || chainId) { + const id = Number(chain?.id ?? chainId); if (isConnected && address && chainId) { const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${id}:${address}`; this.setCaipAddress(caipAddress); + this.resetTransactions(); if (chain?.blockExplorers?.default?.url) { const url = `${chain.blockExplorers.default.url}/address/${address}`; this.setAddressExplorerUrl(url); @@ -485,6 +525,15 @@ export class AppKit extends AppKitScaffold { } } } + + let isSupported = true; + if (isNetworkChange) { + isSupported = await this.checkNetworkSupport({ chainId, isConnected }); + } + + if (isConnected && isSupported && isSiweEnabled) { + this.handleSiweChange({ isNetworkChange, isAccountChange }); + } } private async syncProfile(address: Hex, chainId: number) { @@ -641,4 +690,10 @@ export class AppKit extends AppKitScaffold { this.setLoading(false); }); } + + private async onSiweDisconnect() { + if (this.options?.siweConfig?.options?.signOutOnDisconnect) { + await SIWEController.signOut(); + } + } }