Soulbound NFTs minted automatically after event check-in
Proof of Attendance (POA) NFTs are automatically minted when attendees check in to events, providing permanent, verifiable proof of attendance on the blockchain.
GatePass mints Proof of Attendance NFTs as soulbound tokens (non-transferable) that commemorate event participation. These NFTs serve as digital memorabilia and verifiable credentials for attendance.
The ProofOfAttendance.sol contract implements ERC721 with soulbound mechanics:
contract ProofOfAttendance is ERC721, Ownable, ReentrancyGuard { /// @notice Base URI for token metadata string private _baseTokenURI; /// @notice Counter for minted POA tokens uint256 public tokenCounter = 1; /// @notice Mapping from POA token ID to original ticket ID mapping(uint256 => uint256) public originalTicketId; /// @notice Mapping from original ticket ID to POA token ID mapping(uint256 => uint256) public ticketToPOA; /// @notice Mapping of addresses authorized to mint POAs (event contracts) mapping(address => bool) public authorizedMinters; event POAMinted(address indexed attendee, uint256 indexed poaTokenId, uint256 indexed ticketId); event MinterAuthorized(address indexed minter); event MinterRevoked(address indexed minter);}
/** * @notice Mint a Proof of Attendance NFT * @param attendee Address to mint the POA to * @param ticketId Original ticket ID that was checked in * @return poaTokenId The minted POA token ID */function mintPOA( address attendee, uint256 ticketId) external onlyAuthorizedMinter nonReentrant returns (uint256) { require(attendee != address(0), "Invalid attendee address"); require(ticketToPOA[ticketId] == 0, "POA already minted for this ticket"); uint256 poaTokenId = tokenCounter++; // Store mappings originalTicketId[poaTokenId] = ticketId; ticketToPOA[ticketId] = poaTokenId; // Mint the POA NFT _safeMint(attendee, poaTokenId); emit POAMinted(attendee, poaTokenId, ticketId); return poaTokenId;}
POAs are non-transferable to preserve the integrity of attendance records:
ProofOfAttendance.sol
/** * @notice Override transfer functions to make POAs soulbound (non-transferable) * POAs should stay with the original attendee */function _update( address to, uint256 tokenId, address auth) internal override returns (address) { address from = _ownerOf(tokenId); // Allow minting (from == address(0)) but prevent transfers if (from != address(0) && to != address(0)) { revert("POAs are soulbound and non-transferable"); } return super._update(to, tokenId, auth);}
Soulbound tokens cannot be transferred, sold, or given away, ensuring they permanently represent the original attendee’s participation.
Retrieve POA information for tickets and attendees:
/** * @notice Check if a POA exists for a given ticket ID * @param ticketId Original ticket ID * @return exists True if POA exists * @return poaTokenId The POA token ID (0 if doesn't exist) */function getPOAForTicket(uint256 ticketId) external view returns (bool exists, uint256 poaTokenId) { poaTokenId = ticketToPOA[ticketId]; exists = poaTokenId != 0;}