Write A Event Ticket Smart Contract in Solidity

syed kamruzzaman
Coinmonks

--

Today, we discuss another language. this is solidity language. When I study first this language that times I was a little bit afraid about this language. But when I go deeper then I have fun.

Their Array system little bit different than other languages. In other languages (like PHP and JavaScript) Array you can add as many elements in Array but solidity has some barriers.

Don’t worry, today I didn’t talk about Array. We are making an Event Ticket Contract. Let’s talk about the Ticket story then we work.

In this Contract, any user can create a Ticket as many. When the user creates a Ticket the Contract owner gets some commission. Suppose Jone creates a Ticket and the Ticket Limit is 15 and each Ticket price is 2 Eth. If he sells 15 tickets then he gets revenue 15*2 = 30 Eth. But here we add some complexity, like when other users buy this ticket then the contract owner will get some commission.

Note: We are not making the whole app. We are just creating a contract.

This is the Contract Full code. Let’s talk this function step by step-

// 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; // Fee percentage for creating a ticket
uint256 public purchaseFeePercentage; // Fee percentage for purchasing a ticket

mapping(uint256 => TicketInfo) public tickets;
mapping(address => uint256[]) public userTickets;
mapping(uint256 => PurchaseInfo[]) public ticketPurchases; // Mapping to store purchase information for each ticket

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
});

// Calculate creation fee and transfer to contract owner
uint256 creationFee = creationFeePercentage;
require(msg.value == creationFee, "Incorrect creation fee sent");

// Transfer the creation fee to the contract owner
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");

// Transfer the ticket price directly to the ticket creator
payable(ticket.creator).transfer(totalPrice);

// Transfer the purchase fee to the contract owner
payable(owner()).transfer(purchaseFee);

// Mint tickets and record purchases
for (uint256 i = 0; i < ticketsToBuy; i++) {
uint256 newTokenId = tokenIdCounter.current();
tokenIdCounter.increment();
_safeMint(msg.sender, newTokenId);
_setTokenURI(newTokenId, tokenURI(tokenID));

// Store the purchased ticket for the user
userTickets[msg.sender].push(newTokenId);

// Store the purchase information for the ticket
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);
}

// Mark the ticket as sold when all tickets are sold
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;
}
}

Step-1:

First, we import necessary or useful Other Contract Class.

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

Step-2:

Then we declare Our Contract, name is Ticket and use other import Contracts.

Step-3:

Then we define Counters and tokenIdCounter.

using Counters for Counters.Counter;
Counters.Counter private tokenIdCounter;

Step-4:

Declare Struct or 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;
}

Step-5:

Then call map to read this Struct or Object

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

Here you see we are using 3 maps. The first 2 maps read for TicketInfo and PurchaseInfo Struct And 3rd map little bit tricky, like when the user purchases this Ticket then we push this user address in this map. In the future, we can easily find out this purchase user how many tickets he buys.

Step-6:

Then we declare, create the Ticket Fee, and Purchase the Ticket Fee.

uint256 public creationFeePercentage;  
uint256 public purchaseFeePercentage;

Step-7:

Then we declare event for record in blockchain.

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

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

Step-8:

Here we apply the construct method. When Contract deploys then this Contract gets two parameters.

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

Step-9:

This time we are working createTicket method.

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
});

// Calculate creation fee and transfer to contract owner
uint256 creationFee = creationFeePercentage;
require(msg.value == creationFee, "Incorrect creation fee sent");

// Transfer the creation fee to the contract owner
payable(owner()).transfer(creationFee);

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

This method needs Three parameters.

1. tokenURI

2. _totalTickets

3. _ticketPrice

4. _ticketEndDate

Here tokenURI is a url link. Usually when we use metadata, like ticket name, category, etc., and image. At that time we used IPFS. And Other parameters we already know.

In this function, we do some conditions before creating a Ticket.

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");

Then we collect the currentID and increase this currentID. After that we are doing mint, so we use _safeMint and _setTokenURI Then all data passes tickets map.

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
});

Then we send a small amount to the contract owner as a charge. in the end, we create an event for the record blockchain.

// Calculate creation fee and transfer to contract owner
uint256 creationFee = creationFeePercentage;
require(msg.value == creationFee, "Incorrect creation fee sent");

// Transfer the creation fee to the contract owner
payable(owner()).transfer(creationFee);

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

Step-10:

Now we are working purchaseTicket method.

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");

