WEB3DEV

Cover image for Como Escrever um Contrato Inteligente de Ingresso de Evento em Solidity
Fatima Lima
Fatima Lima

Posted on

Como Escrever um Contrato Inteligente de Ingresso de Evento em Solidity

Image description

Hoje, falaremos sobre outra linguagem, a linguagem Solidity. Quando estudei essa linguagem pela primeira vez, fiquei um pouco receoso em relação a ela. Mas quanto mais me aprofundo, mais me divirto.

Seu sistema de Array é um pouco diferente de outras linguagens. Em outras linguagens (como PHP e JavaScript), você pode adicionar quantos elementos quiser na matriz, mas o Solidity tem algumas barreiras.

Não se preocupe, hoje não falarei sobre Array. Estamos criando um contrato de ingresso de evento. Vamos falar sobre a história do ingresso e depois trabalharemos.

Nesse contrato, qualquer usuário pode criar um ingresso. Quando o usuário cria um ingresso, o proprietário do contrato recebe uma comissão. Suponhamos que Jone crie um ingresso e que o limite de ingressos seja 15 e que o preço de cada ingresso seja de 2 Eth. Se ele vender 15 ingressos, receberá uma receita de 15*2 = 30 Eth. Mas aqui adicionamos alguma complexidade, como, por exemplo, quando outros usuários comprarem esse ingresso, o proprietário do contrato receberá alguma comissão.

Observação: Não estamos criando o aplicativo inteiro. Estamos apenas criando um contrato.

Este é o código completo do contrato. Vamos falar sobre essa função passo a passo.

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

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

contract Ticket is ERC721URIStorage, Ownable {
   using Counters for Counters.Counter;
   Counters.Counter private tokenIdCounter;

   struct TicketInfo {
       uint256 tokenId;
       uint256 totalTickets;
       uint256 ticketsSold;
       uint256 ticketPrice;
       uint256 ticketStartDate;
       uint256 ticketEndDate;
       address creator;
       bool ticketSold;
   }

   struct PurchaseInfo {
       address buyer;
       uint256 ticketsBought;
       uint256 totalPrice;
       uint256 ticketId;
       uint256 purchaseId;
       uint256 purchaseTimestamp;
   }

   uint256 public creationFeePercentage;  // Percentual da taxa para criação  do ingresso
   uint256 public purchaseFeePercentage;  // Percentual da taxa para compra do ingresso

   mapping(uint256 => TicketInfo) public tickets;
   mapping(address => uint256[]) public userTickets;
   mapping(uint256 => PurchaseInfo[]) public ticketPurchases;  // Mapeamento para armazenar informações de compra de cada ingresso.
   event TicketCreated(
       uint256 indexed tokenId,
       uint256 totalTickets,
       uint256 ticketPrice,
       uint256 ticketStartDate,
       uint256 ticketEndDate
   );

   event TicketPurchased(
       uint256 indexed tokenId,
       address buyer,
       uint256 ticketsBought
   );

   constructor(uint256 _creationFeePercentage, uint256 _purchaseFeePercentage) ERC721("Ticket", "TICKET") {
       creationFeePercentage = _creationFeePercentage;
       purchaseFeePercentage = _purchaseFeePercentage;
   }

   function createTicket(
       string calldata tokenURI,
       uint256 _totalTickets,
       uint256 _ticketPrice,
       uint256 _ticketEndDate
   ) external payable {
       require(_totalTickets > 0, "Total tickets must be greater than 0");
       require(_ticketPrice > 0, "Ticket price must be greater than 0");
       require(_ticketEndDate > block.timestamp, "Ticket end date must be in the future");

       uint256 currentID = tokenIdCounter.current();
       tokenIdCounter.increment();

       _safeMint(msg.sender, currentID);
       _setTokenURI(currentID, tokenURI);

       uint256 ticketStartDate = block.timestamp;

       tickets[currentID] = TicketInfo({
           tokenId: currentID,
           totalTickets: _totalTickets,
           ticketsSold: 0,
           ticketPrice: _ticketPrice,
           ticketStartDate: ticketStartDate,
           ticketEndDate: _ticketEndDate,
           creator: msg.sender,
           ticketSold: false
       });

       // Calcula a taxa de criação e transfere para o proprietário do contrato       uint256 creationFee = creationFeePercentage;
       require(msg.value == creationFee, "Incorrect creation fee sent");

       // Transfere a taxa de criação para o proprietário do contrato
       payable(owner()).transfer(creationFee);

       emit TicketCreated(currentID, _totalTickets, _ticketPrice, ticketStartDate, _ticketEndDate);
   }

   function purchaseTicket(uint256 tokenID, uint256 ticketsToBuy) external payable {
       TicketInfo storage ticket = tickets[tokenID];
       require(!ticket.ticketSold, "Ticket has already been sold");
       require(ticketsToBuy > 0 && ticketsToBuy <= ticket.totalTickets - ticket.ticketsSold, "Invalid number of tickets");

       uint256 totalPrice = ticket.ticketPrice * ticketsToBuy;
       uint256 purchaseFee = purchaseFeePercentage;
       uint256 totalPriceWithFee = totalPrice + purchaseFee;

       require(msg.value == totalPriceWithFee, "Incorrect amount sent");

       // Transfere o preço do ingresso diretamente para o criador do ingresso
       payable(ticket.creator).transfer(totalPrice);

       // Transfere a taxa de compra para o proprietário do contrato       payable(owner()).transfer(purchaseFee);

       //Cunha os ingressos e registra as compras
       for (uint256 i = 0; i < ticketsToBuy; i++) {
           uint256 newTokenId = tokenIdCounter.current();
           tokenIdCounter.increment();
           _safeMint(msg.sender, newTokenId);
           _setTokenURI(newTokenId, tokenURI(tokenID));

           // Armazena o ingresso comprado para o usuário
           userTickets[msg.sender].push(newTokenId);

           // Armazena as informações de compra do ingresso
           ticketPurchases[newTokenId].push(PurchaseInfo({
               buyer: msg.sender,
               ticketsBought: 1,
               totalPrice: ticket.ticketPrice,
               ticketId:tokenID,
               purchaseId:newTokenId,
               purchaseTimestamp: block.timestamp
           }));

           ticket.ticketsSold++;

           emit TicketPurchased(newTokenId, msg.sender, ticketsToBuy);
       }

       // Marca o ingresso como vendido quando todos os ingressos são vendidos
       if (ticket.ticketsSold == ticket.totalTickets) {
           ticket.ticketSold = true;
       }
   }

   function getUserTickets(address user) external view returns (uint256[] memory) {
       return userTickets[user];
   }

   function getTicketInfo(uint256 tokenID) external view returns (TicketInfo memory) {
       return tickets[tokenID];
   }

   function getPurchaseInfo(uint256 tokenID) external view returns (PurchaseInfo[] memory) {
       return ticketPurchases[tokenID];
   }

   function updateCreationFeePercentage(uint256 _creationFeePercentage) external onlyOwner {
       creationFeePercentage = _creationFeePercentage;
   }

   function updatePurchaseFeePercentage(uint256 _purchaseFeePercentage) external onlyOwner {
       purchaseFeePercentage = _purchaseFeePercentage;
   }

   function getCreationFeePercentage() external view returns (uint256) {
       return creationFeePercentage;
   }

   function getPurchaseFeePercentage() external view returns (uint256) {
       return purchaseFeePercentage;
   }
}
Enter fullscreen mode Exit fullscreen mode

