Smart Contract

KeeprItems

A.5eb12ad3d5a99945.KeeprItems

Valid From

87,041,267

Deployed

2w ago
Feb 11, 2026, 06:46:03 PM UTC

Dependents

55 imports
1/*
2*
3*  This is an example implementation of a Flow Non-Fungible Token
4*  using the V2 standard.
5*  It is not part of the official standard but it assumed to be
6*  similar to how many NFTs would implement the core functionality.
7*
8*  This contract does not implement any sophisticated classification
9*  system for its NFTs. It defines a simple NFT with minimal metadata.
10*
11*/
12
13import NonFungibleToken from 0x1d7e57aa55817448
14import ViewResolver from 0x1d7e57aa55817448
15import MetadataViews from 0x1d7e57aa55817448
16
17access(all) contract KeeprItems: NonFungibleToken {
18
19    access(all) event Withdraw(id: UInt64, from: Address?)
20    access(all) event Deposit(id: UInt64, to: Address?)
21
22    /// Standard Paths
23    access(all) let CollectionStoragePath: StoragePath
24    access(all) let CollectionPublicPath: PublicPath
25
26    /// Path where the minter should be stored
27    /// The standard paths for the collection are stored in the collection resource type
28    access(all) let MinterStoragePath: StoragePath
29
30    access(all) var totalSupply: UInt64
31    access(all) event Minted(id: UInt64, name: String, imageUrl: String, thumbnailUrl: String, imageCid: String, thumbCid: String, docId: String)
32
33    /// We choose the name NFT here, but this type can have any name now
34    /// because the interface does not require it to have a specific name any more
35    access(all) resource NFT: NonFungibleToken.NFT {
36
37        access(all) let id: UInt64
38        access(all) let cid: String
39        access(all) let path: String
40        access(all) let thumbCid: String
41        access(all) let thumbPath: String
42        access(all) let cardBackCid: String?
43        access(all) let cardBackPath: String?
44        access(all) let name: String
45        access(all) let description: String
46
47        init(id: UInt64, cid: String, path: String, thumbCid: String, thumbPath: String, name: String, description: String, cardBackCid: String, cardBackPath: String) {
48            self.id = id
49            self.cid = cid
50            self.path = path
51            self.thumbCid = thumbCid
52            self.thumbPath = thumbPath
53            self.name = name
54            self.description = description
55            self.cardBackCid = cardBackCid
56            self.cardBackPath = cardBackPath
57        }
58
59        /// createEmptyCollection creates an empty Collection
60        /// and returns it to the caller so that they can own NFTs
61        /// @{NonFungibleToken.Collection}
62        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
63            return <-KeeprItems.createEmptyCollection(nftType: Type<@KeeprItems.NFT>())
64        }
65
66        access(all) view fun getViews(): [Type] {
67            return [
68                Type<MetadataViews.Display>(),
69                Type<MetadataViews.Editions>(),
70                // Type<MetadataViews.ExternalURL>(),
71                // Type<MetadataViews.NFTView>(),
72                Type<MetadataViews.NFTCollectionData>(),
73                Type<MetadataViews.NFTCollectionDisplay>(),
74                Type<MetadataViews.Serial>(),
75                Type<MetadataViews.EVMBridgedMetadata>()
76            ]
77        }
78
79        access(all) fun resolveView(_ view: Type): AnyStruct? {
80            switch view {
81                case Type<MetadataViews.Display>():
82
83                    return MetadataViews.Display(
84                        name: self.name,
85                        description: self.description,
86                        thumbnail: self.cid != "" ? MetadataViews.IPFSFile(
87                            cid: self.thumbCid, 
88                            path: self.thumbPath
89                        ) : MetadataViews.HTTPFile(
90                            url: self.path
91                        ),
92                    )
93                case Type<MetadataViews.Editions>():
94                    // There is no max number of NFTs that can be minted from this contract
95                    // so the max edition field value is set to nil
96                    let editionInfo = MetadataViews.Edition(name: "Keepr NFT Edition", number: self.id, max: nil)
97                    let editionList: [MetadataViews.Edition] = [editionInfo]
98                    return MetadataViews.Editions(
99                        editionList
100                    )
101                case Type<MetadataViews.Serial>():
102                    return MetadataViews.Serial(
103                        self.id
104                    )
105                // case Type<MetadataViews.ExternalURL>():
106                //     return MetadataViews.ExternalURL("https://example-nft.onflow.org/".concat(self.id.toString()))
107                case Type<MetadataViews.NFTCollectionData>():
108                    return KeeprItems.resolveContractView(resourceType: Type<@KeeprItems.NFT>(), viewType: Type<MetadataViews.NFTCollectionData>())
109                case Type<MetadataViews.NFTCollectionDisplay>():
110                    return KeeprItems.resolveContractView(resourceType: Type<@KeeprItems.NFT>(), viewType: Type<MetadataViews.NFTCollectionDisplay>())
111                // case Type<MetadataViews.NFTCollectionDisplay>():
112                //     return MetadataViews.NFTCollectionDisplay(
113                //         name: self.name,
114                //         description: "Keepr Items are NFTs that represent items in the Keepr ecosystem.",
115                //         externalURL: MetadataViews.ExternalURL("https://keepr.gg/"),
116                //         squareImage: MetadataViews.Media(
117                //             file: MetadataViews.HTTPFile(
118                //                 url: self.path
119                //             ),
120                //             mediaType: "image/jpeg"
121                //         ),
122                //         bannerImage: MetadataViews.Media(
123                //             file: MetadataViews.HTTPFile(
124                //                 url: self.path
125                //             ),
126                //             mediaType: "image/jpeg"
127                //         ),
128                //         socials: {
129                //             "twitter": MetadataViews.ExternalURL("https://twitter.com/keeprGG")
130                //         }
131                //     )
132                case Type<MetadataViews.EVMBridgedMetadata>():
133                    // Implementing this view gives the project control over how the bridged NFT is represented as an
134                    // ERC721 when bridged to EVM on Flow via the public infrastructure bridge.
135
136                    // Get the contract-level name and symbol values
137                    let contractLevel = KeeprItems.resolveContractView(
138                            resourceType: nil,
139                            viewType: Type<MetadataViews.EVMBridgedMetadata>()
140                        ) as! MetadataViews.EVMBridgedMetadata?
141                        ?? panic("Could not resolve contract-level EVMBridgedMetadata")
142                    // Compose the token-level URI based on a base URI and the token ID, pointing to a JSON file. This
143                    // would be a file you've uploaded and are hosting somewhere - in this case HTTP, but this could be
144                    // IPFS, S3, a data URL containing the JSON directly, etc.
145                    let baseURI = "https://example-nft.onflow.org/token-metadata/"
146                    let uriValue = self.id.toString().concat(".json")
147
148                    return MetadataViews.EVMBridgedMetadata(
149                        name: contractLevel.name,
150                        symbol: contractLevel.symbol,
151                        uri: MetadataViews.URI(
152                            baseURI: baseURI, // defining baseURI results in a concatenation of baseURI and value
153                            value: self.id.toString().concat(".json")
154                        )
155                    )
156                // case Type<MetadataViews.NFTView>():
157                //     return MetadataViews.NFTView(
158                //         id: self.id,
159                //         uuid: self.id,
160                //         display: MetadataViews.Display(
161                //             name: self.name,
162                //             description: self.description,
163                //             thumbnail: self.cid != "" ? MetadataViews.IPFSFile(
164                //                 cid: self.thumbCid, 
165                //                 path: self.thumbPath
166                //             ) : MetadataViews.HTTPFile(
167                //                 url: self.path
168                //             ),
169                //         ),
170                //         externalURL: MetadataViews.ExternalURL("https://keepr.gg/"),
171                //         collectionData: nil,
172                //         collectionDisplay: nil,
173                //         royalties: nil,
174                //         traits: nil
175                //     )
176            }
177            return nil
178        }
179    }
180
181    // Deprecated: Only here for backward compatibility.
182    access(all) resource interface KeeprItemsCollectionPublic {}
183
184    access(all) resource Collection: NonFungibleToken.Collection, KeeprItemsCollectionPublic {
185        /// dictionary of NFT conforming tokens
186        /// NFT is a resource type with an `UInt64` ID field
187        access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
188
189        init () {
190            self.ownedNFTs <- {}
191        }
192
193        /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
194        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
195            let supportedTypes: {Type: Bool} = {}
196            supportedTypes[Type<@KeeprItems.NFT>()] = true
197            return supportedTypes
198        }
199
200        /// Returns whether or not the given type is accepted by the collection
201        /// A collection that can accept any type should just return true by default
202        access(all) view fun isSupportedNFTType(type: Type): Bool {
203            return type == Type<@KeeprItems.NFT>()
204        }
205
206        /// withdraw removes an NFT from the collection and moves it to the caller
207        access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
208            let token <- self.ownedNFTs.remove(key: withdrawID)
209                ?? panic("Could not withdraw an NFT with the provided ID from the collection")
210
211            emit Withdraw(id: token.id, from: self.owner?.address)
212
213            return <-token
214        }
215
216        /// deposit takes a NFT and adds it to the collections dictionary
217        /// and adds the ID to the id array
218        access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
219            let token <- token as! @KeeprItems.NFT
220            let id = token.id
221
222            // add the new token to the dictionary which removes the old one
223            let oldToken <- self.ownedNFTs[token.id] <- token
224
225            emit Deposit(id: id, to: self.owner?.address)
226
227            destroy oldToken
228        }
229
230        /// getIDs returns an array of the IDs that are in the collection
231        access(all) view fun getIDs(): [UInt64] {
232            return self.ownedNFTs.keys
233        }
234
235        /// Gets the amount of NFTs stored in the collection
236        access(all) view fun getLength(): Int {
237            return self.ownedNFTs.length
238        }
239
240        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
241            return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
242        }
243
244        /// Borrow the view resolver for the specified NFT ID
245        access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
246            if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
247                return nft as &{ViewResolver.Resolver}
248            }
249            return nil
250        }
251
252        /// createEmptyCollection creates an empty Collection of the same type
253        /// and returns it to the caller
254        /// @return A an empty collection of the same type
255        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
256            return <-KeeprItems.createEmptyCollection(nftType: Type<@KeeprItems.NFT>())
257        }
258    }
259
260    /// createEmptyCollection creates an empty Collection for the specified NFT type
261    /// and returns it to the caller so that they can own NFTs
262    access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
263        return <- create Collection()
264    }
265
266    /// Function that returns all the Metadata Views implemented by a Non Fungible Token
267    ///
268    /// @return An array of Types defining the implemented views. This value will be used by
269    ///         developers to know which parameter to pass to the resolveView() method.
270    ///
271    access(all) view fun getContractViews(resourceType: Type?): [Type] {
272        return [
273            Type<MetadataViews.NFTCollectionData>(),
274            Type<MetadataViews.NFTCollectionDisplay>(),
275            Type<MetadataViews.EVMBridgedMetadata>()
276        ]
277    }
278
279    /// Function that resolves a metadata view for this contract.
280    ///
281    /// @param view: The Type of the desired view.
282    /// @return A structure representing the requested view.
283    ///
284    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
285        switch viewType {
286            case Type<MetadataViews.NFTCollectionData>():
287                let collectionData = MetadataViews.NFTCollectionData(
288                    storagePath: self.CollectionStoragePath,
289                    publicPath: self.CollectionPublicPath,
290                    publicCollection: Type<&KeeprItems.Collection>(),
291                    publicLinkedType: Type<&KeeprItems.Collection>(),
292                    createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
293                        return <-KeeprItems.createEmptyCollection(nftType: Type<@KeeprItems.NFT>())
294                    })
295                )
296                return collectionData
297            case Type<MetadataViews.NFTCollectionDisplay>():
298                let media = MetadataViews.Media(
299                    file: MetadataViews.HTTPFile(
300                        url: "https://firebasestorage.googleapis.com/v0/b/keepr-86355.appspot.com/o/static%2Flogo-dark.svg?alt=media&token=9d66d7ea-9b3e-4fe0-8604-04df064af359"
301                    ),
302                    mediaType: "image/svg+xml"
303                )
304                return MetadataViews.NFTCollectionDisplay(
305                    name: "The Keepr Collection",
306                    description: "This collection is used as an example to help you develop your next Flow NFT.",
307                    externalURL: MetadataViews.ExternalURL("https://keepr.gg/"),
308                    squareImage: media,
309                    bannerImage: media,
310                    socials: {
311                        "twitter": MetadataViews.ExternalURL("https://twitter.com/keeprGG")
312                    }
313                )
314            case Type<MetadataViews.EVMBridgedMetadata>():
315                // Implementing this view gives the project control over how the bridged NFT is represented as an ERC721
316                // when bridged to EVM on Flow via the public infrastructure bridge.
317
318                // Compose the contract-level URI. In this case, the contract metadata is located on some HTTP host,
319                // but it could be IPFS, S3, a data URL containing the JSON directly, etc.
320                return MetadataViews.EVMBridgedMetadata(
321                    name: "KeeprItems",
322                    symbol: "XMPL",
323                    uri: MetadataViews.URI(
324                        baseURI: nil, // setting baseURI as nil sets the given value as the uri field value
325                        value: "https://example-nft.onflow.org/contract-metadata.json"
326                    )
327                )
328        }
329        return nil
330    }
331
332    /// Resource that an admin or something similar would own to be
333    /// able to mint new NFTs
334    ///
335    access(all) resource NFTMinter {
336
337        access(all) fun dwebURL(_ cid: String, _ path: String): String {
338            var url = "https://"
339                .concat(cid)
340                .concat(".ipfs.dweb.link/")
341            
342            return url.concat(path)
343        }
344
345        /// mintNFT mints a new NFT with a new ID
346        /// and returns it to the calling context
347        access(all) fun mintNFT(
348            recipient: &{NonFungibleToken.CollectionPublic}, 
349            cid: String, 
350            path: String, 
351            thumbCid: String, 
352            thumbPath: String, 
353            name: String, 
354            description: String,
355            docId: String,
356            cardBackCid: String,
357            cardBackPath: String
358        ) {
359            // deposit it in the recipient's account using their reference
360            let item <-create KeeprItems.NFT(id: KeeprItems.totalSupply, cid: cid, path: path, thumbCid: thumbCid, thumbPath: thumbPath, name: name, description: description, cardBackCid: cardBackCid, cardBackPath: cardBackPath)
361            
362            emit Minted(
363                id: KeeprItems.totalSupply,
364                name: name,
365                imageUrl: self.dwebURL(item.cid, item.path),
366                thumbnailUrl: self.dwebURL(item.thumbCid, item.thumbPath),
367                imageCid: cid,
368                thumbCid: thumbCid,
369                docId: docId
370            )
371
372            recipient.deposit(token: <-item)
373
374            KeeprItems.totalSupply = KeeprItems.totalSupply + (1 as UInt64)
375        }
376    }
377
378    init() {
379        self.totalSupply = 0
380
381        // Set the named paths
382        self.CollectionStoragePath = /storage/KeeprItemsCollectionV10
383        self.CollectionPublicPath = /public/KeeprItemsCollectionV10
384        self.MinterStoragePath = /storage/KeeprItemsMinterV10
385
386        // Create a Collection resource and save it to storage
387        let collection <- create Collection()
388        self.account.storage.save(<-collection, to: self.CollectionStoragePath)
389
390        // create a public capability for the collection
391        let collectionCap = self.account.capabilities.storage.issue<&KeeprItems.Collection>(self.CollectionStoragePath)
392        self.account.capabilities.publish(collectionCap, at: self.CollectionPublicPath)
393
394        // Create a Minter resource and save it to storage
395        let minter <- create NFTMinter()
396        self.account.storage.save(<-minter, to: self.MinterStoragePath)
397    }
398}