WEB3DEV

Cover image for Desenvolvendo NFTs Dinâmicos
Paulo Gio
Paulo Gio

Posted on

Desenvolvendo NFTs Dinâmicos

Sempre quis criar um NFT que reage a eventos e dados do mundo real? Agora você pode, com os Oráculos da Chainlink. Vamos verificar como isso pode ser feito!

https://miro.medium.com/max/1100/0*rVfQFknUlNC3p-J6

Foto por Choong Deng Xiang no Unsplash

Observação: Infelizmente, o Oráculo da Chainlink é configurado apenas na rede de testes Kovan, enquanto a rede de testes do OpenSea está na Goerli. Portanto, desenvolvi e testei tudo em duas redes, o que não foi ideal.

O que é um Oráculo da Chainlink?

A primeira geração de NFTs, como BAYC, CryptoPunks,… são essencialmente estáticos. Sua imagem e metadados geralmente são armazenados apenas em arquivos estáticos em, por exemplo, IPFS ou um servidor (nuvem). Níveis de mudança nos NFTs poderiam ser desenvolvidos pela capacidade de alternar o NFT de um conjunto de arquivos estáticos para outro conjunto.

Os Oráculos da Chainlink fornecem um utilitário onde você pode interagir direto do seu contrato inteligente Ethereum com qualquer API. Isso é muito legal, pois significa que você pode obter todos os dados que estão sendo oferecidos por meio de uma API para o seu contrato inteligente e pode encontrar APIs em praticamente qualquer coisa: preços de ações, dados esportivos, clima,…

Metadados na cadeia

A segunda mudança que precisamos executar é fazer com que nossos metadados NFTs sejam dinâmicos. Portanto, em vez de armazenar os metadados em um arquivo JSON fora da cadeia, agora geraremos os metadados na cadeia.

Passo 1: Usando o Oráculo da Chainlink

Neste exemplo, acessaremos a temperatura de uma cidade por meio de uma API do WeatherAPI.com.

Vejamos este código:

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

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import '@chainlink/contracts/src/v0.8/ChainlinkClient.sol';
import '@chainlink/contracts/src/v0.8/ConfirmedOwner.sol';
import "./Base64.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

contract DynamicNFT is ERC721, ChainlinkClient, Ownable {
   using Chainlink for Chainlink.Request;
   using Counters for Counters.Counter;

uint256 public temperature;
   bytes32 private jobId;
   uint256 private fee;
   Counters.Counter private _tokenIdCounter;
   bool public paused = false;

event RequestTemperature(bytes32 indexed requestId, uint256 temperature);

/**
    * @notice Inicialize o token link e o oráculo de destino
    *
    * Detalhes: Kovan Testnet:
    * Link Token: 0xa36085F69e2889c224210F603D836748e7dC0088
    * Oráculo: 0x74EcC8Bdeb76F2C6760eD2dc8A46ca5e581fA656 (Chainlink DevRel)
    * jobId: ca98366cc7314957b8c012c72f05aeeb
    *
    */
   constructor() ERC721("Dynamic NFT", "DNFT") {

       setChainlinkToken(0xa36085F69e2889c224210F603D836748e7dC0088);

       setChainlinkOracle(0x74EcC8Bdeb76F2C6760eD2dc8A46ca5e581fA656);

       jobId = 'ca98366cc7314957b8c012c72f05aeeb';
       fee = (1 * LINK_DIVISIBILITY) / 10; // 0,1 * 10**18 (Varia de acordo com a rede e o trabalho)
       temperature=0;
   }
function buildMetadata(uint256 _tokenId)
       private
       view
       returns (string memory)
   {
       return
           string(
               abi.encodePacked(
                   "data:application/json;base64,",
                   Base64.encode(
                       bytes(
                           abi.encodePacked(
                               '{"name":"Dynamic NFT", "description":"Dynamic NFT Test","image": "https://gateway.pinata.cloud/ipfs/QmeAKDXvQyGUdvwRSvazCyj4CYeN6qrcpQr4Lmgf7Cc2UC", "attributes": ',
                               "[",
                               '{"trait_type": "Temperature",',
                               '"value":"',
                               Strings.toString(temperature),
                               '"}',
                               "]",
                               "}"
                           )
                       )
                   )
               )
           );
   }

function requestTempData() public returns (bytes32 requestId) {

Chainlink.Request memory req = buildChainlinkRequest(jobId, address(this), this.fulfill.selector);

// Defina o URL para executar a solicitação GET em
       req.add('get', 'http://api.weatherapi.com/v1/current.json?q=Aartselaar&Key=30e737e440484fd18a5134039221006');

req.add('path', 'current,temp_c');
       // Multiplique o resultado por 1000000000000000000 para remover decimais
       int256 timesAmount = 10;
       req.addInt('times', timesAmount);

// Envia o pedido
       return sendChainlinkRequest(req, fee);
   }

/**
    * Receba a resposta na forma de uint256
    */
   function fulfill(bytes32 _requestId, uint256 _temperature) public recordChainlinkFulfillment(_requestId) {
       emit RequestTemperature(_requestId, _temperature);
       temperature = _temperature;
   }
/**
    * Permitir retirada de tokens Link do contrato
    */
   function withdrawLink() public onlyOwner {
       LinkTokenInterface link = LinkTokenInterface(chainlinkTokenAddress());
       require(link.transfer(msg.sender, link.balanceOf(address(this))), 'Nao foi possivel transferir');
   }

// Funções ERC721 
   function safeMint(uint minttimes) external payable {

 require(!paused);

       for(uint i=0;i<minttimes;i++){
  _safeMint(msg.sender, _tokenIdCounter.current());
  _tokenIdCounter.increment();   
 }         
}

function tokenURI(uint256 tokenId)
   public
   view
   virtual
   override(ERC721)
   returns (string memory)
{
 require(
 _exists(tokenId),
 "ERC721Metadata: URI query for nonexistent token"
 );

       return buildMetadata(tokenId);
}

//Retorna o valor atual do contador
function getCounter()

 external
       view
       returns (uint256)
   {
       return _tokenIdCounter.current();
   }

function setTemp(uint256 newTemp) external{
       temperature = newTemp;
   }
}
Enter fullscreen mode Exit fullscreen mode

