diff --git a/packages/connect-evm/CHANGELOG.md b/packages/connect-evm/CHANGELOG.md index 87990901..6d85c45f 100644 --- a/packages/connect-evm/CHANGELOG.md +++ b/packages/connect-evm/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- **BREAKING:** `disconnect()` now revokes the entire session instead of only its `eip155:*` scopes ([#344](https://github.com/MetaMask/connect-monorepo/pull/344)). The wallet may grant scopes beyond those requested (e.g. networks pre-selected on the connect prompt), and revoking only the `eip155:*` scopes stranded those extras in a session the wallet still reported as connected. This matches the legacy EIP-1193 behavior where `wallet_revokePermissions` revokes the origin's entire permission. + ## [2.1.1] ### Added diff --git a/packages/connect-evm/src/connect.test.ts b/packages/connect-evm/src/connect.test.ts index f2f90777..fa4fb74b 100644 --- a/packages/connect-evm/src/connect.test.ts +++ b/packages/connect-evm/src/connect.test.ts @@ -1262,7 +1262,7 @@ describe('MetamaskConnectEVM', () => { }); describe('disconnect', () => { - it('calls core.disconnect with all eip155 scopes from the current session', async () => { + it('calls core.disconnect with no scopes to revoke the entire session', async () => { const mockCore = createMockCore(); mockCore.storage.adapter.get.mockResolvedValue(JSON.stringify('0x1')); const client = await MetamaskConnectEVM.create({ core: mockCore }); @@ -1279,6 +1279,11 @@ describe('MetamaskConnectEVM', () => { notifications: [], accounts: ['eip155:137:0x1234567890123456789012345678901234567890'], }, + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': { + methods: [], + notifications: [], + accounts: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:1234567890'], + }, }, }; mockCore.emit('wallet_sessionChanged', session); @@ -1288,12 +1293,11 @@ describe('MetamaskConnectEVM', () => { await client.disconnect(); + // Called with no scopes so the wallet revokes the whole session, + // including scopes granted beyond the eip155 scopes this client + // requested (e.g. networks pre-selected by the wallet). expect(mockCore.disconnect).toHaveBeenCalledTimes(1); - const [scopes] = mockCore.disconnect.mock.calls[0]; - expect(scopes).toEqual( - expect.arrayContaining(['eip155:1', 'eip155:137']), - ); - expect(scopes).toHaveLength(2); + expect(mockCore.disconnect).toHaveBeenCalledWith(); }); }); diff --git a/packages/connect-evm/src/connect.ts b/packages/connect-evm/src/connect.ts index 3dab13e5..ba1ce026 100644 --- a/packages/connect-evm/src/connect.ts +++ b/packages/connect-evm/src/connect.ts @@ -30,11 +30,7 @@ import type { ProviderRequest, ProviderRequestInterceptor, } from './types'; -import { - getEthAccounts, - getPermittedEthChainIds, - parseScopeString, -} from './utils/caip'; +import { getEthAccounts, getPermittedEthChainIds } from './utils/caip'; import { isAccountsRequest, isAddChainRequest, @@ -601,18 +597,21 @@ export class MetamaskConnectEVM { /** * Disconnects from the wallet by revoking the session and cleaning up event listeners. * + * The entire session is revoked rather than just its `eip155:*` scopes. The + * wallet may grant additional scopes beyond those requested (e.g. it + * pre-selects all of the user's enabled networks for EIP-1193 compatible + * connections), and no other client on the page is responsible for cleaning + * those up. Revoking only the `eip155:*` scopes would strand the extra + * scopes in a session that the wallet still reports as connected. This also + * matches the legacy EIP-1193 behavior, where `wallet_revokePermissions` + * revokes the origin's entire CAIP-25 permission. + * * @returns A promise that resolves when disconnection is complete */ async disconnect(): Promise { logger('request: disconnect'); - const sessionScopes = this.#sessionScopes; - const eip155Scopes = Object.keys(sessionScopes).filter((scope) => { - const { namespace } = parseScopeString(scope as Scope); - return namespace === 'eip155'; - }); - - await this.#core.disconnect(eip155Scopes as Scope[]); + await this.#core.disconnect(); this.#onDisconnect(); this.#clearConnectionState();