-
Notifications
You must be signed in to change notification settings - Fork 1.5k
fix: use up-to-date chainId/accounts when querying EIP1193-derived wallets #749
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
b25f3c1
b83c482
f2c6637
aa720fd
f4ceed0
e898b7b
55713bd
0573c72
8f20745
ffce572
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -85,17 +85,14 @@ export class CoinbaseWallet extends Connector { | |
| try { | ||
| await this.isomorphicInitialize() | ||
|
|
||
| if (!this.connected) throw new Error('No existing connection') | ||
|
|
||
| return Promise.all([ | ||
| // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
| this.provider!.request<string>({ method: 'eth_chainId' }), | ||
| // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
| this.provider!.request<string[]>({ method: 'eth_accounts' }), | ||
| ]).then(([chainId, accounts]) => { | ||
| if (!accounts.length) throw new Error('No accounts returned') | ||
| this.actions.update({ chainId: parseChainId(chainId), accounts }) | ||
| }) | ||
| if (!this.provider || !this.connected) throw new Error('No existing connection') | ||
|
|
||
| // Wallets may resolve eth_chainId and hang on eth_accounts pending user interaction, which may include changing | ||
| // chains; they should be requested serially, with accounts first, so that the chainId can settle. | ||
| const accounts = await this.provider.request<string[]>({ method: 'eth_accounts' }) | ||
| const chainId = await this.provider.request<string>({ method: 'eth_chainId' }) | ||
| if (!accounts.length) throw new Error('No accounts returned') | ||
| this.actions.update({ chainId: parseChainId(chainId), accounts }) | ||
| } catch (error) { | ||
| cancelActivation() | ||
| throw error | ||
|
|
@@ -117,20 +114,18 @@ export class CoinbaseWallet extends Connector { | |
| ? desiredChainIdOrChainParameters | ||
| : desiredChainIdOrChainParameters?.chainId | ||
|
|
||
| if (this.connected) { | ||
| // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
| if (!desiredChainId || desiredChainId === parseChainId(this.provider!.chainId)) return | ||
| if (this.provider && this.connected) { | ||
| if (!desiredChainId || desiredChainId === parseChainId(this.provider.chainId)) return | ||
|
|
||
| const desiredChainIdHex = `0x${desiredChainId.toString(16)}` | ||
| // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
| return this.provider!.request<void>({ | ||
| return this.provider.request<void>({ | ||
| method: 'wallet_switchEthereumChain', | ||
| params: [{ chainId: desiredChainIdHex }], | ||
| }).catch(async (error: ProviderRpcError) => { | ||
| if (error.code === 4902 && typeof desiredChainIdOrChainParameters !== 'number') { | ||
| if (!this.provider) throw new Error('No provider') | ||
| // if we're here, we can try to add a new network | ||
| // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
| return this.provider!.request<void>({ | ||
| return this.provider.request<void>({ | ||
| method: 'wallet_addEthereumChain', | ||
| params: [{ ...desiredChainIdOrChainParameters, chainId: desiredChainIdHex }], | ||
| }) | ||
|
|
@@ -144,38 +139,36 @@ export class CoinbaseWallet extends Connector { | |
|
|
||
| try { | ||
| await this.isomorphicInitialize() | ||
| if (!this.provider) throw new Error('No provider') | ||
|
|
||
| return Promise.all([ | ||
| // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
| this.provider!.request<string>({ method: 'eth_chainId' }), | ||
| // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
| this.provider!.request<string[]>({ method: 'eth_requestAccounts' }), | ||
| ]).then(([chainId, accounts]) => { | ||
| const receivedChainId = parseChainId(chainId) | ||
|
|
||
| if (!desiredChainId || desiredChainId === receivedChainId) | ||
| return this.actions.update({ chainId: receivedChainId, accounts }) | ||
|
|
||
| // if we're here, we can try to switch networks | ||
| const desiredChainIdHex = `0x${desiredChainId.toString(16)}` | ||
| return this.provider | ||
| ?.request<void>({ | ||
| method: 'wallet_switchEthereumChain', | ||
| params: [{ chainId: desiredChainIdHex }], | ||
| }) | ||
| .catch(async (error: ProviderRpcError) => { | ||
| if (error.code === 4902 && typeof desiredChainIdOrChainParameters !== 'number') { | ||
| // if we're here, we can try to add a new network | ||
| // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
| return this.provider!.request<void>({ | ||
| method: 'wallet_addEthereumChain', | ||
| params: [{ ...desiredChainIdOrChainParameters, chainId: desiredChainIdHex }], | ||
| }) | ||
| } | ||
|
|
||
| throw error | ||
| }) | ||
| }) | ||
| // Wallets may resolve eth_chainId and hang on eth_accounts pending user interaction, which may include changing | ||
| // chains; they should be requested serially, with accounts first, so that the chainId can settle. | ||
| const accounts = await this.provider.request<string[]>({ method: 'eth_requestAccounts' }) | ||
| const chainId = await this.provider.request<string>({ method: 'eth_chainId' }) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: probably out of scope for this PR, but I would consider adding to a todo list: potential candidate for abstracting away? Looks like a common code path here and few lines earlier.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that web3-react is the abstraction but, more importantly, I don't want to make this a large change because it's meant to be a bug fix. |
||
| const receivedChainId = parseChainId(chainId) | ||
|
|
||
| if (!desiredChainId || desiredChainId === receivedChainId) | ||
| return this.actions.update({ chainId: receivedChainId, accounts }) | ||
|
|
||
| // if we're here, we can try to switch networks | ||
| const desiredChainIdHex = `0x${desiredChainId.toString(16)}` | ||
| return this.provider | ||
| ?.request<void>({ | ||
| method: 'wallet_switchEthereumChain', | ||
| params: [{ chainId: desiredChainIdHex }], | ||
| }) | ||
| .catch(async (error: ProviderRpcError) => { | ||
| if (error.code === 4902 && typeof desiredChainIdOrChainParameters !== 'number') { | ||
| if (!this.provider) throw new Error('No provider') | ||
| // if we're here, we can try to add a new network | ||
| return this.provider.request<void>({ | ||
| method: 'wallet_addEthereumChain', | ||
| params: [{ ...desiredChainIdOrChainParameters, chainId: desiredChainIdHex }], | ||
| }) | ||
| } | ||
|
|
||
| throw error | ||
| }) | ||
| } catch (error) { | ||
| cancelActivation() | ||
| throw error | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,39 +42,30 @@ export class EIP1193 extends Connector { | |
| }) | ||
| } | ||
|
|
||
| /** {@inheritdoc Connector.connectEagerly} */ | ||
| public async connectEagerly(): Promise<void> { | ||
| private async connect(eager: boolean): Promise<void> { | ||
| const cancelActivation = this.actions.startActivation() | ||
|
|
||
| return Promise.all([ | ||
| this.provider.request({ method: 'eth_chainId' }) as Promise<string>, | ||
| this.provider.request({ method: 'eth_accounts' }) as Promise<string[]>, | ||
| ]) | ||
| .then(([chainId, accounts]) => { | ||
| this.actions.update({ chainId: parseChainId(chainId), accounts }) | ||
| }) | ||
| .catch((error) => { | ||
| try { | ||
| // Wallets may resolve eth_chainId and hang on eth_accounts pending user interaction, which may include changing | ||
| // chains; they should be requested serially, with accounts first, so that the chainId can settle. | ||
| const accounts = await (eager ? this.provider.request({ method: 'eth_accounts' }) : this.provider | ||
|
||
| .request({ method: 'eth_requestAccounts' }) | ||
| .catch(() => this.provider.request({ method: 'eth_accounts' }))) as string[] | ||
| const chainId = await this.provider.request({ method: 'eth_chainId' }) as string | ||
| this.actions.update({ chainId: parseChainId(chainId), accounts }) | ||
| } catch (error) { | ||
| cancelActivation() | ||
| throw error | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| /** {@inheritdoc Connector.connectEagerly} */ | ||
| public async connectEagerly(): Promise<void> { | ||
| return this.connect(true) | ||
| } | ||
|
|
||
| /** {@inheritdoc Connector.activate} */ | ||
| public async activate(): Promise<void> { | ||
| const cancelActivation = this.actions.startActivation() | ||
|
|
||
| return Promise.all([ | ||
| this.provider.request({ method: 'eth_chainId' }) as Promise<string>, | ||
| this.provider | ||
| .request({ method: 'eth_requestAccounts' }) | ||
| .catch(() => this.provider.request({ method: 'eth_accounts' })) as Promise<string[]>, | ||
| ]) | ||
| .then(([chainId, accounts]) => { | ||
| this.actions.update({ chainId: parseChainId(chainId), accounts }) | ||
| }) | ||
| .catch((error) => { | ||
| cancelActivation() | ||
| throw error | ||
| }) | ||
| return this.connect(false) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import type { ProviderRpcError, RequestArguments } from '@web3-react/types' | ||
| import { EventEmitter } from 'node:events' | ||
|
|
||
| export class MockEIP1193Provider extends EventEmitter { | ||
| public chainId?: string | ||
| public accounts?: string[] | ||
|
|
||
| public eth_chainId = jest.fn((chainId?: string) => chainId) | ||
| public eth_accounts = jest.fn((accounts?: string[]) => accounts) | ||
| public eth_requestAccounts = jest.fn((accounts?: string[]) => accounts) | ||
|
|
||
| public request(x: RequestArguments): Promise<unknown> { | ||
| // make sure to throw if we're "not connected" | ||
| if (!this.chainId) return Promise.reject(new Error()) | ||
|
|
||
| switch (x.method) { | ||
| case 'eth_chainId': | ||
| return Promise.resolve(this.eth_chainId(this.chainId)) | ||
| case 'eth_accounts': | ||
| return Promise.resolve(this.eth_accounts(this.accounts)) | ||
| case 'eth_requestAccounts': | ||
| return Promise.resolve(this.eth_requestAccounts(this.accounts)) | ||
| default: | ||
| throw new Error() | ||
| } | ||
| } | ||
|
|
||
| public emitConnect(chainId: string) { | ||
| this.emit('connect', { chainId }) | ||
| } | ||
|
|
||
| public emitDisconnect(error: ProviderRpcError) { | ||
| this.emit('disconnect', error) | ||
| } | ||
|
|
||
| public emitChainChanged(chainId: string) { | ||
| this.emit('chainChanged', chainId) | ||
| } | ||
|
|
||
| public emitAccountsChanged(accounts: string[]) { | ||
| this.emit('accountsChanged', accounts) | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.