-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproject.sol
More file actions
307 lines (243 loc) · 11.5 KB
/
Copy pathproject.sol
File metadata and controls
307 lines (243 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
import {ERC1155} from "@openzeppelin/contracts@5.2.0/token/ERC1155/ERC1155.sol";
import {ERC1155Burnable} from "@openzeppelin/contracts@5.2.0/token/ERC1155/extensions/ERC1155Burnable.sol";
import {ERC1155Pausable} from "@openzeppelin/contracts@5.2.0/token/ERC1155/extensions/ERC1155Pausable.sol";
import {Ownable} from "@openzeppelin/contracts@5.2.0/access/Ownable.sol";
import {Strings} from "@openzeppelin/contracts@5.2.0/utils/Strings.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract NFTicket is ERC1155, Ownable, ERC1155Pausable, ERC1155Burnable, ReentrancyGuard {
using Strings for uint256;
struct Organizer {
string name;
uint256 stakedAmount;
uint256 totalMinted;
uint256 unsoldCount;
bool exist;
}
struct NFTInfo {
address originalOwner;
address currentOwner;
uint256 lastSalePrice;
uint8 resaleCount;
uint256 batchNum;
}
struct BatchInfo {
uint256 startTokenId;
uint256 endTokenId;
}
// State Variables
uint256 private nextTokenId = 1;
uint256 private batchCounter = 1; // Start batch counter at 1
string public postConcertURI;
bool public metadataBurned;
uint256 public constant MAX_TICKETS_PER_USER = 2;
uint256 public constant MAX_RESALES = 2;
uint256 public constant ROYALTY_PERCENT = 5;
mapping(uint256 => NFTInfo) public nftInfo;
mapping(uint256 => uint256) public nftPrices;
mapping(address => Organizer) public organizers;
mapping(address => uint256) public ticketsBought;
mapping(uint256 => BatchInfo) public batchToTokenRange;
mapping(uint256 => bool) public isBatchMetadataBurned;
mapping(uint256 => string) public batchURIs;
// Events
event MetadataBurned(string newURI);
event OrganizerRegistered(address indexed organizer, string name);
event KYCSuccess(address indexed user);
event PriceSet(uint256 indexed tokenId, uint256 price);
event BatchMinted(uint256 batchNumber, uint256 count, uint256 startTokenId, uint256 endTokenId);
event BatchListed(uint256 indexed batchNum, uint256 price, uint256 startTokenId, uint256 endTokenId);
event BatchMetadataBurned(uint256 indexed batchNum, address indexed burner);
constructor(string memory _initialURI, string memory _postConcertURI)
ERC1155(_initialURI)
Ownable(msg.sender)
{
postConcertURI = _postConcertURI;
}
// Modifiers
modifier onlyOrganizer() {
require(organizers[msg.sender].exist, "Not organizer");
_;
}
// Organizer Functions
function registerOrganizer(string memory _name) external payable {
require(msg.value >= 0.5 ether, "Minimum 0.5 ETH stake required");
require(!organizers[msg.sender].exist, "Already registered");
organizers[msg.sender] = Organizer(_name, msg.value, 0, 0, true);
emit OrganizerRegistered(msg.sender, _name);
}
function withdrawStake() external onlyOrganizer nonReentrant {
Organizer storage org = organizers[msg.sender];
require(org.stakedAmount > 0, "No stake to withdraw");
require(org.unsoldCount == 0, "Still has unsold NFTs"); // New check
uint256 amount = org.stakedAmount;
org.stakedAmount = 0;
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "Transfer failed");
}
// Minting
function mintBatch(uint256 count) external onlyOrganizer {
require(count > 0, "Must mint at least one token");
uint256[] memory ids = new uint256[](count);
uint256[] memory amounts = new uint256[](count);
Organizer storage org = organizers[msg.sender];
org.totalMinted += count;
org.unsoldCount += count;
uint256 currentBatch = batchCounter; // Use the current batch number
uint256 startTokenId = nextTokenId;
uint256 endTokenId = nextTokenId + count - 1;
// Store the batch token range
batchToTokenRange[currentBatch] = BatchInfo({
startTokenId: startTokenId,
endTokenId: endTokenId
});
for (uint256 i = 0; i < count; i++) {
ids[i] = nextTokenId;
amounts[i] = 1;
nftInfo[ids[i]] = NFTInfo({
originalOwner: msg.sender,
currentOwner: msg.sender,
lastSalePrice: 0,
resaleCount: 0,
batchNum: currentBatch // Assign current batch number
});
nextTokenId++;
}
// Increment batch counter for the next batch
batchCounter++;
_mintBatch(msg.sender, ids, amounts, "");
emit BatchMinted(currentBatch, count, startTokenId, endTokenId);
}
// NFT Trading
function buyNFT(uint256 tokenId) external payable {
require(ticketsBought[msg.sender] < MAX_TICKETS_PER_USER, "Ticket limit reached");
require(nftPrices[tokenId] > 0, "NFT not for sale");
require(msg.value == nftPrices[tokenId], "Incorrect ETH amount");
address seller = ownerOf(tokenId);
// Update resaleCount if not the original owner
if (seller != nftInfo[tokenId].originalOwner) {
nftInfo[tokenId].resaleCount += 1;
}
// Update state before transfers
nftInfo[tokenId].lastSalePrice = msg.value;
ticketsBought[msg.sender]++;
nftPrices[tokenId] = 0;
// Handle payments
(bool success, ) = payable(seller).call{value: msg.value}("");
require(success, "Payment failed");
_safeTransferFrom(seller, msg.sender, tokenId, 1, "");
}
function listBatchForSale(uint256 batchNum, uint256 price) external onlyOrganizer {
require(batchNum > 0 && batchNum < batchCounter, "Invalid batch number");
BatchInfo memory batchInfo = batchToTokenRange[batchNum];
uint256 startTokenId = batchInfo.startTokenId;
uint256 endTokenId = batchInfo.endTokenId;
// Iterate through all tokens in the batch
for (uint256 tokenId = startTokenId; tokenId <= endTokenId; tokenId++) {
// Check if the caller is the original owner of this token
require(msg.sender == nftInfo[tokenId].originalOwner, "Not original owner of all tokens");
require(balanceOf(msg.sender, tokenId) == 1, "Not owner of all tokens");
require(nftInfo[tokenId].lastSalePrice == 0, "Some tokens already listed");
// Set the price for this token
nftPrices[tokenId] = price;
// Emit event for this token
emit PriceSet(tokenId, price);
}
// Emit a batch listing event
emit BatchListed(batchNum, price, startTokenId, endTokenId);
}
function listForResale(uint256 tokenId, uint256 price) external {
require(balanceOf(msg.sender, tokenId) == 1, "Not owner");
require(nftInfo[tokenId].resaleCount < MAX_RESALES, "Max resales reached");
require(nftInfo[tokenId].lastSalePrice > 0, "Use listForSale for initial listing");
uint256 maxPrice = (nftInfo[tokenId].lastSalePrice * 110) / 100;
require(price <= maxPrice, "Price exceeds 110% limit");
nftPrices[tokenId] = price;
emit PriceSet(tokenId, price);
}
function burnBatchMetadata(uint256 batchNum) external {
require(batchNum > 0 && batchNum < batchCounter, "Invalid batch number");
require(!isBatchMetadataBurned[batchNum], "Batch metadata already burned");
// Get token range for the batch
BatchInfo memory batchInfo = batchToTokenRange[batchNum];
uint256 startTokenId = batchInfo.startTokenId;
// Check if caller is the original organizer who minted this batch
require(msg.sender == nftInfo[startTokenId].originalOwner, "Only the batch creator can burn its metadata");
// Mark this batch as burned
isBatchMetadataBurned[batchNum] = true;
// Store the new URI in a mapping (you'll need to add this mapping)
batchURIs[batchNum] = postConcertURI;
emit BatchMetadataBurned(batchNum, msg.sender);
}
// View Functions
function ownerOf(uint256 tokenId) public view returns (address) {
require(nftInfo[tokenId].currentOwner != address(0), "Nonexistent token");
return nftInfo[tokenId].currentOwner;
}
function uri(uint256 tokenId) public view override returns (string memory) {
require(exists(tokenId), "Nonexistent token");
uint256 batchNum = nftInfo[tokenId].batchNum;
// If this specific batch has burned metadata, use its custom URI
if (isBatchMetadataBurned[batchNum]) {
return string(abi.encodePacked(batchURIs[batchNum], batchNum.toString(), ".json"));
}
// Otherwise use the global metadata state
string memory base = metadataBurned ? postConcertURI : super.uri(0);
return string(abi.encodePacked(base, batchNum.toString(), ".json"));
}
// New function to get token ID range for a specific batch
function getBatchTokenRange(uint256 batchNum) public view returns (uint256 startTokenId, uint256 endTokenId) {
require(batchNum > 0 && batchNum < batchCounter, "Invalid batch number");
BatchInfo memory info = batchToTokenRange[batchNum];
return (info.startTokenId, info.endTokenId);
}
// New function to get all batch information
function getAllBatchInfo() public view returns (uint256[] memory batchNumbers, uint256[] memory startTokenIds, uint256[] memory endTokenIds) {
uint256 totalBatches = batchCounter - 1;
batchNumbers = new uint256[](totalBatches);
startTokenIds = new uint256[](totalBatches);
endTokenIds = new uint256[](totalBatches);
for (uint256 i = 1; i <= totalBatches; i++) {
batchNumbers[i-1] = i;
BatchInfo memory info = batchToTokenRange[i];
startTokenIds[i-1] = info.startTokenId;
endTokenIds[i-1] = info.endTokenId;
}
return (batchNumbers, startTokenIds, endTokenIds);
}
// Internal Overrides
function _update(
address from,
address to,
uint256[] memory ids,
uint256[] memory values
) internal override(ERC1155, ERC1155Pausable) {
super._update(from, to, ids, values);
for (uint256 i = 0; i < ids.length; i++) {
uint256 tokenId = ids[i];
address originalOwner = nftInfo[tokenId].originalOwner;
// Update unsold count when transferring from original owner
if (from == originalOwner) {
Organizer storage org = organizers[from];
org.unsoldCount -= values[i];
}
// Existing currentOwner updates
if (from == address(0)) {
// Minting: currentOwner already set
} else if (to == address(0)) {
nftInfo[tokenId].currentOwner = address(0);
} else {
nftInfo[tokenId].currentOwner = to;
}
}
}
function _ownerOf(uint256 tokenId) internal view returns (address) {
return balanceOf(nftInfo[tokenId].originalOwner, tokenId) > 0
? nftInfo[tokenId].originalOwner
: address(0);
}
function exists(uint256 tokenId) internal view returns (bool) {
return nftInfo[tokenId].originalOwner != address(0);
}
}