Smart Contract
TixologiTickets
A.09e8665388e90671.TixologiTickets
1/*
2 Description: Central Smart Contract for Tixologi Tickets
3
4 This smart contract contains the core functionality for
5 Tixologi Tickets, created by Tixologi
6
7 The contract manages the data associated with all the ticket types and events
8 that are used as templates for the Tickets NFTs
9
10 When a new ticket type wants to be added to the records, an Admin creates
11 a new ticket type struct that is stored in the smart contract.
12
13 Then an Admin can create new Events. Event consist of a public struct that
14 contains public information about a event, and a private resource used
15 to mint new tickets based off of ticket types that have been linked to the Event.
16
17 The admin resource has the power to do all of the important actions
18 in the smart contract. When admins want to call functions in a event,
19 they call their borrowEvent function to get a reference
20 to a event in the contract. Then, they can call functions on the event using that reference.
21
22 In this way, the smart contract and its defined resources interact
23 with great teamwork.
24
25 When tickets are minted, they are initialized with a TicketData struct and
26 are returned by the minter.
27
28 The contract also defines a Collection resource. This is an object that
29 every TixologiTicket owner will store in their account
30 to manage their NFT collection.
31
32 The main TixologiTicket account will also have its own Ticket collections
33 it can use to hold its own tickets that have not yet been sent to a user.
34
35 Note: All state changing functions will panic if an invalid argument is
36 provided or one of its pre-conditions or post conditions aren't met.
37 Functions that don't modify state will simply return 0 or nil
38 and those cases need to be handled by the caller.
39
40*/
41
42import NonFungibleToken from 0x1d7e57aa55817448
43import MetadataViews from 0x1d7e57aa55817448
44import ViewResolver from 0x1d7e57aa55817448
45
46access(all) contract TixologiTickets: NonFungibleToken {
47
48 //access(all) entitlement NFTMinter
49 // -----------------------------------------------------------------------
50 // TixologiTickets deployment variables
51 // -----------------------------------------------------------------------
52 // The network the contract is deployed on
53 //access(all) view fun Network() : String { return ${NETWORK} }
54
55 // -----------------------------------------------------------------------
56 // Tixologi tickets contract Events
57 // -----------------------------------------------------------------------
58
59 // Emitted when the Tixologi tickets contract is created
60 access(all) event ContractInitialized()
61
62 // Emitted when a new Event struct is created
63 access(all) event EventCreated(eventID: UInt32)
64 // Emitted when a new TicketType struct is created
65 access(all) event TicketTypeCreated(eventID: UInt32, ticketTypeID: UInt32, name: String)
66 // Emitted when a new TicketType is added to an Event
67 access(all) event TicketTypeAddedToEvent(eventID: UInt32, ticketTypeID: UInt32)
68 // Emitted when a TicketType is retired from a Event and cannot be used to mint
69 access(all) event TicketTypeRetiredFromEvent(eventID: UInt32, ticketTypeID: UInt32, numTickets: UInt32)
70 // Emitted when a Event is locked, meaning TicketTypes cannot be added
71 access(all) event EventLocked(eventID: UInt32)
72 // Emitted when an Event is closed, meaning Tickets cannot be minted
73 access(all) event EventClosed(eventID: UInt32)
74 // Emitted when a TicketType is sold, meaning Tickets cannot be minted
75 access(all) event TicketTypeSold(eventID: UInt32, ticketTypeID: UInt32)
76 // Emitted when a Ticket is minted from an TicketType
77 access(all) event TicketMinted(ticketID: UInt64, eventID: UInt32, ticketTypeID: UInt32 , serialNumber: UInt32)
78
79 // Events for Collection-related actions
80 //
81 // Emitted when a Ticket is withdrawn from a Collection
82 access(all) event Withdraw(id: UInt64, from: Address?)
83 // Emitted when a Ticket is deposited into a Collection
84 access(all) event Deposit(id: UInt64, to: Address?)
85 // Emitted when a Ticket is transferred from a Collection
86 access(all) event Transfer(id: UInt64, from: Address?, to: Address?)
87
88 // Emitted when a Ticket is destroyed
89 access(all) event TicketDestroyed(id: UInt64)
90
91
92 // Variable size dictionary of TicketType structs
93 access(self) var ticketTypeDatas: {UInt32: TicketType}
94
95 // Variable size dictionary of Event structs
96 access(self) var eventDatas: {UInt32: EventData}
97
98 // Variable size dictionary of TicketType resources
99 access(self) var events: @{UInt32: Event}
100
101 // The ID that is used to create Events.
102 // Every time a Event is created, eventID is assigned
103 // to the new Event's ID and then is incremented by 1.
104 access(all) var totalEvents: UInt32
105
106 // The ID that is used to create TicketTypes. Every time a TicketTypes is created
107 // ticketTypeID is assigned to the new ticketType's ID and then is incremented by 1.
108 access(all) var totalTicketTypes: UInt32
109
110 // The total number of Tickets NFTs that have been created
111 // Because NFTs can be destroyed, it doesn't necessarily mean that this
112 // reflects the total number of NFTs in existence, just the number that
113 // have been minted to date. Also used as global ticket IDs for minting.
114 access(all) var totalSupply: UInt64
115
116 // -----------------------------------------------------------------------
117 // Tixologi contract-level Composite Type definitions
118 // -----------------------------------------------------------------------
119 // These are just *definitions* for Types that this contract
120 // and other accounts can use. These definitions do not contain
121 // actual stored values, but an instance (or object) of one of these Types
122 // can be created by this contract that contains stored values.
123 // -----------------------------------------------------------------------
124
125 // Event is a Struct that holds metadata associated
126 // with specific information : like name, venue, event_type,
127 // Ray Allen hit the 3 to tie the Heat and Spurs in the 2013 finals game 6
128 // or when Lance Stephenson blew in the ear of Lebron James.
129 //
130 // Tickets NFTs will all reference a single Event and a single TicketType.
131 // The Tickets are publicly accessible, so anyone can
132 // read the metadata associated with a specific Ticket ID
133 //
134 access(all) struct EventData {
135
136 // The unique ID for the Event
137 access(all) let eventID: UInt32
138 access(all) let name : String
139 access(all) let startTime : String?
140 access(all) let venue : String?
141 access(all) let eventType : String?
142 access(all) let timeZone : Int?
143 access(all) let gateTime : String?
144 access(all) let image : String?
145 access(all) let description : String?
146
147 init(
148 eventID: UInt32,
149 name: String,
150 startTime : String?,
151 venue: String?,
152 eventType: String?,
153 timeZone: Int?,
154 gateTime : String?,
155 image: String?,
156 description: String?
157 ) {
158 pre {
159 eventID != 0: "Event ID cannot be 0"
160 }
161 self.eventID = eventID
162 self.name = name
163 self.venue = venue
164 self.startTime = startTime
165 self.eventType = eventType
166 self.timeZone = timeZone
167 self.gateTime = gateTime
168 self.image = image
169 self.description = description
170 }
171 }
172
173 // A TicketType contains details for Ticket Categories
174 // that make up a related group of collectibles.
175 // TicketTypeData is a struct that is stored in a field of the contract.
176 // Anyone can query the constant information
177 // about a set by calling various getters located
178 // at the end of the contract. Only the admin has the ability
179 // to modify any data in the private Set resource.
180 //
181 access(all) struct TicketType {
182
183 // Unique ID for the TicketType
184 access(all) let ticketTypeID: UInt32
185
186 // EventID to which the ticket belongs
187 access(all) let eventID: UInt32
188
189 // Name of the TicketType
190 // ex. VIP, GA, etc.
191 access(all) let name: String
192
193 // Initial price of the ticket type in primary market
194 access(all) let initialPrice : UFix64
195
196 // Maximum number of tickets that can exist with this ticket type
197 access(all) let totalAmount : UInt32
198
199 // Image for this TicketType
200 access(all) let image : String?
201
202 // Description of the ticket type
203 access(all) let description : String?
204
205 init(
206 ticketTypeID: UInt32,
207 eventID: UInt32,
208 name: String,
209 initialPrice : UFix64,
210 totalAmount : UInt32,
211 image : String?,
212 description : String?
213 ) {
214
215 pre {
216 ticketTypeID != 0: "TicketTypeID cannot be 0"
217 eventID != 0: "EventID cannot be 0"
218 }
219 self.ticketTypeID = ticketTypeID
220 self.eventID = eventID
221 self.name = name
222 self.initialPrice = initialPrice
223 self.totalAmount = totalAmount
224 self.image = image
225 self.description = description
226 }
227 }
228
229 // Event is a resource type that contains the functions to add and remove
230 // TicketTypes from an event and mint Tickets.
231 //
232 // It is stored in a private field in the contract so that
233 // the admin resource can call its methods.
234 //
235 // The admin can add TicketTypes to a Event so that the event can mint Tickets
236 // that reference that ticket type data.
237 // The Tickets that are minted by a Event will be listed as belonging to
238 // the Event that minted it, as well as the TicketType it references.
239 //
240 // Admin can also retire TicketTypes from the Events, meaning that the retired
241 // TicketType can no longer have Tickets minted from it.
242 //
243 // If the admin locks the Event, no more Plays can be added to it, but
244 // Tickets can still be minted.
245 //
246 // If an Event is closed, no more tickets can be minted.
247 //
248 // If retireAll() and lock() are called back-to-back,
249 // the Set is closed off forever and nothing more can be done with it.
250 access(all) resource Event {
251
252 // Unique ID for the Event
253 access(all) let eventID: UInt32
254
255 // Array of TicketTypes that are a part of this Event.
256 // When a TicketType is added to the Event, its ID gets appended here.
257 // The ID does not get removed from this array when a TicketType is retired.
258 access(contract) var ticketTypes: [UInt32]
259
260 // Map of TicketType IDs that Indicates if a TicketType in this Event can be minted.
261 // When a TicketType is added to a Event, it is mapped to false (not retired).
262 // When a TicketType is retired, this is set to true and cannot be changed.
263 access(contract) var retired: {UInt32: Bool}
264
265 // Indicates if the Event is currently locked.
266 // When a Event is created, it is unlocked
267 // and TicketTypes are allowed to be added to it.
268 // When a event is locked, TicketTypes cannot be added.
269 // A Event can never be changed from locked to unlocked,
270 // the decision to lock a Event it is final.
271 // If a Event is locked, TicketTypes cannot be added, but
272 // Tickets can still be minted from TicketTypes
273 // that exist in the Event.
274 access(all) var locked: Bool
275
276 // Mapping of TicketTypes IDs that indicates the number of Tickets
277 // that have been minted for specific TicketTypes in this Event.
278 // When a Ticket is minted, this value is stored in the Ticket to
279 // show its place in the Event, eg. 13 of 60.
280 access(contract) var numberMintedPerTicketType: {UInt32: UInt32}
281
282 init(
283 eventID: UInt32,
284 name: String,
285 startTime : String?,
286 venue: String?,
287 eventType: String?,
288 timeZone: Int?,
289 gateTime : String?,
290 image: String?,
291 description: String?
292 ) {
293 self.eventID = eventID
294 self.ticketTypes = []
295 self.retired = {}
296 self.locked = false
297 self.numberMintedPerTicketType = {}
298
299 // Create a new EventData for this Event and store it in contract storage
300 TixologiTickets.eventDatas[self.eventID] = EventData(eventID: eventID, name: name, startTime : startTime, venue: venue, eventType: eventType, timeZone: timeZone, gateTime : gateTime, image: image, description: description)
301 }
302
303 // addTicketType adds a ticketType to the event
304 //
305 // Parameters: ticketTypeID: The ID of the ticketType that is being added
306 //
307 // Pre-Conditions:
308 // The TicketType needs to be an existing TicketType
309 // The Event needs to be not locked
310 // The TicketType can't have already been added to the Event
311 //
312 access(all) fun addTicketType(ticketTypeID: UInt32) {
313 pre {
314 TixologiTickets.ticketTypeDatas[ticketTypeID] != nil: "Cannot add the TicketType to Event: TicketType doesn't exist."
315 !self.locked: "Cannot add the ticketType to the Event after the set has been locked."
316 self.numberMintedPerTicketType[ticketTypeID] == nil: "The TicketType has already beed added to the event."
317 }
318
319 // Add the TicketType to the array of TicketTypes
320 self.ticketTypes.append(ticketTypeID)
321
322 // Open the TicketType up for minting
323 self.retired[ticketTypeID] = false
324
325 // Initialize the Tickets count to zero
326 self.numberMintedPerTicketType[ticketTypeID] = 0
327
328 emit TicketTypeAddedToEvent(eventID: self.eventID, ticketTypeID: ticketTypeID)
329 }
330
331 // addTicketTypes adds multiple TicketTypes to the Event
332 //
333 // Parameters: ticketTypesIDs: The IDs of the TicketTypes that are being added
334 // as an array
335 //
336 access(all) fun addTicketTypes(ticketTypeIDs: [UInt32]) {
337 for ticketType in ticketTypeIDs {
338 self.addTicketType(ticketTypeID: ticketType)
339 }
340 }
341
342 // retireTicketType retires a TicketType from the Evebt so that it can't mint new Tickets
343 //
344 // Parameters: ticketTypeID: The ID of the TicketType that is being retired
345 //
346 // Pre-Conditions:
347 // The TicketType is part of the Event and not retired (available for minting).
348 //
349 access(all) fun retireTicketType(ticketTypeID: UInt32) {
350 pre {
351 self.retired[ticketTypeID] != nil: "Cannot retire the TicketType: TicketType doesn't exist in this event!"
352 }
353
354 if !self.retired[ticketTypeID]! {
355 self.retired[ticketTypeID] = true
356
357 emit TicketTypeRetiredFromEvent(eventID: self.eventID, ticketTypeID: ticketTypeID, numTickets: self.numberMintedPerTicketType[ticketTypeID]!)
358 }
359 }
360
361 // retireAll retires all the ticketTypes in the Event
362 // Afterwards, none of the retired TicketTypes will be able to mint new Tickets
363 //
364 access(all) fun retireAll() {
365 for ticketType in self.ticketTypes {
366 self.retireTicketType(ticketTypeID: ticketType)
367 }
368 }
369
370 // mintTicket mints a new Ticket and returns the newly minted Ticket
371 //
372 // Parameters: ticketTypeID: The ID of the TicketType that the Ticket references
373 //
374 // Pre-Conditions:
375 // The TicketType must exist in the Event and be allowed to mint new Tickets
376 //
377 // Returns: The NFT that was minted
378 //
379 access(all) fun mintTicket(ticketTypeID: UInt32, metadata: String?): @NFT {
380 pre {
381 self.retired[ticketTypeID] != nil: "Cannot mint the ticket: This TicketType doesn't exist."
382 !self.retired[ticketTypeID]!: "Cannot mint the ticket from this ticketType: This ticketType has been retired."
383 }
384
385 // Gets the number of Tickets that have been minted for this TicketType
386 // to use as this Ticket's serial number
387 let numInTicketType = self.numberMintedPerTicketType[ticketTypeID]!
388
389 // Mint the new ticket
390 let newTicket: @NFT <- create NFT(serialNumber: numInTicketType + UInt32(1),
391 ticketTypeID: ticketTypeID,
392 eventID: self.eventID,
393 metadata: metadata)
394
395 // Increment the count of Tickets minted for this TicketType
396 self.numberMintedPerTicketType[ticketTypeID] = numInTicketType + UInt32(1)
397
398 return <-newTicket
399 }
400
401 // batchMintTicket mints an arbitrary quantity of Tickets
402 // and returns them as a Collection
403 //
404 // Parameters: ticketTypeID: the ID of the ticketType that the Tickets are minted for
405 // quantity: The quantity of Tickets to be minted
406 //
407 // Returns: Collection object that contains all the Tickets that were minted
408 //
409 access(all) fun batchMintTicket(ticketTypeID: UInt32, metadatas:[String?], quantity: UInt64): @Collection {
410 let newCollection <- create Collection()
411
412 var i: UInt64 = 0
413 while i < quantity {
414 newCollection.deposit(token: <-self.mintTicket(ticketTypeID: ticketTypeID, metadata: metadatas[i]))
415 i = i + 1
416 }
417
418 return <-newCollection
419 }
420
421 access(all) fun getTicketTypes(): [UInt32] {
422 return self.ticketTypes
423 }
424
425 access(all) fun getRetired(): {UInt32: Bool} {
426 return self.retired
427 }
428
429 access(all) fun getNumMintedPerTicketType(): {UInt32: UInt32} {
430 return self.numberMintedPerTicketType
431 }
432 }
433
434 // Struct that contains all of the important data about a event
435 // Can be easily queried by instantiating the `QueryEventData` object
436 // with the desired event ID
437 // let eventData = TixologiTickets.QueryEventData(eventID: 12)
438 //
439 access(all) struct QueryEventData {
440 access(all) let eventID: UInt32
441 access(all) let name: String
442 access(self) var ticketTypes: [UInt32]
443 access(self) var retired: {UInt32: Bool}
444 access(all) var locked: Bool
445 access(self) var numberMintedPerTicketType: {UInt32: UInt32}
446
447 init(eventID: UInt32) {
448 pre {
449 TixologiTickets.events[eventID] != nil: "The event with the provided ID does not exist"
450 }
451
452
453 let tixoEvent = (&TixologiTickets.events[eventID] as &TixologiTickets.Event?)!
454 let eventData = TixologiTickets.eventDatas[eventID]!
455
456 self.eventID = eventID
457 self.name = eventData.name
458 self.ticketTypes = tixoEvent.getTicketTypes()
459 self.retired = tixoEvent.getRetired()
460 self.locked = tixoEvent.locked
461 self.numberMintedPerTicketType = tixoEvent.getNumMintedPerTicketType()
462 }
463
464 access(all) fun getTicketTypes(): [UInt32] {
465 return self.ticketTypes
466 }
467
468 access(all) fun getRetired(): {UInt32: Bool} {
469 return self.retired
470 }
471
472 access(all) fun getNumberMintedPerTicketType(): {UInt32: UInt32} {
473 return self.numberMintedPerTicketType
474 }
475 }
476
477 access(all) struct TicketData {
478
479 // The ID of the Event that the Ticket comes from
480 access(all) let eventID: UInt32
481
482 // The ID of the TicketType that the Ticket references
483 access(all) let ticketTypeID: UInt32
484
485 // The place in the edition that this Ticket was minted
486 // Otherwise know as the serial number
487 access(all) let serialNumber: UInt32
488
489 // Metadata regarding the ticket itself
490 // e.g : seat, row, etc.
491 access(all) let metadata : String?
492
493 init(eventID: UInt32, ticketTypeID: UInt32, serialNumber: UInt32, metadata: String?) {
494 self.eventID = eventID
495 self.ticketTypeID = ticketTypeID
496 self.serialNumber = serialNumber
497 self.metadata = metadata
498 }
499
500 }
501
502 // This is an implementation of a custom metadata view for Tixologi Ticket.
503 // This view contains the ticket metadata.
504 //
505 access(all) struct TixologiTicketMetadataView {
506
507 access(all) let eventName: String?
508 access(all) let ticketTypeName: String?
509 access(all) let venue: String?
510 access(all) let startDate: String?
511 access(all) let serialNumber: UInt32
512 access(all) let ticketTypeID: UInt32
513 access(all) let eventID: UInt32
514 access(all) let numTicketsInTicketType: UInt32?
515
516 init(
517 eventName: String?,
518 ticketTypeName: String?,
519 venue: String?,
520 startDate: String?,
521 serialNumber: UInt32,
522 ticketTypeID: UInt32,
523 eventID: UInt32,
524 numTicketsInTicketType: UInt32?
525 ) {
526 self.eventName = eventName
527 self.ticketTypeName = ticketTypeName
528 self.venue = venue
529 self.startDate = startDate
530 self.serialNumber = serialNumber
531 self.ticketTypeID = ticketTypeID
532 self.eventID = eventID
533 self.numTicketsInTicketType = numTicketsInTicketType
534 }
535 }
536
537 // The resource that represents the Ticket NFTs
538 //
539 access(all) resource NFT: NonFungibleToken.NFT {
540
541 // Global unique ticket ID
542 access(all) let id: UInt64
543
544 // Struct of Ticket metadata
545 access(all) let data: TicketData
546
547 init(serialNumber: UInt32, ticketTypeID: UInt32, eventID: UInt32, metadata: String?) {
548 // Increment the global Ticket IDs
549 TixologiTickets.totalSupply = TixologiTickets.totalSupply + UInt64(1)
550
551 self.id = TixologiTickets.totalSupply
552
553 // Set the metadata struct
554 self.data = TicketData(eventID: eventID, ticketTypeID: ticketTypeID, serialNumber: serialNumber, metadata: metadata)
555
556 emit TicketMinted(ticketID: self.id, eventID: self.data.eventID, ticketTypeID: self.data.ticketTypeID, serialNumber: self.data.serialNumber)
557 }
558
559 access(all) fun name(): String {
560 let ticketTypeName: String = TixologiTickets.getTicketTypeName(ticketTypeID: self.data.ticketTypeID) ?? ""
561 let eventName: String = TixologiTickets.getEventName(eventID: self.data.eventID) ?? ""
562 return eventName
563 .concat(" ")
564 .concat(ticketTypeName)
565 }
566
567 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
568 return <- TixologiTickets.createEmptyCollection(nftType: Type<@TixologiTickets.NFT>())
569 }
570
571 access(all) fun description(): String {
572 let eventName: String = TixologiTickets.getEventName(eventID: self.data.eventID) ?? ""
573 let ticketTypeName: String = TixologiTickets.getTicketTypeName(ticketTypeID: self.data.ticketTypeID) ?? ""
574 let serialNumber: String = self.data.serialNumber.toString()
575 return " A ticketType "
576 .concat(ticketTypeName)
577 .concat("From event ")
578 .concat(eventName)
579 .concat(" ticket with serial number ")
580 .concat(serialNumber)
581 }
582
583 access(all) view fun getViews(): [Type] {
584 return [
585 Type<MetadataViews.Display>(),
586 Type<TixologiTicketMetadataView>(),
587 Type<MetadataViews.Royalties>(),
588 Type<MetadataViews.Editions>(),
589 Type<MetadataViews.ExternalURL>(),
590 Type<MetadataViews.NFTCollectionData>(),
591 Type<MetadataViews.NFTCollectionDisplay>(),
592 Type<MetadataViews.Serial>(),
593 Type<MetadataViews.Traits>(),
594 Type<MetadataViews.Medias>()
595 ]
596 }
597
598 access(all) fun resolveView(_ view: Type): AnyStruct? {
599 switch view {
600 case Type<MetadataViews.Display>():
601 return MetadataViews.Display(
602 name: self.name(),
603 description: self.description(),
604 thumbnail: MetadataViews.HTTPFile(url:"https://ipfs.dapperlabs.com/ipfs/Qmbdj1agtbzpPWZ81wCGaDiMKRFaRN3TU6cfztVCu6nh4o")
605 )
606 case Type<TixologiTicketMetadataView>():
607 return TixologiTicketMetadataView(
608 eventName: TixologiTickets.getEventName(eventID: self.data.eventID),
609 ticketTypeName: TixologiTickets.getTicketTypeName(ticketTypeID: self.data.ticketTypeID),
610 venue: TixologiTickets.getEventVenue(eventID: self.data.eventID),
611 startDate: TixologiTickets.getEventStartDate(eventID: self.data.eventID),
612 serialNumber: self.data.serialNumber,
613 ticketTypeID: self.data.ticketTypeID,
614 eventID: self.data.eventID,
615 numTicketsInTicketType: TixologiTickets.getNumTicketsInTicketType(eventID: self.data.eventID, ticketTypeID: self.data.ticketTypeID)
616 )
617 }
618
619 return nil
620 }
621 }
622
623 // Admin is a special authorization resource that
624 // allows the owner to perform important functions to modify the
625 // various aspects of the TicketTypes, Events, and Tickets
626 //
627 access(all) resource Admin {
628
629 // createTicketType creates a new TicketType struct
630 // and stores it in the TicketTypes dictionary in the TixologiTickets smart contract
631 //
632 // Parameters: ticketTypeID : The ticketTypeID
633 // eventID : The eventID
634 // name : name of the ticketType, examples {"Vip", "GA", "VIP ONLY MEMBERS"}
635 // initialPrice : initial price of tickets for the primary market
636 // image: an URL for an image of that ticket type
637 // description: a description for the ticketType. example {"ticket type created by manu member of Tixologi for launch party event"}
638 //
639 // Returns: the ID of the new TicketType object
640 //
641 access(all) fun createTicketType(ticketTypeID: UInt32, eventID: UInt32, name: String, initialPrice: UFix64, totalAmount: UInt32, image: String?, description: String?): UInt32 {
642 // Create the new TicketType
643 var newTicketType = TicketType(ticketTypeID: ticketTypeID, eventID: eventID, name: name, initialPrice: initialPrice, totalAmount: totalAmount, image: image, description: description)
644 let newID = newTicketType.ticketTypeID
645
646 // Increment the ID so that it isn't used again
647 TixologiTickets.totalTicketTypes = TixologiTickets.totalTicketTypes + UInt32(1)
648
649 emit TicketTypeCreated(eventID: newTicketType.eventID, ticketTypeID: newTicketType.ticketTypeID, name: newTicketType.name)
650
651 // Store it in the contract storage
652 TixologiTickets.ticketTypeDatas[newID] = newTicketType
653
654 return newID
655 }
656
657 // createEvent creates a new Event resource and stores it
658 // in the event mapping in the TixologiTickets contract
659 //
660 // Parameters: eventID : the id of the event
661 // name: The name of the Event
662 // startTime : Start time of the event
663 // venue : The venue where the event will take place. example {"Madison Square Garden"}
664 // eventType : The type of the event example {Sports}
665 // timeZone : timeZone of the event
666 // gateTime : the time the gates will open
667 // image : an image for the event (URL)
668 // description : description of the event. example {NBA finals between Lakers and Golden States}
669 //
670 // Returns: The ID of the created event
671 access(all) fun createEvent(eventID: UInt32, name: String, startTime: String, venue: String, eventType: String, timeZone : Int, gateTime: String, image: String?, description: String?): UInt32 {
672
673 // Create the new Event
674 var newEvent <- create Event(eventID: eventID, name: name, startTime: startTime, venue: venue, eventType: eventType, timeZone: timeZone, gateTime: gateTime, image: image, description: description)
675
676 // Increment the eventID
677 TixologiTickets.totalEvents = TixologiTickets.totalEvents + UInt32(1)
678
679 let newID = newEvent.eventID
680
681 emit EventCreated(eventID: newEvent.eventID)
682 // Store it in the events mapping field
683 TixologiTickets.events[newID] <-! newEvent
684
685
686 return newID
687 }
688
689 // borrowEvent returns a reference to a event in the TixologiTickets
690 // contract so that the admin can call methods on it
691 //
692 // Parameters: eventID: The ID of the Event that you want to
693 // get a reference to
694 //
695 // Returns: A reference to the Event with all of the fields
696 // and methods exposed
697 //
698 access(all) fun borrowEvent(eventID: UInt32): &Event {
699 pre {
700 TixologiTickets.events[eventID] != nil: "Cannot borrow Event: Event doesn't exist"
701 }
702
703 // Get a reference to the event and return it
704 // use `&` to indicate the reference to the object and type
705 return (&TixologiTickets.events[eventID])!
706 }
707
708 // createNewAdmin creates a new Admin resource
709 //
710 access(all) fun createNewAdmin(): @Admin {
711 return <-create Admin()
712 }
713 }
714
715 // This is the interface that users can cast their Ticket Collection as
716 // to allow others to deposit Tickets into their Collection. It also allows for reading
717 // the IDs of Tickets in the Collection.
718 access(all) resource interface TixologiTicketsCollectionPublic {
719 access(all) fun deposit(token: @{NonFungibleToken.NFT})
720 access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection})
721 access(all) fun getIDs(): [UInt64]
722 access(all) fun borrowTicket(id: UInt64): &TixologiTickets.NFT {
723 // If the result isn't nil, the id of the returned reference
724 // should be the same as the argument to the function
725 post {
726 (result == nil) || (result.id == id):
727 "Cannot borrow Ticket reference: The ID of the returned reference is incorrect"
728 }
729 }
730 }
731
732 // Collection is a resource that every user who owns NFTs
733 // will store in their account to manage their NFTS
734 //
735 access(all) resource Collection: TixologiTicketsCollectionPublic, NonFungibleToken.Collection {
736 // Dictionary of Moment conforming tokens
737 // NFT is a resource type with a UInt64 ID field
738 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
739
740 init() {
741 self.ownedNFTs <- {}
742 }
743
744 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
745 let supportedTypes: {Type: Bool} = {}
746 supportedTypes[Type<@TixologiTickets.NFT>()] = true
747 return supportedTypes
748 }
749
750 // Return whether or not the given type is accepted by the collection
751 // A collection that can accept any type should just return true by default
752 access(all) view fun isSupportedNFTType(type: Type): Bool {
753 if type == Type<@TixologiTickets.NFT>() {
754 return true
755 }
756 return false
757 }
758
759 // Return the amount of NFTs stored in the collection
760 access(all) view fun getLength(): Int {
761 return self.ownedNFTs.keys.length
762 }
763
764 // Create an empty Collection for TixologiTickets NFTs and return it to the caller
765 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
766 return <- TixologiTickets.createEmptyCollection(nftType: Type<@TixologiTickets.NFT>())
767 }
768
769
770 // withdraw removes an Ticket from the Collection and moves it to the caller
771 //
772 // Parameters: withdrawID: The ID of the NFT
773 // that is to be removed from the Collection
774 //
775 // returns: @NonFungibleToken.NFT the token that was withdrawn
776 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
777 // Borrow nft and check if locked
778 let nft = self.borrowNFT(withdrawID)
779 ?? panic("Cannot borrow: empty reference")
780 // Remove the nft from the Collection
781 let token <- self.ownedNFTs.remove(key: withdrawID)
782 ?? panic("Cannot withdraw: Ticket does not exist in the collection")
783
784 emit Withdraw(id: token.id, from: self.owner?.address)
785
786 // Return the withdrawn token
787 return <-token
788 }
789
790 // batchWithdraw withdraws multiple tokens and returns them as a Collection
791 //
792 // Parameters: ids: An array of IDs to withdraw
793 //
794 // Returns: @NonFungibleToken.Collection: A collection that contains
795 // the withdrawn moments
796 //
797 access(NonFungibleToken.Withdraw) fun batchWithdraw(ids: [UInt64]): @{NonFungibleToken.Collection} {
798 // Create a new empty Collection
799 var batchCollection <- create Collection()
800
801 // Iterate through the ids and withdraw them from the Collection
802 for id in ids {
803 batchCollection.deposit(token: <-self.withdraw(withdrawID: id))
804 }
805
806 // Return the withdrawn tokens
807 return <-batchCollection
808 }
809
810 // deposit takes a Ticket and adds it to the Collections dictionary
811 //
812 // Paramters: token: the NFT to be deposited in the collection
813 //
814 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
815
816 // Cast the deposited token as a TixologiTickets NFT to make sure
817 // it is the correct type
818 let token <- token as! @TixologiTickets.NFT
819
820 // Get the token's ID
821 let id = token.id
822
823 // Add the new token to the dictionary
824 let oldToken <- self.ownedNFTs[id] <- token
825
826 // Only emit a deposit event if the Collection
827 // is in an account's storage
828 if self.owner?.address != nil {
829 emit Deposit(id: id, to: self.owner?.address)
830 }
831
832 // Destroy the empty old token that was "removed"
833 destroy oldToken
834 }
835
836 // batchDeposit takes a Collection object as an argument
837 // and deposits each contained NFT into this Collection
838 access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection}) {
839
840 // Get an array of the IDs to be deposited
841 let keys = tokens.getIDs()
842
843 // Iterate through the keys in the collection and deposit each one
844 for key in keys {
845 self.deposit(token: <-tokens.withdraw(withdrawID: key))
846 }
847
848 // Destroy the empty Collection
849 destroy tokens
850 }
851
852 // getIDs returns an array of the IDs that are in the Collection
853 access(all) view fun getIDs(): [UInt64] {
854 return self.ownedNFTs.keys
855 }
856
857 // borrowNFT Returns a borrowed reference to a Ticket in the Collection
858 // so that the caller can read its ID
859 //
860 // Parameters: id: The ID of the NFT to get the reference for
861 //
862 // Returns: A reference to the NFT
863 //
864 // Note: This only allows the caller to read the ID of the NFT,
865 // not any tixologi ticket specific data. Please use borrowTicket to
866 // read Ticket data.
867 //
868 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
869 return &self.ownedNFTs[id]
870 }
871
872 // borrowTicket returns a borrowed reference to a Ticket
873 // so that the caller can read data and call methods from it.
874 // They can use this to read its eventID, ticketTypeID, serialNumber,
875 // or any of the eventData or ticketTypeData associated with it by
876 // getting the eventID or ticketTypeID and reading those fields from
877 // the smart contract.
878 //
879 // Parameters: id: The ID of the NFT to get the reference for
880 //
881 // Returns: A reference to the NFT
882 access(all) fun borrowTicket(id: UInt64): &TixologiTickets.NFT {
883 return self.borrowNFT(id) as! &TixologiTickets.NFT
884 }
885
886 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
887 if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
888 return nft as &{ViewResolver.Resolver}
889 }
890 return nil
891 }
892
893 // If a transaction destroys the Collection object,
894 // All the NFTs contained within are also destroyed!
895 //
896 }
897
898 // -----------------------------------------------------------------------
899 // TixologiTickets contract-level function definitions
900 // -----------------------------------------------------------------------
901
902 // createEmptyCollection creates a new, empty Collection object so that
903 // a user can store it in their account storage.
904 // Once they have a Collection in their storage, they are able to receive
905 // Tickets in transactions.
906 //
907 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
908 if nftType != Type<@TixologiTickets.NFT>() {
909 panic("NFT type is not supported")
910 }
911 return <-create TixologiTickets.Collection()
912 }
913
914 // getAllTicketTypes returns all the TicketTypes in tixologi tickets
915 //
916 // Returns: An array of all the TicketTypes that have been created
917 access(all) fun getAllTicketTypes(): [TixologiTickets.TicketType] {
918 return TixologiTickets.ticketTypeDatas.values
919 }
920
921 access(all) fun getTicketTypeName(ticketTypeID: UInt32) : String? {
922 if let ticketType = TixologiTickets.ticketTypeDatas[ticketTypeID] {
923 return ticketType.name
924 } else {
925 return nil
926 }
927 }
928
929 access(all) fun getTicketTypeInitialPrice(ticketTypeID: UInt32) : UFix64? {
930 if let ticketType = TixologiTickets.ticketTypeDatas[ticketTypeID] {
931 return ticketType.initialPrice
932 } else {
933 return nil
934 }
935 }
936
937 access(all) fun getTicketTypeTotalAmount(ticketTypeID: UInt32) : UInt32? {
938 if let ticketType = TixologiTickets.ticketTypeDatas[ticketTypeID] {
939 return ticketType.totalAmount
940 } else {
941 return nil
942 }
943 }
944
945 access(all) fun getTicketTypeImage(ticketTypeID: UInt32) : String? {
946 if let ticketType = TixologiTickets.ticketTypeDatas[ticketTypeID] {
947 return ticketType.image
948 } else {
949 return nil
950 }
951 }
952
953 access(all) fun getTicketTypeDescription(ticketTypeID: UInt32) : String? {
954 if let ticketType = TixologiTickets.ticketTypeDatas[ticketTypeID] {
955 return ticketType.description
956 } else {
957 return nil
958 }
959 }
960
961 // getEventData returns the data that the specified Event
962 // is associated with.
963 //
964 // Parameters: eventID: The id of the Event that is being searched
965 //
966 // Returns: The QueryEventData struct that has all the important information about the Event
967 access(all) fun getEventData(eventID: UInt32): QueryEventData? {
968 if TixologiTickets.events[eventID] == nil {
969 return nil
970 } else {
971 return QueryEventData(eventID: eventID)
972 }
973 }
974
975 // getEventName returns the name that the specified Event
976 // is associated with.
977 //
978 // Parameters: eventID: The id of the Event that is being searched
979 //
980 // Returns: The name of the Event
981 access(all) fun getEventName(eventID: UInt32): String? {
982 // Don't force a revert if the eventID is invalid
983 return TixologiTickets.eventDatas[eventID]?.name
984 }
985
986
987 // getEventVenue returns the venue that the specified Event
988 // is associated with.
989 //
990 // Parameters: eventID: The id of the Event that is being searched
991 //
992 // Returns: The venue of the Event
993 access(all) fun getEventVenue(eventID: UInt32): String? {
994 // Don't force a revert if the eventID is invalid
995 return TixologiTickets.eventDatas[eventID]?.venue
996 }
997
998 // getEventStartDate returns the StartDate that the specified Event
999 // is associated with.
1000 //
1001 // Parameters: eventID: The id of the Event that is being searched
1002 //
1003 // Returns: The StartDate of the Event
1004 access(all) fun getEventStartDate(eventID: UInt32): String? {
1005 // Don't force a revert if the eventID is invalid
1006 return TixologiTickets.eventDatas[eventID]?.startTime
1007 }
1008
1009 // getEventIDsByName returns the IDs that the specified Event name
1010 // is associated with.
1011 //
1012 // Parameters: eventName: The name of the Event that is being searched
1013 //
1014 // Returns: An array of the IDs of the Event if it exists, or nil if doesn't
1015 access(all) fun getEventIDsByName(eventName: String): [UInt32]? {
1016 var eventIDs: [UInt32] = []
1017
1018 // Iterate through all the eventDatas and search for the name
1019 for eventData in TixologiTickets.eventDatas.values {
1020 if eventName == eventData.name {
1021 // If the name is found, return the ID
1022 eventIDs.append(eventData.eventID)
1023 }
1024 }
1025
1026 // If the name isn't found, return nil
1027 // Don't force a revert if the eventName is invalid
1028 if eventIDs.length == 0 {
1029 return nil
1030 } else {
1031 return eventIDs
1032 }
1033 }
1034
1035 // getTicketTypesInEvent returns the list of TicketTypeIDs that are in the Event
1036 //
1037 // Parameters: eventID: The id of the Event that is being searched
1038 //
1039 // Returns: An array of TicketTypeIDs
1040 access(all) fun getTicketTypesInEvent(eventID: UInt32): [UInt32]? {
1041 // Don't force a revert if the eventID is invalid
1042 return TixologiTickets.events[eventID]?.ticketTypes
1043 }
1044
1045 // getNumTicketsInTicketType return the number of Ticket that have been
1046 // minted from a certain TicketType on an event.
1047 //
1048 // Parameters: eventID: The id of the Event that is being searched
1049 // ticketTypeID: The id of the ticketType that is being searched
1050 //
1051 // Returns: The total number of Tickets
1052 // that have been minted from an from a certain TicketType on an event.
1053 access(all) fun getNumTicketsInTicketType(eventID: UInt32, ticketTypeID: UInt32): UInt32? {
1054 if let eventData = self.getEventData(eventID: eventID) {
1055
1056 // Read the numMintedPerTicketType
1057 let amount = eventData.getNumberMintedPerTicketType()[ticketTypeID]
1058
1059 return amount
1060 } else {
1061 // If the set wasn't found return nil
1062 return nil
1063 }
1064 }
1065
1066 //------------------------------------------------------------
1067 // Contract MetadataViews
1068 //------------------------------------------------------------
1069 /// Return the metadata view types available for this contract
1070 ///
1071 access(all) view fun getContractViews(resourceType: Type?): [Type] {
1072 return [
1073 Type<MetadataViews.Display>(),
1074 Type<TixologiTicketMetadataView>()
1075 ]
1076 }
1077
1078 /// Resolve this contract's metadata views
1079 ///
1080 access(all) view fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
1081 return nil
1082 }
1083
1084 // -----------------------------------------------------------------------
1085 // TixologiTickets initialization function
1086 // -----------------------------------------------------------------------
1087 //
1088 init() {
1089 // Initialize contract fields
1090 self.ticketTypeDatas = {}
1091 self.eventDatas = {}
1092 self.events <- {}
1093 self.totalEvents = 0
1094 self.totalTicketTypes = 0
1095 self.totalSupply = 0
1096
1097 // Put a new Collection in storage
1098 self.account.storage.save<@Collection>(<- create Collection(), to: /storage/TixologiTicketsCollection)
1099
1100 let cap = self.account.capabilities.storage.issue<&TixologiTickets.Collection>(/storage/TixologiTicketsCollection)
1101 self.account.capabilities.publish(cap, at: /public/TixologiTicketsCollection)
1102
1103 // Put the Minter in storage
1104 self.account.storage.save<@Admin>(<- create Admin(), to: /storage/TixologiTicketAdmin)
1105
1106 emit ContractInitialized()
1107 }
1108}