Smart Contract
TheFabricantAccessPass
A.7752ea736384322f.TheFabricantAccessPass
1// The TheFabricantAccessPass NFT (AP) is a resource that can be used by holders to gain access to
2// different parts of the platform, at all levels of the architecture (FE/BE/BC)
3// The main principle behind the AP is it contains AccessUnits. These can be spent
4// by the user. When spent, they produce an AccessUnitSpent event, an AccessToken
5// resource, and a dictionary in the associated Promotion is updated to indicate
6// that a unit was spent. Any of these three 'signals' can be used to confirm that
7// an access unit has been spent.
8// An access unit might be spent for any number of reasons - it might provide a
9// free mint, it might provide access to an area of the platform, or could
10// even be used to mint another access pass. The AccessUnits could also be used
11// to model open/closed (1/0), if for example the AccessPass were to represent an envelope.
12
13// Central to the contract is the PublicMinter. This is created by the Promotions (admin)
14// resource and is used to allow for user-initiated (pull) airdrops, where the user
15// mints the AccessPass into their account. This is only possible if the user meets
16// criteria defined within the PublicMinter, which are specified when the PublicMinter
17// is created.
18
19// All PublicMinters are associated with a Promotion (notice singular). A Promotion holds many
20// of the properties that the PublicMinter passes to the AccessPass when one is minted.
21
22// Metadata can be added to a promotion that can be used by AccessPass's.
23// The metadata is stored under a UInt64, so there can be multiple dictionaries
24// of metadata, each targetting a different group of APs.
25
26// Campaign names must be unique; this makes pulling out campaign specific data easier
27// You can have different minters for different variants with different extra metadata
28// This is done by setting accessPassMetadatas in the promotion.
29
30import NonFungibleToken from 0x1d7e57aa55817448
31import MetadataViews from 0x1d7e57aa55817448
32import TheFabricantMetadataViews from 0x7752ea736384322f
33
34pub contract TheFabricantAccessPass: NonFungibleToken {
35 // -----------------------------------------------------------------------
36 // Paths
37 // -----------------------------------------------------------------------
38 pub let TheFabricantAccessPassCollectionStoragePath: StoragePath
39 pub let TheFabricantAccessPassCollectionPublicPath: PublicPath
40 pub let PromotionStoragePath: StoragePath
41 pub let PromotionPublicPath: PublicPath
42 pub let PromotionsStoragePath: StoragePath
43 pub let PromotionsPublicPath: PublicPath
44 pub let PromotionsPrivatePath: PrivatePath
45
46 // -----------------------------------------------------------------------
47 // TheFabricantAccessPassNFT contract Events
48 // -----------------------------------------------------------------------
49
50 // -----------------------------------------------------------------------
51 // Contract
52
53 // Emitted when the contract is deployed
54 pub event ContractInitialized()
55
56 // -----------------------------------------------------------------------
57 // NFT/TheFabricantAccessPass
58
59 // Emitted when an NFT is minted
60 pub event TheFabricantAccessPassMinted(
61 id: UInt64,
62 season: String,
63 campaignName: String,
64 promotionName: String,
65 variant: String,
66 description: String,
67 file: String,
68 dateReceived: UFix64,
69 promotionId: UInt64,
70 promotionHost: Address,
71 metadataId: UInt64?,
72 originalRecipient: Address,
73 serial: UInt64,
74 noOfAccessUnits: UInt8,
75 extraMetadata: {String: String}?
76 )
77
78 // Emitted when an NFT is destroyed
79 pub event TheFabricantAccessPassDestroyed(
80 id: UInt64,
81 campaignName: String,
82 variant: String,
83 description: String,
84 file: String,
85 dateReceived: UFix64,
86 promotionId: UInt64,
87 promotionHost: Address,
88 metadataId: UInt64?,
89 originalRecipient: Address,
90 serial: UInt64,
91 noOfAccessUnits: UInt8,
92 extraMetadata: {String: String}?
93 )
94
95 pub event TheFabricantAccessPassClaimed(
96 id: UInt64,
97 campaignName: String,
98 variant: String,
99 description: String,
100 file: String,
101 dateReceived: UFix64,
102 promotionId: UInt64,
103 promotionHost: Address,
104 metadataId: UInt64?,
105 originalRecipient: Address,
106 serial: UInt64,
107 noOfAccessUnits: UInt8,
108 extraMetadata: {String: String}?,
109 claimNftUuid: UInt64?
110 )
111
112 // Emitted when a TheFabricantAccessPass start date is set
113 pub event TheFabricantAccessPassStartDateSet(
114 id: UInt64,
115 startDate: UFix64,
116 )
117
118 // Emitted when a TheFabricantAccessPass end date is set
119 pub event TheFabricantAccessPassEndDateSet(
120 id: UInt64,
121 endDate: UFix64,
122 )
123
124 // Emitted when the AccessList is updated.
125 pub event TheFabricantAccessPassAccessListUpdated(
126 id: UInt64,
127 addresses: [Address]
128 )
129
130 // Emitted when an Access Unit is spent
131 pub event AccessUnitSpent(
132 promotionId: UInt64,
133 accessPassId: UInt64,
134 accessTokenId: UInt64,
135 serial: UInt64,
136 accessUnitsLeft: UInt8,
137 owner: Address,
138 metadataId: UInt64,
139 )
140
141 pub event TheFabricantAccessPassTransferred(
142 season: String,
143 campaignName: String,
144 promotionName: String,
145 promotionId: UInt64,
146 metadataId: UInt64?,
147 variant: String,
148 id: UInt64,
149 serial: UInt64,
150 from: Address,
151 to: Address
152
153 )
154
155 // -----------------------------------------------------------------------
156 // NFT Collection
157
158 // Emitted when NFT is withdrawn
159 pub event TheFabricantAccessPassWithdraw(
160 id: UInt64,
161 from: Address,
162 promotionId: UInt64,
163 serial: UInt64,
164 accessUnits: UInt64,
165 metadataId: UInt64,
166 )
167
168 // Emitted when NFT is deposited
169 pub event TheFabricantAccessPassDeposit(
170 id: UInt64,
171 to: Address,
172 promotionId: UInt64,
173 serial: UInt64,
174 accessUnits: UInt8,
175 metadataId: UInt64?,
176 )
177
178 // -----------------------------------------------------------------------
179 // Promotion
180
181 pub event PromotionCreated(
182 id: UInt64,
183 season: String,
184 campaignName: String,
185 promotionName: String?,
186 host: Address,
187 promotionAccessIds: [UInt64]?,
188 typeRestrictions: [Type]?,
189 active: Bool,
190 dateCreated: UFix64,
191 description: String,
192 image: String?,
193 limited: Limited?,
194 timelock: Timelock?,
195 url: String?
196
197 )
198
199 pub event PromotionDestroyed(
200 id: UInt64,
201 host: Address,
202 campaignName: String
203 )
204
205 pub event PromotionActiveChanged(
206 promotionId: UInt64,
207 promotionHost: Address,
208 campaignName: String,
209 active: Bool
210 )
211
212 pub event PromotionIsAccessListUsedChanged(
213 promotionId: UInt64,
214 promotionHost: Address,
215 campaignName: String,
216 isAccessListUsed: Bool,
217 )
218
219 pub event PromotionIsOpenAccessChanged(
220 promotionId: UInt64,
221 promotionHost: Address,
222 campaignName: String,
223 isOpenAccess: Bool,
224 )
225
226 pub event PromotionOnlyUseAccessListChanged(
227 promotionId: UInt64,
228 promotionHost: Address,
229 campaignName: String,
230 onlyUseAccessList: Bool,
231 )
232
233 pub event PromotionMetadataAdded(
234 promotionId: UInt64,
235 promotionHost: Address,
236 campaignName: String,
237 active: Bool,
238 metadata: {UInt64: {String: String}}
239 )
240
241 pub event UpdatedAccessList(
242 promotionId: UInt64,
243 promotionHost: Address,
244 campaignName: String,
245 active: Bool,
246 newAddresses: [Address]
247 )
248
249 pub event AddressRemovedFromAccessList(
250 promotionId: UInt64,
251 promotionHost: Address,
252 campaignName: String,
253 active: Bool,
254 addressRemoved: Address
255 )
256
257 pub event AccessListEmptied(
258 promotionId: UInt64,
259 promotionHost: Address,
260 campaignName: String,
261 active: Bool,
262 )
263
264 pub event UpdatedPromotionAccessIds(
265 promotionId: UInt64,
266 promotionHost: Address,
267 campaignName: String,
268 active: Bool,
269 promotionAccessIds: [UInt64]
270 )
271
272 // -----------------------------------------------------------------------
273 // Promotions
274 pub event PublicMinterCreated(
275 id: UInt64,
276 promotionId: UInt64,
277 promotionHost: Address,
278 campaignName: String,
279 variant: String,
280 nftMetadataId: UInt64?,
281 typeRestrictions: [Type]?,
282 promotionAccessIds: [UInt64]?,
283 pathString: String
284 )
285
286 pub event PublicMinterDestroyed(
287 path: String
288 )
289
290 // -----------------------------------------------------------------------
291 // NFT Standard events throwaway
292 pub event Withdraw(id: UInt64, from: Address?)
293 pub event Deposit(id: UInt64, to: Address?)
294
295 // -----------------------------------------------------------------------
296 // State
297 // -----------------------------------------------------------------------
298
299 pub var totalSupply: UInt64
300 pub var totalPromotions: UInt64
301
302 // -----------------------------------------------------------------------
303 // Access Pass Resource
304 // -----------------------------------------------------------------------
305
306 pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver {
307 pub let id: UInt64
308
309 pub let season: String
310
311 // Name of the campaign that the TheFabricantAccessPass is for eg StephyFung, WoW
312 pub let campaignName: String
313
314 pub let promotionName: String
315
316 // The id of the NFT within the promotion
317 pub let serial: UInt64
318
319 // The variant of the TheFabricantAccessPass within the campaign eg Rat_Poster, Pink_Purse
320 pub let variant: String
321
322 pub let description: String
323
324 pub let file: String
325
326 pub let dateReceived: UFix64
327
328 // Points to a promotion
329 pub let promotionId: UInt64
330
331 pub let promotionHost: Address
332
333 // Use this to look in the associated metadata of the promotion
334 // to find out what metadata it has been assigned.
335 pub let metadataId: UInt64?
336
337 pub let originalRecipient: Address
338
339 // Used to provide access to promotions. Decremented when used.
340 pub var accessUnits: UInt8
341
342 pub let initialAccessUnits: UInt8
343
344 // Allows additional String based metadata to be added outside of that associated with the PublicMinter
345 access(self) var extraMetadata: {String: String}?
346
347 // In order for the AP NFT to be compatible with the TF MP and
348 // external MP's, we need two royalty structures.
349 // `royalties` is used by external MPs and
350 // `royaltiesTFMarketplace` is used by TF MP.
351 access(self) let royalties: [MetadataViews.Royalty]
352 access(self) let royaltiesTFMarketplace: [TheFabricantMetadataViews.Royalty]
353
354 // Used to access the details of the Promotion that this AP is assocaited with
355 pub let promotionsCap: Capability<&Promotions{PromotionsAccountAccess, PromotionsPublic, MetadataViews.ResolverCollection}>
356
357 // Returns an AccessToken
358 pub fun spendAccessUnit(): @AccessToken {
359 pre {
360 self.accessUnits > 0 : "Must have more than 0 access units to spend"
361 self.promotionsCap.check():
362 "The promotion this TheFabricantAccessPass came from has been deleted!"
363
364 }
365 post {
366 before(self.accessUnits) == self.accessUnits + 1: "Access units must be decremented"
367 }
368
369 let promotions = self.promotionsCap.borrow()!
370 let promotion = promotions.getPromotionRef(id: self.promotionId) ?? panic("No promotion with id exists")
371
372 // Some gated areas/activities may require an AccessToken for entry.
373 // If no AccessToken is needed, it should be destroyed.
374 // If an AccessToken is needed for entry, it should be stored
375 // and then for record keeping purposes or for limiting access
376 // further.
377 let accessToken <- create AccessToken(
378 spender: self.owner!.address,
379 season: self.season,
380 campaignName: self.campaignName,
381 promotionName: self.promotionName,
382 promotionId: self.promotionId,
383 accessPassId: self.id,
384 accessPassSerial: self.serial,
385 accessPassVariant: self.variant,
386 )
387
388 emit AccessUnitSpent(
389 promotionId: self.promotionId,
390 accessPassId: self.id,
391 accessTokenId: accessToken.id,
392 serial: accessToken.id,
393 accessUnitsLeft: self.accessUnits - 1,
394 owner: self.owner?.address!,
395 metadataId: self.serial,
396 )
397
398 self.accessUnits = self.accessUnits - 1
399
400 return <- accessToken
401
402 }
403
404 pub fun getExtraMetadata(): {String: String}? {
405 return self.extraMetadata
406 }
407
408 pub fun getTFRoyalties(): [TheFabricantMetadataViews.Royalty] {
409 return self.royaltiesTFMarketplace
410 }
411
412 pub fun getStandardRoyalties(): [MetadataViews.Royalty] {
413 return self.royalties
414 }
415
416 // An AccessPass might have metadata associated with it that is provided
417 // by the Promotion
418 pub fun getPromotionMetadata(): {String: String}? {
419 pre {
420 self.promotionsCap.check():
421 "promotions capability invalid"
422 }
423 if let promotions = self.promotionsCap.borrow() {
424 let promotion: &Promotion = promotions.getPromotionRef(id: self.promotionId) ?? panic("promotion with id doesn't exist")
425 if let metadataId = self.metadataId {
426 if let metadatas = promotion.accessPassMetadatas {
427 return metadatas[metadataId]
428 }
429 }
430 return nil
431 }
432 return nil
433 }
434
435 pub fun getViews(): [Type] {
436 return [
437 Type<MetadataViews.Display>(),
438 Type<TheFabricantMetadataViews.AccessPassMetadataViewV2>(),
439 Type<TheFabricantMetadataViews.IdentifierV2>()
440 ]
441 }
442
443 pub fun resolveView(_ view: Type): AnyStruct? {
444 switch view {
445 case Type<MetadataViews.Display>():
446 return MetadataViews.Display(
447 name: self.campaignName.concat("_".concat(self.variant)),
448 description: self.description,
449 thumbnail: MetadataViews.HTTPFile(
450 url: self.file
451 )
452 )
453 case Type<TheFabricantMetadataViews.AccessPassMetadataViewV2>():
454 return TheFabricantMetadataViews.AccessPassMetadataViewV2(
455 id: self.id,
456 season: self.season,
457 campaignName: self.campaignName,
458 promotionName: self.promotionName,
459 edition: self.serial,
460 variant: self.variant,
461 description: self.description,
462 file: self.file,
463 dateReceived: self.dateReceived,
464 promotionId: self.promotionId,
465 promotionHost: self.promotionHost,
466 originalRecipient: self.originalRecipient,
467 accessUnits: self.accessUnits,
468 initialAccessUnits: self.initialAccessUnits,
469 metadataId: self.metadataId,
470 metadata: self.getPromotionMetadata(),
471 extraMetadata: self.extraMetadata,
472 royalties: self.royalties,
473 royaltiesTFMarketplace: self.royaltiesTFMarketplace,
474 owner: self.owner!.address,
475 )
476 case Type<TheFabricantMetadataViews.IdentifierV2>():
477 return TheFabricantMetadataViews.IdentifierV2(
478 season: self.season,
479 campaignName: self.campaignName,
480 promotionName: self.promotionName,
481 promotionId: self.promotionId,
482 edition: self.serial,
483 variant: self.variant,
484 id: self.id,
485 address: self.owner!.address,
486 dateReceived: self.dateReceived,
487 originalRecipient: self.originalRecipient,
488 accessUnits: self.accessUnits,
489 initialAccessUnits: self.initialAccessUnits,
490 metadataId: self.metadataId
491 )
492 }
493
494 return nil
495 }
496
497 init(
498 season: String,
499 campaignName: String,
500 promotionName: String,
501 variant: String,
502 description: String,
503 file: String,
504 promotionId: UInt64,
505 promotionHost: Address,
506 metadataId: UInt64?,
507 originalRecipient: Address,
508 serial: UInt64,
509 accessUnits: UInt8,
510 extraMetadata: {String: String}?,
511 royalties: [MetadataViews.Royalty],
512 royaltiesTFMarketplace: [TheFabricantMetadataViews.Royalty]
513 ) {
514 self.season = season
515 self.campaignName = campaignName
516 self.promotionName = promotionName
517 self.variant = variant
518 self.description = description
519 self.file = file
520 self.promotionId = promotionId
521 self.promotionHost = promotionHost
522 self.metadataId = metadataId
523 self.serial = serial
524 self.accessUnits = accessUnits
525 self.initialAccessUnits = accessUnits
526 self.extraMetadata = extraMetadata
527 self.royalties = royalties
528 self.royaltiesTFMarketplace = royaltiesTFMarketplace
529
530
531 self.promotionsCap = getAccount(promotionHost)
532 .getCapability<&TheFabricantAccessPass.Promotions{TheFabricantAccessPass.PromotionsAccountAccess, TheFabricantAccessPass.PromotionsPublic, MetadataViews.ResolverCollection}>(TheFabricantAccessPass.PromotionsPublicPath)
533
534 TheFabricantAccessPass.totalSupply = TheFabricantAccessPass.totalSupply + 1
535 self.id = self.uuid
536 self.dateReceived = getCurrentBlock().timestamp
537 self.originalRecipient = originalRecipient
538
539 emit TheFabricantAccessPassMinted(
540 id: self.id,
541 season: self.season,
542 campaignName: self.campaignName,
543 promotionName: self.promotionName,
544 variant: self.variant,
545 description: self.description,
546 file: self.file,
547 dateReceived: self.dateReceived,
548 promotionId: self.promotionId,
549 promotionHost: self.promotionHost,
550 metadataId: self.metadataId,
551 originalRecipient: self.originalRecipient,
552 serial: self.serial,
553 noOfAccessUnits: self.accessUnits,
554 extraMetadata: self.extraMetadata
555 )
556
557 }
558
559 destroy() {
560 let promotions = self.promotionsCap.borrow() ?? panic("The promotion this TheFabricantAccessPass came from has been deleted!")
561 let promotion = promotions.getPromotionRef(id: self.promotionId) ?? panic("No promotion with id exists")
562 promotion.accountDeletedTheFabricantAccessPass(serial: self.serial)
563
564 emit TheFabricantAccessPassDestroyed(
565 id: self.id,
566 campaignName: self.campaignName,
567 variant: self.variant,
568 description: self.description,
569 file: self.file,
570 dateReceived: self.dateReceived,
571 promotionId: self.promotionId,
572 promotionHost: self.promotionHost,
573 metadataId: self.metadataId,
574 originalRecipient: self.originalRecipient,
575 serial: self.serial,
576 noOfAccessUnits: self.accessUnits,
577 extraMetadata: self.extraMetadata
578 )
579 }
580 }
581
582 // -----------------------------------------------------------------------
583 // AccessToken Resource
584 // -----------------------------------------------------------------------
585
586 // Resource that is returned when an AccessUnit is spent.
587 // It can be used to allow access at the contract level.
588 // The promotionId can be checked to ensure that it is being
589 // used for the correct promotion (see Verify in PublicMinter)
590 // Its id should be checked to ensure that it is not being
591 // used multiple times.
592 // Could also be stored as a sort of receipt.
593 pub resource AccessToken {
594 pub let spender: Address
595 pub let id: UInt64
596 pub let season: String
597 pub let campaignName: String
598 pub let promotionName: String
599 pub let promotionId: UInt64
600 pub let accessPassId: UInt64
601 pub let accessPassSerial: UInt64
602 pub let accessPassVariant: String?
603
604 init(
605 spender: Address,
606 season: String,
607 campaignName: String,
608 promotionName: String,
609 promotionId: UInt64,
610 accessPassId: UInt64,
611 accessPassSerial: UInt64,
612 accessPassVariant: String?
613 ) {
614 self.id = self.uuid
615 self.season = season
616 self.campaignName = campaignName
617 self.promotionName = promotionName
618 self.promotionId = promotionId
619 self.spender = spender
620 self.accessPassId = accessPassId
621 self.accessPassSerial = accessPassSerial
622 self.accessPassVariant = accessPassVariant
623 }
624 }
625
626
627 // -----------------------------------------------------------------------
628 // Collection Resource
629 // -----------------------------------------------------------------------
630
631 pub resource interface TheFabricantAccessPassCollectionPublic {
632 pub fun deposit(token: @NonFungibleToken.NFT)
633 pub fun getIDs(): [UInt64]
634 pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
635 pub fun borrowTheFabricantAccessPass(id: UInt64): &NFT? {
636 // If the result isn't nil, the id of the returned reference
637 // should be the same as the argument to the function
638 post {
639 (result == nil) || (result?.id == id):
640 "Cannot borrow Item reference: The ID of the returned reference is incorrect"
641 }
642 }
643 pub fun getTheFabricantAccessPassIdsUsingPromoId(promoId: UInt64): [UInt64]?
644 pub fun borrowViewResolver(id: UInt64): &{MetadataViews.Resolver}
645 }
646
647 pub resource Collection: TheFabricantAccessPassCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection {
648 pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
649 pub var promotionIdsToTheFabricantAccessPassIds: {UInt64: [UInt64]}
650
651 pub fun deposit(token: @NonFungibleToken.NFT) {
652 let accessPass <- token as! @NFT
653 let promotionId = accessPass.promotionId
654
655 if self.promotionIdsToTheFabricantAccessPassIds[promotionId] == nil {
656 self.promotionIdsToTheFabricantAccessPassIds[promotionId] = []
657 }
658
659 self.promotionIdsToTheFabricantAccessPassIds[promotionId]!.append(accessPass.id)
660
661 let promotions: &Promotions{PromotionsAccountAccess} = accessPass.promotionsCap.borrow() ?? panic("The Promotions Collection this TheFabricantAccessPass came from has been deleted")
662 let promotion: &Promotion = promotions.getPromotionRef(id: promotionId) ?? panic("No promotion with id")
663 promotion.transferred(
664 season: accessPass.season,
665 campaignName: accessPass.campaignName,
666 promotionName: accessPass.promotionName,
667 promotionId: promotionId,
668 variant: accessPass.variant,
669 id: accessPass.id,
670 serial: accessPass.serial,
671 to: self.owner!.address,
672 dateReceived: accessPass.dateReceived,
673 originalRecipient: accessPass.originalRecipient,
674 accessUnits: accessPass.accessUnits,
675 initialAccessUnits: accessPass.initialAccessUnits,
676 metadataId: accessPass.metadataId
677 )
678 log(self.owner!.address)
679
680 emit TheFabricantAccessPassDeposit(
681 id: accessPass.id,
682 to: self.owner!.address,
683 promotionId: accessPass.promotionId,
684 serial: accessPass.serial,
685 accessUnits: accessPass.accessUnits,
686 metadataId: accessPass.metadataId,
687 )
688 let nft <- accessPass
689 self.ownedNFTs[nft.id] <-! nft
690 }
691
692 pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
693 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
694 let accessPass <- token as! @NFT
695 let promotionId = accessPass.promotionId
696 // Unfortuntately firstIndexOf is not yet implemented so need to loop through to remove
697 // TheFabricantAccessPass id
698 // self.promotionIdsToTheFabricantAccessPassIds[promotionId]!.firstIndex(of: withdrawID)
699
700 let accessPassIds = self.promotionIdsToTheFabricantAccessPassIds[promotionId]!
701
702 var indexOfTheFabricantAccessPassId: Int? = nil
703 var i = 0
704 while i < accessPassIds.length {
705 if accessPassIds[i] == accessPass.id {
706 indexOfTheFabricantAccessPassId = i
707 break
708 }
709 }
710 self.promotionIdsToTheFabricantAccessPassIds[promotionId]!.remove(at: indexOfTheFabricantAccessPassId!)
711
712 emit Withdraw(id: accessPass.id, from: self.owner?.address)
713
714 let nft <- accessPass
715 return <-nft
716 }
717
718
719 // Only returns IDs for TheFabricantAccessPass's whose promoCap is still in tact.
720 // This ensures that you can only get the IDs of nfts from which you
721 // can get the associated promo metadata
722 pub fun getIDs(): [UInt64] {
723 let ids: [UInt64] = self.ownedNFTs.keys
724 let response: [UInt64] = []
725
726 for id in ids {
727 let tokenRef = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
728 let nftRef = tokenRef as! &NFT
729 if nftRef.promotionsCap.check() {
730 response.append(id)
731 }
732 }
733 return response
734 }
735
736 pub fun getTheFabricantAccessPassIdsUsingPromoId(promoId: UInt64): [UInt64]? {
737 return self.promotionIdsToTheFabricantAccessPassIds[promoId]
738 }
739
740 pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
741 return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
742 }
743
744 pub fun borrowTheFabricantAccessPass(id: UInt64): &NFT? {
745 if self.ownedNFTs[id] != nil {
746 let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
747 return ref as! &NFT
748 } else {
749 return nil
750 }
751 }
752
753 pub fun borrowViewResolver(id: UInt64): &{MetadataViews.Resolver} {
754 let tokenRef = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
755 let nftRef = tokenRef as! &NFT
756 return nftRef as &{MetadataViews.Resolver}
757 }
758
759 // Since TheFabricantAccessPass's can't be withdrawn, they can be deleted
760 pub fun destroyTheFabricantAccessPass(id: UInt64) {
761 let token <- self.ownedNFTs.remove(key: id) ?? panic("You do not own this TheFabricantAccessPass")
762 let nft <- token as! @NFT
763 destroy nft
764
765 }
766
767 destroy() {
768 destroy self.ownedNFTs
769 }
770
771 init() {
772 self.ownedNFTs <- {}
773 self.promotionIdsToTheFabricantAccessPassIds = {}
774 }
775
776 }
777
778 // -----------------------------------------------------------------------
779 // Promotion Resource
780 // -----------------------------------------------------------------------
781 // This resource is created by the Promotions (admin) resource. It defines
782 // many of the traits that are passed from the PublicMinter to the
783 // AccessPass's
784
785 pub resource interface PromotionPublic {
786 pub fun getViews(): [Type]
787 pub fun resolveView(_ view: Type): AnyStruct?
788
789 // Added because of bug in BC that prevents PromotionMetadataView from being populated in scripts
790 pub fun getNftsUsedForClaim(): {UInt64: TheFabricantMetadataViews.Identifier}
791
792 }
793
794 // AccessList is separate to PromotionPublic interface as it may be so large
795 // that it can use the max gas limit for scripts - thus it should be fetched
796 // separately
797 pub resource interface PromotionPublicAccessList {
798 pub fun getAccessList(): [Address]
799 pub fun accessListContains(address: Address): Bool?
800 }
801
802 pub resource interface PromotionAdminAccess {
803 pub fun isOpen(): Bool
804 pub fun changeIsAccessListUsed(useAccessList: Bool)
805 pub fun changeOnlyUseAccessList(onlyUseAccessList: Bool)
806 pub fun changeIsOpenAccess(isOpenAccess: Bool)
807 pub fun toggleActive(): Bool
808 pub fun addMetadataForTheFabricantAccessPasses(metadatas: [{String: String}])
809 pub fun addToAccessList(addresses: [Address])
810 pub fun removeFromAccessList(address: Address)
811 pub fun accessListContains(address: Address): Bool?
812 pub fun emptyAccessList()
813 pub fun addPromotionAccessIds(promotionIds: [UInt64])
814 pub fun getSpentAccessUnits(): {UInt64: [TheFabricantMetadataViews.SpentAccessUnitView]}
815 }
816
817
818
819 pub resource Promotion: PromotionAdminAccess, PromotionPublic, PromotionPublicAccessList, MetadataViews.Resolver {
820 // Toggle to turn the promo on or off manually (master switch)
821 pub var active: Bool
822
823
824 // This is a list of addresses that are allowed to access this promotion
825 access(self) var accessList: [Address]
826
827 // Used to determing if the AL should be used or not
828 pub var isAccessListUsed: Bool
829
830 // Used to restrict minting to the AL only. Useful for live tests
831 pub var onlyUseAccessList: Bool
832
833 // Anyone can mint if this is true
834 pub var isOpenAccess: Bool
835
836 // This is an array of resource types (eg NFTs) that the user must possess
837 // in order to access this promotion. This allows minting to be restricted
838 // to users that own particular NFTs or other resources.
839 access(self) var typeRestrictions: [Type]?
840
841 // A promotion can be restricted by an array of promotionIds.
842 // This allows it to be accessed via an AccessToken.
843 // If the accessToken is from a promotion that has a matching
844 // promotionId, then access will be granted to minting.
845 // For example, Admin creates Promotion A. Users mint APs
846 // for Promotion A. Admin then creates a second Promotion,
847 // Promotion B. Admin passes in the promotionId of Promotion A
848 // to the promotionAccessIds property of Promotion B. Now holders
849 // of AccessPass's from Promotion A can spend an AccessUnit to
850 // mint AccessPass's from Promotion B.
851 access(self) var promotionAccessIds: [UInt64]?
852
853 // Maps the uuid of the nft used for claiming to identifiers of the AP
854 /*
855 {uuid of nft used for claim: {
856 id: uuid of AP
857 serial: id of AP in promo
858 address: original recipient of AP
859 ...
860 }
861 }
862 */
863 access(self) var nftsUsedForClaim: {UInt64: TheFabricantMetadataViews.Identifier}
864
865 // Maps the clamiant address to identifiers of the AP
866 /*
867 {Address: [{
868 id: uuid of AP
869 serial: id of AP in promo
870 address: original recipient of AP
871 ...
872 }]
873 }
874 */
875 access(self) var addressesClaimed: {Address: [TheFabricantMetadataViews.Identifier]}
876
877 // Maps the serial number to the identifiers of the AP
878 /*
879 {serial: [{
880 id: uuid of AP
881 serial: id of AP in promo
882 address: current holder of AP
883 ...
884 }]
885 }
886 */
887 access(self) var currentHolders: {UInt64: TheFabricantMetadataViews.Identifier}
888
889 pub let id: UInt64
890 pub let season: String // Season is a string and not an int to give more flexibility JIC...
891 pub let campaignName: String
892 pub let promotionName: String // A promotion within a campaign eg Red Envelope in Stephy Fung
893 pub let dateCreated: UFix64
894 pub let maxMintsPerAddress: Int?
895 pub let description: String
896 pub let host: Address
897 pub let image: String?
898 pub var totalSupply: UInt64
899 pub let url: String?
900
901 // A record of the AccessUnits that have been spent within promotion.
902 // Everytime a user spends an AccessUnit of an AccessPass that was minted
903 // from this Promotion, this dictionary updates.
904 access(self) let spentAccessUnits: {UInt64: [TheFabricantMetadataViews.SpentAccessUnitView]}
905
906 // Two types of royalties are needed - those for in the TF MP, and those
907 // in external MPs. All APs inherit their royalties from the Promotion
908 // on initialisation. They cannot be modified once the AP is created.
909 access(self) let royalties: [MetadataViews.Royalty]
910 access(self) let royaltiesTFMarketplace: [TheFabricantMetadataViews.Royalty]
911
912 // A single promotion can encapsulate multiple AccessPass variants.
913 // For example, you might have a variant called "Rat". Assuming
914 // that the metadata for Rat is the first one added to the
915 // accessPassMetadatas property, then metadataId = 0 should be used
916 // to assign this metadata to the TheFabricantAccessPass.
917 access(self) var nextMetadataId: UInt64
918 pub var accessPassMetadatas: {UInt64: {String: String}}?
919
920 // An array of the paths that the PublicMinter's for this promotion
921 // are stored at
922 access(self) var publicMinterPaths: [String]
923
924 // Options
925 pub let timelock: Timelock?
926 pub let limited: Limited?
927
928 pub fun isOpen(): Bool {
929 var open: Bool = true
930
931 if let timelock = self.timelock {
932 let currentTime = getCurrentBlock().timestamp
933 if currentTime < timelock.dateStart ||
934 currentTime > timelock.dateEnding {
935 open = false
936 }
937 }
938
939 if let limited = self.limited {
940 if self.totalSupply >= limited.capacity {
941 open = false
942 }
943 }
944
945 return self.active && open
946 }
947
948 pub fun changeIsOpenAccess(isOpenAccess: Bool) {
949 self.isOpenAccess = isOpenAccess
950
951 emit PromotionIsOpenAccessChanged(
952 promotionId: self.id,
953 promotionHost: self.host,
954 campaignName: self.campaignName,
955 isOpenAccess: self.isOpenAccess,
956 )
957 }
958
959 pub fun changeIsAccessListUsed(useAccessList: Bool) {
960 self.isAccessListUsed = useAccessList
961
962 emit PromotionIsAccessListUsedChanged(
963 promotionId: self.id,
964 promotionHost: self.host,
965 campaignName: self.campaignName,
966 isAccessListUsed: self.isAccessListUsed,
967 )
968 }
969
970 pub fun changeOnlyUseAccessList(onlyUseAccessList: Bool) {
971 self.onlyUseAccessList = onlyUseAccessList
972
973 emit PromotionOnlyUseAccessListChanged(
974 promotionId: self.id,
975 promotionHost: self.host,
976 campaignName: self.campaignName,
977 onlyUseAccessList: self.onlyUseAccessList,
978 )
979 }
980
981 // Toggle master switch
982 pub fun toggleActive(): Bool {
983 self.active = !self.active
984 emit PromotionActiveChanged(
985 promotionId: self.id,
986 promotionHost: self.host,
987 campaignName: self.campaignName,
988 active: self.active
989 )
990 return self.active
991 }
992
993 pub fun addMetadataForTheFabricantAccessPasses(metadatas: [{String: String}]) {
994 self.accessPassMetadatas = {}
995 var i = 0
996 while i < metadatas.length {
997 self.accessPassMetadatas!.insert(key: self.nextMetadataId, metadatas[i])
998 self.nextMetadataId = self.nextMetadataId + 1
999 i = i + 1
1000 emit PromotionMetadataAdded(
1001 promotionId: self.id,
1002 promotionHost: self.host,
1003 campaignName: self.campaignName,
1004 active: self.active,
1005 metadata: self.accessPassMetadatas!
1006 )
1007 }
1008 }
1009
1010 pub fun getAccessList(): [Address] {
1011 return self.accessList
1012 }
1013
1014 pub fun getTypeRestrictions(): [Type]? {
1015 return self.typeRestrictions
1016 }
1017
1018 pub fun getPromotionAccessIds(): [UInt64]? {
1019 return self.promotionAccessIds
1020 }
1021
1022 pub fun getAddressesClaimed(): {Address: [TheFabricantMetadataViews.Identifier]} {
1023 return self.addressesClaimed
1024 }
1025
1026 pub fun getNftsUsedForClaim(): {UInt64: TheFabricantMetadataViews.Identifier} {
1027 return self.nftsUsedForClaim
1028 }
1029
1030 pub fun getCurrentHolders(): {UInt64: TheFabricantMetadataViews.Identifier} {
1031 return self.currentHolders
1032 }
1033
1034 pub fun getSpentAccessUnits(): {UInt64: [TheFabricantMetadataViews.SpentAccessUnitView]} {
1035 return self.spentAccessUnits
1036 }
1037
1038 pub fun getTFRoyalties(): [TheFabricantMetadataViews.Royalty] {
1039 return self.royaltiesTFMarketplace
1040 }
1041
1042 pub fun getStandardRoyalties(): [MetadataViews.Royalty] {
1043 return self.royalties
1044 }
1045
1046 access(account) fun updateAddressesClaimed(key: Address, _ value: TheFabricantMetadataViews.Identifier) {
1047
1048 if self.addressesClaimed[key] == nil {
1049 self.addressesClaimed[key] = []
1050 }
1051 self.addressesClaimed[key]!.append(value)
1052
1053 }
1054
1055 access(account) fun addToPublicMinterPaths(path: String) {
1056 self.publicMinterPaths.append(path)
1057 }
1058
1059 pub fun addToAccessList(addresses: [Address]) {
1060 if self.accessList == nil {
1061 self.accessList = []
1062 }
1063 let addressesAdded: [Address] = []
1064 for address in addresses {
1065 if !self.accessList!.contains(address) {
1066 self.accessList!.append(address)
1067 addressesAdded.append(address)
1068 }
1069 }
1070
1071 emit UpdatedAccessList(
1072 promotionId: self.id,
1073 promotionHost: self.host,
1074 campaignName: self.campaignName,
1075 active: self.active,
1076 newAddresses: addressesAdded
1077 )
1078 }
1079
1080 pub fun removeFromAccessList(address: Address) {
1081 var count = 0;
1082 if (!self.accessList!.contains(address)) {
1083 panic("address is not in accessList")
1084 }
1085 for addr in self.accessList! {
1086 if (addr == address) {
1087 self.accessList!.remove(at: count)
1088 emit AddressRemovedFromAccessList(
1089 promotionId: self.id,
1090 promotionHost: self.host,
1091 campaignName: self.campaignName,
1092 active: self.active,
1093 addressRemoved: address
1094 )
1095 }
1096 count = count + 1
1097 }
1098 }
1099
1100 pub fun accessListContains(address: Address): Bool? {
1101 return self.accessList.contains(address)
1102 }
1103
1104 pub fun emptyAccessList() {
1105 self.accessList = []
1106
1107 emit AccessListEmptied(
1108 promotionId: self.id,
1109 promotionHost: self.host,
1110 campaignName: self.campaignName,
1111 active: self.active,
1112 )
1113 }
1114
1115 pub fun addPromotionAccessIds(promotionIds: [UInt64]) {
1116 if self.promotionAccessIds == nil {
1117 self.promotionAccessIds = []
1118 }
1119 self.promotionAccessIds!.concat(promotionIds)
1120
1121 emit UpdatedPromotionAccessIds(
1122 promotionId: self.id,
1123 promotionHost: self.host,
1124 campaignName: self.campaignName,
1125 active: self.active,
1126 promotionAccessIds: self.promotionAccessIds!
1127 )
1128 }
1129
1130
1131 access(account) fun transferred(
1132 season: String,
1133 campaignName: String,
1134 promotionName: String,
1135 promotionId: UInt64,
1136 variant: String,
1137 id: UInt64,
1138 serial: UInt64,
1139 to: Address,
1140 dateReceived: UFix64,
1141 originalRecipient: Address,
1142 accessUnits: UInt8,
1143 initialAccessUnits: UInt8,
1144 metadataId: UInt64?
1145 ) {
1146
1147 let identifier = TheFabricantMetadataViews.Identifier(
1148 season: season,
1149 campaignName: campaignName,
1150 promotionName: promotionName,
1151 promotionId: promotionId,
1152 variant: variant,
1153 id: id,
1154 serial: serial,
1155 address: to,
1156 dateReceived: dateReceived,
1157 originalRecipient: originalRecipient,
1158 accessUnits: accessUnits,
1159 initialAccessUnits: initialAccessUnits,
1160 metadataId: metadataId,
1161 )
1162 self.currentHolders[serial] = identifier;
1163
1164 emit TheFabricantAccessPassTransferred(
1165 season: season,
1166 campaignName: campaignName,
1167 promotionName: promotionName,
1168 promotionId: promotionId,
1169 metadataId: metadataId,
1170 variant: variant,
1171 id: id,
1172 serial: serial,
1173 from: self.owner!.address,
1174 to: to
1175 )
1176 }
1177
1178 access(account) fun accountDeletedTheFabricantAccessPass(serial: UInt64) {
1179 self.currentHolders.remove(key: serial)
1180 }
1181
1182 // Used when a user pays for a public mint using an AccessToken.
1183 // It doesn't save the AccessToken resource, instead it keeps a record of
1184 // it and the AT is destroyed in the mintTheFabricantAccessPass function
1185 access(account) fun acceptAccessUnit(
1186 from: Address,
1187 season: String,
1188 campaignName: String,
1189 promotionName: String,
1190 promotionId: UInt64,
1191 variant: String?,
1192 accessPassId: UInt64,
1193 accessPassSerial: UInt64,
1194 accessTokenId: UInt64,
1195 ) {
1196 let spentAccessUnit = TheFabricantMetadataViews.SpentAccessUnitView(
1197 spender: from,
1198 season: season,
1199 campaignName: campaignName,
1200 promotionName: promotionName,
1201 promotionId: promotionId,
1202 variant: variant,
1203 accessPassId: accessPassId,
1204 accessPassSerial: accessPassSerial,
1205 accessTokenId: accessTokenId,
1206 )
1207 if self.spentAccessUnits[accessPassId] == nil {
1208 self.spentAccessUnits[accessPassId] = []
1209 }
1210 self.spentAccessUnits[accessPassId]!.append(spentAccessUnit)
1211 }
1212
1213 access(account) fun mint(
1214 recipient: Address,
1215 variant: String,
1216 numberOfAccessUnits: UInt8,
1217 file: String,
1218 metadataId: UInt64?,
1219 claimNftUuid: UInt64?
1220 ): @NFT {
1221 pre {
1222 self.isOpen()
1223 : "Promotion is not open"
1224 }
1225 post{
1226 self.totalSupply == before(self.totalSupply) + 1
1227 }
1228
1229 self.totalSupply = self.totalSupply + 1
1230 let supply = self.totalSupply
1231 var metadata: {String: String}? = nil
1232 if let _metadatas = self.accessPassMetadatas {
1233 // If there is accessPassMetadata provided, then the nft must have one
1234 if let _metadataId = metadataId {
1235 metadata = _metadatas[metadataId!]
1236 }
1237 }
1238
1239 let nft <- create TheFabricantAccessPass.NFT(
1240 season: self.season,
1241 campaignName: self.campaignName,
1242 promotionName: self.promotionName,
1243 variant: variant,
1244 description: self.description,
1245 file: file,
1246 promotionId: self.id,
1247 promotionHost: self.host,
1248 metadataId: metadataId,
1249 originalRecipient: recipient,
1250 serial: supply,
1251 accessUnits: numberOfAccessUnits,
1252 extraMetadata: metadata,
1253 royalties: self.royalties,
1254 royaltiesTFMarketplace: self.royaltiesTFMarketplace
1255 )
1256
1257 let identifier = TheFabricantMetadataViews.Identifier(
1258 season: nft.season,
1259 campaignName: nft.campaignName,
1260 promotionName: nft.promotionName,
1261 promotionId: nft.promotionId,
1262 variant: nft.variant,
1263 id: nft.uuid,
1264 serial: nft.serial,
1265 address: recipient,
1266 dateReceived: nft.dateReceived,
1267 originalRecipient: nft.originalRecipient,
1268 accessUnits: nft.accessUnits,
1269 initialAccessUnits: nft.initialAccessUnits,
1270 metadataId: nft.metadataId
1271 )
1272
1273 if let _claimNftUuid = claimNftUuid {
1274 self.nftsUsedForClaim.insert(
1275 key: _claimNftUuid,
1276 identifier
1277 )
1278 }
1279
1280 return <- nft
1281 }
1282
1283 pub fun getViews(): [Type] {
1284 return [
1285 Type<TheFabricantMetadataViews.PromotionMetadataView>(),
1286 Type<TheFabricantMetadataViews.PromotionAccessPassClaims>(),
1287 Type<TheFabricantMetadataViews.PromotionAccessPassHolders>(),
1288 Type<TheFabricantMetadataViews.PromotionAccessList>(),
1289 Type<MetadataViews.Royalties>()
1290 ]
1291 }
1292 pub fun resolveView(_ view: Type): AnyStruct? {
1293 switch view {
1294 case Type<TheFabricantMetadataViews.PromotionMetadataView>():
1295 return TheFabricantMetadataViews.PromotionMetadataView(
1296 active: self.active,
1297 id: self.id,
1298 season: self.season,
1299 campaignName: self.campaignName,
1300 promotionName: self.promotionName,
1301 isAccessListUsed: self.isAccessListUsed,
1302 onlyUseAccessList: self.onlyUseAccessList,
1303 isOpenAccess: self.isOpenAccess,
1304 typeRestrictions: self.typeRestrictions,
1305 promotionAccessIds: self.promotionAccessIds,
1306 nftsUsedForClaim: self.nftsUsedForClaim,
1307 addressesClaimed: self.addressesClaimed,
1308 dateCreated: self.dateCreated,
1309 description: self.description,
1310 maxMintsPerAddress: self.maxMintsPerAddress,
1311 host: self.host,
1312 image: self.image,
1313 accessPassMetadatas: self.accessPassMetadatas,
1314 publicMinterPaths: self.publicMinterPaths,
1315 totalSupply: self.totalSupply,
1316 url: self.url,
1317 spentAccessUnits: self.spentAccessUnits,
1318 capacity: self.limited?.capacity,
1319 startTime: self.timelock?.dateStart,
1320 endTime: self.timelock?.dateEnding,
1321 isOpen: self.isOpen()
1322 )
1323 case Type<TheFabricantMetadataViews.PromotionAccessPassClaims>():
1324 return TheFabricantMetadataViews.PromotionAccessPassClaims(
1325 id: self.id,
1326 host: self.host,
1327 claimed: self.addressesClaimed
1328 )
1329 case Type<TheFabricantMetadataViews.PromotionAccessPassHolders>():
1330 return TheFabricantMetadataViews.PromotionAccessPassHolders(
1331 id: self.id,
1332 host: self.host,
1333 currentHolders: self.currentHolders,
1334 )
1335 case Type<TheFabricantMetadataViews.PromotionAccessList>():
1336 return TheFabricantMetadataViews.PromotionAccessList(
1337 id: self.id,
1338 host: self.host,
1339 accessList: self.accessList,
1340 isAccessListUsed: self.isAccessListUsed,
1341 onlyUseAccessList: self.onlyUseAccessList
1342 )
1343 case Type<MetadataViews.Royalties>():
1344 return MetadataViews.Royalties(
1345 cutInfos: self.getStandardRoyalties()
1346 )
1347 case Type<TheFabricantMetadataViews.Royalties>():
1348 return TheFabricantMetadataViews.Royalties(
1349 cutInfos: self.getTFRoyalties()
1350 )
1351 }
1352 return nil
1353 }
1354
1355 init (
1356 typeRestrictions: [Type]?,
1357 season: String,
1358 campaignName: String,
1359 promotionName: String,
1360 promotionAccessIds: [UInt64]?,
1361 description: String,
1362 maxMintsPerAddress: Int?,
1363 accessPassMetadatas: {UInt64: {String: String}}?,
1364 host: Address,
1365 image: String,
1366 limited: Limited?,
1367 timelock: Timelock?,
1368 url: String,
1369 royalties: [MetadataViews.Royalty],
1370 royaltiesTFMarketplace: [TheFabricantMetadataViews.Royalty]
1371
1372 ) {
1373 self.season = season
1374 self.campaignName = campaignName
1375 self.promotionName = promotionName
1376 self.promotionAccessIds = promotionAccessIds
1377 self.active = true
1378 self.isAccessListUsed = true
1379 self.onlyUseAccessList = false
1380 self.isOpenAccess = false
1381 self.nftsUsedForClaim = {}
1382 self.addressesClaimed = {}
1383 self.currentHolders = {}
1384 self.dateCreated = getCurrentBlock().timestamp
1385 self.description = description
1386 self.maxMintsPerAddress = maxMintsPerAddress
1387 self.host = host
1388 self.id = self.uuid
1389 self.image = image
1390 self.accessPassMetadatas = accessPassMetadatas
1391
1392 self.totalSupply = 0
1393 self.url = url
1394 self.royalties = royalties
1395 self.royaltiesTFMarketplace = royaltiesTFMarketplace
1396 self.spentAccessUnits = {}
1397
1398 self.accessList = []
1399 self.typeRestrictions = typeRestrictions
1400
1401 self.nextMetadataId = 0
1402 self.accessPassMetadatas = {}
1403
1404 self.publicMinterPaths = []
1405
1406 self.timelock = timelock
1407 self.limited = limited
1408
1409 TheFabricantAccessPass.totalPromotions = TheFabricantAccessPass.totalPromotions + 1
1410
1411 emit PromotionCreated(
1412 id: self.id,
1413 season: self.season,
1414 campaignName: self.campaignName,
1415 promotionName: self.promotionName,
1416 host: self.host,
1417 promotionAccessIds: self.promotionAccessIds,
1418 typeRestrictions: self.typeRestrictions,
1419 active: self.active,
1420 dateCreated: self.dateCreated,
1421 description: self.description,
1422 image: self.image,
1423 limited: self.limited,
1424 timelock: self.timelock,
1425 url: self.url
1426 )
1427 }
1428
1429 destroy() {
1430 pre {
1431 self.currentHolders.keys.length == 0:
1432 "You cannot delete this promotion because some TheFabricantAccessPass's still exist from this promo."
1433 }
1434 emit PromotionDestroyed(
1435 id: self.id,
1436 host: self.host,
1437 campaignName: self.campaignName
1438 )
1439 }
1440 }
1441
1442
1443 pub struct Timelock {
1444 pub let dateStart: UFix64
1445 pub let dateEnding: UFix64
1446
1447 access(account) fun verify() {
1448 assert(
1449 getCurrentBlock().timestamp >= self.dateStart,
1450 message: "Promotion hasn't started yet"
1451 )
1452 assert (
1453 getCurrentBlock().timestamp <= self.dateEnding,
1454 message: "Promotion has ended"
1455 )
1456 }
1457
1458 init(
1459 dateStart: UFix64,
1460 dateEnding: UFix64
1461 ) {
1462 self.dateStart = dateStart
1463 self.dateEnding = dateEnding
1464 }
1465 }
1466 pub struct Limited{
1467 pub var capacity: UInt64
1468
1469 access(account) fun verify(currentCapacity: UInt64) {
1470 assert(
1471 currentCapacity < self.capacity,
1472 message: "This promotion is at max capacity"
1473 )
1474 }
1475
1476 init(capacity: UInt64) {
1477 self.capacity = capacity
1478 }
1479 }
1480
1481 // -----------------------------------------------------------------------
1482 // Promotions Resource
1483 // -----------------------------------------------------------------------
1484 // This is the admin resource, and is used to create a Promotion and the
1485 // associated PublicMinter.
1486
1487 pub resource interface PromotionsPublic {
1488 pub fun getAllPromotionsByName(): {String: UInt64}
1489 pub fun getPromotionsToPublicPath(): {UInt64: [String]}
1490 pub fun getPromotionPublic(id: UInt64): &TheFabricantAccessPass.Promotion{PromotionPublic, PromotionPublicAccessList}?
1491 }
1492
1493 pub resource interface PromotionsAccountAccess {
1494 access(account) fun getPromotionRef(id: UInt64): &TheFabricantAccessPass.Promotion?
1495 }
1496
1497 pub resource Promotions: PromotionsPublic, PromotionsAccountAccess, MetadataViews.ResolverCollection {
1498 // Campaign names must be unique; this makes pulling out campaign specific data easier
1499 // There might be multiple 'parts' to a campaign. For example, in StephyFung, we have the
1500 // Red_Envelope promotion of the campaign, and then we have the Posters after this.
1501 access(account) var nameToId: {String: UInt64}
1502 access(account) var promotions: @{UInt64: Promotion}
1503 access(account) var promotionsToPublicMinterPath: {UInt64: [String]}
1504
1505 pub fun createPromotion(
1506 typeRestrictions: [Type]?,
1507 season: String,
1508 campaignName: String,
1509 promotionName: String,
1510 promotionAccessIds: [UInt64]?,
1511 description: String,
1512 maxMintsPerAddress: Int,
1513 accessPassMetadatas: {UInt64: {String: String}}?,
1514 image: String,
1515 limited: Limited?,
1516 timelock: Timelock?,
1517 url: String,
1518 royalties: [MetadataViews.Royalty],
1519 royaltiesTFMarketplace: [TheFabricantMetadataViews.Royalty]
1520 ) {
1521 pre {
1522 self.nameToId[campaignName] == nil:
1523 "A promotion with this name already exists"
1524 }
1525
1526 let promotion <- create Promotion(
1527 typeRestrictions: typeRestrictions,
1528 season: season,
1529 campaignName: campaignName,
1530 promotionName: promotionName,
1531 promotionAccessIds: promotionAccessIds,
1532 description: description,
1533 maxMintsPerAddress: maxMintsPerAddress,
1534 accessPassMetadatas: accessPassMetadatas,
1535 host: self.owner!.address,
1536 image: image,
1537 limited: limited,
1538 timelock: timelock,
1539 url: url,
1540 royalties: royalties,
1541 royaltiesTFMarketplace: royaltiesTFMarketplace
1542 )
1543 self.nameToId[promotion.campaignName] = promotion.id
1544 self.promotions[promotion.id] <-! promotion
1545 }
1546
1547 // You can only delete a promotion if 0 people are currently holding associated
1548 // TheFabricantAccessPass's.
1549 pub fun deletePromotion(id: UInt64) {
1550 let ref: &Promotion = self.getPromotionRef(id: id) ?? panic("Can't delete promotion, it doesn't exist!")
1551 let name: String = ref.campaignName
1552
1553 self.nameToId.remove(key: name)
1554 let promotion <- self.promotions.remove(key: id)
1555 destroy promotion
1556
1557 }
1558
1559 // Used to access promotions internally
1560 access(account) fun getPromotionRef(id: UInt64): &TheFabricantAccessPass.Promotion? {
1561 return (&self.promotions[id] as &TheFabricantAccessPass.Promotion?)
1562 }
1563
1564 pub fun getAllPromotionsByName(): {String: UInt64} {
1565 return self.nameToId
1566 }
1567
1568 pub fun getPromotionsToPublicPath(): {UInt64: [String]} {
1569 return self.promotionsToPublicMinterPath
1570 }
1571 pub fun getPromotionPublic(id: UInt64): &TheFabricantAccessPass.Promotion{PromotionPublic, PromotionPublicAccessList}? {
1572 return (&self.promotions[id] as &TheFabricantAccessPass.Promotion{PromotionPublic, PromotionPublicAccessList}?)
1573 }
1574
1575 // Used by admin in txs to access Promotion level functions
1576 pub fun borrowAdminPromotion(id: UInt64): &TheFabricantAccessPass.Promotion{PromotionAdminAccess}? {
1577 return (&self.promotions[id] as &TheFabricantAccessPass.Promotion{PromotionAdminAccess}?)
1578 }
1579
1580 pub fun getIDs(): [UInt64] {
1581 return self.promotions.keys
1582 }
1583
1584 pub fun borrowViewResolver(id: UInt64): &{MetadataViews.Resolver} {
1585 let promoRef = self.getPromotionRef(id: id) ?? panic("Can't borrow view resolver, no promo with that ID!")
1586 return promoRef as &{MetadataViews.Resolver}
1587 }
1588
1589 /*************************************** CLAIMING ***************************************/
1590
1591 // NOTE:
1592 // The Public Minter is linked to public path of the admin after being initialised with
1593 // access criteria, if any. It allows anyone to mint an AccessPass, so long as they have
1594 // the public path and are either in the AccessList, in possession of the required NFTs,
1595 // or can provide an AccessToken from an accepted Promotion.
1596 // The metadataId can be provided if this minter should be restricted to
1597 // only a particular variant in the promotion (eg 20% discount). By setting
1598 // the metadataId, any nfts minted using this minter will use the associated metadata.
1599 //
1600 // The minter is saved in storage and linked publicly at:
1601 // SSeason_CampaignName_PromotionName_PromotionId_MetadataId <- we must prepend 'S' as path can't start with number or special characters, and Season is likely number
1602 // S2_StephyFung_RedEnvelope_123_456
1603 // If metadataId is not provided, then it is not included.
1604 pub fun createPublicMinter(
1605 nftFile: String,
1606 nftNumberOfAccessUnits: UInt8,
1607 promotionId: UInt64,
1608 metadataId: UInt64?,
1609 variant: String
1610 ) {
1611 // This is passed into the PublicMinter
1612 let promotionsCap = getAccount(self.owner!.address)
1613 .getCapability<&Promotions{PromotionsAccountAccess, PromotionsPublic, MetadataViews.ResolverCollection}>(TheFabricantAccessPass.PromotionsPublicPath)
1614
1615 let promotion = self.getPromotionRef(id: promotionId) ?? panic("No promotion with this Id exists")
1616 let season = promotion.season
1617 let campaignName = promotion.campaignName
1618 let promotionName = promotion.promotionName
1619 let accessList = promotion.getAccessList()
1620 let typeRestrictions = promotion.getTypeRestrictions()
1621 let promotionAccessIds = promotion.getPromotionAccessIds()
1622
1623 var pathString = "S".concat(season)
1624 .concat("_")
1625 .concat(campaignName)
1626 .concat("_")
1627 .concat(promotionName)
1628 .concat("_")
1629 .concat(promotionId.toString())
1630 if let _metadataId = metadataId {
1631 pathString = pathString.concat("_")
1632 .concat(_metadataId.toString())
1633 }
1634 promotion.addToPublicMinterPaths(path: pathString)
1635 let publicMinterStoragePath = StoragePath(identifier: pathString)
1636 let publicMinterPublicPath = PublicPath(identifier: pathString)
1637
1638 let publicMinter <- create PublicMinter(
1639 nftFile: nftFile,
1640 nftNumberOfAccessUnits: nftNumberOfAccessUnits,
1641 promotionId: promotionId,
1642 variant: variant,
1643 nftMetadataId: metadataId,
1644 promoCap: promotionsCap,
1645 )
1646 log("***PathString")
1647 log(pathString)
1648 log("***Public")
1649 log(publicMinterPublicPath)
1650 log("***Storage")
1651 log(publicMinterStoragePath)
1652
1653
1654 if self.promotionsToPublicMinterPath[promotionId] == nil {
1655 self.promotionsToPublicMinterPath.insert(key: promotionId, [pathString])
1656 } else {
1657 self.promotionsToPublicMinterPath[promotionId]!.append(pathString)
1658 }
1659
1660 emit PublicMinterCreated(
1661 id: publicMinter.id,
1662 promotionId: publicMinter.promotionId,
1663 promotionHost: promotion.host,
1664 campaignName: publicMinter.campaignName,
1665 variant: publicMinter.variant,
1666 nftMetadataId: publicMinter.nftMetadataId,
1667 typeRestrictions: promotion.getTypeRestrictions(),
1668 promotionAccessIds: promotion.getPromotionAccessIds(),
1669 pathString: pathString
1670 )
1671
1672 // Link the Public Minter to a Public Path of the admin account
1673 TheFabricantAccessPass.account.save(<- publicMinter, to: publicMinterStoragePath!)
1674 TheFabricantAccessPass.account.link<&PublicMinter{TheFabricantAccessPass.IPublicMinter}>(publicMinterPublicPath!, target: publicMinterStoragePath!)
1675 }
1676
1677 pub fun destroyPublicMinter(publicMinterPath: String) {
1678 let storagePath = StoragePath(identifier: publicMinterPath)
1679 ?? panic("Couldn't construct storage path from string")
1680 let minter <- TheFabricantAccessPass.account.load<@PublicMinter>(from: storagePath)
1681 destroy minter
1682 emit PublicMinterDestroyed(
1683 path: publicMinterPath,
1684 )
1685 }
1686
1687 pub fun distributeDirectly(
1688 promotionId: UInt64,
1689 variant: String,
1690 recipient: &{TheFabricantAccessPass.TheFabricantAccessPassCollectionPublic},
1691 numberOfAccessUnits: UInt8,
1692 file: String,
1693 metadataId: UInt64?
1694 ) {
1695 let promo = self.getPromotionRef(id: promotionId) ?? panic("This promotion doesn't exist")
1696 let nft <- promo.mint(
1697 recipient: recipient.owner!.address,
1698 variant: variant,
1699 numberOfAccessUnits: numberOfAccessUnits,
1700 file: file,
1701 metadataId: metadataId,
1702 claimNftUuid: nil
1703 )
1704 let token <- nft
1705 recipient.deposit(token: <- token)
1706 }
1707
1708 /******************************************************************************/
1709
1710 init () {
1711 self.nameToId = {}
1712 self.promotionsToPublicMinterPath = {}
1713 self.promotions <- {}
1714 }
1715
1716 destroy() {
1717 destroy self.promotions
1718 }
1719 }
1720
1721 // -----------------------------------------------------------------------
1722 // PublicMinter Resource
1723 // -----------------------------------------------------------------------
1724 // PublicMinter is created by the Promotions resource. It allows users to
1725 // to mint an AccessPass so long as they have the public path for where it is stored
1726 // and meet the criteria (AccessList, resource ownership restrictions)
1727
1728 pub resource interface IPublicMinter {
1729 pub fun getMinterData(): TheFabricantMetadataViews.PublicMinterView
1730 pub fun isAccessListOnly(): Bool
1731 pub fun promotionIsOpen(): Bool
1732 pub fun getAddressesClaimed(): {Address: [TheFabricantMetadataViews.Identifier]}
1733 pub fun getnftsUsedForClaim(): {UInt64: TheFabricantMetadataViews.Identifier}
1734
1735 pub fun mintUsingAccessList(
1736 receiver: &{TheFabricantAccessPass.TheFabricantAccessPassCollectionPublic}
1737 )
1738 pub fun mintUsingNftRefs(
1739 receiver: &{TheFabricantAccessPass.TheFabricantAccessPassCollectionPublic},
1740 refs: [&AnyResource{NonFungibleToken.INFT}]?
1741 )
1742 pub fun mintUsingAccessToken(
1743 receiver: &{TheFabricantAccessPass.TheFabricantAccessPassCollectionPublic},
1744 accessToken: @AccessToken
1745 )
1746
1747 }
1748
1749 pub resource PublicMinter: IPublicMinter {
1750
1751 pub let id: UInt64
1752
1753 pub let season: String
1754
1755 pub let campaignName: String
1756
1757 pub let promotionName: String
1758
1759 pub let promotionId: UInt64
1760
1761 pub let variant: String
1762
1763 pub let nftFile: String
1764
1765 pub let nftMetadataId: UInt64?
1766
1767 // This is the number of access units that the minted NFT gets (and can therefore spend)
1768 pub let nftNumberOfAccessUnits: UInt8
1769
1770 access(self) var numberOfMints: UInt64
1771
1772 // Used to pull all the metadata down from the promotion
1773 access(self) let promotionsCap: Capability<&Promotions{PromotionsAccountAccess, PromotionsPublic, MetadataViews.ResolverCollection}>
1774
1775 pub fun getMinterData(): TheFabricantMetadataViews.PublicMinterView {
1776 return TheFabricantMetadataViews.PublicMinterView(
1777 id: self.id,
1778 season: self.season,
1779 campaignName: self.campaignName,
1780 promotionName: self.promotionName,
1781 promotionId: self.promotionId,
1782 nftFile: self.nftFile,
1783 nftMetadataId: self.nftMetadataId,
1784 nftNumberOfAccessUnits: self.nftNumberOfAccessUnits,
1785 variant: self.variant,
1786 numberOfMints: self.numberOfMints,
1787 )
1788 }
1789
1790 access(self) fun nftHasBeenUsedForClaim(uuid: UInt64):Bool {
1791 let promotions = self.promotionsCap.borrow() ?? panic("Couldn't get promotions capability to check if nft has been used for claim")
1792 let promotion = promotions.getPromotionRef(id: self.promotionId) ?? panic("Couldn't get promotionRef to check if nft has been used for claim")
1793 let nftsUsedForClaim = promotion.getNftsUsedForClaim()
1794 return nftsUsedForClaim.keys.contains(uuid)
1795 }
1796
1797 pub fun getnftsUsedForClaim(): {UInt64: TheFabricantMetadataViews.Identifier} {
1798 let promotions = self.promotionsCap.borrow() ?? panic("Couldn't get promotions capability to check if address in access list")
1799 let promotion = promotions.getPromotionRef(id: self.promotionId) ?? panic("Couldn't get promotionRef to check if address in access list")
1800 return promotion.getNftsUsedForClaim()
1801 }
1802
1803 access(self) fun nftsCanBeUsedForMint(
1804 receiver: &{TheFabricantAccessPass.TheFabricantAccessPassCollectionPublic},
1805 refs: [&AnyResource{NonFungibleToken.INFT}],
1806 promotion: &Promotion,
1807 ): Bool {
1808 let refTypes = promotion.getTypeRestrictions() ?? panic("There are no type restrictions for this promotion")
1809 assert(refTypes != nil, message: "There are no type restrictions for this promotion")
1810 var i = 0
1811 while i < refs.length {
1812 if refTypes.contains(refs[i].getType())
1813 && !self.nftHasBeenUsedForClaim(uuid: refs[i].uuid)
1814 && receiver.owner!.address == refs[i].owner!.address
1815 {
1816 self.claimingNftUuid = refs[i].uuid
1817 return true
1818 }
1819 i = i + 1
1820 }
1821 return false
1822 }
1823
1824 pub fun getAddressesClaimed(): {Address: [TheFabricantMetadataViews.Identifier]} {
1825 let promotions = self.promotionsCap.borrow() ?? panic("Couldn't get promotions capability to check if address in access list")
1826 let promotion = promotions.getPromotionRef(id: self.promotionId) ?? panic("Couldn't get promotionRef to check if address in access list")
1827 return promotion.getAddressesClaimed()
1828 }
1829
1830 access(self) fun addressHasClaimedMaxTheFabricantAccessPassLimit(address: Address): Bool {
1831 let promotions = self.promotionsCap.borrow() ?? panic("Couldn't get promotions capability to check if address has been used for claim")
1832 let promotion = promotions.getPromotionRef(id: self.promotionId) ?? panic("Couldn't get promotionRef to check if address has been used for claim")
1833 let addressesClaimed = promotion.getAddressesClaimed()
1834 log("addresses claimed")
1835 log(addressesClaimed)
1836 log("maxMints")
1837 log(promotion.maxMintsPerAddress)
1838 if addressesClaimed.keys.contains(address) {
1839 log("addressMints")
1840 log(addressesClaimed[address]!.length)
1841 if addressesClaimed[address]!.length == promotion.maxMintsPerAddress {
1842 log("User has minted max number of access passes")
1843 return true
1844 }
1845 }
1846 return false
1847 }
1848
1849 access(self) fun accessTokenIsValid(
1850 receiver: &{TheFabricantAccessPass.TheFabricantAccessPassCollectionPublic},
1851 accessTokenOwner: Address,
1852 accessTokenPromoId: UInt64,
1853 promotion: &Promotion,
1854 ): Bool {
1855
1856 if promotion.getPromotionAccessIds()!.contains(accessTokenPromoId) && receiver.owner!.address == accessTokenOwner {
1857 return true
1858 }
1859 return false
1860 }
1861
1862 pub fun promotionIsOpen(): Bool {
1863 let promotions = self.promotionsCap.borrow() ?? panic("Couldn't get promotions capability to check if address in access list")
1864 let promotion = promotions.getPromotionRef(id: self.promotionId) ?? panic("Couldn't get promotionRef to check if address in access list")
1865 return promotion.isOpen()
1866 }
1867
1868 pub fun isAccessListOnly(): Bool {
1869 let promotions = self.promotionsCap.borrow() ?? panic("Couldn't get promotions capability to check if address in access list")
1870 let promotion = promotions.getPromotionRef(id: self.promotionId) ?? panic("Couldn't get promotionRef to check if address in access list")
1871 return promotion.onlyUseAccessList
1872 }
1873
1874 // mint not:
1875 // maxMint for this address has been hit √
1876 // maxCapacity has been hit √
1877 // promotion isn't open √
1878 // timelock has expired √
1879 // mint if:
1880 // openAccess √
1881 // OR address on access list AND accessListIsUsed √
1882
1883 // If open access or access list only, use this function
1884 pub fun mintUsingAccessList(receiver: &{TheFabricantAccessPass.TheFabricantAccessPassCollectionPublic}) {
1885 let promotions = self.promotionsCap.borrow()
1886 ?? panic("Couldn't get promotions capability to check if address in access list")
1887 let promotion = promotions.getPromotionRef(id: self.promotionId)
1888 ?? panic("Couldn't get promotionRef for mintTheFabricantAccessPass")
1889
1890 self.claimingNftUuid = nil
1891 // If the promotion is not open access...
1892 if !promotion.isOpenAccess {
1893 assert(promotion.accessListContains(address: receiver.owner!.address)! && promotion.isAccessListUsed,
1894 message: "Address isn't on the access list or the access list isn't used for this promotion")
1895 }
1896
1897 self.mintAccessPass(receiver: receiver)
1898 }
1899
1900 // This variable keeps track of the nftId that was used for the claim so that we can save it
1901 // It is saved during the mint() call in Promotion
1902 access(self) var claimingNftUuid: UInt64?
1903
1904 // mint not:
1905 // accessListOnly √
1906 // maxMint for this address has been hit √
1907 // maxCapacity has been hit √
1908 // timelock has expired √
1909 // promotion isn't open √
1910 // no nft refs are provided √
1911 // mint if:
1912 // openAccess
1913 // OR nft is of correct Type AND hasn't been used for claim before √
1914 pub fun mintUsingNftRefs(
1915 receiver: &{TheFabricantAccessPass.TheFabricantAccessPassCollectionPublic},
1916 refs: [&AnyResource{NonFungibleToken.INFT}]?
1917 ) {
1918 pre {
1919 !self.isAccessListOnly()
1920 : "Only Access List can be used for this promotion"
1921 refs != nil || refs?.length != 0
1922 : "Please provide some nft references to check access"
1923 }
1924
1925 let promotions = self.promotionsCap.borrow()
1926 ?? panic("Couldn't get promotions capability to check if address in access list")
1927 let promotion = promotions.getPromotionRef(id: self.promotionId)
1928 ?? panic("Couldn't get promotionRef for mintTheFabricantAccessPass")
1929
1930 self.claimingNftUuid = nil
1931 // If the promotion is not open access...
1932 if !promotion.isOpenAccess {
1933 assert(self.nftsCanBeUsedForMint(receiver: receiver, refs: refs!, promotion: promotion), message: "nft has been used for claim or is not correct Type")
1934 }
1935
1936 self.mintAccessPass(receiver: receiver)
1937
1938 }
1939
1940 // mint not:
1941 // accessListOnly √
1942 // maxMint for this address has been hit √
1943 // maxCapacity has been hit √
1944 // timelock has expired √
1945 // promotion isn't open √
1946 // no promotionAccessIds provided √
1947 // mint if:
1948 // accessToken is valid
1949 pub fun mintUsingAccessToken(
1950 receiver: &{TheFabricantAccessPass.TheFabricantAccessPassCollectionPublic},
1951 accessToken: @AccessToken
1952 ) {
1953 pre {
1954 !self.isAccessListOnly()
1955 : "Only Access List can be used for this promotion"
1956 }
1957 let promotions = self.promotionsCap.borrow()
1958 ?? panic("Couldn't get promotions capability to check if address in access list")
1959 let promotion = promotions.getPromotionRef(id: self.promotionId)
1960 ?? panic("Couldn't get promotionRef for mintTheFabricantAccessPass")
1961
1962 assert(!promotion.isOpenAccess, message: "Promotion is open access, please use mintUsingAccessList function")
1963 assert(promotion.getPromotionAccessIds() != nil || promotion.getPromotionAccessIds()?.length != 0,
1964 message: "No promotion access ids provided for this promotion"
1965 )
1966
1967 // We can't pass the access token resource into different 'if' statements
1968 // as it is a resource, so we must extract the data here.
1969 var accessTokenOwner: Address = accessToken.spender
1970 var accessTokenPromoId: UInt64 = accessToken.promotionId
1971
1972 self.claimingNftUuid = nil
1973
1974 assert(self.accessTokenIsValid(
1975 receiver: receiver,
1976 accessTokenOwner: accessTokenOwner,
1977 accessTokenPromoId: accessTokenPromoId,
1978 promotion: promotion
1979 ), message: "Access token is not valid")
1980
1981 // If an accessToken was used to mint, save the details of the AT
1982 let accessTokenPromotion = promotions.getPromotionRef(id: accessTokenPromoId!)
1983 ?? panic("Couldn't get promotionRef for AccessToken")
1984 promotion.acceptAccessUnit(
1985 from: accessToken.spender,
1986 season: accessTokenPromotion.season,
1987 campaignName: accessTokenPromotion.campaignName,
1988 promotionName: accessTokenPromotion.promotionName,
1989 promotionId: accessTokenPromoId,
1990 variant: accessToken.accessPassVariant,
1991 accessPassId: accessToken.uuid,
1992 accessPassSerial: accessToken.accessPassSerial,
1993 accessTokenId: accessToken.id
1994 )
1995
1996 self.mintAccessPass(receiver: receiver)
1997 destroy accessToken
1998 }
1999
2000 access(self) fun mintAccessPass(receiver: &{TheFabricantAccessPass.TheFabricantAccessPassCollectionPublic}) {
2001 pre {
2002 !self.addressHasClaimedMaxTheFabricantAccessPassLimit(address: receiver.owner!.address)
2003 : "User has minted max number of access pass's"
2004 self.promotionIsOpen()
2005 : "Promotion is not open yet"
2006 }
2007 log("tests passed, minting AP")
2008 let promotions = self.promotionsCap.borrow()
2009 ?? panic("Couldn't get promotions capability to check if address in access list")
2010 let promotion = promotions.getPromotionRef(id: self.promotionId)
2011 ?? panic("Couldn't get promotionRef for mintTheFabricantAccessPass")
2012 let nft <- promotion.mint(
2013 recipient: receiver.owner!.address,
2014 variant: self.variant,
2015 numberOfAccessUnits: self.nftNumberOfAccessUnits,
2016 file: self.nftFile,
2017 metadataId: self.nftMetadataId,
2018 claimNftUuid: self.claimingNftUuid
2019 )
2020
2021 promotion.updateAddressesClaimed(key: receiver.owner!.address, (
2022 TheFabricantMetadataViews.Identifier(
2023 campaignName: nft.campaignName,
2024 season: nft.season,
2025 promotionName: nft.promotionName,
2026 promotionId: nft.promotionId,
2027 variant: nft.variant,
2028 id: nft.id,
2029 serial: nft.serial,
2030 address: receiver.owner!.address,
2031 dateReceived: nft.dateReceived,
2032 originalRecipient: nft.originalRecipient,
2033 accessUnits: nft.accessUnits,
2034 initialAccessUnits: nft.initialAccessUnits,
2035 metadataId: nft.metadataId
2036 )
2037 )
2038 )
2039
2040 emit TheFabricantAccessPassClaimed(
2041 id: nft.id,
2042 campaignName: nft.campaignName,
2043 variant: nft.variant,
2044 description: nft.description,
2045 file: nft.file,
2046 dateReceived: getCurrentBlock().timestamp,
2047 promotionId: nft.promotionId,
2048 promotionHost: nft.promotionHost,
2049 metadataId: nft.metadataId,
2050 originalRecipient: receiver.owner!.address,
2051 serial: nft.serial,
2052 noOfAccessUnits: nft.accessUnits,
2053 extraMetadata: nft.getExtraMetadata(),
2054 claimNftUuid: self.claimingNftUuid
2055 )
2056 receiver.deposit(token: <- nft)
2057 }
2058
2059 init(
2060 nftFile: String,
2061 nftNumberOfAccessUnits: UInt8,
2062 promotionId: UInt64,
2063 variant: String,
2064 nftMetadataId: UInt64?,
2065 promoCap: Capability<&Promotions{PromotionsAccountAccess, PromotionsPublic, MetadataViews.ResolverCollection}>,
2066 ) {
2067 self.id = self.uuid
2068 self.nftFile = nftFile
2069 self.nftMetadataId = nftMetadataId
2070 self.nftNumberOfAccessUnits = nftNumberOfAccessUnits
2071 self.claimingNftUuid = nil
2072 self.promotionId = promotionId
2073 self.variant = variant
2074 self.promotionsCap = promoCap
2075 self.numberOfMints = 0
2076
2077 let promotions = promoCap.borrow() ?? panic("No promo cap provided for public minter init")
2078 let promotion = promotions.getPromotionRef(id: promotionId) ?? panic("No promotion with that promotionId")
2079 self.season = promotion.season
2080 self.campaignName = promotion.campaignName
2081 self.promotionName = promotion.promotionName
2082 }
2083 }
2084
2085 pub fun createEmptyCollection(): @Collection {
2086 return <- create Collection()
2087 }
2088
2089 pub fun createEmptyPromotionsCollection(): @Promotions {
2090 return <- create Promotions()
2091 }
2092
2093 init() {
2094 self.totalSupply = 0
2095 self.totalPromotions = 0
2096
2097 emit ContractInitialized()
2098
2099 self.TheFabricantAccessPassCollectionStoragePath = /storage/TheFabricantTheFabricantAccessPassCollection001
2100 self.TheFabricantAccessPassCollectionPublicPath = /public/TheFabricantTheFabricantAccessPassCollection001
2101 self.PromotionStoragePath = /storage/TheFabricantPromotionStoragePath001
2102 self.PromotionPublicPath = /public/TheFabricantPromotionPublicPath001
2103 self.PromotionsStoragePath = /storage/TheFabricantPromotionStoragePath001
2104 self.PromotionsPublicPath = /public/TheFabricantPromotionPublicPath001
2105 self.PromotionsPrivatePath = /private/TheFabricantPromotionPublicPath001
2106
2107 // The Admin (Promotions) resource needs to be publicly linked
2108 // using {PromotionsAccountAccess, PromotionsPublic, MetadataViews.ResolverCollection}
2109 // for contract to function properly.
2110 self.account.save(<- create Promotions(), to: self.PromotionsStoragePath)
2111 self.account.link<&Promotions{PromotionsAccountAccess, PromotionsPublic, MetadataViews.ResolverCollection}>(self.PromotionsPublicPath, target: self.PromotionsStoragePath)
2112 }
2113}