Em primeiro lugar, precisamos importar a biblioteca do cliente Chainlink e criar nosso contrato usando essa biblioteca.

Vejamos o construtor:

constructor() ERC721("Dynamic NFT", "DNFT") {
setChainlinkToken(0xa36085F69e2889c224210F603D836748e7dC0088);
setChainlinkOracle(0x74EcC8Bdeb76F2C6760eD2dc8A46ca5e581fA656);
jobId = 'ca98366cc7314957b8c012c72f05aeeb';
fee = (1 * LINK_DIVISIBILITY) / 10; // 0,1 * 10**18 (Varia de acordo com a rede e o trabalho)}
Enter fullscreen mode Exit fullscreen mode

Definir essas variáveis (setChainlinkToken, setChainlinkOracle e jobid) vincula nosso contrato com o Oráculo da Chainlink na rede Kovan.

A taxa para usar o oráculo é de 0,1 LINK por chamada de API. Portanto, você precisará garantir que seu contrato esteja sempre financiado com LINK suficiente para fazer o número esperado de chamadas de API!

Duas funções são definidas para usar o oráculo: requestTempData e fullfill.

function requestTempData() public returns (bytes32 requestId) {

Chainlink.Request memory req = buildChainlinkRequest(jobId, address(this), this.fulfill.selector);

// Defina o URL para executar a solicitação GET em
req.add('get', 'http://api.weatherapi.com/v1/current.json?q=Aartselaar&Key=30e737e440484fd18a5134039221006');

req.add('path', 'current,temp_c');

// Multiplique o resultado por 1000000000000000000 para remover decimais
int256 timesAmount = 10;
req.addInt('times', timesAmount);

// Envia o pedido
return sendChainlinkRequest(req, fee);
}
Enter fullscreen mode Exit fullscreen mode

requestTempData cria a solicitação de API (get) e especifica quais dados da resposta JSON serão buscados (path). Como esperamos um número inteiro e a temperatura é recuperada com uma casa decimal, multiplicamos a resposta por 10. Finalmente, a chamada de API é feita pagando a taxa de LINK.

Fulfill recupera a resposta da API e a armazena na variável de temperatura local:

function fulfill(bytes32 _requestId, uint256 _temperature) 
public recordChainlinkFulfillment(_requestId) {

emit RequestTemperature(_requestId, _temperature);
temperature = _temperature;
}
Enter fullscreen mode Exit fullscreen mode

Vamos experimentar na Kovan:

Antes da chamada de API:

https://miro.medium.com/max/446/1*vpXmNMLugf2VvzKgLJxeqQ.png

Depois de executar a chamada de API:

https://miro.medium.com/max/390/1*60yAMW0wLdCnygEVmog9Fg.png

Na verdade, está cerca de 21ºC aqui:

https://miro.medium.com/max/1100/1*qQvJhP6NA2hPEize_4Q2-w.png

Etapa 2: NFT com metadados na cadeia

Tornar o NFT dinâmico significa que temos que construir os metadados na cadeia com as variáveis que obtemos da API. Fazemos isso chamando a seguinte função na função tokenURI:

function buildMetadata(uint256 _tokenId)
       private
       view
       returns (string memory)
   {
       return
           string(
               abi.encodePacked(
                   "data:application/json;base64,",
                   Base64.encode(
                       bytes(
                           abi.encodePacked(
                               '{"name":"Dynamic NFT", "description":"Dynamic NFT Test","image": "https://gateway.pinata.cloud/ipfs/QmeAKDXvQyGUdvwRSvazCyj4CYeN6qrcpQr4Lmgf7Cc2UC", "attributes": ',
                               "[",
                               '{"trait_type": "Temperature",',
                               '"value":"',
                               Strings.toString(temperature),
                               '"}',
                               "]",
                               "}"
                           )
                       )
                   )
               )
           );
   }
Enter fullscreen mode Exit fullscreen mode

Então, vamos testar isso na Goerli:

https://miro.medium.com/max/828/1*ovLg1ozfRaR39TiWlsTo1w.png

Após a cunhagem, e antes da chamada de API, o valor é inicializado em 0. Agora executamos a chamada de API e verificamos o valor novamente.

https://miro.medium.com/max/1100/1*jJAKnMt9E7El6YvgJVOUWg.png

O valor é atualizado para a temperatura local! Excelente!

Conclusão

Conforme mostrado, você pode tornar seu NFT dinâmico usando a chamada de API externa do oráculo Chainlink para obter dados e gerar seus metadados na cadeia.

As desvantagens são que cada chamada de API custa LINK e, portanto, dinheiro, e que os contratos dinâmicos são mais complexos e, portanto, custam mais gás. Sem almoço grátis, infelizmente. É realmente irritante que as ferramentas da Chainlink estejam disponíveis em Kovan para teste, pois o Opensea está na Goerli.

A Chainlink também oferece ferramentas inteligentes de automação de contratos, para que você também possa automatizar as chamadas de API.

Espero que tenha gostado deste artigo!

Artigo original publicado por Johan De Coster. Traduzido por Paulinho Giovannini.

Top comments (0)