Smart Contract
Analogs
A.427ceada271aa0b1.Analogs
1import FungibleToken from 0xf233dcee88fe0abe
2import NonFungibleToken from 0x1d7e57aa55817448
3import MetadataViews from 0x1d7e57aa55817448
4
5pub contract Analogs: NonFungibleToken {
6
7 pub event ContractInitialized()
8 pub event AccountInitialized()
9 pub event SetCreated(setID: UInt64)
10 pub event NFTTemplateCreated(templateID: UInt64, metadata: {String: String})
11 pub event Withdraw(id: UInt64, from: Address?)
12 pub event Deposit(id: UInt64, to: Address?)
13 pub event Minted(id: UInt64, templateID: UInt64)
14 pub event TemplateAddedToSet(setID: UInt64, templateID: UInt64)
15 pub event TemplateLockedFromSet(setID: UInt64, templateID: UInt64)
16 pub event TemplateUpdated(template: AnalogsTemplate)
17 pub event SetLocked(setID: UInt64)
18 pub event SetUnlocked(setID: UInt64)
19
20 pub let CollectionStoragePath: StoragePath
21 pub let CollectionPublicPath: PublicPath
22 pub let AdminStoragePath: StoragePath
23
24 pub var totalSupply: UInt64
25 pub var initialNFTID: UInt64
26 pub var nextNFTID: UInt64
27 pub var nextTemplateID: UInt64
28 pub var nextSetID: UInt64
29
30 access(self) var analogsTemplates: {UInt64: AnalogsTemplate}
31 access(self) var sets: @{UInt64: Set}
32
33 pub resource interface AnalogsCollectionPublic {
34 pub fun deposit(token: @NonFungibleToken.NFT)
35 pub fun getIDs(): [UInt64]
36 pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
37 pub fun borrowAnalog(id: UInt64): &Analogs.NFT? {
38 post {
39 (result == nil) || (result?.id == id):
40 "Cannot borrow Analogs reference: The ID of the returned reference is incorrect"
41 }
42 }
43 }
44
45 pub struct AnalogsTemplate {
46 pub let templateID: UInt64
47 pub var name: String
48 pub var description: String
49 pub var locked: Bool
50 pub var addedToSet: UInt64
51 access(self) var metadata: {String: String}
52
53 pub fun getMetadata(): {String: String} {
54 return self.metadata
55 }
56
57 pub fun lockTemplate() {
58 self.locked = true
59 }
60
61 pub fun updateMetadata(newMetadata: {String: String}) {
62 pre {
63 newMetadata.length != 0: "New Template metadata cannot be empty"
64 }
65 self.metadata = newMetadata
66 }
67
68 pub fun markAddedToSet(setID: UInt64) {
69 self.addedToSet = setID
70 }
71
72 init(templateID: UInt64, name: String, description: String, metadata: {String: String}){
73 pre {
74 metadata.length != 0: "New Template metadata cannot be empty"
75 }
76
77 self.templateID = templateID
78 self.name = name
79 self.description= description
80 self.metadata = metadata
81 self.locked = false
82 self.addedToSet = 0
83
84 emit NFTTemplateCreated(templateID: self.templateID, metadata: self.metadata)
85 }
86 }
87
88 pub struct Royalty {
89 pub let address: Address
90 pub let primaryCut: UFix64
91 pub let secondaryCut: UFix64
92 pub let description: String
93
94 init(address: Address, primaryCut: UFix64, secondaryCut: UFix64, description: String) {
95 pre {
96 primaryCut >= 0.0 && primaryCut <= 1.0 : "primaryCut value should be in valid range i.e [0,1]"
97 secondaryCut >= 0.0 && secondaryCut <= 1.0 : "secondaryCut value should be in valid range i.e [0,1]"
98 }
99 self.address = address
100 self.primaryCut = primaryCut
101 self.secondaryCut = secondaryCut
102 self.description = description
103 }
104 }
105
106 pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver {
107 pub let id: UInt64
108 pub let templateID: UInt64
109 pub var serialNumber: UInt64
110
111 pub fun getViews(): [Type] {
112 return [
113 Type<MetadataViews.ExternalURL>(),
114 Type<MetadataViews.NFTCollectionData>(),
115 Type<MetadataViews.NFTCollectionDisplay>(),
116 Type<MetadataViews.Display>(),
117 Type<MetadataViews.Medias>(),
118 Type<MetadataViews.Royalties>()
119 ]
120 }
121
122 pub fun resolveView(_ view: Type): AnyStruct? {
123 let metadata = Analogs.analogsTemplates[self.templateID]!.getMetadata()
124 let thumbnailCID = metadata["thumbnailCID"] != nil ? metadata["thumbnailCID"]! : metadata["imageCID"]!
125 switch view {
126 case Type<MetadataViews.ExternalURL>():
127 return MetadataViews.ExternalURL("https://ipfs.io/ipfs/".concat(thumbnailCID))
128 case Type<MetadataViews.NFTCollectionData>():
129 return MetadataViews.NFTCollectionData(
130 storagePath: Analogs.CollectionStoragePath,
131 publicPath: Analogs.CollectionPublicPath,
132 providerPath: /private/AnalogsCollection,
133 publicCollection: Type<&Analogs.Collection{Analogs.AnalogsCollectionPublic}>(),
134 publicLinkedType: Type<&Analogs.Collection{Analogs.AnalogsCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Receiver,MetadataViews.ResolverCollection}>(),
135 providerLinkedType: Type<&Analogs.Collection{Analogs.AnalogsCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Provider,MetadataViews.ResolverCollection}>(),
136 createEmptyCollectionFunction: (fun (): @NonFungibleToken.Collection {
137 return <-Analogs.createEmptyCollection()
138 })
139 )
140 case Type<MetadataViews.NFTCollectionDisplay>():
141 let media = MetadataViews.Media(
142 file: MetadataViews.HTTPFile(url: "https://ipfs.io/ipfs/bafkreidhuylwtdgug3vuamphju44r7eam5wlels4tejbkz4nvelnluktcm"),
143 mediaType: "image/jpeg"
144 )
145 return MetadataViews.NFTCollectionDisplay(
146 name: "Heavy Metal Analogs",
147 description: "",
148 externalURL: MetadataViews.ExternalURL("https://sturdy.exchange/"),
149 squareImage: media,
150 bannerImage: media,
151 socials: {}
152 )
153 case Type<MetadataViews.Display>():
154 return MetadataViews.Display(
155 name: Analogs.analogsTemplates[self.templateID]!.name,
156 description: Analogs.analogsTemplates[self.templateID]!.description,
157 thumbnail: MetadataViews.HTTPFile(
158 url: "https://ipfs.io/ipfs/".concat(thumbnailCID)
159 )
160 )
161 case Type<MetadataViews.Medias>():
162 let medias: [MetadataViews.Media] = [];
163 let videoCID = Analogs.analogsTemplates[self.templateID]!.getMetadata()["videoCID"]
164 let imageCID = thumbnailCID
165 if videoCID != nil {
166 medias.append(
167 MetadataViews.Media(
168 file: MetadataViews.HTTPFile(
169 url: "https://ipfs.io/ipfs/".concat(videoCID!)
170 ),
171 mediaType: "video/mp4"
172 )
173 )
174 }
175 else if imageCID != nil {
176 medias.append(
177 MetadataViews.Media(
178 file: MetadataViews.HTTPFile(
179 url: "https://ipfs.io/ipfs/".concat(imageCID)
180 ),
181 mediaType: "image/jpeg"
182 )
183 )
184 }
185 return MetadataViews.Medias(medias)
186 case Type<MetadataViews.Royalties>():
187 let setID = Analogs.analogsTemplates[self.templateID]!.addedToSet
188 let setRoyalties = Analogs.getSetRoyalties(setID: setID)
189 let royalties: [MetadataViews.Royalty] = []
190 for royalty in setRoyalties {
191 royalties.append(
192 MetadataViews.Royalty(
193 receiver: getAccount(royalty.address)
194 .getCapability<&{FungibleToken.Receiver}>(/public/flowUtilityTokenReceiver),
195 cut: royalty.secondaryCut,
196 description: royalty.description
197 )
198 )
199 }
200 return MetadataViews.Royalties(royalties)
201 }
202 return nil
203 }
204
205 pub fun getNFTMetadata(): {String: String} {
206 return Analogs.analogsTemplates[self.templateID]!.getMetadata()
207 }
208
209 init(initID: UInt64, initTemplateID: UInt64, serialNumber: UInt64) {
210 self.id = initID
211 self.templateID = initTemplateID
212 self.serialNumber = serialNumber
213 }
214 }
215
216 pub resource Collection: AnalogsCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection {
217 pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
218
219 pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
220 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
221 emit Withdraw(id: token.id, from: self.owner?.address)
222 return <-token
223 }
224
225 pub fun deposit(token: @NonFungibleToken.NFT) {
226 let token <- token as! @Analogs.NFT
227 let id: UInt64 = token.id
228 let oldToken <- self.ownedNFTs[id] <- token
229 emit Deposit(id: id, to: self.owner?.address)
230 destroy oldToken
231 }
232
233 pub fun batchDeposit(collection: @Collection) {
234 let keys = collection.getIDs()
235 for key in keys {
236 self.deposit(token: <-collection.withdraw(withdrawID: key))
237 }
238 destroy collection
239 }
240
241 pub fun getIDs(): [UInt64] {
242 return self.ownedNFTs.keys
243 }
244
245 pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
246 return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
247 }
248
249 pub fun borrowAnalog(id: UInt64): &Analogs.NFT? {
250 if self.ownedNFTs[id] != nil {
251 let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
252 return ref as! &Analogs.NFT
253 } else {
254 return nil
255 }
256 }
257
258 pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} {
259 let nft = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
260 let exampleNFT = nft as! &Analogs.NFT
261 return exampleNFT as &AnyResource{MetadataViews.Resolver}
262 }
263
264 destroy() {
265 destroy self.ownedNFTs
266 }
267
268 init () {
269 self.ownedNFTs <- {}
270 }
271 }
272
273 pub fun createEmptyCollection(): @NonFungibleToken.Collection {
274 emit AccountInitialized()
275 return <- create Collection()
276 }
277
278 pub resource Set {
279 pub let setID: UInt64
280 pub let name: String
281 access(self) var templateIDs: [UInt64]
282 access(self) var availableTemplateIDs: [UInt64]
283 access(self) var lockedTemplates: {UInt64: Bool}
284 access(self) var metadata: {String: String}
285 pub var locked: Bool
286 pub var nextSetSerialNumber: UInt64
287 pub var isPublic: Bool
288 pub var analogRoyaltyAddress: Address
289 pub var analogRoyaltySecondaryCut: UFix64
290 pub var artistRoyalties: [Royalty]
291
292
293 init(name: String, analogRoyaltyAddress: Address, analogRoyaltySecondaryCut: UFix64, imageCID: String) {
294 self.name = name
295 self.setID = Analogs.nextSetID
296 self.templateIDs = []
297 self.lockedTemplates = {}
298 self.locked = false
299 self.availableTemplateIDs = []
300 self.nextSetSerialNumber = 1
301 self.isPublic = false
302 self.analogRoyaltyAddress = analogRoyaltyAddress
303 self.analogRoyaltySecondaryCut = analogRoyaltySecondaryCut
304 self.artistRoyalties = []
305 self.metadata = { "imageCID": imageCID }
306
307 Analogs.nextSetID = Analogs.nextSetID + 1
308 emit SetCreated(setID: self.setID)
309 }
310
311 pub fun getAvailableTemplateIDs(): [UInt64] {
312 return self.availableTemplateIDs
313 }
314
315 pub fun makeSetPublic() {
316 self.isPublic = true
317 }
318
319 pub fun makeSetPrivate() {
320 self.isPublic = false
321 }
322
323 pub fun updateAnalogRoyaltyAddress(analogRoyaltyAddress: Address) {
324 self.analogRoyaltyAddress = analogRoyaltyAddress
325 }
326
327 pub fun updateAnalogRoyaltySecondaryCut(analogRoyaltySecondaryCut: UFix64) {
328 self.analogRoyaltySecondaryCut = analogRoyaltySecondaryCut
329 }
330
331 pub fun addArtistRoyalty(royalty: Royalty) {
332 self.artistRoyalties.append(royalty)
333 }
334
335 pub fun addTemplate(templateID: UInt64, available: Bool) {
336 pre {
337 Analogs.analogsTemplates[templateID] != nil:
338 "Template doesn't exist"
339 !self.locked:
340 "Cannot add template - set is locked"
341 !self.templateIDs.contains(templateID):
342 "Cannot add template - template is already added to the set"
343 !(Analogs.analogsTemplates[templateID]!.addedToSet != 0):
344 "Cannot add template - template is already added to another set"
345 }
346
347 self.templateIDs.append(templateID)
348 if available {
349 self.availableTemplateIDs.append(templateID)
350 }
351 self.lockedTemplates[templateID] = !available
352 Analogs.analogsTemplates[templateID]!.markAddedToSet(setID: self.setID)
353
354 emit TemplateAddedToSet(setID: self.setID, templateID: templateID)
355 }
356
357 pub fun addTemplates(templateIDs: [UInt64], available: Bool) {
358 for template in templateIDs {
359 self.addTemplate(templateID: template, available: available)
360 }
361 }
362
363 pub fun lockTemplate(templateID: UInt64) {
364 pre {
365 self.lockedTemplates[templateID] != nil:
366 "Cannot lock the template: Template is locked already!"
367 !self.availableTemplateIDs.contains(templateID):
368 "Cannot lock a not yet minted template!"
369 }
370
371 if !self.lockedTemplates[templateID]! {
372 self.lockedTemplates[templateID] = true
373 emit TemplateLockedFromSet(setID: self.setID, templateID: templateID)
374 }
375 }
376
377 pub fun lockAllTemplates() {
378 for template in self.templateIDs {
379 self.lockTemplate(templateID: template)
380 }
381 }
382
383 pub fun lock() {
384 if !self.locked {
385 self.locked = true
386 emit SetLocked(setID: self.setID)
387 }
388 }
389
390 pub fun unlock() {
391 if self.locked {
392 self.locked = false
393 emit SetUnlocked(setID: self.setID)
394 }
395 }
396
397 pub fun mintNFT(): @NFT {
398 let templateID = self.availableTemplateIDs[0]
399 if (Analogs.analogsTemplates[templateID]!.locked) {
400 panic("template is locked")
401 }
402
403 let newNFT: @NFT <- create Analogs.NFT(initID: Analogs.nextNFTID, initTemplateID: templateID, serialNumber: self.nextSetSerialNumber)
404
405 Analogs.totalSupply = Analogs.totalSupply + 1
406 Analogs.nextNFTID = Analogs.nextNFTID + 1
407 self.nextSetSerialNumber = self.nextSetSerialNumber + 1
408 self.availableTemplateIDs.remove(at: 0)
409
410 emit Minted(id: newNFT.id, templateID: newNFT.templateID)
411
412 return <-newNFT
413 }
414
415 pub fun mintNFTByTemplateID(templateID: UInt64): @NFT {
416 let newNFT: @NFT <- create Analogs.NFT(initID: templateID, initTemplateID: templateID, serialNumber: self.nextSetSerialNumber)
417
418 Analogs.totalSupply = Analogs.totalSupply + 1
419 self.nextSetSerialNumber = self.nextSetSerialNumber + 1
420 self.lockTemplate(templateID: templateID)
421
422 emit Minted(id: newNFT.id, templateID: newNFT.templateID)
423
424 return <-newNFT
425 }
426
427 pub fun updateTemplateMetadata(templateID: UInt64, newMetadata: {String: String}):AnalogsTemplate {
428 pre {
429 Analogs.analogsTemplates[templateID] != nil:
430 "Template doesn't exist"
431 !self.locked:
432 "Cannot edit template - set is locked"
433 }
434
435 Analogs.analogsTemplates[templateID]!.updateMetadata(newMetadata: newMetadata)
436 emit TemplateUpdated(template: Analogs.analogsTemplates[templateID]!)
437 return Analogs.analogsTemplates[templateID]!
438 }
439
440 pub fun getImageCID(): String? {
441 return self.metadata["imageCID"]
442 }
443
444 pub fun updateImageCID(imageCID: String) {
445 self.metadata["imageCID"] = imageCID
446 }
447 }
448
449 pub fun getSetName(setID: UInt64): String {
450 pre {
451 Analogs.sets[setID] != nil: "Cannot borrow Set: The Set doesn't exist"
452 }
453
454 let set = (&Analogs.sets[setID] as &Set?)!
455 return set.name
456 }
457
458 pub fun getSetImageCID(setID: UInt64): String? {
459 pre {
460 Analogs.sets[setID] != nil: "Cannot borrow Set: The Set doesn't exist"
461 }
462
463 let set = (&Analogs.sets[setID] as &Set?)!
464 return set.getImageCID()
465 }
466
467 pub fun getSetRoyalties(setID: UInt64): [Royalty] {
468 pre {
469 Analogs.sets[setID] != nil: "Cannot borrow Set: The Set doesn't exist"
470 }
471
472 let set = (&Analogs.sets[setID] as &Set?)!
473 var analogRoyaltyPrimaryCut: UFix64 = 1.00
474 for royalty in set.artistRoyalties {
475 analogRoyaltyPrimaryCut = analogRoyaltyPrimaryCut - royalty.primaryCut
476 }
477 let royalties = [
478 Royalty(
479 address: set.analogRoyaltyAddress,
480 primaryCut: analogRoyaltyPrimaryCut,
481 secondaryCut: set.analogRoyaltySecondaryCut,
482 description: "Sturdy Royalty"
483 )
484 ]
485 royalties.appendAll(set.artistRoyalties)
486 return royalties
487 }
488
489 pub resource Admin {
490
491 pub fun mintNFT(recipient: &{NonFungibleToken.CollectionPublic}, setID: UInt64) {
492 let set = self.borrowSet(setID: setID)
493 if (set.getAvailableTemplateIDs()!.length == 0){
494 panic("Set is empty")
495 }
496 if (set.locked) {
497 panic("Set is locked")
498 }
499 recipient.deposit(token: <- set.mintNFT())
500 }
501
502 pub fun createAndMintNFT(recipient: &{NonFungibleToken.CollectionPublic}, templateID: UInt64, setID: UInt64, name: String, description: String, metadata: {String: String}) {
503 if Analogs.analogsTemplates[Analogs.nextTemplateID] != nil {
504 panic("Template already exists")
505 }
506 Analogs.analogsTemplates[templateID] = AnalogsTemplate(
507 templateID: templateID,
508 name: name,
509 description: description,
510 metadata: metadata
511 )
512 let set = self.borrowSet(setID: setID)
513 set.addTemplate(templateID: templateID, available: false)
514 recipient.deposit(token: <- set.mintNFTByTemplateID(templateID: templateID))
515 }
516
517 pub fun createAnalogsTemplate(name: String, description: String, metadata: {String: String}) {
518 Analogs.analogsTemplates[Analogs.nextTemplateID] = AnalogsTemplate(
519 templateID: Analogs.nextTemplateID,
520 name: name,
521 description: description,
522 metadata: metadata
523 )
524 Analogs.nextTemplateID = Analogs.nextTemplateID + 1
525 }
526
527 pub fun createSet(name: String, analogRoyaltyAddress: Address, analogRoyaltySecondaryCut: UFix64, imageCID: String): UInt64 {
528 var newSet <- create Set(name: name, analogRoyaltyAddress: analogRoyaltyAddress, analogRoyaltySecondaryCut: analogRoyaltySecondaryCut, imageCID: imageCID)
529 let setID = newSet.setID
530 Analogs.sets[setID] <-! newSet
531 return setID
532 }
533
534 pub fun borrowSet(setID: UInt64): &Set {
535 pre {
536 Analogs.sets[setID] != nil:
537 "Cannot borrow Set: The Set doesn't exist"
538 }
539
540 return (&Analogs.sets[setID] as &Set?)!
541 }
542
543 pub fun updateSetImageCID(setID: UInt64, imageCID: String) {
544 pre {
545 Analogs.sets[setID] != nil: "Cannot borrow Set: The Set doesn't exist"
546 }
547
548 let set = (&Analogs.sets[setID] as &Set?)!
549 return set.updateImageCID(imageCID: imageCID)
550 }
551
552 pub fun updateAnalogsTemplate(templateID: UInt64, newMetadata: {String: String}) {
553 pre {
554 Analogs.analogsTemplates.containsKey(templateID) != nil:
555 "Template does not exists."
556 }
557 Analogs.analogsTemplates[templateID]!.updateMetadata(newMetadata: newMetadata)
558 }
559
560 pub fun setInitialNFTID(initialNFTID: UInt64) {
561 pre {
562 Analogs.initialNFTID == 0:
563 "initialNFTID is already initialized"
564 }
565 Analogs.initialNFTID = initialNFTID
566 Analogs.nextNFTID = initialNFTID
567 Analogs.nextTemplateID = initialNFTID
568 }
569
570 }
571
572 pub fun getAnalogsTemplateByID(templateID: UInt64): Analogs.AnalogsTemplate {
573 return Analogs.analogsTemplates[templateID]!
574 }
575
576 pub fun getAnalogsTemplates(): {UInt64: Analogs.AnalogsTemplate} {
577 return Analogs.analogsTemplates
578 }
579
580 pub fun getAvailableTemplateIDsInSet(setID: UInt64): [UInt64] {
581 pre {
582 Analogs.sets[setID] != nil:
583 "Cannot borrow Set: The Set doesn't exist"
584 }
585 let set = (&Analogs.sets[setID] as &Set?)!
586 return set.getAvailableTemplateIDs()
587 }
588
589 init() {
590 self.CollectionStoragePath = /storage/AnalogsCollection
591 self.CollectionPublicPath = /public/AnalogsCollection
592 self.AdminStoragePath = /storage/AnalogsAdmin
593
594 self.totalSupply = 0
595 self.nextSetID = 1
596 self.initialNFTID = 0
597 self.nextNFTID = 0
598 self.nextTemplateID = 0
599 self.sets <- {}
600
601 self.analogsTemplates = {}
602
603 let admin <- create Admin()
604 self.account.save(<-admin, to: self.AdminStoragePath)
605
606 emit ContractInitialized()
607 }
608}