Skip to content

Commit 9c8c35a

Browse files
EdouardBougonbaptiste-marchandtmm
authored
feat: update MetaMask version and fix switch network (#4471)
* fix: update metamask sdk * Refactor: MetaMask switchNetwork consistently emit a 'change' event * Refactor: remove duplicated MetaMask switchNetwork logic * Add MetaMask to vite-vue example * refactor: reorg * chore: changeset --------- Co-authored-by: Baptiste Marchand <[email protected]> Co-authored-by: Tom Meagher <[email protected]>
1 parent 3892ebd commit 9c8c35a

File tree

5 files changed

+93
-120
lines changed

5 files changed

+93
-120
lines changed

.changeset/afraid-cows-mix.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@wagmi/connectors": patch
3+
---
4+
5+
Improved MetaMask chain switching behavior.

packages/connectors/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
},
4747
"dependencies": {
4848
"@coinbase/wallet-sdk": "4.2.3",
49-
"@metamask/sdk": "0.31.2",
49+
"@metamask/sdk": "0.31.4",
5050
"@safe-global/safe-apps-provider": "0.18.5",
5151
"@safe-global/safe-apps-sdk": "9.1.0",
5252
"@walletconnect/ethereum-provider": "2.17.0",

packages/connectors/src/metaMask.ts

Lines changed: 45 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -329,106 +329,86 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
329329
})()
330330

