Tutorial de Smart Contract ERC-1155 (Multitoken) com Solidity

Web3 e Blockchain

Tutorial de Smart Contract ERC-1155 (Multitoken) com Solidity

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

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

Recentemente escrevi um tutorial aqui no blog ensinando os fundamentos sobre contratos multitoken e explicando o padrão ERC-1155, uma junção dos padrões ERC-20 e ERC-721 para unir o melhor dos dois mundos em um só contrato, muitas vezes sendo chamado inclusive de evolução dos contratos de NFTs. No tutorial de hoje, vamos avançar nossos estudos indo para a prática, ou seja, programando nosso Smart Contract seguindo o padrão. É importante você encarar este tutorial como uma parte 2 e que volte ao primeiro se não possuir domínio do assunto ainda.

Para este tutorial usarei a ferramenta Remix, mas você nem verá nada específico dela durante o tutorial, é apenas para abstrair qualquer aspecto de ambiente já que é a ferramenta mais crua que existe atualmente. Nada impede no entanto que você use dos mesmos códigos para escrever seu contrato em toolkits como Truffle e HardHat, o que eu mesmo pretendo fazer mais à frente aqui no blog e nos cursos.

Então abra o Remix, crie um novo arquivo que vamos chamar de MyTokens.sol e bora programar!

Primeiro a estrutura básica

Conforme explicitado na documentação oficial do padrão, todo contrato multi-token deve implementar as interfaces ERC1155 e ERC165. A primeira diz respeito às funções e eventos obrigatórios de todos contratos multi-token, enquanto que a segunda diz respeito a como o contrato avisa que é compliance com determinados padrões/interfaces.

Embora não seja obrigatório (até onde sei) você ter a interface declarada no seu código, pode ser um bom ponto de partida fazê-lo, para ter certeza que estará aderente aos padrões acima citados e ajuda em algumas funções como a exigida pelo ERC165 que veremos mais à frente. Então, logo no topo do seu MyTokens.sol, declare as interfaces tal como fornecidas pela Ethereum.

Com estas interfaces no seu código, o próximo passo é criar logo abaixo o contrato em si, que deverá implementar as mesmas.

Repare que aqui eu implemento as interfaces previamente descritas com a keyword ‘is’. Agora é a hora que devemos estruturar as variáveis de estado do nosso contrato, necessárias para controle de propriedade dos tokens e outros.

Aqui temos um primeiro mapping que relaciona para cada token existente (a chave é o tokenId, um uint256) com cada um dos proprietários de uma cópia dele e seus saldos do respectivo token. Neste exemplo, estou considerando tokens semi-fungíveis, ou seja, NFTs com quantidade de unidades de cada exemplar.

A segunda variável é o mapeamento de contas para operadores aprovados. Ou seja, é o equivalente ao allowance dos tokens ERC-20, mas binário (tem ou não tem permissão). Diferente da ERC721, na 1155 ou você aprova acesso total à sua conta para um operador terceiro ou restringe tudo, não existe granularidade por tokenId no padrão, embora você possa implementar por conta.

Na sequência eu incluí três constantes para exemplificar a vantagem de um contrato multi-token. Esse contrato vai ter três tipos de NFT: a 1, a 2 e a 3, que por falta de criatividade apenas numerei, mas sei que você pode pensar em nomes melhores. Para cada tipo de token eu dei um id fixo, já que serão apenas 3, mas você pode fazer mecanismos no seu contrato para os ids serem infinitos ou considerar que cada NFT tem seu próprio id recebido no mint (semelhante ao que temos no ERC721), embora não é especificado minting no padrão.

Antes de entrarmos nas funções específicas da ERC-1155, vamos implementar a única função exigida pela ERC-165, a supportsInterface.

Essa função retorna um booleano indicando se determinada interface passada por parâmetro está ou não implementada por este contrato. Como nosso contrato deverá implementar as interfaces ERC1155, ERC1155Metadata_URI e ERC165, é com elas que faço a comparação para devolver se o contrato suporta ou não a interface informada (os valores hexadecimais estão na especificação do padrão).

Curso Web3 para Iniciantes

Implementando as funções de saldo e delegação

Por uma questão de organização vou quebrar as funções do contrato em quatro grupos: as funções de saldo, as funções de delegação, as de transferência e as funções opcionais, não obrigatórias. Vamos começar pelas funções de saldo e dentro desse grupo, com as funções single e batch.

