WEB3DEV

Cover image for ERC-137: O Serviço de Nome de Domínio Ethereum (ENS)
Paulo Gio
Paulo Gio

Posted on • Atualizado em

ERC-137: O Serviço de Nome de Domínio Ethereum (ENS)

Introdução

Com a ascensão e popularização das criptomoedas, a maneira como interagimos com a Internet está se transformando. Nesse contexto, os endereços de carteira, que são normalmente representados como longas sequências alfanuméricas, se tornam um empecilho para a usabilidade e a adoção em massa. É aqui que entra o Serviço de Nome Ethereum (ENS, ou Ethereum Name Service), um serviço implementado na blockchain Ethereum que permite que endereços, contratos inteligentes e outros recursos sejam associados a domínios mais legíveis e amigáveis.

Essa ideia não é nova. Na internet tradicional, o Sistema de Nomes de Domínio (DNS) desempenha um papel semelhante, traduzindo URLs legíveis por humanos em endereços IP numéricos. O ENS segue uma lógica similar, mas aplicada à blockchain Ethereum.

Neste artigo, vamos explorar em detalhes o funcionamento do ENS, o papel do ERC-137 e como os desenvolvedores podem implementar essa solução em seus projetos na blockchain Ethereum. Além disso, discutiremos exemplos práticos de uso do ENS, suas vantagens e considerações importantes ao utilizar essa tecnologia.

Contexto: O Problema dos Endereços Complexos

Cada conta na rede Ethereum é identificada por um endereço exclusivo. Esses endereços são sequências de 40 caracteres hexadecimais, precedidos pelo prefixo "0x". Embora este sistema seja extremamente eficaz para máquinas, ele não é amigável para os usuários humanos. Lembrar ou digitar manualmente esses endereços complexos é propenso a erros e dificulta a adoção em massa.

O ERC-137 surge como uma solução para este problema, permitindo que nomes legíveis por humanos sejam usados para representar esses endereços complexos. Por exemplo, em vez de usar um endereço como "0x4cbe58c50480.."., um usuário pode simplesmente usar "meunome.eth".

A Solução: Serviço de Nome Ethereum (ENS)

O ENS é um conjunto de contratos inteligentes na rede Ethereum que possibilita a associação de nomes legíveis a endereços Ethereum, outros nomes de domínio, interfaces de contrato e muito mais. O ENS é implementado completamente na cadeia, ou seja, on-chain, o que possibilita que todas as associações sejam acessíveis e modificáveis por qualquer participante da rede.

A funcionalidade do ENS é dividida em três componentes principais: o Registro, os Resolvedores e os Registradores. Vamos entender melhor cada um deles.

Todos os exemplos de códigos dados neste artigo são implementações básicas e podem não incluir todas as considerações de segurança e recursos avançados necessários em um sistema ENS completo. É importante realizar uma análise mais aprofundada, adaptação ao caso de uso, testes e auditorias de segurança antes de implantar esses códigos em um ambiente de produção.

O Registro

O Registro ENS é um contrato inteligente que mantém um registro de todos os domínios e subdomínios, associando cada nome a um Resolvedor e um proprietário. O Registro é a única fonte verdadeira e inalterável de informações no ENS.

Exemplo de implementação de um Registro:

pragma solidity ^0.8.0;

