Blockchain Domain Service Development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Blockchain Domain Service Development
Complex
from 1 week to 3 months
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1214
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Blockchain Domain Service Development

Blockchain domains solve a problem that stands out especially acutely in Web3: cryptographic addresses are unreadable for humans. 0x742d35Cc6634C0532925a3b844Bc454e4438f44e — this is not an address but a source of errors. A blockchain domain service replaces the address with a human-readable name while simultaneously turning that name into a portable identity record.

DNS-like System Architecture on Blockchain

Namespace and Registry

The central component is the Registry contract. It stores the mapping from hashed names (namehash) to owner and resolver addresses. ENS uses exactly this architecture, and it is justified: separating ownership (Registry) and data storage (Resolver) allows changing the resolver without losing ownership.

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 Algorithm

Names are transformed into bytes32 through recursive hashing. alice.mynskeccak256(keccak256('' bytes32(0)) + keccak256('myns')) → keccak256(result + keccak256('alice')). This allows computing the hash of any nesting level without knowing the full name — only its components.

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 Contract

Resolver stores data associated with a name. One resolver can serve multiple names.

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);
    }
    
    // Text records: "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 for decentralized websites
    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 Contract and NFT

Top-level domain names (TLD) are registered through a Registrar. Each registered name is an ERC-721 NFT, allowing trading names on OpenSea and other marketplaces.

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)) {
            // If token existed before — just update 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 and Registration

Registration prices usually depend on name length:

contract PriceOracle {
    // Price in USD/year, in wei via Chainlink ETH/USD feed
    uint256[5] public rentPrices = [
        160e18, // 1 character: $160/year
        40e18,  // 2 characters: $40/year
        10e18,  // 3 characters: $10/year
        5e18,   // 4 characters: $5/year
        1e18    // 5+ characters: $1/year
    ];
    
    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
    }
}

Reverse Resolution

Forward resolution: alice.myns → 0x742d.... Reverse resolution: 0x742d... → alice.myns. This is necessary for displaying names in interfaces.

It is implemented through a special reverse namespace: address 0x742d... is mapped to a record 742d...addr.reverse. The user himself sets the reverse record — this is his choice, which name to display.

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 Delegation

Powerful feature: domain owner can create subdomains and delegate them. team.alice.myns, dao.alice.myns — created in one transaction. Protocols use this for on-chain identity systems for participants.

NameWrapper (ENS v2 pattern) — a wrapper that turns subdomains into ERC-1155 tokens and adds a permission system: fuses. The "CANNOT_TRANSFER" fuse — name cannot be transferred (soulbound subdomain). "CANNOT_CREATE_SUBDOMAIN" fuse — cannot create second-level subdomains.

Offchain Resolver (CCIP-Read / EIP-3668)

For scalability — off-chain data storage with on-chain verification. Resolver returns an OffchainLookup error with URL and request data. Client makes a request to the off-chain gateway, gets a signed response, passes it back to the contract for signature verification.

This reduces data writing costs from on-chain gas to off-chain storage. Suitable for profile data, large amounts of text records.

Stack and Integration

Component Technology
Smart contracts Solidity 0.8.x + OpenZeppelin
Chainlink Oracle AggregatorV3Interface for ETH/USD
Frontend resolution ethers.js provider.resolveName()
Indexing The Graph subgraph
CCIP-Read gateway Node.js server + ECDSA signature

Development Timelines

Basic service (Registry + Resolver + Registrar + Price Oracle): 6-8 weeks.

Extended (NameWrapper + reverse resolution + CCIP-Read gateway + marketplace integration): 10-14 weeks.

Audit is mandatory — Registrar manages ETH payments. 2-4 weeks additional.