Работа с несколькими кошельками
Как обнаружить несколько injected wallets через EIP-6963, дать пользователю выбрать кошелёк и подключить выбранный provider через ethers.js v6.
Почему window.ethereum недостаточно
В браузере может быть несколько кошельков: MetaMask, Rabby, Coinbase Wallet, OKX Wallet.
Старый подход через window.ethereum не говорит, какой именно кошелёк пользователь хочет подключить. EIP-6963 решает это: каждый кошелёк объявляет свой provider, а dApp показывает пользователю выбор.
Находим кошельки и подключаем выбранный
import { BrowserProvider, formatEther } from "ethers";
import type { Eip1193Provider } from "ethers";
type Eip6963ProviderInfo = {
uuid: string;
name: string;
icon: string;
rdns: string;
};
type Eip6963ProviderDetail = {
info: Eip6963ProviderInfo;
provider: Eip1193Provider;
};
type Eip6963AnnounceProviderEvent = CustomEvent<Eip6963ProviderDetail>;
const discoveredWallets = new Map<string, Eip6963ProviderDetail>();
const app = document.createElement("main");
app.innerHTML = `
<button id="discover-wallets">Найти кошельки</button>
<div id="wallet-list"></div>
<pre id="wallet-info">Кошельки ещё не найдены</pre>
`;
document.body.appendChild(app);
const discoverButton = document.querySelector<HTMLButtonElement>("#discover-wallets");
const walletList = document.querySelector<HTMLDivElement>("#wallet-list");
const output = document.querySelector<HTMLPreElement>("#wallet-info");
if (!discoverButton || !walletList || !output) {
throw new Error("Не удалось найти элементы интерфейса");
}
async function connectWallet(wallet: Eip6963ProviderDetail) {
const accounts = (await wallet.provider.request({
method: "eth_requestAccounts",
})) as string[];
if (accounts.length === 0) {
output.textContent = "Кошелёк не вернул доступный аккаунт.";
return;
}
const provider = new BrowserProvider(wallet.provider);
const address = accounts[0];
const network = await provider.getNetwork();
const balance = await provider.getBalance(address);
output.textContent = [
`Кошелёк: ${wallet.info.name}`,
`RDNS: ${wallet.info.rdns}`,
`Адрес: ${address}`,
`Сеть: ${network.name} (${network.chainId})`,
`Баланс: ${formatEther(balance)} ETH`,
].join("\n");
}
function renderWallets() {
walletList.replaceChildren();
if (discoveredWallets.size === 0) {
output.textContent = "Injected wallets не найдены.";
return;
}
for (const wallet of discoveredWallets.values()) {
const button = document.createElement("button");
button.type = "button";
button.textContent = `Подключить ${wallet.info.name}`;
button.addEventListener("click", () => {
connectWallet(wallet).catch((error: unknown) => {
const message = error instanceof Error ? error.message : "Неизвестная ошибка";
output.textContent = `Ошибка подключения: ${message}`;
});
});
walletList.appendChild(button);
}
output.textContent = `Найдено кошельков: ${discoveredWallets.size}`;
}
function handleAnnounceProvider(event: Event) {
const { detail } = event as Eip6963AnnounceProviderEvent;
discoveredWallets.set(detail.info.uuid, detail);
renderWallets();
}
window.addEventListener("eip6963:announceProvider", handleAnnounceProvider);
discoverButton.addEventListener("click", () => {
discoveredWallets.clear();
renderWallets();
window.dispatchEvent(new Event("eip6963:requestProvider"));
});
window.dispatchEvent(new Event("eip6963:requestProvider"));Если браузерные кошельки поддерживают EIP-6963, они ответят событием eip6963:announceProvider. Пользователь увидит отдельную кнопку для каждого найденного кошелька.
Как это работает
eip6963:requestProvider — событие, которым dApp просит кошельки объявить себя.
eip6963:announceProvider — событие, которым кошелёк возвращает info и provider.
info.uuid — уникальный идентификатор объявления. Его удобно использовать как ключ, чтобы не показывать один кошелёк дважды.
info.name — человекочитаемое имя кошелька: MetaMask, Rabby, Coinbase Wallet.
info.rdns — reverse DNS идентификатор кошелька. Например, MetaMask использует его, чтобы dApp мог отличить один wallet extension от другого.
new BrowserProvider(wallet.provider) — оборачивает выбранный EIP-1193 provider в ethers.js.
Как выбирать кошелёк
dApp не должен автоматически выбирать первый найденный provider.
Правильный UX:
1. Найти все injected wallets через EIP-6963
2. Показать пользователю список кошельков
3. Подключить только выбранный provider
4. Сохранить выбор на время сессииТак пользователь сам решает, каким кошельком подписывать сообщения и транзакции.
Частые ошибки
Подключаться к window.ethereum без выбора → при нескольких расширениях это может быть не тот кошелёк, который ожидает пользователь.
Сравнивать кошельки только по имени → используйте uuid для списка и rdns для определения типа кошелька.
Добавлять один и тот же provider несколько раз → кошелёк может объявиться повторно. Храните найденные provider в Map.
Что дальше
- Подключение MetaMask через BrowserProvider — подключить конкретный injected provider через ethers
- WalletConnect v2 интеграция — добавить кошельки, которые не внедряют provider в браузер
- Отслеживание смены аккаунта и сети — обновлять UI после смены аккаунта или сети