Smart Contract

TuneGONFT

A.c6945445cdbefec9.TuneGONFT

Deployed

2w ago
Feb 11, 2026, 06:38:51 PM UTC

Dependents

3391 imports
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}