Smart Contract
RogueBunnies_NFT
A.396646f110afb2e6.RogueBunnies_NFT
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