contract ENS {
    struct Registro {
        // Proprietário do nome de domínio
        address owner; 
        // Resolvedor associado ao nome de domínio
        address resolver; 
        // TTL - Tempo de Vida
        uint64 ttl; 
    }

    // Mapeia nomes de domínio para registros
    mapping(bytes32 => Record) private records; 

    // Evento emitido quando o proprietário de um nome é alterado
    event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner); 
    // Evento emitido quando a propriedade de um nome é transferida
    event Transfer(bytes32 indexed node, address owner); 
    // Evento emitido quando o Resolvedor de um nome é alterado
    event NewResolver(bytes32 indexed node, address resolver); 
    // Evento emitido quando o TTL de um nome é alterado
    event NewTTL(bytes32 indexed node, uint64 ttl); 

    modifier onlyOwner(bytes32 node) {
        // Verifica se o chamador é o proprietário do nome
        require(records[node].owner == msg.sender, "Chamador nao e o proprietario"); 
        _;
    }

    constructor() {
        // Define o nó raiz como o hash vazio
        bytes32 rootNode = keccak256(abi.encodePacked(""));
        // Define o remetente da mensagem como o proprietário do nó raiz 
        records[rootNode].owner = msg.sender; 
    }

    function owner(bytes32 node) public view returns (address) {
        // Retorna o endereço do proprietário do nome
        return records[node].owner; 
    }

    function resolver(bytes32 node) public view returns (address) {
        // Retorna o endereço do Resolvedor associado ao nome
        return records[node].resolver; 
    }

    function ttl(bytes32 node) public view returns (uint64) {
        return records[node].ttl; // Retorna o TTL do nome
    }

    function setOwner(bytes32 node, address newOwner) public onlyOwner(node) {
        // Define o novo proprietário para o nome
        records[node].owner = newOwner; 
        // Emite o evento de transferência de propriedade
        emit Transfer(node, newOwner); 
    }

    function setSubnodeOwner(bytes32 node, bytes32 label, address newOwner) public onlyOwner(node) {
        // Calcula o subnó a partir do nó e do rótulo
        bytes32 subnode = keccak256(abi.encodePacked(node, label)); 
        // Define o novo proprietário para o subnó
        records[subnode].owner = newOwner; 
        // Emite o evento de alteração do proprietário
        emit NewOwner(node, label, newOwner); 
    }

    function setResolver(bytes32 node, address newResolver) public onlyOwner(node) {
        // Define o novo Resolvedor para o nome
        records[node].resolver = newResolver; 
        // Emite o evento de alteração do Resolvedor
        emit NewResolver(node, newResolver); 
    }

    function setTTL(bytes32 node, uint64 newTTL) public onlyOwner(node) {
        // Define o novo TTL para o nome
        records[node].ttl = newTTL;
        // Emite o evento de alteração do TTL 
        emit NewTTL(node, newTTL); 
    }
}
Enter fullscreen mode Exit fullscreen mode

Este exemplo de um contrato Registro, ENS, é um contrato de gerenciamento de nomes de domínio na rede Ethereum. Ele implementa funções para definir e obter informações sobre a propriedade de nomes, o Resolvedor associado a esses nomes e o tempo de vida, ou TTL, de um nome.

A estrutura Record é usada para armazenar informações sobre cada domínio. Isso inclui o proprietário do domínio, o Resolvedor associado ao domínio e o TTL do domínio. Esta estrutura é então usada na definição do mapeamento chamado records, que liga os hashes de nomes de domínio a suas respectivas estruturas Record.

Quatro eventos são definidos para serem emitidos quando ocorrem alterações em um registro de domínio. Estes são: NewOwner (emitido quando o proprietário de um domínio é alterado), Transfer (emitido quando a propriedade de um domínio é transferida), NewResolver (emitido quando o Resolvedor de um domínio é alterado) e NewTTL (emitido quando o TTL de um domínio é alterado).

Existe um modificador onlyOwner, que é usado para garantir que apenas o proprietário atual de um domínio possa chamar certas funções. Esse modificador é usado nas funções setOwner, setSubnodeOwner, setResolver e setTTL, que permitem que o proprietário de um domínio altere o proprietário do domínio, o proprietário de um subdomínio, o Resolvedor e o TTL, respectivamente.

O construtor do contrato é usado para definir o nó raiz do serviço de nomes. Ele faz isso criando um hash vazio e definindo o remetente da transação como o proprietário desse nó raiz.

Os Resolvedores

Os Resolvedores são contratos inteligentes que traduzem nomes em endereços. Cada nome no ENS aponta para um Resolvedor que é responsável por lidar com as consultas para esse nome. Um Resolvedor, portanto, pode ser visto como a fonte da verdade para informações associadas a um nome específico.

pragma solidity ^0.8.0;

