Smart Contract

pinknft_NFT

A.273f7ae4c0177f7a.pinknft_NFT

Valid From

82,064,922

Deployed

1y ago
Jul 09, 2024, 04:01:44 PM UTC

Dependents

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