Smart Contract
ExampleNFT
A.93c18b0282a6b82c.ExampleNFT
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 ExampleNFT: NonFungibleToken {
18
19 /// Path where the minter should be stored
20 /// The standard paths for the collection are stored in the collection resource type
21 access(all) let MinterStoragePath: StoragePath
22
23 /// We choose the name NFT here, but this type can have any name now
24 /// because the interface does not require it to have a specific name any more
25 access(all) resource NFT: NonFungibleToken.NFT, ViewResolver.Resolver {
26
27 access(all) let id: UInt64
28
29 /// From the Display metadata view
30 access(all) let name: String
31 access(all) let description: String
32 access(all) let thumbnail: String
33
34 /// For the Royalties metadata view
35 access(self) let royalties: [MetadataViews.Royalty]
36
37 /// Generic dictionary of traits the NFT has
38 access(self) let metadata: {String: AnyStruct}
39
40 init(
41 name: String,
42 description: String,
43 thumbnail: String,
44 royalties: [MetadataViews.Royalty],
45 metadata: {String: AnyStruct},
46 ) {
47 self.id = self.uuid
48 self.name = name
49 self.description = description
50 self.thumbnail = thumbnail
51 self.royalties = royalties
52 self.metadata = metadata
53 }
54
55 /// createEmptyCollection creates an empty Collection
56 /// and returns it to the caller so that they can own NFTs
57 /// @{NonFungibleToken.Collection}
58 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
59 return <-ExampleNFT.createEmptyCollection(nftType: Type<@ExampleNFT.NFT>())
60 }
61
62 access(all) view fun getViews(): [Type] {
63 return [
64 Type<MetadataViews.Display>(),
65 Type<MetadataViews.Royalties>(),
66 Type<MetadataViews.Editions>(),
67 Type<MetadataViews.ExternalURL>(),
68 Type<MetadataViews.NFTCollectionData>(),
69 Type<MetadataViews.NFTCollectionDisplay>(),
70 Type<MetadataViews.Serial>(),
71 Type<MetadataViews.Traits>()
72 ]
73 }
74
75 access(all) fun resolveView(_ view: Type): AnyStruct? {
76 switch view {
77 case Type<MetadataViews.Display>():
78 return MetadataViews.Display(
79 name: self.name,
80 description: self.description,
81 thumbnail: MetadataViews.HTTPFile(
82 url: self.thumbnail
83 )
84 )
85 case Type<MetadataViews.Editions>():
86 // There is no max number of NFTs that can be minted from this contract
87 // so the max edition field value is set to nil
88 let editionInfo = MetadataViews.Edition(name: "Example NFT Edition", number: self.id, max: nil)
89 let editionList: [MetadataViews.Edition] = [editionInfo]
90 return MetadataViews.Editions(
91 editionList
92 )
93 case Type<MetadataViews.Serial>():
94 return MetadataViews.Serial(
95 self.id
96 )
97 case Type<MetadataViews.Royalties>():
98 return MetadataViews.Royalties(
99 self.royalties
100 )
101 case Type<MetadataViews.ExternalURL>():
102 return MetadataViews.ExternalURL("https://example-nft.onflow.org/".concat(self.id.toString()))
103 case Type<MetadataViews.NFTCollectionData>():
104 return ExampleNFT.resolveContractView(resourceType: Type<@ExampleNFT.NFT>(), viewType: Type<MetadataViews.NFTCollectionData>())
105 case Type<MetadataViews.NFTCollectionDisplay>():
106 return ExampleNFT.resolveContractView(resourceType: Type<@ExampleNFT.NFT>(), viewType: Type<MetadataViews.NFTCollectionDisplay>())
107 case Type<MetadataViews.Traits>():
108 // exclude mintedTime and foo to show other uses of Traits
109 let excludedTraits = ["mintedTime", "foo"]
110 let traitsView = MetadataViews.dictToTraits(dict: self.metadata, excludedNames: excludedTraits)
111
112 // foo is a trait with its own rarity
113 let fooTraitRarity = MetadataViews.Rarity(score: 10.0, max: 100.0, description: "Common")
114 let fooTrait = MetadataViews.Trait(name: "foo", value: self.metadata["foo"], displayType: nil, rarity: fooTraitRarity)
115 traitsView.addTrait(fooTrait)
116
117 return traitsView
118 }
119 return nil
120 }
121 }
122
123 access(all) resource Collection: NonFungibleToken.Collection {
124 /// dictionary of NFT conforming tokens
125 /// NFT is a resource type with an `UInt64` ID field
126 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
127
128 access(all) var storagePath: StoragePath
129 access(all) var publicPath: PublicPath
130
131 init () {
132 self.ownedNFTs <- {}
133 let identifier = "cadenceExampleNFTCollection"
134 self.storagePath = StoragePath(identifier: identifier)!
135 self.publicPath = PublicPath(identifier: identifier)!
136 }
137
138 /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
139 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
140 let supportedTypes: {Type: Bool} = {}
141 supportedTypes[Type<@ExampleNFT.NFT>()] = true
142 return supportedTypes
143 }
144
145 /// Returns whether or not the given type is accepted by the collection
146 /// A collection that can accept any type should just return true by default
147 access(all) view fun isSupportedNFTType(type: Type): Bool {
148 if type == Type<@ExampleNFT.NFT>() {
149 return true
150 } else {
151 return false
152 }
153 }
154
155 /// withdraw removes an NFT from the collection and moves it to the caller
156 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
157 let token <- self.ownedNFTs.remove(key: withdrawID)
158 ?? panic("Could not withdraw an NFT with the provided ID from the collection")
159
160 return <-token
161 }
162
163 /// deposit takes a NFT and adds it to the collections dictionary
164 /// and adds the ID to the id array
165 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
166 let token <- token as! @ExampleNFT.NFT
167
168 // add the new token to the dictionary which removes the old one
169 let oldToken <- self.ownedNFTs[token.id] <- token
170
171 destroy oldToken
172 }
173
174 /// getIDs returns an array of the IDs that are in the collection
175 access(all) view fun getIDs(): [UInt64] {
176 return self.ownedNFTs.keys
177 }
178
179 /// Gets the amount of NFTs stored in the collection
180 access(all) view fun getLength(): Int {
181 return self.ownedNFTs.keys.length
182 }
183
184 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
185 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
186 }
187
188 /// Borrow the view resolver for the specified NFT ID
189 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
190 if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
191 return nft as &{ViewResolver.Resolver}
192 }
193 return nil
194 }
195
196 /// createEmptyCollection creates an empty Collection of the same type
197 /// and returns it to the caller
198 /// @return A an empty collection of the same type
199 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
200 return <-ExampleNFT.createEmptyCollection(nftType: Type<@ExampleNFT.NFT>())
201 }
202 }
203
204 /// createEmptyCollection creates an empty Collection for the specified NFT type
205 /// and returns it to the caller so that they can own NFTs
206 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
207 return <- create Collection()
208 }
209
210 /// Function that returns all the Metadata Views implemented by a Non Fungible Token
211 ///
212 /// @return An array of Types defining the implemented views. This value will be used by
213 /// developers to know which parameter to pass to the resolveView() method.
214 ///
215 access(all) view fun getContractViews(resourceType: Type?): [Type] {
216 return [
217 Type<MetadataViews.NFTCollectionData>(),
218 Type<MetadataViews.NFTCollectionDisplay>()
219 ]
220 }
221
222 /// Function that resolves a metadata view for this contract.
223 ///
224 /// @param view: The Type of the desired view.
225 /// @return A structure representing the requested view.
226 ///
227 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
228 switch viewType {
229 case Type<MetadataViews.NFTCollectionData>():
230 let collectionData = MetadataViews.NFTCollectionData(
231 storagePath: /storage/cadenceExampleNFTCollection,
232 publicPath: /public/cadenceExampleNFTCollection,
233 publicCollection: Type<&ExampleNFT.Collection>(),
234 publicLinkedType: Type<&ExampleNFT.Collection>(),
235 createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
236 return <-ExampleNFT.createEmptyCollection(nftType: Type<@ExampleNFT.NFT>())
237 })
238 )
239 return collectionData
240 case Type<MetadataViews.NFTCollectionDisplay>():
241 let media = MetadataViews.Media(
242 file: MetadataViews.HTTPFile(
243 url: "https://assets.website-files.com/5f6294c0c7a8cdd643b1c820/5f6294c0c7a8cda55cb1c936_Flow_Wordmark.svg"
244 ),
245 mediaType: "image/svg+xml"
246 )
247 return MetadataViews.NFTCollectionDisplay(
248 name: "The Example Collection",
249 description: "This collection is used as an example to help you develop your next Flow NFT.",
250 externalURL: MetadataViews.ExternalURL("https://example-nft.onflow.org"),
251 squareImage: media,
252 bannerImage: media,
253 socials: {
254 "twitter": MetadataViews.ExternalURL("https://twitter.com/flow_blockchain")
255 }
256 )
257 }
258 return nil
259 }
260
261 /// Resource that an admin or something similar would own to be
262 /// able to mint new NFTs
263 ///
264 access(all) resource NFTMinter {
265
266 /// mintNFT mints a new NFT with a new ID
267 /// and returns it to the calling context
268 access(all) fun mintNFT(
269 name: String,
270 description: String,
271 thumbnail: String,
272 royalties: [MetadataViews.Royalty]
273 ): @ExampleNFT.NFT {
274
275 let metadata: {String: AnyStruct} = {}
276 let currentBlock = getCurrentBlock()
277 metadata["mintedBlock"] = currentBlock.height
278 metadata["mintedTime"] = currentBlock.timestamp
279
280 // this piece of metadata will be used to show embedding rarity into a trait
281 metadata["foo"] = "bar"
282
283 // create a new NFT
284 var newNFT <- create NFT(
285 name: name,
286 description: description,
287 thumbnail: thumbnail,
288 royalties: royalties,
289 metadata: metadata,
290 )
291
292 return <-newNFT
293 }
294 }
295
296 init() {
297
298 // Set the named paths
299 self.MinterStoragePath = /storage/cadenceExampleNFTMinter
300
301 // Create a Collection resource and save it to storage
302 let collection <- create Collection()
303 let defaultStoragePath = collection.storagePath
304 let defaultPublicPath = collection.publicPath
305 self.account.storage.save(<-collection, to: defaultStoragePath)
306
307 // create a public capability for the collection
308 let collectionCap = self.account.capabilities.storage.issue<&ExampleNFT.Collection>(defaultStoragePath)
309 self.account.capabilities.publish(collectionCap, at: defaultPublicPath)
310
311 // Create a Minter resource and save it to storage
312 let minter <- create NFTMinter()
313 self.account.storage.save(<-minter, to: self.MinterStoragePath)
314 }
315}
316