331331
// Avoid back and forth on mobile by using `'wallet_addEthereumChain'` for non-default chains
332-
if (!isDefaultChain)
333-
try {
334-
const blockExplorerUrls = (() => {
335-
const { default: blockExplorer, ...blockExplorers } =
336-
chain.blockExplorers ?? {}
337-
if (addEthereumChainParameter?.blockExplorerUrls)
338-
return addEthereumChainParameter.blockExplorerUrls
339-
if (blockExplorer)
340-
return [
341-
blockExplorer.url,
342-
...Object.values(blockExplorers).map((x) => x.url),
343-
]
344-
return
345-
})()
346-
347-
const rpcUrls = (() => {
348-
if (addEthereumChainParameter?.rpcUrls?.length)
349-
return addEthereumChainParameter.rpcUrls
350-
return [chain.rpcUrls.default?.http[0] ?? '']
351-
})()
352-
332+
try {
333+
if (!isDefaultChain)
353334
await provider.request({
354335
method: 'wallet_addEthereumChain',
355336
params: [
356337
{
357-
blockExplorerUrls,
338+
blockExplorerUrls: (() => {
339+
const { default: blockExplorer, ...blockExplorers } =
340+
chain.blockExplorers ?? {}
341+
if (addEthereumChainParameter?.blockExplorerUrls)
342+
return addEthereumChainParameter.blockExplorerUrls
343+
if (blockExplorer)
344+
return [
345+
blockExplorer.url,
346+
...Object.values(blockExplorers).map((x) => x.url),
347+
]
348+
return
349+
})(),
358350
chainId: numberToHex(chainId),
359351
chainName: addEthereumChainParameter?.chainName ?? chain.name,
360352
iconUrls: addEthereumChainParameter?.iconUrls,
361353
nativeCurrency:
362354
addEthereumChainParameter?.nativeCurrency ??
363355
chain.nativeCurrency,
364-
rpcUrls,
356+
rpcUrls: (() => {
357+
if (addEthereumChainParameter?.rpcUrls?.length)
358+
return addEthereumChainParameter.rpcUrls
359+
return [chain.rpcUrls.default?.http[0] ?? '']
360+
})(),
365361
} satisfies AddEthereumChainParameter,
366362
],
367363
})
364+
else
365+
await provider.request({
366+
method: 'wallet_switchEthereumChain',
367+
params: [{ chainId: numberToHex(chainId) }],
368+
})
369+
370+
// During `'wallet_switchEthereumChain'`, MetaMask makes a `'net_version'` RPC call to the target chain.
371+
// If this request fails, MetaMask does not emit the `'chainChanged'` event, but will still switch the chain.
372+
// To counter this behavior, we request and emit the current chain ID to confirm the chain switch either via
373+
// this callback or an externally emitted `'chainChanged'` event.
374+
// https://github.com/MetaMask/metamask-extension/issues/24247
375+
await waitForChainIdToSync()
376+
await sendAndWaitForChangeEvent(chainId)
368377

378+
async function waitForChainIdToSync() {
369379
// On mobile, there is a race condition between the result of `'wallet_addEthereumChain'` and `'eth_chainId'`.
370-
// (`'eth_chainId'` from the MetaMask relay server).
371380
// To avoid this, we wait for `'eth_chainId'` to return the expected chain ID with a retry loop.
372-
let retryCount = 0
373-
const currentChainId = await withRetry(
381+
await withRetry(
374382
async () => {
375-
retryCount += 1
376383
const value = hexToNumber(
377384
// `'eth_chainId'` is cached by the MetaMask SDK side to avoid unnecessary deeplinks
378385
(await provider.request({ method: 'eth_chainId' })) as Hex,
379386
)
380-
if (value !== chainId) {
381-
if (retryCount === 5) return -1
382-
// `value` doesn't match expected `chainId`, throw to trigger retry
383-
throw new Error('Chain ID mismatch')
384-
}
387+
// `value` doesn't match expected `chainId`, throw to trigger retry
388+
if (value !== chainId)
389+
throw new Error('User rejected switch after adding network.')
385390
return value
386391
},
387392
{
388-
delay: 100,
389-
retryCount: 5, // android device encryption is slower
393+
delay: 50,
394+
retryCount: 20, // android device encryption is slower
390395
},
391396
)
392-
393-
if (currentChainId !== chainId)
394-
throw new Error('User rejected switch after adding network.')
395-
396-
return chain
397-
} catch (err) {
398-
const error = err as RpcError
399-
if (error.code === UserRejectedRequestError.code)
400-
throw new UserRejectedRequestError(error)
401-
throw new SwitchChainError(error)
402397
}
403398

404-
// Use to `'wallet_switchEthereumChain'` for default chains
405-
try {
406-
await Promise.all([
407-
provider
408-
.request({
409-
method: 'wallet_switchEthereumChain',
410-
params: [{ chainId: numberToHex(chainId) }],
411-
})
412-
// During `'wallet_switchEthereumChain'`, MetaMask makes a `'net_version'` RPC call to the target chain.
413-
// If this request fails, MetaMask does not emit the `'chainChanged'` event, but will still switch the chain.
414-
// To counter this behavior, we request and emit the current chain ID to confirm the chain switch either via
415-
// this callback or an externally emitted `'chainChanged'` event.
416-
// https://github.com/MetaMask/metamask-extension/issues/24247
417-
.then(async () => {
418-
const currentChainId = await this.getChainId()
419-
if (currentChainId === chainId)
420-
config.emitter.emit('change', { chainId })
421-
}),
422-
new Promise<void>((resolve) => {
399+
async function sendAndWaitForChangeEvent(chainId: number) {
400+
await new Promise<void>((resolve) => {
423401
const listener = ((data) => {
424402
if ('chainId' in data && data.chainId === chainId) {
425403
config.emitter.off('change', listener)
426404
resolve()
427405
}
428406
}) satisfies Parameters<typeof config.emitter.on>[1]
429407
config.emitter.on('change', listener)
430-
}),
431-
])
408+
config.emitter.emit('change', { chainId })
409+
})
410+
}
411+
432412
return chain
433413
} catch (err) {
434414
const error = err as RpcError

playgrounds/vite-vue/src/wagmi.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { http, createConfig, createStorage } from '@wagmi/vue'
22
import { mainnet, optimism, sepolia } from '@wagmi/vue/chains'
3-
import { coinbaseWallet, walletConnect } from '@wagmi/vue/connectors'
3+
import { coinbaseWallet, metaMask, walletConnect } from '@wagmi/vue/connectors'
44

55
export const config = createConfig({
66
chains: [mainnet, sepolia, optimism],
@@ -9,6 +9,7 @@ export const config = createConfig({
99
projectId: import.meta.env.VITE_WC_PROJECT_ID,
1010
}),
1111
coinbaseWallet({ appName: 'Vite Vue Playground', darkMode: true }),
12+
metaMask(),
1213
],
1314
storage: createStorage({ storage: localStorage, key: 'vite-vue' }),
1415
transports: {

pnpm-lock.yaml

Lines changed: 40 additions & 53 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)