Розробка сервісу блокчейн-доменів

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка сервісу блокчейн-доменів
Складний
від 1 тижня до 3 місяців
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1285
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    902
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1121
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    857

Розробка сервісу блокчейн-доменів

Сервіс блокчейн-доменів розв'язує проблему, яка особливо гостро стоїть у Web3: криптографічні адреси нечитаємі для людини. 0x742d35Cc6634C0532925a3b844Bc454e4438f44e — це не адреса, а джерело помилок. Сервіс блокчейн-домену замінює адресу на людиночитаному імені, водночас перетворюючи це ім'я на портативний запис ідентичності.

Архітектура DNS-подібної системи на блокчейні

Простір імен та реєстр

Центральний компонент — контракт Registry. Він зберігає маппінг від хешованого імені (namehash) до адреси власника та resolver. ENS використовує саме цю архітектуру, і вона виправдана: розділення власництва (Registry) та зберігання даних (Resolver) дозволяє змінювати resolver без втрати власництва.

contract DomainRegistry {
    struct Record {
        address owner;
        address resolver;
        uint64 ttl;
    }
    
    // namehash => Record
    mapping(bytes32 => Record) private records;
    
    // namehash => operator => approved
    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"
        );
        _;
    }
}

Алгоритм Namehash

Імена перетворюються на bytes32 через рекурсивний хеш. alice.mynskeccak256(keccak256('' bytes32(0)) + keccak256('myns')) → keccak256(result + keccak256('alice')). Це дозволяє обчислювати хеш будь-якого рівня вкладення без знання повного імені — лише його компонентів.

import { ethers } from "ethers";

function namehash(name: string): string {
  let node = "0x" + "0".repeat(64);
  
  if (name === "") return node;
  
  const labels = name.split(".").reverse();
  for (const label of labels) {
    node = ethers.utils.keccak256(
      ethers.utils.concat([
        node,
        ethers.utils.keccak256(ethers.utils.toUtf8Bytes(label))
      ])
    );
  }
  
  return node;
}

Контракт Resolver

Resolver зберігає дані, пов'язані з іменем. Один resolver може обслуговувати багато імен.

contract PublicResolver {
    DomainRegistry immutable registry;
    
    // node => coinType => address (EIP-2304: multi-chain addresses)
    mapping(bytes32 => mapping(uint256 => bytes)) private _addresses;
    
    // node => key => value (text records)
    mapping(bytes32 => mapping(string => string)) private _textRecords;
    
    // node => contenthash (IPFS/Swarm/Arweave)
    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);
    
    // Ethereum address (coinType 60 = ETH)
    function setAddr(bytes32 node, address addr) external authorised(node) {
        setAddr(node, 60, addressToBytes(addr));
    }
    
    // Multi-chain: BTC coinType = 0, ETH = 60, SOL = 501
    function setAddr(bytes32 node, uint256 coinType, bytes calldata a) 
        public authorised(node) 
    {
        _addresses[node][coinType] = a;
        emit AddressChanged(node, coinType, a);
    }
    
    // Текстові записи: "email", "url", "avatar", "description", "twitter"
    function setText(bytes32 node, string calldata key, string calldata value) 
        external authorised(node) 
    {
        _textRecords[node][key] = value;
        emit TextChanged(node, key, value);
    }
    
    // IPFS contenthash для децентралізованих сайтів
    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; // namehash TLD (e.g., namehash("myns"))
    
    mapping(uint256 => uint256) public expiries; // tokenId => expiry timestamp
    
    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)) {
            // Якщо токен існував раніше — просто оновлюємо expiry
            _transfer(address(0), owner, id); // re-issue
        } 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 та реєстрація

Ціни реєстрації зазвичай залежать від довжини імені:

contract PriceOracle {
    // Ціна в USD/рік, у wei через Chainlink ETH/USD feed
    uint256[5] public rentPrices = [
        160e18, // 1 символ: $160/рік
        40e18,  // 2 символи: $40/рік
        10e18,  // 3 символи: $10/рік
        5e18,   // 4 символи: $5/рік
        1e18    // 5+ символів: $1/рік
    ];
    
    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); // 8 decimal Chainlink feed
    }
}

Зворотне розв'язування

Пряме розв'язування: alice.myns → 0x742d.... Зворотне розв'язування: 0x742d... → alice.myns. Це необхідно для відображення імен в інтерфейсах.

Реалізується через спеціальний зворотний простір імен: адреса 0x742d... маппується на запис 742d...addr.reverse. Користувач сам встановлює зворотний запис — це його вибір, яке ім'я показувати.

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)));
    }
}

Делегування поддоменів

Потужна функція: власник домену може створювати поддомени та делегувати їх. team.alice.myns, dao.alice.myns — створюються за одну транзакцію. Протоколи використовують це для on-chain систем ідентичності учасників.

NameWrapper (паттерн ENS v2) — обгортка, яка перетворює поддомени на ERC-1155 токени та додає систему дозволів: fuses. Fuse "CANNOT_TRANSFER" — ім'я неможливо передати (soulbound поддомен). Fuse "CANNOT_CREATE_SUBDOMAIN" — неможливо створити поддомени другого рівня.

Offchain resolver (CCIP-Read / EIP-3668)

Для масштабованості — off-chain зберігання даних з on-chain верифікацією. Resolver повертає помилку OffchainLookup з URL та даними запиту. Клієнт робить запит до off-chain gateway, отримує підписану відповідь, передає її назад у контракт для верифікації підпису.

Це зменшує вартість запису даних з on-chain газу до off-chain зберігання. Підходить для даних профілю, великої кількості текстових записів.

Стек та інтеграція

Компонент Технологія
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 підпис

Графіки розробки

Базовий сервіс (Registry + Resolver + Registrar + Price Oracle): 6-8 тижнів.

Розширений (NameWrapper + зворотне розв'язування + CCIP-Read gateway + інтеграція маркетплейсу): 10-14 тижнів.

Аудит є обов'язковим — Registrar управляє платежами у ETH. Додаткові 2-4 тижні.