← flow-mainnet0xc8f9…0611Succeededblock 168391034
No position yet0 / 161
FetchingOpeningPositioning
cadence
Narrow session: Code, Call Trace and Values only, read-only. The event log and stepping need a wider viewport.
  • CODE
1 /*
2 Description: Central Smart Contract for NBA TopShot
3
4 This smart contract contains the core functionality for
5 NBA Top Shot, created by Dapper Labs
6
7 The contract manages the data associated with all the plays and sets
8 that are used as templates for the Moment NFTs
9
10 When a new Play wants to be added to the records, an Admin creates
11 a new Play struct that is stored in the smart contract.
12
13 Then an Admin can create new Sets. Sets consist of a public struct that
14 contains public information about a set, and a private resource used
15 to mint new moments based off of plays that have been linked to the Set.
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 set,
19 they call their borrowSet function to get a reference
20 to a set in the contract. Then, they can call functions on the set using that reference.
21
22 In this way, the smart contract and its defined resources interact
23 with great teamwork, just like the Indiana Pacers, the greatest NBA team
24 of all time.
25
26 When moments are minted, they are initialized with a MomentData struct and
27 are returned by the minter.
28
29 The contract also defines a Collection resource. This is an object that
30 every TopShot NFT owner will store in their account
31 to manage their NFT collection.
32
33 The main Top Shot account will also have its own Moment collections
34 it can use to hold its own moments that have not yet been sent to a user.
35
36 Note: All state changing functions will panic if an invalid argument is
37 provided or one of its pre-conditions or post conditions aren't met.
38 Functions that don't modify state will simply return 0 or nil
39 and those cases need to be handled by the caller.
40
41 It is also important to remember that
42 The Golden State Warriors blew a 3-1 lead in the 2016 NBA finals.
43
44 */
45
46 import FungibleToken from 0xf233dcee88fe0abe
47 import NonFungibleToken from 0x1d7e57aa55817448
48 import MetadataViews from 0x1d7e57aa55817448
49 import TopShotLocking from 0x0b2a3299cc857e29
50 import ViewResolver from 0x1d7e57aa55817448
51 import CrossVMMetadataViews from 0x1d7e57aa55817448
52 import TopShotIPFSResolver from 0x0b2a3299cc857e29
53 import EVM from 0xe467b9dd11fa00df
54
55 access(all) contract TopShot: NonFungibleToken {
56 // -----------------------------------------------------------------------
57 // TopShot deployment variables
58 // -----------------------------------------------------------------------
59
60 // The network the contract is deployed on
61 access(all) view fun Network(): String { return "mainnet" }
62
63 // The address to which royalties should be deposited
64 access(all) view fun RoyaltyAddress(): Address { return 0xfaf0cc52c6e3acaf }
65
66 // The path to the Subedition Admin resource belonging to the Account
67 // which the contract is deployed on
68 access(all) view fun SubeditionAdminStoragePath(): StoragePath { return /storage/TopShotSubeditionAdmin}
69
70 // -----------------------------------------------------------------------
71 // TopShot contract Events
72 // -----------------------------------------------------------------------
73
74 // Emitted when a new Play struct is created
75 access(all) event PlayCreated(id: UInt32, metadata: {String: String})
76 // Emitted when an admin sets one metadata field of an existing Play
77 access(all) event PlayMetadataUpdated(id: UInt32, key: String, value: String)
78 // Emitted when a new series has been triggered by an admin
79 access(all) event NewSeriesStarted(newCurrentSeries: UInt32)
80
81 // Events for Set-Related actions
82 //
83 // Emitted when a new Set is created
84 access(all) event SetCreated(setID: UInt32, series: UInt32)
85 // Emitted when a new Play is added to a Set
86 access(all) event PlayAddedToSet(setID: UInt32, playID: UInt32)
87 // Emitted when a Play is retired from a Set and cannot be used to mint
88 access(all) event PlayRetiredFromSet(setID: UInt32, playID: UInt32, numMoments: UInt32)
89 // Emitted when a Set is locked, meaning Plays cannot be added
90 access(all) event SetLocked(setID: UInt32)
91 // Emitted when a Moment is minted from a Set
92 access(all) event MomentMinted(momentID: UInt64, playID: UInt32, setID: UInt32, serialNumber: UInt32, subeditionID: UInt32)
93
94 // Events for Collection-related actions
95 //
96 // Emitted when a moment is withdrawn from a Collection
97 access(all) event Withdraw(id: UInt64, from: Address?)
98 // Emitted when a moment is deposited into a Collection
99 access(all) event Deposit(id: UInt64, to: Address?)
100
101 // Emitted when a Moment is destroyed
102 access(all) event MomentDestroyed(id: UInt64)
103
104 // Emitted when a Subedition is created
105 access(all) event SubeditionCreated(subeditionID: UInt32, name: String, metadata: {String: String})
106
107 // Emitted when a Subedition is linked to the specific Moment
108 access(all) event SubeditionAddedToMoment(momentID: UInt64, subeditionID: UInt32, setID: UInt32, playID: UInt32)
109
110 // -----------------------------------------------------------------------
111 // TopShot contract-level fields.
112 // These contain actual values that are stored in the smart contract.
113 // -----------------------------------------------------------------------
114
115 // Series that this Set belongs to.
116 // Series is a concept that indicates a group of Sets through time.
117 // Many Sets can exist at a time, but only one series.
118 access(all) var currentSeries: UInt32
119
120 // Variable size dictionary of Play structs
121 access(self) var playDatas: {UInt32: Play}
122
123 // Variable size dictionary of SetData structs
124 access(self) var setDatas: {UInt32: SetData}
125
126 // Variable size dictionary of Set resources
127 access(self) var sets: @{UInt32: Set}
128
129 // The ID that is used to create Plays.
130 // Every time a Play is created, playID is assigned
131 // to the new Play's ID and then is incremented by 1.
132 access(all) var nextPlayID: UInt32
133
134 // The ID that is used to create Sets. Every time a Set is created
135 // setID is assigned to the new set's ID and then is incremented by 1.
136 access(all) var nextSetID: UInt32
137
138 // The total number of Top shot Moment NFTs that have been created
139 // Because NFTs can be destroyed, it doesn't necessarily mean that this
140 // reflects the total number of NFTs in existence, just the number that
141 // have been minted to date. Also used as global moment IDs for minting.
142 access(all) var totalSupply: UInt64
143
144 // -----------------------------------------------------------------------
145 // TopShot contract-level Composite Type definitions
146 // -----------------------------------------------------------------------
147 // These are just *definitions* for Types that this contract
148 // and other accounts can use. These definitions do not contain
149 // actual stored values, but an instance (or object) of one of these Types
150 // can be created by this contract that contains stored values.
151 // -----------------------------------------------------------------------
152
153 // Play is a Struct that holds metadata associated
154 // with a specific NBA play, like the legendary moment when
155 // Ray Allen hit the 3 to tie the Heat and Spurs in the 2013 finals game 6
156 // or when Lance Stephenson blew in the ear of Lebron James.
157 //
158 // Moment NFTs will all reference a single play as the owner of
159 // its metadata. The plays are publicly accessible, so anyone can
160 // read the metadata associated with a specific play ID
161 //
162 access(all) struct Play {
163 // The unique ID for the Play
164 access(all) let playID: UInt32
165
166 // Stores all the metadata about the play as a string mapping
167 // This is not the long term way NFT metadata will be stored. It's a temporary
168 // construct while we figure out a better way to do metadata.
169 //
170 access(all) let metadata: {String: String}
171
172 init(metadata: {String: String}) {
173 pre {
174 metadata.length != 0: "New Play metadata cannot be empty"
175 }
176 self.playID = TopShot.nextPlayID
177 self.metadata = metadata
178 }
179
180 /// This function is intended to backfill the Play on blockchain with a more detailed
181 /// description of the Play. The benefit of having the description is that anyone would
182 /// be able to know the story of the Play directly from Flow
183 access(contract) fun updateTagline(tagline: String): UInt32 {
184 self.metadata["Tagline"] = tagline
185
186 TopShot.playDatas[self.playID] = self
187 return self.playID
188 }
189
190 /// Sets one metadata field and writes the Play back into the contract's
191 /// play store. Moments read their metadata through the Play, so the change
192 /// applies to every Moment minted from it.
193 access(contract) fun updateMetadataField(key: String, value: String) {
194 self.metadata[key] = value
195
196 TopShot.playDatas[self.playID] = self
197 }
198 }
199
200 // A Set is a grouping of Plays that have occured in the real world
201 // that make up a related group of collectibles, like sets of baseball
202 // or Magic cards. A Play can exist in multiple different sets.
203 //
204 // SetData is a struct that is stored in a field of the contract.
205 // Anyone can query the constant information
206 // about a set by calling various getters located
207 // at the end of the contract. Only the admin has the ability
208 // to modify any data in the private Set resource.
209 //
210 access(all) struct SetData {
211 // Unique ID for the Set
212 access(all) let setID: UInt32
213
214 // Name of the Set
215 // ex. "Times when the Toronto Raptors choked in the playoffs"
216 access(all) let name: String
217
218 // Series that this Set belongs to.
219 // Series is a concept that indicates a group of Sets through time.
220 // Many Sets can exist at a time, but only one series.
221 access(all) let series: UInt32
222
223 init(name: String) {
224 pre {
225 name.length > 0: "New Set name cannot be empty"
226 }
227 self.setID = TopShot.nextSetID
228 self.name = name
229 self.series = TopShot.currentSeries
230 }
231 }
232
233 // Set is a resource type that contains the functions to add and remove
234 // Plays from a set and mint Moments.
235 //
236 // It is stored in a private field in the contract so that
237 // the admin resource can call its methods.
238 //
239 // The admin can add Plays to a Set so that the set can mint Moments
240 // that reference that playdata.
241 // The Moments that are minted by a Set will be listed as belonging to
242 // the Set that minted it, as well as the Play it references.
243 //
244 // Admin can also retire Plays from the Set, meaning that the retired
245 // Play can no longer have Moments minted from it.
246 //
247 // If the admin locks the Set, no more Plays can be added to it, but
248 // Moments can still be minted.
249 //
250 // If retireAll() and lock() are called back-to-back,
251 // the Set is closed off forever and nothing more can be done with it.
252 access(all) resource Set {
253 // Unique ID for the set
254 access(all) let setID: UInt32
255
256 // Array of plays that are a part of this set.
257 // When a play is added to the set, its ID gets appended here.
258 // The ID does not get removed from this array when a Play is retired.
259 access(contract) var plays: [UInt32]
260
261 // Map of Play IDs that Indicates if a Play in this Set can be minted.
262 // When a Play is added to a Set, it is mapped to false (not retired).
263 // When a Play is retired, this is set to true and cannot be changed.
264 access(contract) var retired: {UInt32: Bool}
265
266 // Indicates if the Set is currently locked.
267 // When a Set is created, it is unlocked
268 // and Plays are allowed to be added to it.
269 // When a set is locked, Plays cannot be added.
270 // A Set can never be changed from locked to unlocked,
271 // the decision to lock a Set it is final.
272 // If a Set is locked, Plays cannot be added, but
273 // Moments can still be minted from Plays
274 // that exist in the Set.
275 access(all) var locked: Bool
276
277 // Mapping of Play IDs that indicates the number of Moments
278 // that have been minted for specific Plays in this Set.
279 // When a Moment is minted, this value is stored in the Moment to
280 // show its place in the Set, eg. 13 of 60.
281 access(contract) var numberMintedPerPlay: {UInt32: UInt32}
282
283 init(name: String) {
284 self.setID = TopShot.nextSetID
285 self.plays = []
286 self.retired = {}
287 self.locked = false
288 self.numberMintedPerPlay = {}
289
290 // Create a new SetData for this Set and store it in contract storage
291 TopShot.setDatas[self.setID] = SetData(name: name)
292 }
293
294 // addPlay adds a play to the set
295 //
296 // Parameters: playID: The ID of the Play that is being added
297 //
298 // Pre-Conditions:
299 // The Play needs to be an existing play
300 // The Set needs to be not locked
301 // The Play can't have already been added to the Set
302 //
303 access(all) fun addPlay(playID: UInt32) {
304 pre {
305 TopShot.playDatas[playID] != nil: "Cannot add the Play to Set: Play doesn't exist."
306 !self.locked: "Cannot add the play to the Set after the set has been locked."
307 self.numberMintedPerPlay[playID] == nil: "The play has already beed added to the set."
308 }
309
310 // Add the Play to the array of Plays
311 self.plays.append(playID)
312
313 // Open the Play up for minting
314 self.retired[playID] = false
315
316 // Initialize the Moment count to zero
317 self.numberMintedPerPlay[playID] = 0
318
319 emit PlayAddedToSet(setID: self.setID, playID: playID)
320 }
321
322 // addPlays adds multiple Plays to the Set
323 //
324 // Parameters: playIDs: The IDs of the Plays that are being added
325 // as an array
326 //
327 access(all) fun addPlays(playIDs: [UInt32]) {
328 for play in playIDs {
329 self.addPlay(playID: play)
330 }
331 }
332
333 // retirePlay retires a Play from the Set so that it can't mint new Moments
334 //
335 // Parameters: playID: The ID of the Play that is being retired
336 //
337 // Pre-Conditions:
338 // The Play is part of the Set and not retired (available for minting).
339 //
340 access(all) fun retirePlay(playID: UInt32) {
341 pre {
342 self.retired[playID] != nil: "Cannot retire the Play: Play doesn't exist in this set!"
343 }
344
345 if !self.retired[playID]! {
346 self.retired[playID] = true
347
348 emit PlayRetiredFromSet(setID: self.setID, playID: playID, numMoments: self.numberMintedPerPlay[playID]!)
349 }
350 }
351
352 // retireAll retires all the plays in the Set
353 // Afterwards, none of the retired Plays will be able to mint new Moments
354 //
355 access(all) fun retireAll() {
356 for play in self.plays {
357 self.retirePlay(playID: play)
358 }
359 }
360
361 // lock() locks the Set so that no more Plays can be added to it
362 //
363 // Pre-Conditions:
364 // The Set should not be locked
365 access(all) fun lock() {
366 if !self.locked {
367 self.locked = true
368 emit SetLocked(setID: self.setID)
369 }
370 }
371
372 // mintMoment mints a new Moment and returns the newly minted Moment
373 //
374 // Parameters: playID: The ID of the Play that the Moment references
375 //
376 // Pre-Conditions:
377 // The Play must exist in the Set and be allowed to mint new Moments
378 //
379 // Returns: The NFT that was minted
380 //
381 access(all) fun mintMoment(playID: UInt32): @NFT {
382 pre {
383 self.retired[playID] != nil: "Cannot mint the moment: This play doesn't exist."
384 !self.retired[playID]!: "Cannot mint the moment from this play: This play has been retired."
385 }
386
387 // Gets the number of Moments that have been minted for this Play
388 // to use as this Moment's serial number
389 let numInPlay = self.numberMintedPerPlay[playID]!
390
391 // Mint the new moment
392 let newMoment: @NFT <- create NFT(
393 serialNumber: numInPlay + UInt32(1),
394 playID: playID,
395 setID: self.setID,
396 subeditionID: 0
397 )
398
399 // Increment the count of Moments minted for this Play
400 self.numberMintedPerPlay[playID] = numInPlay + UInt32(1)
401
402 return <-newMoment
403 }
404
405 // batchMintMoment mints an arbitrary quantity of Moments
406 // and returns them as a Collection
407 //
408 // Parameters: playID: the ID of the Play that the Moments are minted for
409 // quantity: The quantity of Moments to be minted
410 //
411 // Returns: Collection object that contains all the Moments that were minted
412 //
413 access(all) fun batchMintMoment(playID: UInt32, quantity: UInt64): @Collection {
414 let newCollection <- create Collection()
415
416 var i: UInt64 = 0
417 while i < quantity {
418 newCollection.deposit(token: <-self.mintMoment(playID: playID))
419 i = i + UInt64(1)
420 }
421
422 return <- newCollection
423 }
424
425 // mintMomentWithSubedition mints a new Moment with subedition and returns the newly minted Moment
426 //
427 // Parameters: playID: The ID of the Play that the Moment references
428 // subeditionID: The ID of the subedition within Edition that the Moment references
429 //
430 // Pre-Conditions:
431 // The Play must exist in the Set and be allowed to mint new Moments
432 //
433 // Returns: The NFT that was minted
434 //
435 access(all) fun mintMomentWithSubedition(playID: UInt32, subeditionID: UInt32): @NFT {
436 pre {
437 self.retired[playID] != nil: "Cannot mint the moment: This play doesn't exist."
438 !self.retired[playID]!: "Cannot mint the moment from this play: This play has been retired."
439 }
440
441 // Gets the number of Moments that have been minted for this subedition
442 // to use as this Moment's serial number
443 let subeditionRef = TopShot.account.storage.borrow<&SubeditionAdmin>(from: TopShot.SubeditionAdminStoragePath())
444 ?? panic("No subedition admin resource in storage")
445
446 let numInSubedition = subeditionRef.getNumberMintedPerSubedition(
447 setID: self.setID,
448 playID: playID,
449 subeditionID: subeditionID
450 )
451
452 // Mint the new moment
453 let newMoment: @NFT <- create NFT(
454 serialNumber: numInSubedition + UInt32(1),
455 playID: playID,
456 setID: self.setID,
457 subeditionID: subeditionID
458 )
459
460 // Increment the count of Moments minted for this subedition
461 subeditionRef.addToNumberMintedPerSubedition(
462 setID: self.setID,
463 playID: playID,
464 subeditionID: subeditionID
465 )
466
467 subeditionRef.setMomentsSubedition(nftID: newMoment.id, subeditionID: subeditionID, setID: self.setID, playID: playID)
468
469 self.numberMintedPerPlay[playID] = self.numberMintedPerPlay[playID]! + UInt32(1)
470
471 return <- newMoment
472 }
473
474 // batchMintMomentWithSubedition mints an arbitrary quantity of Moments with subedition
475 // and returns them as a Collection
476 //
477 // Parameters: playID: the ID of the Play that the Moments are minted for
478 // quantity: The quantity of Moments to be minted
479 // subeditionID: The ID of the subedition within Edition that the Moments references
480 //
481 // Returns: Collection object that contains all the Moments that were minted
482 //
483 access(all) fun batchMintMomentWithSubedition(playID: UInt32, quantity: UInt64, subeditionID: UInt32): @Collection {
484 let newCollection <- create Collection()
485
486 var i: UInt64 = 0
487 while i < quantity {
488 newCollection.deposit(token: <-self.mintMomentWithSubedition(playID: playID, subeditionID: subeditionID))
489 i = i + UInt64(1)
490 }
491
492 return <-newCollection
493 }
494
495 access(all) view fun getPlays(): [UInt32] {
496 return self.plays
497 }
498
499 access(all) view fun getRetired(): {UInt32: Bool} {
500 return self.retired
501 }
502
503 access(all) view fun getNumMintedPerPlay(): {UInt32: UInt32} {
504 return self.numberMintedPerPlay
505 }
506 }
507
508 // Struct that contains all of the important data about a set
509 // Can be easily queried by instantiating the `QuerySetData` object
510 // with the desired set ID
511 // let setData = TopShot.QuerySetData(setID: 12)
512 //
513 access(all) struct QuerySetData {
514 access(all) let setID: UInt32
515 access(all) let name: String
516 access(all) let series: UInt32
517 access(self) var plays: [UInt32]
518 access(self) var retired: {UInt32: Bool}
519 access(all) var locked: Bool
520 access(self) var numberMintedPerPlay: {UInt32: UInt32}
521
522 init(setID: UInt32) {
523 pre {
524 TopShot.sets[setID] != nil: "The set with the provided ID does not exist"
525 }
526
527 let set = (&TopShot.sets[setID] as &Set?)!
528 let setData = TopShot.setDatas[setID]!
529
530 self.setID = setID
531 self.name = setData.name
532 self.series = setData.series
533 self.plays = set.getPlays()
534 self.retired = set.getRetired()
535 self.locked = set.locked
536 self.numberMintedPerPlay = set.getNumMintedPerPlay()
537 }
538
539 access(all) view fun getPlays(): [UInt32] {
540 return self.plays
541 }
542
543 access(all) view fun getRetired(): {UInt32: Bool} {
544 return self.retired
545 }
546
547 access(all) view fun getNumberMintedPerPlay(): {UInt32: UInt32} {
548 return self.numberMintedPerPlay
549 }
550 }
551
552 access(all) struct MomentData {
553 // The ID of the Set that the Moment comes from
554 access(all) let setID: UInt32
555
556 // The ID of the Play that the Moment references
557 access(all) let playID: UInt32
558
559 // The place in the edition that this Moment was minted
560 // Otherwise know as the serial number
561 access(all) let serialNumber: UInt32
562
563 init(setID: UInt32, playID: UInt32, serialNumber: UInt32) {
564 self.setID = setID
565 self.playID = playID
566 self.serialNumber = serialNumber
567 }
568 }
569
570 // This is an implementation of a custom metadata view for Top Shot.
571 // This view contains the play metadata.
572 //
573 access(all) struct TopShotMomentMetadataView {
574 access(all) let fullName: String?
575 access(all) let firstName: String?
576 access(all) let lastName: String?
577 access(all) let birthdate: String?
578 access(all) let birthplace: String?
579 access(all) let jerseyNumber: String?
580 access(all) let draftTeam: String?
581 access(all) let draftYear: String?
582 access(all) let draftSelection: String?
583 access(all) let draftRound: String?
584 access(all) let teamAtMomentNBAID: String?
585 access(all) let teamAtMoment: String?
586 access(all) let primaryPosition: String?
587 access(all) let height: String?
588 access(all) let weight: String?
589 access(all) let totalYearsExperience: String?
590 access(all) let nbaSeason: String?
591 access(all) let dateOfMoment: String?
592 access(all) let playCategory: String?
593 access(all) let playType: String?
594 access(all) let homeTeamName: String?
595 access(all) let awayTeamName: String?
596 access(all) let homeTeamScore: String?
597 access(all) let awayTeamScore: String?
598 access(all) let seriesNumber: UInt32?
599 access(all) let setName: String?
600 access(all) let serialNumber: UInt32
601 access(all) let playID: UInt32
602 access(all) let setID: UInt32
603 access(all) let numMomentsInEdition: UInt32?
604
605 init(
606 fullName: String?,
607 firstName: String?,
608 lastName: String?,
609 birthdate: String?,
610 birthplace: String?,
611 jerseyNumber: String?,
612 draftTeam: String?,
613 draftYear: String?,
614 draftSelection: String?,
615 draftRound: String?,
616 teamAtMomentNBAID: String?,
617 teamAtMoment: String?,
618 primaryPosition: String?,
619 height: String?,
620 weight: String?,
621 totalYearsExperience: String?,
622 nbaSeason: String?,
623 dateOfMoment: String?,
624 playCategory: String?,
625 playType: String?,
626 homeTeamName: String?,
627 awayTeamName: String?,
628 homeTeamScore: String?,
629 awayTeamScore: String?,
630 seriesNumber: UInt32?,
631 setName: String?,
632 serialNumber: UInt32,
633 playID: UInt32,
634 setID: UInt32,
635 numMomentsInEdition: UInt32?
636 ) {
637 self.fullName = fullName
638 self.firstName = firstName
639 self.lastName = lastName
640 self.birthdate = birthdate
641 self.birthplace = birthplace
642 self.jerseyNumber = jerseyNumber
643 self.draftTeam = draftTeam
644 self.draftYear = draftYear
645 self.draftSelection = draftSelection
646 self.draftRound = draftRound
647 self.teamAtMomentNBAID = teamAtMomentNBAID
648 self.teamAtMoment = teamAtMoment
649 self.primaryPosition = primaryPosition
650 self.height = height
651 self.weight = weight
652 self.totalYearsExperience = totalYearsExperience
653 self.nbaSeason = nbaSeason
654 self.dateOfMoment= dateOfMoment
655 self.playCategory = playCategory
656 self.playType = playType
657 self.homeTeamName = homeTeamName
658 self.awayTeamName = awayTeamName
659 self.homeTeamScore = homeTeamScore
660 self.awayTeamScore = awayTeamScore
661 self.seriesNumber = seriesNumber
662 self.setName = setName
663 self.serialNumber = serialNumber
664 self.playID = playID
665 self.setID = setID
666 self.numMomentsInEdition = numMomentsInEdition
667 }
668 }
669
670 // The resource that represents the Moment NFTs
671 //
672 access(all) resource NFT: NonFungibleToken.NFT {
673 // Global unique moment ID
674 access(all) let id: UInt64
675
676 // Struct of Moment metadata
677 access(all) let data: MomentData
678
679 init(serialNumber: UInt32, playID: UInt32, setID: UInt32, subeditionID: UInt32) {
680 // Increment the global Moment IDs
681 TopShot.totalSupply = TopShot.totalSupply + UInt64(1)
682
683 self.id = TopShot.totalSupply
684
685 // Set the metadata struct
686 self.data = MomentData(setID: setID, playID: playID, serialNumber: serialNumber)
687
688 emit MomentMinted(
689 momentID: self.id,
690 playID: playID,
691 setID: self.data.setID,
692 serialNumber: self.data.serialNumber,
693 subeditionID: subeditionID
694 )
695 }
696
697 // If the Moment is destroyed, emit an event to indicate
698 // to outside observers that it has been destroyed
699 access(all) event ResourceDestroyed(
700 id: UInt64 = self.id,
701 serialNumber: UInt32 = self.data.serialNumber,
702 playID: UInt32 = self.data.playID,
703 setID: UInt32 = self.data.setID
704 )
705
706 access(all) view fun name(): String {
707 let fullName: String = TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "FullName") ?? ""
708 let playType: String = TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "PlayType") ?? ""
709 return fullName
710 .concat(" ")
711 .concat(playType)
712 }
713
714 // The description of the Moment.
715 // If the Tagline prop exists, use is as the description; else, build the description using set, series, and serial number.
716 access(all) view fun description(): String {
717 // Return early if the tagline is non-empty
718 if let tagline = TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "Tagline") {
719 return tagline
720 }
721
722 // Build the description using set name, series number, and serial number
723 let setName: String = TopShot.getSetName(setID: self.data.setID) ?? ""
724 let serialNumber: String = self.data.serialNumber.toString()
725 let seriesNumber: String = TopShot.getSetSeries(setID: self.data.setID)?.toString() ?? ""
726 return "A series "
727 .concat(seriesNumber)
728 .concat(" ")
729 .concat(setName)
730 .concat(" moment with serial number ")
731 .concat(serialNumber)
732 }
733
734 // All supported metadata views for the Moment including the Core NFT Views
735 access(all) view fun getViews(): [Type] {
736 return [
737 Type<MetadataViews.Display>(),
738 Type<TopShotMomentMetadataView>(),
739 Type<MetadataViews.Royalties>(),
740 Type<MetadataViews.Editions>(),
741 Type<MetadataViews.ExternalURL>(),
742 Type<MetadataViews.NFTCollectionData>(),
743 Type<MetadataViews.NFTCollectionDisplay>(),
744 Type<CrossVMMetadataViews.EVMPointer>(),
745 Type<MetadataViews.EVMBridgedMetadata>(),
746 Type<MetadataViews.Serial>(),
747 Type<MetadataViews.Traits>(),
748 Type<MetadataViews.Medias>()
749 ]
750 }
751
752 // resolves the view with the given type for the NFT
753 access(all) fun resolveView(_ view: Type): AnyStruct? {
754 switch view {
755 case Type<MetadataViews.Display>():
756 return MetadataViews.Display(
757 name: self.name(),
758 description: self.description(),
759 thumbnail: MetadataViews.HTTPFile(url: self.thumbnail())
760 )
761 // Custom metadata view unique to TopShot Moments
762 case Type<TopShotMomentMetadataView>():
763 return TopShotMomentMetadataView(
764 fullName: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "FullName"),
765 firstName: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "FirstName"),
766 lastName: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "LastName"),
767 birthdate: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "Birthdate"),
768 birthplace: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "Birthplace"),
769 jerseyNumber: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "JerseyNumber"),
770 draftTeam: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "DraftTeam"),
771 draftYear: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "DraftYear"),
772 draftSelection: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "DraftSelection"),
773 draftRound: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "DraftRound"),
774 teamAtMomentNBAID: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "TeamAtMomentNBAID"),
775 teamAtMoment: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "TeamAtMoment"),
776 primaryPosition: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "PrimaryPosition"),
777 height: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "Height"),
778 weight: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "Weight"),
779 totalYearsExperience: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "TotalYearsExperience"),
780 nbaSeason: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "NbaSeason"),
781 dateOfMoment: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "DateOfMoment"),
782 playCategory: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "PlayCategory"),
783 playType: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "PlayType"),
784 homeTeamName: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "HomeTeamName"),
785 awayTeamName: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "AwayTeamName"),
786 homeTeamScore: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "HomeTeamScore"),
787 awayTeamScore: TopShot.getPlayMetaDataByField(playID: self.data.playID, field: "AwayTeamScore"),
788 seriesNumber: TopShot.getSetSeries(setID: self.data.setID),
789 setName: TopShot.getSetName(setID: self.data.setID),
790 serialNumber: self.data.serialNumber,
791 playID: self.data.playID,
792 setID: self.data.setID,
793 numMomentsInEdition: TopShot.getNumMomentsInEdition(setID: self.data.setID, playID: self.data.playID)
794 )
795 case Type<MetadataViews.Editions>():
796 let name = self.getEditionName()
797 let max = TopShot.getNumMomentsInEdition(setID: self.data.setID, playID: self.data.playID) ?? 0
798 let editionInfo = MetadataViews.Edition(name: name, number: UInt64(self.data.serialNumber), max: max > 0 ? UInt64(max) : nil)
799 let editionList: [MetadataViews.Edition] = [editionInfo]
800 return MetadataViews.Editions(
801 editionList
802 )
803 case Type<MetadataViews.Serial>():
804 return MetadataViews.Serial(
805 UInt64(self.data.serialNumber)
806 )
807 case Type<MetadataViews.Royalties>():
808 return TopShot.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.Royalties>())
809 case Type<MetadataViews.ExternalURL>():
810 return MetadataViews.ExternalURL(self.getMomentURL())
811 case Type<MetadataViews.NFTCollectionData>():
812 return TopShot.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.NFTCollectionData>())
813 case Type<MetadataViews.NFTCollectionDisplay>():
814 return TopShot.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.NFTCollectionDisplay>())
815 case Type<CrossVMMetadataViews.EVMPointer>():
816 return TopShot.resolveContractView(resourceType: nil, viewType: Type<CrossVMMetadataViews.EVMPointer>())
817 case Type<MetadataViews.EVMBridgedMetadata>():
818 // Project-defined ERC721 EVM contract stores baseURI, name, and symbol in its own contract storage
819 // Name, symbol, and baseURI below are only used for legacy bridge-deployed ERC721 contract
820 return MetadataViews.EVMBridgedMetadata(
821 name: "NBA Top Shot",
822 symbol: "NBAT",
823 uri: MetadataViews.URI(
824 baseURI: "https://metadata-api.production.studio-platform.dapperlabs.com/v1/topshot/moment/",
825 value: self.id.toString()
826 )
827 )
828 case Type<MetadataViews.Traits>():
829 return self.resolveTraitsView()
830 case Type<MetadataViews.Medias>():
831 var items = [
832 MetadataViews.Media(
833 file: MetadataViews.HTTPFile(
834 url: self.mediumimage()
835 ),
836 mediaType: "image/jpeg"
837 ),
838 MetadataViews.Media(
839 file: MetadataViews.HTTPFile(
840 url: self.video()
841 ),
842 mediaType: "video/mp4"
843 )
844 ]
845 let subeditionID = TopShot.getMomentsSubedition(nftID: self.id) ?? 0
846 if let ipfsCIDs = TopShotIPFSResolver.getCIDs(setID: self.data.setID, playID: self.data.playID, subeditionID: subeditionID) {
847 for mediaType in ipfsCIDs.keys {
848 let cid = ipfsCIDs[mediaType]!
849 items.append(
850 MetadataViews.Media(
851 file: MetadataViews.IPFSFile(
852 cid: cid,
853 path: nil
854 ),
855 mediaType: mediaType
856 )
857 )
858 items.append(
859 MetadataViews.Media(
860 file: MetadataViews.HTTPFile(
861 url: TopShotIPFSResolver.gateway.concat(cid)
862 ),
863 mediaType: mediaType
864 )
865 )
866 }
867 }
868 return MetadataViews.Medias(items)
869 }
870 return nil
871 }
872
873 // resolves this NFT's Traits view
874 access(all) fun resolveTraitsView(): MetadataViews.Traits {
875 // sports radar team id
876 let excludedNames: [String] = ["TeamAtMomentNBAID"]
877
878 // Get subedition
879 let subedition = TopShot.getSubeditionByNFTID(self.id)
880
881 // Create a dictionary of this NFT's traits with default metadata
882 var traits: {String: AnyStruct} = {
883 "SeriesNumber": TopShot.getSetSeries(setID: self.data.setID),
884 "SetName": TopShot.getSetName(setID: self.data.setID),
885 "SerialNumber": self.data.serialNumber,
886 "Locked": TopShotLocking.isLocked(nftRef: &self as &{NonFungibleToken.NFT}),
887 "Subedition": subedition?.name ?? "Standard",
888 "SubeditionID": subedition?.subeditionID ?? 0
889 }
890
891 // Add play specific data
892 traits = self.mapPlayData(dict: traits)
893
894 return MetadataViews.dictToTraits(dict: traits, excludedNames: excludedNames)
895 }
896
897 // Functions used for computing MetadataViews
898
899 // mapPlayData helps build our trait map from play metadata
900 // Returns: The trait map with all non-empty fields from play data added
901 access(all) fun mapPlayData(dict: {String: AnyStruct}) : {String: AnyStruct} {
902 let playMetadata = TopShot.getPlayMetaData(playID: self.data.playID) ?? {}
903 for name in playMetadata.keys {
904 let value = playMetadata[name] ?? ""
905 if value != "" {
906 dict.insert(key: name, value)
907 }
908 }
909 return dict
910 }
911
912 // getMomentURL
913 // Returns: The computed external url of the moment
914 access(all) view fun getMomentURL(): String {
915 return "https://nbatopshot.com/moment/".concat(self.id.toString())
916 }
917
918 // getEditionName Moment's edition name is a combination of the Moment's setName and playID
919 // `setName: #playID`
920 access(all) view fun getEditionName(): String {
921 let setName: String = TopShot.getSetName(setID: self.data.setID) ?? ""
922 let editionName = setName.concat(": #").concat(self.data.playID.toString())
923 return editionName
924 }
925
926 access(all) view fun assetPath(): String {
927 return "https://assets.nbatopshot.com/media/".concat(self.id.toString())
928 }
929
930 // returns a url to display an medium sized image
931 access(all) view fun mediumimage(): String {
932 return self.appendOptionalParams(url: self.assetPath().concat("?width=512"), firstDelim: "&")
933 }
934
935 // a url to display a thumbnail associated with the moment
936 access(all) view fun thumbnail(): String {
937 return self.appendOptionalParams(url: self.assetPath().concat("?width=256"), firstDelim: "&")
938 }
939
940 // a url to display a video associated with the moment
941 access(all) view fun video(): String {
942 return self.appendOptionalParams(url: self.assetPath().concat("/video"), firstDelim: "?")
943 }
944
945 // appends and optional network param needed to resolve the media
946 access(all) view fun appendOptionalParams(url: String, firstDelim: String): String {
947 if TopShot.Network() == "testnet" {
948 return url.concat(firstDelim).concat("testnet")
949 }
950 return url
951 }
952
953 // Create an empty Collection for TopShot NFTs and return it to the caller
954 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
955 return <- TopShot.createEmptyCollection(nftType: Type<@NFT>())
956 }
957 }
958
959 // Admin is a special authorization resource that
960 // allows the owner to perform important functions to modify the
961 // various aspects of the Plays, Sets, and Moments
962 //
963 access(all) resource Admin {
964 // createPlay creates a new Play struct
965 // and stores it in the Plays dictionary in the TopShot smart contract
966 //
967 // Parameters: metadata: A dictionary mapping metadata titles to their data
968 // example: {"Player Name": "Kevin Durant", "Height": "7 feet"}
969 // (because we all know Kevin Durant is not 6'9")
970 //
971 // Returns: the ID of the new Play object
972 //
973 access(all) fun createPlay(metadata: {String: String}): UInt32 {
974 // Create the new Play
975 var newPlay = Play(metadata: metadata)
976 let newID = newPlay.playID
977
978 // Increment the ID so that it isn't used again
979 TopShot.nextPlayID = TopShot.nextPlayID + UInt32(1)
980
981 emit PlayCreated(id: newPlay.playID, metadata: metadata)
982
983 // Store it in the contract storage
984 TopShot.playDatas[newID] = newPlay
985
986 return newID
987 }
988
989 /// Temporarily enabled so the description of the play can be backfilled
990 /// Parameters: playID: The ID of the play to update
991 /// tagline: A string to be used as the tagline for the play
992 /// Returns: The ID of the play
993 access(all) fun updatePlayTagline(playID: UInt32, tagline: String): UInt32 {
994 let tmpPlay = TopShot.playDatas[playID]
995 ?? panic("playID does not exist")
996
997 // Update the play's tagline
998 tmpPlay.updateTagline(tagline: tagline)
999
1000 // Return the play's ID
1001 return playID
1002 }
1003
1004 /// Sets one metadata field of an existing Play
1005 /// Parameters: playID: The ID of the play to update
1006 /// key: The metadata field to set
1007 /// value: The new value for the field
1008 /// Returns: The ID of the play
1009 access(all) fun updatePlayMetadata(playID: UInt32, key: String, value: String): UInt32 {
1010 pre {
1011 key.length != 0: "Metadata key cannot be empty"
1012 }
1013 let tmpPlay = TopShot.playDatas[playID]
1014 ?? panic("playID does not exist")
1015
1016 tmpPlay.updateMetadataField(key: key, value: value)
1017
1018 emit PlayMetadataUpdated(id: playID, key: key, value: value)
1019
1020 return playID
1021 }
1022
1023 // createSet creates a new Set resource and stores it
1024 // in the sets mapping in the TopShot contract
1025 //
1026 // Parameters: name: The name of the Set
1027 //
1028 // Returns: The ID of the created set
1029 access(all) fun createSet(name: String): UInt32 {
1030 // Create the new Set
1031 var newSet <- create Set(name: name)
1032
1033 // Increment the setID so that it isn't used again
1034 TopShot.nextSetID = TopShot.nextSetID + UInt32(1)
1035
1036 let newID = newSet.setID
1037
1038 emit SetCreated(setID: newSet.setID, series: TopShot.currentSeries)
1039
1040 // Store it in the sets mapping field
1041 TopShot.sets[newID] <-! newSet
1042
1043 return newID
1044 }
1045
1046 // borrowSet returns a reference to a set in the TopShot
1047 // contract so that the admin can call methods on it
1048 //
1049 // Parameters: setID: The ID of the Set that you want to
1050 // get a reference to
1051 //
1052 // Returns: A reference to the Set with all of the fields
1053 // and methods exposed
1054 //
1055 access(all) view fun borrowSet(setID: UInt32): &Set {
1056 pre {
1057 TopShot.sets[setID] != nil: "Cannot borrow Set: The Set doesn't exist"
1058 }
1059
1060 // Get a reference to the Set and return it
1061 // use `&` to indicate the reference to the object and type
1062 return (&TopShot.sets[setID] as &Set?)!
1063 }
1064
1065 // startNewSeries ends the current series by incrementing
1066 // the series number, meaning that Moments minted after this
1067 // will use the new series number
1068 //
1069 // Returns: The new series number
1070 //
1071 access(all) fun startNewSeries(): UInt32 {
1072 // End the current series and start a new one
1073 // by incrementing the TopShot series number
1074 TopShot.currentSeries = TopShot.currentSeries + UInt32(1)
1075
1076 emit NewSeriesStarted(newCurrentSeries: TopShot.currentSeries)
1077
1078 return TopShot.currentSeries
1079 }
1080
1081 // createSubeditionResource creates new SubeditionMap resource that
1082 // will be used to mint Moments with Subeditions
1083 access(all) fun createSubeditionAdminResource() {
1084 TopShot.account.storage.save<@SubeditionAdmin>(<- create SubeditionAdmin(), to: TopShot.SubeditionAdminStoragePath())
1085 }
1086
1087 // setMomentsSubedition saves which Subedition the Moment belongs to
1088 //
1089 // Parameters: nftID: The ID of the NFT
1090 // subeditionID: The ID of the Subedition the Moment belongs to
1091 // setID: The ID of the Set that the Moment references
1092 // playID: The ID of the Play that the Moment references
1093 //
1094 access(all) fun setMomentsSubedition(nftID: UInt64, subeditionID: UInt32, setID: UInt32, playID: UInt32) {
1095 let subeditionAdmin = TopShot.account.storage.borrow<&SubeditionAdmin>(from: TopShot.SubeditionAdminStoragePath())
1096 ?? panic("No subedition admin resource in storage")
1097
1098 subeditionAdmin.setMomentsSubedition(nftID: nftID, subeditionID: subeditionID, setID: setID, playID: playID)
1099 }
1100
1101 // createSubedition creates a new Subedition struct
1102 // and stores it in the Subeditions dictionary in the SubeditionAdmin resource
1103 //
1104 // Parameters: name: The name of the Subedition
1105 // metadata: A dictionary mapping metadata titles to their data
1106 //
1107 // Returns: the ID of the new Subedition object
1108 //
1109 access(all) fun createSubedition(name: String, metadata: {String: String}): UInt32 {
1110 let subeditionAdmin = TopShot.account.storage.borrow<&SubeditionAdmin>(from: TopShot.SubeditionAdminStoragePath())
1111 ?? panic("No subedition admin resource in storage")
1112
1113 return subeditionAdmin.createSubedition(name:name, metadata:metadata)
1114 }
1115
1116 // createNewAdmin creates a new Admin resource
1117 //
1118 access(all) fun createNewAdmin(): @Admin {
1119 return <- create Admin()
1120 }
1121 }
1122
1123 // This is the interface that users can cast their Moment Collection as
1124 // to allow others to deposit Moments into their Collection. It also allows for reading
1125 // the IDs of Moments in the Collection.
1126 /// Deprecated: This is no longer used for defining access control anymore.
1127 access(all) resource interface MomentCollectionPublic : NonFungibleToken.CollectionPublic {
1128 access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection})
1129 access(all) fun borrowMoment(id: UInt64): &NFT? {
1130 // If the result isn't nil, the id of the returned reference
1131 // should be the same as the argument to the function
1132 post {
1133 (result == nil) || (result?.id == id):
1134 "Cannot borrow Moment reference: The ID of the returned reference is incorrect"
1135 }
1136 }
1137 }
1138
1139 // Collection is a resource that every user who owns NFTs
1140 // will store in their account to manage their NFTS
1141 //
1142 access(all) resource Collection: MomentCollectionPublic, NonFungibleToken.Collection {
1143 // Dictionary of Moment conforming tokens
1144 // NFT is a resource type with a UInt64 ID field
1145 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
1146
1147 init() {
1148 self.ownedNFTs <- {}
1149 }
1150
1151 // Return a list of NFT types that this receiver accepts
1152 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
1153 let supportedTypes: {Type: Bool} = {}
1154 supportedTypes[Type<@NFT>()] = true
1155 return supportedTypes
1156 }
1157
1158 // Return whether or not the given type is accepted by the collection
1159 // A collection that can accept any type should just return true by default
1160 access(all) view fun isSupportedNFTType(type: Type): Bool {
1161 if type == Type<@NFT>() {
1162 return true
1163 }
1164 return false
1165 }
1166
1167 // Return the amount of NFTs stored in the collection
1168 access(all) view fun getLength(): Int {
1169 return self.ownedNFTs.length
1170 }
1171
1172 // Create an empty Collection for TopShot NFTs and return it to the caller
1173 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
1174 return <- TopShot.createEmptyCollection(nftType: Type<@NFT>())
1175 }
1176
1177 // withdraw removes an Moment from the Collection and moves it to the caller
1178 //
1179 // Parameters: withdrawID: The ID of the NFT
1180 // that is to be removed from the Collection
1181 //
1182 // returns: @NonFungibleToken.NFT the token that was withdrawn
1183 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
1184 // Borrow nft and check if locked
1185 let nft = self.borrowNFT(withdrawID)
1186 ?? panic("Cannot borrow: empty reference")
1187
1188 if TopShotLocking.isLocked(nftRef: nft) {
1189 panic("Cannot withdraw: Moment is locked")
1190 }
1191
1192 // Remove the nft from the Collection
1193 let token <- self.ownedNFTs.remove(key: withdrawID)
1194 ?? panic("Cannot withdraw: Moment does not exist in the collection")
1195
1196 emit Withdraw(id: token.id, from: self.owner?.address)
1197
1198 // Return the withdrawn token
1199 return <- token
1200 }
1201
1202 // batchWithdraw withdraws multiple tokens and returns them as a Collection
1203 //
1204 // Parameters: ids: An array of IDs to withdraw
1205 //
1206 // Returns: @NonFungibleToken.Collection: A collection that contains
1207 // the withdrawn moments
1208 //
1209 access(NonFungibleToken.Withdraw) fun batchWithdraw(ids: [UInt64]): @{NonFungibleToken.Collection} {
1210 // Create a new empty Collection
1211 var batchCollection <- create Collection()
1212
1213 // Iterate through the ids and withdraw them from the Collection
1214 for id in ids {
1215 batchCollection.deposit(token: <- self.withdraw(withdrawID: id))
1216 }
1217
1218 // Return the withdrawn tokens
1219 return <- batchCollection
1220 }
1221
1222 // deposit takes a Moment and adds it to the Collections dictionary
1223 //
1224 // Paramters: token: the NFT to be deposited in the collection
1225 //
1226 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
1227 // Cast the deposited token as a TopShot NFT to make sure
1228 // it is the correct type
1229 let token <- token as! @NFT
1230
1231 // Get the token's ID
1232 let id = token.id
1233
1234 // Add the new token to the dictionary
1235 let oldToken <- self.ownedNFTs[id] <- token
1236
1237 // Only emit a deposit event if the Collection
1238 // is in an account's storage
1239 if self.owner?.address != nil {
1240 emit Deposit(id: id, to: self.owner?.address)
1241 }
1242
1243 // Destroy the empty old token that was "removed"
1244 destroy oldToken
1245 }
1246
1247 // batchDeposit takes a Collection object as an argument
1248 // and deposits each contained NFT into this Collection
1249 access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection}) {
1250 // Get an array of the IDs to be deposited
1251 let keys = tokens.getIDs()
1252
1253 // Iterate through the keys in the collection and deposit each one
1254 for key in keys {
1255 self.deposit(token: <- tokens.withdraw(withdrawID: key))
1256 }
1257
1258 // Destroy the empty Collection
1259 destroy tokens
1260 }
1261
1262 // lock takes a token id and a duration in seconds and locks
1263 // the moment for that duration
1264 access(NonFungibleToken.Update) fun lock(id: UInt64, duration: UFix64) {
1265 // Remove the nft from the Collection
1266 let token <- self.ownedNFTs.remove(key: id)
1267 ?? panic("Cannot lock: Moment does not exist in the collection")
1268
1269 TopShot.emitNFTUpdated(&token as auth(NonFungibleToken.Update) &{NonFungibleToken.NFT})
1270
1271 // pass the token to the locking contract
1272 // store it again after it comes back
1273 let oldToken <- self.ownedNFTs[id] <- TopShotLocking.lockNFT(nft: <- token, duration: duration)
1274
1275 destroy oldToken
1276 }
1277
1278 // batchLock takes an array of token ids and a duration in seconds
1279 // it iterates through the ids and locks each for the specified duration
1280 access(NonFungibleToken.Update) fun batchLock(ids: [UInt64], duration: UFix64) {
1281 // Iterate through the ids and lock them
1282 for id in ids {
1283 self.lock(id: id, duration: duration)
1284 }
1285 }
1286
1287 // unlock takes a token id and attempts to unlock it
1288 // TopShotLocking.unlockNFT contains business logic around unlock eligibility
1289 access(NonFungibleToken.Update) fun unlock(id: UInt64) {
1290 // Remove the nft from the Collection
1291 let token <- self.ownedNFTs.remove(key: id)
1292 ?? panic("Cannot lock: Moment does not exist in the collection")
1293
1294 TopShot.emitNFTUpdated(&token as auth(NonFungibleToken.Update) &{NonFungibleToken.NFT})
1295
1296 // Pass the token to the TopShotLocking contract then get it back
1297 // Store it back to the ownedNFTs dictionary
1298 let oldToken <- self.ownedNFTs[id] <- TopShotLocking.unlockNFT(nft: <- token)
1299
1300 destroy oldToken
1301 }
1302
1303 // batchUnlock takes an array of token ids
1304 // it iterates through the ids and unlocks each if they are eligible
1305 access(NonFungibleToken.Update) fun batchUnlock(ids: [UInt64]) {
1306 // Iterate through the ids and unlocks them
1307 for id in ids {
1308 self.unlock(id: id)
1309 }
1310 }
1311
1312 // destroyMoments destroys moments in this collection
1313 // unlocks the moments if they are locked
1314 //
1315 // Parameters: ids: An array of NFT IDs
1316 // to be destroyed from the Collection
1317 access(NonFungibleToken.Update) fun destroyMoments(ids: [UInt64]) {
1318 let topShotLockingAdmin = TopShot.account.storage.borrow<&TopShotLocking.Admin>(from: TopShotLocking.AdminStoragePath())
1319 ?? panic("No TopShotLocking admin resource in storage")
1320
1321 for id in ids {
1322 // Remove the nft from the Collection
1323 let token <- self.ownedNFTs.remove(key: id)
1324 ?? panic("Cannot destroy: Moment does not exist in collection: ".concat(id.toString()))
1325
1326 // Emit a withdraw event here so that platforms do not have to understand TopShot-specific events to see ownership change
1327 // A withdraw without a corresponding deposit means the NFT in question has no owner address
1328 emit Withdraw(id: id, from: self.owner?.address)
1329
1330 // does nothing if the moment is not locked
1331 topShotLockingAdmin.unlockByID(id: id)
1332
1333 destroy token
1334 }
1335 }
1336
1337 // getIDs returns an array of the IDs that are in the Collection
1338 access(all) view fun getIDs(): [UInt64] {
1339 return self.ownedNFTs.keys
1340 }
1341
1342 // borrowNFT Returns a borrowed reference to a Moment in the Collection
1343 // so that the caller can read its ID
1344 //
1345 // Parameters: id: The ID of the NFT to get the reference for
1346 //
1347 // Returns: A reference to the NFT
1348 //
1349 // Note: This only allows the caller to read the ID of the NFT,
1350 // not any topshot specific data. Please use borrowMoment to
1351 // read Moment data.
1352 //
1353 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
1354 return &self.ownedNFTs[id]
1355 }
1356
1357 // borrowMoment returns a borrowed reference to a Moment
1358 // so that the caller can read data and call methods from it.
1359 // They can use this to read its setID, playID, serialNumber,
1360 // or any of the setData or Play data associated with it by
1361 // getting the setID or playID and reading those fields from
1362 // the smart contract.
1363 //
1364 // Parameters: id: The ID of the NFT to get the reference for
1365 //
1366 // Returns: A reference to the NFT
1367 access(all) view fun borrowMoment(id: UInt64): &NFT? {
1368 return self.borrowNFT(id) as! &NFT?
1369 }
1370
1371 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
1372 if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
1373 return nft as &{ViewResolver.Resolver}
1374 }
1375 return nil
1376 }
1377 }
1378
1379 // -----------------------------------------------------------------------
1380 // TopShot contract-level function definitions
1381 // -----------------------------------------------------------------------
1382
1383 // createEmptyCollection creates a new, empty Collection object so that
1384 // a user can store it in their account storage.
1385 // Once they have a Collection in their storage, they are able to receive
1386 // Moments in transactions.
1387 //
1388 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
1389 if nftType != Type<@NFT>() {
1390 panic("NFT type is not supported")
1391 }
1392 return <- create TopShot.Collection()
1393 }
1394
1395 // getAllPlays returns all the plays in topshot
1396 //
1397 // Returns: An array of all the plays that have been created
1398 access(all) view fun getAllPlays(): [Play] {
1399 return TopShot.playDatas.values
1400 }
1401
1402 // getPlayMetaData returns all the metadata associated with a specific Play
1403 //
1404 // Parameters: playID: The id of the Play that is being searched
1405 //
1406 // Returns: The metadata as a String to String mapping optional
1407 access(all) view fun getPlayMetaData(playID: UInt32): {String: String}? {
1408 return self.playDatas[playID]?.metadata
1409 }
1410
1411 // getPlayMetaDataByField returns the metadata associated with a
1412 // specific field of the metadata
1413 // Ex: field: "Team" will return something
1414 // like "Memphis Grizzlies"
1415 //
1416 // Parameters: playID: The id of the Play that is being searched
1417 // field: The field to search for
1418 //
1419 // Returns: The metadata field as a String Optional
1420 access(all) view fun getPlayMetaDataByField(playID: UInt32, field: String): String? {
1421 // Don't force a revert if the playID or field is invalid
1422 if let play = TopShot.playDatas[playID] {
1423 return play.metadata[field]
1424 }
1425 return nil
1426 }
1427
1428 // getSetData returns the data that the specified Set
1429 // is associated with.
1430 //
1431 // Parameters: setID: The id of the Set that is being searched
1432 //
1433 // Returns: The QuerySetData struct that has all the important information about the set
1434 access(all) fun getSetData(setID: UInt32): QuerySetData? {
1435 if TopShot.sets[setID] == nil {
1436 return nil
1437 }
1438 return QuerySetData(setID: setID)
1439 }
1440
1441 // getSetName returns the name that the specified Set
1442 // is associated with.
1443 //
1444 // Parameters: setID: The id of the Set that is being searched
1445 //
1446 // Returns: The name of the Set
1447 access(all) view fun getSetName(setID: UInt32): String? {
1448 // Don't force a revert if the setID is invalid
1449 return TopShot.setDatas[setID]?.name
1450 }
1451
1452 // getSetSeries returns the series that the specified Set
1453 // is associated with.
1454 //
1455 // Parameters: setID: The id of the Set that is being searched
1456 //
1457 // Returns: The series that the Set belongs to
1458 access(all) view fun getSetSeries(setID: UInt32): UInt32? {
1459 // Don't force a revert if the setID is invalid
1460 return TopShot.setDatas[setID]?.series
1461 }
1462
1463 // getSetIDsByName returns the IDs that the specified Set name
1464 // is associated with.
1465 //
1466 // Parameters: setName: The name of the Set that is being searched
1467 //
1468 // Returns: An array of the IDs of the Set if it exists, or nil if doesn't
1469 access(all) fun getSetIDsByName(setName: String): [UInt32]? {
1470 var setIDs: [UInt32] = []
1471
1472 // Iterate through all the setDatas and search for the name
1473 for setData in TopShot.setDatas.values {
1474 if setName == setData.name {
1475 // If the name is found, return the ID
1476 setIDs.append(setData.setID)
1477 }
1478 }
1479
1480 // If the name isn't found, return nil
1481 // Don't force a revert if the setName is invalid
1482 if setIDs.length == 0 {
1483 return nil
1484 }
1485 return setIDs
1486 }
1487
1488 // getPlaysInSet returns the list of Play IDs that are in the Set
1489 //
1490 // Parameters: setID: The id of the Set that is being searched
1491 //
1492 // Returns: An array of Play IDs
1493 access(all) view fun getPlaysInSet(setID: UInt32): [UInt32]? {
1494 // Don't force a revert if the setID is invalid
1495 return TopShot.sets[setID]?.plays
1496 }
1497
1498 // isEditionRetired returns a boolean that indicates if a Set/Play combo
1499 // (otherwise known as an edition) is retired.
1500 // If an edition is retired, it still remains in the Set,
1501 // but Moments can no longer be minted from it.
1502 //
1503 // Parameters: setID: The id of the Set that is being searched
1504 // playID: The id of the Play that is being searched
1505 //
1506 // Returns: Boolean indicating if the edition is retired or not
1507 access(all) fun isEditionRetired(setID: UInt32, playID: UInt32): Bool? {
1508 // Return the retired status for the play in the set if it exists
1509 if let setdata = self.getSetData(setID: setID) {
1510 return setdata.getRetired()[playID]
1511 }
1512 return nil
1513 }
1514
1515 // isSetLocked returns a boolean that indicates if a Set
1516 // is locked. If it's locked,
1517 // new Plays can no longer be added to it,
1518 // but Moments can still be minted from Plays the set contains.
1519 //
1520 // Parameters: setID: The id of the Set that is being searched
1521 //
1522 // Returns: Boolean indicating if the Set is locked or not
1523 access(all) view fun isSetLocked(setID: UInt32): Bool? {
1524 // Don't force a revert if the setID is invalid
1525 return TopShot.sets[setID]?.locked
1526 }
1527
1528 // getNumMomentsInEdition return the number of Moments that have been
1529 // minted from a certain edition.
1530 //
1531 // Parameters: setID: The id of the Set that is being searched
1532 // playID: The id of the Play that is being searched
1533 //
1534 // Returns: The total number of Moments
1535 // that have been minted from an edition
1536 access(all) fun getNumMomentsInEdition(setID: UInt32, playID: UInt32): UInt32? {
1537 // Return the number of moments minted for the play in the set if it exists
1538 if let setdata = self.getSetData(setID: setID) {
1539 return setdata.getNumberMintedPerPlay()[playID]
1540 }
1541 return nil
1542 }
1543
1544 // getMomentsSubedition returns the Subedition the Moment belongs to
1545 //
1546 // Parameters: nftID: The ID of the NFT
1547 //
1548 // returns: UInt32? Subedition's ID if exists
1549 //
1550 access(all) view fun getMomentsSubedition(nftID: UInt64): UInt32? {
1551 let subeditionAdmin = self.account.storage.borrow<&SubeditionAdmin>(from: TopShot.SubeditionAdminStoragePath())
1552 ?? panic("No subedition admin resource in storage")
1553 return subeditionAdmin.getMomentsSubedition(nftID: nftID)
1554 }
1555
1556 // getAllSubeditions returns all the subeditions in topshot subeditionAdmin resource
1557 //
1558 // Returns: An array of all the subeditions that have been created
1559 access(all) view fun getAllSubeditions(): &[Subedition] {
1560 let subeditionAdmin = self.account.storage.borrow<&SubeditionAdmin>(from: TopShot.SubeditionAdminStoragePath())
1561 ?? panic("No subedition admin resource in storage")
1562 return subeditionAdmin.subeditionDatas.values
1563 }
1564
1565 // getSubeditionByID returns the subedition struct entity
1566 //
1567 // Parameters: subeditionID: The id of the Subedition that is being searched
1568 //
1569 // Returns: The Subedition struct
1570 access(all) view fun getSubeditionByID(subeditionID: UInt32): &Subedition {
1571 let subeditionAdmin = self.account.storage.borrow<&SubeditionAdmin>(from: TopShot.SubeditionAdminStoragePath())
1572 ?? panic("No subedition admin resource in storage")
1573 return subeditionAdmin.subeditionDatas[subeditionID]!
1574 }
1575
1576 // getSubeditionByNFTID returns the subedition struct that the NFT belongs to
1577 //
1578 // Parameters: nftID: The id of the NFT that is being searched
1579 //
1580 // Returns: The subedition struct that the NFT belongs to
1581 access(all) view fun getSubeditionByNFTID(_ nftID: UInt64): &Subedition? {
1582 if let subeditionAdmin = self.account.storage.borrow<&SubeditionAdmin>(from: TopShot.SubeditionAdminStoragePath()) {
1583 if let subeditionID = subeditionAdmin.getMomentsSubedition(nftID: nftID) {
1584 return subeditionAdmin.subeditionDatas[subeditionID]
1585 }
1586 }
1587 return nil
1588 }
1589
1590 // This script reads the public nextSubeditionID from the SubeditionAdmin resource and
1591 // returns that number to the caller
1592 //
1593 // Returns: UInt32
1594 // the next number in nextSubeditionID from the SubeditionAdmin resource
1595 access(all) view fun getNextSubeditionID(): UInt32 {
1596 let subeditionAdmin = self.account.storage.borrow<&SubeditionAdmin>(from: TopShot.SubeditionAdminStoragePath())
1597 ?? panic("No subedition admin resource in storage")
1598 return subeditionAdmin.nextSubeditionID
1599 }
1600
1601 // SubeditionAdmin is a resource that allows Set to mint Moments with Subeditions
1602 //
1603 access(all) struct Subedition {
1604 access(all) let subeditionID: UInt32
1605
1606 access(all) let name: String
1607
1608 access(all) let metadata: {String: String}
1609
1610 init(subeditionID: UInt32, name: String, metadata: {String: String}) {
1611 pre {
1612 name.length != 0: "New Subedition name cannot be empty"
1613 }
1614 self.subeditionID = subeditionID
1615 self.name = name
1616 self.metadata = metadata
1617 }
1618 }
1619
1620 access(all) resource SubeditionAdmin {
1621 // Map of number of already minted Moments using Subedition.
1622 // When a new Moment with Subedition is minted, 1 is added to the
1623 // number in this map by the key, formed by concatinating of
1624 // SetID, PlayID and SubeditionID
1625 access(contract) let numberMintedPerSubedition: {String: UInt32}
1626
1627 // Map of Subedition which the Moment belongs to.
1628 // This map updates after each minting.
1629 access(contract) let momentsSubedition: {UInt64: UInt32}
1630
1631 // The ID that is used to create Subeditions.
1632 // Every time a Subeditions is created, subeditionID is assigned
1633 // to the new Subedition's ID and then is incremented by 1.
1634 access(contract) var nextSubeditionID: UInt32
1635
1636 // Variable size dictionary of Subedition structs
1637 access(contract) let subeditionDatas: {UInt32: Subedition}
1638
1639 // createSubedition creates a new Subedition struct
1640 // and stores it in the Subeditions dictionary in the SubeditionAdmin resource
1641 //
1642 // Parameters: name: The name of the Subedition
1643 // metadata: A dictionary mapping metadata titles to their data
1644 //
1645 // Returns: the ID of the new Subedition object
1646 //
1647 access(all) fun createSubedition(name: String, metadata: {String: String}): UInt32 {
1648 let newID = self.nextSubeditionID
1649
1650 var newSubedition = Subedition(subeditionID: newID, name: name, metadata: metadata)
1651
1652 self.nextSubeditionID = self.nextSubeditionID + UInt32(1)
1653
1654 self.subeditionDatas[newID] = newSubedition
1655
1656 emit SubeditionCreated(subeditionID: newID, name: name, metadata: metadata)
1657
1658 return newID
1659 }
1660
1661 // getMomentsSubedition function that return's wich Subedition the Moment belongs to
1662 //
1663 // Parameters: nftID: The ID of the NFT
1664 //
1665 // returns: UInt32? Subedition's ID if exists
1666 //
1667 access(all) view fun getMomentsSubedition(nftID: UInt64): UInt32? {
1668 return self.momentsSubedition[nftID]
1669 }
1670
1671 // getNumberMintedPerSubedition function that return's
1672 // the number of Moments that have been minted for this subedition
1673 // to use as this Moment's serial number
1674 //
1675 // Parameters: setID: The ID of the Set Moment will be minted from
1676 // playID: The ID of the Play Moment will be minted from
1677 // subeditionID: The ID of the Subedition using which moment will be minted
1678 //
1679 // returns: UInt32 Number of Moments, already minted for this Subedition
1680 //
1681 access(all) fun getNumberMintedPerSubedition(setID: UInt32, playID: UInt32, subeditionID: UInt32): UInt32 {
1682 let setPlaySubedition = self.getSetPlaySubeditionString(setID, playID, subeditionID)
1683 if !self.numberMintedPerSubedition.containsKey(setPlaySubedition) {
1684 self.numberMintedPerSubedition.insert(key: setPlaySubedition, UInt32(0))
1685 return UInt32(0)
1686 }
1687 return self.numberMintedPerSubedition[setPlaySubedition]!
1688 }
1689
1690 // addToNumberMintedPerSubedition function that increments 1 to the
1691 // number of Moments that have been minted for this subedition
1692 //
1693 // Parameters: setID: The ID of the Set Moment will be minted from
1694 // playID: The ID of the Play Moment will be minted from
1695 // subeditionID: The ID of the Subedition using which moment will be minted
1696 //
1697 //
1698 access(contract) fun addToNumberMintedPerSubedition(setID: UInt32, playID: UInt32, subeditionID: UInt32) {
1699 let setPlaySubedition = self.getSetPlaySubeditionString(setID, playID, subeditionID)
1700
1701 // Get number of moments minted for this subedition
1702 let numberMinted = self.numberMintedPerSubedition[setPlaySubedition]
1703 ?? panic("Could not find number of moments minted for specified Subedition!")
1704
1705 // Increment the number of moments minted for this subedition
1706 self.numberMintedPerSubedition[setPlaySubedition] = numberMinted + UInt32(1)
1707 }
1708
1709 // getSetPlaySubeditionString builds a string that is used as a key in the numberMintedPerSubedition map
1710 access(self) view fun getSetPlaySubeditionString(_ setID: UInt32, _ playID: UInt32, _ subeditionID: UInt32): String {
1711 return setID.toString().concat(playID.toString()).concat(subeditionID.toString())
1712 }
1713
1714
1715 // setMomentsSubedition saves which Subedition the Moment belongs to
1716 //
1717 // Parameters: nftID: The ID of the NFT
1718 // subeditionID: The ID of the Subedition the Moment belongs to
1719 // setID: The ID of the Set that the Moment references
1720 // playID: The ID of the Play that the Moment references
1721 //
1722 access(all) fun setMomentsSubedition(nftID: UInt64, subeditionID: UInt32, setID: UInt32, playID: UInt32) {
1723 pre {
1724 !self.momentsSubedition.containsKey(nftID) : "Subedition for this moment already exists!"
1725 }
1726
1727 self.momentsSubedition.insert(key: nftID, subeditionID)
1728
1729 emit SubeditionAddedToMoment(momentID: nftID, subeditionID: subeditionID, setID: setID, playID: playID)
1730 }
1731
1732 init() {
1733 self.momentsSubedition = {}
1734 self.numberMintedPerSubedition = {}
1735 self.subeditionDatas = {}
1736 self.nextSubeditionID = 1
1737 }
1738 }
1739
1740 //------------------------------------------------------------
1741 // Contract MetadataViews
1742 //------------------------------------------------------------
1743
1744 // getContractViews returns the metadata view types available for this contract
1745 access(all) view fun getContractViews(resourceType: Type?): [Type] {
1746 return [
1747 Type<MetadataViews.NFTCollectionData>(),
1748 Type<MetadataViews.NFTCollectionDisplay>(),
1749 Type<CrossVMMetadataViews.EVMPointer>(),
1750 Type<MetadataViews.Royalties>()
1751 ]
1752 }
1753
1754 // resolveContractView resolves this contract's metadata views
1755 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
1756 post {
1757 result == nil || result!.getType() == viewType: "The returned view must be of the given type or nil"
1758 }
1759 switch viewType {
1760 case Type<MetadataViews.NFTCollectionData>():
1761 return MetadataViews.NFTCollectionData(
1762 storagePath: /storage/MomentCollection,
1763 publicPath: /public/MomentCollection,
1764 publicCollection: Type<&Collection>(),
1765 publicLinkedType: Type<&Collection>(),
1766 createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
1767 return <- TopShot.createEmptyCollection(nftType: Type<@NFT>())
1768 })
1769 )
1770 case Type<MetadataViews.NFTCollectionDisplay>():
1771 let bannerImage = MetadataViews.Media(
1772 file: MetadataViews.HTTPFile(
1773 url: "https://nbatopshot.com/static/img/top-shot-logo-horizontal-white.svg"
1774 ),
1775 mediaType: "image/svg+xml"
1776 )
1777 let squareImage = MetadataViews.Media(
1778 file: MetadataViews.HTTPFile(
1779 url: "https://nbatopshot.com/static/favicon/favicon.svg"
1780 ),
1781 mediaType: "image/svg+xml"
1782 )
1783 return MetadataViews.NFTCollectionDisplay(
1784 name: "NBA Top Shot",
1785 description: "NBA Top Shot is your chance to own, sell, and trade official digital collectibles of the NBA and WNBA's greatest plays and players",
1786 externalURL: MetadataViews.ExternalURL("https://nbatopshot.com"),
1787 squareImage: squareImage,
1788 bannerImage: bannerImage,
1789 socials: {
1790 "twitter": MetadataViews.ExternalURL("https://twitter.com/nbatopshot"),
1791 "discord": MetadataViews.ExternalURL("https://discord.com/invite/nbatopshot"),
1792 "instagram": MetadataViews.ExternalURL("https://www.instagram.com/nbatopshot")
1793 }
1794 )
1795 case Type<MetadataViews.Royalties>():
1796 let royaltyReceiver: Capability<&{FungibleToken.Receiver}> =
1797 getAccount(TopShot.RoyaltyAddress()).capabilities.get<&{FungibleToken.Receiver}>(MetadataViews.getRoyaltyReceiverPublicPath())!
1798 return MetadataViews.Royalties(
1799 [
1800 MetadataViews.Royalty(
1801 receiver: royaltyReceiver,
1802 cut: 0.05,
1803 description: "NBATopShot marketplace royalty"
1804 )
1805 ]
1806 )
1807 case Type<CrossVMMetadataViews.EVMPointer>():
1808 return CrossVMMetadataViews.EVMPointer(
1809 cadenceType: Type<@TopShot.NFT>(),
1810 cadenceContractAddress: self.account.address,
1811 evmContractAddress: EVM.addressFromString("0x84c6a2e6765e88427c41bb38c82a78b570e24709"),
1812 nativeVM: CrossVMMetadataViews.VM.Cadence
1813 )
1814 }
1815 return nil
1816 }
1817
1818 // -----------------------------------------------------------------------
1819 // TopShot initialization function
1820 // -----------------------------------------------------------------------
1821 //
1822 init() {
1823 // Initialize contract fields
1824 self.currentSeries = 0
1825 self.playDatas = {}
1826 self.setDatas = {}
1827 self.sets <- {}
1828 self.nextPlayID = 1
1829 self.nextSetID = 1
1830 self.totalSupply = 0
1831
1832 // Put a new Collection in storage
1833 self.account.storage.save<@Collection>(<- create Collection(), to: /storage/MomentCollection)
1834
1835 // Create and publish a capability for the collection
1836 self.account.capabilities.publish(
1837 self.account.capabilities.storage.issue<&Collection>(/storage/MomentCollection),
1838 at: /public/MomentCollection
1839 )
1840
1841 // Put the Minter in storage
1842 self.account.storage.save<@Admin>(<- create Admin(), to: /storage/TopShotAdmin)
1843 }
1844 }
FunctionCallsSelf ACIR opcodes
<transaction>10
<toplevel>10
<host>10
FlowFees.verifyPayersBalanceForTransactionExecution10
FlowFees.computeFees20
FlowFees.getFeeParameters20
FlowStorageFees.defaultTokenReservedBalance10
FlowStorageFees.convertUInt64StorageBytesToUFix64Megabytes10
FlowStorageFees.storageCapacityToFlow10
FlowFees.VerifyPayerBalanceResult.init10
prepare10
TopShot.resolveContractView10
MetadataViews.NFTCollectionData.init10
DapperOffersV2.DapperOffer.addProxyCapability10
main10
execute10
OffersV2.Royalty.init10
DapperOffersV2.DapperOffer.createOffer10
OffersV2.makeOffer10
OffersV2.Offer.init10
OffersV2.OfferDetails.init10
FlowStorageFees.getAccountsCapacityForTransactionStorageCheck10
FlowStorageFees.accountBalanceToAccountStorageCapacity20
FlowStorageFees.flowToStorageCapacity20
FlowFees.deductTransactionFee20
FungibleToken.Vault.withdraw20
FlowToken.Vault.withdraw10
FlowToken.Vault.init10
FungibleToken.Provider.withdraw10
FlowFees.collectFeesOnChildAccounts20
FlowFees.borrowChildFeeAccounts10
FungibleToken.Vault.deposit10
FlowToken.Vault.deposit10
By function — self cost, callees excluded.Call order
FrameACIR opcodes
<transaction>
<toplevel>:1
<host>:237
FlowFees.verifyPayersBalanceForTransactionExecution:237
FlowFees.computeFees:335
FlowFees.getFeeParameters:322
FlowStorageFees.defaultTokenReservedBalance:158
FlowStorageFees.convertUInt64StorageBytesToUFix64Megabytes:134
FlowStorageFees.storageCapacityToFlow:123
FlowFees.VerifyPayerBalanceResult.init:217
prepare:18
TopShot.resolveContractView:1759
MetadataViews.NFTCollectionData.init:691
DapperOffersV2.DapperOffer.addProxyCapability:116
main:103
execute:108
OffersV2.Royalty.init:71
DapperOffersV2.DapperOffer.createOffer:150
OffersV2.makeOffer:343
OffersV2.Offer.init:172
OffersV2.OfferDetails.init:116
FlowStorageFees.getAccountsCapacityForTransactionStorageCheck:87
FlowStorageFees.accountBalanceToAccountStorageCapacity:105
FlowStorageFees.flowToStorageCapacity:117
FlowStorageFees.accountBalanceToAccountStorageCapacity:105
FlowStorageFees.flowToStorageCapacity:117
FlowFees.deductTransactionFee:268
FlowFees.computeFees:335
FlowFees.getFeeParameters:322
FungibleToken.Vault.withdraw:234
FlowToken.Vault.withdraw:79
FlowToken.Vault.init:44
FungibleToken.Provider.withdraw:126
FungibleToken.Vault.withdraw:228
FlowFees.deductTransactionFee:291
FlowFees.collectFeesOnChildAccounts:300
FlowFees.borrowChildFeeAccounts:58
FungibleToken.Vault.deposit:259
FlowToken.Vault.deposit:116
FlowFees.collectFeesOnChildAccounts:305
By call order.Self cost

