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