Подключение криптокошелька — первая точка входа в любое 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 дней. Стоимость рассчитывается индивидуально в зависимости от сложности бэкенда и количества кошельков. Получите консультацию — мы ответим в течение дня.







