Smart Contract

JollyJokers

A.699bf284101a76f1.JollyJokers

Deployed

1w ago
Feb 15, 2026, 01:29:31 PM UTC

Dependents

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