WalletConnect v2 интеграция
Как подключить внешний кошелёк через WalletConnect v2, открыть QR modal и использовать подключение как ethers.js BrowserProvider.
Что решает WalletConnect
WalletConnect подключает кошелёк, который не внедряет window.ethereum в страницу.
Так dApp может работать с мобильными кошельками, аппаратными кошельками и браузерами без MetaMask. Пользователь сканирует QR-код или открывает deep link, а приложение получает EIP-1193 provider.
Подключаем кошелёк через QR modal
import { EthereumProvider } from "@walletconnect/ethereum-provider";
import { BrowserProvider, formatEther } from "ethers";
const projectId = import.meta.env.VITE_WALLETCONNECT_PROJECT_ID as
| string
| undefined;
if (!projectId) {
throw new Error("Добавьте VITE_WALLETCONNECT_PROJECT_ID в .env");
}
const app = document.createElement("main");
app.innerHTML = `
<button id="connect-walletconnect">Подключить WalletConnect</button>
<button id="disconnect-walletconnect">Отключить</button>
<pre id="walletconnect-info">WalletConnect не подключён</pre>
`;
document.body.appendChild(app);
const connectButton = document.querySelector<HTMLButtonElement>(
"#connect-walletconnect",
);
const disconnectButton = document.querySelector<HTMLButtonElement>(
"#disconnect-walletconnect",
);
const output = document.querySelector<HTMLPreElement>("#walletconnect-info");
if (!connectButton || !disconnectButton || !output) {
throw new Error("Не удалось найти элементы интерфейса");
}
let walletConnectProvider: Awaited<ReturnType<typeof EthereumProvider.init>> | null =
null;
async function getWalletConnectProvider() {
if (walletConnectProvider) {
return walletConnectProvider;
}
walletConnectProvider = await EthereumProvider.init({
projectId,
showQrModal: true,
optionalChains: [1, 8453, 42161, 137],
rpcMap: {
1: "https://ethereum.publicnode.com",
8453: "https://mainnet.base.org",
42161: "https://arb1.arbitrum.io/rpc",
137: "https://polygon-rpc.com",
},
metadata: {
name: "Kitaev Docs Demo",
description: "Пример подключения кошелька через WalletConnect v2",
url: window.location.origin,
icons: ["https://avatars.githubusercontent.com/u/37784886"],
},
});
walletConnectProvider.on("accountsChanged", (accounts: string[]) => {
output.textContent = `Аккаунт изменился: ${accounts[0] ?? "нет аккаунта"}`;
});
walletConnectProvider.on("chainChanged", (chainId: string) => {
output.textContent = `Сеть изменилась: ${chainId}`;
});
walletConnectProvider.on("disconnect", () => {
output.textContent = "WalletConnect-сессия отключена";
});
return walletConnectProvider;
}
async function connectWalletConnect() {
const provider = await getWalletConnectProvider();
await provider.connect();
const ethersProvider = new BrowserProvider(provider);
const signer = await ethersProvider.getSigner();
const address = await signer.getAddress();
const network = await ethersProvider.getNetwork();
const balance = await ethersProvider.getBalance(address);
output.textContent = [
`Адрес: ${address}`,
`Сеть: ${network.name} (${network.chainId})`,
`Баланс: ${formatEther(balance)} ETH`,
].join("\n");
}
async function disconnectWalletConnect() {
if (!walletConnectProvider) {
output.textContent = "WalletConnect уже отключён";
return;
}
await walletConnectProvider.disconnect();
walletConnectProvider = null;
}
connectButton.addEventListener("click", () => {
connectWalletConnect().catch((error: unknown) => {
const message = error instanceof Error ? error.message : "Неизвестная ошибка";
output.textContent = `Ошибка WalletConnect: ${message}`;
});
});
disconnectButton.addEventListener("click", () => {
disconnectWalletConnect().catch((error: unknown) => {
const message = error instanceof Error ? error.message : "Неизвестная ошибка";
output.textContent = `Ошибка отключения: ${message}`;
});
});Перед запуском установите зависимости и добавьте project ID из Reown Cloud:
npm install ethers @walletconnect/ethereum-providerVITE_WALLETCONNECT_PROJECT_ID=ваш_project_id_из_reown_cloudprojectId публичен для frontend-приложения, но домен приложения должен совпадать с настройками проекта в Reown Cloud.
Как это работает
EthereumProvider.init() — создаёт WalletConnect v2 provider для EVM-сетей.
projectId — идентификатор проекта из Reown Cloud. Без него WalletConnect relay не примет подключение.
showQrModal: true — включает готовое окно с QR-кодом и списком кошельков.
optionalChains — сети, с которыми dApp готов работать. Лучше указывать несколько поддерживаемых сетей, а не жёстко требовать одну.
rpcMap — RPC URL для каждой сети. WalletConnect использует их для запросов к chain.
new BrowserProvider(provider) — превращает WalletConnect provider в ethers.js provider.
WalletConnect vs MetaMask
MetaMask в браузере обычно доступен через window.ethereum.
WalletConnect создаёт provider самостоятельно после pairing-сессии:
MetaMask extension -> window.ethereum -> BrowserProvider
WalletConnect v2 -> EthereumProvider -> BrowserProviderПосле обёртки в BrowserProvider код ethers почти одинаковый: getSigner(), getNetwork(), getBalance(), sendTransaction().
Частые ошибки
Оставлять projectId пустым → получите project ID в Reown Cloud и добавьте его в env. Это обязательный параметр WalletConnect v2.
Создавать provider при каждом клике → храните provider как singleton. Иначе можно получить несколько параллельных сессий и повторные listeners.
Не обрабатывать disconnect → пользователь может разорвать сессию из кошелька. После disconnect очистите адрес, signer и UI.
Что дальше
- Подключение MetaMask через BrowserProvider — сравнить injected provider и WalletConnect provider
- Отслеживание смены аккаунта и сети — реагировать на
accountsChanged,chainChangedиdisconnect - Typed Data подпись — EIP-712 — подписывать структурированные данные подключённым кошельком