Smart Contract

FlowtyTestNFT

A.4aec40272c01a94e.FlowtyTestNFT

Valid From

84,148,047

Deployed

21h ago
Feb 27, 2026, 09:02:21 PM UTC

Dependents

0 imports
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