From f333bc0324f9ff1d695b1f06d125dfc0e27ae7ed Mon Sep 17 00:00:00 2001 From: Khizr Reown Date: Mon, 11 May 2026 21:38:46 +0500 Subject: [PATCH 1/3] fix(ethers,ethers5): resolve provider before emitting accountChanged in connect early-return path When switching accounts in the modal, connect() takes an early-return path for existing connections but never initialised connector.provider. This caused the base-client's accountChanged handler to skip syncProvider() (which guards on connector?.provider), leaving useAppKitProvider stale. Fix: resolve connector.provider from ethersProviders before emitting the event when it is not yet set. Applies to both ethers and ethers5 adapters. Co-Authored-By: Claude Sonnet 4.6 --- packages/adapters/ethers/src/client.ts | 14 +++ .../adapters/ethers/src/tests/client.test.ts | 89 +++++++++++++++++++ packages/adapters/ethers5/src/client.ts | 14 +++ 3 files changed, 117 insertions(+) diff --git a/packages/adapters/ethers/src/client.ts b/packages/adapters/ethers/src/client.ts index cad8f580bb..c3c763012c 100644 --- a/packages/adapters/ethers/src/client.ts +++ b/packages/adapters/ethers/src/client.ts @@ -459,6 +459,20 @@ export class EthersAdapter extends AdapterBlueprint { } if (connection.account) { + /* + * Resolve the provider before emitting so the base-client's accountChanged + * handler can call syncProvider() — keeping useAppKitProvider reactive + * when the user switches accounts inside the modal. + */ + if (!connector.provider) { + const ethersProvider = + this.ethersProviders[connector.id as keyof Omit] + if (ethersProvider) { + await ethersProvider.initialize() + connector.provider = (await ethersProvider.getProvider()) as Provider | undefined + } + } + this.emit('accountChanged', { address: this.toChecksummedAddress(connection.account.address), chainId: caipNetwork.id, diff --git a/packages/adapters/ethers/src/tests/client.test.ts b/packages/adapters/ethers/src/tests/client.test.ts index 0890bd410c..b6e4842932 100644 --- a/packages/adapters/ethers/src/tests/client.test.ts +++ b/packages/adapters/ethers/src/tests/client.test.ts @@ -394,6 +394,95 @@ describe('EthersAdapter', () => { preferredAccountType: 'smartAccount' }) }) + + it('should resolve provider from ethersProviders when connector has no provider in early-return path', async () => { + const resolvedProvider = { + request: vi.fn(), + on: vi.fn(), + removeListener: vi.fn() + } as unknown as Provider + + const mockEthersProvider = { + initialize: vi.fn().mockResolvedValue(undefined), + getProvider: vi.fn().mockResolvedValue(resolvedProvider) + } + + const connector = { id: 'injected', provider: undefined, type: 'EXTERNAL', chain: 'eip155' } + + Object.defineProperty(adapter, 'connectors', { + value: [connector], + configurable: true, + writable: true + }) + + adapter['ethersProviders'] = { injected: mockEthersProvider as any } + + vi.spyOn(adapter as any, 'getConnection').mockReturnValue({ + connectorId: 'injected', + caipNetwork: mainnet, + account: { address: '0x1234567890123456789012345678901234567890' }, + accounts: [{ address: '0x1234567890123456789012345678901234567890' }] + }) + + const accountChangedSpy = vi.fn() + adapter.on('accountChanged', accountChangedSpy) + + const result = await adapter.connect({ id: 'injected', type: 'EXTERNAL', chainId: 1 }) + + expect(mockEthersProvider.initialize).toHaveBeenCalled() + expect(mockEthersProvider.getProvider).toHaveBeenCalled() + expect(accountChangedSpy).toHaveBeenCalledWith( + expect.objectContaining({ connector: expect.objectContaining({ provider: resolvedProvider }) }) + ) + expect(result.provider).toBe(resolvedProvider) + }) + + it('should not re-initialize provider when connector already has one in early-return path', async () => { + const existingProvider = { + request: vi.fn(), + on: vi.fn(), + removeListener: vi.fn() + } as unknown as Provider + + const mockEthersProvider = { + initialize: vi.fn().mockResolvedValue(undefined), + getProvider: vi.fn() + } + + const connector = { + id: 'injected', + provider: existingProvider, + type: 'EXTERNAL', + chain: 'eip155' + } + + Object.defineProperty(adapter, 'connectors', { + value: [connector], + configurable: true, + writable: true + }) + + adapter['ethersProviders'] = { injected: mockEthersProvider as any } + + vi.spyOn(adapter as any, 'getConnection').mockReturnValue({ + connectorId: 'injected', + caipNetwork: mainnet, + account: { address: '0x1234567890123456789012345678901234567890' }, + accounts: [{ address: '0x1234567890123456789012345678901234567890' }] + }) + + const accountChangedSpy = vi.fn() + adapter.on('accountChanged', accountChangedSpy) + + const result = await adapter.connect({ id: 'injected', type: 'EXTERNAL', chainId: 1 }) + + expect(mockEthersProvider.initialize).not.toHaveBeenCalled() + expect(mockEthersProvider.getProvider).not.toHaveBeenCalled() + expect(accountChangedSpy).toHaveBeenCalledWith( + expect.objectContaining({ connector: expect.objectContaining({ provider: existingProvider }) }) + ) + expect(result.provider).toBe(existingProvider) + }) }) describe('EthersAdapter -reconnect', () => { diff --git a/packages/adapters/ethers5/src/client.ts b/packages/adapters/ethers5/src/client.ts index bdcb1f17db..4bbef4067d 100644 --- a/packages/adapters/ethers5/src/client.ts +++ b/packages/adapters/ethers5/src/client.ts @@ -454,6 +454,20 @@ export class Ethers5Adapter extends AdapterBlueprint { } if (connection.account) { + /* + * Resolve the provider before emitting so the base-client's accountChanged + * handler can call syncProvider() — keeping useAppKitProvider reactive + * when the user switches accounts inside the modal. + */ + if (!connector.provider) { + const ethersProvider = + this.ethersProviders[connector.id as keyof Omit] + if (ethersProvider) { + await ethersProvider.initialize() + connector.provider = (await ethersProvider.getProvider()) as Provider | undefined + } + } + this.emit('accountChanged', { address: this.toChecksummedAddress(connection.account.address), chainId: caipNetwork.id, From b8783f8e8dd1f70781872e8c2ef993f7b769da21 Mon Sep 17 00:00:00 2001 From: Khizr Reown Date: Mon, 11 May 2026 21:39:10 +0500 Subject: [PATCH 2/3] chore: apply prettier formatting to ethers adapter tests Co-Authored-By: Claude Sonnet 4.6 --- packages/adapters/ethers/src/tests/client.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/adapters/ethers/src/tests/client.test.ts b/packages/adapters/ethers/src/tests/client.test.ts index b6e4842932..50a5638336 100644 --- a/packages/adapters/ethers/src/tests/client.test.ts +++ b/packages/adapters/ethers/src/tests/client.test.ts @@ -432,7 +432,9 @@ describe('EthersAdapter', () => { expect(mockEthersProvider.initialize).toHaveBeenCalled() expect(mockEthersProvider.getProvider).toHaveBeenCalled() expect(accountChangedSpy).toHaveBeenCalledWith( - expect.objectContaining({ connector: expect.objectContaining({ provider: resolvedProvider }) }) + expect.objectContaining({ + connector: expect.objectContaining({ provider: resolvedProvider }) + }) ) expect(result.provider).toBe(resolvedProvider) }) @@ -479,7 +481,9 @@ describe('EthersAdapter', () => { expect(mockEthersProvider.initialize).not.toHaveBeenCalled() expect(mockEthersProvider.getProvider).not.toHaveBeenCalled() expect(accountChangedSpy).toHaveBeenCalledWith( - expect.objectContaining({ connector: expect.objectContaining({ provider: existingProvider }) }) + expect.objectContaining({ + connector: expect.objectContaining({ provider: existingProvider }) + }) ) expect(result.provider).toBe(existingProvider) }) From 3301083e77299ebc6c455a44d1a1956cb5dc5c3a Mon Sep 17 00:00:00 2001 From: Khizr Reown Date: Mon, 11 May 2026 21:39:44 +0500 Subject: [PATCH 3/3] chore: add changeset for REOWN-4657 Ethers walletProvider account switch fix Co-Authored-By: Claude Sonnet 4.6 --- .changeset/fix-ethers-account-switch-provider.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .changeset/fix-ethers-account-switch-provider.md diff --git a/.changeset/fix-ethers-account-switch-provider.md b/.changeset/fix-ethers-account-switch-provider.md new file mode 100644 index 0000000000..a63271a235 --- /dev/null +++ b/.changeset/fix-ethers-account-switch-provider.md @@ -0,0 +1,12 @@ +--- +"@reown/appkit-adapter-ethers": patch +"@reown/appkit-adapter-ethers5": patch +--- + +fix(ethers,ethers5): resolve walletProvider after account switch in modal + +`useAppKitProvider` returned a stale provider when switching accounts inside the +modal. In the early-return path of `connect()`, `connector.provider` was never +initialised, causing the base-client's `accountChanged` handler to skip +`syncProvider()`. The provider is now resolved from `ethersProviders` before the +event is emitted.