Smart Contract
pinknft_NFT
A.273f7ae4c0177f7a.pinknft_NFT
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