Bootcamp — NFT Dinâmicos com Truffle e Chainlink Automation — Parte 2

Lucas Archangelo
10 min readMar 15, 2023

--

Fala pessoal, essa é a parte 2 do tutorial sobre NFTs Dinâmicos com Truffle e Chainlink Automation, se você não fez a parte 1 clique aqui.

Para criar a NFT Dinâmica nós vamos utilizar o protocolo ERC721, mas o que é esse protocolo ERC721?
O protocolo ERC721 é um padrão das redes ethereum (EVM), onde torna possível a criação de tokens únicos não fungíveis, e principalmente se somado a técnica de NFT dinâmico ninguém do mundo vai ter um Token ERC721 igual ao seu, muito legal né? Vamos lá!

O que vamos criar?

O NFT que vamos criar é uma representação de um dia, o token vai nascer Manhã que vai mudar para Tarde e depois para Noite, repetindo o ciclo infinitamente, e você vai conseguir ver essa mundança acontecer automaticamente pelo Opensea ao final do tutorial. Esse token poderia ser utilizado para representar o ciclo de um dia em algum jogo por exemplo trazendo diferentes prêmios para os jogadores dependendo da hora que eles estão farmando.

Mãos a obra

Lembrando que nós vamos utilizar todo trabalho já feito na parte 1, então se você pulo alguma etapa da parte 1 você pode ter problemas aqui, volte lá qualquer problema que tiver.

Criando o NFT no projeto

Comece criando um novo arquivo chamado "TimeToken.sol" na pasta "contracts" e copie o código abaixo:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

import "@chainlink/contracts/src/v0.8/AutomationCompatible.sol";

contract TimeToken is
ERC721,
Ownable,
AutomationCompatibleInterface
{
using Counters for Counters.Counter;

Counters.Counter private _tokenIdCounter;

uint256 public interval;
bool public isOn;

enum TimeType {
MORNING,
AFTERMOON,
NIGHT
}

struct DayAttribute {
TimeType timeType;
uint256 lastChange;
}

string private constant MORNING =
"Qmass3MAH94Dkmi8vT7hGLr6EyLqu36JPPMEwPe1UYUidj";
string private constant AFTERMOON =
"QmNdz3LAo7ajCrmsLfhF6Tn8nDknpKpAXNxTSDLYyah7v7";
string private constant NIGHT =
"QmVjmLuz3EhnVgNk2Gb9YSqvwKffuMsY9D4akEk82gsmG4";

mapping(uint256 => DayAttribute) dayAttributes;
uint256[] listTokens;

constructor() ERC721("TimeToken", "TIC") {
//Start Counter by 1
_tokenIdCounter.increment();

interval = 5 minutes;
isOn = true;
}

function _baseURI() internal pure override returns (string memory) {
return "https://chainlinkbootcamp.infura-ipfs.io/ipfs/";
}

function safeMint(address to) external onlyOwner {
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_safeMint(to, tokenId);
listTokens.push(tokenId);
dayAttributes[tokenId] = DayAttribute(TimeType.MORNING, block.timestamp);
}

function tokenURI(uint256 _tokenId)
public
view
override
returns (string memory)
{
_requireMinted(_tokenId);
string memory base = _baseURI();

if (dayAttributes[_tokenId].timeType == TimeType.MORNING) {
return string(abi.encodePacked(base, MORNING));
} else if (dayAttributes[_tokenId].timeType == TimeType.AFTERMOON) {
return string(abi.encodePacked(base, AFTERMOON));
} else {
return string(abi.encodePacked(base, NIGHT));
}
}

function checkUpkeep(
bytes calldata /* checkData */
)
external
view
override
returns (
bool upkeepNeeded,
bytes memory performData
)
{
if (!isOn) {
return (false, "");
}
for (uint256 i = 0; i < listTokens.length; i++) {
uint256 tokenId = listTokens[i];
if((block.timestamp - dayAttributes[tokenId].lastChange) > interval) {
return (true, "");
}
}
return (false, "");
}

function performUpkeep(
bytes calldata /* performData */
) external override {
if (isOn) {
for (uint256 i = 0; i < listTokens.length; i++) {
uint256 tokenId = listTokens[i];
if (
(block.timestamp - dayAttributes[tokenId].lastChange) >
interval
) {
updateToken(tokenId);
}
}
}
}

function setVariables(uint256 _interval, bool _status) external onlyOwner {
interval = _interval;
isOn = _status;
}

function updateToken(uint256 tokenId) private {
if (dayAttributes[tokenId].timeType == TimeType.NIGHT) {
dayAttributes[tokenId].timeType = TimeType.MORNING;
} else {
dayAttributes[tokenId].timeType = TimeType(uint8(dayAttributes[tokenId].timeType) + uint8(1));
}
dayAttributes[tokenId].lastChange = block.timestamp;
}
}

