Перейти к основному содержимому
Блокчейн51ГЛАВА 51Кошелёк в браузере: подключение и сетьстраницаconnectrequest0xf39F…кошелёкключ не выходитchainId 0x539

Кошелёк в браузере: подключение и сеть

Прочитано0%
Квизы0/4
Тренажёрыне пройден0/1
Уровень 1 · Новичок

Зачем это нужно​

До сих пор транзакции подписывал ключ, лежащий у нас в настройках. На чемпионате так нельзя: ключи пользователей у приложения быть не должно. Подписывает человек — в своём кошельке, своим ключом, который никогда не покидает расширение.

Задание требует именно этого: подключение кошелька, подтверждение операций в нём, работа в нужной сети.

Эта глава — про то, как страница вообще разговаривает с кошельком: какой у него интерфейс, как выглядит подключение, что приходит при отказе и как попросить переключить сеть.

Интересный факт

Прогоны этой главы сделаны на подделке кошелька — объекте с тем же интерфейсом, что описан стандартом. Расширения в этом браузере нет: строка window.ethereum: нет в первом же прогоне — настоящая. Зато подделка позволяет показать то, чего вживую не поймать: отказ пользователя, чужую сеть, два кошелька сразу.

Кошелёк — это объект с одним методом​

── есть ли кошелёк в этом браузере ──
window.ethereum: нет — расширение не установлено

Расширение кладёт на страницу объект. У него один по-настоящему важный метод и пара подписок:

КОШЕЛЁК ДЛЯ СТРАНИЦЫ — ЭТО ОБЪЕКТ С ОДНИМ МЕТОДОМwindow.ethereumrequest({ method, params })on(«accountsChanged», …)on(«chainChanged», …)больше ничего и не нужноeth_accountsкто пользователь — если уже разрешилeth_requestAccountsспросить разрешение (откроет окно)eth_chainIdв какой сети кошелёк сейчасприватный ключ не покидает кошелёк: страница может только просить, а подписывает человекбиблиотеке всё равно, кто перед ней — важен только этот интерфейс
Один метод запроса и две подписки — этого достаточно для всего
await window.ethereum.request({ method: "eth_chainId" });
window.ethereum.on("accountsChanged", (accounts) => { … });
Нажми на часть выражения

Приватный ключ страница не видит никогда. Она может только попросить; подписывает человек, нажимая кнопку в окне расширения. Отсюда все особенности: любая операция может быть отклонена, и любая занимает время.

Два метода про счета, и путать их нельзя​

── до подключения ──
eth_accounts → [] (пусто: страница не знает, кто пользователь)
eth_chainId → 0x1 = 1

── пользователь разрешил ──
eth_requestAccounts → ["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"]
eth_accounts теперь → ["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"]
ЧТО ОТВЕЧАЕТ КОШЕЛЁК НА КАЖДОМ ШАГЕдо подключенияeth_accounts[]страница не знает, кто пользовательпользователь отказалсяeth_requestAccountsкод 4001это решение человека, а не ошибкапользователь разрешилeth_requestAccounts[0xf39F…]дальше eth_accounts тоже отвечаетпросим чужую сетьwallet_switch…код 4902такой сети у кошелька нет — сначала добавитьпросим свою сетьwallet_switch…0x539 = 1337плюс событие chainChanged
Пять шагов и пять разных ответов кошелька

Спросить, кто пользователь. Отвечает пустым массивом, если доступа ещё нет. Ничего не спрашивает у человека — окно не откроется. Это то, что зовут при загрузке страницы: если доступ уже давали раньше, подключение восстановится само.

Попросить доступ. Открывает окно кошелька. Зовут только по нажатию кнопки — вызов при загрузке страницы кошельки блокируют, да и выглядит это навязчиво.

// при загрузке — тихо
useEffect(() => {
provider.request({ method: "eth_accounts" }).then(setAccounts);
}, []);

// по нажатию — с окном
const connect = () => provider.request({ method: "eth_requestAccounts" }).then(setAccounts);

Отказ — это не ошибка​

── пользователь отказался ──
код 4001: User rejected the request. ← это не ошибка приложения, а решение человека

У ошибок кошелька есть номера, и знать стоит три:

