Como criar tokens usando Solidity e o padrão ERC-1155

Cripto

Como criar tokens usando Solidity e o padrão ERC-1155

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

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

Neste tutorial eu vou lhe ensinar como criar contratos inteligentes (smart contracts) multi-token: fungíveis, não-fungíveis (NFT) e semi-fungíveis na blockchain usando a linguagem de programação Solidity e o padrão ERC-1155, seguido por todas as redes baseadas em Ethereum (BSC, Avalanche, Polygon, Tron, etc). Este não é um tutorial de Solidity para iniciantes (você pode aprender o básico de Solidity neste tutorial) e é recomendado antes de avançarmos que o programador já tenha certa familiaridade com tokens (o que são e para que servem) e sobre outros padrões mais básicos ligados a tokens, tanto os fungíveis definidos pelo padrão ERC-20, quanto os não-fungíveis definidos pelo padrão ERC-721.

A seguir, eu exploro a problemática que originou o padrão ERC-1155 para contratos multi-tokens e o que ele define. Sinta-se livre para pular esta etapa caso já tenha estudado o assunto antes.

Para que serve um contrato multi-token?

Enquanto que os tokens fungíveis servem muito bem ao propósito de dinheiro virtual e os tokens não-fungíveis servem bem ao propósito de colecionáveis e propriedades virtuais, o que acontece quando precisamos combinar estas duas necessidades?

Imagine por exemplo um jogo, onde tem a moeda do jogo, que faz girar sua economia. Claramente um token fungível, certo?

Mas e se no mesmo jogo são oferecidos itens únicos ou criaturas mágicas colecionáveis que possuem uso no jogo e que podem ser negociados offgame por estarem registrados na blockchain. Isso seria um token não-fungível (NFT), certo?

E como fica a programação das funcionalidades web3 deste jogo, quando envolver a compra de um NFT com o dinheiro do jogo?

Tecnicamente falando você pode criar smart contracts separados e um chamar o outro. No entanto isso irá envolver transações em mais de um contrato e consequentemente mais taxas e maior tempo de registro dessas informações. Em resumo? A experiência do usuário vai pro brejo!

Mas Luiz, eu não vou criar um jogo, eu apenas quero criar NFTs como tokens utilitários, para dar vantagens aos portadores, como acessos a eventos. O ERC-721 já não me atende plenamente?

Sim e não. Mas vou explicar.

Imagine que você quer fornecer acesso a um metaverso privado somente a quem possuir um NFT seu. Mas você não é um artista ou não quer criar milhares de imagens diferentes para que cada NFT seja único. Apenas quer vender os NFTs como se fossem ingressos. Neste caso entra outra utilidade de ter um contrato multi-token: você pode ter vários donos de um mesmo NFT ou várias cópias da mesma NFT, como preferir entender.

O fato é: você cria uma arte para representar o NFT e vende-a para o supply máximo que você definir, como se fosse um token fungível, mas onde cada portador terá no máximo 1 em carteira e que será usado como acesso no seu evento/metaverso/etc. Foi o que fez a Adidas por exemplo.

E como se isso tudo não fosse o bastante, um contrato multi-token seguindo o padrão ERC-1155 também permite batch transfer, ou seja, transferências de múltiplos tokens ao mesmo tempo, algo normalmente não permitido em contratos ERC-721, mas permitido em contratos ERC-20. Se você conhecer outro padrão que já estudamos aqui, o ERC-721A, deve lembrar que fazer operações em lote é muito mais vantajoso que transações individuais, tanto do ponto de vista de tempo de registro na blockchain quanto de economia nas taxas de transação.

E aí, entendidas as diferenças e convencido das vantagens? A seguir vamos estudar a interface do padrão, para entender como implementá-lo futuramente.

Curso Node.js e MongoDB

O padrão ERC-1155

Programar seu próprio contrato multi-token com a ERC-1155 passa a fazer sentido se você quer empreender neste mercado, quer criar plataformas, quer trabalhar como programador em empresas de tokenização (que pegam propriedades físicas e registram na blockchain) e por aí vai. Se esse for o seu caso, a sua primeira missão é entender os padrões de contratos de tokens mais usados no mercado, como o ERC-20 para tokens fungíveis que já ensinei neste tutorial e o ERC-721 para NFTs que já ensinei neste tutorial, e agora o ERC-1155, que você confere a documentação completa neste link.

Assim como outros padrões ERC (Ethereum Request for Comments), o 1155 define funções e eventos para que os tokens sejam registrados de maneira padronizada em redes EVM-based (Ethereum e compatíveis). Isso implica em, por exemplo, utilizarmos a linguagem Solidity na sua programação e é o que faremos aqui. Primeiro, apenas vamos dar uma rápida olhada no que é definido pelo padrão para entendermos melhor o que vamos programar.

São 6 funções e 4 eventos:

balanceOf(address _owner, uint256 _id) external view returns (uint256)

Nesta função você passa o endereço de uma carteira e o id do token e o contrato vai lhe retornar quantas unidades daquele token aquele endereço possui neste contrato. Isso porque em um contrato multi-token, cada token diferente possui o seu próprio ID, geralmente armazenado em constantes, como veremos no exemplo de implementação mais à frente. Isso é algo normal no conceito de NFT (onde a quantidade geralmente vai ser 1), mas algo inédito para quem só havia lidado com tokens ERC20 (fungíveis). Além disso, essa abordagem permite tokens semi-fungíveis, que seriam edições bem limitadas de tokens fungíveis (total supply de poucas unidades) ou com limitação por carteira.

balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory)

O padrão 1155 possui várias funções no estilo “batch”, ou seja: para processamento em lote e essa é uma delas. Ao invés de apenas consultar o saldo de um token Id de um owner, você pode passar um array de owners e de ids para serem pesquisados. Importante atentar que o retorna é para cada par (owner/tokenId), como em uma matriz.

safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external

Aqui nós temos algumas funções diferentes para fazer a mesma coisa: transferir a propriedade de tokens entre carteiras. A diferença entre safeTransferFrom e safeBatchTransferFrom é que a segunda serve para transferir múltiplos tokens diferentes (especificados pelo seu id) ao mesmo tempo, tornando-a mais econômica (do ponto de vista de gás) e rápida.

Repare no parâmetro value, que permite determinar a quantidade do token a ser transferido. Enquanto que em NFTs o value será sempre 1, este não é o caso em tokens fungíveis e semi-fungíveis. Importante frisar também que o parâmetro from deve ser igual ao msg.sender da transação ou algum outro endereço pré-aprovado (falaremos mais adiante) ou dará erro e que todas as funções de transferência são no estilo “safe”.

safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external

O mesmo que o anterior, mas passando um array de ids e valores (formando pares) a serem transferidos de um owner (from) para outro (to).

setApprovalForAll(address _operator, bool _approved) external

Esta função é para permitir que você delegue o controle de TODOS os seus tokens a um operador específico. Basicamente o mesmo que a função homônima da ERC-721 faz. O booleano permite revogar este acesso. Repare que diferente do padrão 721, o 1155 não possui mais função para delegação de controle de um token específico, apenas de todos, pois eles quiseram manter o padrão o mais simples e genérico possível. Caso o desenvolvedor entenda que ainda é necessário ter alguma granularidade, a recomendação é seguir a ERC-1761.

isApprovedForAll(address _owner, address _operator) external view returns (bool)

Esta função retorna se todos os tokens do owner em questão estão sob controle do operador especificado.

Além destas funções o programador pode criar outras e isso é bem comum aliás. Funções comuns incluem minting e burning dos tokens, para saque dos fundos do contrato, para metadados dos tokens e muito mais. Falaremos sobre isso no futuro.

O padrão ERC-1155: Eventos e Metadados

Dependendo da função que for chamada e da situação do contrato, um ou mais eventos podem ser disparados. Eu já expliquei aqui no blog como escutar estes eventos estando conectado à blockchain com EthersJS e Web3.js, Os eventos abaixo são os obrigatórios do padrão ERC-1155, devendo ser implementados em todos contratos deste tipo.

TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value)

Esse evento é disparado toda vez que um token muda de owner, incluindo em casos de minting (from 0x) e burning (to 0x). Também é importante reforçar que em transferências em lote (batch), ele deve ser emitido para cada token também, além do evento específico.

TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values)

O mesmo que o anterior, mas para um grupo de tokens sendo transferidos ao mesmo tempo.

ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved)

Esse evento é disparado toda vez que o controlador/operador dos tokens de um owner muda.

URI(string _value, uint256 indexed _id)

Esse evento deve ser disparado toda vez que a URI de um token id mudar por qualquer motivo. Isso mostra que, embora não seja obrigatório, o padrão ERC-1155 encoraja o uso de JSON Metadata e URIs para registro dos metadados de tokens (NFTs principalmente). Assim como no caso do ERC-721, aqui existe uma extensão para metadados, a ERC-1155 Metadata URI Extension que funciona de forma análoga ao que você já conhece do padrão anterior e que exige a função abaixo implementada.

uri(uint256 _id) external view returns (string memory)

Nesta função, você passa o id do token e tem como retorno a sua URI com os metadados seguindo o padrão de URI JSON Schema, com um exemplo ilustrativo sendo fornecido na especificação.

Além disso o padrão sugere como lidar com a localização dos JSONs (traduções), propondo um formato no JSON para especificar os idiomas suportados e qual é o padrão.

E abaixo o exemplo de JSON em espanhol.

Indo Além

Você deve ter notado que nesta ERC foi abolido o uso de name e symbol como funções que descrevem o token, certo? Isso é intencional, com os autores afirmando que o symbol era inútil fora de exchanges e causava muitas colisões em todos os demais cenários, tornando-se inútil. Já o nome se optou por não incluir na especificação para que ele obrigatoriamente tenha de ser consultado no JSON, mantendo-o como fonte oficial dessas informações e poupando espaço na blockchain.

Ainda nessa linha de deixar tudo mais simples, optou-se por não incluir uma extensão Enumerable, sendo recomendado o uso dos logs para descobrir informações dos tokens ou mater bases de dados locais nas aplicações, fora da blockchain.

Além das funções e eventos obrigatórios existem uma série de itens opcionais ou que fogem do escopo tradicional de implementação. Por exemplo, se você é desenvolvedor de um software de carteira e deseja que a sua carteira possa guardar multi-tokens, você deve implementar a interface ERC1155TokenReceiver descrita na documentação. Também tem diversas regras e detalhes em relação a cenários de uso das funções descritas acima. Mas estes conceitos funcionam melhor quando explicados junto a código, certo?

Faremos isso na parte 2 deste tutorial, que você confere neste link!

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 *