Como criar NFTs usando Solidity e HardHat (JS)

Cripto

Como criar NFTs usando Solidity e HardHat (JS)

Luiz Duarte
Escrito por Luiz Duarte em 05/12/2023
Junte-se a mais de 34 mil devs

Entre para minha lista e receba conteúdos exclusivos e com prioridade

Recentemente ensinei como criar NFTs (tokens não-fungíveis), ou melhor, um contrato para cunhar NFTs, no padrão da rede Ethereum (ERC-721), o mais usado mundialmente. Naquele momento, usamos a ferramenta Remix e fizemos todo o processo até o deploy e a configuração do NFT na nossa carteira MetaMask. Recomendo fortemente que faça esse outro tutorial primeiro antes desse aqui, pois hoje vamos aprender novamente a criar novos NFTs ERC-721, mas usando ferramentas mais profissionais como o HardHat e OpenZeppelin.

Outro ponto importante é que este não deve ser o seu primeiro tutorial envolvendo a linguagem Solidity ou mesmo o toolkit HardHat. Se esse for o seu caso, recomendo começar por este outro aqui.

Curso Node.js e MongoDB

#1 – Criando o Projeto

O primeiro passo é você criar um novo projeto HardHat. Para isso, crie uma pasta na sua máquina, que eu vou chamar de nft-erc721-hardhat. Abra o terminal e navegue até ela, executando os comandos para criação do projeto HardHat de exemplo (selecione a opção TypeScript e o resto deixe default).

Com o projeto criado, limpe as pastas contracts, scripts e test.

Na pasta contracts, crie somente um contrato nela, cujo nome do arquivo vai ser o nome da sua coleção, no meu caso: MyNFT. Se você fez meu outro tutorial de ERC-721, pode inclusive copiar e colar o código-fonte de lá para ganhar tempo, mas a abordagem que vou propor aqui é diferente e mais profissional pois vamos usar contratos da OpenZeppelin.

A OpenZeppelin é uma empresa conhecida mundialmente na área de cripto e blockchain por oferecer produtos e serviços na área de segurança de aplicações decentralizadas. Como alguns códigos Solidity são muito frequentes, como os códigos de padrões ERC, e é muito fácil de você deixar brechas em smart contracts para atacantes, eles resolvem os dois problemas fornecendo bibliotecas de contratos open-source já testados e auditados extensivamente para você usar, além de serviços de auditoria requisitados por grandes players do mercado.

Assim, vamos instalar a biblioteca de contratos deles em nosso projeto e você verá os ganhos que ela vai nos trazer logo mais.

Repare que estou usando a versão 0.8.20 ou superior no meu contrato, você deve usar a versão que possuir instalada na sua máquina, a partir da que informei acima. E por fim, vamos deixar a estrutura de nossos testes preparada, criando na pasta test um arquivo MyNFT.test.ts e deixando a estrutura abaixo nele.

Aqui estamos carregando as importações necessárias, definindo uma suíte de testes (describe) e criando a função de deploy fixture, que serve para o contrato ser provisionado apenas uma vez e limpo a cada teste, além de termos acesso às carteiras de teste.

Agora na pasta scripts vamos criar um arquivo deploy.ts colocando o código necessário para fazer o deploy do seu contrato apenas.

Agora temos o setup do nosso projeto de novo token pronto, é hora de programarmos ele!

#2 – Contrato da coleção NFT

Agora vamos nos concentrar no arquivo MyNFT.sol, que é a implementação da nossa coleção de NFTs ERC721. Como vamos usar a biblioteca de contratos da OpenZeppelin, vamos começar importando a mesma, ou melhor, os contratos que precisaremos, logo no topo do arquivo e dizendo que nosso contrato vai herdar todas as características deles. Fazemos isso com a keyword ‘is’ ao lado do nome do contrato novo e antes do nome dos contratos-pai, como abaixo.

