Smart Contract
YBees
A.20187093790b9aef.YBees
1/*
2 Description:
3
4 authors: Joseph Djenandji, Matthew Balazsi, Jennifer McIntyre
5
6 This smart contract contains the core functionality for Mint PFP NFT.
7
8 MINT is a platform where teams can create a fully branded environment to sell NFTs and launch branded marketplaces.
9 This will give fans a fully immersive experience as they interact with drops, buy and sell in the marketplace, and
10 deepen their relationships with the brand.
11
12
13 Enjoy!
14*/
15
16// import NonFungibleToken from "./NonFungibleToken.cdc"
17// import MetadataViews from "./MetadataViews.cdc"
18
19// for tests
20// import NonFungibleToken from "0xNonFungibleToken"
21// import ViewResolver from "0xViewResolver"
22// import MetadataViews from "0xMetadataViews"
23// import FungibleToken from "0xFungibleToken"
24
25// for testnet
26// import NonFungibleToken from 0x631e88ae7f1d7c20
27// import MetadataViews from 0x631e88ae7f1d7c20
28// import ViewResolver from 0x631e88ae7f1d7c20
29
30// for mainnet
31import NonFungibleToken from 0x1d7e57aa55817448
32import MetadataViews from 0x1d7e57aa55817448
33import ViewResolver from 0x1d7e57aa55817448
34
35
36access(all) contract YBees: NonFungibleToken {
37
38 // -----------------------------------------------------------------------
39 // MintPFPs contract Events
40 // -----------------------------------------------------------------------
41
42 // Emitted when the MintPFPs contract is created
43 access(all) event ContractInitialized()
44
45
46 // Emitted when a new item was minted
47 access(all) event ItemMinted(itemID:UInt64, merchantID: UInt32, name: String)
48
49 // Item related events
50 //
51 // Emitted when an Item is withdrawn from a Collection
52 access(all) event Withdraw(id: UInt64, from: Address?)
53 // Emitted when an Item is deposited into a Collection
54 access(all) event Deposit(id: UInt64, to: Address?)
55 // Emitted when an Item is withdrawn from a Collection
56 access(all) event ItemMutated(id: UInt64, mutation: ItemData)
57 // Emitted when adding a default royalty recipient
58 access(all) event DefaultRoyaltyAdded(name: String, rate: UFix64)
59 // Emitted when removing a default royalty recipient
60 access(all) event DefaultRoyaltyRemoved(name: String)
61 // Emitted when an existing Royalty rate is changed
62 access(all) event DefaultRoyaltyRateChanged(name: String, previousRate: UFix64, rate: UFix64)
63 // Emitted when adding a royalty for a specific NFT
64 access(all) event RoyaltyForPFPAdded(tokenID: UInt64, name: String, rate: UFix64)
65 // Emitted when an existing Royalty rate is changed
66 access(all) event RoyaltyForPFPChanged(tokenID: UInt64, name: String, previousRate: UFix64, rate: UFix64)
67 // Emitted when an existing Royalty rate is changed
68 access(all) event RoyaltyForPFPRemoved(tokenID: UInt64, name: String)
69 // Emitted when reverting the Royalty rate of a given NFT back to default settings
70 access(all) event RoyaltyForPFPRevertedToDefault(tokenID: UInt64)
71 // Emitted when an Item is destroyed
72 access(all) event ItemDestroyed(id: UInt64)
73
74
75 // Named paths
76 //
77 access(all) let CollectionStoragePath: StoragePath
78 access(all) let CollectionPublicPath: PublicPath
79 access(all) let AdminStoragePath: StoragePath
80 access(all) let MutatorStoragePath: StoragePath
81
82
83 // -----------------------------------------------------------------------
84 // MintPFPs contract-level fields.
85 // These contain actual values that are stored in the smart contract.
86 // -----------------------------------------------------------------------
87
88
89
90 // The ID that is used to create Admins.
91 // Every Admins should have a unique identifier.
92 access(all) var nextAdminID: UInt32
93
94 // The ID that is used to create Mutators.
95 // Every Mutators should have a unique identifier.
96 access(all) var nextMutatorID: UInt32
97
98 // If ever a mutator goes rouge, we would like to be able to have the option of
99 // locking the mutator's ability to mutate NFTs. Additionally, we would like
100 // to be able to unlock them too.
101 access(all) var lockedMutators: {UInt32: Bool}
102
103 // The merchant ID (see MintPFPs)
104 access(all) var merchantID: UInt32
105
106
107 // The total number of MintPFPs NFTs that have been created
108 // Because NFTs can be destroyed, it doesn't necessarily mean that this
109 // reflects the total number of NFTs in existence, just the number that
110 // have been minted to date. Also used as global nft IDs for minting.
111 access(all) var totalSupply: UInt64
112
113
114 // Mutations are upgrades or modifications of the NFTs' metadata.
115 // These will be store at the contract level, allowing dapps administrators to
116 // mutate NFTs even after they have been transferred to other wallets.
117 // It also ensures that the original metadata of the NFT will never be deleted
118 // offering some protection to the holder.
119 access(all) var mutations: {UInt64: ItemData}
120
121
122
123 // the default royalties will be applied to all PFPs unless a specific royalty
124 // is set for a given PFP
125 access(all) var defaultRoyalties: {String: MetadataViews.Royalty}
126
127 // If a specific NFT requires their own royalties,
128 // the default royalties can be overwritten in this dictionary.
129 access(all) var royaltiesForSpecificPFP: {UInt64: {String: MetadataViews.Royalty}}
130
131 access(all) var ExternalURL: MetadataViews.ExternalURL
132
133 access(all) var Socials: {String: MetadataViews.ExternalURL}
134
135 access(all) var Description: String
136
137 access(all) var SquareImage: MetadataViews.Media
138
139 access(all) var BannerImage: MetadataViews.Media
140
141
142
143
144
145 // -----------------------------------------------------------------------
146 // MintPFPs contract-level Composite Type definitions
147 // -----------------------------------------------------------------------
148 // These are just *definitions* for Types that this contract
149 // and other accounts can use. These definitions do not contain
150 // actual stored values, but an instance (or object) of one of these Types
151 // can be created by this contract that contains stored values.
152 // -----------------------------------------------------------------------
153
154 // The struct representing an NFT Item data
155 access(all) struct ItemData {
156
157
158 // The ID of the merchant
159 access(all) let merchantID: UInt32
160
161 // the name
162 access(all) let name: String
163
164 // the description
165 access(all) let description: String
166
167 // The thumbnail
168 access(all) let thumbnail: String
169
170 // the thumbnail cid (if thumbnailHosting is IPFS )
171 access(all) let thumbnailCID: String
172
173 // the thumbnail path (if thumbnailHosting is IPFS )
174 access(all) let thumbnailPathIPFS: String?
175
176 // The mimetype of the thumbnail
177 access(all) let thumbnailMimeType: String
178
179 // The method of hosting the thumbnail (IPFS | HTTPFile)
180 access(all) let thumbnailHosting: String
181
182 // the media file
183 access(all) let mediaURL: String
184
185 // the media cid (if mediaHosting is IPFS )
186 access(all) let mediaCID: String
187
188 // the media path (if mediaHosting is IPFS )
189 access(all) let mediaPathIPFS: String?
190
191 // the mimetype
192 access(all) let mimetype: String
193
194 // the method of hosting the media file (IPFS | HTTPFile)
195 access(all) let mediaHosting: String
196
197 // the attributes
198 access(all) let attributes: {String: String}
199
200 // rarity
201 access(all) let rarity: String
202
203
204
205
206 init(name: String, description: String, thumbnail: String, thumbnailCID: String, thumbnailPathIPFS: String?, thumbnailMimeType: String, thumbnailHosting: String, mediaURL: String, mediaCID: String, mediaPathIPFS: String?, mimetype: String, mediaHosting: String, attributes: {String: String}, rarity: String) {
207 self.merchantID = YBees.merchantID
208 self.name = name
209 self.description = description
210 self.thumbnail = thumbnail
211 self.thumbnailMimeType = thumbnailMimeType
212 self.thumbnailCID = thumbnailCID
213 self.thumbnailPathIPFS = thumbnailPathIPFS
214 self.thumbnailHosting = thumbnailHosting
215 self.mediaURL = mediaURL
216 self.mediaCID = mediaCID
217 self.mediaPathIPFS = mediaPathIPFS
218 self.mediaHosting = mediaHosting
219 self.mimetype = mimetype
220 self.attributes = attributes
221 self.rarity = rarity
222 }
223
224 }
225
226 // The resource that represents the Item NFTs
227 //
228 access(all) resource NFT: NonFungibleToken.NFT {
229
230 // Global unique item ID
231 access(all) let id: UInt64
232
233 // Struct of MintPFPs metadata
234 access(all) let data: ItemData
235
236
237 init(name: String, description: String, thumbnail: String, thumbnailCID: String, thumbnailPathIPFS: String?, thumbnailMimeType: String, thumbnailHosting: String, mediaURL: String, mediaCID: String, mediaPathIPFS: String?, mimetype: String, mediaHosting: String, attributes: {String: String}, rarity: String) {
238
239 pre{
240
241 }
242 // Increment the global Item IDs
243 YBees.totalSupply = YBees.totalSupply + (1 as UInt64)
244
245 self.id = YBees.totalSupply
246
247 // Set the metadata struct
248 self.data = ItemData(name: name, description: description, thumbnail: thumbnail, thumbnailCID: thumbnailCID, thumbnailPathIPFS: thumbnailPathIPFS, thumbnailMimeType: thumbnailMimeType, thumbnailHosting: thumbnailHosting, mediaURL: mediaURL, mediaCID: mediaCID, mediaPathIPFS: mediaPathIPFS, mimetype: mimetype, mediaHosting: mediaHosting, attributes: attributes, rarity: rarity)
249
250 emit ItemMinted(itemID: self.id, merchantID: YBees.merchantID, name: name)
251
252 }
253
254 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
255 return <-YBees.createEmptyCollection(nftType: Type<@YBees.NFT>())
256 }
257
258 access(all) fun getSerialNumber(): UInt64 {
259 return self.id;
260 }
261
262 access(all) fun getOriginalData(): ItemData {
263 return self.data;
264 }
265
266 access(all) fun getMutation(): ItemData? {
267
268 return YBees.mutations[self.id];
269 }
270
271 access(all) fun getData(): ItemData {
272 return self.getMutation() ?? self.getOriginalData();
273 }
274
275 access(all) view fun getViews(): [Type] {
276 return [
277 Type<MetadataViews.Display>(),
278 Type<MetadataViews.Royalties>(),
279 Type<MetadataViews.Editions>(),
280 Type<MetadataViews.ExternalURL>(),
281 Type<MetadataViews.NFTCollectionData>(),
282 Type<MetadataViews.NFTCollectionDisplay>(),
283 Type<MetadataViews.Serial>(),
284 Type<MetadataViews.Traits>()
285 ]
286 }
287
288 access(all) fun resolveView(_ view: Type): AnyStruct? {
289 switch view {
290 case Type<MetadataViews.Display>():
291 let data = self.getData();
292
293 var thumbnail = MetadataViews.HTTPFile(
294 url: data.thumbnail
295 )
296 // if data.thumbnailHosting == "IPFS" {
297 // thumbnail = MetadataViews.IPFSFile(
298 // cid: data.thumbnailCID,
299 // path: data.thumbnailPathIPFS
300 // )
301 // }
302 return MetadataViews.Display(
303 name: data.name,
304 description: data.description,
305 thumbnail: thumbnail
306 )
307
308 case Type<MetadataViews.Editions>():
309 let editionInfo = MetadataViews.Edition(name: self.data.name, number: UInt64(1), max:1)
310 let editionList: [MetadataViews.Edition] = [editionInfo]
311 return MetadataViews.Editions(
312 editionList
313 )
314
315 case Type<MetadataViews.Royalties>():
316 let royaltiesDictionary = YBees.royaltiesForSpecificPFP[self.id] ?? YBees.defaultRoyalties
317 var royalties: [MetadataViews.Royalty] = []
318 for royaltyName in royaltiesDictionary.keys {
319 royalties.append(royaltiesDictionary[royaltyName]!)
320 }
321 return MetadataViews.Royalties(royalties)
322
323 case Type<MetadataViews.ExternalURL>():
324 return YBees.ExternalURL
325
326 case Type<MetadataViews.NFTCollectionData>():
327 return MetadataViews.NFTCollectionData(
328 storagePath: YBees.CollectionStoragePath,
329 publicPath: YBees.CollectionPublicPath,
330 publicCollection: Type<&YBees.Collection>(),
331 publicLinkedType: Type<&YBees.Collection>(),
332 createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
333 return <-YBees.createEmptyCollection(nftType: Type<@YBees.NFT>())
334 })
335 )
336
337 case Type<MetadataViews.NFTCollectionDisplay>():
338 let data = self.getData();
339 let media = MetadataViews.Media(
340 file: MetadataViews.HTTPFile(
341 url: data.thumbnail
342 ),
343 mediaType: data.thumbnailMimeType
344 )
345
346 return MetadataViews.NFTCollectionDisplay(
347 name: "YBees",
348 description: YBees.Description,
349 externalURL: YBees.ExternalURL,
350 squareImage: YBees.SquareImage,
351 bannerImage: YBees.BannerImage,
352 socials: YBees.Socials
353 )
354
355 case Type<MetadataViews.Traits>():
356 // exclude mintedTime and foo to show other uses of Traits
357 let excludedTraits = ["name", "description", "thumbnail", "externalUrl"]
358 let data = self.getData();
359 let dict = data.attributes;
360 let traitsView = MetadataViews.dictToTraits(dict: dict, excludedNames: excludedTraits)
361
362 return traitsView
363
364 }
365 return nil
366 }
367
368
369 }
370
371 // Mutator is an authorization resource that allows for the mutations of NFTs
372 access(all) resource Mutator {
373
374 access(all) let id: UInt32
375
376 init(id: UInt32) {
377 self.id = id
378 }
379
380 // Mutator role should only be able to mutate a NFT
381 access(all) fun mutatePFP(tokenID: UInt64, name: String, description: String, thumbnail: String, thumbnailCID: String, thumbnailPathIPFS: String?, thumbnailMimeType: String, thumbnailHosting: String, mediaURL: String, mediaCID: String, mediaPathIPFS: String?, mimetype: String, mediaHosting: String, attributes: {String: String}, rarity: String){
382
383 pre{
384 tokenID <= YBees.totalSupply: "the tokenID does not exist"
385 }
386 if (YBees.lockedMutators[self.id] != true) {
387 let mutation = ItemData(name: name, description: description, thumbnail: thumbnail, thumbnailCID: thumbnailCID, thumbnailPathIPFS: thumbnailPathIPFS, thumbnailMimeType: thumbnailMimeType, thumbnailHosting: thumbnailHosting, mediaURL: mediaURL, mediaCID: mediaCID, mediaPathIPFS: mediaPathIPFS, mimetype: mimetype, mediaHosting: mediaHosting, attributes: attributes, rarity: rarity)
388
389 YBees.mutations[tokenID] = mutation
390 emit ItemMutated(id: tokenID, mutation: mutation)
391 }
392 else {
393 log("Cannot let mutator mutate")
394 }
395 }
396 }
397
398 access(all) view fun getContractViews(resourceType: Type?): [Type] {
399 return []
400 }
401
402 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
403
404 return nil
405 }
406
407 // Admin is a special authorization resource that
408 // allows the owner to perform important functions to modify the
409 // various aspects of the Editions and Items
410 //
411 access(all) resource Admin {
412
413 access(all) let id: UInt32
414
415
416 init(id: UInt32) {
417 self.id = id
418 }
419
420 access(all) fun setExternalURL(url: String) {
421 YBees.ExternalURL = MetadataViews.ExternalURL(url);
422 }
423
424 access(all) fun addSocial(key: String, url: String): {String: MetadataViews.ExternalURL} {
425 YBees.Socials.insert(key: key, MetadataViews.ExternalURL(url));
426 return YBees.getSocials();
427 }
428
429 access(all) fun removeSocial(key: String): {String: MetadataViews.ExternalURL} {
430 YBees.Socials.remove(key: key);
431 return YBees.getSocials();
432 }
433
434 access(all) fun setDescription(description: String) {
435 YBees.Description = description;
436 }
437
438 access(all) fun setSquareImage(url: String, mediaType: String) {
439 YBees.SquareImage = MetadataViews.Media(
440 file: MetadataViews.HTTPFile(
441 url: url
442 ),
443 mediaType: mediaType
444 );
445 }
446
447 access(all) fun setBannerImage(url: String, mediaType: String) {
448 YBees.BannerImage = MetadataViews.Media(
449 file: MetadataViews.HTTPFile(
450 url: url
451 ),
452 mediaType: mediaType
453 );
454 }
455
456 // createNewAdmin creates a new Admin resource
457 //
458 access(all) fun createNewAdmin(): @Admin {
459
460
461 let newID = YBees.nextAdminID
462 // Increment the ID so that it isn't used again
463 YBees.nextAdminID = YBees.nextAdminID + (1 as UInt32)
464
465 return <-create Admin(id: newID)
466 }
467
468 // createNewMutator creates a new Mutator resource
469 access(all) fun createNewMutator(): @Mutator {
470
471
472 let newID = YBees.nextMutatorID
473 // Increment the ID so that it isn't used again
474 YBees.nextMutatorID = YBees.nextMutatorID + (1 as UInt32)
475
476 return <-create Mutator(id: newID)
477 }
478
479 // Locks a mutator
480 access(all) fun lockMutator(id: UInt32): Int{
481 YBees.lockedMutators.insert(key: id, true);
482 return YBees.lockedMutators.length;
483 }
484
485 // Unlocks a mutator
486 access(all) fun unlockMutator(id: UInt32): Int{
487 YBees.lockedMutators.remove(key: id);
488 return YBees.lockedMutators.length;
489 }
490
491 access(all) fun setMerchantID(merchantID: UInt32): UInt32{
492 YBees.merchantID=merchantID;
493 return YBees.merchantID;
494 }
495
496
497 access(all) fun mintPFP(name: String, description: String, thumbnail: String, thumbnailCID: String, thumbnailPathIPFS: String?, thumbnailMimeType: String, thumbnailHosting: String, mediaURL: String, mediaCID: String, mediaPathIPFS: String?, mimetype: String, mediaHosting: String, attributes: {String: String}, rarity: String): @NFT {
498
499 // Mint the new item
500 let newItem: @NFT <- create NFT(name: name, description: description, thumbnail: thumbnail, thumbnailCID: thumbnailCID, thumbnailPathIPFS: thumbnailPathIPFS, thumbnailMimeType: thumbnailMimeType, thumbnailHosting: thumbnailHosting, mediaURL: mediaURL, mediaCID: mediaCID, mediaPathIPFS: mediaPathIPFS, mimetype: mimetype, mediaHosting: mediaHosting, attributes: attributes, rarity: rarity)
501
502
503 return <-newItem
504
505 }
506
507 access(all) fun mutatePFP(tokenID: UInt64, name: String, description: String, thumbnail: String, thumbnailCID: String, thumbnailPathIPFS: String?, thumbnailMimeType: String, thumbnailHosting: String, mediaURL: String, mediaCID: String, mediaPathIPFS: String?,mimetype: String, mediaHosting: String, attributes: {String: String}, rarity: String){
508
509 pre{
510 tokenID <= YBees.totalSupply: "the tokenID does not exist"
511 }
512 let mutation = ItemData(name: name, description: description, thumbnail: thumbnail, thumbnailCID: thumbnailCID, thumbnailPathIPFS: thumbnailPathIPFS, thumbnailMimeType: thumbnailMimeType, thumbnailHosting: thumbnailHosting, mediaURL: mediaURL, mediaCID: mediaCID, mediaPathIPFS: mediaPathIPFS, mimetype: mimetype, mediaHosting:mediaHosting, attributes: attributes, rarity: rarity)
513
514 YBees.mutations[tokenID] = mutation
515 emit ItemMutated(id: tokenID, mutation: mutation)
516 }
517
518
519 // addDefaultRoyalty adds a new default recipient for the cut of the sale
520 //
521 // Parameters: name: the key to store the new royalty
522 // recipientAddress: the wallet address of the recipient of the cut of the sale
523 // rate: the percentage of the sale that goes to that recipient
524 //
525 access(all) fun addDefaultRoyalty(name: String, royalty: MetadataViews.Royalty, rate: UFix64){
526
527 pre {
528 YBees.defaultRoyalties[name] == nil: "The royalty with that name already exists"
529 rate > 0.0: "Cannot set rate to less than 0%"
530 rate <= 1.0: "Cannot set rate to more than 100%"
531 }
532 YBees.defaultRoyalties[name] = royalty
533
534 // emit DefaultRoyaltyAdded(name: name, rate: rate)
535
536
537
538 }
539
540
541 // changeDefaultRoyaltyRate updates a recipient's part of the cut of the sale
542 //
543 // Parameters: name: the key of the recipient to update
544 // rate: the new percentage of the sale that goes to that recipient
545 //
546 access(all) fun changeDefaultRoyaltyRate(name: String, rate: UFix64) {
547 pre {
548 YBees.defaultRoyalties[name] != nil: "The royalty with that name does not exist"
549 rate > 0.0: "Cannot set rate to less than 0%"
550 rate <= 1.0: "Cannot set rate to more than 100%"
551 }
552 let royalty = YBees.defaultRoyalties[name]!
553 let previousRate = royalty.cut
554 let previousRecipientAddress = royalty.receiver
555 YBees.defaultRoyalties[name] = MetadataViews.Royalty(receiver: previousRecipientAddress, cut: UFix64(rate), description: "YBees Royalties")
556 emit DefaultRoyaltyRateChanged(name: name, previousRate: previousRate, rate: rate)
557 }
558
559 // removeDefaultRoyalty removes a default recipient from the cut of the sale
560 //
561 // Parameters: name: the key to store the royalty to remove
562 access(all) fun removeDefaultRoyalty(name: String) {
563 pre {
564 YBees.defaultRoyalties[name] != nil: "The royalty with that name does not exist"
565 }
566 YBees.defaultRoyalties.remove(key: name)
567 emit DefaultRoyaltyRemoved(name: name)
568 }
569
570
571
572 // addRoyaltyForPFP adds a new recipient for the cut of the sale on a specific PFP
573 //
574 // Parameters: tokenID: the unique ID of the PFP
575 // name: the key to store the new royalty
576 // recipientAddress: the wallet address of the recipient of the cut of the sale
577 // rate: the percentage of the sale that goes to that recipient
578 //
579 access(all) fun addRoyaltyForPFP(tokenID: UInt64, name: String, royalty: MetadataViews.Royalty, rate: UFix64){
580
581 pre {
582 rate > 0.0: "Cannot set rate to less than 0%"
583 rate <= 1.0: "Cannot set rate to more than 100%"
584 }
585
586 if YBees.royaltiesForSpecificPFP.containsKey(tokenID) == false{
587
588 let newEntry: {String: MetadataViews.Royalty}= {}
589 newEntry.insert(key: name, royalty)
590 YBees.royaltiesForSpecificPFP!.insert(key: tokenID, newEntry)
591 emit RoyaltyForPFPAdded(tokenID: tokenID, name: name, rate: rate)
592 return
593 }
594
595 // the TokenID already has an entry
596
597 if YBees.royaltiesForSpecificPFP[tokenID]!.containsKey(name) {
598 // the entry already exists
599 panic("The royalty with that name already exists")
600
601 }
602 YBees.royaltiesForSpecificPFP[tokenID]!.insert(key: name, royalty)
603
604 emit RoyaltyForPFPAdded(tokenID: tokenID, name: name, rate: rate)
605
606
607
608 }
609
610
611 // changeRoyaltyRateForPFP changes the royalty rate for the sale on a specific PFP
612 //
613 // Parameters: tokenID: the unique ID of the PFP
614 // name: the key to store the new royalty
615 // rate: the percentage of the sale that goes to that recipient
616 //
617 access(all) fun changeRoyaltyRateForPFP(tokenID: UInt64, name: String, rate: UFix64){
618
619 pre {
620 rate > 0.0: "Cannot set rate to less than 0%"
621 rate <= 1.0: "Cannot set rate to more than 100%"
622 }
623
624 let previousRoyalty: MetadataViews.Royalty = YBees.royaltiesForSpecificPFP[tokenID]![name]!
625
626 let newRoyalty = MetadataViews.Royalty(receiver: previousRoyalty.receiver, cut: UFix64(rate), description: "YBees Royalties")
627 YBees.royaltiesForSpecificPFP[tokenID]!.insert(key: name, newRoyalty);
628
629 emit RoyaltyForPFPChanged(tokenID: tokenID, name: name, previousRate: previousRoyalty.cut, rate: rate)
630
631 }
632
633 // removeRoyaltyForPFP changes the royalty rate for the sale on a specific PFP
634 //
635 // Parameters: tokenID: the unique ID of the PFP
636 // name: the key to store the royalty to remove
637 //
638 access(all) fun removeRoyaltyForPFP(tokenID: UInt64, name: String){
639
640 YBees.royaltiesForSpecificPFP[tokenID]!.remove(key: name);
641 emit RoyaltyForPFPRemoved(tokenID: tokenID, name: name)
642
643 }
644
645
646 // revertRoyaltyForPFPToDefault removes the royalty setttings for the specific PFP
647 // so it uses the default roylaties going forward
648 //
649 // Parameters: tokenID: the unique ID of the PFP
650 //
651 access(all) fun revertRoyaltyForPFPToDefault(tokenID: UInt64){
652
653 YBees.royaltiesForSpecificPFP.remove(key: tokenID);
654 emit RoyaltyForPFPRevertedToDefault(tokenID: tokenID)
655
656 }
657
658
659 }
660
661
662
663 // This is the interface that users can cast their MintPFPs Collection as
664 // to allow others to deposit MintPFPs into their Collection. It also allows for reading
665 // the IDs of MintPFPs in the Collection.
666 access(all) resource interface YBeesCollectionPublic {
667
668
669 }
670
671
672
673 // Collection is a resource that every user who owns NFTs
674 // will store in their account to manage their NFTS
675 //
676 access(all) resource Collection: NonFungibleToken.Collection, YBeesCollectionPublic {
677 // Dictionary of MintPFPs conforming tokens
678 // NFT is a resource type with a UInt64 ID field
679 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
680
681 init() {
682 self.ownedNFTs <- {}
683 }
684
685
686
687
688 // withdraw removes a MintPFPs from the Collection and moves it to the caller
689 //
690 // Parameters: withdrawID: The ID of the NFT
691 // that is to be removed from the Collection
692 //
693 // returns: @NonFungibleToken.NFT the token that was withdrawn
694 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
695
696 // Remove the nft from the Collection
697 let token <- self.ownedNFTs.remove(key: withdrawID)
698 ?? panic("Cannot withdraw: PFP does not exist in the collection")
699
700 emit Withdraw(id: token.id, from: self.owner?.address)
701
702 // Return the withdrawn token
703 return <-token
704 }
705
706 // batchWithdraw withdraws multiple tokens and returns them as a Collection
707 //
708 // Parameters: ids: An array of IDs to withdraw
709 //
710 // Returns: @NonFungibleToken.Collection: A collection that contains
711 // the withdrawn MintPFPs items
712 //
713 access(NonFungibleToken.Withdraw) fun batchWithdraw(ids: [UInt64]): @{NonFungibleToken.Collection} {
714 // Create a new empty Collection
715 var batchCollection <- create Collection()
716
717 // Iterate through the ids and withdraw them from the Collection
718 for id in ids {
719
720 let token <-self.withdraw(withdrawID: id)
721
722 batchCollection.deposit(token: <-token)
723 }
724
725 // Return the withdrawn tokens
726 return <-batchCollection
727 }
728
729 // deposit takes a MintPFPs and adds it to the Collections dictionary
730 //
731 // Paramters: token: the NFT to be deposited in the collection
732 //
733 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
734
735 // Cast the deposited token as a MintPFPs NFT to make sure
736 // it is the correct type
737 let token <- token as! @YBees.NFT
738
739 // Get the token's ID
740 let id = token.id
741
742 // Add the new token to the dictionary
743 let oldToken <- self.ownedNFTs[id] <- token
744
745 // Only emit a deposit event if the Collection
746 // is in an account's storage
747 if self.owner?.address != nil {
748 emit Deposit(id: id, to: self.owner?.address)
749 }
750
751 // Destroy the empty old token that was "removed"
752 destroy oldToken
753 }
754
755 // batchDeposit takes a Collection object as an argument
756 // and deposits each contained NFT into this Collection
757 access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection}) {
758
759 // Get an array of the IDs to be deposited
760 let keys = tokens.getIDs()
761
762 // Iterate through the keys in the collection and deposit each one
763 for key in keys {
764 self.deposit(token: <-tokens.withdraw(withdrawID: key))
765 }
766
767 // Destroy the empty Collection
768 destroy tokens
769 }
770
771 // getIDs returns an array of the IDs that are in the Collection
772 access(all) view fun getIDs(): [UInt64] {
773 return self.ownedNFTs.keys
774 }
775
776 /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
777 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
778 let supportedTypes: {Type: Bool} = {}
779 supportedTypes[Type<@YBees.NFT>()] = true
780 return supportedTypes
781 }
782
783 /// Returns whether or not the given type is accepted by the collection
784 /// A collection that can accept any type should just return true by default
785 access(all) view fun isSupportedNFTType(type: Type): Bool {
786 return type == Type<@YBees.NFT>()
787 }
788
789 /// Gets the amount of NFTs stored in the collection
790 access(all) view fun getLength(): Int {
791 return self.ownedNFTs.length
792 }
793
794
795
796
797
798 // borrowNFT Returns a borrowed reference to a MintPFPs in the Collection
799 // so that the caller can read its ID
800 //
801 // Parameters: id: The ID of the NFT to get the reference for
802 //
803 // Returns: A reference to the NFT
804 //
805 // Note: This only allows the caller to read the ID of the NFT,
806 // not any MintPFPs specific data. Please use borrowYBeess to
807 // read MintPFPs data.
808 //
809 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
810 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
811 }
812
813
814 // Making the collection conform to MetadataViews.Resolver
815 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
816 if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
817 return nft as &{ViewResolver.Resolver}
818 }
819 return nil
820 }
821
822 /// createEmptyCollection creates an empty Collection of the same type
823 /// and returns it to the caller
824 /// @return A an empty collection of the same type
825 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
826 return <-YBees.createEmptyCollection(nftType: Type<@YBees.NFT>())
827 }
828
829 }
830
831
832
833 // -----------------------------------------------------------------------
834 // MintPFPs contract-level function definitions
835 // -----------------------------------------------------------------------
836
837 // createEmptyCollection creates a new, empty Collection object so that
838 // a user can store it in their account storage.
839 // Once they have a Collection in their storage, they are able to receive
840 // MintPFPs in transactions.
841 //
842 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
843 return <-create YBees.Collection()
844 }
845
846 access(all) fun createEmptyMintPFPsCollection(): @YBees.Collection {
847 return <-create YBees.Collection()
848 }
849
850 access(all) fun getExternalURL(): MetadataViews.ExternalURL {
851 return YBees.ExternalURL;
852 }
853
854 access(all) fun getSocials(): {String: MetadataViews.ExternalURL} {
855 return YBees.Socials;
856 }
857
858 access(all) fun getDescription(): String {
859 return YBees.Description
860 }
861
862 access(all) fun getSquareImage(): MetadataViews.Media {
863 return YBees.SquareImage;
864 }
865
866 access(all) fun getBannerImage(): MetadataViews.Media {
867 return YBees.BannerImage;
868 }
869
870 // Returns all of the locked mutator IDs
871 access(all) fun getLockedMutators() : {UInt32: Bool} {
872 return YBees.lockedMutators;
873 }
874
875 // getMerchantID returns the merchant ID
876 access(all) fun getMerchantID(): UInt32 {
877 return self.merchantID
878 }
879
880
881 // getDefaultRoyalties returns the default royalties
882 access(all) fun getDefaultRoyalties(): {String: MetadataViews.Royalty} {
883 return self.defaultRoyalties
884 }
885
886 // getDefaultRoyalties returns the default royalties
887 access(all) fun getDefaultRoyaltyNames(): [String] {
888 return self.defaultRoyalties.keys
889 }
890
891 // getDefaultRoyaltyRate returns a royalty object
892 access(all) fun getDefaultRoyalty(name: String): MetadataViews.Royalty? {
893 return self.defaultRoyalties[name]
894 }
895
896 // returns the default
897 access(all) fun getTotalDefaultRoyaltyRate(): UFix64 {
898 var totalRoyalty = 0.0
899 for key in self.defaultRoyalties.keys {
900 let royal = self.defaultRoyalties[key] ?? panic("Royalty does not exist")
901 totalRoyalty = totalRoyalty + royal.cut
902 }
903 return totalRoyalty
904 }
905
906
907 // getRoyaltiesForPFP returns the specific royalties for a PFP or the default royalties
908 access(all) fun getRoyaltiesForPFP(tokenID: UInt64): {String: MetadataViews.Royalty} {
909 return self.royaltiesForSpecificPFP[tokenID] ?? self.getDefaultRoyalties()
910 }
911
912 // getRoyaltyNamesForPFP returns the royalty names for a specific PFP or the default royalty names
913 access(all) fun getRoyaltyNamesForPFP(tokenID: UInt64): [String] {
914 return self.royaltiesForSpecificPFP[tokenID]?.keys ?? self.getDefaultRoyaltyNames()
915 }
916
917 // getRoyaltyNamesForPFP returns a given royalty for a specific PFP or the default royalty names
918 access(all) fun getRoyaltyForPFP(tokenID: UInt64, name: String): MetadataViews.Royalty? {
919
920 if self.royaltiesForSpecificPFP.containsKey(tokenID){
921 let royaltiesForPFP: {String: MetadataViews.Royalty} = self.royaltiesForSpecificPFP[tokenID]!
922 return royaltiesForPFP[name]!
923 }
924
925 // if no specific royalty is set
926 return self.getDefaultRoyalty(name: name)
927 }
928
929 // getTotalRoyaltyRateForPFP returns the total royalty rate for a give PFP
930 access(all) fun getTotalRoyaltyRateForPFP(tokenID: UInt64): UFix64 {
931
932 var totalRoyalty = 0.0
933 let royalties = self.getRoyaltiesForPFP(tokenID: tokenID)
934 for key in royalties.keys {
935 let royal = royalties[key] ?? panic("Royalty does not exist")
936 totalRoyalty = totalRoyalty + royal.cut
937 }
938 return totalRoyalty
939 }
940
941
942
943
944
945
946
947 // -----------------------------------------------------------------------
948 // MintPFPs initialization function
949 // -----------------------------------------------------------------------
950 //
951 init() {
952 // Initialize contract fields
953
954 self.totalSupply = 0
955 self.merchantID = 36
956 self.mutations = {}
957 self.defaultRoyalties = {}
958 self.royaltiesForSpecificPFP = {}
959 self.lockedMutators = {}
960 self.ExternalURL = MetadataViews.ExternalURL("https://google.ca")
961 self.Socials = {"test": MetadataViews.ExternalURL("https://google.ca")}
962 self.Description = "Templated Contract!"
963 self.SquareImage = MetadataViews.Media(
964 file: MetadataViews.HTTPFile(
965 url: "https://mint-store-metadata.s3.us-east-2.amazonaws.com/BSC-Young-Boys/BSC_Young_Boys_logo.png"
966 ),
967 mediaType: "image/png"
968 )
969
970 self.BannerImage = MetadataViews.Media(
971 file: MetadataViews.HTTPFile(
972 url: "https://mint-store-metadata.s3.us-east-2.amazonaws.com/BSC-Young-Boys/BSC_Young_Boys_logo.png"
973 ),
974 mediaType: "image/png"
975 )
976
977 self.CollectionStoragePath = /storage/YBeesCollection
978 self.CollectionPublicPath = /public/YBeesCollection
979 self.AdminStoragePath = /storage/YBeesAdmin
980 self.MutatorStoragePath = /storage/YBeesMutator
981
982 // Put a new Collection in storage
983 self.account.storage.save<@Collection>(<- create Collection(), to: self.CollectionStoragePath)
984
985 // Create a public capability for the Collection
986 // self.account.link<&{YBeesCollectionPublic}>(self.CollectionPublicPath, target: self.CollectionStoragePath)
987 let collectionCap = self.account.capabilities.storage.issue<&YBees.Collection>(self.CollectionStoragePath)
988 self.account.capabilities.publish(collectionCap, at: self.CollectionPublicPath)
989
990 // Put the admin ressource in storage
991 self.account.storage.save<@Admin>(<- create Admin(id: 1), to: self.AdminStoragePath)
992 self.nextAdminID = 2
993
994 // Put the admin ressource in storage
995 self.account.storage.save<@Mutator>(<- create Mutator(id: 1), to: self.MutatorStoragePath)
996 self.nextMutatorID = 2
997
998 emit ContractInitialized()
999 }
1000
1001
1002}
1003
1004