Smart Contract
DoodlePacks
A.e81193c424cfd3fb.DoodlePacks
1import FungibleToken from 0xf233dcee88fe0abe
2import NonFungibleToken from 0x1d7e57aa55817448
3import MetadataViews from 0x1d7e57aa55817448
4import DoodlePackTypes from 0xe81193c424cfd3fb
5import OpenDoodlePacks from 0xe81193c424cfd3fb
6import Burner from 0xf233dcee88fe0abe
7import ViewResolver from 0x1d7e57aa55817448
8
9access(all) contract DoodlePacks: NonFungibleToken {
10 access(all) event ContractInitialized()
11 access(all) event Withdraw(id: UInt64, from: Address?)
12 access(all) event Deposit(id: UInt64, to: Address?)
13 access(all) event Minted(id: UInt64, typeId:UInt64)
14 access(all) event Opened(id: UInt64, address: Address)
15
16 access(all) let CollectionStoragePath: StoragePath
17 access(all) let CollectionPublicPath: PublicPath
18
19 access(all) var totalSupply: UInt64
20 access(all) var currentPackTypesSerialNumber: {UInt64: UInt64} // packTypeId => currentPackTypeSerialNumber
21 access(all) var packTypesCurrentSupply: {UInt64: UInt64} // packTypeId => currentSupply
22
23 access(self) let extra: {String: AnyStruct}
24
25 access(all) resource NFT: NonFungibleToken.NFT, ViewResolver.Resolver, Burner.Burnable {
26 access(all) let id: UInt64
27 access(all) let serialNumber: UInt64
28 access(all) let typeId: UInt64
29
30 init(typeId: UInt64) {
31 if (DoodlePackTypes.getPackType(id: typeId) == nil) {
32 panic("Invalid pack type")
33 }
34 DoodlePacks.totalSupply = DoodlePacks.totalSupply + 1
35 DoodlePacks.currentPackTypesSerialNumber[typeId] = (DoodlePacks.currentPackTypesSerialNumber[typeId] ?? 0) + 1
36 DoodlePacks.packTypesCurrentSupply[typeId] = (DoodlePacks.packTypesCurrentSupply[typeId] ?? 0) + 1
37
38 self.id = self.uuid
39 self.serialNumber = DoodlePacks.currentPackTypesSerialNumber[typeId]!
40 self.typeId = typeId
41 }
42
43 access(all) fun getPackType(): DoodlePackTypes.PackType {
44 return DoodlePackTypes.getPackType(id: self.typeId)!
45 }
46
47 access(contract) fun burnCallback() {
48 DoodlePacks.packTypesCurrentSupply[self.typeId] = DoodlePacks.packTypesCurrentSupply[self.typeId]! - 1
49 }
50
51 access(all) view fun getViews(): [Type] {
52 return [
53 Type<MetadataViews.Display>(),
54 Type<MetadataViews.Royalties>(),
55 Type<MetadataViews.ExternalURL>(),
56 Type<MetadataViews.NFTCollectionDisplay>(),
57 Type<MetadataViews.NFTCollectionData>(),
58 Type<MetadataViews.Traits>(),
59 Type<MetadataViews.Editions>()
60 ]
61 }
62
63 access(all) fun resolveView(_ view: Type): AnyStruct? {
64 let packType: DoodlePackTypes.PackType = DoodlePackTypes.getPackType(id: self.typeId)!
65 switch view {
66 case Type<MetadataViews.Display>():
67 return MetadataViews.Display(
68 name: packType.name,
69 description: packType.description,
70 thumbnail: packType.thumbnail.file,
71 )
72 case Type<MetadataViews.ExternalURL>():
73 return MetadataViews.ExternalURL("https://doodles.app")
74 case Type<MetadataViews.Royalties>():
75 // let cut: UFix64 = packType.saleInfo.secondaryMarketCut
76 // let dapperReceiver = DoodlePackTypes.getPaymentReceiver(paymentToken: DoodlePackTypes.PaymentToken.DUC)!
77 // let dapperReceiverAddress = dapperReceiver.address
78 let dapperReceiverAddress = 0x014e9ddc4aaaf557
79 let cut = 0.05
80
81 let description: String = "Doodle Royalty"
82
83 let doodlesMerchantAccountMainnet="0x014e9ddc4aaaf557"
84 //royalties if we sell on something else then DapperWallet cannot go to the address stored in the contract, and Dapper will not allow us to setup forwarders for Flow/USDC
85 if dapperReceiverAddress.toString() == doodlesMerchantAccountMainnet {
86 //this is an account that have setup a forwarder for DUC/FUT to the merchant account of Doodles.
87 let royaltyAccountWithDapperForwarder = getAccount(0x12be92985b852cb8)
88 let cap = royaltyAccountWithDapperForwarder.capabilities.get<&{FungibleToken.Receiver}>(/public/fungibleTokenSwitchboardPublic)!
89 return MetadataViews.Royalties([MetadataViews.Royalty(receiver:cap, cut: cut, description: description)])
90 }
91 let doodlesMerchanAccountTestnet="0xd5b1a1553d0ed52e"
92 if dapperReceiverAddress.toString() == doodlesMerchanAccountTestnet {
93 //on testnet we just send this to the main vault, it is not important
94 let cap = DoodlePacks.account.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)!
95 return MetadataViews.Royalties([MetadataViews.Royalty(receiver:cap, cut: cut, description: description)])
96 }
97 case Type<MetadataViews.Editions>():
98 return MetadataViews.Editions([
99 MetadataViews.Edition(
100 name: packType.name,
101 number: self.serialNumber,
102 max: nil
103 )
104 ])
105 case Type<MetadataViews.Traits>():
106 return MetadataViews.Traits([
107 MetadataViews.Trait(
108 name: "name",
109 value: packType.name,
110 displayType: "string",
111 rarity: nil,
112 ),
113 MetadataViews.Trait(
114 name: "pack_type_id",
115 value: packType.id.toString(),
116 displayType: "string",
117 rarity: nil,
118 )
119 ])
120 }
121 return DoodlePacks.resolveContractView(resourceType: Type<@DoodlePacks.NFT>(), viewType: view)
122 }
123
124 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
125 return <- DoodlePacks.createEmptyCollection(nftType: Type<@DoodlePacks.NFT>())
126 }
127 }
128
129 access(all) resource interface CollectionPublic {
130 access(all) fun deposit(token: @{NonFungibleToken.NFT})
131 access(all) view fun getIDs(): [UInt64]
132 access(all) fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
133 access(all) fun contains(_ id: UInt64): Bool
134 access(all) fun getPacksLeft(): Int
135 access(all) fun borrowDoodlePack(id: UInt64): &DoodlePacks.NFT?
136 }
137
138 access(all) resource Collection: CollectionPublic, NonFungibleToken.Collection{
139 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
140
141 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
142 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("Could not withdraw nft")
143
144 let nft <- token
145
146 emit Withdraw(id: nft.id, from: self.owner?.address)
147
148 return <-nft
149 }
150
151 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
152 let token <- token as! @DoodlePacks.NFT
153
154 let id: UInt64 = token.id
155
156 let oldToken <- self.ownedNFTs[id] <- token
157
158 emit Deposit(id: id, to: self.owner?.address)
159
160 destroy oldToken
161 }
162
163 access(all) view fun getIDs(): [UInt64] {
164 return self.ownedNFTs.keys
165 }
166
167 access(all) fun contains(_ id: UInt64): Bool {
168 return self.ownedNFTs.containsKey(id)
169 }
170
171 access(all) fun getPacksLeft(): Int {
172 return self.ownedNFTs.length
173 }
174
175 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
176 return &self.ownedNFTs[id]
177 }
178
179 access(all) fun borrowDoodlePack(id: UInt64): &DoodlePacks.NFT? {
180 return &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? as! &DoodlePacks.NFT?
181 }
182
183 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver} {
184 let nft = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
185 let doodlePack: &DoodlePacks.NFT = nft as! &DoodlePacks.NFT
186 return doodlePack
187 }
188
189 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
190 let supportedTypes: {Type: Bool} = {}
191 supportedTypes[Type<@DoodlePacks.NFT>()] = true
192 return supportedTypes
193 }
194
195 access(all) view fun isSupportedNFTType(type: Type): Bool {
196 if type == Type<@DoodlePacks.NFT>() {
197 return true
198 } else {
199 return false
200 }
201 }
202
203 access(all) view fun getLength(): Int {
204 return self.ownedNFTs.keys.length
205 }
206
207 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
208 return <- DoodlePacks.createEmptyCollection(nftType: Type<@DoodlePacks.NFT>())
209 }
210
211 init () {
212 self.ownedNFTs <- {}
213 }
214 }
215
216 access(account) fun mintNFT(recipient: &{NonFungibleToken.Receiver}, typeId: UInt64) {
217 let packType = DoodlePackTypes.getPackType(id: typeId) ?? panic("Invalid pack type")
218 assert(packType.maxSupply == nil || DoodlePackTypes.getPackTypesMintedCount(typeId: packType.id) < packType.maxSupply!, message: "Max supply reached")
219
220 let pack: @DoodlePacks.NFT <- create DoodlePacks.NFT(typeId: typeId)
221
222 DoodlePackTypes.addMintedCountToPackType(typeId: typeId, amount: 1)
223
224 emit Minted(id: pack.id, typeId: typeId)
225
226 recipient.deposit(token: <-pack)
227 }
228
229 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
230 return <- create Collection()
231 }
232
233 access(all) fun open(collection: auth(NonFungibleToken.Withdraw) &{DoodlePacks.CollectionPublic, NonFungibleToken.Provider}, packId: UInt64): @OpenDoodlePacks.NFT {
234 let pack = collection.borrowDoodlePack(id: packId) ?? panic("Could not borrow a reference to the pack")
235
236 let openPack <- OpenDoodlePacks.mintNFT(id: pack.id, serialNumber: pack.serialNumber, typeId: pack.typeId)
237 emit Opened(id: packId, address: collection.owner!.address)
238 Burner.burn(<- collection.withdraw(withdrawID: packId))
239 return <- openPack
240 }
241
242 access(all) view fun getContractViews(resourceType: Type?): [Type] {
243 return [
244 Type<MetadataViews.NFTCollectionDisplay>(),
245 Type<MetadataViews.NFTCollectionData>()
246 ]
247 }
248
249 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
250 switch viewType {
251 case Type<MetadataViews.NFTCollectionDisplay>():
252 return MetadataViews.NFTCollectionDisplay(
253 name: "Doodle Packs",
254 description: "Doodle Packs can be open to obtain multiple NFTs!",
255 externalURL: MetadataViews.ExternalURL("https://doodles.app"),
256 squareImage: MetadataViews.Media(file: MetadataViews.IPFSFile(cid: "", path: nil), mediaType:"image/png"),
257 bannerImage: MetadataViews.Media(file: MetadataViews.IPFSFile(cid: "", path: nil), mediaType:"image/png"),
258 socials: {
259 "instagram": MetadataViews.ExternalURL("https://www.instagram.com/thedoodles"),
260 "discord": MetadataViews.ExternalURL("https://discord.gg/doodles"),
261 "twitter": MetadataViews.ExternalURL("https://twitter.com/doodles")
262 }
263 )
264 case Type<MetadataViews.NFTCollectionData>():
265 return MetadataViews.NFTCollectionData(
266 storagePath: DoodlePacks.CollectionStoragePath,
267 publicPath: DoodlePacks.CollectionPublicPath,
268 publicCollection: Type<&Collection>(),
269 publicLinkedType: Type<&Collection>(),
270 createEmptyCollectionFunction: (fun():
271 @{NonFungibleToken.Collection} {return <- DoodlePacks.createEmptyCollection(nftType: Type<@DoodlePacks.NFT>())})
272 )
273 }
274 return nil
275 }
276
277 init() {
278 self.CollectionStoragePath = /storage/DoodlePacksCollection
279 self.CollectionPublicPath = /public/DoodlePacksCollection
280
281 self.totalSupply = 0
282 self.currentPackTypesSerialNumber = {}
283 self.packTypesCurrentSupply = {}
284
285 self.extra = {}
286
287 self.account.storage.save<@{NonFungibleToken.Collection}>(<- DoodlePacks.createEmptyCollection(nftType: Type<@DoodlePacks.NFT>()), to: DoodlePacks.CollectionStoragePath)
288 let cap = self.account.capabilities.storage.issue<&Collection>(DoodlePacks.CollectionStoragePath)
289 self.account.capabilities.publish(cap, at: DoodlePacks.CollectionPublicPath)
290
291 emit ContractInitialized()
292 }
293}
294
295
296