contract Resolver {
    // Evento emitido quando um endereço é alterado
    event AddrChanged(bytes32 indexed node, address a); 

    // Proprietário do contrato Resolvedor
    address public owner;
    // Mapeia nomes para endereços 
    mapping(bytes32 => address) public addresses; 

    // Modificador para funções que apenas o proprietário pode chamar
    modifier onlyOwner() {
        require(msg.sender == owner, "Chamador nao e o proprietario");
        _;
    }

    // Inicializa o contrato com o proprietário como o remetente da mensagem
    constructor() {
        owner = msg.sender;
    }

    // Função para recuperar o endereço associado a um nome
    function addr(bytes32 node) public view returns (address) {
        return addresses[node];
    }

    // Função para atualizar o endereço associado a um nome
    function setAddr(bytes32 node, address addr) public onlyOwner {
        addresses[node] = addr;
        emit AddrChanged(node, addr);
    }

    // Função para verificar se o contrato suporta uma interface específica
    function supportsInterface(bytes4 interfaceID) public pure returns (bool) {
        return interfaceID == 0x3b3b57de || interfaceID == 0xd8389dc5;
    }
}
Enter fullscreen mode Exit fullscreen mode

O contrato Resolver é uma implementação de um Resolvedor para o ENS. Este contrato tem a responsabilidade de mapear um nome de domínio a um endereço Ethereum.

O contrato define um evento AddrChanged que é emitido sempre que um endereço associado a um nome de domínio é alterado.

A variável owner representa o proprietário do contrato Resolver. O modificador onlyOwner é usado para restringir o acesso a certas funções do contrato apenas ao proprietário.

O mapeamento addresses é usado para associar nomes de domínio a endereços Ethereum.

A função addr pode ser usada para obter o endereço associado a um nome de domínio.

A função setAddr é usada para atualizar o endereço associado a um nome de domínio. Esta função só pode ser chamada pelo proprietário do contrato. Quando um endereço é alterado, o evento AddrChanged é emitido.

A função supportsInterface é usada para verificar se o contrato suporta uma interface específica, de acordo com o padrão ERC-165. Neste caso, o contrato indica que suporta as interfaces 0x3b3b57de e 0xd8389dc5.

Os Registradores

Os Registradores são contratos que administram a propriedade de domínios no ENS. Eles determinam quem pode ser o proprietário de um domínio e estabelecem regras para a transferência de propriedade.

Exemplo de implementação de um Registrador:

pragma solidity ^0.8.0;

contract FIFSRegistrar {
    // Contrato ENS
    ENS public ens;
    // Nome de domínio raiz 
    bytes32 public rootNode; 

    // Evento emitido quando um subdomínio é registrado
    event SubdomainRegistered(bytes32 indexed subnode, address indexed owner);

    // Inicializa o contrato com o endereço do contrato ENS e o nome de domínio raiz
    constructor(address ensAddr, bytes32 node) {
        ens = ENS(ensAddr);
        rootNode = node;
    }

    // Função para registrar um subdomínio
    function register(bytes32 subnode, address owner) public {
        bytes32 node = keccak256(abi.encodePacked(rootNode, subnode));
        address currentOwner = ens.owner(node);

        // Verifica se o subdomínio já está registrado
        require(currentOwner == address(0), "Subdominio previamente registrado");

        // Verifica se o chamador é o proprietário atual do nome de domínio raiz
        require(ens.owner(rootNode) == msg.sender, "O chamador nao e o proprietario do dominio raiz");

        // Define o proprietário do subdomínio
        ens.setSubnodeOwner(rootNode, subnode, owner);
        emit SubdomainRegistered(subnode, owner);
    }
}
Enter fullscreen mode Exit fullscreen mode

Nesta implementação do Registrador ENS (FIFSRegistrar), o contrato é responsável pelo registro de subdomínios. Ele permite que o proprietário do nome de domínio raiz registre novos subdomínios, definindo o proprietário para cada um deles.

O contrato mantém uma referência ao contrato ENS (ens) e ao nome de domínio raiz (rootNode). O evento SubdomainRegistered é emitido sempre que um novo subdomínio é registrado, fornecendo o subnó (subnode) e o endereço do proprietário.

A função register permite que o proprietário do nome de domínio raiz registre um novo subdomínio. O subnó é calculado concatenando o nome de domínio raiz (rootNode) com o subdomínio fornecido. Em seguida, o contrato verifica se o subdomínio já está registrado verificando se o proprietário atual (currentOwner) é um endereço vazio.

Além disso, a função verifica se o chamador é o proprietário atual do nome de domínio raiz, comparando o proprietário do nome de domínio raiz (ens.owner(rootNode)) com o remetente da transação (msg.sender). Somente o proprietário do nome de domínio raiz pode registrar subdomínios.

