WEB3DEV

Panegali
Panegali

Posted on

Cunhagem de NFT utilizando a Chainlink VRF (Chainlink VRF Parte III)

Na primeira parte da nossa série sobre a Chainlink VRF, comecei mostrando como configurar um canal de assinatura na Chainlink. Esse canal de assinatura ajuda a permitir que adicionemos consumidores (contratos inteligentes) que farão solicitações de valores aleatórios da Chainlink VRF, usando tokens LINK em nossa assinatura.

Na segunda parte, implementei um sistema de loteria como um exemplo de uso da Chainlink VRF. Vou implementar outro exemplo de uso da Chainlink VRF, mas desta vez, com o processo de criação de NFT ERC721.

Seremos capazes de criar um NFT que possui atributos diferentes (únicos). Para cada NFT que será criado, teremos atributos exclusivos para ele.

Nossos NFTs terão os seguintes atributos - Energia, Foco, Salto, Físico, Velocidade e Resistência. Cada atributo terá um valor variando de 0 a 100. Isso tornará cada NFT único.

Primeiro, adicionaremos uma imagem ao NFT.STORAGE que será criada com atributos únicos.

NFT.STORAGE é um armazenamento e largura de banda descentralizados gratuitos para NFTs no IPFS e Filecoin.

Vá para NFT.STORAGE e crie uma conta. Adicione qualquer imagem que você deseja que as pessoas criem com atributos diferentes. Você pode adicionar uma pasta de imagens.

Mas, para o propósito do nosso contrato, adicionaremos apenas uma imagem que terá diferentes artigos.

Depois de adicionar uma imagem, copie o URL do IPFS no menu Actions. Você precisará dela.


Vamos começar a escrever nosso contrato de cunhagem. Como você já sabe, estaremos estabelecendo a base para o nosso contrato. Na segunda parte desta série, mostrei como adicionar valores para uma VRF que estará na rede Goerli.

Desta vez, vamos torná-la diferente. Estaremos trabalhando com a rede Polygon (Mumbai).

contract GontarV9 is VRFConsumerBaseV2, ConfirmedOwner,ERC721URIStorage {

   //eventos quando o pedido é feito a VRF e quando é cunhado

   event RequestSent(uint256 requestId, uint32 numWords);

   event RequestFulfilled(uint256 requestId, uint256[] randomWords);

   using Strings for uint256;

   using Counters for Counters.Counter;

   //Mantém o registo dos NFTS cunhados

   Counters.Counter private _tokenIds;

   //o nosso uri de imagem nft carregado em NFT.STORAGE

   string private _imageURI = "ipfs://bafybeiftyqrafajaxxhicwbe5njg3fmrzw4evx4i5qwoxlr3filyzp27eu";

   //struct para conter os nossos atributos NFT cunhados na rede

   struct GontarPack {

       uint256 energy;

       uint256 speed;

       uint256 jump;

       uint256 stamina;

       uint256 physique;

       uint256 focus;

   }

   //Número máximo de NFTs a serem cunhados

   uint256 constant MAX_VALUE = 100;

   //mapeamento do ID do token NFT para atributos

   mapping(uint => GontarPack) public gontarPacks;

   //struct para obter o estado do pedido do id do pedido da VRF

   struct RequestStatus {

       bool fulfilled; // se o pedido foi satisfeito com êxito

       bool exists; // se existe um requestId

       uint256[] randomWords;

   }



   mapping(uint256 => RequestStatus)

       public s_requests; /* requestId --> requestStatus */

   VRFCoordinatorV2Interface COORDINATOR;

   // O seu ID de subscrição.

   uint64 s_subscriptionId;

   // Id de solicitações passadas.

   uint256 public lastRequestId;

   // A faixa de gás a utilizar, que especifica o preço máximo do gás a atingir.

   // Para obter uma lista das faixas de gás disponíveis em cada rede,

   // veja https://docs.chain.link/docs/vrf/v2/subscription/supported-networks/#configurations

   bytes32 keyHash = 0x4b09e658ed251bcafeebbc69400383d49f344ace09b9576fe248bb02c003fe9f;

// Depende do número de valores solicitados que você deseja enviar para a função

    // fulfillRandomWords(). O armazenamento de cada palavra custa cerca de 20.000 gas,

    // portanto, 100.000 é um padrão seguro para esse contrato de exemplo. Teste e ajuste

    // esse limite com base na rede que você selecionar, no tamanho da solicitação,

    // e o processamento da solicitação de callback na função fulfillRandomWords()

    // na função fulfillRandomWords().

   uint32 callbackGasLimit = 1000000;

   // O padrão é 3, mas você pode definir um valor mais alto.

   uint16 requestConfirmations = 3;

   // Para este exemplo, recupere 6 valores aleatórios num único pedido.

   // Não pode exceder VRFCoordinatorV2.MAX_NUM_WORDS.

   uint32 numWords = 6;

    constructor(uint64 subscriptionId) ERC721("Gontar Warriors","GTWRS") VRFConsumerBaseV2(0x7a1BaC17Ccc5b313516C5E16fb24f7659aA5ebed) ConfirmedOwner(msg.sender)

   {

       COORDINATOR = VRFCoordinatorV2Interface(

           0x7a1BaC17Ccc5b313516C5E16fb24f7659aA5ebed

       );

       s_subscriptionId = subscriptionId;

   }

}
Enter fullscreen mode Exit fullscreen mode

