Skip to content
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

feat: Improve chain switching via MetaMask connector #4397

Closed
wants to merge 4 commits into from
Closed
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
5 changes: 5 additions & 0 deletions .changeset/thirty-icons-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@wagmi/connectors": patch
---

feat: Improve chain switching via MetaMask connector
117 changes: 60 additions & 57 deletions packages/connectors/src/metaMask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
createConnector,
extractRpcUrls,
} from '@wagmi/core'
import { linea, lineaSepolia, mainnet, sepolia } from '@wagmi/core/chains'
import type {
Compute,
ExactPartial,
Expand All @@ -23,7 +24,6 @@ import {
type Address,
type Hex,
type ProviderConnectInfo,
type ProviderRpcError,
ResourceUnavailableRpcError,
type RpcError,
SwitchChainError,
Expand Down Expand Up @@ -308,6 +308,65 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
const chain = config.chains.find((x) => x.id === chainId)
if (!chain) throw new SwitchChainError(new ChainNotConfiguredError())

// MetaMask default chains
// This avoids the need to react to the "unrecognized chain" error
// and consequent back and forth between MetaMask and Wagmi.
const metaMaskDefaultChains = [
mainnet.id,
sepolia.id,
linea.id,
lineaSepolia.id,
]
const isDefaultChain = metaMaskDefaultChains.find((x) => x === chainId)

if (!isDefaultChain) {
try {
const { default: blockExplorer, ...blockExplorers } =
chain.blockExplorers ?? {}
let blockExplorerUrls: string[] | undefined
if (addEthereumChainParameter?.blockExplorerUrls)
blockExplorerUrls = addEthereumChainParameter.blockExplorerUrls
else if (blockExplorer)
blockExplorerUrls = [
blockExplorer.url,
...Object.values(blockExplorers).map((x) => x.url),
]

let rpcUrls: readonly string[]
if (addEthereumChainParameter?.rpcUrls?.length)
rpcUrls = addEthereumChainParameter.rpcUrls
else rpcUrls = [chain.rpcUrls.default?.http[0] ?? '']

const addEthereumChain = {
blockExplorerUrls,
chainId: numberToHex(chainId),
chainName: addEthereumChainParameter?.chainName ?? chain.name,
iconUrls: addEthereumChainParameter?.iconUrls,
nativeCurrency:
addEthereumChainParameter?.nativeCurrency ?? chain.nativeCurrency,
rpcUrls,
} satisfies AddEthereumChainParameter

await provider.request({
method: 'wallet_addEthereumChain',
params: [addEthereumChain],
})

const currentChainId = hexToNumber(
// Call `'eth_chainId'` directly to guard against `this.state.chainId` (via `provider.getChainId`) being stale.
(await provider.request({ method: 'eth_chainId' })) as Hex,
)
if (currentChainId !== chainId)
throw new UserRejectedRequestError(
new Error('User rejected switch after adding network.'),
)

return chain
} catch (error) {
throw new UserRejectedRequestError(error as Error)
}
}

try {
await Promise.all([
provider
Expand Down Expand Up @@ -339,62 +398,6 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
} catch (err) {
const error = err as RpcError

// Indicates chain is not added to provider
if (
error.code === 4902 ||
// Unwrapping for MetaMask Mobile
// https://github.com/MetaMask/metamask-mobile/issues/2944#issuecomment-976988719
(error as ProviderRpcError<{ originalError?: { code: number } }>)
?.data?.originalError?.code === 4902
) {
try {
const { default: blockExplorer, ...blockExplorers } =
chain.blockExplorers ?? {}
let blockExplorerUrls: string[] | undefined
if (addEthereumChainParameter?.blockExplorerUrls)
blockExplorerUrls = addEthereumChainParameter.blockExplorerUrls
else if (blockExplorer)
blockExplorerUrls = [
blockExplorer.url,
...Object.values(blockExplorers).map((x) => x.url),
]

let rpcUrls: readonly string[]
if (addEthereumChainParameter?.rpcUrls?.length)
rpcUrls = addEthereumChainParameter.rpcUrls
else rpcUrls = [chain.rpcUrls.default?.http[0] ?? '']

const addEthereumChain = {
blockExplorerUrls,
chainId: numberToHex(chainId),
chainName: addEthereumChainParameter?.chainName ?? chain.name,
iconUrls: addEthereumChainParameter?.iconUrls,
nativeCurrency:
addEthereumChainParameter?.nativeCurrency ??
chain.nativeCurrency,
rpcUrls,
} satisfies AddEthereumChainParameter

await provider.request({
method: 'wallet_addEthereumChain',
params: [addEthereumChain],
})

const currentChainId = hexToNumber(
// Call `'eth_chainId'` directly to guard against `this.state.chainId` (via `provider.getChainId`) being stale.
(await provider.request({ method: 'eth_chainId' })) as Hex,
)
if (currentChainId !== chainId)
throw new UserRejectedRequestError(
new Error('User rejected switch after adding network.'),
)

return chain
} catch (error) {
throw new UserRejectedRequestError(error as Error)
}
}

if (error.code === UserRejectedRequestError.code)
throw new UserRejectedRequestError(error)
throw new SwitchChainError(error)
Expand Down