← Contracts

Contracts · MIT license

EventRegistry.sol

The Library's community listings of upcoming events.

Download .sol
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.28;
3
4/// @title EventRegistry
5/// @notice A public library of upcoming real-world events. Anyone can list one
6/// (what, where, when), and tokens on the launchpad point at a listing with the
7/// event id "listing:<id>".
8///
9/// Listings are permanent records. The lister can cancel their own listing, and
10/// 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 library
15 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 stream
18 string imageURI; // optional
19 uint64 startsAt; // unix seconds
20 uint64 endsAt; // unix seconds, or 0 if unknown
21 int32 latE6; // microdegrees
22 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 // ----------------------------------------------------------------- list
81
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_LENGTH
90 || bytes(p.url).length > MAX_URI_LENGTH || bytes(p.imageURI).length > MAX_URI_LENGTH
91 ) 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.imageURI
116 })
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 // ------------------------------------------------------------ moderation
130
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 // ----------------------------------------------------------------- views
144
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}