Solidity - Creating a simple ticketing application

61 Views Asked by At

I'm trying to build a simple ticketing application in which buyers are able to buy a ticket (i.e. NFT) from a seller. Note: I don't have a lot of experience in coding, but starting to understand the concepts within Solidity decently.

For me it makes most sense if the seller creates a contract with max tickets, price etc. from which the buyer is able to mint if the correct amount is transferred (saves on gas fees compared to first minting and then selling). See below code. As you notice, only the deployer is able to change the arguments to list multiple events (i.e. occasions) with different parameters.

My questions relates to the deployment of the contracts as I want different sellers being able to deploy the contract from the frond-end with a simple button and some inputs, after which the buyers are able to mint the tickets/NFTs from these contracts. Is this possible and if yes, how? If someone has a better solution, also fine! :)

Cheers,


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

contract TicketBlock is ERC721 {
    address public owner;
    uint public numOccasions;
    uint public totalSupply;

    struct Occasion {
        uint id;
        string name;
        uint cost;
        uint tickets;
        uint maxTickets;
    }

    mapping(uint => Occasion) occasions;
    mapping(uint => mapping(address => bool)) public hasBought;

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    constructor(string memory _name, string memory _symbol) ERC721 (_name, _symbol) {
        owner = msg.sender;
    }

    function list(string memory _name, uint _cost, uint _maxTickets) public onlyOwner {
            numOccasions++;
            occasions[numOccasions] = Occasion(
                numOccasions,
                _name,
                _cost,
                _maxTickets,
                _maxTickets 
            );
        }

    function mint (uint _id, uint _seat) public payable { 
        require(_id != 0);                              
        require(_id <= numOccasions);                   
        require(msg.value >= occasions[_id].cost);

        occasions[_id].tickets -= 1; // <-- Update ticket account

        hasBought[_id][msg.sender] = true; // <-- Update buying status

        totalSupply++;
        _safeMint(msg.sender,totalSupply);

    }
}

2

There are 2 best solutions below

0
Yilmaz On

you have to implement factory contract logic:

A factory contract is a smart contract that produces other smart contracts. Much like a shoe factory produces shoes to a certain standard, factory contracts will ensure that all of the smart contracts that it produces adhere to certain arbitrary qualities. This is a common pattern that you see many, if not all, large dApps using. For example if you are familiar with Uniswap, they implement this pattern. Every time you are interacting with a Uniswap pool, that pool is actually a smart contract that was generated by the Uniswap factory contract.

an example of factory contract would be:

contract TicketFactory {
    address[] public deployedTickets;
    uint public ticketsCount;

    // args will be the args that Ticket conract's constructor need.
    // person who calls this should be marked as manager
    function createTicket(string memory _name, string memory _symbol) public {
        // msg.sender is the user who tries to create the Ticket
        Ticket newTicketContract = new Ticket(string memory _name, string memory _symbol);
        deployedTickets.push(address(newTicketContract));
        ticketsCount++;
    }

    function getDeployedTicket(uint index) public view returns (address) {
        return deployedTickets[index];
    }

    function getTicketCounts() public view returns (uint) {
        return ticketsCount;
    }
    // you can add more logic
}

in createTicket function you see new keyword. I explained here What is the difference between creating a new solidity contract with and without the `new` keyword?

Creating a new contract with the new keyword deploys and creates a new contract instance. Deploying a contract involves checking whether the sender has provided enough gas to complete the deployment.

To integrate this with a front-end, you would interact with the TicketFactory contract, you import the deployedfactory contract

import factoryTicket from "../ethereumContracts/campaignFactoryInstance";

you should have a form to create a Ticket contract, so after user enters the inputs for the createTicket function parameters, you submit the form. so you should have an onSubmit method and inside

const onSubmit = async (event) => {
  event.preventDefault();

  if (window.ethereum) {
    const connected_account = await window.ethereum.request({
      method: "eth_requestAccounts",
    });
    try {
      setLoading(true);
      setError("");
      await factory.methods
        .createTicket(nameState,symbolState)
        // whenever you send transactions in the browser, metamask will automatically calculate the amount of gas we need to run this function. so we dont need to specify
        .send({
          from: connected_account[0],
        });
      setSuccessMessage("New Ticket successfully created");
      Router.push("/");
    } catch (err) {
      setError(err.message);
    }
    setLoading(false);
  }
};
0
Bouzid Zitouni On

I would prefer a simpler approach, deploy one contract where any seller can list an event, and any buyer can mint a ticket to the selected event. this will save you on gas fees deploying separate contracts for every seller.

The way to do this is simple, just add a "seller" field to the occasion struct:

    struct Occasion {
    uint id;
    string name;
    uint cost;
    uint tickets;
    uint maxTickets;
    address seller;
}

remove onlyOwner from the list function and add code to automatically set the seller as the msg.sender

for the mint function, you can add a line to transfer the event cost to the seller , something like this will get you started:

function mint (uint _id, uint _seat) public payable { 
    require(_id != 0);                              
    require(_id <= numOccasions);                   
    require(msg.value >= occasions[_id].cost);
    address payable receiver = payable(occasions[_id].seller);
    receiver.transfer(msg.value);
    occasions[_id].tickets -= 1; // <-- Update ticket account

    hasBought[_id][msg.sender] = true; // <-- Update buying status

    totalSupply++;
    _safeMint(msg.sender,totalSupply);

}

you can maybe set a fee to be transferred to the contract owner, if you're offering this as a service.