Интеграция кошелька с MetaMask
MetaMask инжектирует window.ethereum в страницу — и на этом прямая работа с провайдером обычно заканчивается. В 2024 году писать window.ethereum.request({ method: 'eth_requestAccounts' }) напрямую — это либо прототип, либо технический долг. Правильная интеграция через wagmi + viem обрабатывает все edge cases: несколько кошельков в браузере, смена сети, разрыв соединения, мобильный MetaMask через deeplink.
Стек и подключение
wagmi v2 + viem — стандарт для современных dApp. Wagmi управляет состоянием подключения, автоматическим переподключением, событиями. Viem — типизированный low-level клиент вместо ethers.js.
import { createConfig, http } from 'wagmi'
import { mainnet, base } from 'wagmi/chains'
import { metaMask } from 'wagmi/connectors'
export const config = createConfig({
chains: [mainnet, base],
connectors: [metaMask()],
transports: {
[mainnet.id]: http(),
[base.id]: http(),
},
})
Кнопка подключения:
import { useConnect, useAccount, useDisconnect } from 'wagmi'
function ConnectButton() {
const { address, isConnected } = useAccount()
const { connect, connectors } = useConnect()
const { disconnect } = useDisconnect()
if (isConnected) {
return (
<button onClick={() => disconnect()}>
{address?.slice(0, 6)}...{address?.slice(-4)}
</button>
)
}
const metamask = connectors.find(c => c.id === 'metaMaskSDK')
return (
<button onClick={() => connect({ connector: metamask! })}>
Connect MetaMask
</button>
)
}
Смена сети и добавление кастомной сети
Частая проблема: пользователь на Ethereum mainnet, dApp работает на Base. Нужно запросить переключение, а если сеть не добавлена — добавить её.
import { useSwitchChain } from 'wagmi'
function NetworkGuard({ children, requiredChainId }: Props) {
const { chainId } = useAccount()
const { switchChain, isPending } = useSwitchChain()
if (chainId !== requiredChainId) {
return (
<button
onClick={() => switchChain({ chainId: requiredChainId })}
disabled={isPending}
>
Switch to Base
</button>
)
}
return children
}
wagmi автоматически вызывает wallet_addEthereumChain если wallet_switchEthereumChain возвращает ошибку 4902 (chain not found).
Подпись сообщений и транзакции
EIP-712 typed data signing — для структурированных подписей (permit, orders, auth tokens):
import { useSignTypedData } from 'wagmi'
const { signTypedData } = useSignTypedData()
signTypedData({
domain: { name: 'MyApp', version: '1', chainId: 8453 },
types: {
Login: [
{ name: 'address', type: 'address' },
{ name: 'nonce', type: 'string' },
],
},
primaryType: 'Login',
message: { address: userAddress, nonce: sessionNonce },
})
Для SIWE (Sign-In with Ethereum) — siwe библиотека генерирует стандартизированное сообщение, backend верифицирует через SiweMessage.verify().
Обработка ошибок
MetaMask возвращает специфичные error codes:
-
4001— пользователь отклонил запрос -
4902— сеть не найдена -
-32002— запрос уже ожидает подтверждения (второй вызов connect пока первый висит) -
-32603— internal JSON-RPC error (чаще всего insufficient gas или revert)
const { error } = useConnect()
useEffect(() => {
if (error?.name === 'UserRejectedRequestError') {
toast('Подключение отменено')
} else if (error?.name === 'ConnectorAlreadyConnectedError') {
// уже подключён, игнорируем
}
}, [error])
Mobile deeplink
На мобильных MetaMask открывается через deeplink metamask://dapp/<your-url>. wagmi MetaMask connector автоматически обрабатывает это через MetaMask SDK. Для корректной работы на iOS нужен HTTPS (не HTTP) и правильно настроенный WagmiProvider на верхнем уровне приложения.







