WEB3DEV

Cover image for Crie um sistema de emissão de ingressos Web3 com NFTs e interrompa a Ticketmaster
Fatima Lima
Fatima Lima

Posted on

Crie um sistema de emissão de ingressos Web3 com NFTs e interrompa a Ticketmaster

Introdução

Um caso de uso popular e prático para NFTs é a geração de ingressos para eventos ao vivo. As blockchains, como a Ethereum, podem garantir a propriedade, a origem e a autenticidade de um item digital, resolvendo efetivamente o problema dos ingressos falsificados. Enquanto grandes empresas, como a Ticketmaster, lutam para mitigar os cambistas (tentando desesperadamente controlar quem pode revender ingressos, onde e por quanto) e a fraude de ingressos, a web3 já tem uma solução. O setor de venda de ingressos está pronto para a disrupção.

Neste tutorial, veremos como criar uma solução de venda de ingressos usando o ConsenSys Truffle, Infura, e a API de NFT da Infura. Implantaremos um contrato inteligente que atua como um serviço de emissão de ingressos e cria ingressos como tokens não fungíveis (NFTs) ERC-20. Também analisaremos algumas arquiteturas de possíveis front-ends que poderiam interagir com o contrato e, juntos, funcionar como um sistema de emissão de ingressos integrado, full-stack e web3.

Vamos começar a construir!

Crie um sistema de emissão de ingressos NFT na Ethereum

A arquitetura básica do nosso sistema tem como objetivo criar um contrato inteligente que emite nossos ingressos como tokens não fungíveis (NFTs). Os NFTs são perfeitos para o que queremos construir. Eles são tokens digitais comprovadamente exclusivos que nos permitem garantir que cada ingresso seja único e não possa ser copiado ou falsificado. Isso não apenas garante uma experiência segura de venda de ingressos para os frequentadores de shows, mas também permite que os artistas (e organizadores de eventos) tenham maior controle sobre a distribuição, o preço e a revenda dos ingressos. O uso de contratos inteligentes e NFTs permite até mesmo novos fluxos de receita, como pagamentos de royalties e compartilhamento de arrecadação!

(Se você precisar de informações básicas sobre qualquer um desses termos, sobre a tecnologia blockchain ou sobre a web3 em geral, consulte este artigo Aprendendo a se tornar um desenvolvedor Web3 explorando a stack Web3).

Passo 1: Instale a MetaMask

A primeira coisa que faremos é configurar uma carteira MetaMask e adicionar a rede de teste Sepolia a ela. A MetaMask é a carteira digital de autocustódia mais popular, segura e mais fácil de usar do mundo.

Primeiro, faça download da extensão Metamask. Depois de instalar a extensão, a MetaMask configurará a carteira para você. No processo, você receberá uma frase secreta. Mantenha-a em segurança e, em nenhuma circunstância, torne-a pública.

Depois de configurar a MetaMask, clique na guia Network (Rede) no canto superior direito. Você verá uma opção para mostrar/ocultar redes de teste.

Depois de ativar as redes de teste, você poderá ver a rede de teste Sepolia no menu suspenso. Queremos usar a rede Sepolia para que possamos implantar e testar nosso sistema sem gastar dinheiro real.

Passo 2: Obtenha algum ETH de teste

Para implantar nosso contrato inteligente e interagir com ele, precisaremos de alguns ETH gratuitos de teste. Você pode obter ETH de teste gratuito da Sepolia no site Sepolia faucet.

Depois de financiar sua carteira, você deverá ver um saldo diferente de zero quando mudar para a rede de teste Sepolia na MetaMask.

Image description

Passo 3: Instale o NPM e o Node

Como todos os dapps da Ethereum, criaremos nosso projeto usando o node e o npm. Caso não os tenha instalado em seu computador local, você pode fazer isso aqui.

Para garantir que tudo funcione corretamente, execute o seguinte comando:


$ node -v

Enter fullscreen mode Exit fullscreen mode

Se tudo correr bem, você verá um número da versão para o Node.

Passo 4: Inscreva-se em uma conta Infura

Para implantar nosso contrato na rede Sepolia, precisaremos de uma conta Infura. A Infura nos dá acesso a pontos de extremidade (endpoints) RPC que permitem acesso rápido, confiável e fácil à blockchain de nossa escolha.

Inscreva-se para obter uma conta gratuita. Depois de criar sua conta, navegue até o dashboard e selecione Create New Key (Crie uma Nova Chave).

Image description

Para a rede, escolha a API Web3 e nomeie-a como Ticketing System, ou algo de sua preferência.

Depois de clicar em Create (Criar), a Infura gerará uma chave de API para você e fornecerá endpoints de RPC para Ethereum, Goerli, Sepolia, L2s e L1s não-EVM (e suas redes de teste correspondentes) automaticamente.

Para este tutorial, estamos interessados apenas no endpoint RPC da Sepolia. Esse URL tem o seguinte formato https://sepolia.infura.io/v3/←API KEY→

Passo 5: Crie um projeto Node e instale os pacotes necessários

Vamos configurar um repositório de projeto vazio executando os seguintes comandos:


$ mkdir nft-ticketing && cd nft-ticketing

$ npm init -y

Enter fullscreen mode Exit fullscreen mode

Usaremos o Truffle, um ambiente de desenvolvimento reconhecido mundialmente e uma estrutura de teste para contratos inteligentes EVM, para criar e implementar nosso contrato inteligente de criptomoedas. Instale o Truffle, executando:


$ npm install —save truffle

Enter fullscreen mode Exit fullscreen mode

Agora podemos criar um projeto Truffle básico executando o seguinte comando:


$ npx truffle init

Enter fullscreen mode Exit fullscreen mode

Para verificar se tudo está funcionando corretamente, execute:


$ npx truffle test

Enter fullscreen mode Exit fullscreen mode

Agora o Truffle foi configurado com sucesso. Em seguida, vamos instalar o pacote de contratos OpenZeppelin. Esse pacote nos dará acesso à implementação básica do ERC-721 (o padrão para tokens não fungíveis), bem como a algumas funcionalidades adicionais úteis.


$ npm install @openzeppelin/contracts

Enter fullscreen mode Exit fullscreen mode

Para permitir que o Truffle use nossa carteira MetaMask, assine transações e pague o gas em nosso nome, precisaremos de outro pacote chamado hdwalletprovider. Instale-o usando o seguinte comando:


$ npm install @truffle/hdwallet-provider

Enter fullscreen mode Exit fullscreen mode

Por fim, para manter nossas informações confidenciais da carteira com segurança, usaremos o pacote dotenv.


$ npm install dotenv

Enter fullscreen mode Exit fullscreen mode

Passo 6: Criar o contrato inteligente de emissão de ingressos para o NFT

Abra o repositório do projeto em um editor de código (por exemplo, VS Code). Na pasta contracts,crie um artigo novo chamado NftTicketing.sol.

Nosso contrato de emissão de ingressos herdará todas as funcionalidades oferecidas pela implementação ERC721Enumerable do OpenZeppelin. Isso inclui transferências, rastreamento de metadados, dados de propriedade etc.

Implementaremos os seguintes recursos do zero:

  1. Venda pública primária: Nosso contrato dará a seu proprietário o poder de vender ingressos a um determinado preço. O proprietário terá o poder de abrir e fechar vendas, atualizar os preços dos ingressos e retirar qualquer dinheiro enviado ao contrato para a compra de ingressos. O público terá a oportunidade de comprar ingressos pelo preço de venda sempre que a venda estiver aberta e ainda houver ingressos disponíveis.
  2. Airdropping: O proprietário poderá fazer airdrop dos ingressos para uma lista de endereços de carteiras.
  3. Reserva: O proprietário também poderá reservar ingressos para si mesmo sem ter que pagar o preço de venda pública.

Adicione o seguinte código a NftTicketing.sol.


//SPDX-License-Identifier: MIT

pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";

import "@openzeppelin/contracts/access/Ownable.sol";

import "@openzeppelin/contracts/utils/Counters.sol";

import "@openzeppelin/contracts/utils/Base64.sol";

import "@openzeppelin/contracts/utils/Strings.sol";

contract NftTicketing is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable  {

    using Counters for Counters.Counter;

    Counters.Counter private _tokenIds;

    // Número total de ingressos disponíveis para o evento

    uint public constant MAX_SUPPLY = 10000;

    // Número de ingressos que você pode reservar de cada vez; evita o envio de spam

    uint public constant MAX_PER_MINT = 5;

    string public baseTokenURI;

    // Preço de um único ingresso

    uint public price = 0.05 ether;

    // Sinalizador para ativar e desativar as vendas

    bool public saleIsActive = false;

    // Dar um nome e um marcador à coleção

    constructor() ERC721("My NFT ingressos", "MNT") {}

    // Gerar metadados do NFT

    function generateMetadata(uint tokenId) public pure returns (string memory) {

        string memory svg = string(abi.encodePacked(

            "<svg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='xMinyMin meet' viewBox='0 0 350 350'>",

            "<style>.base { fill: white; font-family: serif; font-size: 25px; }</style>",

            "<rect width='100%' height='100%' fill='red' />",

            "<text x='50%' y='40%' class='base' dominant-baseline='middle' text-anchor='middle'>",

            "<tspan y='50%' x='50%'>NFT Ticket #",

            Strings.toString(tokenId),

            "</tspan></text></svg>"

        ));

        string memory json = Base64.encode(

            bytes(

                string(

                    abi.encodePacked(

                        '{"name": "NFT Ticket #',

                        Strings.toString(tokenId),

                        '", "description": "A ticket that gives you access to a cool event!", "image": "data:image/svg+xml;base64,',

                        Base64.encode(bytes(svg)),

                        '", "attributes": [{"trait_type": "Type", "value": "Base Ticket"}]}'

                    )

                )

            )

        );

        string memory metadata = string(

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

        );

        return metadata;

    }

    // Reserva de ingressos para a carteira do criador

    function reserveNfts(uint _count) public onlyOwner {

        uint nextId = _tokenIds.current();

        require(nextId + _count < MAX_SUPPLY, "Not enough NFTs left to reserve");

        for (uint i = 0; i < _count; i++) {

            string memory metadata = generateMetadata(nextId + i);

            _mintSingleNft(msg.sender, metadata);

        }

    }

    // Airdrop de NFTs

    function airDropNfts(address[] calldata _wAddresses) public onlyOwner {

        uint nextId = _tokenIds.current();

        uint count = _wAddresses.length;

        require(nextId + count < MAX_SUPPLY, "Not enough NFTs left to reserve");

        for (uint i = 0; i < count; i++) {

            string memory metadata = generateMetadata(nextId + i);

            _mintSingleNft(_wAddresses[i], metadata);

        }

    }

    // Configuração do estado Sale (Compra)

    function setSaleState(bool _activeState) public onlyOwner {

        saleIsActive = _activeState;

    }

    // Permissão ao público para cunhar NFTs

    function mintNfts(uint _count) public payable {

        uint nextId = _tokenIds.current();

        require(nextId + _count < MAX_SUPPLY, "Not enough NFT ingressos left!");

        require(_count > 0 && _count <= MAX_PER_MINT, "Cannot mint specified number of NFT ingressos.");

        require(saleIsActive, "Sale is not currently active!");

        require(msg.value >= price * _count, "Not enough ether to purchase NFTs.");

        for (uint i = 0; i < _count; i++) {

            string memory metadata = generateMetadata(nextId + i);

            _mintSingleNft(msg.sender, metadata);

        }

    }

    // Cunhagem de um único ingresso NFT

    function _mintSingleNft(address _wAddress, string memory _tokenURI) private {

        // Verificação de integridade para o pior cenário possível

        require(totalSupply() == _tokenIds.current(), "Indexing has broken down!");

        uint newTokenID = _tokenIds.current();

        _safeMint(_wAddress, newTokenID);

        _setTokenURI(newTokenID, _tokenURI);

        _tokenIds.increment();

    }

    // Atualização do preço

    function updatePrice(uint _newPrice) public onlyOwner {

        price = _newPrice;

    }

    // Retirada de ether

    function withdraw() public payable onlyOwner {

        uint balance = address(this).balance;

        require(balance > 0, "No ether left to withdraw");

        (bool success, ) = (msg.sender).call{value: balance}("");

        require(success, "Transfer failed.");

    }

    // Obtenção de tokens de um proprietário

    function tokensOfOwner(address _owner) external view returns (uint[] memory) {

        uint tokenCount = balanceOf(_owner);

        uint[] memory tokensId = new uint256[](tokenCount);

        for (uint i = 0; i < tokenCount; i++) {

            tokensId[i] = tokenOfOwnerByIndex(_owner, i);

        }

        return tokensId;

    }

    // As seguintes funções são substituições exigidas pelo Solidity.

    function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize)

        internal

        override(ERC721, ERC721Enumerable)

    {

        super._beforeTokenTransfer(from, to, tokenId, batchSize);

    }

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {

        super._burn(tokenId);

    }

    function tokenURI(uint256 tokenId)

        public

        view

        override(ERC721, ERC721URIStorage)

        returns (string memory)

    {

        return super.tokenURI(tokenId);

    }

    function supportsInterface(bytes4 interfaceId)

        public

        view

        override(ERC721, ERC721Enumerable)

        returns (bool)

    {

        return super.supportsInterface(interfaceId);

    }

}

Enter fullscreen mode Exit fullscreen mode

Verifique se o contrato está sendo compilado corretamente, executando:


npx truffle compile

Enter fullscreen mode Exit fullscreen mode

Nosso contrato já é bastante complexo, mas é possível adicionar alguns recursos extras conforme você achar necessário.

Por exemplo, você pode implementar um mecanismo anti-scalping (para evitar cambista) em seu contrato. Os passos para fazer isso seriam os seguintes:

  1. Defina um mapeamento Solidity que funcione como uma lista de permissões para carteiras que possam conter mais de um ingresso.
  2. Crie uma função que permita que o proprietário adicione endereços a essa lista de permissões.
  3. Introduza uma verificação em _beforeTokenTransfer que permita a cunhagem ou transferência para uma carteira que já tenha um ingresso, somente se ela estiver na lista de permissões.

Adicione o seguinte trecho de código abaixo do constructor do contrato:


mapping(address => bool) canMintMultiple;

    // Função que permite que os endereços da lista de permissões retenham vários NFTs.

    function addToAllowlist(address[] calldata _wAddresses) public onlyOwner {

        for (uint i = 0; i < _wAddresses.length; i++) {

            canMintMultiple[_wAddresses[i]] = true;

        }

    }

Finally, modify the __beforeTokenTranfer_ function to the following:

// As funções a seguir são substituições exigidas pelo Solidity.

    function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize)

        internal

        override(ERC721, ERC721Enumerable)

    {

        if (balanceOf(to) > 0) {

            require(to == owner() || canMintMultiple[to], "Not authorized to hold more than one ticket");

        }

        super._beforeTokenTransfer(from, to, tokenId, batchSize);

    }

Enter fullscreen mode Exit fullscreen mode

Compile o contrato mais uma vez usando o comando Truffle acima.

Passo 7: Atualize a configuração do Truffle e crie um arquivo .env

Crie um novo arquivo no diretório raiz do projeto, chamado .env e adicione o seguinte conteúdo:


INFURA_API_KEY = "https://sepolia.infura.io/v3/<Your-API-Key>"

MNEMONIC = "<Your-MetaMask-Secret-Recovery-Phrase>"

Enter fullscreen mode Exit fullscreen mode

Em seguida, vamos adicionar informações sobre nossa carteira, o endpoint RPC da Infura e a rede Sepolia ao nosso arquivo de configuração do Truffle. Substitua o conteúdo de truffle.config.js com o seguinte:


require('dotenv').config();

const HDWalletProvider = require('@truffle/hdwallet-provider');

const { INFURA_API_KEY, MNEMONIC } = process.env;

module.exports = {

  networks: {

    development: {

      host: "127.0.0.1",

      port: 8545,

      network_id: "*"

    },

    sepolia: {

      provider: () => new HDWalletProvider(MNEMONIC, INFURA_API_KEY),

      network_id: '5',

    }

  }

};

Enter fullscreen mode Exit fullscreen mode

Passo 8: Implante o Contrato Inteligente NFT

Vamos agora escrever um script para implantar nosso contrato na blockchain Sepolia. Na pasta migrations, crie um novo arquivo chamado 1_deploy_contract.js e adicione o seguinte código:


// Obter a instância do contrato NFT

const nftContract = artifacts.require("NftTicketing");

module.exports = async function (deployer) {

    // Implantar o contrato

    await deployer.deploy(nftContract);

    const contract = await nftContract.deployed();

    // Cunhar 5 ingressos

    await contract.reserveNfts(5);

    console.log("5 NFT ingressos have been minted!")

};

Enter fullscreen mode Exit fullscreen mode

Está tudo pronto! Implante o contrato executando o seguinte comando:


truffle migrate --network sepolia

Enter fullscreen mode Exit fullscreen mode

Se tudo correr bem, você verá um resultado (contendo o endereço do contrato) parecido com esse:


Starting migrations...

======================

> Network name:    'sepolia'

> Network id:      5

> Block gas limit: 30000000 (0x1c9c380)

1_deploy_contract.js

====================

   Deploying 'NftTicketing'

   -----------------------

   > transaction hash:    …

   > Blocks: 2            Seconds: 23

    …

   > Saving artifacts

   -------------------------------------

   > Total cost:     0.1201 ETH

Summary

=======

> Total deployments:   1

> Final cost:          0.1201 ETH

Enter fullscreen mode Exit fullscreen mode

Você pode pesquisar o endereço de seu contrato no etherscan da Sepolia e vê-lo ao vivo.

Parabéns! Você implantou com sucesso o contrato na Sepolia.

Passo 9: Interface com o contrato inteligente

Temos nosso contrato inteligente! A próxima etapa é implantar front-ends que façam interface com o contrato e permitam que qualquer pessoa chame a função mint para fazer uma doação e cunhar um ingresso para si.

Para um serviço de emissão de ingressos totalmente funcional, você normalmente precisaria dos seguintes front-ends:

  1. Um site (com uma excelente experiência do usuário) onde os usuários públicos podem pagar e cunhar seus ingressos.
  2. Um portal de administração no qual o proprietário pode reservar e distribuir ingressos, atualizar preços, transferir a função de administrador para outra carteira, retirar a receita de vendas, abrir e fechar vendas etc.
  3. Uma ferramenta que verifica se uma pessoa tem um determinado ingresso, tanto on-line quanto na vida real.

A criação desses sistemas a partir do zero está fora do escopo deste tutorial, mas deixaremos alguns recursos e dicas para você.

  1. Para o site de cunhagem de front-end, dê uma olhada no front-end que criei no tutorial Obrigado NFT como ponto de partida.
  2. Se você observar seu contrato no Etherscan, ele fornecerá automaticamente um portal de administração no qual você poderá chamar qualquer função do seu contrato. Esse é um bom primeiro passo antes de você decidir criar uma solução personalizada.
  3. Verificar se uma carteira tem um ingresso de sua coleção é extremamente simples, usando a função balanceOf. Se alguém puder provar que possui uma carteira com um de nossos ingressos, isso é basicamente uma prova de que ele possui um ingresso Isso pode ser feito usando assinaturas digitais.

Verificação usando a API de NFT da Infura

Mais uma dica: depois de ter seu contrato inteligente e seu frontend (ou mesmo antes de seu frontend estar completo e você quiser provar que tudo funciona), você pode usar a API de NFT da Infura para verificar se o novo NFT existe. A API de NFT da Infura é uma maneira rápida de substituir muitos códigos relacionados ao NFT por uma única chamada de API.

Por exemplo, as informações de que precisamos para mostrar a propriedade de nosso NFT estão facilmente disponíveis para nós por meio da API. Tudo o que precisamos fornecer é o endereço da carteira. O código ficaria mais ou menos assim:


const walletAddress = <your wallet address>

const chainId = "1"

const baseUrl = "https://nft.api.infura.io"

const url = `${baseUrl}/networks/${chainId}/accounts/${walletAddress}/assets/nfts`

// Solicitação da API 

const config = {

    method: 'get',

    url: url,

    auth: {

        username: '<-- INFURA_API_KEY –>',

        password: '<-- INFURA_API_SECRET –>',

    }

};

// Solicitação da API

axios(config)

    .then(response => {

        console.log(response['data'])

    })

    .catch(error => console.log('error', error));

Enter fullscreen mode Exit fullscreen mode

Execute-o …


$ node <filename>.js

Enter fullscreen mode Exit fullscreen mode

E você deverá ver algo como isto:


{

  total: 1,

  pageNumber: 1,

  pageSize: 100,

  network: 'ETHEREUM',

  account: <account address>,

  cursor: null,

  assets: [

    {

      contract: <NFT contract address>,

      tokenId: '0',

      supply: '1',

      type: 'ERC20',

      metadata: [Object]

    },

   …

  ]

}

Enter fullscreen mode Exit fullscreen mode

Conclusão

Neste tutorial, implantamos um serviço de emissão de ingressos NFT totalmente funcional usando o Truffle, Infura, e a API de NFT da Infura. Obviamente, não é tudo o que você precisaria para interromper o Ticketmaster, mas é um começo sólido e uma ótima prova de conceito! Mesmo que você não utilize esse código e inicie sua própria plataforma de emissão de ingressos NFT, esperamos que tenha aprendido um pouco sobre a web3 no processo.

Esse artigo foi escrito por Michael Bogan e traduzido por Fátima Lima. O original pode ser lido aqui.

Top comments (0)