Подключение криптокошелька — первая точка входа в любое Web3-приложение. Пользователь нажимает «Connect Wallet», браузер открывает MetaMask или WalletConnect QR, приложение получает адрес и подпись. Звучит просто, но под капотом — три разных протокола, десяток поставщиков кошельков и ряд проблем с состоянием, которые нужно решать правильно с самого начала. Например, типичная ошибка — вызов eth_requestAccounts при каждом монтировании компонента, что приводит к постоянным popup-запросам и ухудшает UX. Мы с этим работаем не первый год: за плечами 30+ проектов с интеграцией кошельков на React, Next.js и Vue.
Какие кошельки поддерживаем?
Мы реализуем подключение MetaMask (инжектированный EIP-1193), WalletConnect v2 (QR-код/WebSocket для мобильных кошельков), Coinbase Wallet (оба способа), Rabby и Brave Wallet. Таблица ниже показывает особенности:
| Кошелёк | Тип подключения | Особенности |
|---|---|---|
| MetaMask | EIP-1193 инъекция | Популярен, расширение браузера, тестовая сеть |
| WalletConnect | QR-код / WebSocket | Любой мобильный кошелёк (Trust, Rainbow, OKX) |
| Coinbase Wallet | Инъекция + WalletConnect | Интеграция с Coinbase, поддержка dapp browser |
| Rabby | EIP-1193 инъекция | Мультичейн, удобный интерфейс, open source |
Для унификации используем библиотеки-адаптеры: @web3-onboard или wagmi с viem. В сравнении с ручной реализацией через ethers.js, эти решения сокращают объём кода на 40% и снижают вероятность ошибок при смене кошелька. Какой стек выбрать — зависит от требований к размеру бандла и гибкости. Мы оценим ваш проект и предложим оптимальное решение.
Минимальная реализация через ethers.js
Ниже — базовая реализация подключения через ethers.js (v6). Этот код подходит для кастомных интерфейсов, когда нужен полный контроль.
// lib/wallet.ts import { BrowserProvider, JsonRpcSigner } from 'ethers'; export interface WalletState { address: string | null; chainId: number | null; provider: BrowserProvider | null; signer: JsonRpcSigner | null; } export async function connectWallet(): Promise<WalletState> { if (!window.ethereum) { throw new Error('No injected wallet found. Install MetaMask.'); } const provider = new BrowserProvider(window.ethereum); const accounts = await provider.send('eth_requestAccounts', []); const network = await provider.getNetwork(); const signer = await provider.getSigner(); return { address: accounts[0], chainId: Number(network.chainId), provider, signer, }; } export async function switchChain(chainId: number): Promise<void> { await window.ethereum.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: `0x${chainId.toString(16)}` }], }); } Как обработать события кошелька без утечек памяти?
Кошелёк может сменить аккаунт или сеть без уведомления приложения — нужно подписаться на события. Правильная подписка с очисткой при размонтировании — залог стабильности.
// hooks/useWalletEvents.ts import { useEffect } from 'react'; import { useWalletStore } from '@/store/wallet'; export function useWalletEvents() { const { disconnect, setAddress, setChainId } = useWalletStore(); useEffect(() => { if (!window.ethereum) return; const handleAccountsChanged = (accounts: string[]) => { if (accounts.length === 0) { disconnect(); } else { setAddress(accounts[0]); } }; const handleChainChanged = (chainIdHex: string) => { setChainId(parseInt(chainIdHex, 16)); // Страницу не перезагружаем — обновляем состояние }; window.ethereum.on('accountsChanged', handleAccountsChanged); window.ethereum.on('chainChanged', handleChainChanged); window.ethereum.on('disconnect', disconnect); return () => { window.ethereum.removeListener('accountsChanged', handleAccountsChanged); window.ethereum.removeListener('chainChanged', handleChainChanged); window.ethereum.removeListener('disconnect', disconnect); }; }, [disconnect, setAddress, setChainId]); } Почему SIWE — стандарт аутентификации без паролей?
Адрес кошелька не является идентификатором пользователя — его легко подделать в HTTP-запросе. SIWE (Sign-In with Ethereum) решает эту проблему: пользователь подписывает сообщение, бэкенд верифицирует подпись через ecrecover. Nonce с TTL 5 минут защищает от replay-атак. Такой подход даёт безопасную сессию без хранения паролей.
// lib/siwe.ts import { SiweMessage } from 'siwe'; export async function signInWithEthereum( address: string, chainId: number, signer: JsonRpcSigner, ): Promise<{ message: string; signature: string }> { const nonce = await fetch('/api/auth/nonce').then(r => r.text()); const message = new SiweMessage({ domain: window.location.host, address, statement: 'Sign in to MyApp', uri: window.location.origin, version: '1', chainId, nonce, }); const messageStr = message.prepareMessage(); const signature = await signer.signMessage(messageStr); return { message: messageStr, signature }; } Бэкенд верифицирует подпись через siwe пакет (Node.js) или любую реализацию ecrecover. Подробнее о стандарте можно прочитать в EIP-4361 или на Wikipedia.
Как унифицировать подключение множества кошельков?
Для поддержки нескольких провайдеров без custom logic используем библиотеки-адаптеры. Ручная реализация через ethers.js даёт полный контроль, но требует написания абстракции для каждого кошелька. wagmi с viem предлагает React-хуки и type-safe API, уменьшая код на 30%, однако не подходит для Vue. @web3-onboard — максимально универсален: встроенный UI и поддержка 20+ кошельков, но добавляет около 50 KB к бандлу. Выбор зависит от приоритетов: лёгкость против скорости разработки.
Пример конфигурации с @web3-onboard:
import Onboard from '@web3-onboard/core'; import injectedModule from '@web3-onboard/injected-wallets'; import walletConnectModule from '@web3-onboard/walletconnect'; const injected = injectedModule(); const walletConnect = walletConnectModule({ projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID!, requiredChains: [1, 137], }); export const onboard = Onboard({ wallets: [injected, walletConnect], chains: [ { id: '0x1', token: 'ETH', label: 'Ethereum Mainnet', rpcUrl: process.env.ETH_RPC_URL! }, { id: '0x89', token: 'MATIC', label: 'Polygon', rpcUrl: process.env.POLYGON_RPC_URL! }, ], appMetadata: { name: 'MyApp', icon: '/logo.svg', description: 'DeFi platform', }, }); Восстановление сессии после перезагрузки
Без персистентности пользователю придётся каждый раз проходить весь флоу. Используем eth_accounts — он возвращает адреса без popup, если кошелёк уже авторизован.
// Проверка при инициализации async function restoreConnection(): Promise<void> { if (!window.ethereum) return; const accounts: string[] = await window.ethereum.request({ method: 'eth_accounts', }); if (accounts.length > 0) { const provider = new BrowserProvider(window.ethereum); const network = await provider.getNetwork(); walletStore.set({ address: accounts[0], chainId: Number(network.chainId) }); } } Типичные ошибки и как их избежать
- Использование
eth_requestAccountsпри каждом монтировании — вызывает popup, даже если кошелёк уже подключён. Решение: проверятьeth_accountsсначала. - Отсутствие обработки
disconnect— может привести к утечкам памяти. Всегда подписывайтесь и очищайте слушатели. - Неправильный расчёт nonce в SIWE — если nonce не сохраняется на сервере, подпись не верифицируется. Используйте unique nonce с TTL.
Что входит в работу
Мы подготавливаем полный комплект:
- Интеграция 3-4 кошельков (MetaMask, WalletConnect, Coinbase, Rabby) через адаптер
- SIWE-аутентификация с бэкенд-частью (Node.js/Python)
- Обработка событий (accountsChanged, chainChanged, disconnect)
- Восстановление сессии и обработка ошибок (отклонение, отсутствие кошелька)
- Переключение сетей и проверка баланса
- Документация по интеграции и Readme
- Консультация по выбору библиотек под ваш стек
Свяжитесь с нами для бесплатной оценки вашего проекта — мы ответим в течение дня. Закажите интеграцию кошелька прямо сейчас и получите чистый код с полным покрытием кейсов.
Сроки и стоимость
Базовая интеграция одного кошелька с SIWE — от 2 до 3 дней. Полноценное решение под ключ с поддержкой 4-5 кошельков, обработкой всех состояний и документацией — от 5 до 7 дней. Стоимость рассчитывается индивидуально в зависимости от сложности бэкенда и количества кошельков. Получите консультацию — мы ответим в течение дня.