Passo-1:

Primeiro, importamos a classe de outro contrato necessário ou útil.

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
Enter fullscreen mode Exit fullscreen mode

Passo-2:

Em seguida, declaramos nosso contrato, cujo nome é Ticket e usamos outros contratos de importação.

Passo-3:

Em seguida, definimos Counters e tokenIdCounter.

using Counters for Counters.Counter;
Counters.Counter private tokenIdCounter;
Enter fullscreen mode Exit fullscreen mode

Passo-4:

Declaramos Struct ou Object

struct TicketInfo {
       uint256 tokenId;
       uint256 totalTickets;
       uint256 ticketsSold;
       uint256 ticketPrice;
       uint256 ticketStartDate;
       uint256 ticketEndDate;
       address creator;
       bool ticketSold;
   }

   struct PurchaseInfo {
       address buyer;
       uint256 ticketsBought;
       uint256 totalPrice;
       uint256 ticketId;
       uint256 purchaseId;
       uint256 purchaseTimestamp;
   }
Enter fullscreen mode Exit fullscreen mode

Passo-5:

A seguir, chame map para ler essa Struct ou Object

mapping(uint256 => TicketInfo) public tickets;
mapping(uint256 => PurchaseInfo[]) public ticketPurchases;
mapping(address => uint256[]) public userTickets;
Enter fullscreen mode Exit fullscreen mode

Aqui você vê que estamos usando 3 mapas. Os dois primeiros mapas são lidos para a Struct TicketInfo e PurchaseInfo. E o terceiro mapa é um pouco complicado, pois quando o usuário compra esse ingresso, colocamos o endereço desse usuário nesse mapa. No futuro, poderemos descobrir facilmente quantos ingressos esse usuário comprou.