Com esta implementação agora nossa coleção MyNFT tem acesso a todas características e funções compartilhadas pelos contratos ERC721.sol, ERC721Enumerable.sol, ERC721URIStorage.sol e ERC721Burnable.sol, acelerando bastante o nosso desenvolvimento e reduzindo drasticamente a nossa chance de deixar brechas em nosso código pois ele será mais de customização do que de implementação.

Mas para que servem todos esses contratos afinal?

  • ERC721.sol: implementa de forma abstrata o padrão ERC-721, a base para os NFTs mais algumas funções auxiliares, como para minting;
  • ERC721Enumerable.sol: implementa de forma abstrata a extensão Enumerable do padrão ERC-721, permitindo acessar NFTs por índice;
  • ERCURIStorage.sol: implementa de forma abstrata a extensão Metadata do padrão ERC-721, permitindo registrar junto do token id a URI com os metadados do mesmo;
  • ERC721Burnable.sol: implementa funções auxiliares de burn de tokens;
  • Strings.sol: library que fornece funções utilitárias para manipular strings;

Repare que os quatro primeiros contratos descritos são implementações abstratas de contratos (abstract contract), ou seja, incompletas, e servem como base para o nosso, onde faremos a versão final que pode ser feito o deploy. Esse processo de implementação concreta (o oposto da abstrata) requer que a gente herde dos contratos anteriores, o que fazemos com a keyword ‘is’ logo no início. Ao fazer isso assumimos o compromisso de implementar a versão final das funcionalidades que exijam sobrescrita, marcadas como virtual.

Por exemplo, para definir o nome e symbol do nosso token, basta chamarmos o constructor da superclasse/classe-pai junto ao nosso constructor, como abaixo, passando os seus parâmetros.

Aqui estou dizendo que o constructor de MyNFT, quando chamado, deve invocar o constructor de ERC721 passando no primeiro argumento o nome da coleção e no segundo o symbol dela.

Aproveitei também para declarar que vamos usar um contador para gerar ids de tokens auto-incrementais, o que vai ser muito útil em nossa função de minting logo a seguir.

E mais ao final deste primeiro bloco de código eu sobrescrevi (override) a função supportsInterface presente de forma virtual nos contratos abstratos ERC721 e ERC721Enumerable e que é uma exigência da ERC-165, dos quais o 721 deve respeitar também.

Agora vamos implementar as funções que servem para montar a URL do JSON de metadados que é a forma mais comum de armazenar as informações do ativo digital que o NFT representa. Eu não vou criar aqui o JSON com você ou sequer hospedá-lo pois foge do escopo deste tutorial, mas você pode facilmente usar qualquer linguagem de programação para fazê-lo, usando como base o formato sugerido na própria ERC-721, na parte de extensão metadata.

Aqui sobrescrevi duas funções exigidas pelos contratos que herdamos do OpenZeppelin. A primeira, _baseURI deve retornar a URL base dos seus tokens. Aqui estou usando um exemplo didático, eu não tenho essa URL no meu servidor. A partir dele montaremos na função tokenURI o caminho completo até o JSON com os metadados. Repare que usei a função string.concat para juntar os pedaços da string, algo que existe no Solidity a partir da versão 0.8.12.

Agora temos todos os ingredientes para criar nossa função de mint, como segue.

Minha função de mint começa pegando o próximo id de token e incrementando o contador. Usamos a informação do msg.sender e o novo id para mintar o token usando _safeMint, uma função herdada dos contratos que carregamos. Após mintar o token, registramos a URI dele (só a parte que é diferente entre os nfts) usando _setTokenURI, outra função herdada.

Aproveitei a deixa para incluir outras duas funções que são obrigatórias sobrescrevermos, a _update e a _increaseBalance. Dá pra complicar, mas a base da construção de um contrato de NFT ERC-721 mintable, burnable, enumerable e com metadados baseados em URL é esse aí mesmo.

Agora uma etapa que pode ser bem interessante de ser feita é escrevermos testes do nosso contrato. Como esse tutorial já está bem grandinho, vou deixar isso para a parte 2, ok?

Acesse a parte 2 aqui.

 

Olá, tudo bem?

O que você achou deste conteúdo? Conte nos comentários.

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *