Web3-кошельки: выбор провайдера в браузере
Оглавление курса «Web3-интерфейс: реестр в браузере» · Урок 10 из 30.
У посетителя может быть установлено несколько кошельков. Тогда привычное обращение к window.ethereum не объясняет, каким расширением будет подтверждаться действие. В этом уроке обнаружим внедрённые провайдеры и предоставим пользователю явный выбор. Результат — подключение относится к выбранному объекту провайдера, а интерфейс не переключает его незаметно.
Публичное чтение курса 17 продолжает работать через наш HTTP-клиент. Этот шаг добавляет только список источников подписи. Основная модель основана на EIP-6963 и совместимом интерфейсе EIP-1193. Код не запускался; наличие конкретного расширения и поддержка стандарта в браузере в данной работе не проверялись.
Обнаружение вместо угадывания
EIP-6963 использует события окна: приложение запрашивает объявление провайдеров, расширения отвечают объектом с информацией и провайдером. Это позволяет избежать борьбы нескольких расширений за одну глобальную переменную. Сам механизм обнаружения не является удостоверением подлинности кошелька: страница получает объявление в своей среде и должна осторожно обращаться с его полями.
Имя, иконка и доменное обозначение rdns помогают пользователю узнать установленное расширение. Они не доказывают принадлежность разработчику. Нельзя отправлять запрос подписи автоматически первому объекту с красивым названием. Выбор делает пользователь, а сайт показывает, какой источник сейчас используется и как вернуться к списку.
Уникальный идентификатор объявления полезен для устранения повторов в пределах страницы. Он не становится постоянным идентификатором человека. Запоминание кошельков и адресов затрагивает приватность посетителя; наш учебный пример хранит список только в памяти и не передаёт его в аналитику.
Модуль списка провайдеров
Создадим discover-providers.js. Функция возвращает доступ к текущему списку и очистку подписки. Проверяем минимальную форму объекта, потому что событие приходит через публичную среду страницы.
export function discoverProviders(onChange) {
const choices = new Map();
function announced(event) {
const detail = event.detail;
if (!detail?.info || typeof detail.info.uuid !== 'string') return;
if (typeof detail.provider?.request !== 'function') return;
if (!choices.has(detail.info.uuid)) {
choices.set(detail.info.uuid, detail);
onChange([...choices.values()]);
}
}
window.addEventListener('eip6963:announceProvider', announced);
window.dispatchEvent(new Event('eip6963:requestProvider'));
return {
list: () => [...choices.values()],
dispose: () => window.removeEventListener('eip6963:announceProvider', announced),
};
}
Подписка устанавливается до запроса объявления. Иначе быстрое расширение могло бы ответить до того, как приложение начало слушать событие. Повторное объявление не создаёт вторую одинаковую кнопку, поскольку используется карта по идентификатору. При этом список может пополняться после первоначальной загрузки, поэтому UI обновляется вызовом onChange.
В модуле ещё нет eth_requestAccounts. Обнаружить источник и запросить доступ к аккаунту — разные операции. Пользователь может открыть список, сравнить варианты и закрыть его без согласия на предоставление адреса. Такое разделение уменьшает число неожиданно открывающихся окон расширения.
Представление выбора
Кнопки списка получают имя через textContent. Иконка, если она используется, отображается как изображение, а не встраивается в DOM как произвольная SVG-разметка. Отдельно продумывают допустимые форматы и размер источника. Поля объявления не должны превращаться в исполняемый код даже тогда, когда расширение выглядит знакомым.
Если название очень длинное, карточка выбора сохраняет читаемость и полное доступное имя. Два расширения могут объявить одинаковые названия. В таком случае дополнительное обозначение помогает ориентироваться, но не выдаётся за проверенную подпись разработчика. Интерфейс также может предложить открыть сам кошелёк и сверить активное расширение.
После выбора сохраняем непосредственно объект provider, связанный с кнопкой. Не надо повторно обращаться к window.ethereum перед отправкой: это вернуло бы неопределённость, от которой мы только что избавились. Все запросы разрешений, сетевого контекста и подписи проходят через выбранный источник до явной смены пользователем.
Если стандарт не поддерживается
При пустом списке приложение объясняет, что внедрённый провайдер не обнаружен. Возможен отдельный запасной маршрут через window.ethereum, но он помечается как ограниченный учебный вариант с одним источником. Нельзя объявлять, что отсутствие ответа EIP-6963 означает отсутствие любого кошелька: некоторые реализации используют другие способы соединения.
Удалённые кошельки, мобильные переходы и дополнительные транспортные протоколы относятся к следующим расширениям приложения. У них собственные жизненные циклы и ограничения. В этой серии первая линия остаётся явным EIP-1193-провайдером, чтобы научиться правильно разделять чтение и подпись без скрытой инфраструктуры соединения.
Если провайдер исчезнет или потеряет доступ к сети, выбранное состояние перестанет быть пригодным для записи. Приложение сбрасывает подготовленный запрос и сообщает о необходимости нового подключения. Сохранённый текст формы можно оставить, потому что он не является доказательством действующих полномочий.
Контекст и доверие
Любой код страницы технически может сгенерировать похожее событие. Поэтому список объявлений следует считать интерфейсом обнаружения, а не списком криптографически удостоверенных участников. Защита страницы от внедрения постороннего JavaScript остаётся обычной задачей веб-безопасности. Контракт также не доверяет имени провайдера: право издателя определяется адресом отправителя.
Не передавайте объект провайдера в серверный журнал и не пытайтесь сериализовать его. Он представляет локальную возможность запрашивать действия, а не полезный серверный документ. Для истории операции достаточно сети, адреса аккаунта и фактического хеша после отправки; сведения о расширении могут оставаться локальными настройками интерфейса.
Для упражнения рассмотрите объявления двух провайдеров, повтор одного объявления и позднее появление третьего. Ожидается, что список содержит три самостоятельных варианта, повтор не добавляет лишнюю кнопку, а уже выбранный источник не меняется сам. Следующий урок запросит аккаунт только после явного действия. Поля и события описаны в EIP-6963.
Выбор не меняется вслед за порядком
Порядок объявлений зависит от загрузки расширений и не должен стать правилом предпочтения. Если приложение отсортировало кнопки по имени, выбранный объект всё равно связывается с идентификатором объявления, а не номером строки. Позднее появление нового кошелька не должно переназначать первую кнопку уже подключённому пользователю.
Смена выбранного провайдера является изменением контекста, даже если он сообщает тот же адрес и сеть. Подготовленный запрос сбрасывается, подписки прежнего источника очищаются, а новый проходит явное чтение разрешений. Нельзя оставить обработчики старого кошелька активными: его событие могло бы инвалидировать работу нового источника и создать непонятное поведение.
Если список содержит подозрительное или незнакомое объявление, сайт не предлагает считать его доверенным из-за похожего логотипа. Пользователь выбирает установленный им инструмент, а приложение сохраняет понятную возможность отмены. Эти ограничения обнаружения становятся особенно важны при сторонних скриптах на странице.