Smart Contract

TheFabricantAccessPass

A.7752ea736384322f.TheFabricantAccessPass

Deployed

3h ago
Feb 28, 2026, 08:14:46 PM UTC

Dependents

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