Криптографічні адреси нечитабельні для людини. 0x742d35Cc6634C0532925a3b844Bc454e4438f44e — це не адреса, а джерело помилок: до 30% транзакцій помиляються через невірно скопійовані адреси. Блокчейн-доменна система замінює адресу на людиночитане ім’я, одночасно перетворюючи це ім’я на portable identity record. Такі сервіси вирішують проблему не лише зручності, але й безпеки: фішингові атаки на основі схожих адрес практично неможливі, коли використовується перевірене ім’я. Наприклад, блокчейн-домен alice.myns може слугувати єдиною точкою прив’язки для гаманців в Ethereum, Polygon та Solana, а також вказувати на IPFS-сайт. Ми розробляємо подібні системи під ключ: від архітектури до аудиту та запуску. Отримайте консультацію — оцінимо ваш проект прямо зараз.
Архітектура DNS-подібної системи на блокчейні
Namespace та registry
Центральний компонент — Registry контракт. Зберігає мапінг від хешованого імені (namehash) до owner та resolver адреси. Wikipedia: Ethereum Name Service використовує саме цю архітектуру: розділення ownership (Registry) та data storage (Resolver) дозволяє змінювати resolver без втрати ownership.
contract DomainRegistry {
struct Record {
address owner;
address resolver;
uint64 ttl;
}
mapping(bytes32 => Record) private records;
mapping(bytes32 => mapping(address => bool)) private operators;
event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner);
event Transfer(bytes32 indexed node, address owner);
event NewResolver(bytes32 indexed node, address resolver);
function setOwner(bytes32 node, address _owner) external authorised(node) {
records[node].owner = _owner;
emit Transfer(node, _owner);
}
function setSubnodeOwner(bytes32 node, bytes32 label, address _owner) external authorised(node) returns (bytes32) {
bytes32 subnode = keccak256(abi.encodePacked(node, label));
records[subnode].owner = _owner;
emit NewOwner(node, label, _owner);
return subnode;
}
modifier authorised(bytes32 node) {
address owner = records[node].owner;
require(owner == msg.sender || operators[node][msg.sender], "Not authorised");
_;
}
}
Імена перетворюються в bytes32 через рекурсивний хеш: alice.myns → keccak256(keccak256('' bytes32(0)) + keccak256('myns')) → keccak256(result + keccak256('alice')). Цей підхід — основа ENS сервісу, який використовується мільйонами користувачів.
Resolver контракт
Resolver зберігає дані, асоційовані з ім’ям. Один resolver може обслуговувати безліч імен.
contract PublicResolver {
DomainRegistry immutable registry;
mapping(bytes32 => mapping(uint256 => bytes)) private _addresses;
mapping(bytes32 => mapping(string => string)) private _textRecords;
mapping(bytes32 => bytes) private _contenthash;
event AddressChanged(bytes32 indexed node, uint256 coinType, bytes newAddress);
event TextChanged(bytes32 indexed node, string indexed key, string value);
event ContenthashChanged(bytes32 indexed node, bytes hash);
function setAddr(bytes32 node, address addr) external authorised(node) {
setAddr(node, 60, addressToBytes(addr));
}
function setAddr(bytes32 node, uint256 coinType, bytes calldata a) public authorised(node) {
_addresses[node][coinType] = a;
emit AddressChanged(node, coinType, a);
}
function setText(bytes32 node, string calldata key, string calldata value) external authorised(node) {
_textRecords[node][key] = value;
emit TextChanged(node, key, value);
}
function setContenthash(bytes32 node, bytes calldata hash) external authorised(node) {
_contenthash[node] = hash;
emit ContenthashChanged(node, hash);
}
modifier authorised(bytes32 node) {
require(registry.owner(node) == msg.sender, "Not authorised");
_;
}
}
Registrar контракт та NFT
Імена верхнього рівня (TLD) реєструються через Registrar. Кожне зареєстроване ім’я — ERC-721 NFT, що дозволяє торгувати іменами на OpenSea та інших маркетплейсах. Реєстрація доменів у блокчейні — це не просто запис, а створення ліквідного активу.
contract BaseRegistrar is ERC721 {
DomainRegistry public registry;
bytes32 public baseNode;
mapping(uint256 => uint256) public expiries;
uint256 public constant GRACE_PERIOD = 90 days;
function available(uint256 id) public view returns (bool) {
return expiries[id] + GRACE_PERIOD < block.timestamp;
}
function register(uint256 id, address owner, uint256 duration) external onlyController returns (uint256) {
require(available(id), "Not available");
expiries[id] = block.timestamp + duration;
if (_exists(id)) {
_transfer(address(0), owner, id);
} else {
_mint(owner, id);
}
registry.setSubnodeOwner(baseNode, bytes32(id), owner);
return expiries[id];
}
function renew(uint256 id, uint256 duration) external onlyController returns (uint256) {
require(expiries[id] + GRACE_PERIOD >= block.timestamp, "Expired");
expiries[id] += duration;
return expiries[id];
}
}
Price Oracle та реєстрація
Ціни реєстрації зазвичай залежать від довжини імені. Використовуємо Chainlink для отримання актуального курсу ETH/USD.
contract PriceOracle {
uint256[5] public rentPrices = [
160e18, 40e18, 10e18, 5e18, 1e18
];
AggregatorV3Interface public immutable usdOracle;
function price(string calldata name, uint256 duration) external view returns (uint256 weiAmount) {
uint256 len = strlen(name);
uint256 usdPrice = rentPrices[min(len - 1, 4)];
uint256 annualUsd = usdPrice * duration / 365 days;
(, int256 usdEthPrice,,,) = usdOracle.latestRoundData();
return annualUsd * 1e8 / uint256(usdEthPrice);
}
}
Як працює reverse resolution?
Forward resolution: alice.myns → 0x742d.... Reverse resolution: 0x742d... → alice.myns. Це необхідно для відображення імен в інтерфейсах. Реалізується через спеціальний reverse namespace: адреса мапінг на запис ...addr.reverse. Користувач сам встановлює reverse record — це його вибір, яке ім’я показувати.
contract ReverseRegistrar {
bytes32 constant ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2;
function setName(string calldata name) external returns (bytes32) {
bytes32 node = claimWithResolver(msg.sender, address(defaultResolver));
defaultResolver.setName(node, name);
return node;
}
function node(address addr) public pure returns (bytes32) {
return keccak256(abi.encodePacked(ADDR_REVERSE_NODE, sha3HexAddress(addr)));
}
}
Subdomain делегування та NameWrapper
Власник домену може створювати субдомени та делегувати їх: team.alice.myns, dao.alice.myns. Протоколи використовують це для on-chain identity системи учасників. NameWrapper (патерн ENS v2) перетворює субдомени на ERC-1155 токени та додає permission system: fuses — наприклад, CANNOT_TRANSFER (soulbound) або CANNOT_CREATE_SUBDOMAIN. Це дозволяє скоротити кількість контрактів на 50% порівняно з попередньою версією. Деталі реалізації fuses: CANNOT_TRANSFER блокує safeTransferFrom, CANNOT_CREATE_SUBDOMAIN забороняє setSubnodeOwner для субдоменів. Кожен fuse — біт в uint256, який спалюється при активації.
Таблиця fuse прапорів NameWrapper
| Fuse | Значення | Опис |
|---|---|---|
CANNOT_TRANSFER |
0x01 | Забороняє передачу токена субдомену |
CANNOT_CREATE_SUBDOMAIN |
0x02 | Забороняє створення підсубдоменів |
CANNOT_SET_RESOLVER |
0x04 | Забороняє зміну resolver'а |
CANNOT_SET_TTL |
0x08 | Забороняє зміну TTL |
Offchain resolver (CCIP-Read / EIP-3668)
Для масштабованості застосовуємо off-chain зберігання даних з on-chain верифікацією. Resolver повертає помилку OffchainLookup з URL та даними запиту. Клієнт запитує дані у off-chain gateway, отримує підписану відповідь і передає її назад у контракт для верифікації підпису. Це знижує вартість запису даних у 10-100 разів порівняно з повним on-chain зберіганням — ідеально для профілів та великої кількості текстових записів. При обсязі 10 000 запитів CCIP-Read gateway економить понад $500 на місяць порівняно з повним on-chain зберіганням. Для інтеграції CCIP-Read виконайте кроки:
- Реалізуйте
OffchainLookupу вашому resolver. - Розгорніть gateway сервер (Node.js + ECDSA).
- Налаштуйте клієнт (ethers.js/viem) на обробку помилки.
Стек та інтеграція
| Компонент | Технологія |
|---|---|
| Smart contracts | Solidity 0.8.x + OpenZeppelin |
| Chainlink Oracle | AggregatorV3Interface для ETH/USD |
| Frontend resolution | ethers.js provider.resolveName() |
| Indexing | The Graph subgraph |
| CCIP-Read gateway | Node.js сервер + ECDSA підпис |
Типові вразливості та методи захисту
| Вразливість | Наслідки | Захист |
|---|---|---|
| Reentrancy | Крадіжка коштів | ReentrancyGuard від OpenZeppelin |
| Oracle manipulation | Некоректна ціна | Кілька оракулів + Time-weighted average |
| Integer overflow/underflow | Неправильні розрахунки | Solidity 0.8+ вбудована перевірка |
| Front-running (MEV) | Втрата вигідної ціни | Commit-reveal схеми або Subgraph |
Чому важливий аудит смарт-контрактів?
Будь-яка помилка в Registrar або Price Oracle може коштувати тисячі доларів. Понад 80% критичних вразливостей виявляються на етапі формальної верифікації. Ми проводимо аудит за допомогою Slither, Mythril, Echidna (fuzzing) та формальної верифікації. За час роботи ми накопичили досвід, який дозволяє гарантувати безпеку коду. Наші клієнти вже запустили понад 50 проектів по всьому світу.
Для економії газу використовуйте bytes32 замість string, об'єднуйте записи в batch, а для субдоменів обирайте NameWrapper з fuse CANNOT_CREATE_SUBDOMAIN. Це знижує газові витрати в середньому на 20% — економія до $2000 на рік у масштабі.
Що входить у роботу?
- Аналіз вимог та проектування архітектури
- Розробка смарт-контрактів (Registry, Resolver, Registrar, Price Oracle, NameWrapper)
- Інтеграція Chainlink Oracle та CCIP-Read gateway
- Фронтенд-компонент вирішення імен на основі ethers.js/viem
- Аудит коду (Slither + Echidna) та виправлення вразливостей
- Документація API та розгортання
- Підтримка після запуску
Терміни орієнтовно
- Базовий сервіс (Registry + Resolver + Registrar + Price Oracle): 6-8 тижнів.
- Розширений (NameWrapper + reverse resolution + CCIP-Read gateway + marketplace інтеграція): 10-14 тижнів.
- Аудит обов'язковий — 2-4 тижні додатково. Вартість розраховується індивідуально.
Замовте консультацію — ми оцінимо обсяг робіт та запропонуємо оптимальне рішення. Якщо у вас є питання щодо архітектури, зв'яжіться з нами для попереднього аналізу.







