Минулого тижня я намагався зрозуміти, як насправді працюють домени .night. Не за специфікацією, а шляхом створення чогось, що безпосередньо взаємодіє з блокчейном. Результатом став невеликий переглядач профілів. Ви вводите ім'я, наприклад tomin.night, і він отримує профіль прямо з блокчейну. Жодних API реєстраторів. Жодних бар'єрів автентифікації. Тільки смартконтракт і трохи JavaScript.
Нижче я розповім, як це працює, чого я навчився та про дві конкретні пастки, які з'їли мій післяобідній час.
Чому важливі on-chain імена
У мережі Midnight доменами .night керує Midnames. Замість того, щоб робити запит до центрального сервера компанії, ви читаєте дані безпосередньо зі смартконтракту, який живе в мережі. Сам реєстр містить домен, власника та будь-які поля профілю, які додав власник. Оскільки ці дані знаходяться on-chain, ідентичність є портативною. Ви не орендуєте її у платформи, яка може змінити умови або просто вимкнути сервіс. Якщо ви контролюєте ключі, ви контролюєте ім'я.
Ця зміна важлива і для розробників. Коли ви працюєте з традиційною системою DNS, вам доводиться мати справу з лімітами запитів (rate limits), API-ключами та обіцянками безперебійної роботи (uptime). Тут же станом контракту є першоджерело істини (source of truth). Ваш застосунок читає його так само, як і будь-який інший застосунок. Тут немає привілейованих рівнів API.
Код майже занадто простий
@midnames/sdk бере на себе всю важку роботу. Розв'язання домену до адреси та профілю займає рівно два рядки:
const provider = createDefaultProvider({ networkId: "mainnet" });
const result = await resolveDomain(provider, "tomin.night");
Ось і все. Провайдер націлений на мережу, а резолвер спілкується з контрактом. SDK повертає об'єкт результату, який містить прапорець успіху (success flag). Якщо домен не існує, вам не потрібні багаторівнева обробка помилок або блоки try-catch навколо збоїв RPC. SDK чітко повідомляє вам, що нічого немає. Це робить розробку UI напрочуд приємною. Ви можете використати прапорець успіху для розгалуження логіки та показати стан «не знайдено», не гадаючи, чи була помилка через відсутність імені, чи через непрацюючий вузол.
Що ви отримуєте у відповідь
Коли пошук успішний, корисне навантаження (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 та vanilla JavaScript. Жодних етапів збірки. Жодних фреймворків. Жодних адаптерів гаманців.
Я вирішив запускати SDK на бекенді, а не в браузері, з кількох практичних причин. Це дозволяє тримати конфігурацію провайдера поза межами клієнта, дає єдине місце для виправлення хаосу з серіалізацією, і означає, що фронтенду потрібно лише отримувати дані та відображати їх.
Ось що здикувало мене найбільше: резолвінг імені — це операція публічного читання. Вам не потрібне підключення гаманця. Вам не потрібен підпис. Вам не потрібно, щоб користувач проходив авторизацію. Якщо домен існує, стан контракту доступний кожному, хто запитає. Це суттєва відмінність від типового web3-процесу, де кожна взаємодія починається з «connect wallet». Читання ідентифікаційних даних у Midnight є бездозвільним так само, як і читання публічного вебсайту.
Головний висновок
Створення цього в'ювера нагадало мені, що найскладнішою частиною розробки на блокчейні рідко є сам блокчейн. Midnight уже вирішив складну проблему: надав людям можливість володіти своїм іменем і профілем без централізованої бази даних. Найскладнішим з погляду розробника було пам'ятати, до якої мережі я звертаюся, та написати допоміжну функцію для очищення типів даних.
Протокол надає вам переносну ідентичність. Ваше завдання як розробника — просто правильно її прочитати та не заважати користувачеві. Тримайте архітектуру простою, відокремлюйте логіку взаємодії з чейном від UI та ставтеся до публічного читання так, як воно є насправді: як до звичайних запитів до бази даних, які просто живуть у розподіленому реєстрі.
Якщо ви хочете переглянути код або запустити його самостійно, повний вихідний код доступний за посиланням https://github.com/tomiin/midnames-profile-viewer.
