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