Smart Contract
FooBar
A.3eebe1cb4a1126b2.FooBar
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 FooBar: NonFungibleToken {
18
19 /// Standard Paths
20 access(all) let CollectionStoragePath: StoragePath
21 access(all) let CollectionPublicPath: PublicPath
22
23 /// Path where the minter should be stored
24 /// The standard paths for the collection are stored in the collection resource type
25 access(all) let MinterStoragePath: StoragePath
26
27 /// We choose the name NFT here, but this type can have any name now
28 /// because the interface does not require it to have a specific name any more
29 access(all) resource NFT: NonFungibleToken.NFT {
30
31 access(all) let id: UInt64
32
33 /// From the Display metadata view
34 access(all) let name: String
35 access(all) let description: String
36 access(all) let thumbnail: String
37
38 /// For the Royalties metadata view
39 access(self) let royalties: [MetadataViews.Royalty]
40
41 /// Generic dictionary of traits the NFT has
42 access(self) let metadata: {String: AnyStruct}
43
44 init(
45 name: String,
46 description: String,
47 thumbnail: String,
48 royalties: [MetadataViews.Royalty],
49 metadata: {String: AnyStruct},
50 ) {
51 self.id = self.uuid
52 self.name = name
53 self.description = description
54 self.thumbnail = thumbnail
55 self.royalties = royalties
56 self.metadata = metadata
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 <-FooBar.createEmptyCollection(nftType: Type<@FooBar.NFT>())
64 }
65
66 access(all) view fun getViews(): [Type] {
67 return [
68 Type<MetadataViews.Display>(),
69 Type<MetadataViews.Royalties>(),
70 Type<MetadataViews.Editions>(),
71 Type<MetadataViews.ExternalURL>(),
72 Type<MetadataViews.NFTCollectionData>(),
73 Type<MetadataViews.NFTCollectionDisplay>(),
74 Type<MetadataViews.Serial>(),
75 Type<MetadataViews.Traits>(),
76 Type<MetadataViews.EVMBridgedMetadata>()
77 ]
78 }
79
80 access(all) fun resolveView(_ view: Type): AnyStruct? {
81 switch view {
82 case Type<MetadataViews.Display>():
83 return MetadataViews.Display(
84 name: self.name,
85 description: self.description,
86 thumbnail: MetadataViews.HTTPFile(
87 url: self.thumbnail
88 )
89 )
90 case Type<MetadataViews.Editions>():
91 // There is no max number of NFTs that can be minted from this contract
92 // so the max edition field value is set to nil
93 let editionInfo = MetadataViews.Edition(name: "Example NFT Edition", number: self.id, max: nil)
94 let editionList: [MetadataViews.Edition] = [editionInfo]
95 return MetadataViews.Editions(
96 editionList
97 )
98 case Type<MetadataViews.Serial>():
99 return MetadataViews.Serial(
100 self.id
101 )
102 case Type<MetadataViews.Royalties>():
103 return MetadataViews.Royalties(
104 self.royalties
105 )
106 case Type<MetadataViews.ExternalURL>():
107 return MetadataViews.ExternalURL("https://example-nft.onflow.org/".concat(self.id.toString()))
108 case Type<MetadataViews.NFTCollectionData>():
109 return FooBar.resolveContractView(resourceType: Type<@FooBar.NFT>(), viewType: Type<MetadataViews.NFTCollectionData>())
110 case Type<MetadataViews.NFTCollectionDisplay>():
111 return FooBar.resolveContractView(resourceType: Type<@FooBar.NFT>(), viewType: Type<MetadataViews.NFTCollectionDisplay>())
112 case Type<MetadataViews.Traits>():
113 // exclude mintedTime and foo to show other uses of Traits
114 let excludedTraits = ["mintedTime", "foo"]
115 let traitsView = MetadataViews.dictToTraits(dict: self.metadata, excludedNames: excludedTraits)
116
117 // mintedTime is a unix timestamp, we should mark it with a displayType so platforms know how to show it.
118 let mintedTimeTrait = MetadataViews.Trait(name: "mintedTime", value: self.metadata["mintedTime"]!, displayType: "Date", rarity: nil)
119 traitsView.addTrait(mintedTimeTrait)
120
121 // foo is a trait with its own rarity
122 let fooTraitRarity = MetadataViews.Rarity(score: 10.0, max: 100.0, description: "Common")
123 let fooTrait = MetadataViews.Trait(name: "foo", value: self.metadata["foo"], displayType: nil, rarity: fooTraitRarity)
124 traitsView.addTrait(fooTrait)
125
126 return traitsView
127 case Type<MetadataViews.EVMBridgedMetadata>():
128 // Implementing this view gives the project control over how the bridged NFT is represented as an
129 // ERC721 when bridged to EVM on Flow via the public infrastructure bridge.
130
131 // Get the contract-level name and symbol values
132 let contractLevel = FooBar.resolveContractView(
133 resourceType: nil,
134 viewType: Type<MetadataViews.EVMBridgedMetadata>()
135 ) as! MetadataViews.EVMBridgedMetadata?
136 ?? panic("Could not resolve contract-level EVMBridgedMetadata")
137 // Compose the token-level URI based on a base URI and the token ID, pointing to a JSON file. This
138 // would be a file you've uploaded and are hosting somewhere - in this case HTTP, but this could be
139 // IPFS, S3, a data URL containing the JSON directly, etc.
140 let baseURI = "https://example-nft.onflow.org/token-metadata/"
141 let uriValue = self.id.toString().concat(".json")
142
143 return MetadataViews.EVMBridgedMetadata(
144 name: contractLevel.name,
145 symbol: contractLevel.symbol,
146 uri: MetadataViews.URI(
147 baseURI: baseURI, // defining baseURI results in a concatenation of baseURI and value
148 value: self.id.toString().concat(".json")
149 )
150 )
151
152 }
153 return nil
154 }
155 }
156
157 // Deprecated: Only here for backward compatibility.
158 access(all) resource interface FooBarCollectionPublic {}
159
160 access(all) resource Collection: NonFungibleToken.Collection, FooBarCollectionPublic {
161 /// dictionary of NFT conforming tokens
162 /// NFT is a resource type with an `UInt64` ID field
163 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
164
165 init () {
166 self.ownedNFTs <- {}
167 }
168
169 /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
170 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
171 let supportedTypes: {Type: Bool} = {}
172 supportedTypes[Type<@FooBar.NFT>()] = true
173 return supportedTypes
174 }
175
176 /// Returns whether or not the given type is accepted by the collection
177 /// A collection that can accept any type should just return true by default
178 access(all) view fun isSupportedNFTType(type: Type): Bool {
179 return type == Type<@FooBar.NFT>()
180 }
181
182 /// withdraw removes an NFT from the collection and moves it to the caller
183 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
184 let token <- self.ownedNFTs.remove(key: withdrawID)
185 ?? panic("Could not withdraw an NFT with the provided ID from the collection")
186
187 return <-token
188 }
189
190 /// deposit takes a NFT and adds it to the collections dictionary
191 /// and adds the ID to the id array
192 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
193 let token <- token as! @FooBar.NFT
194 let id = token.id
195
196 // add the new token to the dictionary which removes the old one
197 let oldToken <- self.ownedNFTs[token.id] <- token
198
199 destroy oldToken
200
201 // This code is for testing purposes only
202 // Do not add to your contract unless you have a specific
203 // reason to want to emit the NFTUpdated event somewhere
204 // in your contract
205 let authTokenRef = (&self.ownedNFTs[id] as auth(NonFungibleToken.Update) &{NonFungibleToken.NFT}?)!
206 //authTokenRef.updateTransferDate(date: getCurrentBlock().timestamp)
207 FooBar.emitNFTUpdated(authTokenRef)
208 }
209
210 /// getIDs returns an array of the IDs that are in the collection
211 access(all) view fun getIDs(): [UInt64] {
212 return self.ownedNFTs.keys
213 }
214
215 /// Gets the amount of NFTs stored in the collection
216 access(all) view fun getLength(): Int {
217 return self.ownedNFTs.length
218 }
219
220 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
221 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
222 }
223
224 /// Borrow the view resolver for the specified NFT ID
225 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
226 if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
227 return nft as &{ViewResolver.Resolver}
228 }
229 return nil
230 }
231
232 /// createEmptyCollection creates an empty Collection of the same type
233 /// and returns it to the caller
234 /// @return A an empty collection of the same type
235 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
236 return <-FooBar.createEmptyCollection(nftType: Type<@FooBar.NFT>())
237 }
238 }
239
240 /// createEmptyCollection creates an empty Collection for the specified NFT type
241 /// and returns it to the caller so that they can own NFTs
242 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
243 return <- create Collection()
244 }
245
246 /// Function that returns all the Metadata Views implemented by a Non Fungible Token
247 ///
248 /// @return An array of Types defining the implemented views. This value will be used by
249 /// developers to know which parameter to pass to the resolveView() method.
250 ///
251 access(all) view fun getContractViews(resourceType: Type?): [Type] {
252 return [
253 Type<MetadataViews.NFTCollectionData>(),
254 Type<MetadataViews.NFTCollectionDisplay>(),
255 Type<MetadataViews.EVMBridgedMetadata>()
256 ]
257 }
258
259 /// Function that resolves a metadata view for this contract.
260 ///
261 /// @param view: The Type of the desired view.
262 /// @return A structure representing the requested view.
263 ///
264 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
265 switch viewType {
266 case Type<MetadataViews.NFTCollectionData>():
267 let collectionData = MetadataViews.NFTCollectionData(
268 storagePath: self.CollectionStoragePath,
269 publicPath: self.CollectionPublicPath,
270 publicCollection: Type<&FooBar.Collection>(),
271 publicLinkedType: Type<&FooBar.Collection>(),
272 createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
273 return <-FooBar.createEmptyCollection(nftType: Type<@FooBar.NFT>())
274 })
275 )
276 return collectionData
277 case Type<MetadataViews.NFTCollectionDisplay>():
278 let media = MetadataViews.Media(
279 file: MetadataViews.HTTPFile(
280 url: "https://assets.website-files.com/5f6294c0c7a8cdd643b1c820/5f6294c0c7a8cda55cb1c936_Flow_Wordmark.svg"
281 ),
282 mediaType: "image/svg+xml"
283 )
284 return MetadataViews.NFTCollectionDisplay(
285 name: "The Example Collection",
286 description: "This collection is used as an example to help you develop your next Flow NFT.",
287 externalURL: MetadataViews.ExternalURL("https://example-nft.onflow.org"),
288 squareImage: media,
289 bannerImage: media,
290 socials: {
291 "twitter": MetadataViews.ExternalURL("https://twitter.com/flow_blockchain")
292 }
293 )
294 case Type<MetadataViews.EVMBridgedMetadata>():
295 // Implementing this view gives the project control over how the bridged NFT is represented as an ERC721
296 // when bridged to EVM on Flow via the public infrastructure bridge.
297
298 // Compose the contract-level URI. In this case, the contract metadata is located on some HTTP host,
299 // but it could be IPFS, S3, a data URL containing the JSON directly, etc.
300 return MetadataViews.EVMBridgedMetadata(
301 name: "FooBar",
302 symbol: "XMPL",
303 uri: MetadataViews.URI(
304 baseURI: nil, // setting baseURI as nil sets the given value as the uri field value
305 value: "https://example-nft.onflow.org/contract-metadata.json"
306 )
307 )
308 }
309 return nil
310 }
311
312 /// Resource that an admin or something similar would own to be
313 /// able to mint new NFTs
314 ///
315 access(all) resource NFTMinter {
316
317 /// mintNFT mints a new NFT with a new ID
318 /// and returns it to the calling context
319 access(all) fun mintNFT(
320 name: String,
321 description: String,
322 thumbnail: String,
323 royalties: [MetadataViews.Royalty]
324 ): @FooBar.NFT {
325
326 let metadata: {String: AnyStruct} = {}
327 let currentBlock = getCurrentBlock()
328 metadata["mintedBlock"] = currentBlock.height
329 metadata["mintedTime"] = currentBlock.timestamp
330
331 // this piece of metadata will be used to show embedding rarity into a trait
332 metadata["foo"] = "bar"
333
334 // create a new NFT
335 var newNFT <- create NFT(
336 name: name,
337 description: description,
338 thumbnail: thumbnail,
339 royalties: royalties,
340 metadata: metadata,
341 )
342
343 return <-newNFT
344 }
345 }
346
347 init() {
348
349 // Set the named paths
350 self.CollectionStoragePath = /storage/FooBarCollection
351 self.CollectionPublicPath = /public/FooBarCollection
352 self.MinterStoragePath = /storage/FooBarMinter
353
354 // Create a Collection resource and save it to storage
355 let collection <- create Collection()
356 self.account.storage.save(<-collection, to: self.CollectionStoragePath)
357
358 // create a public capability for the collection
359 let collectionCap = self.account.capabilities.storage.issue<&FooBar.Collection>(self.CollectionStoragePath)
360 self.account.capabilities.publish(collectionCap, at: self.CollectionPublicPath)
361
362 // Create a Minter resource and save it to storage
363 let minter <- create NFTMinter()
364 self.account.storage.save(<-minter, to: self.MinterStoragePath)
365 }
366}