Криптографические адреса нечитаемы для человека. 0x742d35Cc6634C0532925a3b844Bc454e4438f44e — это не адрес, а источник ошибок: до 30% транзакций ошибаются из-за неверно скопированных адресов. Блокчейн-доменная система заменяет адрес на человекочитаемое имя, одновременно превращая это имя в portable identity record. Такие сервисы решают проблему не только удобства, но и безопасности: фишинговые атаки на основе похожих адресов практически невозможны, когда используется проверенное имя. Например, блокчейн-домен alice.myns может служить единой точкой привязки для кошельков в Ethereum, Polygon и Solana, а также указывать на IPFS-сайт. Мы разрабатываем подобные системы под ключ: от архитектуры до аудита и запуска. Получите консультацию — оценим ваш проект прямо сейчас.
Архитектура DNS-like системы на блокчейне
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 недели дополнительно. Стоимость рассчитывается индивидуально.
Закажите консультацию — мы оценим объём работ и предложим оптимальное решение. Если у вас есть вопросы по архитектуре, свяжитесь с нами для предварительного анализа.







