Smart Contract

RogueBunnies_NFT

A.396646f110afb2e6.RogueBunnies_NFT

Valid From

85,998,639

Deployed

1w ago
Feb 16, 2026, 03:05:26 AM UTC

Dependents

3396 imports
1import Burner from 0xf233dcee88fe0abe
2import FungibleToken from 0xf233dcee88fe0abe
3import NonFungibleToken from 0x1d7e57aa55817448
4import MetadataViews from 0x1d7e57aa55817448
5import ViewResolver from 0x1d7e57aa55817448
6
7access(all) contract RogueBunnies_NFT: NonFungibleToken {
8
9    // An entitlement to perform Admin actions on this contract
10    access(all) entitlement Administrator
11
12    // RogueBunnies_NFT Events
13    //
14
15    // Emitted when an NFT is minted
16    access(all) event Minted(id: UInt64, setId: UInt32, seriesId: UInt32)
17
18    // Events for Series-related actions
19    //
20    // Emitted when a new Series is created
21    access(all) event SeriesCreated(seriesId: UInt32)
22    // Emitted when a Series is sealed, meaning Series metadata
23    // cannot be updated
24    access(all) event SeriesSealed(seriesId: UInt32)
25    // Emitted when a Series' metadata is updated
26    access(all) event SeriesMetadataUpdated(seriesId: UInt32)
27
28    // NFT Withdraw, Deposit, and Burn Events
29    access(all) event Withdraw(id: UInt64, from: Address?)
30    access(all) event Deposit(id: UInt64, to: Address?)
31    access(all) event NFTDestroyed(id: UInt64)
32
33    // Events for Set-related actions
34    //
35    // Emitted when a new Set is created
36    access(all) event SetCreated(seriesId: UInt32, setId: UInt32)
37    // Emitted when a Set's metadata is updated
38    access(all) event SetMetadataUpdated(seriesId: UInt32, setId: UInt32)
39
40    // Named Paths
41    //
42    access(all) let CollectionStoragePath: StoragePath
43    access(all) let CollectionPublicPath: PublicPath
44    access(all) let AdminStoragePath: StoragePath
45
46    // Variable size dictionary of SetData structs
47    access(self) var setData: {UInt32: NFTSetData}
48
49    // Variable size dictionary of SeriesData structs
50    access(self) var seriesData: {UInt32: SeriesData}
51
52    // Variable size dictionary of Series resources
53    access(self) var series: @{UInt32: Series}
54
55
56    // An NFTSetData is a Struct that holds metadata associated with
57    // a specific NFT Set.
58    access(all) struct NFTSetData {
59
60        // Unique ID for the Set
61        access(all) let setId: UInt32
62
63        // Series ID the Set belongs to
64        access(all) let seriesId: UInt32
65
66        // Maximum number of editions that can be minted in this Set
67        access(all) let maxEditions: UInt32
68                  
69        // The JSON metadata for each NFT edition can be stored off-chain on IPFS.
70        // This is an optional dictionary of IPFS hashes, which will allow marketplaces
71        // to pull the metadata for each NFT edition
72        access(self) var ipfsMetadataHashes: {UInt32: String}
73
74        // Set level metadata
75        // Dictionary of metadata key value pairs
76        access(self) var metadata: {String: String}
77        
78        init(
79            setId: UInt32,
80            seriesId: UInt32,
81            maxEditions: UInt32,
82            ipfsMetadataHashes: {UInt32: String},
83            metadata: {String: String}) {
84
85            self.setId = setId
86            self.seriesId = seriesId
87            self.maxEditions = maxEditions
88            self.metadata = metadata
89            self.ipfsMetadataHashes = ipfsMetadataHashes
90        }
91
92        access(all) fun getIpfsMetadataHash(editionNum: UInt32): String? {
93            return self.ipfsMetadataHashes[editionNum]
94        }
95
96        access(all) fun getMetadata(): {String: String} {
97            return self.metadata
98        }
99
100        access(all) fun getMetadataField(field: String): String? {
101            return self.metadata[field]
102        }
103    }
104
105    // A SeriesData is a struct that groups metadata for a 
106    // a related group of NFTSets.
107    access(all) struct SeriesData {
108
109        // Unique ID for the Series
110        access(all) let seriesId: UInt32
111
112        // Dictionary of metadata key value pairs
113        access(self) var metadata: {String: String}
114
115        init(
116            seriesId: UInt32,
117            metadata: {String: String}) {
118            self.seriesId = seriesId
119            self.metadata = metadata
120        }
121
122        access(all) fun getMetadata(): {String: String} {
123            return self.metadata
124        }
125    }
126
127
128    // A Series is special resource type that contains functions to mint RogueBunnies_NFT NFTs, 
129    // add NFTSets, update NFTSet and Series metadata, and seal Series.
130	access(all) resource Series {
131
132        // Unique ID for the Series
133        access(all) let seriesId: UInt32
134
135        // Array of NFTSets that belong to this Series
136        access(all) var setIds: [UInt32]
137
138        // Series sealed state
139        access(all) var seriesSealedState: Bool;
140
141        // Set sealed state
142        access(self) var setSealedState: {UInt32: Bool};
143
144        // Current number of editions minted per Set
145        access(all) var numberEditionsMintedPerSet: {UInt32: UInt32}
146
147        init(
148            seriesId: UInt32,
149            metadata: {String: String}) {
150
151            self.seriesId = seriesId
152            self.seriesSealedState = false
153            self.numberEditionsMintedPerSet = {}
154            self.setIds = []
155            self.setSealedState = {}
156
157            RogueBunnies_NFT.seriesData[seriesId] = SeriesData(
158                    seriesId: seriesId,
159                    metadata: metadata
160            )
161
162            emit SeriesCreated(seriesId: seriesId)   
163        }
164
165        access(all) fun addNftSet(
166            setId: UInt32,
167            maxEditions: UInt32,
168            ipfsMetadataHashes: {UInt32: String},
169            metadata: {String: String}) {
170            pre {
171                self.setIds.contains(setId) == false: "The Set has already been added to the Series."
172            }
173
174            // Create the new Set struct
175            var newNFTSet = NFTSetData(
176                setId: setId,
177                seriesId: self.seriesId,
178                maxEditions: maxEditions,
179                ipfsMetadataHashes: ipfsMetadataHashes,
180                metadata: metadata
181            )
182
183            // Add the NFTSet to the array of Sets
184            self.setIds.append(setId)
185
186            // Initialize the NFT edition count to zero
187            self.numberEditionsMintedPerSet[setId] = 0
188
189            // Store it in the sets mapping field
190            RogueBunnies_NFT.setData[setId] = newNFTSet
191
192            emit SetCreated(seriesId: self.seriesId, setId: setId)
193        }
194
195        // updateSeriesMetadata
196        // For practical reasons, a short period of time is given to update metadata
197        // following Series creation or minting of the NFT editions. Once the Series is
198        // sealed, no updates to the Series metadata will be possible - the information
199        // is permanent and immutable.
200        access(all) fun updateSeriesMetadata(metadata: {String: String}) {
201            pre {
202                self.seriesSealedState == false:
203                    "The Series is permanently sealed. No metadata updates can be made."
204            }
205            let newSeriesMetadata = SeriesData(
206                    seriesId: self.seriesId,
207                    metadata: metadata
208            )  
209            // Store updated Series in the Series mapping field
210            RogueBunnies_NFT.seriesData[self.seriesId] = newSeriesMetadata
211
212            emit SeriesMetadataUpdated(seriesId: self.seriesId)
213        }
214
215        // updateSetMetadata
216        // For practical reasons, a short period of time is given to update metadata
217        // following Set creation or minting of the NFT editions. Once the Series is
218        // sealed, no updates to the Set metadata will be possible - the information
219        // is permanent and immutable.
220        access(all) fun updateSetMetadata(
221            setId: UInt32,
222            maxEditions: UInt32,
223            ipfsMetadataHashes: {UInt32: String},
224            metadata: {String: String}) {
225            pre {
226                self.seriesSealedState == false:
227                    "The Series is permanently sealed. No metadata updates can be made."
228                self.setIds.contains(setId) == true: "The Set is not part of this Series."
229            }
230            let newSetMetadata = NFTSetData(
231                setId: setId,
232                seriesId: self.seriesId,
233                maxEditions: maxEditions,
234                ipfsMetadataHashes: ipfsMetadataHashes,
235                metadata: metadata
236            )
237            // Store updated Set in the Sets mapping field
238            RogueBunnies_NFT.setData[setId] = newSetMetadata
239
240            emit SetMetadataUpdated(seriesId: self.seriesId, setId: setId)
241        }
242
243		// mintRogueBunnies_NFT
244        // Mints a new NFT with a new ID
245		// and deposits it in the recipients collection using their collection reference
246        //
247	    access(all) fun mintRogueBunnies_NFT(
248            recipient: &{NonFungibleToken.CollectionPublic},
249            tokenId: UInt64,
250            setId: UInt32) {
251            
252            pre {
253                self.numberEditionsMintedPerSet[setId] != nil: "The Set does not exist."
254                self.numberEditionsMintedPerSet[setId]! <= RogueBunnies_NFT.getSetMaxEditions(setId: setId)!:
255                    "Set has reached maximum NFT edition capacity."
256            }
257
258            // Gets the number of editions that have been minted so far in 
259            // this set
260            let editionNum: UInt32 = self.numberEditionsMintedPerSet[setId]! + 1
261
262			// deposit it in the recipient's account using their reference
263			recipient.deposit(token: <-create RogueBunnies_NFT.NFT(
264                tokenId: tokenId,
265                setId: setId,
266                editionNum: editionNum
267            ))
268
269            // Update the count of Editions minted in the set
270            self.numberEditionsMintedPerSet[setId] = editionNum
271        }
272
273        // mintEditionRogueBunnies_NFT
274        // Mints a new NFT with a new ID and specific edition Num (random open edition)
275		// and deposits it in the recipients collection using their collection reference
276        //
277	    access(all) fun mintEditionRogueBunnies_NFT(
278            recipient: &{NonFungibleToken.CollectionPublic},
279            tokenId: UInt64,
280            setId: UInt32,
281            edition: UInt32) {
282            
283            pre {
284                self.numberEditionsMintedPerSet[setId] != nil: "The Set does not exist."
285                self.numberEditionsMintedPerSet[setId]! <= RogueBunnies_NFT.getSetMaxEditions(setId: setId)!:
286                    "Set has reached maximum NFT edition capacity."
287            }
288
289			// deposit it in the recipient's account using their reference
290			recipient.deposit(token: <-create RogueBunnies_NFT.NFT(
291                tokenId: tokenId,
292                setId: setId,
293                editionNum: edition
294            ))
295
296            // Update the count of Editions minted in the set
297            self.numberEditionsMintedPerSet[setId] = self.numberEditionsMintedPerSet[setId]! + 1
298        }
299
300        // batchMintRogueBunnies_NFT
301        // Mints multiple new NFTs given and deposits the NFTs
302        // into the recipients collection using their collection reference
303		access(all) fun batchMintRogueBunnies_NFT(
304            recipient: &{NonFungibleToken.CollectionPublic},
305            setId: UInt32,
306            tokenIds: [UInt64]) {
307
308            pre {
309                tokenIds.length > 0:
310                    "Number of token Ids must be > 0"
311            }
312
313            for tokenId in tokenIds {
314                self.mintRogueBunnies_NFT(
315                    recipient: recipient,
316                    tokenId: tokenId,
317                    setId: setId
318                )
319            }
320		}
321
322        // sealSeries
323        // Once a series is sealed, the metadata for the NFTs in the Series can no
324        // longer be updated
325        //
326        access(all) fun sealSeries() {
327            pre {
328                self.seriesSealedState == false: "The Series is already sealed"
329            }
330            self.seriesSealedState = true
331
332            emit SeriesSealed(seriesId: self.seriesId)
333        }
334	}
335
336    // A resource that represents the RogueBunnies_NFT NFT
337    //
338    access(all) resource NFT: NonFungibleToken.NFT, Burner.Burnable {
339        // The token's ID
340        access(all) let id: UInt64
341
342        // The Set id references this NFT belongs to
343        access(all) let setId: UInt32
344
345        // The specific edition number for this NFT
346        access(all) let editionNum: UInt32
347
348        // initializer
349        //
350        init(
351          tokenId: UInt64,
352          setId: UInt32,
353          editionNum: UInt32) {
354
355            self.id = tokenId
356            self.setId = setId
357            self.editionNum = editionNum
358
359            let seriesId = RogueBunnies_NFT.getSetSeriesId(setId: setId)!
360
361            emit Minted(id: self.id, setId: setId, seriesId: seriesId)
362        }
363
364        access (contract) fun burnCallback() {
365            emit NFTDestroyed(id: self.id)
366        }
367
368        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
369            return <- RogueBunnies_NFT.createEmptyCollection(nftType: Type<@RogueBunnies_NFT.NFT>())
370        }
371
372        access(all) view fun getViews(): [Type] {
373            return [
374                Type<MetadataViews.Display>(),
375                Type<MetadataViews.Royalties>(),
376                Type<MetadataViews.Editions>(),
377                Type<MetadataViews.ExternalURL>(),
378                Type<MetadataViews.NFTCollectionData>(),
379                Type<MetadataViews.NFTCollectionDisplay>(),
380                Type<MetadataViews.Serial>(),
381                Type<MetadataViews.Traits>(),
382                Type<MetadataViews.Medias>()
383            ]
384        }
385        access(all) fun resolveView(_ view: Type): AnyStruct? {
386            switch view {
387                case Type<MetadataViews.Display>():
388                    return MetadataViews.Display(
389                        name: RogueBunnies_NFT.getSetMetadataByField(setId: self.setId, field: "name")!,
390                        description: RogueBunnies_NFT.getSetMetadataByField(setId: self.setId, field: "description")!,
391                        thumbnail: MetadataViews.HTTPFile(
392                            url: RogueBunnies_NFT.getSetMetadataByField(setId: self.setId, field: "preview")!
393                        )
394                    )
395                case Type<MetadataViews.Serial>():
396                    return MetadataViews.Serial(
397                        self.id
398                    )
399                case Type<MetadataViews.Editions>():
400                    let maxEditions = RogueBunnies_NFT.setData[self.setId]?.maxEditions ?? 0
401                    let editionName = RogueBunnies_NFT.getSetMetadataByField(setId: self.setId, field: "name")!
402                    let editionInfo = MetadataViews.Edition(name: editionName, number: UInt64(self.editionNum), max: maxEditions > 0 ? UInt64(maxEditions) : nil)
403                    let editionList: [MetadataViews.Edition] = [editionInfo]
404                    return MetadataViews.Editions(
405                        editionList
406                    )
407                case Type<MetadataViews.ExternalURL>():
408                    if let externalBaseURL = RogueBunnies_NFT.getSetMetadataByField(setId: self.setId, field: "external_token_base_url") {
409                        return MetadataViews.ExternalURL(externalBaseURL.concat("/").concat(self.id.toString()))
410                    }
411                    return MetadataViews.ExternalURL("")
412                case Type<MetadataViews.Royalties>():
413                    let royalties: [MetadataViews.Royalty] = []
414                    // There is only a legacy {String: String} dictionary to store royalty information.
415                    // There may be multiple royalty cuts defined per NFT. Pull each royalty
416                    // based on keys that have the "royalty_addr_" prefix in the dictionary.
417                    for metadataKey in RogueBunnies_NFT.getSetMetadata(setId: self.setId)!.keys {
418                        // For efficiency, only check keys that are > 13 chars, which is the length of "royalty_addr_" key
419                        if metadataKey.length >= 13 {
420                            if metadataKey.slice(from: 0, upTo: 13) == "royalty_addr_" {
421                                // A royalty has been found. Use the suffix from the key for the royalty name.
422                                let royaltyName = metadataKey.slice(from: 13, upTo: metadataKey.length)
423                                let royaltyAddress = RogueBunnies_NFT.convertStringToAddress(RogueBunnies_NFT.getSetMetadataByField(setId: self.setId, field: "royalty_addr_".concat(royaltyName))!)!
424                                let royaltyReceiver: PublicPath = PublicPath(identifier: RogueBunnies_NFT.getSetMetadataByField(setId: self.setId, field: "royalty_rcv_".concat(royaltyName))!)!
425                                let royaltyCut = RogueBunnies_NFT.getSetMetadataByField(setId: self.setId, field: "royalty_cut_".concat(royaltyName))!
426                                let cutValue: UFix64 = RogueBunnies_NFT.royaltyCutStringToUFix64(royaltyCut)
427                                if cutValue != 0.0 {
428                                    royalties.append(MetadataViews.Royalty(
429                                        receiver: getAccount(royaltyAddress).capabilities.get<&{FungibleToken.Receiver}>(royaltyReceiver),
430                                        cut: cutValue,
431                                        description: RogueBunnies_NFT.getSetMetadataByField(setId: self.setId, field: "royalty_desc_".concat(royaltyName))!
432                                    )
433                                    )
434                                }
435                            }
436                        }
437                    }
438                    return MetadataViews.Royalties(royalties)
439                case Type<MetadataViews.NFTCollectionData>():
440                    return RogueBunnies_NFT.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.NFTCollectionData>())
441                case Type<MetadataViews.NFTCollectionDisplay>():
442                    return RogueBunnies_NFT.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.NFTCollectionDisplay>())
443                case Type<MetadataViews.Traits>():
444                    let traitDictionary: {String: AnyStruct} = {}
445                    // There is only a legacy {String: String} dictionary to store trait information.
446                    // There may be multiple traits defined per NFT. Pull trait information
447                    // based on keys that have the "trait_" prefix in the dictionary.
448                    for metadataKey in RogueBunnies_NFT.getSetMetadata(setId: self.setId)!.keys {
449                        // For efficiency, only check keys that are > 6 chars, which is the length of "trait_" key
450                        if metadataKey.length >= 6 {
451                            if metadataKey.slice(from: 0, upTo: 6) == "trait_" {
452                                // A trait has been found. Set the trait name to only the trait key suffix.
453                                traitDictionary.insert(key: metadataKey.slice(from: 6, upTo: metadataKey.length), RogueBunnies_NFT.getSetMetadataByField(setId: self.setId, field: metadataKey)!)
454                            }
455                        }
456                    }
457                    return MetadataViews.dictToTraits(dict: traitDictionary, excludedNames: [])
458                case Type<MetadataViews.Medias>():
459                    return MetadataViews.Medias(
460                        [
461                            MetadataViews.Media(
462                                file: MetadataViews.HTTPFile(
463                                    url: RogueBunnies_NFT.getSetMetadataByField(setId: self.setId, field: "image")!
464                                ),
465                                mediaType: self.getMimeType()
466                            )
467                        ]
468                    )
469            }
470            return nil
471        }
472
473        access(all) fun getMimeType(): String {
474            var metadataFileType = RogueBunnies_NFT.getSetMetadataByField(setId: self.setId, field: "image_file_type")!.toLower()
475            switch metadataFileType {
476                case "mp4":
477                    return "video/mp4"
478                case "mov":
479                    return "video/quicktime"
480                case "webm":
481                    return "video/webm"
482                case "ogv":
483                    return "video/ogg"
484                case "png":
485                    return "image/png"
486                case "jpeg":
487                    return "image/jpeg"
488                case "jpg":
489                    return "image/jpeg"
490                case "gif":
491                    return "image/gif"
492                case "webp":
493                    return "image/webp"
494                case "svg":
495                    return "image/svg+xml"
496                case "glb":
497                    return "model/gltf-binary"
498                case "gltf":
499                    return "model/gltf+json"
500                case "obj":
501                    return "model/obj"
502                case "mtl":
503                    return "model/mtl"
504                case "mp3":
505                    return "audio/mpeg"
506                case "ogg":
507                    return "audio/ogg"
508                case "oga":
509                    return "audio/ogg"
510                case "wav":
511                    return "audio/wav"
512                case "html":
513                    return "text/html"
514            }
515            return ""
516        }
517    }
518
519    // Admin is a special authorization resource that 
520    // allows the owner to perform important NFT 
521    // functions
522    //
523    access(all) resource Admin {
524
525        access(all) fun addSeries(seriesId: UInt32, metadata: {String: String}) {
526            pre {
527                RogueBunnies_NFT.series[seriesId] == nil:
528                    "Cannot add Series: The Series already exists"
529            }
530
531            // Create the new Series
532            var newSeries <- create Series(
533                seriesId: seriesId,
534                metadata: metadata
535            )
536
537            // Add the new Series resource to the Series dictionary in the contract
538            RogueBunnies_NFT.series[seriesId] <-! newSeries
539        }
540
541        access(all) fun borrowSeries(seriesId: UInt32): &Series  {
542            pre {
543                RogueBunnies_NFT.series[seriesId] != nil:
544                    "Cannot borrow Series: The Series does not exist"
545            }
546
547            // Get a reference to the Series and return it
548            return (&RogueBunnies_NFT.series[seriesId])!
549        }
550
551        access(all) fun createNewAdmin(): @Admin {
552            return <-create Admin()
553        }
554
555    }
556
557    // This is the interface that users can cast their NFT Collection as
558    // to allow others to deposit RogueBunnies_NFT into their Collection. It also allows for reading
559    // the details of RogueBunnies_NFT in the Collection.
560    access(all) resource interface RogueBunnies_NFTCollectionPublic : NonFungibleToken.CollectionPublic {
561        access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection})
562        access(all) fun borrowRogueBunnies_NFT(id: UInt64): &NFT? {
563            // If the result isn't nil, the id of the returned reference
564            // should be the same as the argument to the function
565            post {
566                (result == nil) || (result?.id == id): 
567                    "Cannot borrow RogueBunnies_NFT reference: The ID of the returned reference is incorrect"
568            }
569        }
570    }
571
572    // Collection
573    // A collection of RogueBunnies_NFT NFTs owned by an account
574    //
575    access(all) resource Collection: RogueBunnies_NFTCollectionPublic, NonFungibleToken.Collection {
576        // dictionary of NFT conforming tokens
577        // NFT is a resource type with an UInt64 ID field
578        //
579        access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
580
581        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
582            let supportedType: {Type: Bool} = {}
583            supportedType[Type<@RogueBunnies_NFT.NFT>()] = true
584            return supportedType
585        }
586
587        access(all) view fun isSupportedNFTType(type: Type): Bool {
588            if type == Type<@RogueBunnies_NFT.NFT>() {
589                return true
590            }
591            return false
592        }
593
594        // withdraw
595        // Removes an NFT from the collection and moves it to the caller
596        //
597        access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
598            let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
599            emit Withdraw(id: token.id, from: self.owner?.address)
600            return <-token
601        }
602
603        // batchWithdraw withdraws multiple NFTs and returns them as a Collection
604        //
605        // Parameters: ids: An array of IDs to withdraw
606        //
607        // Returns: @NonFungibleToken.Collection: The collection of withdrawn tokens
608        //
609
610        access(NonFungibleToken.Withdraw) fun batchWithdraw(ids: [UInt64]): @{NonFungibleToken.Collection} {
611            // Create a new empty Collection
612            var batchCollection <- create Collection()
613            
614            // Iterate through the ids and withdraw them from the Collection
615            for id in ids {
616                batchCollection.deposit(token: <-self.withdraw(withdrawID: id))
617            }
618            
619            // Return the withdrawn tokens
620            return <-batchCollection
621        }
622
623        // deposit
624        // Takes a NFT and adds it to the collections dictionary
625        // and adds the ID to the id array
626        //
627        access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
628            let token <- token as! @RogueBunnies_NFT.NFT
629
630            let id: UInt64 = token.id
631
632            // add the new token to the dictionary which removes the old one
633            let oldToken <- self.ownedNFTs[id] <- token
634            emit Deposit(id: id, to: self.owner?.address)
635            destroy oldToken
636        }
637
638        // batchDeposit takes a Collection object as an argument
639        // and deposits each contained NFT into this Collection
640        access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection}) {
641
642            // Get an array of the IDs to be deposited
643            let keys = tokens.getIDs()
644
645            // Iterate through the keys in the collection and deposit each one
646            for key in keys {
647                self.deposit(token: <-tokens.withdraw(withdrawID: key))
648            }
649
650            // Destroy the empty Collection
651            destroy tokens
652        }
653
654        // getIDs
655        // Returns an array of the IDs that are in the collection
656        //
657        access(all) view fun getIDs(): [UInt64] {
658            return self.ownedNFTs.keys
659        }
660
661        access(all) view fun getLength(): Int {
662            return self.ownedNFTs.keys.length
663        }
664
665        // borrowNFT
666        // Gets a reference to an NFT in the collection
667        // so that the caller can read its metadata and call its methods
668        //
669        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
670            return &self.ownedNFTs[id]
671        }
672
673        // borrowRogueBunnies_NFT
674        // Gets a reference to an NFT in the collection as a RogueBunnies_NFT,
675        // exposing all of its fields.
676        // This is safe as there are no functions that can be called on the RogueBunnies_NFT.
677        //
678        access(all) fun borrowRogueBunnies_NFT(id: UInt64): &NFT? {
679            if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
680                return nft as! &NFT
681            }
682            return nil
683        }
684
685        // borrowViewResolver
686        // Gets a reference to the MetadataViews resolver in the collection,
687        // giving access to all metadata information made available.
688        //
689        access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
690            if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
691                return nft as &{ViewResolver.Resolver}
692            }
693            return nil
694        }
695
696        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
697            return <- create Collection()
698        }
699
700        // initializer
701        //
702        init () {
703            self.ownedNFTs <- {}
704        }
705    }
706
707    // createEmptyCollection
708    // public function that anyone can call to create a new empty collection
709    //
710    access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
711        return <- create Collection()
712    }
713
714    access(all) view fun getContractViews(resourceType: Type?): [Type] {
715        return [
716            Type<MetadataViews.NFTCollectionData>(),
717            Type<MetadataViews.NFTCollectionDisplay>()
718        ]
719    }
720
721    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
722        switch viewType {
723            case Type<MetadataViews.NFTCollectionData>():
724                return MetadataViews.NFTCollectionData(
725                    storagePath: RogueBunnies_NFT.CollectionStoragePath,
726                    publicPath: RogueBunnies_NFT.CollectionPublicPath,
727                    publicCollection: Type<&RogueBunnies_NFT.Collection>(),
728                    publicLinkedType: Type<&RogueBunnies_NFT.Collection>(),
729                    createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
730                        return <-RogueBunnies_NFT.createEmptyCollection(nftType: Type<@RogueBunnies_NFT.NFT>())
731                    })
732                )
733            case Type<MetadataViews.NFTCollectionDisplay>():
734                let squareImage = MetadataViews.Media(
735                    file: MetadataViews.HTTPFile(
736                        url: "https://media-local.gigantik.io/roguebunnies/square.png"
737                    ),
738                    mediaType: "image/png"
739                )
740                let bannerImage = MetadataViews.Media(
741                    file: MetadataViews.HTTPFile(
742                        url: "https://media-local.gigantik.io/roguebunnies/banner.png"
743                    ),
744                    mediaType: "image/png"
745                )
746                var socials: {String: MetadataViews.ExternalURL} = {
747                    "twitter": MetadataViews.ExternalURL("https://x.com/theRoguebunnies"),
748                    "discord": MetadataViews.ExternalURL("https://discord.com/invite/qm2AbnNT9T"),
749                    "instagram": MetadataViews.ExternalURL("https://instagram.com/TheRogueBunnies")
750                }
751
752                return MetadataViews.NFTCollectionDisplay(
753                    name: "RogueBunnies shop",
754                    description: "This is the RogueBunnies card collection.",
755                    externalURL: MetadataViews.ExternalURL("https://tradingcards.roguebunnies.com"),
756                    squareImage: squareImage,
757                    bannerImage: bannerImage,
758                    socials: socials
759                )
760        }
761        return nil
762    }
763
764    // getAllSeries returns all the sets
765    //
766    // Returns: An array of all the series that have been created
767    access(all) fun getAllSeries(): [RogueBunnies_NFT.SeriesData] {
768        return RogueBunnies_NFT.seriesData.values
769    }
770
771    // getAllSets returns all the sets
772    //
773    // Returns: An array of all the sets that have been created
774    access(all) fun getAllSets(): [RogueBunnies_NFT.NFTSetData] {
775        return RogueBunnies_NFT.setData.values
776    }
777
778    // getSeriesMetadata returns the metadata that the specified Series
779    //            is associated with.
780    // 
781    // Parameters: seriesId: The id of the Series that is being searched
782    //
783    // Returns: The metadata as a String to String mapping optional
784    access(all) fun getSeriesMetadata(seriesId: UInt32): {String: String}? {
785        return RogueBunnies_NFT.seriesData[seriesId]?.getMetadata()
786    }
787
788    // getSetMaxEditions returns the the maximum number of NFT editions that can
789    //        be minted in this Set.
790    // 
791    // Parameters: setId: The id of the Set that is being searched
792    //
793    // Returns: The max number of NFT editions in this Set
794    access(all) view fun getSetMaxEditions(setId: UInt32): UInt32? {
795        return RogueBunnies_NFT.setData[setId]?.maxEditions
796    }
797
798    // getSetMetadata returns all the metadata associated with a specific Set
799    // 
800    // Parameters: setId: The id of the Set that is being searched
801    //
802    // Returns: The metadata as a String to String mapping optional
803    access(all) fun getSetMetadata(setId: UInt32): {String: String}? {
804        return RogueBunnies_NFT.setData[setId]?.getMetadata()
805    }
806
807    // getSetSeriesId returns the Series Id the Set belongs to
808    // 
809    // Parameters: setId: The id of the Set that is being searched
810    //
811    // Returns: The Series Id
812    access(all) fun getSetSeriesId(setId: UInt32): UInt32? {
813        return RogueBunnies_NFT.setData[setId]?.seriesId
814    }
815
816    // getSetMetadata returns all the ipfs hashes for each nft 
817    //     edition in the Set.
818    // 
819    // Parameters: setId: The id of the Set that is being searched
820    //
821    // Returns: The ipfs hashes of nft editions as a Array of Strings
822    access(all) fun getIpfsMetadataHashByNftEdition(setId: UInt32, editionNum: UInt32): String? {
823        // Don't force a revert if the setId or field is invalid
824        if let set = RogueBunnies_NFT.setData[setId] {
825            return set.getIpfsMetadataHash(editionNum: editionNum)
826        } else {
827            return nil
828        }
829    }
830
831    // getSetMetadataByField returns the metadata associated with a 
832    //                        specific field of the metadata
833    // 
834    // Parameters: setId: The id of the Set that is being searched
835    //             field: The field to search for
836    //
837    // Returns: The metadata field as a String Optional
838    access(all) fun getSetMetadataByField(setId: UInt32, field: String): String? {
839        // Don't force a revert if the setId or field is invalid
840        if let set = RogueBunnies_NFT.setData[setId] {
841            return set.getMetadataField(field: field)
842        } else {
843            return nil
844        }
845    }
846
847    // stringToAddress Converts a string to a Flow address
848    // 
849    // Parameters: input: The address as a String
850    //
851    // Returns: The flow address as an Address Optional
852	access(all) fun convertStringToAddress(_ input: String): Address? {
853		var address=input
854		if input.utf8[1] == 120 {
855			address = input.slice(from: 2, upTo: input.length)
856		}
857		var r:UInt64 = 0 
858		var bytes = address.decodeHex()
859
860		while bytes.length>0{
861			r = r  + (UInt64(bytes.removeFirst()) << UInt64(bytes.length * 8 ))
862		}
863
864		return Address(r)
865	}
866
867    // royaltyCutStringToUFix64 Converts a royalty cut string
868    //        to a UFix64
869    // 
870    // Parameters: royaltyCut: The cut value 0.0 - 1.0 as a String
871    //
872    // Returns: The royalty cut as a UFix64
873    access(all) fun royaltyCutStringToUFix64(_ royaltyCut: String): UFix64 {
874        var decimalPos = 0
875        if royaltyCut[0] == "." {
876            decimalPos = 1
877        } else if royaltyCut[1] == "." {
878            if royaltyCut[0] == "1" {
879                // "1" in the first postiion must be 1.0 i.e. 100% cut
880                return 1.0
881            } else if royaltyCut[0] == "0" {
882                decimalPos = 2
883            }
884        } else {
885            // Invalid royalty value
886            return 0.0
887        }
888
889        var royaltyCutStrLen = royaltyCut.length
890        if royaltyCut.length > (8 + decimalPos) {
891            // UFix64 is capped at 8 digits after the decimal
892            // so truncate excess decimal values from the string
893            royaltyCutStrLen = (8 + decimalPos)
894        }
895        let royaltyCutPercentValue = royaltyCut.slice(from: decimalPos, upTo: royaltyCutStrLen)
896        var bytes = royaltyCutPercentValue.utf8
897        var i = 0
898        var cutValueInteger: UInt64 = 0
899        var cutValueDivisor: UFix64 = 1.0
900        let zeroAsciiIntValue: UInt64 = 48
901        // First convert the string to a non-decimal Integer
902        while i < bytes.length {
903            cutValueInteger = (cutValueInteger * 10) + UInt64(bytes[i]) - zeroAsciiIntValue
904            cutValueDivisor = cutValueDivisor * 10.0
905            i = i + 1
906        }
907
908        // Convert the resulting Integer to a decimal in the range 0.0 - 0.99999999
909        return (UFix64(cutValueInteger) / cutValueDivisor)
910    }
911
912    // initializer
913    //
914	init() {
915        // Set named paths
916        self.CollectionStoragePath = /storage/RogueBunnies_NFTCollection
917        self.CollectionPublicPath = /public/RogueBunnies_NFTCollection
918        self.AdminStoragePath = /storage/RogueBunnies_NFTAdmin
919
920        self.setData = {}
921        self.seriesData = {}
922        self.series <- {}
923
924        // Put Admin in storage
925        self.account.storage.save(<-create Admin(), to: self.AdminStoragePath)
926
927        let collectionCap: Capability<&RogueBunnies_NFT.Collection> = self.account.capabilities.storage.issue<&RogueBunnies_NFT.Collection>(self.CollectionStoragePath)
928        self.account.capabilities.publish(collectionCap, at: self.CollectionPublicPath)
929	}
930}
931