кодчто значитчто делать
4001пользователь отклонилничего: убрать «загружаем», оставить кнопку
4100нет разрешенияпопросить доступ заново
4902такой сети у кошелька нетпредложить добавить сеть

Код 4001 нельзя показывать как ошибку. Красная плашка «Ошибка: User rejected the request» после того, как человек сам нажал «Отклонить», выглядит как неисправность. Правильно — вернуть интерфейс в исходное состояние и промолчать.

Отвечено вопросов: 0 из 2
Чем «спросить, кто пользователь» отличается от «попросить доступ»?
Кошелёк вернул код 4001. Что показать пользователю?
Отвечено верно: 0 из 2

Сеть: спросить и попросить переключить​

── просим переключить сеть ──
чужая сеть → код 4902: Unrecognized chain ID. Try adding the chain first.
наша сеть → переключено, chainId = 0x539 = 1337
событие chainChanged: 0x539
await provider.request({
method: "wallet_switchEthereumChain",
params: [{ chainId: "0x539" }],
});
Нажми на часть выражения

Для приватной сети порядок такой: попросить переключить, поймать код 4902, попросить добавить, повторить переключение. Три строки, которые на демонстрации выглядят как магия — кошелёк сам предлагает добавить вашу сеть.

Важно

Номер сети проверяют перед каждой операцией, меняющей состояние. Кошелёк мог переключиться в другую сеть в соседней вкладке. Транзакция, ушедшая не в ту сеть, в лучшем случае отклонится, в худшем — уйдёт куда-то по-настоящему.

События: счёт и сеть могут смениться без вас​

provider.on("accountsChanged", (accounts) => {
if (accounts.length === 0) setConnected(false); // доступ отозвали
else setAddress(accounts[0]);
});

provider.on("chainChanged", () => window.location.reload());

Смена счёта — обычное дело: у людей их несколько. Все данные, привязанные к адресу (баланс, операции), надо перезапросить, а старые — убрать. Это ровно та ситуация с гонкой ответов из главы про данные из сети.

Смена сети — сам кошелёк советует просто перезагрузить страницу. Это не лень: в сети другие адреса контрактов, другие данные и, возможно, другой набор возможностей. Проще начать заново, чем чинить состояние по частям.

Пустой массив счетов означает, что пользователь отозвал доступ через интерфейс кошелька. Интерфейс должен вернуться к кнопке «подключить».

Когда кошельков несколько​

── современный способ: расширения объявляют о себе сами ──
страница спросила один раз и получила 2 ответа:
MetaMask io.metamask 11111111…
Rabby Wallet io.rabby 22222222…
ДВА КОШЕЛЬКА В ОДНОМ БРАУЗЕРЕодно поле на всехвсе расширения пишут в одно и то же местокто последний записал — того и виднопользователь не выбирает, выбирает случайрасширения объявляют о себестраница спросила один раз — получила всеMetaMask · io.metamaskRabby Wallet · io.rabbyвыбор показывают пользователюу каждого кошелька есть имя, значок и обратное доменное имя — по нему их и различаютстарое поле никуда не делось и работает: новый способ добавили рядом, не ломая прежний
Одно общее поле — гонка расширений; объявления — выбор пользователя

Старый способ — одно поле на странице, куда пишут все расширения сразу. Стоит поставить второй кошелёк, и какой из них увидит страница, решает случай.

Новый способ: страница один раз объявляет «кто здесь», а расширения отвечают — каждое своим именем, значком и обратным доменным именем. Дальше пользователю показывают список и дают выбрать.

const wallets = new Map();
window.addEventListener("eip6963:announceProvider", (e) =>
wallets.set(e.detail.info.uuid, e.detail));
window.dispatchEvent(new Event("eip6963:requestProvider"));

Старое поле при этом никуда не делось и работает — новый способ добавили рядом.

Библиотека поверх кошелька​

── ethers работает поверх того же объекта ──
provider.getNetwork() → chainId 1337
provider.getSigner() → 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
библиотеке всё равно, кто перед ней: важен только интерфейс request
import { BrowserProvider } from "ethers";

const provider = new BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const vault = new Contract(ADDRESS, ABI, signer);
Нажми на часть выражения

Читать можно без подключения. Баланс контракта, список операций, параметры — всё это доступно любому. Подключение нужно только для того, чтобы писать.

