Smart Contract

HWGaragePMV2

A.d0bcefdf1e67ea85.HWGaragePMV2

Deployed

3h ago
Feb 26, 2026, 09:43:55 PM UTC

Dependents

0 imports
1/* 
2*   A contract that manages the creation and sale of packs, cards and tokens
3*
4*/
5
6import NonFungibleToken from 0x1d7e57aa55817448
7import FungibleToken from 0xf233dcee88fe0abe
8import FlowToken from 0x1654653399040a61
9import HWGarageTokenV2 from 0xd0bcefdf1e67ea85
10import HWGarageCardV2 from 0xd0bcefdf1e67ea85
11import HWGaragePackV2 from 0xd0bcefdf1e67ea85
12
13access(all) contract HWGaragePMV2 {
14    /* 
15    *   Events
16    *
17    *   emitted when the contract is deployed
18    */
19    access(all) event ContractInitialized()
20
21    /*
22     * HWGarageCardV2 Airdrop
23     */
24    // emitted when a new pack series is added
25    access(all) event AdminAddNewTokenSeries(
26        tokenSeriesID: UInt64
27        )
28
29access(all) event AdminMintToken(
30        uuid: UInt64
31        , id: UInt64
32        , metadata: {String: String}
33        )
34    // emitted when an admin airdrops a redeemable card to an address
35    access(all) event AirdropRedeemable(
36        WalletAddress: Address
37        , TokenID:UInt64
38        , TokenMintID: String
39        , OriginalCardSerial: String
40        , TokenSerial: String
41        , SeriesName: String
42        , Name: String
43        , TokenImageHash: String
44        , TokenReleaseDate: String
45        , TokenExpireDate: String
46        , CardID: String
47        , TemplateID: String
48        )
49    
50    // emitted when a user initiates a burn on a redeemable airdrop
51    access(all) event AirdropBurn(
52        WalletAddress: Address
53        , TokenSerial: String
54        , AirdropEditionId: UInt64
55        )
56
57    /* 
58    *   HWGarageCardV2
59    *
60    *   emmited when an admin has initiated a mint of a HWGarageCardV2
61    */
62    access(all) event AdminMintCard(
63        uuid: UInt64
64        , id: UInt64
65        , metadata: {String: String}
66        , packHash: String
67        , address: Address
68        )
69
70    // emitted when a new pack series is added
71    access(all) event AdminAddNewCardSeries(
72        cardSeriesID: UInt64
73        )
74
75    // emmited when the metadata for an HWGarageCardV2 collection is updated
76    access(all) event UpdateCardCollectionMetadata()
77
78
79    /* 
80    *   HWGaragePackV2
81    *
82    */
83
84    // emitted when a new pack series is added
85    access(all) event AdminAddNewPackSeries(
86        packSeriesID: UInt64
87        )
88
89
90    // emitted when an admin has initiated a mint of a HWGarageCardV2
91    access(all) event AdminMintPack(
92        uuid: UInt64
93        , packHash: String
94        , packSeriesID: UInt64
95        , packID: UInt64 // aka packEditionID
96        , metadata: {String: String}
97        )
98
99
100    // emitted when someone redeems a HWGarageCardV2 for Tokens
101    access(all) event RedeemPack(
102        id: UInt64
103        , packID: UInt64 // aka packEditionID
104        , packSeriesID: UInt64
105        , address: Address
106        , packHash: String
107        )
108
109
110    // emitted when a user submits a packHash to claim a pack
111    access(all) event PackClaimBegin(
112        address: Address
113        , packHash: String
114        )
115
116
117    // emitted when a user successfully claims a pack
118    access(all) event PackClaimSuccess(
119        address: Address
120        , packHash: String
121        , packID: UInt64
122        , seriesPackMintID: String
123        )
124
125    // emitted when a user begins a migration from Wax to Flow
126    access(all) event ClaimBridgeAsset(
127        waxWallet: String
128        , assetIds: [String]
129        , flowWallet: Address
130        )
131    /// End of events  
132
133    /* 
134    *   Named Paths
135    */
136    access(all) let ManagerStoragePath: StoragePath
137
138
139    /* 
140    *   HWGaragePMV2 fields
141    */
142    access(self) var HWGaragePMV2SeriesIdIsLive: {UInt64: Bool}
143
144
145    /* 
146    *   HWGarageTokenV2
147    */
148    access(self) var HWGarageTokenV2SeriesIdIsLive: {UInt64: Bool}
149
150
151    /* 
152    *   HWGarageCardV2
153    */
154    access(self) var HWGarageCardV2SeriesIdIsLive: {UInt64: Bool}
155
156
157    /* 
158    *   HWGaragePackV2
159    */
160    // We need a way to track if a series is live
161    // This dictionary will be updated before each drop to insert the series that is to be released
162    // For example, the contract launches with an empty dictionary of {}
163    // In order to prepare for a drop we need to have an admin execute admin_add_packSeries.cdc
164    // This transaction takes a UInt64 as a parameter
165    // NOTE: Once a series is added, it is live and a valid pack hash will mint that a pack to a user wallet
166    access(self) var HWGaragePackV2SeriesIdIsLive: {UInt64: Bool}
167
168
169    // Do we want to update when the pack Redeeming begins? because we can
170    access(all) var packRedeemStartTime: UFix64
171    // should we have a state variable to start/stop packClaim
172    // should we have a state variable to start/stop packRedeem
173
174
175    /* 
176    *   Manager resource for all NFTs
177    */
178    access(all) resource Manager {
179        /*
180         *  HWGaragePMV2
181         */
182
183         // One call to add a new seriesID for Packs and Card
184         access(all) fun addNewSeriesID(seriesID: UInt64){
185            HWGaragePMV2.addSeriesID(seriesID: seriesID)
186            self.addPackSeriesID(packSeriesID: seriesID)
187            self.addCardSeriesID(packSeriesID: seriesID)
188            self.addTokenSeriesID(packSeriesID: seriesID)
189         }
190
191
192        /*
193         * HWGarageToken
194         */
195         access(all) fun addTokenSeriesID(packSeriesID: UInt64) {
196            pre {
197                packSeriesID >= 1: "Requested series does not exist in this scope."
198            }
199
200            HWGaragePMV2.addTokenSeriesID(tokenSeriesID: packSeriesID)
201            
202            emit AdminAddNewTokenSeries(tokenSeriesID: packSeriesID)
203
204         }
205         /// To accomodate any further changes to the metadata we can emit the entire 
206         /// metadata payload for an airdropped token and avoid staically assigning 
207         /// payload. All fields can get sent to the traits struct
208         access(all) fun airdropRedeemable(
209            airdropSeriesID: UInt64
210            , address: Address
211            , tokenMintID: String
212            , originalCardSerial: String
213            , tokenSerial: String
214            , seriesName: String
215            , carName: String
216            , tokenImageHash: String
217            , tokenReleaseDate: String
218            , tokenExpireDate: String
219            , card_ID: String
220            , template_ID: String
221            , metadata: {String: String}
222            ): @{NonFungibleToken.NFT} {
223            
224            let HWGarageAirdrop <- HWGaragePMV2.mintSequentialAirdrop(
225                seriesIDAirdrop: airdropSeriesID
226                , metadata: metadata
227                )
228            emit AdminMintToken(
229                uuid: HWGarageAirdrop.uuid
230                , id: HWGarageAirdrop.id
231                , metadata: metadata
232            )
233            emit AirdropRedeemable(
234                WalletAddress: address
235                , TokenID:HWGarageAirdrop.id // tokenEditionID
236                , TokenMintID: tokenMintID
237                , OriginalCardSerial: originalCardSerial
238                , TokenSerial: tokenSerial
239                , SeriesName: seriesName
240                , Name: carName
241                , TokenImageHash: tokenImageHash
242                , TokenReleaseDate: tokenReleaseDate
243                , TokenExpireDate: tokenExpireDate
244                , CardID: card_ID
245                , TemplateID: template_ID
246                )
247            return <- HWGarageAirdrop 
248         }
249
250        /* 
251        *   HWGarageCardV2
252        */
253
254        // Add a packSeries to the dictionary to support a new drop
255        access(all) fun addCardSeriesID(packSeriesID: UInt64) {
256            pre {
257                packSeriesID >= 1: "Requested series does not exist in this scope."
258            }
259
260            HWGaragePMV2.addCardSeriesID(cardSeriesID: packSeriesID)
261            
262            emit AdminAddNewCardSeries(cardSeriesID: packSeriesID)
263        }
264
265
266        access(all) fun mintSequentialHWGarageCardV2(
267            address: Address
268            , packHash: String
269            , packSeriesID: UInt64
270            , packEditionID: UInt64
271            , redeemable: String
272            , metadata: {String: String}
273       
274            ): @{NonFungibleToken.NFT} {
275
276            let HWGarageCardV2 <- HWGaragePMV2.mintSequentialCard(
277                packHash: packHash
278                , packSeriesID: packSeriesID
279                , packEditionID: packEditionID
280                , redeemable: redeemable
281                , metadata: metadata
282                )
283
284            emit AdminMintCard(
285                uuid: HWGarageCardV2.uuid
286                , id: HWGarageCardV2.id
287                , metadata: metadata
288                , packHash: packHash
289                , address: address
290                )
291
292            return <- HWGarageCardV2
293        }
294
295
296        /* 
297        *   HWGaragePackV2
298        */
299
300        // Add a packSeries to the dictionary to support a new drop
301        access(all) fun addPackSeriesID(packSeriesID: UInt64) {
302            pre {
303                packSeriesID >= 1: "Requested series does not exist in this scope."
304                // HWGaragePMV2.HWGaragePackV2SeriesIdIsLive.containsKey(packSeriesID) == true: "Requested series already exists."
305            }
306
307            HWGaragePMV2.addPackSeriesID(packSeriesID: packSeriesID)
308            
309            emit AdminAddNewPackSeries(packSeriesID: packSeriesID)
310        }
311
312
313        access(all) fun mintSequentialHWGaragePackV2(
314            address: Address
315            , packHash: String
316            , packSeriesID: UInt64
317            , metadata: {String: String}
318            // , packName: String
319            // , packDescription: String
320            // , thumbnailCID: String
321            // , thumbnailPath: String
322            // , collectionName: String
323            // , collectionDescription: String
324            ): @{NonFungibleToken.NFT} {
325
326            let HWGaragePackV2 <- HWGaragePMV2.mintSequentialPack(
327                packHash: packHash
328                , packSeriesID: packSeriesID
329                , metadata: metadata
330                // , packName: packName
331                // , packDescription: packDescription
332                // , thumbnailCID: thumbnailCID
333                // , thumbnailPath: thumbnailPath
334                // , collectionName: collectionName
335                // , collectionDescription: collectionDescription
336                )
337
338            emit AdminMintPack(
339                uuid:HWGaragePackV2.uuid
340                , packHash: packHash
341                , packSeriesID: packSeriesID
342                , packID: HWGaragePackV2.id // aka packEditionID
343                , metadata: metadata
344                )
345
346            emit PackClaimSuccess(
347                address: address
348                , packHash: packHash
349                , packID: HWGaragePackV2.id
350                , seriesPackMintID: metadata["seriesPackMintID"] ?? ""
351                )
352
353            return <-HWGaragePackV2
354        }
355
356
357    } /// end admin block
358
359    access(self) fun addSeriesID(seriesID: UInt64) {
360        pre {
361            seriesID >= 1: "Requested series does not exist in this scope."
362        }
363
364        self.HWGaragePMV2SeriesIdIsLive.insert(key: seriesID, true)
365    }
366
367    /* 
368    *   HWGarageToken2
369    *
370    *   Mint a HWGarageTokenV2
371    */
372
373    // Add a packSeries to the dictionary to support a new drop
374    access(self) fun addTokenSeriesID(tokenSeriesID: UInt64) {
375        pre {
376            tokenSeriesID >= 1: "Requested series does not exist in this scope."
377        }
378
379        self.HWGarageTokenV2SeriesIdIsLive.insert(key: tokenSeriesID, true)
380        
381        HWGarageTokenV2.addNewSeries(newTokenSeriesID: tokenSeriesID)
382    }
383    /// useful fields to pass into metadata 
384    // thumbnailCID: ipfs hash for thumbnail (default: "ThumbnailCID not set")
385    // thumbnaiPath: path to ipfs thumbnail resource (default: "ThumbnailPath not set")
386    // cardName: ie: Series X Airdrop
387    // cardDescription: ie: This airdrop is redeemable for Y
388    // url: url for the single product page for this token
389    /// the remaining fields are optional
390    // collectionName: (default value) collectionName not set
391    // collectionDescription: (default value) collection description not set
392    /// NOTE:
393    //   - This is an admin function
394    //   - by default all airdropped tokens are redeemable
395    //   - require no packHash to be minted
396    access(self) fun mintSequentialAirdrop(seriesIDAirdrop: UInt64, metadata: {String: String}): @{NonFungibleToken.NFT} {
397        let currentAirdrop = HWGarageTokenV2.getTotalSupply() + 1
398        let newAirdropToken <- HWGarageTokenV2.mint(
399            nftID: currentAirdrop
400            , packSeriesID: seriesIDAirdrop
401            , metadata: metadata
402            )
403            return <- newAirdropToken
404    }    
405
406
407    /* 
408    *   HWGarageCardV2
409    *
410    *   Mint a HWGarageCardV2
411    */
412
413    // Add a packSeries to the dictionary to support a new drop
414    access(self) fun addCardSeriesID(cardSeriesID: UInt64) {
415        pre {
416            cardSeriesID >= 1: "Requested series does not exist in this scope."
417        }
418
419        self.HWGarageCardV2SeriesIdIsLive.insert(key: cardSeriesID, true)
420        
421        HWGarageCardV2.addNewSeries(newCardSeriesID: cardSeriesID)
422    }
423
424
425    // look for the next Card in the sequence, and mint there
426    access(self) fun mintSequentialCard(
427        packHash: String
428        , packSeriesID: UInt64
429        , packEditionID: UInt64
430        , redeemable: String
431        , metadata: {String: String}
432     
433        ): @{NonFungibleToken.NFT} {
434        pre {
435            self.HWGaragePMV2SeriesIdIsLive.containsKey(packSeriesID) == true: "Requested pack series is not ready at this time."
436        }
437
438        var currentEditionNumber = HWGarageCardV2.getTotalSupply() + 1
439        
440        let newCard <- HWGarageCardV2.mint(
441            nftID:currentEditionNumber
442            , packSeriesID: packSeriesID
443            , cardEditionID: currentEditionNumber
444            , packHash: packHash
445            , redeemable: redeemable
446            , metadata: metadata
447            )
448
449        return <- newCard
450    }
451
452
453    /* 
454    *   HWGaragePackV2
455    *
456    *   Mint a HWGaragePackV2
457    */
458    // Add a packSeries to the dictionary to support a new drop
459    access(self) fun addPackSeriesID(packSeriesID: UInt64) {
460        pre {
461            packSeriesID >= 1: "Requested series does not exist in this scope."
462            // self.HWGaragePackV2SeriesIdIsLive.containsKey(packSeriesID) == true: "Requested series already exists."
463        }
464
465        self.HWGaragePackV2SeriesIdIsLive.insert(key: packSeriesID, true)
466
467        HWGaragePackV2.addNewSeries(newPackSeriesID: packSeriesID)
468    }
469
470
471    // Look for the next available pack, and mint there
472    access(self) fun mintSequentialPack(
473        packHash: String
474        , packSeriesID: UInt64
475        , metadata: {String: String}
476        // , packName: String
477        // , packDescription: String
478        // , thumbnailCID: String
479        // , thumbnailPath: String
480        // , collectionName: String
481        // , collectionDescription: String
482    ): @{NonFungibleToken.NFT} {
483        pre {
484            packSeriesID >= 1: "Requested series does not exist in this scope."
485            // check to verify if a valid series has been passed in
486            self.HWGaragePMV2SeriesIdIsLive.containsKey(packSeriesID) == true: "Requested pack series is not ready at this time."
487        }
488
489        // Grab the packEditionID to mint 
490        var currentPackEditionNumber: UInt64 = HWGaragePackV2.getTotalSupply() + 1
491
492        let newPack: @{NonFungibleToken.NFT} <- HWGaragePackV2.mint(
493            nftID: currentPackEditionNumber // pack X of Y
494            , packEditionID: currentPackEditionNumber // pack X of Y
495            , packSeriesID: packSeriesID  // aka series
496            , packHash: packHash
497            , metadata: metadata
498            )
499
500        return <-newPack
501    }
502
503
504    /* 
505    *   Public Functions
506    *
507    *   HWGaragePMV2
508    */
509
510    access(all) fun getEnabledSeries(): {UInt64: Bool}{
511        return HWGaragePMV2.HWGaragePMV2SeriesIdIsLive
512    }
513
514    access(all) fun getEnabledTokenSeries(): {UInt64: Bool}{
515        return HWGaragePMV2.HWGarageTokenV2SeriesIdIsLive
516    }
517    
518    access(all) fun getEnabledCardSeries(): {UInt64: Bool}{
519        return HWGaragePMV2.HWGarageCardV2SeriesIdIsLive
520    }
521
522
523    access(all) fun getEnabledPackSeries(): {UInt64: Bool}{
524        return HWGaragePMV2.HWGaragePackV2SeriesIdIsLive
525    }
526
527
528    /*
529     *  Public Pack Functions
530     */
531
532    access(all) fun claimPack(address: Address, packHash: String) {
533        // this event is picked up by a web hook to verify packHash
534        // if packHash is valid, the backend will mint the pack and
535        // deposit to the recipient address
536        emit PackClaimBegin(address: address, packHash: packHash)
537    }
538
539
540    access(all) fun publicRedeemPack(
541        address: Address,
542        pack: @{NonFungibleToken.NFT},
543        packHash: String
544        ) {
545        pre {
546            getCurrentBlock().timestamp >= self.packRedeemStartTime: "Redemption has not yet started"
547            pack.isInstance(Type<@HWGaragePackV2.NFT>())
548        }
549
550        let packInstance <- pack as! @HWGaragePackV2.NFT
551
552        // emit event that our backend will read and mint pack contents to the associated address
553        emit RedeemPack(
554            id: packInstance.id,
555            packID: packInstance.packEditionID, // aka packEditionID
556            packSeriesID: packInstance.packSeriesID,
557            address: address,
558            packHash: packHash
559            )
560        // burn pack since it was redeemed for HWGarageCardV2(s)
561        destroy packInstance
562    }
563
564
565    access(all) fun getPackEditionIdByPackSeriesId(): {UInt64: UInt64}{
566        return *HWGaragePackV2.currentPackEditionIdByPackSeriesId
567    }
568
569
570    access(all) fun getCardEditionIdByPackSeriesId(): {UInt64: UInt64}{
571        return *HWGarageCardV2.currentCardEditionIdByPackSeriesId
572    }
573
574    /* 
575     *  Public Airdrop functions
576     */
577    
578    access(all) fun burnAirdrop(
579        walletAddress: Address
580        , tokenSerial: String
581        , airdropToken: @{NonFungibleToken.NFT}
582        
583    ) {
584        pre{
585            // check airdropIdEdition is the Type
586            airdropToken.isInstance(Type<@HWGarageTokenV2.NFT>())
587        }
588        let airdropInstance <- airdropToken as! @HWGarageTokenV2.NFT
589        // emit event signaling Airdrop is burned
590        emit AirdropBurn(
591            WalletAddress: walletAddress
592            , TokenSerial: tokenSerial
593            , AirdropEditionId: airdropInstance.id
594            )
595        destroy airdropInstance
596    }
597
598    /* 
599     *  Public Bridge functions
600     */
601
602    access(all) fun migrateAsset(
603        waxWallet: String
604        , assetIds: [String]
605        , flowWallet: Address
606    ) {
607        // emit event to start asset migration
608        emit ClaimBridgeAsset(
609            waxWallet: waxWallet
610            , assetIds: assetIds
611            , flowWallet: flowWallet
612        )
613    }
614
615
616    init(){
617        /*
618        *   State variables
619        *   HWGaragePMV2
620        */
621        // start with no existing series enabled
622        // {1: true, 2: true} when series 1 and 2 are live
623        self.HWGaragePMV2SeriesIdIsLive = {}
624
625        /*
626        *   HWGarageTokenV2
627        */
628        self.HWGarageTokenV2SeriesIdIsLive = {}
629
630
631        /*
632        *   HWGarageCardV2
633        */
634        self.HWGarageCardV2SeriesIdIsLive = {}
635
636
637        /* 
638        *   HWGaragePackV2
639        */
640        self.packRedeemStartTime = 1658361290.0
641        self.HWGaragePackV2SeriesIdIsLive = {}
642
643
644        // manager resource is only saved to the deploying account's storage
645        self.ManagerStoragePath = /storage/HWGaragePMV2
646        self.account.storage.save(<- create Manager(), to: self.ManagerStoragePath)
647
648        emit ContractInitialized()
649    }
650}
651