Eu não vou detalhar todas as funcionalidades do contrato, mas o que você precisa saber é o seguinte:

  1. o método _baseURI() é o método responsável por retornar a sua URL base onde você armazena os arquivos .json do seu NFT, sempre que utilizar ele, você precisa somar ao ID do seu token ou a referência do estado que o seu token está.
  2. Os métodos checkUpkeep (leitura) e performUpkeep (escrita) são os métodos da Chainlink Automation, onde torna possível a automação da alteração de estado do seu NFT.
  3. o método updateToken() é o metodo responsável por alterar o estado da sua NFT entre (Morning (manhã), Aftermoon (tarde), Night (noite).
  4. o método tokenURI() é o método utilizado pelos sites de venda de NFT para recuperar informações sobre a sua NFT, é nele que você cria a lógica de qual estado cada NFT está para ser exibido nos sites de NFT, jogos etc…

Comente ai caso você esteja com dúvida de mais alguma coisa referente a esse código ok?

Esse contrato possui algumas dependências das bibliotecas da Openzeppelin e ChainLink, execute o comando abaixo para instalar essas dependências no seu projeto:

npm install @chainlink/contracts @openzeppelin/contracts

Criar script para migrations

Com o seu contrato criado, agora precisamos criar o script para realizar o deploy nas redes, crie então um arquivo chamado "2_time_token.js" na pasta "migrations" e copie o scrit abaixo:

const TimeToken = artifacts.require("TimeToken");

module.exports = async function (deployer, _network, accounts) {
await deployer.deploy(TimeToken);
}

Criar script para mintar tokens

Vamos criar um script onde você cria tokens de forma automatizada, sem a necessidade da interação diretamente ao contrato pelo mumbai.polygonscan ou remix para fazer isso, a ideia de scripts é que você pode desenvolver facilitadores para que quando você realize um deploy na rede, você execute o que precisa ser executado para que ele esteja devidamente configurado. Aqui você pode criar todos os NFTs que o seu projeto vai ter, alterar parametrizações necessarias, enfim, aqui é um livro aberto utilize sem moderação.

crie uma pasta chamada "script" na raiz do projeto e então crie um arquivo chamado "mint_nft.js" e copie o script abaixo:

const TimeToken = artifacts.require("TimeToken");

module.exports = async function(callback) {
const accounts = await web3.eth.getAccounts();
const _instanceTimeToken = await TimeToken.deployed();
try {
console.log("Creating an awesome NFT...");
await _instanceTimeToken.safeMint(accounts[0]);
console.log(`NFT created for wallet ${accounts[0]}`);
} catch (err) {
console.error(err);
}

callback();
}

Esse script vai gerar 1 Token do seu NFT para a sua carteira principal da seed que você configurou no começo do projeto.

Realizar deploy na rede local

Enfim vamos realizar um deploy na rede local utilizando ganache para ver que tudo que fizemos está se comportando como esperamos e sem erros.

Para isso execute os comandos abaixo, utilizando dois terminais diferentes:

No terminal 1 execute esse comando:

ganache

Aguarde apresentar algo parecido com isso:

E no segundo terminal execute o seguinte comando:

truffle migrate

Esse comando vai rodar os dois scripts que temos na pasta migration "1_hello_world.js" e "2_time_token.js", essa é uma das facilidades que temos em ferramentas como o Truffle, se o seu projeto possui vários contratos (e na maioria dos casos tem), você pode criar a sequência em que eles vão ser deployados, por muitas vezes um depende do outro. O retorno vai ser algo parecido com o console abaixo:

Se tudo deu certo até aqui, então precisamos testar o nosso script que cria tokens, execute então o comando abaixo:

truffle exec ./scripts/mint_nft.js

A mensagem de sucesso é algo parecido com isso:

ótimo nossos scripts estão devidamentes configurados e funcionais, agora podemos migrar na rede de teste da Polygon (mumbai).

Realizar deploy na rede Mumbai

Nesse trecho vamos utilizar configurações que realizamos na parte 1, apenas para revisar, verifique se no seu arquivo "truffle-config.js" você possui devidamente configurado a rede mumbai você deve ter algo parecido com essa configuração:

    mumbai: {
provider: () => new HDWalletProvider(mnemonic, `https://endpoints.omniatech.io/v1/matic/mumbai/public`),
network_id: 80001,
confirmations: 2,
timeoutBlocks: 200,
skipDryRun: true
},

Se não tiver, eu recomendo que volte para a parte 1 do tutorial e configure seu arquivo conforme eu explico lá.

Com tudo isso revisado, basta executar o comando abaixo:

truffle migrate --network mumbai

Lembrando que você pode ter problemas de estabilidade com o RPC que está utilizando, na parte 1 do tutorial eu ensino como encontrar um que esteja estável para você trocar, mas tente subir algumas vezes antes de trocar ok?

Dando tudo certo deve apresentar algo parecido com isso no seu console:

Agora você possui um contrato ERC721 na rede mumbai isso é para poucos!
O endereço do seu contrato vai aparecer tanto no console log como "contract address", ou no arquivo "TimeToken.json" dentro da pasta "build/contracts" que o Truffle gera ao compilar os contratos. Pegue esse endereço e pesquise na mumbai.polygonscan veja o meu contrato como ficou clicando aqui. Se você clicar na aba contract vai ver que não é possível ainda enxergar o seu código fonte, isso acontece porque ainda não verificamos nosso contrato, vamos fazer isso na próxima etapa.

Verificando os contratos na rede mumbai

Estamos a poucos passos de finalizar esse tutorial a etapa de verificar contratos é de extrema importância para seu projeto, pois é com isso que você traz confiança e atrai usuários, todos os projetos grandes nas redes possuem seus contratos públicos e verificamos nas redes em que estão o truffle torna esse trabalho fácil utilizando um plugin chamado "truffle-plugin-verify" que já utilizamos ele na parte 1 e vamos utilizar ele exatamente igual fizemos com o HelloWorld.

Apenas para revisar, para que esse passo funcione corretamente, você precisa ter a sua POLYGONSCANAPIKEY devidamente configurada no arquivo .env do projeto, esses passos eu mostro na parte 1 do tutorial.

Execute o seguinte comando no console:

truffle run verify HelloWorld TimeToken --network mumbai

Esse comando verifica todos os contratos que você coloca depois da palavra verify, como temos 2 contratos nesse tutorial entã eu já verifiquei os 2 em um único comando.

se tudo der certo ao entrar novamente no seu contrato vai ver que agora ele apresenta todo o seu código fonte:

Com o contrato verificado, precisamos agora criar alguns tokens. Execute então o comando abaixo:

truffle exec ./scripts/mint_nft.js --network mumbai

Esse comando vai gerar um token para sua wallet principal e você deve ver algo parecido com isso em seu console:

Para verificar se a sua NFT foi devidamente criada, entre em um site de venda de NFT, eu recomendo a opensea, pesquise pelo endereço do seu contrato na opensea e entre no token que foi criado, você deve chegar a uma tela parecida com essa, pode ter um delay para apresentar o seu contrato no site deles, se receber a mensagem 404 nas primeiras tentativas não se assuste.

se quiser ter mais NFTS é simples, você só precisa rodar novamente o script "mint_nft.js" para cada vez que rodar 1 NFT vai ser criada na sua wallet, caso queira transferir ela para alguma outra carteira, utilize o site do contrato na mumbai.polygonscan abrindo a aba contract e selecionando a opção Write Contract. conecte sua carteira e utilize então o método safeTransferFrom, os parametros são:

from — a wallet que possui o token nft

to — a wallet que você deseja enviar

tokenid — o id do token que você quer transferir

Chainlink Automation — Faucets LINK

Finalmente chegamos na parte mais esperada, é fazer que a nossa NFT altere o seu estado automaticamente e para isso vamos utilizar o automations da Chainlink. Para isso precisamos solicitar alguns faucets LINK da rede da mumbai, faça o seguinte:

  1. entre nesse link.
  2. conecte a sua carteira com a rede mumbai configurada (fizemos isso na parte 1 do tutoria)
  3. Realize o teste "Sou Humano" e clique em "Send me 20 Testnet LINK"
  4. Após confirmar o recebimento você está devidamente apto a utilizar o automation

Chainlink Automation — Inicialização

Agora vamos adicionar o nosso contrato como um Upkeep dentro do Automation, realize os passos abaixo:

Entre nesse link

Clique em Launch App, uma nova aba vai ser aberta.

Conecte a sua wallet na nova janela.

Com a wallet conectada, clique em "Register new Upkeep"

Na tela que apresentar selecione o trigger "Custom logic"

Em Target contract address coloque o endereço do contrato que subimos na rede Mumbai e clique em next. A tela abaixo vai ser apresentada.

Dê um nome para sua automação a minha vou chamar de Chainlink Automation tutorial, coloque 5 LINKS no campo Starting balance (LINK). O resto você pode deixar sem preenchimento, clique em Register Upkeep e execute a transação pela sua metamask. Quando a metamask der a mensagem de sucesso volte para home da Chainlink clicando no logo.

Mais pra baixo você vai ver uma tabela com o upkeep que acabamos de criar.

clique nele para abrir o detalhe, descendo um pouco mais a tela, você vai encontrar a área History, ali ele vai mostrar quantas vezes a automação foi chamada.

Cada execução é realizada ele vai alterar todas os seus NFTS mintados para a próxima fase (Morning/Aftermoon/Night), para que você monitore essas mudanças, você pode olhar na opensea

Não esqueça de atualizar os metadatas da opensea para receber as atualizações do seu NFT.

Perfeito tudo está devidamente configurado e pronto, agora tudo que você precisa fazer é acompanhar as atualizações do seu NFT, você pode monitar as chamadas pelo site da Chainlink Automation, e ver se o seu Token está mudando atualizando os metadata dele pela opensea.

Parabéns, se você chegou até você acaba de criar integrações importante que muitos projetos de mercado estão utilizando.

Caso queira o repositório completo desse projeto pode baixar ele aqui.

As imagens foram fornecidas por um parceiro de trabalho o Fabio, muito obrigado por isso amigo.

--

--