NFT Ticketing Contract

What it does:
Issues event tickets as NFTs that represent verifiable ownership, transfer rules, and on-chain access rights for physical or digital events.

Why it matters:
Prevents ticket fraud and duplication, reduces scalping through programmable transfer rules, enables direct artist–fan relationships, and creates transparent secondary markets.

How it works:

  • Event organizers mint tickets as NFTs with event metadata and seat or access info

  • Buyers purchase tickets directly from the smart contract

  • Ownership of the NFT serves as proof of ticket validity

  • Transfer rules (resale limits, price caps, or lockups) are enforced on-chain

  • Event staff verify entry by checking wallet ownership of the NFT

  • Tickets can be burned or marked as used after entry

  • Integrates with wallets, QR codes, scanners, and event platforms

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

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

/**
 * @title NFTTicketing
 * @author Nam
 * @notice NFT-based ticketing system with anti-scalping controls
 */
contract NFTTicketing is ERC721, Ownable {

    uint256 public nextTokenId;
    uint256 public ticketPrice;
    bool public transferable;
    bool public saleActive;

    mapping(uint256 => bool) public usedTickets;
    mapping(uint256 => string) private ticketMetadata;

    constructor(
        string memory _name,
        string memory _symbol,
        uint256 _ticketPrice,
        bool _transferable
    ) ERC721(_name, _symbol) {
        ticketPrice = _ticketPrice;
        transferable = _transferable;
        saleActive = true;
    }

    // -------------------- EVENTS --------------------

    event TicketMinted(address indexed buyer, uint256 indexed tokenId);
    event TicketUsed(uint256 indexed tokenId);
    event SaleStatusUpdated(bool active);
    event TransferabilityUpdated(bool transferable);

    // -------------------- TICKETING LOGIC --------------------

    function buyTicket(string calldata _metadataURI) external payable {
        require(saleActive, "Sale inactive");
        require(msg.value == ticketPrice, "Incorrect price");

        uint256 tokenId = ++nextTokenId;
        _safeMint(msg.sender, tokenId);
        ticketMetadata[tokenId] = _metadataURI;

        emit TicketMinted(msg.sender, tokenId);
    }

    function markTicketUsed(uint256 _tokenId) external onlyOwner {
        require(_exists(_tokenId), "Invalid ticket");
        require(!usedTickets[_tokenId], "Already used");

        usedTickets[_tokenId] = true;
        emit TicketUsed(_tokenId);
    }

    // -------------------- TRANSFER CONTROLS --------------------

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId,
        uint256 batchSize
    ) internal override {
        super._beforeTokenTransfer(from, to, tokenId, batchSize);

        if (from != address(0) && to != address(0)) {
            require(transferable, "Transfers disabled");
            require(!usedTickets[tokenId], "Used ticket");
        }
    }

    // -------------------- ADMIN CONTROLS --------------------

    function setSaleActive(bool _active) external onlyOwner {
        saleActive = _active;
        emit SaleStatusUpdated(_active);
    }

    function setTransferable(bool _transferable) external onlyOwner {
        transferable = _transferable;
        emit TransferabilityUpdated(_transferable);
    }

    function withdraw() external onlyOwner {
        payable(owner()).transfer(address(this).balance);
    }

    // -------------------- METADATA --------------------

    function tokenURI(uint256 _tokenId) public view override returns (string memory) {
        require(_exists(_tokenId), "Nonexistent token");
        return ticketMetadata[_tokenId];
    }
}