Contracts · MIT license
EventRegistry.sol
The Library's community listings of upcoming events.
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.28;3 4/// @title EventRegistry5/// @notice A public library of upcoming real-world events. Anyone can list one6/// (what, where, when), and tokens on the launchpad point at a listing with the7/// event id "listing:<id>".8///9/// Listings are permanent records. The lister can cancel their own listing, and10/// the owner can hide spam from the app; neither deletes anything.11contract EventRegistry {12 struct ListingParams {13 string title;14 string description; // short pitch, shown in the library15 string category; // "sports", "tech", "space", "music", ...16 string location; // human-readable place, e.g. "SoFi Stadium, Inglewood"17 string url; // optional link to the official page or stream18 string imageURI; // optional19 uint64 startsAt; // unix seconds20 uint64 endsAt; // unix seconds, or 0 if unknown21 int32 latE6; // microdegrees22 int32 lngE6;23 }24 25 struct Listing {26 uint256 id;27 address lister;28 uint64 listedAt;29 uint64 startsAt;30 uint64 endsAt;31 int32 latE6;32 int32 lngE6;33 bool cancelled;34 bool hidden;35 string title;36 string description;37 string category;38 string location;39 string url;40 string imageURI;41 }42 43 uint256 public constant MAX_TITLE_LENGTH = 80;44 uint256 public constant MAX_DESCRIPTION_LENGTH = 280;45 uint256 public constant MAX_CATEGORY_LENGTH = 24;46 uint256 public constant MAX_LOCATION_LENGTH = 80;47 uint256 public constant MAX_URI_LENGTH = 256;48 /// @notice Events can be listed at most this far ahead.49 uint256 public constant MAX_LEAD_TIME = 730 days;50 51 address public owner;52 Listing[] private _listings;53 54 event Listed(uint256 indexed id, address indexed lister, uint64 startsAt, string title);55 event Cancelled(uint256 indexed id);56 event HiddenSet(uint256 indexed id, bool hidden);57 event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);58 59 error BadTitle();60 error BadCategory();61 error TooLong();62 error BadTime();63 error BadCoordinates();64 error UnknownListing();65 error NotLister();66 error NotOwner();67 error ZeroAddress();68 69 modifier onlyOwner() {70 if (msg.sender != owner) revert NotOwner();71 _;72 }73 74 constructor(address owner_) {75 if (owner_ == address(0)) revert ZeroAddress();76 owner = owner_;77 emit OwnershipTransferred(address(0), owner_);78 }79 80 // ----------------------------------------------------------------- list81 82 /// @notice List an upcoming event. It must start in the future.83 function list(ListingParams calldata p) external returns (uint256 id) {84 uint256 titleLen = bytes(p.title).length;85 uint256 categoryLen = bytes(p.category).length;86 if (titleLen == 0 || titleLen > MAX_TITLE_LENGTH) revert BadTitle();87 if (categoryLen == 0 || categoryLen > MAX_CATEGORY_LENGTH) revert BadCategory();88 if (89 bytes(p.description).length > MAX_DESCRIPTION_LENGTH || bytes(p.location).length > MAX_LOCATION_LENGTH90 || bytes(p.url).length > MAX_URI_LENGTH || bytes(p.imageURI).length > MAX_URI_LENGTH91 ) revert TooLong();92 if (p.startsAt <= block.timestamp || p.startsAt > block.timestamp + MAX_LEAD_TIME) revert BadTime();93 if (p.endsAt != 0 && p.endsAt < p.startsAt) revert BadTime();94 if (p.latE6 < -90_000_000 || p.latE6 > 90_000_000 || p.lngE6 < -180_000_000 || p.lngE6 > 180_000_000) {95 revert BadCoordinates();96 }97 98 id = _listings.length;99 _listings.push(100 Listing({101 id: id,102 lister: msg.sender,103 listedAt: uint64(block.timestamp),104 startsAt: p.startsAt,105 endsAt: p.endsAt,106 latE6: p.latE6,107 lngE6: p.lngE6,108 cancelled: false,109 hidden: false,110 title: p.title,111 description: p.description,112 category: p.category,113 location: p.location,114 url: p.url,115 imageURI: p.imageURI116 })117 );118 emit Listed(id, msg.sender, p.startsAt, p.title);119 }120 121 /// @notice The lister can mark their event as cancelled (called off, or listed by mistake).122 function cancel(uint256 id) external {123 Listing storage l = _get(id);124 if (msg.sender != l.lister) revert NotLister();125 l.cancelled = true;126 emit Cancelled(id);127 }128 129 // ------------------------------------------------------------ moderation130 131 /// @notice Hide (or unhide) a listing from the app, e.g. spam. The record stays onchain.132 function setHidden(uint256 id, bool hidden) external onlyOwner {133 _get(id).hidden = hidden;134 emit HiddenSet(id, hidden);135 }136 137 function transferOwnership(address newOwner) external onlyOwner {138 if (newOwner == address(0)) revert ZeroAddress();139 emit OwnershipTransferred(owner, newOwner);140 owner = newOwner;141 }142 143 // ----------------------------------------------------------------- views144 145 function listingCount() external view returns (uint256) {146 return _listings.length;147 }148 149 function getListing(uint256 id) external view returns (Listing memory) {150 return _get(id);151 }152 153 /// @notice Page through listings, oldest first. Returns fewer than `limit` at the end.154 function getListings(uint256 offset, uint256 limit) external view returns (Listing[] memory page) {155 uint256 total = _listings.length;156 if (offset >= total) return new Listing[](0);157 uint256 end = offset + limit > total ? total : offset + limit;158 page = new Listing[](end - offset);159 for (uint256 i = offset; i < end; i++) {160 page[i - offset] = _listings[i];161 }162 }163 164 function _get(uint256 id) internal view returns (Listing storage) {165 if (id >= _listings.length) revert UnknownListing();166 return _listings[id];167 }168}