Smart Contract
JollyJokers
A.699bf284101a76f1.JollyJokers
1// SPDX-License-Identifier: MIT
2
3/*
4* This is a 5,000 supply based NFT collection named Jolly Jokers with minimal metadata.
5*/
6
7import FungibleToken from 0xf233dcee88fe0abe
8import NonFungibleToken from 0x1d7e57aa55817448
9import MetadataViews from 0x1d7e57aa55817448
10import ViewResolver from 0x1d7e57aa55817448
11
12access(all) contract JollyJokers: NonFungibleToken {
13 access(all) var totalSupply: UInt64
14 access(all) var maxSupply: UInt64
15 access(all) var baseURI: String
16 access(all) var price: UFix64
17 access(all) var name: String
18 access(all) var description: String
19 access(all) var thumbnails: {UInt64: String}
20 access(contract) var metadatas: {UInt64: {String: AnyStruct}}
21 access(contract) var traits: {UInt64: {String: String}}
22
23 access(all) event ContractInitialized()
24 access(all) event BaseURISet(newBaseURI: String)
25 access(all) event Deposit(id: UInt64, to: Address?)
26 access(all) event Mint(id: UInt64, to: Address?)
27 access(all) event Withdraw(id: UInt64, from: Address?)
28
29 access(all) let CollectionStoragePath: StoragePath
30 access(all) let CollectionPublicPath: PublicPath
31 access(all) let MinterStoragePath: StoragePath
32 access(all) let AdminStoragePath: StoragePath
33 access(all) let AdminPrivatePath: PrivatePath
34
35 access(all) resource NFT: NonFungibleToken.NFT {
36 access(all) let id: UInt64
37
38 access(all) let name: String
39 access(all) let description: String
40 access(self) let metadata: {String: AnyStruct}
41
42 init(id: UInt64, metadata: {String: AnyStruct}) {
43 self.id = id
44 self.name = JollyJokers.name.concat(" #").concat(id.toString())
45 self.description = JollyJokers.description
46 self.metadata = metadata
47 }
48
49 /// createEmptyCollection creates an empty Collection
50 /// and returns it to the caller so that they can own NFTs
51 /// @{NonFungibleToken.Collection}
52 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
53 return <-JollyJokers.createEmptyCollection(nftType: Type<@JollyJokers.NFT>())
54 }
55
56 access(all) fun getThumbnail(): String {
57 return JollyJokers.thumbnails[self.id] ?? JollyJokers.baseURI.concat(self.id.toString()).concat(".png")
58 }
59
60 access(all) view fun getViews(): [Type] {
61 return [
62 Type<MetadataViews.Display>(),
63 Type<MetadataViews.Royalties>(),
64 Type<MetadataViews.Editions>(),
65 Type<MetadataViews.ExternalURL>(),
66 Type<MetadataViews.NFTCollectionData>(),
67 Type<MetadataViews.NFTCollectionDisplay>(),
68 Type<MetadataViews.Traits>()
69 ]
70 }
71
72 access(all) fun resolveView(_ view: Type): AnyStruct? {
73 switch view {
74 case Type<MetadataViews.Display>():
75 return MetadataViews.Display(
76 name: self.name,
77 description: self.description,
78 thumbnail: MetadataViews.HTTPFile(url: self.getThumbnail())
79 )
80 case Type<MetadataViews.Editions>():
81 // There is no max number of NFTs that can be minted from this contract
82 // so the max edition field value is set to nil
83 let editionInfo = MetadataViews.Edition(name: "Jolly Jokers Edition", number: self.id, max: JollyJokers.maxSupply)
84 let editionList: [MetadataViews.Edition] = [editionInfo]
85 return MetadataViews.Editions(editionList)
86 case Type<MetadataViews.ExternalURL>():
87 return MetadataViews.ExternalURL("https://otmnft-jj.s3.amazonaws.com/Jolly_Jokers.png")
88 case Type<MetadataViews.NFTCollectionData>():
89 return JollyJokers.resolveContractView(resourceType: Type<@JollyJokers.NFT>(), viewType: Type<MetadataViews.NFTCollectionData>())
90 case Type<MetadataViews.NFTCollectionDisplay>():
91 return JollyJokers.resolveContractView(resourceType: Type<@JollyJokers.NFT>(), viewType: Type<MetadataViews.NFTCollectionDisplay>())
92 case Type<MetadataViews.Traits>():
93 return MetadataViews.dictToTraits(dict: JollyJokers.traits[self.id] ?? {}, excludedNames: [])
94 case Type<MetadataViews.Royalties>():
95 // note: Royalties are not aware of the token being used with, so the path is not useful right now
96 // eventually the FungibleTokenSwitchboard might be an option
97 // https://github.com/onflow/flow-ft/blob/master/contracts/FungibleTokenSwitchboard.cdc
98 let cut = MetadataViews.Royalty(
99 receiver: JollyJokers.account.capabilities.get<&{FungibleToken.Receiver}>(/public/JollyJokersCollection),
100 cut: 0.05, // 5% royalty
101 description: "Creator Royalty"
102 )
103 var royalties: [MetadataViews.Royalty] = [cut]
104 return MetadataViews.Royalties(royalties)
105 }
106 return nil
107 }
108 }
109
110 access(all) resource interface JollyJokersCollectionPublic {}
111
112 access(all) resource Collection: NonFungibleToken.Collection, JollyJokersCollectionPublic {
113 // dictionary of NFT conforming tokens
114 // NFT is a resource type with an `UInt64` ID field
115 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
116
117 init () {
118 self.ownedNFTs <- {}
119 }
120
121 /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
122 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
123 let supportedTypes: {Type: Bool} = {}
124 supportedTypes[Type<@JollyJokers.NFT>()] = true
125 return supportedTypes
126 }
127
128 /// Returns whether or not the given type is accepted by the collection
129 /// A collection that can accept any type should just return true by default
130 access(all) view fun isSupportedNFTType(type: Type): Bool {
131 return type == Type<@JollyJokers.NFT>()
132 }
133
134 // withdraw removes an NFT from the collection and moves it to the caller
135 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
136 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("Could not withdraw an NFT with the provided ID from the collection")
137
138 emit Withdraw(id: token.id, from: self.owner?.address)
139
140 return <-token
141 }
142
143 // deposit takes a NFT and adds it to the collections dictionary
144 // and adds the ID to the id array
145 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
146 let token <- token as! @JollyJokers.NFT
147 let id: UInt64 = token.id
148
149 // add the new token to the dictionary which removes the old one
150 let oldToken <- self.ownedNFTs[id] <- token
151
152 emit Deposit(id: id, to: self.owner?.address)
153
154 destroy oldToken
155 }
156
157 // getIDs returns an array of the IDs that are in the collection
158 access(all) view fun getIDs(): [UInt64] {
159 return self.ownedNFTs.keys
160 }
161
162 /// Gets the amount of NFTs stored in the collection
163 access(all) view fun getLength(): Int {
164 return self.ownedNFTs.length
165 }
166
167 // borrowNFT gets a reference to an NFT in the collection
168 // so that the caller can read its metadata and call its methods
169 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
170 return &self.ownedNFTs[id]
171 }
172
173 access(all) view fun borrowJollyJokers(id: UInt64): &JollyJokers.NFT? {
174 if self.ownedNFTs[id] != nil {
175 // Create an authorized reference to allow downcasting
176 let ref = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}?
177 return ref as! &JollyJokers.NFT
178 }
179
180 return nil
181 }
182
183 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
184 if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
185 return nft as &{ViewResolver.Resolver}
186 }
187 return nil
188 }
189
190 /// createEmptyCollection creates an empty Collection of the same type
191 /// and returns it to the caller
192 /// @return A an empty collection of the same type
193 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
194 return <-JollyJokers.createEmptyCollection(nftType: Type<@JollyJokers.NFT>())
195 }
196 }
197
198 // public function that anyone can call to create a new empty collection
199 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
200 return <- create Collection()
201 }
202
203 /// Function that returns all the Metadata Views implemented by a Non Fungible Token
204 ///
205 /// @return An array of Types defining the implemented views. This value will be used by
206 /// developers to know which parameter to pass to the resolveView() method.
207 ///
208 access(all) view fun getContractViews(resourceType: Type?): [Type] {
209 return [
210 Type<MetadataViews.NFTCollectionData>(),
211 Type<MetadataViews.NFTCollectionDisplay>(),
212 Type<MetadataViews.EVMBridgedMetadata>()
213 ]
214 }
215
216 /// Function that resolves a metadata view for this contract.
217 ///
218 /// @param view: The Type of the desired view.
219 /// @return A structure representing the requested view.
220 ///
221 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
222 switch viewType {
223 case Type<MetadataViews.NFTCollectionData>():
224 let collectionData = MetadataViews.NFTCollectionData(
225 storagePath: self.CollectionStoragePath,
226 publicPath: self.CollectionPublicPath,
227 publicCollection: Type<&JollyJokers.Collection>(),
228 publicLinkedType: Type<&JollyJokers.Collection>(),
229 createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
230 return <-JollyJokers.createEmptyCollection(nftType: Type<@JollyJokers.NFT>())
231 })
232 )
233 return collectionData
234 case Type<MetadataViews.NFTCollectionDisplay>():
235 let squareMedia = MetadataViews.Media(
236 file: MetadataViews.HTTPFile(
237 url: "https://otmnft-jj.s3.amazonaws.com/Jolly_Jokers.png"
238 ),
239 mediaType: "image/png"
240 )
241 let bannerMedia = MetadataViews.Media(
242 file: MetadataViews.HTTPFile(
243 url: "https://otmnft-jj.s3.amazonaws.com/Joker-Banner.png"
244 ),
245 mediaType: "image/png"
246 )
247 return MetadataViews.NFTCollectionDisplay(
248 name: "Jolly Jokers",
249 description: "The Jolly Joker Sports Society is a collection of 5,000 Jolly Jokers living on the Flow blockchain. Owning a Jolly Joker gets you access to the Own the Moment ecosystem, including analytics tools for NBA Top Shot and NFL ALL DAY, token-gated fantasy sports and poker competitions, and so much more. If you are a fan of sports, leaderboards, and fun – then the Jolly Jokers is the perfect community for you!",
250 externalURL: MetadataViews.ExternalURL("https://otmnft.com/"),
251 squareImage: squareMedia,
252 bannerImage: bannerMedia,
253 socials: {
254 "twitter": MetadataViews.ExternalURL("https://twitter.com/jollyjokersnft")
255 }
256 )
257 case Type<MetadataViews.EVMBridgedMetadata>():
258 // Implementing this view gives the project control over how the bridged NFT is represented as an ERC721
259 // when bridged to EVM on Flow via the public infrastructure bridge.
260
261 // Compose the contract-level URI. In this case, the contract metadata is located on some HTTP host,
262 // but it could be IPFS, S3, a data URL containing the JSON directly, etc.
263 return MetadataViews.EVMBridgedMetadata(
264 name: "Jolly Jokers",
265 symbol: "JJSS",
266 uri: MetadataViews.URI(
267 baseURI: nil, // setting baseURI as nil sets the given value as the uri field value
268 value: "https://example-nft.onflow.org/contract-metadata.json"
269 )
270 )
271 }
272 return nil
273 }
274
275 // Resource that an admin or something similar would own to be
276 // able to mint new NFTs
277 //
278 access(all) resource NFTMinter {
279 // mintNFT mints a new NFT with a new ID
280 // and deposit it in the recipients collection using their collection reference
281 access(all) fun mintNFT(recipient: &{NonFungibleToken.CollectionPublic}) {
282 pre {
283 JollyJokers.totalSupply < JollyJokers.maxSupply: "Total supply reached maximum limit"
284 }
285 let metadata: {String: AnyStruct} = {}
286 let currentBlock = getCurrentBlock()
287 metadata["mintedBlock"] = currentBlock.height
288 metadata["mintedAt"] = currentBlock.timestamp
289 metadata["minter"] = recipient.owner!.address
290
291 // create a new NFT
292 JollyJokers.totalSupply = JollyJokers.totalSupply + 1
293
294 var newNFT <- create NFT(id: JollyJokers.totalSupply, metadata: metadata)
295
296 emit Mint(id: JollyJokers.totalSupply, to: recipient.owner?.address)
297
298 // deposit it in the recipient's account using their reference
299 recipient.deposit(token: <-newNFT)
300 }
301 }
302
303 // Admin is a special authorization resource that allows the owner
304 // to create or update SKUs and to manage baseURI
305 //
306 access(all) resource Admin {
307 access(all) fun setBaseURI(newBaseURI: String) {
308 JollyJokers.baseURI = newBaseURI
309 emit BaseURISet(newBaseURI: newBaseURI)
310 }
311
312 access(all) fun setMaxSupply(newMaxSupply: UInt64) {
313 JollyJokers.maxSupply = newMaxSupply
314 }
315
316 access(all) fun setPrice(newPrice: UFix64) {
317 JollyJokers.price = newPrice
318 }
319
320 access(all) fun setMetadata(name: String, description: String) {
321 JollyJokers.name = name
322 JollyJokers.description = description
323 }
324
325 access(all) fun setThumbnail(id: UInt64, thumbnail: String) {
326 JollyJokers.thumbnails[id] = thumbnail
327 }
328
329 access(all) fun updateTraits(id: UInt64, traits: {String: String}) {
330 JollyJokers.traits[id] = traits
331 }
332 }
333
334 init() {
335 // Initialize the total, max supply and base uri
336 self.totalSupply = 0
337 self.maxSupply = 0
338 self.baseURI = ""
339 self.price = 0.0
340 self.name = ""
341 self.description = ""
342 self.thumbnails = {}
343 self.metadatas = {}
344 self.traits = {}
345
346 // Set the named paths
347 self.CollectionStoragePath = /storage/JollyJokersCollection
348 self.CollectionPublicPath = /public/JollyJokersCollection
349 self.MinterStoragePath = /storage/JollyJokersMinter
350 self.AdminStoragePath = /storage/JollyJokersAdmin
351 self.AdminPrivatePath = /private/JollyJokersAdmin
352
353 let collection <- create Collection()
354 self.account.storage.save(<-collection, to: self.CollectionStoragePath)
355 let minter <- create NFTMinter()
356 self.account.storage.save(<-minter, to: self.MinterStoragePath)
357 let admin <- create Admin()
358 self.account.storage.save(<-admin, to: self.AdminStoragePath)
359
360 // create a public capability for the collection
361 let collectionCap: Capability = self.account.capabilities.storage.issue<&JollyJokers.Collection>(self.CollectionStoragePath)
362 self.account.capabilities.publish(collectionCap, at: self.CollectionPublicPath)
363
364
365 emit ContractInitialized()
366 }
367}
368