40 recorded entries — every call this recording opened, in the order it opened them, and 0 storage writes. Each row's step is its coordinate in the recording. The replay's reproduced effects are published as a count rather than one record each, so they are in the transaction pane and not in this timeline.

→call0<transaction>
→call0<toplevel>t.c8f9190752baabcbe010c201d4b34bcda9c3de91b53a6fe6accf8d95442e0611.cdc:1
→call1<host>A.f919ee77447b7497.FlowFees.cdc:237
→call1FlowFees.verifyPayersBalanceForTransactionExecutionA.f919ee77447b7497.FlowFees.cdc:237
→call2FlowFees.computeFeesA.f919ee77447b7497.FlowFees.cdc:335
→call3FlowFees.getFeeParametersA.f919ee77447b7497.FlowFees.cdc:322
→call7FlowStorageFees.defaultTokenReservedBalanceA.e467b9dd11fa00df.FlowStorageFees.cdc:158
→call9FlowStorageFees.convertUInt64StorageBytesToUFix64MegabytesA.e467b9dd11fa00df.FlowStorageFees.cdc:134
→call12FlowStorageFees.storageCapacityToFlowA.e467b9dd11fa00df.FlowStorageFees.cdc:123
→call22FlowFees.VerifyPayerBalanceResult.initA.f919ee77447b7497.FlowFees.cdc:217
→call25preparet.c8f9190752baabcbe010c201d4b34bcda9c3de91b53a6fe6accf8d95442e0611.cdc:18
→call26TopShot.resolveContractViewA.0b2a3299cc857e29.TopShot.cdc:1759
→call28MetadataViews.NFTCollectionData.initA.1d7e57aa55817448.MetadataViews.cdc:691
→call46DapperOffersV2.DapperOffer.addProxyCapabilityA.b8ea91944fd51c43.DapperOffersV2.cdc:116
→call54maint.c8f9190752baabcbe010c201d4b34bcda9c3de91b53a6fe6accf8d95442e0611.cdc:103
→call56executet.c8f9190752baabcbe010c201d4b34bcda9c3de91b53a6fe6accf8d95442e0611.cdc:108
→call59OffersV2.Royalty.initA.b8ea91944fd51c43.OffersV2.cdc:71
→call68DapperOffersV2.DapperOffer.createOfferA.b8ea91944fd51c43.DapperOffersV2.cdc:150
→call69OffersV2.makeOfferA.b8ea91944fd51c43.OffersV2.cdc:343
→call70OffersV2.Offer.initA.b8ea91944fd51c43.OffersV2.cdc:172
→call83OffersV2.OfferDetails.initA.b8ea91944fd51c43.OffersV2.cdc:116
→call99FlowStorageFees.getAccountsCapacityForTransactionStorageCheckA.e467b9dd11fa00df.FlowStorageFees.cdc:87
→call104FlowStorageFees.accountBalanceToAccountStorageCapacityA.e467b9dd11fa00df.FlowStorageFees.cdc:105
→call106FlowStorageFees.flowToStorageCapacityA.e467b9dd11fa00df.FlowStorageFees.cdc:117
→call111FlowStorageFees.accountBalanceToAccountStorageCapacityA.e467b9dd11fa00df.FlowStorageFees.cdc:105
→call113FlowStorageFees.flowToStorageCapacityA.e467b9dd11fa00df.FlowStorageFees.cdc:117
→call115FlowFees.deductTransactionFeeA.f919ee77447b7497.FlowFees.cdc:268
→call116FlowFees.computeFeesA.f919ee77447b7497.FlowFees.cdc:335
→call117FlowFees.getFeeParametersA.f919ee77447b7497.FlowFees.cdc:322
→call124FungibleToken.Vault.withdrawA.f233dcee88fe0abe.FungibleToken.cdc:234
→call127FlowToken.Vault.withdrawA.1654653399040a61.FlowToken.cdc:79
→call132FlowToken.Vault.initA.1654653399040a61.FlowToken.cdc:44
→call133FungibleToken.Provider.withdrawA.f233dcee88fe0abe.FungibleToken.cdc:126
→call134FungibleToken.Vault.withdrawA.f233dcee88fe0abe.FungibleToken.cdc:228
→call137FlowFees.deductTransactionFeeA.f919ee77447b7497.FlowFees.cdc:291
→call138FlowFees.collectFeesOnChildAccountsA.f919ee77447b7497.FlowFees.cdc:300
→call139FlowFees.borrowChildFeeAccountsA.f919ee77447b7497.FlowFees.cdc:58
→call142FungibleToken.Vault.depositA.f233dcee88fe0abe.FungibleToken.cdc:259
→call150FlowToken.Vault.depositA.1654653399040a61.FlowToken.cdc:116
→call159FlowFees.collectFeesOnChildAccountsA.f919ee77447b7497.FlowFees.cdc:305
  • VALUES

The recorded values are in the published recording. Reading them needs the replay engine, which this page has not started.