Smart Contract
KeeprItems
A.5eb12ad3d5a99945.KeeprItems
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}