From edc7a17879fa54c1257aa985c833ce48af9c2144 Mon Sep 17 00:00:00 2001 From: Felipe Mendes Date: Fri, 13 Dec 2024 10:34:29 -0300 Subject: [PATCH 1/2] feat: add bitcoin network image id (#3456) --- .changeset/cool-pumas-cough.md | 22 ++++++++++++++++++++++ packages/appkit-utils/src/PresetsUtil.ts | 4 +++- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 .changeset/cool-pumas-cough.md diff --git a/.changeset/cool-pumas-cough.md b/.changeset/cool-pumas-cough.md new file mode 100644 index 0000000000..9a6dc9632b --- /dev/null +++ b/.changeset/cool-pumas-cough.md @@ -0,0 +1,22 @@ +--- +'@reown/appkit-utils': patch +'@reown/appkit-adapter-ethers': patch +'@reown/appkit-adapter-ethers5': patch +'@reown/appkit-adapter-solana': patch +'@reown/appkit-adapter-wagmi': patch +'@reown/appkit': patch +'@reown/appkit-cdn': patch +'@reown/appkit-cli': patch +'@reown/appkit-common': patch +'@reown/appkit-core': patch +'@reown/appkit-experimental': patch +'@reown/appkit-polyfills': patch +'@reown/appkit-scaffold-ui': patch +'@reown/appkit-siwe': patch +'@reown/appkit-siwx': patch +'@reown/appkit-ui': patch +'@reown/appkit-wallet': patch +'@reown/appkit-wallet-button': patch +--- + +Add Bitcoin network image id diff --git a/packages/appkit-utils/src/PresetsUtil.ts b/packages/appkit-utils/src/PresetsUtil.ts index ef826b2aab..7a72ba02b9 100644 --- a/packages/appkit-utils/src/PresetsUtil.ts +++ b/packages/appkit-utils/src/PresetsUtil.ts @@ -78,7 +78,9 @@ export const PresetsUtil = { // Solana networks '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': 'a1b58899-f671-4276-6a5e-56ca5bd59700', '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z': 'a1b58899-f671-4276-6a5e-56ca5bd59700', - EtWTRABZaYq6iMfeYKouRu166VU2xqa1: 'a1b58899-f671-4276-6a5e-56ca5bd59700' + EtWTRABZaYq6iMfeYKouRu166VU2xqa1: 'a1b58899-f671-4276-6a5e-56ca5bd59700', + // Bitcoin + '000000000019d6689c085ae165831e93': '21c895fa-e105-4829-9434-378bb54fa600' } as Record, ConnectorImageIds: { From 444d1dd2c6216f47bcf32c98551e5c4338d872c5 Mon Sep 17 00:00:00 2001 From: Enes Date: Fri, 13 Dec 2024 17:18:08 +0300 Subject: [PATCH 2/2] refactor: disconnect logic and state update for multiple adapter use cases (#3460) --- .changeset/ninety-kangaroos-promise.md | 22 ++++ packages/appkit/src/client.ts | 8 -- .../core/src/controllers/ChainController.ts | 44 ++++++++ .../src/controllers/ConnectionController.ts | 6 +- packages/core/src/utils/TypeUtil.ts | 3 + .../tests/controllers/ChainController.test.ts | 106 +++++++++++++++++- 6 files changed, 170 insertions(+), 19 deletions(-) create mode 100644 .changeset/ninety-kangaroos-promise.md diff --git a/.changeset/ninety-kangaroos-promise.md b/.changeset/ninety-kangaroos-promise.md new file mode 100644 index 0000000000..702c860655 --- /dev/null +++ b/.changeset/ninety-kangaroos-promise.md @@ -0,0 +1,22 @@ +--- +'@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-scaffold-ui': patch +'@reown/appkit-siwe': patch +'@reown/appkit-siwx': patch +'@reown/appkit-ui': patch +'@reown/appkit-wallet': patch +'@reown/appkit-wallet-button': patch +--- + +Refactors disconnect business logic for multiple adapter use cases diff --git a/packages/appkit/src/client.ts b/packages/appkit/src/client.ts index a6d9ef784f..bbd31ce715 100644 --- a/packages/appkit/src/client.ts +++ b/packages/appkit/src/client.ts @@ -904,20 +904,12 @@ export class AppKit { const provider = ProviderUtil.getProvider( ChainController.state.activeChain as ChainNamespace ) - const providerType = ProviderUtil.state.providerIds[ChainController.state.activeChain as ChainNamespace] await adapter?.disconnect({ provider, providerType }) this.setStatus('disconnected', ChainController.state.activeChain as ChainNamespace) - - StorageUtil.deleteConnectedConnector() - StorageUtil.deleteActiveCaipNetworkId() - - ChainController.state.chains.forEach(chain => { - this.resetAccount(chain.namespace as ChainNamespace) - }) }, checkInstalled: (ids?: string[]) => { if (!ids) { diff --git a/packages/core/src/controllers/ChainController.ts b/packages/core/src/controllers/ChainController.ts index 940afdacf9..089c28ccc4 100644 --- a/packages/core/src/controllers/ChainController.ts +++ b/packages/core/src/controllers/ChainController.ts @@ -23,6 +23,7 @@ import { EventsController } from './EventsController.js' import { RouterController } from './RouterController.js' import { StorageUtil } from '../utils/StorageUtil.js' import { OptionsController } from './OptionsController.js' +import { ConnectionController } from './ConnectionController.js' // -- Constants ----------------------------------------- // const accountState: AccountControllerState = { @@ -519,5 +520,48 @@ export const ChainController = { allAccounts: [] }) ) + }, + + async disconnect() { + try { + const disconnectResults = await Promise.allSettled( + Array.from(state.chains.entries()).map(async ([namespace, adapter]) => { + try { + if (adapter.connectionControllerClient?.disconnect) { + await adapter.connectionControllerClient.disconnect() + } + this.resetAccount(namespace) + this.resetNetwork(namespace) + } catch (error) { + throw new Error(`Failed to disconnect chain ${namespace}: ${(error as Error).message}`) + } + }) + ) + + const failures = disconnectResults.filter( + (result): result is PromiseRejectedResult => result.status === 'rejected' + ) + + if (failures.length > 0) { + throw new Error(failures.map(f => f.reason.message).join(', ')) + } + + StorageUtil.deleteConnectedConnector() + ConnectionController.resetWcConnection() + EventsController.sendEvent({ + type: 'track', + event: 'DISCONNECT_SUCCESS' + }) + } catch (error) { + // eslint-disable-next-line no-console + console.error((error as Error).message || 'Failed to disconnect chains') + EventsController.sendEvent({ + type: 'track', + event: 'DISCONNECT_ERROR', + properties: { + message: (error as Error).message || 'Failed to disconnect chains' + } + }) + } } } diff --git a/packages/core/src/controllers/ConnectionController.ts b/packages/core/src/controllers/ConnectionController.ts index ab3714b6ac..6f303d9df4 100644 --- a/packages/core/src/controllers/ConnectionController.ts +++ b/packages/core/src/controllers/ConnectionController.ts @@ -249,8 +249,6 @@ export const ConnectionController = { async disconnect() { try { - const connectionControllerClient = this._getClient() - const siwx = OptionsController.state.siwx if (siwx) { const activeCaipNetwork = ChainController.getActiveCaipNetwork() @@ -261,9 +259,7 @@ export const ConnectionController = { } } - await connectionControllerClient?.disconnect() - - this.resetWcConnection() + await ChainController.disconnect() } catch (error) { throw new Error('Failed to disconnect') } diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index 1d1574dac8..01d5de238c 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -460,6 +460,9 @@ export type Event = | { type: 'track' event: 'DISCONNECT_ERROR' + properties?: { + message: string + } } | { type: 'track' diff --git a/packages/core/tests/controllers/ChainController.test.ts b/packages/core/tests/controllers/ChainController.test.ts index 82ffe8afa3..7ea680f8e8 100644 --- a/packages/core/tests/controllers/ChainController.test.ts +++ b/packages/core/tests/controllers/ChainController.test.ts @@ -1,4 +1,4 @@ -import { beforeAll, describe, expect, it, vi } from 'vitest' +import { beforeEach, describe, expect, it, vi } from 'vitest' import { ConstantsUtil, SafeLocalStorageKeys, @@ -11,6 +11,9 @@ import { type ConnectionControllerClient } from '../../src/controllers/Connectio import type { NetworkControllerClient } from '../../exports/index.js' import { RouterController } from '../../src/controllers/RouterController.js' import { SafeLocalStorage } from '@reown/appkit-common' +import { StorageUtil } from '../../src/utils/StorageUtil.js' +import { ConnectionController } from '../../src/controllers/ConnectionController.js' +import { EventsController } from '../../src/controllers/EventsController.js' // -- Setup -------------------------------------------------------------------- const chainNamespace = 'eip155' as ChainNamespace @@ -127,7 +130,8 @@ const solanaAdapter = { caipNetworks: [solanaCaipNetwork] as unknown as CaipNetwork[] } -beforeAll(() => { +beforeEach(() => { + ChainController.state.noAdapters = false ChainController.initialize([evmAdapter], requestedCaipNetworks) }) @@ -154,7 +158,6 @@ describe('ChainController', () => { }) it('should update state correctly on getApprovedCaipNetworkIds()', async () => { - const namespace = 'eip155' const networkController = { ...networkControllerClient } const networkControllerSpy = vi .spyOn(networkController, 'getApprovedCaipNetworksData') @@ -163,7 +166,7 @@ describe('ChainController', () => { supportsAllNetworks: false }) const evmAdapter = { - chainNamespace, + namespace: chainNamespace, connectionControllerClient, networkControllerClient: networkController, caipNetworks: [] as CaipNetwork[] @@ -171,9 +174,11 @@ describe('ChainController', () => { // Need to re-initialize to set the spy properly ChainController.initialize([evmAdapter], requestedCaipNetworks) - await ChainController.setApprovedCaipNetworksData(namespace) + await ChainController.setApprovedCaipNetworksData(chainNamespace) - expect(ChainController.getApprovedCaipNetworkIds(namespace)).toEqual(approvedCaipNetworkIds) + expect(ChainController.getApprovedCaipNetworkIds(chainNamespace)).toEqual( + approvedCaipNetworkIds + ) expect(networkControllerSpy).toHaveBeenCalled() }) @@ -346,4 +351,93 @@ describe('ChainController', () => { ChainController.initialize([], requestedCaipNetworks) expect(ChainController.state.noAdapters).toBe(true) }) + + it('should properly handle disconnect', async () => { + const evmConnectionController = { ...connectionControllerClient } + const solanaConnectionController = { ...connectionControllerClient } + const disconnectSpy = vi.spyOn(evmConnectionController, 'disconnect') + const disconnectSpy2 = vi.spyOn(solanaConnectionController, 'disconnect') + + const customEvmAdapter = { + ...evmAdapter, + connectionControllerClient: evmConnectionController + } + const customSolanaAdapter = { + ...solanaAdapter, + connectionControllerClient: solanaConnectionController + } + + const resetAccountSpy = vi.spyOn(ChainController, 'resetAccount') + const resetNetworkSpy = vi.spyOn(ChainController, 'resetNetwork') + const deleteConnectorSpy = vi.spyOn(StorageUtil, 'deleteConnectedConnector') + const resetWcConnectionSpy = vi.spyOn(ConnectionController, 'resetWcConnection') + const sendEventSpy = vi.spyOn(EventsController, 'sendEvent') + + ChainController.initialize([customEvmAdapter, customSolanaAdapter], requestedCaipNetworks) + + await ChainController.disconnect() + + expect(disconnectSpy).toHaveBeenCalled() + expect(disconnectSpy2).toHaveBeenCalled() + + expect(resetAccountSpy).toHaveBeenCalledWith(ConstantsUtil.CHAIN.EVM) + expect(resetAccountSpy).toHaveBeenCalledWith(ConstantsUtil.CHAIN.SOLANA) + expect(resetNetworkSpy).toHaveBeenCalledWith(ConstantsUtil.CHAIN.EVM) + expect(resetNetworkSpy).toHaveBeenCalledWith(ConstantsUtil.CHAIN.SOLANA) + + expect(deleteConnectorSpy).toHaveBeenCalled() + expect(resetWcConnectionSpy).toHaveBeenCalled() + + expect(sendEventSpy).toHaveBeenCalledWith({ + type: 'track', + event: 'DISCONNECT_SUCCESS' + }) + + resetAccountSpy.mockRestore() + resetNetworkSpy.mockRestore() + deleteConnectorSpy.mockRestore() + resetWcConnectionSpy.mockRestore() + sendEventSpy.mockRestore() + }) + + it('should handle disconnect errors gracefully', async () => { + const evmConnectionController = { + ...connectionControllerClient, + disconnect: vi.fn().mockRejectedValue(new Error('EVM disconnect failed')) + } + const solanaConnectionController = { + ...connectionControllerClient, + disconnect: vi.fn().mockRejectedValue(new Error('Solana disconnect failed')) + } + const customEvmAdapter = { + ...evmAdapter, + connectionControllerClient: evmConnectionController + } + const customSolanaAdapter = { + ...solanaAdapter, + connectionControllerClient: solanaConnectionController + } + const sendEventSpy = vi.spyOn(EventsController, 'sendEvent') + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + + ChainController.initialize([customEvmAdapter, customSolanaAdapter], requestedCaipNetworks) + + await ChainController.disconnect() + + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('EVM disconnect failed')) + expect(sendEventSpy).toHaveBeenCalledWith({ + type: 'track', + event: 'DISCONNECT_ERROR', + properties: { + message: expect.stringContaining('EVM disconnect failed') + } + }) + expect(sendEventSpy).not.toHaveBeenCalledWith({ + type: 'track', + event: 'DISCONNECT_SUCCESS' + }) + + sendEventSpy.mockRestore() + consoleSpy.mockRestore() + }) })