Por fim, se todas as verificações forem bem-sucedidas, a função define o proprietário do subdomínio usando a função ens.setSubnodeOwner e emite o evento SubdomainRegistered.

Registro ENS e o processo de Resolução

Agora que entendemos os componentes principais do ENS, vejamos como eles trabalham juntos durante o processo de resolução de nomes.

Quando um nome é resolvido no ENS, o processo começa com uma consulta ao Registro ENS para obter o Resolvedor associado a esse nome. Em seguida, a consulta é passada para o Resolvedor, que retorna a informação solicitada. É importante notar que o Resolvedor pode ser qualquer contrato que implemente a interface do Resolvedor ENS, permitindo que os dados sejam armazenados e gerenciados de maneira flexível.

Para compreender melhor, vejamos um exemplo em código Solidity. Imagine que queremos resolver o nome "meunome.eth" para um endereço Ethereum:

pragma solidity ^0.8.0;

contract Exemplo {
    // Instância do Registro ENS
    ENS public ens;

    // Construtor que configura a instância do ENS a ser usada
    constructor(ENS _ens) {
        ens = _ens;
    }

    // Função que resolve um nome ENS para um endereço Ethereum
    function resolveName(string memory name) public view returns (address) {
        // Converte o nome ENS em um nó ENS usando a função hash do nome
        bytes32 node = ens.namehash(name);

        // Obtém o endereço do Resolvedor para este nó do Registro ENS
        address resolverAddress = ens.resolver(node);

        // Cria uma instância do Resolvedor usando o endereço obtido
        Resolver resolver = Resolver(resolverAddress);

        // Retorna o endereço Ethereum associado a este nó, fazendo uma consulta ao Resolvedor
        return resolver.addr(node);
    }
}
Enter fullscreen mode Exit fullscreen mode

Neste exemplo, temos o contrato Exemplo, que demonstra como resolver um nome ENS para um endereço Ethereum. O contrato recebe uma instância do Registro ENS no construtor e possui uma função resolveName que realiza a resolução.

Na função resolveName, o nome ENS fornecido é convertido em um nó ENS usando a função namehash do Registro ENS. Em seguida, é obtido o endereço do Resolvedor associado a esse nó através da função resolver do Registro ENS.

Uma instância do Resolvedor é criada usando o endereço obtido do Resolvedor. Em seguida, é feita uma consulta ao Resolvedor chamando a função addr para obter o endereço Ethereum associado a esse nó específico.

Por fim, o endereço Ethereum é retornado como resultado da função resolveName.

No exemplo apresentado, o nome "meunome.eth" não é especificado diretamente no código. Ele é passado como argumento para a função resolveName quando o contrato Exemplo é chamado. A função resolveName recebe um parâmetro name do tipo string, que é o nome a ser resolvido.

Portanto, quando você chamar a função resolveName com o argumento "meunome.eth", o contrato irá realizar o processo de resolução para encontrar o endereço Ethereum associado a esse nome específico.

Interagindo com o ENS

Uma das principais vantagens do ENS é a sua interatividade. Como é totalmente on-chain, qualquer pessoa pode interagir com o ENS e seus contratos associados. Isso permite que usuários registrem seus próprios nomes de domínio, atualizem o Resolvedor de um domínio, configurem os registros de um domínio e muito mais.

Por exemplo, para registrar um novo nome de domínio com o ENS, um usuário deve primeiro interagir com o Registrador apropriado. Dependendo do domínio de nível superior (TLD) que o usuário deseja registrar (por exemplo, ".eth"), pode haver um Registrador diferente. No caso de ".eth", o Registrador é o contrato FIFSRegistrar mencionado anteriormente.

Aqui está um exemplo de como um usuário poderia registrar um novo nome de domínio "meunome.eth":

pragma solidity ^0.8.0;

