Virtual Event Access Control

What it does:
Controls access to virtual events (livestreams, webinars, workshops, private calls) using on-chain ownership of tokens, NFTs, or paid access passes.

Why it matters:
Eliminates password sharing, prevents unauthorized access, enables flexible pricing models, and gives creators cryptographically verifiable control over who can attend.

How it works:

  • Event creators define access rules (NFT ownership, token balance, or paid passes)

  • Users prove eligibility by connecting their wallet

  • Smart contract verifies access conditions on-chain

  • Access grants are time-bound and revocable if needed

  • Entry status is tracked on-chain for auditing and analytics

  • Supports single-event, season pass, or subscription access

  • Integrates with livestream platforms, Zoom, Discord, or custom frontends

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

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

/**
 * @title VirtualEventAccessControl
 * @author Nam
 * @notice Token-gated and NFT-gated access control for virtual events
 */
contract VirtualEventAccessControl is Ownable {

    struct EventConfig {
        bool active;
        uint256 startTime;
        uint256 endTime;
        address paymentToken; // address(0) for ETH
        uint256 accessPrice;
        address requiredNFT;
        address requiredToken;
        uint256 minTokenBalance;
    }

    uint256 public eventCount;
    mapping(uint256 => EventConfig) public events;
    mapping(uint256 => mapping(address => bool)) public hasAccess;

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

    event EventCreated(uint256 indexed eventId);
    event AccessGranted(uint256 indexed eventId, address indexed user);
    event EventStatusUpdated(uint256 indexed eventId, bool active);

    // -------------------- EVENT SETUP --------------------

    function createEvent(
        uint256 _startTime,
        uint256 _endTime,
        address _paymentToken,
        uint256 _accessPrice,
        address _requiredNFT,
        address _requiredToken,
        uint256 _minTokenBalance
    ) external onlyOwner {
        require(_endTime > _startTime, "Invalid time range");

        eventCount++;
        events[eventCount] = EventConfig({
            active: true,
            startTime: _startTime,
            endTime: _endTime,
            paymentToken: _paymentToken,
            accessPrice: _accessPrice,
            requiredNFT: _requiredNFT,
            requiredToken: _requiredToken,
            minTokenBalance: _minTokenBalance
        });

        emit EventCreated(eventCount);
    }

    // -------------------- ACCESS LOGIC --------------------

    function purchaseAccess(uint256 _eventId) external payable {
        EventConfig memory e = events[_eventId];
        require(e.active, "Event inactive");
        require(block.timestamp  0) {
            if (e.paymentToken == address(0)) {
                require(msg.value == e.accessPrice, "Incorrect ETH amount");
            } else {
                IERC20(e.paymentToken).transferFrom(
                    msg.sender,
                    owner(),
                    e.accessPrice
                );
            }
        }

        hasAccess[_eventId][msg.sender] = true;
        emit AccessGranted(_eventId, msg.sender);
    }

    function checkAccess(uint256 _eventId, address _user) external view returns (bool) {
        EventConfig memory e = events[_eventId];
        if (!e.active) return false;
        if (block.timestamp  e.endTime) return false;

        if (e.requiredNFT != address(0)) {
            if (IERC721(e.requiredNFT).balanceOf(_user) == 0) return false;
        }

        if (e.requiredToken != address(0)) {
            if (IERC20(e.requiredToken).balanceOf(_user) < e.minTokenBalance) return false;
        }

        return hasAccess[_eventId][_user];
    }

    // -------------------- ADMIN --------------------

    function setEventActive(uint256 _eventId, bool _active) external onlyOwner {
        events[_eventId].active = _active;
        emit EventStatusUpdated(_eventId, _active);
    }

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