Passo-6:

Em seguida, declaramos e criamos o Ticket Fee e compramos o Ticket Fee.

uint256 public creationFeePercentage; 
uint256 public purchaseFeePercentage;
Enter fullscreen mode Exit fullscreen mode

Passo-7:

Em seguida, declaramos o evento para registro na blockchain.

event TicketCreated(
       uint256 indexed tokenId,
       uint256 totalTickets,
       uint256 ticketPrice,
       uint256 ticketStartDate,
       uint256 ticketEndDate
   );

   event TicketPurchased(
       uint256 indexed tokenId,
       address buyer,
       uint256 ticketsBought
   );
Enter fullscreen mode Exit fullscreen mode

Passo-8:

Aqui aplicamos o método construct. Quando o contrato é implantado, esse contrato recebe dois parâmetros.

constructor(uint256 _creationFeePercentage, uint256 _purchaseFeePercentage) ERC721("Ticket", "TICKET") {
       creationFeePercentage = _creationFeePercentage;
       purchaseFeePercentage = _purchaseFeePercentage;
}
Enter fullscreen mode Exit fullscreen mode

Passo-9:

Desta vez, estamos trabalhando com o método createTicket

function createTicket(
       string calldata tokenURI,
       uint256 _totalTickets,
       uint256 _ticketPrice,
       uint256 _ticketEndDate
   ) external payable {
       require(_totalTickets > 0, "Total tickets must be greater than 0");
       require(_ticketPrice > 0, "Ticket price must be greater than 0");
       require(_ticketEndDate > block.timestamp, "Ticket end date must be in the future");

       uint256 currentID = tokenIdCounter.current();
       tokenIdCounter.increment();

       _safeMint(msg.sender, currentID);
       _setTokenURI(currentID, tokenURI);

       uint256 ticketStartDate = block.timestamp;

       tickets[currentID] = TicketInfo({
           tokenId: currentID,
           totalTickets: _totalTickets,
           ticketsSold: 0,
           ticketPrice: _ticketPrice,
           ticketStartDate: ticketStartDate,
           ticketEndDate: _ticketEndDate,
           creator: msg.sender,
           ticketSold: false
       });

       // Calcula a taxa de criação e transfere para o proprietário do contrato
       uint256 creationFee = creationFeePercentage;
       require(msg.value == creationFee, "Incorrect creation fee sent");

       // Transfer a taxa de criação para o proprietário do contrato
       payable(owner()).transfer(creationFee);e

       emit TicketCreated(currentID, _totalTickets, _ticketPrice, ticketStartDate, _ticketEndDate);
   }
Enter fullscreen mode Exit fullscreen mode

Este método precisa de três parâmetros.

  1. tokenURI

  2. _totalTickets

  3. _ticketPrice

  4. _ticketEndDate

Aqui, tokenURI é um link de url. Normalmente, quando usamos metadados, como nome do ingresso, categoria, etc., e imagem, usamos o IPFS. E outros parâmetros que já conhecemos.

Nessa função, criamos algumas condições antes de criar um ingresso.

require(_totalTickets > 0, "Total tickets must be greater than 0");
require(_ticketPrice > 0, "Ticket price must be greater than 0");
require(_ticketEndDate > block.timestamp, "Ticket end date must be in the future");
Enter fullscreen mode Exit fullscreen mode

Em seguida, pegamos o currentID e aumentamos esse currentID. Depois disso, vamos cunhar; então usamos _safeMint e _setTokenURI.

uint256 currentID = tokenIdCounter.current();
       tokenIdCounter.increment();

       _safeMint(msg.sender, currentID);
       _setTokenURI(currentID, tokenURI);

       uint256 ticketStartDate = block.timestamp;

       tickets[currentID] = TicketInfo({
           tokenId: currentID,
           totalTickets: _totalTickets,
           ticketsSold: 0,
           ticketPrice: _ticketPrice,
           ticketStartDate: ticketStartDate,
           ticketEndDate: _ticketEndDate,
           creator: msg.sender,
           ticketSold: false
});
Enter fullscreen mode Exit fullscreen mode

Em seguida, enviamos uma pequena quantia para o proprietário do contrato como uma taxa. No final, criamos um evento para a blockchain de registro.

// Calcula a taxa de criação e transfere para o proprietário do contrato
uint256 creationFee = creationFeePercentage;
require(msg.value == creationFee, "Incorrect creation fee sent");

