китаев.tech

WalletConnect v2 интеграция

Как подключить внешний кошелёк через WalletConnect v2, открыть QR modal и использовать подключение как ethers.js BrowserProvider.

Что решает WalletConnect

WalletConnect подключает кошелёк, который не внедряет window.ethereum в страницу.

Так dApp может работать с мобильными кошельками, аппаратными кошельками и браузерами без MetaMask. Пользователь сканирует QR-код или открывает deep link, а приложение получает EIP-1193 provider.


Подключаем кошелёк через QR modal

typescript
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:

bash
npm install ethers @walletconnect/ethereum-provider
env
VITE_WALLETCONNECT_PROJECT_ID=ваш_project_id_из_reown_cloud

projectId публичен для 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-сессии:

text
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.


Что дальше

Материалы китаev.tech публикуются в образовательных целях и не являются инвестиционной рекомендацией. Примеры кода и описания протоколов — для обучения; использование в продакшне на ваш собственный риск. Дисклеймер и политика конфиденциальности.