contract Exemplo {
    ENS public ens;
    FIFSRegistrar public registrar;
    bytes32 public rootNode;

    // Evento emitido quando um nome de domínio é registrado
    event NameRegistered(bytes32 indexed node, bytes32 indexed label, address owner);

    constructor(ENS _ens, FIFSRegistrar _registrar, bytes32 _rootNode) {
        ens = _ens;
        registrar = _registrar;
        rootNode = _rootNode;
    }

    // Função que registra um novo nome de domínio no ENS
    function registerName(string memory name) public {
        bytes32 label = keccak256(bytes(name));

        // Verifica se o subdomínio já está registrado
        bytes32 node = keccak256(abi.encodePacked(rootNode, label));
        require(ens.owner(node) == address(0), "Subdominio previamente registrado");

        // Registra o subdomínio para o remetente
        registrar.register(label, msg.sender);
        emit NameRegistered(node, label, msg.sender);
    }
}
Enter fullscreen mode Exit fullscreen mode

O contrato Exemplo tem a função registerName que recebe o nome de domínio como parâmetro. O nome de domínio fornecido é convertido em um rótulo (label) usando a função keccak256. O rótulo é uma representação hash do nome do domínio.

Em seguida, é criado o nó concatenando o hash do nó de domínio raiz (rootNode) com o hash do rótulo. O contrato verifica se o nó já está registrado verificando se o proprietário atual do nó é o endereço vazio.

Se o nó ainda não estiver registrado, a função register do contrato FIFSRegistrar é chamada para registrar o rótulo para o remetente (o endereço que chama a função registerName). Em seguida, o contrato emite o evento NameRegistered, fornecendo o nó, o rótulo e o endereço do remetente.

Portanto, ao chamar a função registerName com o nome de domínio desejado, o contrato Exemplo realiza o registro do nome de domínio no ENS.

É importante lembrar que a função register no exemplo é usada para registrar um rótulo para o endereço do remetente, não o nome de domínio completo. Em um ambiente real, existem mais etapas envolvidas, como definir um Resolvedor para o nó registrado e definir o endereço que o nome de domínio deve resolver.

Além disso, vale lembrar que este exemplo pressupõe que o contrato tem permissões para registrar um nome de domínio sob o rootNode especificado, o que pode não ser o caso em um ambiente de produção real.

Portanto, vale destacar que este exemplo é simplificado para fins de demonstração e pode não funcionar diretamente com o Registrador real em uso pelo ENS sem modificações. Recomenda-se sempre verificar a documentação mais recente e o código-fonte dos contratos do ENS quando estiver trabalhando com registros de domínio ENS.

Interagindo com o ENS usando JavaScript

Uma maneira conveniente de interagir com o ENS é por meio do JavaScript, usando bibliotecas como o web3.js. Essas bibliotecas fornecem uma interface simplificada para se conectar a um nó Ethereum e enviar consultas à rede.

Configuração do ambiente

Para começar a interagir com o ENS usando JavaScript, é necessário ter um ambiente de desenvolvimento configurado e as bibliotecas necessárias instaladas. É comum utilizar o Node.js e a biblioteca web3.js para essa finalidade. Vamos conferir alguns passos básicos para interagir com o ENS usando JavaScript.

  • Certifique-se de ter o Node.js instalado em seu sistema.

  • Crie um novo diretório para o projeto e inicialize um novo projeto Node.js executando o seguinte comando no terminal:

npm init -y
Enter fullscreen mode Exit fullscreen mode
  • Em seguida, instale a biblioteca web3.js executando o seguinte comando:
npm install web3
Enter fullscreen mode Exit fullscreen mode

Conexão com um nó Ethereum

Para interagir com a rede Ethereum, é necessário se conectar a um nó Ethereum. Isso pode ser um nó local em execução em sua máquina ou um nó remoto.

Aqui está um exemplo de como se conectar a um nó Ethereum usando a biblioteca web3.js:

const Web3 = require('web3');

// Crie uma instância do Web3 e forneça o URL do nó Ethereum
const web3 = new Web3('URL_DO_NÓ_ETHEREUM');

// Verifique se a conexão foi estabelecida
web3.eth.net.isListening()
  .then(() => console.log('Conectado a um nó Ethereum'))
  .catch((error) => console.log('Erro ao conectar ao nó Ethereum:', error));

Enter fullscreen mode Exit fullscreen mode

Certifique-se de substituir URL_DO_NÓ_ETHEREUM pelo URL do nó Ethereum ao qual deseja se conectar.

