Всю прошлую неделю я пытался разобраться, как на самом деле работают домены .night. Не по спецификациям, а путем создания чего-то, что напрямую взаимодействует с блокчейном. Результатом стал небольшой просмотрщик профилей. Вы вводите имя, например tomin.night, и оно разрешается (resolves) прямо из блокчейна. Никаких API регистраторов. Никаких стен авторизации. Только смарт-контракт и немного JavaScript.
Ниже я расскажу, как это работает, чему я научился и о двух конкретных ловушках, которые съели мой день.
Почему важны имена в блокчейне (On-Chain)
В сети Midnight доменами .night управляет Midnames. Вместо того чтобы делать запросы к центральному серверу компании, вы читаете данные напрямую из смарт-контракта, который живет в сети. Сам реестр хранит домен, владельца и любые поля профиля, которые владелец прикрепил к нему. Поскольку эти данные находятся on-chain, личность становится переносимой. Вы не арендуете её у платформы, которая может изменить условия или «выдернуть вилку из розетки». Если вы контролируете ключи, вы контролируете имя.
Этот сдвиг важен и для разработчиков. Когда вы работаете с традиционной системой DNS, вы сталкиваетесь с лимитами запросов (rate limits), API-ключами и обещаниями аптайма. Здесь же состояние контракта является единственным источником истины. Ваше приложение читает его точно так же, как и любое другое приложение. Здесь нет привилегированных уровней доступа к API.
Код почти слишком прост
@midnames/sdk берет на себя всю сложную работу. Разрешение домена до адреса и профиля занимает ровно две строки:
const provider = createDefaultProvider({ networkId: "mainnet" });
const result = await resolveDomain(provider, "tomin.night");
Это всё. Провайдер указывает на сеть, а резолвер взаимодействует с контрактом. SDK возвращает объект результата, который включает флаг успеха. Если домен не существует, вам не нужны уровни обработки ошибок или блоки try-catch вокруг сбоев RPC. SDK четко сообщает вам, что ничего не найдено. Благодаря этому создавать интерфейсы на удивление приятно. Вы можете использовать флаг успеха для ветвления логики и показывать состояние «не найдено», не гадая, была ли ошибка вызвана отсутствием имени или упавшим узлом.
Что вы получаете в ответ
Когда поиск проходит успешно, полезная нагрузка (payload) содержит две важные части.
Target — это адрес кошелька, на который указывает домен. Это основной функционал. Он превращает длинный hex-адрес в нечто, что человек может прочитать, напечатать и запомнить.
Fields содержат детали профиля. Все, что владелец прикрепил к домену — ссылки на соцсети, аватары, текстовые записи — находится внутри этой структуры. Эти поля не хранятся в каком-нибудь MongoDB-кластере компании. Это поля в состоянии контракта, а значит, любое приложение, которое умеет читать реестр, сможет отобразить тот же самый профиль. Синхронизация баз данных не требуется.
Две ловушки, которые меня замедлили
Не всё в этой разработке ограничивалось двумя строками и флагом успеха. Я столкнулся с двумя конкретными препятствиями, о которых стоит рассказать, чтобы вы их не повторяли.
Несоответствие сетей
SDK поддерживает как mainnet, так и preprod среды. Я потратил уйму времени, отлаживая домен, который «не существовал». Имя было верным. Код выглядел правильно. Провайдер работал. Проблема заключалась в том, что мой скрипт делал запрос к preprod, в то время как сам домен был зарегистрирован в mainnet. Ошибка выглядела как отсутствие домена, но на самом деле это было отсутствие контекста сети.
Если вы разрешаете имя и получаете ошибку, проверьте настройки провайдера, прежде чем приступать к отладке чего-либо еще. Убедитесь, что ваш networkId соответствует сети, в которой на самом деле выпущен (minted) домен. Это та самая ошибка, которая кажется очевидной задним числом, но её действительно трудно заметить, когда вы уверены, что проблема в логике контракта.
Трудности с сериализацией
Данные, возвращаемые SDK, не являются стандартным JavaScript. Они содержат значения BigInt и объекты Map. Если попытаться передать их напрямую в JSON.stringify для отправки в браузер, возникнет ошибка или данные будут молча потеряны. У BigInt нет нативного представления в JSON, а Map не сериализуется так же, как обычные объекты.
В итоге мне пришлось написать кастомный сериализатор. Он обходит объект результата, преобразует значения BigInt в строки и трансформирует экземпляры Map в обычные объекты перед тем, как ответ покинет сервер. Если вы строите API, которое отдает данные Midnight на фронтенд, планируйте этот шаг заранее. Не предполагайте, что вывод SDK сразу готов к использованию на фронтенде только потому, что это JavaScript.
Архитектура
Я намеренно выбрал максимально простой стек. Бэкенд — это Node-сервер на Express. Он импортирует @midnames/sdk, выполняет логику разрешения имен, занимается «акробатикой» с сериализацией и отдает чистый JSON. Фронтенд — это обычный HTML и чистый JavaScript. Никаких этапов сборки. Никаких фреймворков. Никаких адаптеров кошельков.
Я решил запускать SDK на бэкенде, а не в браузере, по нескольким практическим причинам. Это позволяет не выносить конфигурацию провайдера на сторону клиента, дает единое место для исправления хаоса с сериализацией, а фронтенду остается только загрузить данные и отрисовать их.
Вот что удивило меня больше всего: разрешение имени — это операция публичного чтения. Вам не нужно подключать кошелек. Вам не нужна подпись. Пользователю не нужно нигде авторизовываться. Если домен существует, состояние контракта доступно любому, кто сделает запрос. Это существенное отличие от типичного web3-процесса, где каждое взаимодействие начинается с «подключить кошелек». Чтение идентификационных данных в Midnight происходит без разрешений (permissionless) — точно так же, как просмотр публичного веб-сайта.
Главный вывод
Создание этого вьюера напомнило мне, что самая сложная часть разработки на блокчейне — это редко сам блокчейн. Midnight уже решил сложную задачу: позволил людям владеть своим именем и профилем без централизованной базы данных. С точки зрения разработчика, самой сложной задачей было не забыть, к какой сети я обращаюсь, и написать вспомогательную функцию для очистки типов данных.
Протокол дает вам переносимую идентичность (portable identity). Ваша задача как разработчика — просто правильно её прочитать и не мешать пользователю. Держите архитектуру простой, отделяйте логику взаимодействия с чейном от UI и относитесь к публичному чтению как к тому, чем оно является: к обычным запросам к базе данных, которые просто живут в распределенном реестре.
Если вы хотите посмотреть код или запустить его самостоятельно, полный исходный код доступен по ссылке https://github.com/tomiin/midnames-profile-viewer.
