@@ -11,7 +11,6 @@ import {
11
11
createConnector ,
12
12
extractRpcUrls ,
13
13
} from '@wagmi/core'
14
- import type { linea , lineaSepolia , mainnet , sepolia } from '@wagmi/core/chains'
15
14
import type {
16
15
Compute ,
17
16
ExactPartial ,
@@ -24,6 +23,7 @@ import {
24
23
type Address ,
25
24
type Hex ,
26
25
type ProviderConnectInfo ,
26
+ type ProviderRpcError ,
27
27
ResourceUnavailableRpcError ,
28
28
type RpcError ,
29
29
SwitchChainError ,
@@ -315,57 +315,11 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
315
315
const chain = config . chains . find ( ( x ) => x . id === chainId )
316
316
if ( ! chain ) throw new SwitchChainError ( new ChainNotConfiguredError ( ) )
317
317
318
- // Default chains cannot be added or removed
319
- const isDefaultChain = ( ( ) => {
320
- const metaMaskDefaultChains = [
321
- 1 , 11_155_111 , 59_144 , 59_141 ,
322
- ] satisfies [
323
- typeof mainnet . id ,
324
- typeof sepolia . id ,
325
- typeof linea . id ,
326
- typeof lineaSepolia . id ,
327
- ]
328
- return metaMaskDefaultChains . find ( ( x ) => x === chainId )
329
- } ) ( )
330
-
331
- // Avoid back and forth on mobile by using `'wallet_addEthereumChain'` for non-default chains
332
318
try {
333
- if ( ! isDefaultChain )
334
- await provider . request ( {
335
- method : 'wallet_addEthereumChain' ,
336
- params : [
337
- {
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
- } ) ( ) ,
350
- chainId : numberToHex ( chainId ) ,
351
- chainName : addEthereumChainParameter ?. chainName ?? chain . name ,
352
- iconUrls : addEthereumChainParameter ?. iconUrls ,
353
- nativeCurrency :
354
- addEthereumChainParameter ?. nativeCurrency ??
355
- chain . nativeCurrency ,
356
- rpcUrls : ( ( ) => {
357
- if ( addEthereumChainParameter ?. rpcUrls ?. length )
358
- return addEthereumChainParameter . rpcUrls
359
- return [ chain . rpcUrls . default ?. http [ 0 ] ?? '' ]
360
- } ) ( ) ,
361
- } satisfies AddEthereumChainParameter ,
362
- ] ,
363
- } )
364
- else
365
- await provider . request ( {
366
- method : 'wallet_switchEthereumChain' ,
367
- params : [ { chainId : numberToHex ( chainId ) } ] ,
368
- } )
319
+ await provider . request ( {
320
+ method : 'wallet_switchEthereumChain' ,
321
+ params : [ { chainId : numberToHex ( chainId ) } ] ,
322
+ } )
369
323
370
324
// During `'wallet_switchEthereumChain'`, MetaMask makes a `'net_version'` RPC call to the target chain.
371
325
// If this request fails, MetaMask does not emit the `'chainChanged'` event, but will still switch the chain.
@@ -375,47 +329,101 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
375
329
await waitForChainIdToSync ( )
376
330
await sendAndWaitForChangeEvent ( chainId )
377
331
378
- async function waitForChainIdToSync ( ) {
379
- // On mobile, there is a race condition between the result of `'wallet_addEthereumChain'` and `'eth_chainId'`.
380
- // To avoid this, we wait for `'eth_chainId'` to return the expected chain ID with a retry loop.
381
- await withRetry (
382
- async ( ) => {
383
- const value = hexToNumber (
384
- // `'eth_chainId'` is cached by the MetaMask SDK side to avoid unnecessary deeplinks
385
- ( await provider . request ( { method : 'eth_chainId' } ) ) as Hex ,
386
- )
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.' )
390
- return value
391
- } ,
392
- {
393
- delay : 50 ,
394
- retryCount : 20 , // android device encryption is slower
395
- } ,
396
- )
397
- }
398
-
399
- async function sendAndWaitForChangeEvent ( chainId : number ) {
400
- await new Promise < void > ( ( resolve ) => {
401
- const listener = ( ( data ) => {
402
- if ( 'chainId' in data && data . chainId === chainId ) {
403
- config . emitter . off ( 'change' , listener )
404
- resolve ( )
405
- }
406
- } ) satisfies Parameters < typeof config . emitter . on > [ 1 ]
407
- config . emitter . on ( 'change' , listener )
408
- config . emitter . emit ( 'change' , { chainId } )
409
- } )
410
- }
411
-
412
332
return chain
413
333
} catch ( err ) {
414
334
const error = err as RpcError
335
+
415
336
if ( error . code === UserRejectedRequestError . code )
416
337
throw new UserRejectedRequestError ( error )
338
+
339
+ // Indicates chain is not added to provider
340
+ if (
341
+ error . code === 4902 ||
342
+ // Unwrapping for MetaMask Mobile
343
+ // https://github.com/MetaMask/metamask-mobile/issues/2944#issuecomment-976988719
344
+ ( error as ProviderRpcError < { originalError ?: { code : number } } > )
345
+ ?. data ?. originalError ?. code === 4902
346
+ ) {
347
+ try {
348
+ await provider . request ( {
349
+ method : 'wallet_addEthereumChain' ,
350
+ params : [
351
+ {
352
+ blockExplorerUrls : ( ( ) => {
353
+ const { default : blockExplorer , ...blockExplorers } =
354
+ chain . blockExplorers ?? { }
355
+ if ( addEthereumChainParameter ?. blockExplorerUrls )
356
+ return addEthereumChainParameter . blockExplorerUrls
357
+ if ( blockExplorer )
358
+ return [
359
+ blockExplorer . url ,
360
+ ...Object . values ( blockExplorers ) . map ( ( x ) => x . url ) ,
361
+ ]
362
+ return
363
+ } ) ( ) ,
364
+ chainId : numberToHex ( chainId ) ,
365
+ chainName : addEthereumChainParameter ?. chainName ?? chain . name ,
366
+ iconUrls : addEthereumChainParameter ?. iconUrls ,
367
+ nativeCurrency :
368
+ addEthereumChainParameter ?. nativeCurrency ??
369
+ chain . nativeCurrency ,
370
+ rpcUrls : ( ( ) => {
371
+ if ( addEthereumChainParameter ?. rpcUrls ?. length )
372
+ return addEthereumChainParameter . rpcUrls
373
+ return [ chain . rpcUrls . default ?. http [ 0 ] ?? '' ]
374
+ } ) ( ) ,
375
+ } satisfies AddEthereumChainParameter ,
376
+ ] ,
377
+ } )
378
+
379
+ await waitForChainIdToSync ( )
380
+ await sendAndWaitForChangeEvent ( chainId )
381
+
382
+ return chain
383
+ } catch ( err ) {
384
+ const error = err as RpcError
385
+ if ( error . code === UserRejectedRequestError . code )
386
+ throw new UserRejectedRequestError ( error )
387
+ throw new SwitchChainError ( error )
388
+ }
389
+ }
390
+
417
391
throw new SwitchChainError ( error )
418
392
}
393
+
394
+ async function waitForChainIdToSync ( ) {
395
+ // On mobile, there is a race condition between the result of `'wallet_addEthereumChain'` and `'eth_chainId'`.
396
+ // To avoid this, we wait for `'eth_chainId'` to return the expected chain ID with a retry loop.
397
+ await withRetry (
398
+ async ( ) => {
399
+ const value = hexToNumber (
400
+ // `'eth_chainId'` is cached by the MetaMask SDK side to avoid unnecessary deeplinks
401
+ ( await provider . request ( { method : 'eth_chainId' } ) ) as Hex ,
402
+ )
403
+ // `value` doesn't match expected `chainId`, throw to trigger retry
404
+ if ( value !== chainId )
405
+ throw new Error ( 'User rejected switch after adding network.' )
406
+ return value
407
+ } ,
408
+ {
409
+ delay : 50 ,
410
+ retryCount : 20 , // android device encryption is slower
411
+ } ,
412
+ )
413
+ }
414
+
415
+ async function sendAndWaitForChangeEvent ( chainId : number ) {
416
+ await new Promise < void > ( ( resolve ) => {
417
+ const listener = ( ( data ) => {
418
+ if ( 'chainId' in data && data . chainId === chainId ) {
419
+ config . emitter . off ( 'change' , listener )
420
+ resolve ( )
421
+ }
422
+ } ) satisfies Parameters < typeof config . emitter . on > [ 1 ]
423
+ config . emitter . on ( 'change' , listener )
424
+ config . emitter . emit ( 'change' , { chainId } )
425
+ } )
426
+ }
419
427
} ,
420
428
async onAccountsChanged ( accounts ) {
421
429
// Disconnect if there are no accounts
0 commit comments