Это стоит использовать: интерфейс, который показывает данные до нажатия «подключить», выглядит гораздо живее пустого экрана с одной кнопкой.

Отвечено вопросов: 0 из 2
Контракт создан с провайдером, а не с подписывающим. Что не так?
Нужно ли подключение кошелька, чтобы показать баланс контракта?
Отвечено верно: 0 из 2

Типичные ошибки​

Просят доступ при загрузке страницы. Кошельки это блокируют, а пользователи закрывают вкладку.

Показывают отказ пользователя как ошибку. Код 4001 — не ошибка.

Не проверяют сеть перед транзакцией. Кошелёк мог переключиться в соседней вкладке.

Номер сети передают десятичным числом. Ждут строку с приставкой.

Не обрабатывают смену счёта. На экране остаются данные прошлого адреса — та же гонка, что в главе про запросы.

Контракт создан с провайдером вместо подписывающего. Читает, но не пишет.

Считают, что кошелёк один. Два расширения — и страница видит то, которое записалось последним.

Ждут, что подпись будет мгновенной. Человек может отойти от компьютера; интерфейс должен оставаться в состоянии ожидания сколько угодно.

Отвечено вопросов: 0 из 2
Почему после смены сети принято перезагружать страницу?
В браузере два кошелька. Какой увидит страница через общее поле?
Отвечено верно: 0 из 2

Слова главы​

  • Провайдер — объект, который кладёт на страницу расширение; у него один метод запроса и подписки.
  • Спросить счета — тихо, без окна; отвечает пустым массивом, если доступа нет.
  • Попросить доступ — открывает окно; зовут только по нажатию.
  • Код 4001 — пользователь отклонил; не ошибка.
  • Код 4902 — такой сети у кошелька нет; предложить добавить.
  • Номер сети — строкой, шестнадцатеричным, с приставкой.
  • Обнаружение кошельков — расширения объявляют о себе, а не дерутся за одно поле.
  • Подписывающий — тот, через кого идут транзакции; без него контракт только читает.

Тренажёр: набрать вслепую​

Цель: скорость ≥ 95 зн/мин, точность ≥ 88%

await window.ethereum.request({ method: "eth_requestAccounts" });

На чемпионате​

Кнопка «подключить» и состояние рядом. Адрес сокращённо, номер сети, кнопка «отключить». Это первое, что смотрят.

Своя сеть добавляется одним нажатием. Поймать код 4902 и предложить добавить — десять строк, а выглядит как готовый продукт.

Проверка сети перед каждой транзакцией. Одна строка, которая спасает от «почему ничего не произошло».

Читайте данные до подключения. Пустой экран с одной кнопкой производит плохое впечатление.

И про подделку: свой кошелёк с известным ключом — отличный способ отлаживать интерфейс, пока настоящее расширение не настроено. Но показывать надо на настоящем: проверяющий захочет увидеть окно подтверждения.

Куда углубиться​

  • Добавление сети одним запросом — какие поля обязательны и что кошелёк покажет пользователю.
  • Подключение мобильного кошелька — когда расширения нет, а телефон есть.
  • Библиотеки подключения — берут на себя выбор кошелька, сеть и восстановление сеанса.
  • Разрешения на счета — как отозвать доступ из интерфейса приложения.
  • Наблюдение за блоками — обновлять баланс не по кнопке, а по приходу нового блока.

Сводная проверка по всей главе: 8 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.

Куда дальше: проверенные ресурсы​

Видео по теме

ВидеоПодключаем MetaMask к web3 приложению. Делаем интерфейс для токена. Часть 2.Future Markt
ВидеоРабота со смарт-контрактами Ethereum с помощью Ethers.jsIlya Krukowski
ВидеоПодключаем контракт через библиотеку ethers.js. Фронт web3 приложения. Предоновогоднее видео #ethersDima HideCrypto - Web3 | Solidity
ВидеоSolidity и смарт-контракты Ethereum, урок #43 | Ethers.js v6 - апдейты, BigInt, перегрузка, импортыIlya Krukowski
ВидеоConnect a Crypto Wallet with JavaScript - MetaMask - UI/UX & CodeDesignCourse

Комментарии

Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.