Smart Contract
HWGaragePMV2
A.d0bcefdf1e67ea85.HWGaragePMV2
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