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);
}
}
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;
}
}
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);
}
}
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);
}
}
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);
}
}
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
- Em seguida, instale a biblioteca web3.js executando o seguinte comando:
npm install web3
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));
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));
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
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
Oldest comments (0)