// Transfere a taxa de criação para o proprietário do contrato
payable(owner()).transfer(creationFee);

emit TicketCreated(currentID, _totalTickets, _ticketPrice, ticketStartDate, _ticketEndDate);
Enter fullscreen mode Exit fullscreen mode

Passo-10:

Agora estamos trabalhando com o método purchaseTicket.

function purchaseTicket(uint256 tokenID, uint256 ticketsToBuy) external payable {
       TicketInfo storage ticket = tickets[tokenID];
       require(!ticket.ticketSold, "Ticket has already been sold");
       require(ticketsToBuy > 0 && ticketsToBuy <= ticket.totalTickets - ticket.ticketsSold, "Invalid number of tickets");

       uint256 totalPrice = ticket.ticketPrice * ticketsToBuy;
       uint256 purchaseFee = purchaseFeePercentage;
       uint256 totalPriceWithFee = totalPrice + purchaseFee;

       require(msg.value == totalPriceWithFee, "Incorrect amount sent");

       // Transfere o preço do ingresso diretamente para o criador do ingresso
       payable(ticket.creator).transfer(totalPrice);

       // Transfere a taxa de compra para o proprietário do ingresso
       payable(owner()).transfer(purchaseFee);

       // Cunha ingressos e registra compras
       for (uint256 i = 0; i < ticketsToBuy; i++) {
           uint256 newTokenId = tokenIdCounter.current();
           tokenIdCounter.increment();
           _safeMint(msg.sender, newTokenId);
           _setTokenURI(newTokenId, tokenURI(tokenID));

           // Armazena o ingresso comprado para o usuário
           userTickets[msg.sender].push(newTokenId);

           // Armazena a informação da compra para o ingresso
           ticketPurchases[newTokenId].push(PurchaseInfo({
               buyer: msg.sender,
               ticketsBought: 1,
               totalPrice: ticket.ticketPrice,
               ticketId:tokenID,
               purchaseId:newTokenId,
               purchaseTimestamp: block.timestamp
           }));

           ticket.ticketsSold++;

           emit TicketPurchased(newTokenId, msg.sender, ticketsToBuy);
       }

       // Marca o ingresso como vendido quando todos os ingressos são vendidos
       if (ticket.ticketsSold == ticket.totalTickets) {
           ticket.ticketSold = true;
       }
   }
Enter fullscreen mode Exit fullscreen mode

Este método aceita dois parâmetros.

  1. tokenID

  2. ticketsToBuy (quantos ingressos)

Primeiro, encontre as informações e armazene como TicketInfo. Em seguida, criamos algumas condições antes de comprar os ingressos.

TicketInfo storage ticket = tickets[tokenID];
require(!ticket.ticketSold, "Ticket has already been sold");
require(ticketsToBuy > 0 && ticketsToBuy <= ticket.totalTickets - ticket.ticketsSold, "Invalid number of tickets");
Enter fullscreen mode Exit fullscreen mode

Em seguida, fazemos alguns cálculos e novamente criamos condições.

uint256 totalPrice = ticket.ticketPrice * ticketsToBuy;
uint256 purchaseFee = purchaseFeePercentage;
uint256 totalPriceWithFee = totalPrice + purchaseFee;

require(msg.value == totalPriceWithFee, "Incorrect amount sent");
Enter fullscreen mode Exit fullscreen mode

Então, o proprietário do ingresso e o proprietário do contrato recebem uma taxa pela compra dos ingressos.

// Transfere o preço do ingresso diretamente para o criador do ingresso
 payable(ticket.creator).transfer(totalPrice);

// Transfere a taxa de compra para o proprietário do contrato
payable(owner()).transfer(purchaseFee);
Enter fullscreen mode Exit fullscreen mode

Em seguida, cunhamos esse ingresso de compra e registramos todos os dados necessários.

// Cunha ingressos e registra compras
for (uint256 i = 0; i < ticketsToBuy; i++) {
   uint256 newTokenId = tokenIdCounter.current();
   tokenIdCounter.increment();
   _safeMint(msg.sender, newTokenId);
   _setTokenURI(newTokenId, tokenURI(tokenID));

   // Armazena o ingresso comprado para o usuário
   userTickets[msg.sender].push(newTokenId);

   // Armazena a informação da compra para o ingresso
   ticketPurchases[newTokenId].push(PurchaseInfo({
       buyer: msg.sender,
       ticketsBought: 1,
       totalPrice: ticket.ticketPrice,
       ticketId:tokenID,
       purchaseId:newTokenId,
       purchaseTimestamp: block.timestamp
   }));

   ticket.ticketsSold++;

   emit TicketPurchased(newTokenId, msg.sender, ticketsToBuy);
}