Considere utilizar um gerenciador de ambiente, como o dotenv, para armazenar informações sensíveis, como o URL do nó Ethereum ou chaves de API, em um arquivo separado. Dessa forma, você pode evitar expor essas informações diretamente no código.

Resolução de nomes ENS

Com a conexão estabelecida, é possível resolver nomes ENS para obter informações associadas, como o endereço do contrato ou o Resolvedor.

Aqui está um exemplo de como resolver um nome ENS usando a biblioteca web3.js:

const name = 'meunome.eth';
const node = web3.utils.sha3(name);

// Obtenha o contrato ENS
const ensContractAddress = '0x123456789...'; // Endereço do contrato ENS
const ensABI = [...]; // ABI do contrato ENS
const ensContract = new web3.eth.Contract(ensABI, ensContractAddress);

// Obtenha o Resolvedor para o nome ENS
ensContract.methods.resolver(node).call()
  .then((resolverAddress) => {
    console.log('Endereço do Resolvedor:', resolverAddress);

    // Obtenha o endereço associado ao nome ENS do Resolvedor
    const resolverABI = [...]; // ABI do contrato Resolvedor
    const resolverContract = new web3.eth.Contract(resolverABI, resolverAddress);
    return resolverContract.methods.addr(node).call();
  })
  .then((address) => {
    console.log('Endereço Ethereum associado:', address);
  })
  .catch((error) => console.log('Erro ao resolver o nome ENS:', error));

Enter fullscreen mode Exit fullscreen mode

Certifique-se de substituir meunome.eth pelo nome ENS que você deseja resolver e de fornecer o endereço e a ABI corretos para o contrato ENS e o contrato Resolvedor. Além disso, substitua 0x123456789... pelo endereço do contrato ENS correto e preencha as variáveis ensABI e resolverABI com as ABIs adequadas para os contratos ENS e Resolvedor que você está utilizando.

É uma boa prática gerenciar a ABI e os endereços dos contratos em arquivos separados ou em uma variável de configuração. Isso facilita a manutenção do código e a atualização dos contratos, caso seja necessário.

Execução e resultado

Após configurar o ambiente, conectar-se a um nó Ethereum e escrever o código para resolver um nome ENS, você pode executar o arquivo JavaScript e ver os resultados no console.

Execute o arquivo JavaScript no terminal usando o seguinte comando:

node nome-do-arquivo.js
Enter fullscreen mode Exit fullscreen mode

Você verá os resultados impressos no console, incluindo o endereço do Resolvedor e o endereço Ethereum associado ao nome ENS.

Lembre-se de que esses são apenas exemplos básicos de interação com o ENS usando JavaScript e web3.js. A implementação real pode variar com base em suas necessidades específicas e na estrutura que você está usando.

Ao implementar soluções em produção, é essencial realizar validações e tratamentos de erros adequados, além de considerar aspectos de segurança, como a validação de entradas e a proteção de chaves privadas. Certifique-se de estudar e entender completamente a biblioteca web3.js e as melhores práticas de desenvolvimento para garantir a segurança e a robustez de suas interações com o ENS.

Conclusão

O Serviço de Nome Ethereum (ENS), conforme definido pelo ERC-137, representa um avanço significativo na usabilidade da blockchain Ethereum. Ao permitir que endereços Ethereum sejam representados por nomes legíveis, o ENS torna a blockchain muito mais acessível para usuários comuns. Além disso, a natureza totalmente on-chain do ENS permite que ele seja facilmente integrado a outros contratos e dapps, tornando-o um recurso valioso para desenvolvedores de blockchain.

No entanto, é importante notar que o ENS, como qualquer outro recurso na blockchain, deve ser usado de maneira responsável. Como os nomes ENS são únicos e possuem propriedade definida, é possível que ocorram disputas de propriedade e outros problemas. Portanto, ao utilizar o ENS, os usuários e desenvolvedores devem estar cientes desses riscos potenciais e agir de acordo.

Vale destacar que a implementação real do ENS pode variar em detalhes e complexidade. Além disso, sempre é uma boa prática verificar a documentação atualizada e os recursos oficiais ao implementar e interagir com contratos inteligentes para garantir a segurança e a precisão.

Referências

ERC-137: Ethereum Domain Name Service - Specification - https://eips.ethereum.org/EIPS/eip-137

Latest comments (0)