// Transfer the ticket price directly to the ticket creator
payable(ticket.creator).transfer(totalPrice);

// Transfer the purchase fee to the contract owner
payable(owner()).transfer(purchaseFee);

// Mint tickets and record purchases
for (uint256 i = 0; i < ticketsToBuy; i++) {
uint256 newTokenId = tokenIdCounter.current();
tokenIdCounter.increment();
_safeMint(msg.sender, newTokenId);
_setTokenURI(newTokenId, tokenURI(tokenID));

// Store the purchased ticket for the user
userTickets[msg.sender].push(newTokenId);

// Store the purchase information for the ticket
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);
}

// Mark the ticket as sold when all tickets are sold
if (ticket.ticketsSold == ticket.totalTickets) {
ticket.ticketSold = true;
}
}

This method accepts two parameters.

1. tokenID

2. ticketsToBuy (how many tickets)

First, find the ticket information and storage as TicketInfo Then we do some conditions before purchasing Tickets.

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");

Then we do some calculations and again condition

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

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

Then ticket owner and the contract owner get a fee for purchasing tickets.

// Transfer the ticket price directly to the ticket creator
payable(ticket.creator).transfer(totalPrice);

// Transfer the purchase fee to the contract owner
payable(owner()).transfer(purchaseFee);

Then we mint this purchasing ticket and record all necessary things.

// Mint tickets and record purchases
for (uint256 i = 0; i < ticketsToBuy; i++) {
uint256 newTokenId = tokenIdCounter.current();
tokenIdCounter.increment();
_safeMint(msg.sender, newTokenId);
_setTokenURI(newTokenId, tokenURI(tokenID));

// Store the purchased ticket for the user
userTickets[msg.sender].push(newTokenId);

// Store the purchase information for the ticket
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);
}

// Mark the ticket as sold when all tickets are sold
if (ticket.ticketsSold == ticket.totalTickets) {
ticket.ticketSold = true;
}

Step-11:

Make some getter function.

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;
}

Step-12:

Now we have made some functions for future update charges.

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

function getPurchaseFeePercentage() external view returns (uint256) {
return purchaseFeePercentage;
}

Congratulations, we have finished our simple contract. Now we are doing some testing. For testing we are use remix online editor. here its link

https://remix.ethereum.org/

Step-13:

Testing….

This picture small red box on the left side this is the deploy button. When you click this button then you see this. Now click the big red box inside the arrow button.

Then we put some charge. Here I put 1000000000000000000 Wei.

Note: 1000000000000000000 Wei = 1 Eth.

Then click the arrow button again. Now we can see the Deploy button. Click this button to deploy the contract.

When deploying The contract we see some methods for testing like this way

Now we change user

Here we see 15 users for testing the contract. 1st user is a contract owner. When the contract is deployed that times system gets some gas, That’s why, the 1st user balance is not 100 ether but other users have 100 ether. Now we change 2nd user who creates a ticket for his event.

Here you see createTicket button [2nd red box]. This button on the right side you see an arrow button. Click this button then you see some input field. Fill up this field and then fill up the value field [1st red box] and change currency type. This value field is charge field. When the user creates a ticket, the contract owner gets some commission. Now click createTicket button

We successfully create ticket. Our 1st ticket tokenId is 0.

You already notice that the 2nd user balance is decreasing for creating tickets and the 1st user balance is increasing for getting a commission.

Now we are changing the user. Here 3rd user is purchasing a ticket. Let's do it.

Here 3rd box is purchaseTicket method button. With this button on the right side you see an arrow, click this arrow then you can see two input fields. Fill up this input field. Here we buy 2 Tickets and tokenID is 0.

In the 2nd red box, we set value 3. Because we buy 2 tickets and each ticket price is 1eth and the commission is 1 eth. so the total price is 3 eth. Now click purchaseTicket button.

Now, we can see purchase ticket information.

Here 1st user is the contract owner, 2nd user is the ticket owner and 3rd user is purchasing a ticket. When a ticket is created or sold the 1st user gets a commission. 2nd user is the Event owner, when a ticket is sold, 2nd user gets the selling price.

Now it’s time to deploy it on a live Ethereum network or Testnets network and share it with the world!

Happy coding! 🚀

--

--

syed kamruzzaman
Coinmonks

I'm a Frontend and Backend developer with a passion for PHP and JavaScript, along with expertise in JavaScript-related libraries.