Smart Contract
FlowtyTestNFT
A.4aec40272c01a94e.FlowtyTestNFT
1/*
2*
3* This is an example implementation of a Flow Non-Fungible Token
4* It is not part of the official standard but it assumed to be
5* similar to how many NFTs would implement the core functionality.
6*
7* This contract does not implement any sophisticated classification
8* system for its NFTs. It defines a simple NFT with minimal metadata.
9*
10*/
11
12import NonFungibleToken from 0x1d7e57aa55817448
13import MetadataViews from 0x1d7e57aa55817448
14import FungibleToken from 0xf233dcee88fe0abe
15import ViewResolver from 0x1d7e57aa55817448
16
17access(all) contract FlowtyTestNFT: ViewResolver, NonFungibleToken {
18
19 access(all) var totalSupply: UInt64
20
21 access(all) event ContractInitialized()
22 access(all) event Withdraw(id: UInt64, from: Address?)
23 access(all) event Deposit(id: UInt64, to: Address?)
24
25 access(all) event CollectionCreated(id: UInt64)
26 access(all) event CollectionDestroyed(id: UInt64)
27
28 access(all) let CollectionStoragePath: StoragePath
29 access(all) let CollectionPublicPath: PublicPath
30 access(all) let MinterStoragePath: StoragePath
31 access(all) let MinterPublicPath: PublicPath
32
33 access(all) resource NFT: NonFungibleToken.NFT {
34 access(all) let id: UInt64
35
36 access(all) let name: String
37 access(all) let description: String
38 access(all) let thumbnail: String
39 access(all) let data: {String: AnyStruct}
40
41 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
42 return <- FlowtyTestNFT.createEmptyCollection(nftType: Type<@NFT>())
43 }
44
45 init(
46 id: UInt64,
47 name: String,
48 description: String,
49 thumbnail: String,
50 ) {
51 self.id = id
52 self.name = name
53 self.description = description
54 self.thumbnail = thumbnail
55 self.data = {
56 "name": name,
57 "createdOn": getCurrentBlock().timestamp
58 }
59 }
60
61 access(all) view fun getViews(): [Type] {
62 return [
63 Type<MetadataViews.Display>(),
64 Type<MetadataViews.Royalties>(),
65 Type<MetadataViews.Editions>(),
66 Type<MetadataViews.ExternalURL>(),
67 Type<MetadataViews.NFTCollectionData>(),
68 Type<MetadataViews.NFTCollectionDisplay>(),
69 Type<MetadataViews.Serial>(),
70 Type<MetadataViews.Traits>()
71 ]
72 }
73
74 access(all) view fun resolveView(_ view: Type): AnyStruct? {
75 switch view {
76 case Type<MetadataViews.Display>():
77 return MetadataViews.Display(
78 name: self.name,
79 description: self.description,
80 thumbnail: MetadataViews.HTTPFile(
81 url: self.thumbnail
82 )
83 )
84 case Type<MetadataViews.Editions>():
85 let editionName = self.id % 2 == 0 ? "Evens" : "Odds"
86 let editionInfo = MetadataViews.Edition(name: editionName, number: self.id, max: nil)
87 let editionList: [MetadataViews.Edition] = [editionInfo]
88 return MetadataViews.Editions(
89 editionList
90 )
91 case Type<MetadataViews.Serial>():
92 return MetadataViews.Serial(
93 self.id
94 )
95 case Type<MetadataViews.Royalties>():
96 let royaltyRecipient = getAccount(FlowtyTestNFT.account.address).capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)!
97 let cutInfo = MetadataViews.Royalty(receiver: royaltyRecipient, cut: 0.0, description: "")
98
99 return MetadataViews.Royalties([cutInfo])
100 case Type<MetadataViews.ExternalURL>():
101 return MetadataViews.ExternalURL("https://flowty.io/".concat(self.id.toString()))
102 case Type<MetadataViews.NFTCollectionData>():
103 return MetadataViews.NFTCollectionData(
104 storagePath: FlowtyTestNFT.CollectionStoragePath,
105 publicPath: FlowtyTestNFT.CollectionPublicPath,
106 publicCollection: Type<&{FlowtyTestNFT.FlowtyTestNFTCollectionPublic}>(),
107 publicLinkedType: Type<&{FlowtyTestNFT.FlowtyTestNFTCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Receiver,ViewResolver.ResolverCollection}>(),
108 createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
109 return <-FlowtyTestNFT.createEmptyCollection(nftType: Type<@NFT>())
110 })
111 )
112 case Type<MetadataViews.NFTCollectionDisplay>():
113 return MetadataViews.NFTCollectionDisplay(
114 name: "Flowty Test NFT Collection",
115 description: "This collection is used for testing things out on flowty.",
116 externalURL: MetadataViews.ExternalURL("https://flowty.io/"),
117 squareImage: MetadataViews.Media(
118 file: MetadataViews.HTTPFile(
119 url: "https://storage.googleapis.com/flowty-images/flowty-logo.jpeg"
120 ),
121 mediaType: "image/jpeg"
122 ),
123 bannerImage: MetadataViews.Media(
124 file: MetadataViews.HTTPFile(
125 url: "https://storage.googleapis.com/flowty-images/flowty-banner.jpeg"
126 ),
127 mediaType: "image/jpeg"
128 ),
129 socials: {
130 "twitter": MetadataViews.ExternalURL("https://twitter.com/flowty_io")
131 }
132 )
133 case Type<MetadataViews.Traits>():
134 return FlowtyTestNFT.dictToTraits(dict: self.data, excludedNames: nil)
135 }
136 return nil
137 }
138 }
139
140 access(all) resource interface FlowtyTestNFTCollectionPublic: NonFungibleToken.Collection {
141 access(all) fun deposit(token: @{NonFungibleToken.NFT})
142 access(all) view fun borrowFlowtyTestNFT(id: UInt64): &FlowtyTestNFT.NFT? {
143 post {
144 (result == nil) || (result?.id == id):
145 "Cannot borrow FlowtyTestNFT reference: the ID of the returned reference is incorrect"
146 }
147 }
148 }
149
150 access(all) resource Collection: FlowtyTestNFTCollectionPublic {
151 // dictionary of NFT conforming tokens
152 // NFT is a resource type with an `UInt64` ID field
153 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
154
155 init () {
156 self.ownedNFTs <- {}
157 emit CollectionCreated(id: self.uuid)
158 }
159
160 access(all) view fun getLength(): Int {
161 return self.ownedNFTs.length
162 }
163
164 // withdraw removes an NFT from the collection and moves it to the caller
165 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
166 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
167
168 emit Withdraw(id: token.id, from: self.owner?.address)
169
170 return <-token
171 }
172
173 // deposit takes a NFT and adds it to the collections dictionary
174 // and adds the ID to the id array
175 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
176 let token <- token as! @FlowtyTestNFT.NFT
177
178 let id: UInt64 = token.id
179
180 // add the new token to the dictionary which removes the old one
181 let oldToken <- self.ownedNFTs[id] <- token
182
183 emit Deposit(id: id, to: self.owner?.address)
184
185 destroy oldToken
186 }
187
188 // getIDs returns an array of the IDs that are in the collection
189 access(all) view fun getIDs(): [UInt64] {
190 return self.ownedNFTs.keys
191 }
192
193 // borrowNFT gets a reference to an NFT in the collection
194 // so that the caller can read its metadata and call its methods
195 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
196 return &self.ownedNFTs[id]
197 }
198
199 access(all) view fun borrowFlowtyTestNFT(id: UInt64): &FlowtyTestNFT.NFT? {
200 if self.ownedNFTs[id] != nil {
201 // Create an authorized reference to allow downcasting
202 let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
203 return ref as! &FlowtyTestNFT.NFT
204 }
205
206 return nil
207 }
208
209 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver} {
210 let nft = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
211 let FlowtyTestNFT = nft as! &FlowtyTestNFT.NFT
212 return FlowtyTestNFT
213 }
214
215 access(contract) fun burnCallback() {
216 emit CollectionDestroyed(id: self.uuid)
217 }
218
219 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
220 return {
221 Type<@FlowtyTestNFT.NFT>(): true
222 }
223 }
224
225 access(all) view fun isSupportedNFTType(type: Type): Bool {
226 return type == Type<@FlowtyTestNFT.NFT>()
227 }
228
229 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
230 return <- FlowtyTestNFT.createEmptyCollection(nftType: Type<@NFT>())
231 }
232 }
233
234 // public function that anyone can call to create a new empty collection
235 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
236 return <- create Collection()
237 }
238
239 // Resource that an admin or something similar would own to be
240 // able to mint new NFTs
241 //
242 access(all) resource NFTMinter {
243
244 // mintNFT mints a new NFT with a new ID
245 // and deposit it in the recipients collection using their collection reference
246 access(all) fun mintNFT(
247 recipient: &{NonFungibleToken.CollectionPublic},
248 ) {
249 let royaltyRecipient = getAccount(FlowtyTestNFT.account.address).capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)!
250 let cutInfo = MetadataViews.Royalty(receiver: royaltyRecipient, cut: 0.0, description: "")
251
252 FlowtyTestNFT.totalSupply = FlowtyTestNFT.totalSupply + 1
253
254 let thumbnail = "https://storage.googleapis.com/flowty-images/flowty-logo.jpeg"
255 let name = "Flowty Test NFT #".concat(FlowtyTestNFT.totalSupply.toString())
256 let description = "This nft is used for testing things out on flowty."
257
258 // create a new NFT
259 var newNFT <- create NFT(
260 id: FlowtyTestNFT.totalSupply,
261 name: name,
262 description: description,
263 thumbnail: thumbnail
264 )
265
266 // deposit it in the recipient's account using their reference
267 recipient.deposit(token: <-newNFT)
268 }
269 }
270
271 access(all) view fun getContractViews(resourceType: Type?): [Type] {
272 return [
273 Type<MetadataViews.ExternalURL>(),
274 Type<MetadataViews.NFTCollectionDisplay>(),
275 Type<MetadataViews.NFTCollectionData>()
276 ]
277 }
278
279 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
280 switch viewType {
281 case Type<MetadataViews.ExternalURL>():
282 return MetadataViews.ExternalURL("https://flowty.io/")
283 case Type<MetadataViews.NFTCollectionDisplay>():
284 return MetadataViews.NFTCollectionDisplay(
285 name: "Flowty Test NFT Collection",
286 description: "This collection is used for testing things out on flowty.",
287 externalURL: MetadataViews.ExternalURL("https://flowty.io/"),
288 squareImage: MetadataViews.Media(
289 file: MetadataViews.HTTPFile(
290 url: "https://storage.googleapis.com/flowty-images/flowty-logo.jpeg"
291 ),
292 mediaType: "image/jpeg"
293 ),
294 bannerImage: MetadataViews.Media(
295 file: MetadataViews.HTTPFile(
296 url: "https://storage.googleapis.com/flowty-images/flowty-banner.jpeg"
297 ),
298 mediaType: "image/jpeg"
299 ),
300 socials: {
301 "twitter": MetadataViews.ExternalURL("https://twitter.com/flowty_io")
302 }
303 )
304 case Type<MetadataViews.NFTCollectionData>():
305 return MetadataViews.NFTCollectionData(
306 storagePath: FlowtyTestNFT.CollectionStoragePath,
307 publicPath: FlowtyTestNFT.CollectionPublicPath,
308 publicCollection: Type<&{FlowtyTestNFT.FlowtyTestNFTCollectionPublic}>(),
309 publicLinkedType: Type<&{FlowtyTestNFT.FlowtyTestNFTCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Receiver,ViewResolver.ResolverCollection}>(),
310 createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
311 return <-FlowtyTestNFT.createEmptyCollection(nftType: Type<@NFT>())
312 })
313 )
314 }
315
316 return nil
317 }
318
319 access(all) view fun dictToTraits(dict: {String: AnyStruct}, excludedNames: [String]?): MetadataViews.Traits {
320 let copy: {String: AnyStruct} = {}
321 let excluded: {String: Bool} = {}
322
323 // Collection owners might not want all the fields in their metadata included.
324 // They might want to handle some specially, or they might just not want them included at all.
325 if excludedNames != nil {
326 for k in excludedNames! {
327 excluded[k] = true
328 }
329 }
330
331 let traits: [MetadataViews.Trait] = []
332 var count = 0
333 for k in dict.keys {
334 if excluded[k] == true {
335 continue
336 }
337
338 let trait = MetadataViews.Trait(name: k, value: dict[k]!, displayType: nil, rarity: nil)
339 traits.concat([trait])
340 }
341
342 return MetadataViews.Traits(traits)
343 }
344
345 init() {
346 // Initialize the total supply
347 self.totalSupply = 0
348
349 // Set the named paths
350 self.CollectionStoragePath = /storage/FlowtyTestNFTCollection
351 self.CollectionPublicPath = /public/FlowtyTestNFTCollection
352 self.MinterStoragePath = /storage/FlowtyTestNFTMinter
353 self.MinterPublicPath = /public/FlowtyTestNFTMinter
354
355 // Create a Collection resource and save it to storage
356 let collection <- create Collection()
357 self.account.storage.save(<-collection, to: self.CollectionStoragePath)
358
359 let cap = self.account.capabilities.storage.issue<&{NonFungibleToken.CollectionPublic, FlowtyTestNFT.FlowtyTestNFTCollectionPublic, ViewResolver.ResolverCollection}>(self.CollectionStoragePath)
360 self.account.capabilities.publish(cap, 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 let minterCap = self.account.capabilities.storage.issue<&NFTMinter>(self.MinterStoragePath)
367 self.account.capabilities.publish(minterCap, at: self.MinterPublicPath)
368
369 emit ContractInitialized()
370 }
371}
372