Se você estiver familiarizado com o Solidity, entenderá o trecho de código acima. Ele simplesmente possui os blocos de construção (variáveis de estado com diferentes tipos de dados, eventos, tipos de dados de referência e um construtor).

Vamos começar com nossa primeira função - setNFTTraits.

function setNFTTraits(uint256 randNum1,uint256 randNum2,uint256 randNum3,

   uint256 randNum4,uint256 randNum5,uint256 randNum6) internal pure returns(string memory){

       return string(abi.encodePacked(

           '{','"trait_type": "energy",',

     '"value": ', randNum1.toString(),',',

     '"max_value": ',MAX_VALUE.toString(),'}',',',

   '{','"trait_type": "speed",',

     '"value": ', randNum2.toString(),',',

     '"max_value": ',MAX_VALUE.toString(),'}',',',

   '{','"trait_type": "jump",',

     '"value": ', randNum3.toString(),',',

     '"max_value": ',MAX_VALUE.toString(),'}',',',

   '{','"trait_type": "stamina",',

     '"value": ', randNum4.toString(),',',

     '"max_value": ',MAX_VALUE.toString(),'}',',',

   '{','"trait_type": "physique",',

     '"value": ', randNum5.toString(),',',

     '"max_value": ',MAX_VALUE.toString(),'}',',',

   '{','"trait_type": "focus",',

     '"value": ', randNum6.toString(),',',

     '"max_value": ',MAX_VALUE.toString(),'}'

       ));}
Enter fullscreen mode Exit fullscreen mode

O código acima pode ser um pouco avassalador, eu entendo esse sentimento e já estive nessa situação antes. Então, vou explicar para que você possa entender.

A função abi.encodedPacked recebe uma ou mais variáveis e as codifica em ABI. Ela retorna um array de tamanho dinâmico de até 32 bytes. Em seguida, realizamos uma função de conversão de tipo (convertendo dados de um tipo de dados diferente para outro).

Estamos convertendo os dados de bytes para dados de string. Esses dados de string serão posteriormente usados na conversão de uma imagem para um formato base64, que o computador entende melhor.

Vamos adicionar uma função 'a' que retorna um URI para visualizar nosso NFT emitido no navegador.

