Smart Contract

DoodlePackTypes

A.e81193c424cfd3fb.DoodlePackTypes

Deployed

2d ago
Feb 26, 2026, 01:49:14 PM UTC

Dependents

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