Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/fix-ethers-account-switch-provider.md
Original file line number Diff line number Diff line change
@@ -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.
14 changes: 14 additions & 0 deletions packages/adapters/ethers/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ProviderType, 'metadata' | 'EIP6963'>]
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,
Expand Down
93 changes: 93 additions & 0 deletions packages/adapters/ethers/src/tests/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,99 @@ 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', () => {
Expand Down
14 changes: 14 additions & 0 deletions packages/adapters/ethers5/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ProviderType, 'metadata' | 'EIP6963'>]
if (ethersProvider) {
await ethersProvider.initialize()
connector.provider = (await ethersProvider.getProvider()) as Provider | undefined
Comment on lines +465 to +467
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the initialize returns the provider? If so it could be like:

Suggested change
if (ethersProvider) {
await ethersProvider.initialize()
connector.provider = (await ethersProvider.getProvider()) as Provider | undefined
if (ethersProvider) {
connector.provider = await ethersProvider.initialize()

}
}

this.emit('accountChanged', {
address: this.toChecksummedAddress(connection.account.address),
chainId: caipNetwork.id,
Expand Down
Loading