function getTokenURI(uint256 tokenId,uint256 randNum1, uint256 randNum2, uint256 randNum3, uint256 randNum4, uint256 randNum5, uint256 randNum6) public view returns (string memory){

       string memory dataURI = Base64.encode(bytes(string(abi.encodePacked(

           '{',

               '"name": "Gontar #', tokenId.toString(), '",',

               '"description": "Battles on chain",',

               '"image": "', _imageURI, '",',

               '"attributes": [', setNFTTraits(randNum1, randNum2, randNum3, randNum4, randNum5, randNum6,']','}'

       ))));

   return string(abi.encodePacked("data:application/json;base64,",dataURI));

   }
Enter fullscreen mode Exit fullscreen mode

A função recebe valores aleatórios (que obteremos da VRF), um tokenID (nosso NFT cunhado) e retorna uma string que será uma string.

Na nossa função, codificamos o nome, descrição, URI da imagem enviada para o NFT.STORAGE e nossos atributos já definidos.

Em seguida, fazemos uma conversão de tipo para string. Mas você pode estar se perguntando por que precisamos converter para bytes e depois para Base64?

Isso faz com que nossos dados permaneçam inalterados, sem modificação durante a transferência, que é nosso principal objetivo. Não queremos que a imagem do nosso NFT que será emitido seja corrompida durante a transmissão.

Bem, a função Base64.encode() é usada quando há necessidade de codificar informações binárias que precisam ser armazenadas e transferidas por meio de mídias desenvolvidas para lidar com informações textuais.

Adicionamos a string "data:application/json;base64" antes de codificar o resultado de Base64.encode(), para especificar ao navegador que a string Base64 é um JSON e como abri-lo.

Se você tem acompanhado esta série desde a parte I, entenderá a razão para os próximos trechos de código.

Eu sei que você tem acompanhado a série. Portanto, faremos solicitações à Chainlink VRF para obter valores aleatórios para nossos seis (6) atributos.

A função requestRandomWords faz a solicitação e fulfillRandomWords preenche as palavras usando o requestID obtido de nossa primeira solicitação. Dessa forma, o sistema garante que os valores sejam verdadeiramente verificados.

// Pressupõe que a subscrição é suficientemente financiada.

   function requestRandomWords()

       external

       onlyOwner

       returns (uint256 requestId)

   {

// Será revertido se a subscrição não estiver definida e financiada.

       requestId = COORDINATOR.requestRandomWords(

           keyHash,

           s_subscriptionId,

           requestConfirmations,

           callbackGasLimit,

           numWords

       );

       s_requests[requestId] = RequestStatus({

           randomWords: new uint256[](0),

           exists: true,

           fulfilled: false

       });

       lastRequestId = requestId;

       emit RequestSent(requestId, numWords);

       return requestId;

   }

   function fulfillRandomWords(

       uint256 _requestId,

       uint256[] memory _randomWords

   ) internal override {

       require(s_requests[_requestId].exists, "request not found");

       s_requests[_requestId].fulfilled = true;

       s_requests[_requestId].randomWords = _randomWords;

       emit RequestFulfilled(_requestId, _randomWords);

   }

   function getRequestStatus(

       uint256 _requestId

   ) public view returns (bool fulfilled, uint256[] memory randomWords) {

       require(s_requests[_requestId].exists, "request not found");

       RequestStatus memory request = s_requests[_requestId];

       return (request.fulfilled, request.randomWords);

   }
Enter fullscreen mode Exit fullscreen mode

Ótimo trabalho até agora, conseguimos abordar as principais funções do nosso contrato de criação de NFTs com a Chainlink.

A função de cunhagem, que efetivamente cunhará nosso NFT, vem a seguir.

Espero que você tenha conseguido entender os passos que tomamos até agora. Se não, você sempre pode se referir à parte um (1) e parte dois (2) desta série sobre VRF.

Você pode sempre começar do início se perder algum passo. A coisa mais importante não é o quão rápido você chega a este ponto, mas sim entender o fluxo.

Então, continuando;

function mint(uint256 _requestId) public {

   //isto simplesmente devolve o estado de um pedido que fizemos a Chainlink VRF

   (bool fufilled, uint256[] memory randomWords) = getRequestStatus(_requestId);



   //verifica se o nosso pedido foi satisfeito

   require(fufilled,"Request not fufilled");

   //uma função do ERC721 que aumenta as contagens quando o NFT é cunhado

   _tokenIds.increment();

   uint256 tokenID = _tokenIds.current();

   //uma função do ERC721 para cunhar o nosso NFT para a conta que faz o pedido

   _safeMint(msg.sender, newItemId);

//Isso simplesmente divide nossos valores aleatórios por 101, e então retorna um resto

   //que pode estar entre 0-100. Este será o nosso valor de atributos

   uint256 randNum1 = randomWords[1] % 101;

   gontarPacks[newItemId].energy = randNum2;

   uint256 randNum2 = randomWords[2] % 101;

   gontarPacks[newItemId].speed = randNum3;

   uint256 randNum3 = randomWords[3] % 101;

   gontarPacks[newItemId].jump = randNum4;

   uint256 randNum4 = randomWords[4] % 101;

   gontarPacks[newItemId].stamina = randNum5;

   uint256 randNum5 = randomWords[5] % 101;

   gontarPacks[newItemId].physique = randNum6;

   uint256 randNum6 = randomWords[6] % 101;

   gontarPacks[tokenID].focus = randNum7;

   //define o token uri como um tokenID e os atributos gerados aleatoriamente

   _setTokenURI(tokenID, getTokenURI(newItemId,randNum1,randNum2,randNum3,randNum4,randNum5,randNum6));

}
Enter fullscreen mode Exit fullscreen mode

Podemos adicionar uma função auxiliar que simplesmente retorna os atributos de um NFT tokenID criado.

function getGontarPacks(
        uint256 _tokenId
    ) public view returns (GontarPack memory) {
        return gontarPacks[_tokenId];
    }

Enter fullscreen mode Exit fullscreen mode

Esta é uma função de visualização simples (uma função que apenas lê do estado) que retorna os atributos de um NFT cunhado.

Se você seguiu os passos corretamente, pode implantar seu contrato na rede Polygon Mumbai. Em seguida, usando o Scanner da Rede de Testes da Polygon, você pode criar um NFT com atributos exclusivos.

Bem, eu implantei o meu e testei as funcionalidades. Obtive um URI de token que pode ser visualizado no OpenSea Mumbai.

Aqui está o meu NFT. Se você olhar com cuidado... eu tenho um NFT criado com seis (6) atributos (Energia, Foco, Salto, Físico, Velocidade e Resistência). Se criarmos outro NFT, observaremos a diferença nos valores dos atributos.

Espero que, ao seguir a série sobre a Chainlink VRF, você tenha conseguido aprender como configurar um método de assinatura ChainLink VRF e escrever contratos inteligentes (usando o Solidity) que fazem uso do Oráculo da Chainlink.

Aplauda, siga e comente para mais artigos como este.

Você também pode me oferecer um café, se desejar. Eu vou apreciar -> https://www.buymeacoffee.com/etimpaul22a


Artigo escrito por Joe. Traduzido por Marcelo Panegali.

Latest comments (0)