Smart Contract
TuneGONFT
A.c6945445cdbefec9.TuneGONFT
1import NonFungibleToken from 0x1d7e57aa55817448
2import FungibleToken from 0xf233dcee88fe0abe
3import MetadataViews from 0x1d7e57aa55817448
4import ViewResolver from 0x1d7e57aa55817448
5
6// Contract
7//
8access(all) contract TuneGONFT: NonFungibleToken {
9
10 // Events
11 //
12 access(all) event ContractInitialized()
13 access(all) event Withdraw(id: UInt64, from: Address?)
14 access(all) event Deposit(id: UInt64, to: Address?)
15 access(all) event Minted(
16 id: UInt64,
17 itemId: String,
18 edition: UInt64,
19 royalties: [RoyaltyData],
20 additionalInfo: {String: String}
21 )
22 access(all) event Destroyed(id: UInt64)
23 access(all) event Burned(id: UInt64)
24 access(all) event Claimed(id: UInt64, type: String, recipient: Address, tag: String?)
25 access(all) event ClaimedReward(id: String, recipient: Address)
26
27 // Named Paths
28 //
29 access(all) let CollectionStoragePath: StoragePath
30 access(all) let CollectionPublicPath: PublicPath
31 access(all) let MinterStoragePath: StoragePath
32
33 // totalSupply
34 // The total number of TuneGONFT that have been minted
35 //
36 access(all) var totalSupply: UInt64
37
38 // itemEditions
39 //
40 access(contract) var itemEditions: {String: UInt64}
41
42 // Default Collection Metadata
43 access(all) struct CollectionMetadata {
44 access(all) let collectionName: String
45 access(all) let collectionDescription: String
46 access(all) let collectionURL: String
47 access(all) let collectionMedia: String
48 access(all) let collectionMediaMimeType: String
49 access(all) let collectionMediaBanner: String?
50 access(all) let collectionMediaBannerMimeType: String?
51 access(all) let collectionSocials: {String:String}
52
53 init(
54 collectionName: String,
55 collectionDescription: String,
56 collectionURL: String,
57 collectionMedia: String,
58 collectionMediaMimeType: String,
59 collectionMediaBanner: String?,
60 collectionMediaBannerMimeType: String?,
61 collectionSocials: {String:String},
62 ) {
63 self.collectionName = collectionName
64 self.collectionDescription = collectionDescription
65 self.collectionURL = collectionURL
66 self.collectionMedia = collectionMedia
67 self.collectionMediaMimeType = collectionMediaMimeType
68 self.collectionMediaBanner = collectionMediaBanner
69 self.collectionMediaBannerMimeType = collectionMediaBannerMimeType
70 self.collectionSocials = collectionSocials
71 }
72
73 }
74
75 access(all) fun getDefaultCollectionMetadata(): CollectionMetadata {
76 let media = "https://www.tunegonft.com/assets/images/tunego-beta-logo.png"
77 return TuneGONFT.CollectionMetadata(
78 collectionName: "TuneGO NFT",
79 collectionDescription: "Unique music collectibles from the TuneGO Community",
80 collectionURL: "https://www.tunegonft.com/",
81 collectionMedia: media,
82 collectionMediaMimeType: "image/png",
83 collectionMediaBanner: media,
84 collectionMediaBannerMimeType: "image/png",
85 collectionSocials: {
86 "discord": "https://discord.gg/nsGnsRbMke",
87 "facebook": "https://www.facebook.com/tunego",
88 "instagram": "https://www.instagram.com/tunego",
89 "twitter": "https://twitter.com/TuneGONFT",
90 "tiktok": "https://www.tiktok.com/@tunegoadmin?lang=en"
91 },
92 )
93 }
94
95 // Metadata
96 //
97 access(all) struct Metadata {
98 access(all) let title: String
99 access(all) let description: String
100 access(all) let creator: String
101 access(all) let asset: String
102 access(all) let assetMimeType: String
103 access(all) let assetHash: String
104 access(all) let artwork: String
105 access(all) let artworkMimeType: String
106 access(all) let artworkHash: String
107 access(all) let artworkAlternate: String?
108 access(all) let artworkAlternateMimeType: String?
109 access(all) let artworkAlternateHash: String?
110 access(all) let thumbnail: String
111 access(all) let thumbnailMimeType: String
112 access(all) let termsUrl: String
113 access(all) let rarity: String?
114 access(all) let credits: String?
115
116 // Miscellaneous
117 access(all) let mintedBlock: UInt64
118 access(all) let mintedTime: UFix64
119
120 init(
121 title: String,
122 description: String,
123 creator: String,
124 asset: String,
125 assetMimeType: String,
126 assetHash: String,
127 artwork: String,
128 artworkMimeType: String,
129 artworkHash: String,
130 artworkAlternate: String?,
131 artworkAlternateMimeType: String?,
132 artworkAlternateHash: String?,
133 thumbnail: String,
134 thumbnailMimeType: String,
135 termsUrl: String,
136 rarity: String?,
137 credits: String?,
138 mintedBlock: UInt64?,
139 mintedTime: UFix64?
140 ) {
141
142 self.title = title
143 self.description = description
144 self.creator = creator
145 self.asset = asset
146 self.assetMimeType = assetMimeType
147 self.assetHash = assetHash
148 self.artwork = artwork
149 self.artworkMimeType = artworkMimeType
150 self.artworkHash = artworkHash
151 self.artworkAlternate = artworkAlternate
152 self.artworkAlternateMimeType = artworkAlternateMimeType
153 self.artworkAlternateHash = artworkAlternateHash
154 self.thumbnail = thumbnail
155 self.thumbnailMimeType = thumbnailMimeType
156 self.termsUrl = termsUrl
157 self.credits = credits
158 self.rarity = rarity
159 self.mintedBlock = mintedBlock ?? UInt64(0)
160 self.mintedTime = mintedTime ?? UFix64(0)
161 }
162
163 access(all) fun getCollectionMetadata(): CollectionMetadata {
164 return TuneGONFT.getDefaultCollectionMetadata()
165 }
166
167 access(all) fun toDict(): {String: AnyStruct?} {
168 let rawMetadata: {String: AnyStruct?} = {}
169 rawMetadata.insert(key: "title", self.title)
170 rawMetadata.insert(key: "description", self.description)
171 rawMetadata.insert(key: "creator", self.creator)
172 if(self.asset.length == 0){
173 rawMetadata.insert(key: "asset", nil)
174 rawMetadata.insert(key: "assetMimeType", nil)
175 rawMetadata.insert(key: "assetHash", nil)
176 }else{
177 rawMetadata.insert(key: "asset", self.asset)
178 rawMetadata.insert(key: "assetMimeType", self.assetMimeType)
179 rawMetadata.insert(key: "assetHash", self.assetHash)
180 }
181 rawMetadata.insert(key: "artwork", self.artwork)
182 rawMetadata.insert(key: "artworkMimeType", self.artworkMimeType)
183 rawMetadata.insert(key: "artworkHash", self.artworkHash)
184 rawMetadata.insert(key: "artworkAlternate", self.artworkAlternate)
185 rawMetadata.insert(key: "artworkAlternateMimeType", self.artworkAlternateMimeType)
186 rawMetadata.insert(key: "artworkAlternateHash", self.artworkAlternateHash)
187 rawMetadata.insert(key: "thumbnail", self.thumbnail)
188 rawMetadata.insert(key: "thumbnailMimeType", self.thumbnailMimeType)
189 rawMetadata.insert(key: "termsUrl", self.termsUrl)
190 rawMetadata.insert(key: "rarity", self.rarity)
191 rawMetadata.insert(key: "credits", self.credits)
192
193 let collectionSource = self.getCollectionMetadata()
194 rawMetadata.insert(key: "collectionName", collectionSource.collectionName)
195 rawMetadata.insert(key: "collectionDescription", collectionSource.collectionDescription)
196 rawMetadata.insert(key: "collectionURL", collectionSource.collectionURL)
197 rawMetadata.insert(key: "collectionMedia", collectionSource.collectionMedia)
198 rawMetadata.insert(key: "collectionMediaMimeType", collectionSource.collectionMediaMimeType)
199 rawMetadata.insert(key: "collectionMediaBanner", collectionSource.collectionMediaBanner ?? collectionSource.collectionMedia)
200 rawMetadata.insert(key: "collectionMediaBannerMimeType", collectionSource.collectionMediaBannerMimeType ?? collectionSource.collectionMediaBannerMimeType)
201 rawMetadata.insert(key: "collectionSocials", collectionSource.collectionSocials)
202
203 //rawMetadata.insert(key: "mintedBlock", self.mintedBlock)
204 //rawMetadata.insert(key: "mintedTime", self.mintedTime)
205 return rawMetadata
206 }
207
208 access(all) fun toStringDict(): {String: String?} {
209 let rawMetadata: {String: String?} = {}
210 rawMetadata.insert(key: "title", self.title)
211 rawMetadata.insert(key: "description", self.description)
212 rawMetadata.insert(key: "creator", self.creator)
213 if(self.asset.length == 0){
214 rawMetadata.insert(key: "asset", nil)
215 rawMetadata.insert(key: "assetMimeType", nil)
216 rawMetadata.insert(key: "assetHash", nil)
217 }else{
218 rawMetadata.insert(key: "asset", self.asset)
219 rawMetadata.insert(key: "assetMimeType", self.assetMimeType)
220 rawMetadata.insert(key: "assetHash", self.assetHash)
221 }
222 rawMetadata.insert(key: "artwork", self.artwork)
223 rawMetadata.insert(key: "artworkMimeType", self.artworkMimeType)
224 rawMetadata.insert(key: "artworkHash", self.artworkHash)
225 rawMetadata.insert(key: "artworkAlternate", self.artworkAlternate)
226 rawMetadata.insert(key: "artworkAlternateMimeType", self.artworkAlternateMimeType)
227 rawMetadata.insert(key: "artworkAlternateHash", self.artworkAlternateHash)
228 rawMetadata.insert(key: "thumbnail", self.thumbnail)
229 rawMetadata.insert(key: "thumbnailMimeType", self.thumbnailMimeType)
230 rawMetadata.insert(key: "termsUrl", self.termsUrl)
231 rawMetadata.insert(key: "rarity", self.rarity)
232 rawMetadata.insert(key: "credits", self.credits)
233
234 let collectionSource = self.getCollectionMetadata()
235 rawMetadata.insert(key: "collectionName", collectionSource.collectionName)
236 rawMetadata.insert(key: "collectionDescription", collectionSource.collectionDescription)
237 rawMetadata.insert(key: "collectionURL", collectionSource.collectionURL)
238 rawMetadata.insert(key: "collectionMedia", collectionSource.collectionMedia)
239 rawMetadata.insert(key: "collectionMediaMimeType", collectionSource.collectionMediaMimeType)
240 rawMetadata.insert(key: "collectionMediaBanner", collectionSource.collectionMediaBanner ?? collectionSource.collectionMedia)
241 rawMetadata.insert(key: "collectionMediaBannerMimeType", collectionSource.collectionMediaBannerMimeType ?? collectionSource.collectionMediaBannerMimeType)
242
243 //rawMetadata.insert(key: "mintedBlock", self.mintedBlock.toString())
244 //rawMetadata.insert(key: "mintedTime", self.mintedTime.toString())
245
246 // Socials
247 for key in collectionSource.collectionSocials!.keys {
248 rawMetadata.insert(key: "collectionSocials_".concat(key), collectionSource.collectionSocials![key])
249 }
250
251 return rawMetadata
252 }
253 }
254
255 // Edition
256 //
257 access(all) struct Edition {
258 access(all) let edition: UInt64
259 access(all) let totalEditions: UInt64
260
261 init(edition: UInt64, totalEditions: UInt64) {
262 self.edition = edition
263 self.totalEditions = totalEditions
264 }
265 }
266
267 access(all) fun editionCirculatingKey(itemId: String): String {
268 return "circulating:".concat(itemId)
269 }
270
271 // RoyaltyData
272 //
273 access(all) struct RoyaltyData {
274 access(all) let receiver: Address
275 access(all) let percentage: UFix64
276
277 init(receiver: Address, percentage: UFix64) {
278 self.receiver = receiver
279 self.percentage = percentage
280 }
281 }
282
283 // NFT
284 //
285 access(all) resource NFT: NonFungibleToken.NFT, ViewResolver.Resolver {
286 access(all) let id: UInt64
287 access(all) let itemId: String
288 access(all) let edition: UInt64
289 access(self) let metadata: Metadata
290 access(self) let royalties: [MetadataViews.Royalty]
291 access(self) let additionalInfo: {String: String}
292
293 access(all) event ResourceDestroyed(id: UInt64 = self.id)
294
295 init(
296 id: UInt64,
297 itemId: String,
298 edition: UInt64,
299 metadata: Metadata,
300 royalties: [MetadataViews.Royalty],
301 additionalInfo: {String: String}
302 ) {
303 self.id = id
304 self.itemId = itemId
305 self.edition = edition
306 self.metadata = metadata
307 self.royalties = royalties
308 self.additionalInfo = additionalInfo
309 }
310
311 access(all) fun getAdditionalInfo(): {String: String} {
312 return self.additionalInfo
313 }
314
315 access(all) fun totalEditions(): UInt64 {
316 return TuneGONFT.itemEditions[self.itemId] ?? UInt64(0)
317 }
318
319 access(all) fun circulatingEditions(): UInt64 {
320 return TuneGONFT.itemEditions[TuneGONFT.editionCirculatingKey(itemId: self.itemId)] ?? self.totalEditions()
321 }
322
323 access(all) fun _metadata(): Metadata? {
324 let gMetadata = TuneGONFT.loadMetadataStorage().getMetadata(id: self.itemId, edition: self.edition)
325 if gMetadata != nil {
326 return gMetadata!
327 }
328 return self.metadata
329 }
330
331 access(all) fun mintedBlock(): UInt64? {
332 let addVal = self.additionalInfo["mintedBlock"]
333 if addVal != nil {
334 let parsed = UInt64.fromString(addVal!)
335 if parsed != nil { return parsed }
336 }
337 return self._metadata()?.mintedBlock
338 }
339
340 access(all) fun mintedTime(): UFix64? {
341 let addVal = self.additionalInfo["mintedTime"]
342 if addVal != nil {
343 let parsed = UFix64.fromString(addVal!)
344 if parsed != nil { return parsed }
345 }
346 return self._metadata()?.mintedTime
347 }
348
349 access(all) view fun getViews(): [Type] {
350 return [
351 Type<Metadata>(),
352 Type<Edition>(),
353 Type<MetadataViews.Royalties>(),
354 Type<MetadataViews.Display>(),
355 Type<MetadataViews.Editions>(),
356 Type<MetadataViews.ExternalURL>(),
357 Type<MetadataViews.NFTCollectionData>(),
358 Type<MetadataViews.NFTCollectionDisplay>(),
359 Type<MetadataViews.Serial>(),
360 Type<MetadataViews.Traits>()
361 ]
362 }
363
364 access(all) fun resolveView(_ view: Type): AnyStruct? {
365 let metadata = self._metadata()
366 switch view {
367 case Type<Metadata>():
368 return metadata
369 case Type<Edition>():
370 return Edition(
371 edition: self.edition,
372 totalEditions: self.totalEditions()
373 )
374 case Type<MetadataViews.Royalties>():
375 return MetadataViews.Royalties(
376 self.royalties
377 )
378 case Type<MetadataViews.Display>():
379 if metadata == nil { return nil }
380 return MetadataViews.Display(
381 name: metadata!.title,
382 description: metadata!.description,
383 thumbnail: MetadataViews.HTTPFile(
384 url: metadata!.thumbnail
385 )
386 )
387 case Type<MetadataViews.Editions>():
388 let editionInfo = MetadataViews.Edition(name: "TuneGO NFT", number: self.edition, max: nil)
389 let editionList: [MetadataViews.Edition] = [editionInfo]
390 return MetadataViews.Editions(
391 editionList
392 )
393 case Type<MetadataViews.Serial>():
394 return MetadataViews.Serial(
395 self.edition
396 )
397 case Type<MetadataViews.ExternalURL>():
398 return MetadataViews.ExternalURL("https://www.tunegonft.com/view-collectible/".concat(self.uuid.toString()))
399 case Type<MetadataViews.NFTCollectionData>():
400 return TuneGONFT.resolveContractView(resourceType: Type<@TuneGONFT.NFT>(), viewType: Type<MetadataViews.NFTCollectionData>())
401 case Type<MetadataViews.NFTCollectionDisplay>():
402 return TuneGONFT.resolveContractView(resourceType: Type<@TuneGONFT.NFT>(), viewType: Type<MetadataViews.NFTCollectionDisplay>())
403 case Type<MetadataViews.Traits>():
404 let excludedTraits = ["mintedTime"]
405 let dict : {String: AnyStruct?} = metadata == nil ? {} : metadata!.toDict()
406 dict.forEachKey(fun (key: String): Bool {
407 if (dict[key] == nil) {
408 dict.remove(key: key)
409 }
410 return false
411 })
412 let traitsView = MetadataViews.dictToTraits(dict: dict, excludedNames: excludedTraits)
413
414 let mintedTime = self.mintedTime()
415 if mintedTime != nil {
416 // mintedTime is a unix timestamp, we should mark it with a displayType so platforms know how to show it.
417 let mintedTimeTrait = MetadataViews.Trait(name: "mintedTime", value: mintedTime!, displayType: "Date", rarity: nil)
418 traitsView.addTrait(mintedTimeTrait)
419 }
420 let mintedBlock = self.mintedBlock()
421 if mintedBlock != nil {
422 let mintedBlockTrait = MetadataViews.Trait(name: "mintedBlock", value: mintedBlock!, displayType: "Number", rarity: nil)
423 traitsView.addTrait(mintedBlockTrait)
424 }
425 return traitsView
426 }
427
428 return nil
429 }
430
431 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
432 return <-TuneGONFT.createEmptyCollection(nftType: Type<@TuneGONFT.NFT>())
433 }
434
435 }
436
437 // TuneGONFTCollectionPublic
438 //
439 access(all) resource interface TuneGONFTCollectionPublic {
440 access(all) fun deposit(token: @{NonFungibleToken.NFT})
441 access(all) view fun getIDs(): [UInt64]
442 access(all) view fun getLength(): Int
443 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
444 access(all) view fun borrowTuneGONFT(id: UInt64): &TuneGONFT.NFT? {
445 post {
446 (result == nil) || (result?.id == id):
447 "Cannot borrow TuneGONFT reference: The ID of the returned reference is incorrect"
448 }
449 }
450
451 access(all) view fun getSupportedNFTTypes(): {Type: Bool}
452 access(all) view fun isSupportedNFTType(type: Type): Bool
453 }
454
455 // Collection
456 //
457 access(all) resource Collection: NonFungibleToken.Collection, TuneGONFTCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, ViewResolver.ResolverCollection {
458 access(all) event ResourceDestroyed()
459 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
460
461 access(NonFungibleToken.Withdraw)
462 fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
463 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("Missing NFT")
464 let nft <- token as! @TuneGONFT.NFT
465 // let nft = & as! &TuneGONFT.NFT
466 if(!TuneGONFT.loadCollectionManager().checkWithdraw(collectionId: self.uuid, owner: self.owner, itemId: nft.itemId, id: nft.id)){
467 panic("Withdrawal of NFT [".concat(nft.id.toString()).concat("] is currently not permitted."))
468 }
469
470 emit Withdraw(id: nft.id, from: self.owner?.address)
471
472 return <-nft as! @{NonFungibleToken.NFT}
473 }
474
475 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
476 let supportedTypes: {Type: Bool} = {}
477 supportedTypes[Type<@TuneGONFT.NFT>()] = true
478 return supportedTypes
479 }
480
481 access(all) view fun isSupportedNFTType(type: Type): Bool {
482 if type == Type<@TuneGONFT.NFT>() {
483 return true
484 }
485 return false
486 }
487
488 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
489 let token <- token as! @TuneGONFT.NFT
490 let id: UInt64 = token.id
491 let oldToken <- self.ownedNFTs[id] <- token
492
493 emit Deposit(id: id, to: self.owner?.address)
494
495 destroy oldToken
496 }
497
498 access(all) view fun getIDs(): [UInt64] {
499 return self.ownedNFTs.keys
500 }
501
502 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
503 return &self.ownedNFTs[id] as &{NonFungibleToken.NFT}?
504 }
505
506 access(all) view fun borrowTuneGONFT(id: UInt64): &TuneGONFT.NFT? {
507 if self.ownedNFTs[id] != nil {
508 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}? as! &TuneGONFT.NFT?)
509 } else {
510 return nil
511 }
512 }
513
514 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
515 if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
516 return nft as &{ViewResolver.Resolver}
517 }
518 return nil
519 }
520
521 access(all) view fun getLength(): Int {
522 return self.ownedNFTs.keys.length
523 }
524
525 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
526 return <-create TuneGONFT.Collection()
527 }
528
529 init () {
530 self.ownedNFTs <- {}
531 }
532 }
533
534 // createEmptyCollection
535 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
536 assert(nftType == Type<@TuneGONFT.NFT>(), message: "I don't know how to create ".concat(nftType.identifier))
537 return <-create TuneGONFT.Collection()
538 }
539
540 access(all) view fun getContractViews(resourceType: Type?): [Type] {
541 return [
542 Type<MetadataViews.NFTCollectionData>(),
543 Type<MetadataViews.NFTCollectionDisplay>()
544 ]
545 }
546
547 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
548 switch viewType {
549 case Type<MetadataViews.NFTCollectionData>():
550 return MetadataViews.NFTCollectionData(
551 storagePath: TuneGONFT.CollectionStoragePath,
552 publicPath: TuneGONFT.CollectionPublicPath,
553 publicCollection: Type<&TuneGONFT.Collection>(),
554 publicLinkedType: Type<&{TuneGONFT.TuneGONFTCollectionPublic,NonFungibleToken.Collection,ViewResolver.ResolverCollection}>(),
555 createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
556 return <-TuneGONFT.createEmptyCollection(nftType: Type<@TuneGONFT.NFT>())
557 })
558 )
559 case Type<MetadataViews.NFTCollectionDisplay>():
560 let collectionMetadata = TuneGONFT.getDefaultCollectionMetadata()
561 let media = MetadataViews.Media(
562 file: MetadataViews.HTTPFile(
563 url: collectionMetadata.collectionMedia
564 ),
565 mediaType: collectionMetadata.collectionMediaMimeType
566 )
567 let mediaBanner = collectionMetadata.collectionMediaBanner != nil ?
568 MetadataViews.Media(
569 file: MetadataViews.HTTPFile(
570 url: collectionMetadata.collectionMediaBanner!
571 ),
572 mediaType: collectionMetadata.collectionMediaBannerMimeType!
573 )
574 : media
575 let socials: {String:MetadataViews.ExternalURL} = {}
576 for key in collectionMetadata.collectionSocials.keys {
577 socials.insert(key: key,MetadataViews.ExternalURL(collectionMetadata.collectionSocials![key]!))
578 }
579 return MetadataViews.NFTCollectionDisplay(
580 name: collectionMetadata.collectionName,
581 description: collectionMetadata.collectionDescription,
582 externalURL: MetadataViews.ExternalURL(collectionMetadata.collectionURL),
583 squareImage: media,
584 bannerImage: mediaBanner,
585 socials: socials
586 )
587 }
588 return nil
589 }
590
591
592 // burnNFTs
593 //
594 access(all) fun burnNFTs(nfts: @{UInt64: TuneGONFT.NFT}) {
595 let toBurn: Int = nfts.keys.length
596 var nftItemID: String? = nil
597
598 for nftID in nfts.keys {
599 let nft <- nfts.remove(key: nftID)!
600 assert(nft.id == nftID, message: "Invalid nftID")
601
602 nftItemID = nftItemID ?? nft.itemId
603 assert(nftItemID == nft.itemId, message: "All burned NFTs must have the same itemID")
604 assert(Int64(nft.edition) > Int64(nft.circulatingEditions()) - Int64(toBurn), message: "Invalid NFT edition to burn")
605
606 TuneGONFT.itemEditions[TuneGONFT.editionCirculatingKey(itemId: nftItemID!)] = nft.circulatingEditions() - UInt64(1)
607
608 destroy nft
609 emit Burned(id: nftID)
610 }
611
612 destroy nfts
613 }
614
615 // Claiming
616 access(all) fun claimNFT(nft: @{NonFungibleToken.NFT}, receiver: &{NonFungibleToken.Receiver}, tag: String?) {
617 let id = nft.id
618 let type = nft.getType().identifier
619 let recipient = receiver.owner?.address ?? panic("Receiver must be owned")
620
621 receiver.deposit(token:<- nft)
622
623 emit Claimed(id: id, type: type, recipient: recipient, tag: tag)
624 }
625
626 // NFTMinter
627 //
628 access(all) resource NFTMinter {
629
630 access(all) fun computeRoyaltyData(_ royalties: [MetadataViews.Royalty]): [RoyaltyData] {
631 var totalRoyaltiesPercentage: UFix64 = 0.0
632 let royaltiesData: [RoyaltyData] = []
633
634 for royalty in royalties {
635 assert(royalty.receiver.borrow() != nil, message: "Missing royalty receiver")
636 let receiverAccount = getAccount(royalty.receiver.address)
637 let receiverDUCVaultCapability = receiverAccount.capabilities.get<&{FungibleToken.Receiver}>(/public/dapperUtilityCoinReceiver)
638 assert(receiverDUCVaultCapability.borrow() != nil, message: "Missing royalty receiver DapperUtilityCoin vault")
639
640 let royaltyPercentage = royalty.cut * 100.0
641 royaltiesData.append(RoyaltyData(
642 receiver: receiverAccount.address,
643 percentage: royaltyPercentage
644 ))
645 totalRoyaltiesPercentage = totalRoyaltiesPercentage + royaltyPercentage;
646 }
647 assert(totalRoyaltiesPercentage <= 95.0, message: "Total royalties percentage is too high")
648
649 return royaltiesData
650 }
651
652 access(all) fun mintNFTDirect(
653 itemId: String,
654 metadata: Metadata,
655 royalties: [MetadataViews.Royalty],
656 additionalInfo: {String: String}
657 ): @NFT {
658 assert(TuneGONFT.itemEditions[TuneGONFT.editionCirculatingKey(itemId: itemId)] == nil, message: "New NFTs cannot be minted")
659
660 let royaltiesData: [RoyaltyData] = self.computeRoyaltyData(royalties)
661
662 let totalEditions = TuneGONFT.itemEditions[itemId] != nil ? TuneGONFT.itemEditions[itemId] : UInt64(0)
663 let edition = totalEditions! + UInt64(1)
664
665 let id = TuneGONFT.totalSupply
666
667 emit Minted(
668 id: id,
669 itemId: itemId,
670 edition: edition,
671 royalties: royaltiesData,
672 additionalInfo: additionalInfo
673 )
674
675 let res <-create TuneGONFT.NFT(
676 id: TuneGONFT.totalSupply,
677 itemId: itemId,
678 edition: edition,
679 metadata: metadata,
680 royalties: royalties,
681 additionalInfo: additionalInfo
682 )
683
684 TuneGONFT.itemEditions[itemId] = totalEditions! + UInt64(1)
685 TuneGONFT.totalSupply = TuneGONFT.totalSupply + UInt64(1)
686
687 return <-res
688 }
689
690 access(all) fun mintNFT(
691 recipient: &{NonFungibleToken.CollectionPublic},
692 itemId: String,
693 metadata: Metadata,
694 royalties: [MetadataViews.Royalty],
695 additionalInfo: {String: String}
696 ): UInt64 {
697 let id = TuneGONFT.totalSupply
698 recipient.deposit(token: <- self.mintNFTDirect(itemId: itemId, metadata: metadata, royalties: royalties, additionalInfo: additionalInfo) )
699 return id
700 }
701
702 access(all) fun batchMintNFTOld(
703 recipient: &{NonFungibleToken.CollectionPublic},
704 itemId: String,
705 metadata: Metadata,
706 royalties: [MetadataViews.Royalty],
707 additionalInfo: {String: String},
708 quantity: UInt64
709 ) {
710 var i: UInt64 = 0
711 while i < quantity {
712 i = i + UInt64(1)
713 self.mintNFT(
714 recipient: recipient,
715 itemId: itemId,
716 metadata: metadata,
717 royalties: royalties,
718 additionalInfo: additionalInfo
719 )
720 }
721 }
722
723 access(all) fun batchMintNFT(
724 recipient: &{NonFungibleToken.CollectionPublic},
725 itemId: String,
726 metadata: Metadata,
727 royalties: [MetadataViews.Royalty],
728 additionalInfo: {String: String},
729 quantity: UInt64
730 ) {
731
732 assert(TuneGONFT.itemEditions[TuneGONFT.editionCirculatingKey(itemId: itemId)] == nil, message: "New NFTs cannot be minted")
733
734 let royaltiesData: [RoyaltyData] = self.computeRoyaltyData(royalties)
735
736 let totalEditions = TuneGONFT.itemEditions[itemId] != nil ? TuneGONFT.itemEditions[itemId]! : UInt64(0)
737 var i: UInt64 = 0
738 while i < quantity {
739 let id = TuneGONFT.totalSupply + i
740 i = i + UInt64(1)
741 let edition = totalEditions + i
742
743 emit Minted(
744 id: id,
745 itemId: itemId,
746 edition: edition,
747 royalties: royaltiesData,
748 additionalInfo: additionalInfo
749 )
750
751 recipient.deposit(token: <-create TuneGONFT.NFT(
752 id: id,
753 itemId: itemId,
754 edition: edition,
755 metadata: metadata,
756 royalties: royalties,
757 additionalInfo: additionalInfo
758 ))
759
760 }
761 TuneGONFT.itemEditions[itemId] = totalEditions + quantity
762 TuneGONFT.totalSupply = TuneGONFT.totalSupply + quantity
763 }
764
765 access(all) fun mintNftNew(
766 recipient: &{NonFungibleToken.CollectionPublic},
767 itemId: String,
768 royalties: [MetadataViews.Royalty],
769 additionalInfo: {String: String},
770 ): UInt64 {
771 let totalEditions = TuneGONFT.itemEditions[itemId] != nil ? TuneGONFT.itemEditions[itemId] : UInt64(0)
772 let edition = totalEditions! + UInt64(1)
773 let metadata = TuneGONFT.loadMetadataStorage().getMetadata(id: itemId, edition: edition)
774 assert(metadata != nil, message: "Metadata for itemId must be set")
775
776 let currentBlock = getCurrentBlock()
777 additionalInfo["mintedBlock"] = currentBlock.height.toString()
778 additionalInfo["mintedTime"] = currentBlock.timestamp.toString()
779
780 return self.mintNFT(
781 recipient: recipient,
782 itemId: itemId,
783 metadata: metadata!,
784 royalties: royalties,
785 additionalInfo: additionalInfo
786 )
787 }
788
789 access(all) fun batchMintNew(
790 recipient: &{NonFungibleToken.CollectionPublic},
791 itemId: String,
792 royalties: [MetadataViews.Royalty],
793 additionalInfo: {String: String},
794 quantity: UInt64
795 ) {
796 let metadata = TuneGONFT.loadMetadataStorage().getMetadata(id: itemId, edition: nil)
797 assert(metadata != nil, message: "Metadata for itemId must be set")
798
799 assert(TuneGONFT.itemEditions[TuneGONFT.editionCirculatingKey(itemId: itemId)] == nil, message: "New NFTs cannot be minted")
800
801 let royaltiesData: [RoyaltyData] = self.computeRoyaltyData(royalties)
802 let totalEditions = TuneGONFT.itemEditions[itemId] != nil ? TuneGONFT.itemEditions[itemId]! : UInt64(0)
803 var i: UInt64 = 0
804 while i < quantity {
805 let id = TuneGONFT.totalSupply + i
806 i = i + UInt64(1)
807 let edition = totalEditions + i
808
809 emit Minted(
810 id: id,
811 itemId: itemId,
812 edition: edition,
813 royalties: royaltiesData,
814 additionalInfo: additionalInfo
815 )
816
817 recipient.deposit(token: <-create TuneGONFT.NFT(
818 id: id,
819 itemId: itemId,
820 edition: edition,
821 metadata: metadata!,
822 royalties: royalties,
823 additionalInfo: additionalInfo
824 ))
825 }
826 TuneGONFT.itemEditions[itemId] = totalEditions + quantity
827 TuneGONFT.totalSupply = TuneGONFT.totalSupply + quantity
828 }
829
830 access(all) fun checkIncClaim(id: String, address: Address, max: UInt16): Bool {
831 return TuneGONFT.loadMetadataStorage().checkIncClaim(id: id, address: address, max: max)
832 }
833
834 access(all) fun readClaims(id: String, addresses: [Address]): {Address: UInt16} {
835 let empty: {String: UInt16} = {}
836 let storage = TuneGONFT.loadMetadataStorage()
837 let res: {Address: UInt16} = {}
838 for address in addresses {
839 let claims: &{String:UInt16} = storage.claims[address] ?? &empty
840 res[address] = claims[id] ?? UInt16(0)
841 }
842 return res
843 }
844
845 access(all) fun emitClaimedReward(id: String, address: Address) {
846 emit ClaimedReward(id: id, recipient: address)
847 }
848
849 access(all) fun setMetadata(id: String, edition: UInt64?, metadata: Metadata) {
850 TuneGONFT.loadMetadataStorage().setMetadata(id: id, edition: edition, data: metadata)
851 }
852 access(all) fun getMetadata(id: String, edition: UInt64?): Metadata? {
853 return TuneGONFT.loadMetadataStorage().getMetadata(id: id, edition: edition)
854 }
855
856 access(all) fun setRoyalties(id: String, edition: UInt64?, royalties: {Address:UFix64}) {
857 TuneGONFT.loadMetadataStorage().setRoyalties(id: id, edition: edition, royalties: royalties)
858 }
859 access(all) fun getRoyalties(id: String, edition: UInt64?): {Address:UFix64}? {
860 return TuneGONFT.loadMetadataStorage().getRoyalties(id: id, edition: edition)
861 }
862
863 access(all) fun getCollectionManager(): &CollectionManager {
864 return TuneGONFT.loadCollectionManager()
865 }
866 access(all) fun createNFTMinter(): @NFTMinter {
867 return <- create NFTMinter()
868 }
869 }
870
871 access(all) resource MetadataStorage {
872 access(account) let metadatas: {String: Metadata }
873 access(account) let royalties: {String: {Address:UFix64} }
874 access(account) let claims: {Address: {String:UInt16} }
875 access(account) fun setMetadata(id: String, edition: UInt64?, data: Metadata) {
876 let fullId = edition == nil ? id : id.concat(edition!.toString())
877 if(self.metadatas[fullId] != nil){ return }
878 self.metadatas[fullId] = data
879 }
880 access(all) fun getMetadata(id: String, edition: UInt64?): Metadata? {
881 if edition != nil {
882 let perItem = self.metadatas[id.concat(edition!.toString())]
883 if perItem != nil { return perItem }
884 }
885 return self.metadatas[id]
886 }
887 access(account) fun setRoyalties(id: String, edition: UInt64?, royalties: {Address:UFix64}) {
888 let fullId = edition == nil ? id : id.concat(edition!.toString())
889 if(self.royalties[fullId] != nil){ return }
890 self.royalties[fullId] = royalties
891 }
892 access(all) fun getRoyalties(id: String, edition: UInt64?): {Address:UFix64}? {
893 if edition != nil {
894 let perItem = self.royalties[id.concat(edition!.toString())]
895 if perItem != nil { return perItem }
896 }
897 return self.royalties[id]
898 }
899 access(account) fun checkIncClaim(id: String, address: Address, max: UInt16): Bool {
900 if(self.claims[address] == nil) {
901 self.claims[address] = {}
902 }
903 let claims = self.claims[address]!
904 let prev: UInt16 = claims[id] ?? 0
905 if(prev >= max){ return false }
906 claims[id] = prev + UInt16(1)
907 self.claims[address] = claims
908 return true
909 }
910 init() {
911 self.metadatas = {}
912 self.royalties = {}
913 self.claims = {}
914 }
915 }
916
917 access(contract) fun loadMetadataStorage(): &MetadataStorage {
918 if let existing = self.account.storage.borrow<&MetadataStorage>(from: /storage/metadataStorage) {
919 return existing
920 }
921 let res <- create MetadataStorage()
922 self.account.storage.save(<-res, to: /storage/metadataStorage)
923
924 return self.account.storage.borrow<&MetadataStorage>(from: /storage/metadataStorage)!
925 }
926
927 access(all) fun getMetadata(id: String, edition: UInt64?): Metadata? {
928 return TuneGONFT.loadMetadataStorage().getMetadata(id: id, edition: edition)
929 }
930
931 //
932 access(all) resource CollectionManager {
933 access(contract) var withheldDefault: Bool
934 access(contract) let privilegedCollections: [UInt64]
935 access(contract) let privilegedCollectionOwners: [String]
936 access(contract) let withheldItemIds: [String]
937 access(contract) let withheldIds: [UInt64]
938 access(contract) let whitelistedItemIds: [String]
939 access(contract) let whitelistedIds: [UInt64]
940
941
942 init() {
943 self.withheldDefault = false
944 self.privilegedCollections = []
945 self.privilegedCollectionOwners = []
946 self.withheldItemIds = []
947 self.withheldIds = []
948 self.whitelistedItemIds = []
949 self.whitelistedIds = []
950 }
951
952 access(all) fun checkWithdraw(collectionId: UInt64, owner: &Account?, itemId: String, id: UInt64): Bool {
953 if(self.privilegedCollections.contains(collectionId)){ return true }
954 if(owner != nil && self.privilegedCollectionOwners.contains(owner!.address.toString())){ return true }
955 if(self.withheldIds.contains(id)){ return false }
956 if(self.whitelistedIds.contains(id)){ return true }
957 if(self.withheldItemIds.contains(itemId)){ return false }
958 if(self.whitelistedItemIds.contains(itemId)){ return true }
959 return !self.withheldDefault
960 }
961
962 access(all) fun setWithheldDefault(_ value: Bool) {
963 self.withheldDefault = value;
964 }
965
966 access(all) fun addPrivilegedCollection(_ collectionId: UInt64) {
967 if(!self.privilegedCollections.contains(collectionId)){
968 self.privilegedCollections.append(collectionId)
969 }
970 }
971 access(all) fun removePrivilegedCollection(_ collectionId: UInt64) {
972 let index = self.privilegedCollections.firstIndex(of: collectionId)
973 if(index != nil){
974 self.privilegedCollections.remove(at: index!)
975 }
976 }
977
978 access(all) fun addPrivilegedCollectionOwner(_ addrString: String) {
979 if(!self.privilegedCollectionOwners.contains(addrString)){
980 self.privilegedCollectionOwners.append(addrString)
981 }
982 }
983 access(all) fun removePrivilegedCollectionOwner(_ addrString: String) {
984 let index = self.privilegedCollectionOwners.firstIndex(of: addrString)
985 if(index != nil){
986 self.privilegedCollectionOwners.remove(at: index!)
987 }
988 }
989
990 access(all) fun addWithheldItemId(_ itemId: String) {
991 if(!self.withheldItemIds.contains(itemId)){
992 self.withheldItemIds.append(itemId)
993 }
994 }
995 access(all) fun removeWithheldItemId(_ itemId: String) {
996 let index = self.withheldItemIds.firstIndex(of: itemId)
997 if(index != nil){
998 self.withheldItemIds.remove(at: index!)
999 }
1000 }
1001
1002 access(all) fun addWithheldId(_ id: UInt64) {
1003 if(!self.withheldIds.contains(id)){
1004 self.withheldIds.append(id)
1005 }
1006 }
1007 access(all) fun removeWithheldId(_ id: UInt64) {
1008 let index = self.withheldIds.firstIndex(of: id)
1009 if(index != nil){
1010 self.withheldIds.remove(at: index!)
1011 }
1012 }
1013
1014
1015 access(all) fun addWhitelistedItemId(_ itemId: String) {
1016 if(!self.whitelistedItemIds.contains(itemId)){
1017 self.whitelistedItemIds.append(itemId)
1018 }
1019 }
1020 access(all) fun removeWhitelistedItemId(_ itemId: String) {
1021 let index = self.whitelistedItemIds.firstIndex(of: itemId)
1022 if(index != nil){
1023 self.whitelistedItemIds.remove(at: index!)
1024 }
1025 }
1026
1027 access(all) fun addWhitelistedId(_ id: UInt64) {
1028 if(!self.whitelistedIds.contains(id)){
1029 self.whitelistedIds.append(id)
1030 }
1031 }
1032 access(all) fun removeWhitelistedId(_ id: UInt64) {
1033 let index = self.whitelistedIds.firstIndex(of: id)
1034 if(index != nil){
1035 self.whitelistedIds.remove(at: index!)
1036 }
1037 }
1038
1039 }
1040
1041 access(account) fun loadCollectionManager(): &CollectionManager {
1042 if let existing = self.account.storage.borrow<&CollectionManager>(from: /storage/tunegoCollectionManager) {
1043 return existing
1044 }
1045 let res <- create CollectionManager()
1046 self.account.storage.save(<-res, to: /storage/tunegoCollectionManager)
1047
1048 return self.account.storage.borrow<&CollectionManager>(from: /storage/tunegoCollectionManager)!
1049 }
1050
1051 init () {
1052 self.CollectionStoragePath = /storage/tunegoNFTCollection013
1053 self.CollectionPublicPath = /public/tunegoNFTCollection013
1054 self.MinterStoragePath = /storage/tunegoNFTMinter013
1055
1056 self.totalSupply = 0
1057 self.itemEditions = {}
1058
1059 let minter <- create NFTMinter()
1060 self.account.storage.save(<-minter, to: self.MinterStoragePath)
1061
1062 emit ContractInitialized()
1063 }
1064}