// Marca o ingresso como vendido quando todos os ingressos são vendidos
if (ticket.ticketsSold == ticket.totalTickets) {
   ticket.ticketSold = true;
}
Enter fullscreen mode Exit fullscreen mode

Passo-11:

Crie uma função getter.

function getUserTickets(address user) external view returns (uint256[] memory) {
     return userTickets[user];
 }

function getTicketInfo(uint256 tokenID) external view returns (TicketInfo memory) {
   return tickets[tokenID];
}

function getPurchaseInfo(uint256 tokenID) external view returns (PurchaseInfo[] memory) {
   return ticketPurchases[tokenID];
}
function getCreationFeePercentage() external view returns (uint256) {
     return creationFeePercentage;
 }

function getPurchaseFeePercentage() external view returns (uint256) {
   return purchaseFeePercentage;
}
Enter fullscreen mode Exit fullscreen mode

Passo-12:

Agora criamos algumas funções para futura atualização de cobranças.

function getCreationFeePercentage() external view returns (uint256) {
     return creationFeePercentage;
 }

function getPurchaseFeePercentage() external view returns (uint256) {
   return purchaseFeePercentage;
}
Enter fullscreen mode Exit fullscreen mode

Parabéns, concluímos nosso contrato simples. Agora estamos fazendo alguns testes. Para testar, estamos usando o editor remix on-line. Aqui está o link

https://remix.ethereum.org/

Passo-13:

Testando….

Image description

Esta imagem de uma pequena caixa vermelha no lado esquerdo é o botão de implantação. Ao clicar nesse botão, você verá o seguinte. Agora, clique na caixa vermelha grande dentro do botão de seta.

Image description

Em seguida, depositamos um valor. Aqui eu coloquei 1000000000000000000 Wei.

Nota: 1000000000000000000 Wei = 1 Eth.

Em seguida, clique novamente no botão de seta. Agora podemos ver o botão Deploy. Clique nesse botão para implantar o contrato.

Image description

Ao implantar o contrato, vemos alguns métodos de teste como este.

Image description

Agora mudamos o usuário

Image description

Aqui vemos 15 usuários para testar o contrato. O primeiro usuário é o proprietário do contrato. Quando o contrato é implantado, o sistema recebe algum gas. Por isso, o saldo do primeiro usuário não é de 100 ether, mas os outros usuários têm 100 ether. Agora mudamos o segundo usuário que cria um ingresso para seu evento.

Image description

Aqui você vê o botão createTicket [2ª caixa vermelha]. Nesse botão, no lado direito, você vê um botão de seta. Clique nesse botão e você verá um campo de entrada. Preencha esse campo e, em seguida, preencha o campo de valor [1ª caixa vermelha] e altere o tipo de moeda. Esse campo de valor é o campo de cobrança. Quando o usuário cria um ingresso, o proprietário do contrato recebe uma comissão. Agora clique no botão createTicket

Image description

Image description

Criamos o ingresso com sucesso. Nosso primeiro tokenId de ingresso é 0.

Image description

Você já deve ter percebido que o saldo do segundo usuário está diminuindo por criar ingressos e o saldo do primeiro usuário está aumentando por receber uma comissão.

Agora estamos mudando o usuário. Aqui, o terceiro usuário está comprando um ingresso. Vamos fazer isso.

Image description

Aqui, a terceira caixa é o botão do método purchaseTicket. Com esse botão, no lado direito, você verá uma seta. Clique nessa seta e verá dois campos de entrada. Preencha esse campo de entrada. Aqui compramos 2 ingressos e o tokenID é 0.

Na segunda caixa vermelha, definimos o valor 3. Porque compramos 2 ingressos e o preço de cada um é 1 eth e a comissão é 1 eth. Portanto, o preço total é 3 eth. Agora clique no botão purchaseTicket.

Image description

Image description

Agora, podemos ver as informações do ingresso de compra.

Image description

Aqui, o primeiro usuário é o proprietário do contrato, o segundo usuário é o proprietário do ingresso e o terceiro usuário está comprando um ingresso. Quando um ingresso é criado ou vendido, o primeiro usuário recebe uma comissão. O segundo usuário é o proprietário do evento e, quando um ingresso é vendido, o segundo usuário recebe o valor de venda.

Agora é hora de implantá-lo em uma rede Ethereum ao vivo ou em uma rede Testnet e compartilhá-lo com o mundo!

Bom trabalho! 🚀

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

Latest comments (0)