A função balanceOf, exigida pela interface, espera o endereço do owner e o id de um token para consultar esse id no mapping de _balances para ver quantas unidades daquele token a conta possui. Caso o saldo retornado seja zero, indica que aquela conta não possui/não é dona de um token com aquele id. Também adicionei uma validação caso o id seja inválido.

Uma característica dos contratos 1155 é a exigência de funções batch, como abaixo.

Aqui nós esperamos um array de owners e um array de tokenIds, que devem ser do mesmo tamanho. Para cada par de owner/tokenId nós consultamos o saldo, montamos um array com os resultados e devolvemos ao requisitante.

Além do saldo de tokens que também expressa a propriedade sobre os mesmos, a ERC-1155 determina que podemos delegar o controle de nossos tokens a outras carteiras, o que é especialmente útil para brokers, marketplaces, etc. Para isso, precisamos implementar algumas funções específicas que agem em cima de variáveis de estado que definimos antes.

Primeiro vamos falar da função setApprovalForAll, que é obrigatória do padrão e que começa já setando que, para o requisitante da transação (msg.sender), vamos definir que o operator tem controle total (approved = true) ou nenhum controle (approved = false) sobre todos os tokens do owner neste contrato. Ao término da execução dessa transação, emitimos um evento conforme manda a especificação. Note que é idêntico ao padrão ERC721.

Já a segunda função, isApprovedForAll, verifica se determinado operador possui permissão sobre os tokens de um owner, o que inclusive pode ser substituído pela mudança de nome do mapping de aprovações e da sua visibilidade para public, se assim o quiser.

Já a terceira função (abaixo) desse grupo NÃO É do padrão ERC-1155

Ela retorna um booleano indicando se determinada carteira (spender) é o owner do token ou alguém aprovado pelo owner. Nada demais, mas muito útil em diversas situações que teremos a seguir.

Implementando as funções de transferência

Uma das principais vantagens de se usar tokens padronizados é que não apenas podemos registrar nossas criações e propriedades na blockchain como também podemos transferi-los para outras pessoas, o que é realizado através de duas funções de transferência exigidas pelo padrão ERC-1155, sendo a primeira delas a safeTransferFrom.

A função safeTransferFrom espera que você informe quem é o dono atual do token, quem vai ser o novo dono, o id do token e a quantidade (value) a ser transferida. Ela não assume que o requisitante seja o dono automaticamente porque ele pode ser o controlador aprovado do mesmo, e não o dono original, mas ainda assim ele deve saber quem é o dono. Esse dono será validado na primeira linha da implementação, bem como se as contas envolvidas são carteiras válidas, se há saldo suficiente, etc. Tudo dando certo nas verificações, o balance dos envolvidos é atualizado.

Por fim, como manda o padrão, o evento de transferência é emitido e a verificação de destino compatível é realizada, afinal é uma safe transfer. Você já viu isso quando estudou o ERC721, então não vou me alongar aqui, apenas não se preocupe com o fato dela estar no final, já que em caso de falha toda transação será revertida.

E agora vamos falar da segunda função de transferência, que serve para transferir múltiplos tokens (diferentes) na mesma transação.

Aqui temos um array de ids de tokens e um array de quantidades de tokens (values) e para cada par de id/value (motivo pelo qual eles devem ter o mesmo tamanho) nós devemos realizar uma safe transfer. Resista à tentação de fazer isso chamando a outra função, já que o custo em gás seria maior se você fizesse isso e essa é justamente a vantagem de uma batch transfer.

Além disso, repare que ao final das transferências um evento de transferência batch é emitido e a verificação de safe batch transfer é realizada. Como toda transação sempre é atômica, caso dê erro em QUALQUER etapa do processo para QUALQUER um dos token ids envolvidos, toda transação será revertida graças ao uso dos requires.

E com isso nós finalizamos toda a implementação obrigatória de funções e eventos definidos pela ERC-1155. Com o que implementamos já é possível implementar um contrato de multi-tokens e podemos definir no constructor do contrato que ao ser criado um token já será transferido para o criador do mesmo, ou seja, um único mint no deploy mesmo.

Dessa forma, você já tem todas as funcionalidades multi-token, mas não da forma como normalmente são feitas os contratos profissionais, as grandes coleções, etc. Falaremos sobre algumas funções opcionais/adicionais que certamente você sentiu falta, na próxima parte 2 deste tutorial, que você confere neste link.

TAGS:

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 *