Smart Contract
DoodlePackTypes
A.e81193c424cfd3fb.DoodlePackTypes
1import FungibleToken from 0xf233dcee88fe0abe
2import MetadataViews from 0x1d7e57aa55817448
3import DapperUtilityCoin from 0xead892083b3e2c6c
4import FlowUtilityToken from 0xead892083b3e2c6c
5
6access(all) contract DoodlePackTypes {
7 access(all) event ContractInitialized()
8 access(all) event PackTypeRegistered(id: UInt64)
9
10 access(all) var totalSupply: UInt64
11
12 access(contract) let packTypes: {UInt64: PackType}
13 // Number of times a pack type has been minted
14 access(all) var packTypesMintedCount: {UInt64: UInt64} // packTypeId => mintedCount
15 // Number of times a template distribution has been minted
16 access(all) var templateDistributionsMintedCount: {UInt64: {UInt64: UInt64}} // packTypeId => templateDistributionId => mintedCount
17
18 access(self) let extra: {String: AnyStruct}
19
20 access(all) enum PaymentToken: UInt8 {
21 access(all) case DUC
22 access(all) case FUT
23 }
24
25 access(all) enum Collection: UInt8 {
26 access(all) case Wearables
27 access(all) case Redeemables
28 }
29
30 access(all) struct PackType {
31 access(all) let id: UInt64
32
33 // Number of tokens that should be minted per pack
34 access(all) let amountOfTokens: UInt8
35
36 access(all) var name: String
37 access(all) var description: String
38 access(all) var thumbnail: MetadataViews.Media
39 access(all) var image: MetadataViews.Media
40
41 access(all) var templateDistributions: [TemplateDistribution]
42
43 // access(all) var saleInfo: SaleInfo
44
45 // Max amount of packs that can be minted
46 access(all) var maxSupply: UInt64?
47
48 access(self) let extra: {String: AnyStruct}
49
50 init(
51 id: UInt64,
52 name: String,
53 description: String,
54 thumbnail: MetadataViews.Media,
55 image: MetadataViews.Media,
56 amountOfTokens: UInt8,
57 templateDistributions: [TemplateDistribution],
58 maxSupply: UInt64?
59 ) {
60 self.id = id
61 self.name = name
62 self.description = description
63 self.thumbnail = thumbnail
64 self.image = image
65 self.amountOfTokens = amountOfTokens
66 self.templateDistributions = templateDistributions
67 // self.saleInfo = saleInfo
68 self.maxSupply = maxSupply
69 self.extra = {}
70
71 DoodlePackTypes.totalSupply = DoodlePackTypes.totalSupply + 1
72
73 // If there is a limited amount of packs to be minted,
74 // validate that each distribution has a max mint amount of templates
75 // and that the sum of all max mint amounts is equal to the total packs max supply,
76 // multiplied by the amount of tokens in each pack.
77 if maxSupply != nil {
78 var totalMaxMint: UInt64 = 0
79 for templateDistribution in templateDistributions {
80 if templateDistribution.maxMint == nil {
81 panic("Can't have unlimited mints in a distribution if the pack type has a limited supply")
82 }
83 totalMaxMint = totalMaxMint + templateDistribution.maxMint!
84 }
85 if totalMaxMint != maxSupply! * UInt64(amountOfTokens) {
86 panic("Total max mint must be equal to max supply multiplied by amount of tokens")
87 }
88 }
89 }
90
91 // access(contract) fun updateSaleInfo(saleInfo: SaleInfo) {
92 // self.saleInfo = saleInfo
93 // }
94 }
95
96 access(contract) fun validateTemplateDistributions(amountOfTokens: UInt8, templateDistributions: [TemplateDistribution]) {
97 pre {
98 amountOfTokens > 0: "Amount of tokens must be greater than 0"
99 templateDistributions.length > 0: "Template distributions must be greater than 0"
100 }
101
102 var minAmount: UInt8 = 0
103 var maxAmount: UInt8 = 0
104 for templateDistribution in templateDistributions {
105 minAmount = minAmount + templateDistribution.minAmount
106 maxAmount = maxAmount + templateDistribution.maxAmount
107 }
108 if amountOfTokens < minAmount {
109 panic("Amount of tokens must be greater than or equal to total min amount")
110 }
111 if amountOfTokens > maxAmount {
112 panic("Amount of tokens must be less than or equal to total max amount")
113 }
114
115 var totalProbability: UFix64 = 0.0
116 for templateDistribution in templateDistributions {
117 for templateProbability in templateDistribution.templateProbabilities {
118 totalProbability = totalProbability + templateProbability.probability
119 }
120 }
121 if totalProbability != 1.0 {
122 panic("Total probability must be 1.0 but is ".concat(totalProbability.toString()))
123 }
124 }
125
126 access(all) struct TemplateDistribution {
127 access(all) let id: UInt64
128 access(all) var templateProbabilities: [TemplateProbability]
129 // Min amount of tokens in this distribution that must be minted in a single pack
130 access(all) var minAmount: UInt8
131 // Max amount of tokens in this distribution that can be minted in a single pack
132 access(all) var maxAmount: UInt8
133 // Max amount of tokens in this distribution that can be minted across all packs
134 access(all) var maxMint: UInt64?
135 access(self) let extra: {String: AnyStruct}
136
137 init(id: UInt64, templateProbabilities: [TemplateProbability], minAmount: UInt8, maxAmount: UInt8, maxMint: UInt64?) {
138 pre {
139 templateProbabilities.length > 0: "Template probabilities must be greater than 0"
140 maxAmount > 0: "Max amount must be greater than 0"
141 maxAmount >= minAmount: "Max amount must be greater than or equal to min amount"
142 maxMint == nil || maxMint! > 0: "Max mint must be greater than 0"
143 }
144 self.id = id
145 self.templateProbabilities = templateProbabilities
146 self.minAmount = minAmount
147 self.maxAmount = maxAmount
148 self.maxMint = maxMint
149 self.extra = {}
150 }
151 }
152
153 access(all) struct TemplateProbability {
154 access(all) let collection: Collection
155 access(all) let templateId: UInt64
156 access(all) let probability: UFix64
157 access(self) let extra: {String: AnyStruct}
158
159 init(collection: Collection, templateId: UInt64, probability: UFix64) {
160 self.collection = collection
161 self.templateId = templateId
162 self.probability = probability
163 self.extra = {}
164 }
165 }
166
167 access(all) struct SaleInfo {
168 access(all) var price: UFix64
169 access(all) var paymentToken: PaymentToken
170 access(all) var paymentType: Type
171 access(all) var secondaryMarketCut: UFix64
172 access(all) var saleStart: UFix64?
173 access(all) var saleEnd: UFix64?
174 access(all) var saleActive: Bool
175 access(self) let extra: {String: AnyStruct}
176
177 init(
178 price: UFix64,
179 paymentToken: PaymentToken,
180 secondaryMarketCut: UFix64,
181 saleStart: UFix64?,
182 saleEnd: UFix64?,
183 saleActive: Bool
184 ) {
185 pre {
186 secondaryMarketCut <= 1.0: "Secondary market cut must be less than or equal to 1"
187 }
188 self.price = price
189 self.paymentToken = paymentToken
190 self.secondaryMarketCut = secondaryMarketCut
191 self.saleStart = saleStart
192 self.saleEnd = saleEnd
193 self.saleActive = saleActive
194 self.extra = {}
195
196 self.paymentType = DoodlePackTypes.mapPaymentType(paymentToken: paymentToken)
197 ?? panic("Unsupported payment type")
198 }
199
200 access(contract) fun setPaymentType(paymentToken: PaymentToken) {
201 self.paymentToken = paymentToken
202 self.paymentType = DoodlePackTypes.mapPaymentType(paymentToken: paymentToken)
203 ?? panic("Unsupported payment type")
204 }
205
206 access(contract) fun update(
207 price: UFix64?,
208 paymentToken: PaymentToken?,
209 secondaryMarketCut: UFix64?,
210 saleStart: UFix64?,
211 saleEnd: UFix64?,
212 saleActive: Bool?
213 ) {
214 if price != nil {
215 self.price = price!
216 }
217 if paymentToken != nil {
218 self.paymentToken = paymentToken!
219 self.paymentType = DoodlePackTypes.mapPaymentType(paymentToken: paymentToken!)
220 ?? panic("Unsupported payment type")
221 }
222 if secondaryMarketCut != nil {
223 self.secondaryMarketCut = secondaryMarketCut!
224 }
225 if saleStart != nil {
226 self.saleStart = saleStart!
227 }
228 if saleEnd != nil {
229 self.saleEnd = saleEnd!
230 }
231 if saleActive != nil {
232 self.saleActive = saleActive!
233 }
234 }
235 }
236
237 access(all) fun mapPaymentType(paymentToken: PaymentToken): Type? {
238 if paymentToken == PaymentToken.DUC {
239 return Type<@DapperUtilityCoin.Vault>()
240 }
241 if paymentToken == PaymentToken.FUT {
242 return Type<@FlowUtilityToken.Vault>()
243 }
244 return nil
245 }
246
247 access(all) fun mapPaymentPublicPath(paymentToken: PaymentToken): PublicPath? {
248 if paymentToken == PaymentToken.DUC {
249 return /public/dapperUtilityCoinReceiver
250 }
251 if paymentToken == PaymentToken.FUT {
252 return /public/flowUtilityTokenReceiver
253 }
254 return nil
255 }
256
257 access(all) fun getPackType(id: UInt64): DoodlePackTypes.PackType? {
258 return self.packTypes[id]
259 }
260
261 access(all) fun getPackTypes(): [DoodlePackTypes.PackType] {
262 return self.packTypes.values
263 }
264
265 // access(all) fun getSaleInfo(id: UInt64): DoodlePackTypes.SaleInfo? {
266 // let packType: DoodlePackTypes.PackType? = self.getPackType(id: id)
267 // if packType == nil {
268 // return nil
269 // }
270 // return packType!.saleInfo
271 // }
272
273 // access(all) fun getPaymentReceiver(paymentToken: PaymentToken): Capability<&{FungibleToken.Receiver}>? {
274 // // return self.paymentReceivers[paymentToken]
275 // }
276
277 // access(all) fun canPurchasePack(typeId: UInt64): Bool {
278 // let packType: DoodlePackTypes.PackType = self.packTypes[typeId] ?? panic("No pack type found")
279 // let saleInfo: DoodlePackTypes.SaleInfo = packType.saleInfo
280
281 // if !saleInfo.saleActive {
282 // return false
283 // }
284 // if packType.maxSupply != nil && packType.mintedCount >= packType.maxSupply! {
285 // return false
286 // }
287 // if saleInfo.saleStart != nil && saleInfo.saleStart! > getCurrentBlock().timestamp {
288 // return false
289 // }
290 // if saleInfo.saleEnd != nil && saleInfo.saleEnd! < getCurrentBlock().timestamp {
291 // return false
292 // }
293
294 // return true
295 // }
296
297 access(account) fun addPackType(
298 id: UInt64,
299 name: String,
300 description: String,
301 thumbnail: MetadataViews.Media,
302 image: MetadataViews.Media,
303 amountOfTokens: UInt8,
304 templateDistributions: [TemplateDistribution],
305 maxSupply: UInt64?
306 ): DoodlePackTypes.PackType {
307 pre {
308 DoodlePackTypes.packTypes[id] == nil: "Pack type already exists"
309 name.length > 0: "Name must be greater than 0"
310 description.length > 0: "Description must be greater than 0"
311 amountOfTokens > 0: "Amount of tokens must be greater than 0"
312 templateDistributions.length > 0: "Template distributions must be greater than 0"
313 }
314
315 // if (saleInfo.saleStart == nil || saleInfo.saleStart! > getCurrentBlock().timestamp) {
316 // panic("Sale start must be in the past")
317 // }
318
319 // if (saleInfo.saleEnd != nil && saleInfo.saleEnd! < getCurrentBlock().timestamp) {
320 // panic("Sale end must be in the future")
321 // }
322
323 DoodlePackTypes.validateTemplateDistributions(amountOfTokens: amountOfTokens, templateDistributions: templateDistributions)
324
325 let packType = PackType(
326 id: id,
327 name: name,
328 description: description,
329 thumbnail: thumbnail,
330 image: image,
331 amountOfTokens: amountOfTokens,
332 templateDistributions: templateDistributions,
333 // saleInfo: saleInfo,
334 maxSupply: maxSupply
335 )
336
337 emit PackTypeRegistered(id: id)
338
339 DoodlePackTypes.packTypes[id] = packType
340
341 return packType
342 }
343
344 // access(account) fun updateSaleInfo(
345 // id: UInt64,
346 // price: UFix64?,
347 // paymentToken: PaymentToken?,
348 // secondaryMarketCut: UFix64?,
349 // saleStart: UFix64?,
350 // saleEnd: UFix64?,
351 // saleActive: Bool?
352 // ): DoodlePackTypes.PackType {
353 // let packType: DoodlePackTypes.PackType = DoodlePackTypes.packTypes[id] ?? panic("No pack type found")
354 // let saleInfo: DoodlePackTypes.SaleInfo = packType.saleInfo
355
356 // saleInfo.update(
357 // price: price,
358 // paymentToken: paymentToken,
359 // secondaryMarketCut: secondaryMarketCut,
360 // saleStart: saleStart,
361 // saleEnd: saleEnd,
362 // saleActive: saleActive
363 // )
364
365 // packType.updateSaleInfo(saleInfo: saleInfo)
366
367 // DoodlePackTypes.packTypes[id] = packType
368
369 // emit SaleInfoUpdated(id: id)
370
371 // return packType
372 // }
373
374 // access(account) fun setPaymentReceiver(
375 // paymentToken: PaymentToken,
376 // receiver: Capability<&{FungibleToken.Receiver}>
377 // ) {
378 // pre {
379 // receiver.check(): "Invalid payment receiver"
380 // }
381 // DoodlePackTypes.paymentReceivers[paymentToken] = receiver
382 // emit PaymentReceiverSet(paymentToken: paymentToken.rawValue, receiverAddress: receiver.address)
383 // }
384
385 access(account) fun addMintedCountToPackType(typeId: UInt64, amount: UInt64) {
386 if (DoodlePackTypes.packTypes[typeId] == nil) {
387 panic("No pack type found")
388 }
389 DoodlePackTypes.packTypesMintedCount[typeId] = DoodlePackTypes.getPackTypesMintedCount(typeId: typeId) + 1
390 }
391
392 access(account) fun addMintedCountToTemplateDistribution(typeId: UInt64, templateDistributionId: UInt64, amount: UInt64) {
393 let packType: DoodlePackTypes.PackType = DoodlePackTypes.packTypes[typeId] ?? panic("No pack type found")
394 if (packType.templateDistributions[templateDistributionId] == nil) {
395 panic("No template distribution found")
396 }
397
398 let templateDistributionsMintedCount = DoodlePackTypes.templateDistributionsMintedCount[typeId] ?? {}
399 templateDistributionsMintedCount[templateDistributionId] = (templateDistributionsMintedCount[templateDistributionId] ?? 0) + 1
400
401 DoodlePackTypes.templateDistributionsMintedCount[typeId] = templateDistributionsMintedCount
402 }
403
404 access(all) fun getPackTypesMintedCount(typeId: UInt64): UInt64 {
405 return DoodlePackTypes.packTypesMintedCount[typeId] ?? 0
406 }
407
408 access(all) fun getTemplateDistributionMintedCount(typeId: UInt64, templateDistributionId: UInt64): UInt64 {
409 let templateDistributionsMintedCount = DoodlePackTypes.templateDistributionsMintedCount[typeId] ?? {}
410 return templateDistributionsMintedCount[templateDistributionId] ?? 0
411 }
412
413 init() {
414 self.totalSupply = 0
415 self.packTypes = {}
416 self.packTypesMintedCount = {}
417 self.templateDistributionsMintedCount = {}
418 // self.paymentReceivers = {}
419
420 self.extra = {}
421
422 emit ContractInitialized()
423 }
424}
425
426
427