Smart Contract

LeofyNFT

A.14af75b8c487333c.LeofyNFT

Deployed

3d ago
Feb 25, 2026, 02:33:21 PM UTC

Dependents

2 imports
1/*
2    Description: Central Smart Contract for Leofy
3
4    This smart contract contains the core functionality for 
5    Leofy, created by LEOFY DIGITAL S.L.
6
7    The contract manages the data associated with all the items
8    that are used as templates for the NFTs
9
10    Then an Admin can create new Items. Items consist of a public struct that 
11    contains public information about a item, and a private resource used
12    to mint new NFT's linked to the Item.
13
14    The admin resource has the power to do all of the important actions
15    in the smart contract. When admins want to call functions in a Item,
16    they call their borrowItem function to get a reference 
17    to a item in the contract. Then, they can call functions on the item using that reference.
18    
19    When NFTs are minted, they are initialized with a ItemID and
20    are returned by the minter.
21
22    The contract also defines a Collection resource. This is an object that 
23    every Leofy NFT owner will store in their account
24    to manage their NFT collection.
25
26    The main Leofy account will also have its own NFT's collections
27    it can use to hold its own NFT's that have not yet been sent to a user.
28
29    Note: All state changing functions will panic if an invalid argument is
30    provided or one of its pre-conditions or post conditions aren't met.
31    Functions that don't modify state will simply return 0 or nil 
32    and those cases need to be handled by the caller.
33
34*/
35
36import NonFungibleToken from 0x1d7e57aa55817448
37import MetadataViews from 0x1d7e57aa55817448
38import FungibleToken from 0xf233dcee88fe0abe
39import LeofyCoin from 0x14af75b8c487333c
40
41pub contract LeofyNFT: NonFungibleToken {
42
43    // -----------------------------------------------------------------------
44    // Leofy contract Events
45    // -----------------------------------------------------------------------
46
47    // Emitted when the LeofyNFT contract is created
48    pub event ContractInitialized()
49
50    // Emitted when a new Item struct is created
51    pub event ItemCreated(id: UInt64, metadata: {String:String})
52    pub event SetCreated(id: UInt64, name: String)
53
54    pub event Withdraw(id: UInt64, from: Address?)
55    pub event Deposit(id: UInt64, to: Address?)
56    pub event Minted(id: UInt64, itemID: UInt64, serialNumber: UInt32)
57
58    // Named Paths
59    //
60    pub let ItemStoragePath: StoragePath
61    pub let ItemPublicPath: PublicPath
62
63    pub let CollectionStoragePath: StoragePath
64    pub let CollectionPublicPath: PublicPath
65    pub let AdminStoragePath: StoragePath
66
67    // -----------------------------------------------------------------------
68    // TopShot contract-level fields.
69    // These contain actual values that are stored in the smart contract.
70    // -----------------------------------------------------------------------
71
72    // Variable size dictionary of Item structs
73    //access(self) var items: @{UInt64: Item}
74
75    pub var totalSupply: UInt64
76    pub var totalItemSupply: UInt64
77
78    // -----------------------------------------------------------------------
79    // LeofyNFT contract-level Composite Type definitions
80    // -----------------------------------------------------------------------
81    // These are just *definitions* for Types that this contract
82    // and other accounts can use. These definitions do not contain
83    // actual stored values, but an instance (or object) of one of these Types
84    // can be created by this contract that contains stored values.
85    // -----------------------------------------------------------------------
86
87    
88    // Item is a Resource that holds metadata associated 
89    // with a specific Artist Item, like the picture from Artist John Doe
90    //
91    // Leofy NFTs will all reference a single item as the owner of
92    // its metadata. 
93    //
94
95    pub resource interface ItemCollectionPublic {
96        pub fun getIDs(): [UInt64]
97        pub fun getItemsLength(): Int
98        pub fun getItemMetaDataByField(itemID: UInt64, field: String): String?
99        pub fun borrowItem(itemID: UInt64): &Item{ItemPublic}?
100    }
101
102    pub resource ItemCollection: ItemCollectionPublic {
103        pub var items: @{UInt64: LeofyNFT.Item}
104
105        init () {
106            self.items <- {}
107        }
108
109        pub fun createItem(metadata: {String: String}, price: UFix64): UInt64 {
110
111            // Create the new Item
112            var newItem <- create Item(
113               metadata: metadata,
114               price: price
115            )
116            
117            let newID = newItem.itemID
118
119            // Store it in the contract storage
120            self.items[newID] <-! newItem
121            emit ItemCreated(id: LeofyNFT.totalItemSupply, metadata:metadata)
122
123            // Increment the ID so that it isn't used again
124            LeofyNFT.totalItemSupply = LeofyNFT.totalItemSupply + 1
125
126            return newID            
127        }
128
129        pub fun borrowItem(itemID: UInt64): &Item{ItemPublic}? {
130            pre {
131                self.items[itemID] != nil: "Cannot borrow Item: The Item doesn't exist"
132            }
133
134            return &self.items[itemID] as &Item{ItemPublic}?;
135        }
136
137        // getIDs returns an array of the IDs that are in the Item Collection
138        pub fun getIDs(): [UInt64] {
139            return self.items.keys
140        }
141
142        // getItemsLength 
143        // Returns: Int length of items created
144        pub fun getItemsLength(): Int {
145            return self.items.length
146        }
147
148        // getItemMetaDataByField returns the metadata associated with a 
149        //                        specific field of the metadata
150        //                        Ex: field: "Artist" will return something
151        //                        like "John Doe"
152        // 
153        // Parameters: itemID: The id of the Item that is being searched
154        //             field: The field to search for
155        //
156        // Returns: The metadata field as a String Optional
157        pub fun getItemMetaDataByField(itemID: UInt64, field: String): String? {
158            // Don't force a revert if the itemID or field is invalid
159            let item = (&self.items[itemID] as &Item?)!
160            return item.metadata[field]
161        }
162        
163        destroy(){
164            destroy self.items
165        }
166    }
167
168    pub resource interface ItemPublic{
169        pub let itemID: UInt64
170        pub var numberMinted: UInt32
171        pub var price: UFix64
172
173        pub fun getMetadata(): {String: String}
174        pub fun borrowCollection(): &LeofyNFT.Collection{LeofyCollectionPublic}
175        pub fun purchase(payment: @FungibleToken.Vault): @NonFungibleToken.NFT
176    }
177
178    pub resource Item: ItemPublic {
179
180        // The unique ID for the Item
181        pub let itemID: UInt64
182
183        // Stores all the metadata about the item as a string mapping
184        // This is not the long term way NFT metadata will be stored. It's a temporary
185        // construct while we figure out a better way to do metadata.
186        //
187        access(contract) let metadata: {String: String}
188
189        pub var numberMinted: UInt32
190
191        pub var NFTsCollection: @LeofyNFT.Collection
192
193        pub var price: UFix64
194
195        init(metadata: {String: String}, price: UFix64) {
196            pre {
197                metadata.length != 0: "New Item metadata cannot be empty"
198            }
199            self.itemID = LeofyNFT.totalItemSupply
200            self.metadata = metadata
201            self.price = price
202            self.numberMinted = 0
203            self.NFTsCollection <- create Collection()
204        }
205
206        pub fun mintNFT() {
207            // create a new NFT
208            var newNFT <- create NFT(
209                id: LeofyNFT.totalSupply,
210                itemID: self.itemID,
211                serialNumber: self.numberMinted + 1
212            )
213
214            // deposit it in the recipient's account using their reference
215            self.NFTsCollection.deposit(token: <-newNFT)
216
217            emit Minted(id: LeofyNFT.totalSupply, itemID: self.itemID, serialNumber: self.numberMinted + 1)
218
219            self.numberMinted =  self.numberMinted + 1
220            LeofyNFT.totalSupply = LeofyNFT.totalSupply + 1
221        }
222
223        pub fun getMetadata(): {String: String} {
224            return self.metadata
225        }
226
227        pub fun batchMintNFT(quantity: UInt64){
228            var i: UInt64 = 0
229            while i < quantity {
230                self.mintNFT()
231                i = i + 1;
232            }
233        }
234
235        pub fun setPrice(price: UFix64) {
236            self.price = price
237        }
238
239        pub fun borrowCollection(): &LeofyNFT.Collection{LeofyCollectionPublic} {
240            return &self.NFTsCollection as &Collection{LeofyCollectionPublic}
241        }
242
243        pub fun purchase(payment: @FungibleToken.Vault): @NonFungibleToken.NFT {
244            pre {
245                self.NFTsCollection.getIDs().length > 0: "listing has already been purchased"
246                payment.isInstance(Type<@LeofyCoin.Vault>()): "payment vault is not requested fungible token"
247                payment.balance == self.price: "payment vault does not contain requested price"
248            }
249
250            let nft <- self.NFTsCollection.withdraw(withdrawID: self.NFTsCollection.getIDs()[0])
251            let vault = LeofyNFT.getLeofyCoinVault()
252            vault.deposit(from: <- payment)
253            
254            return <- nft
255        }
256
257        destroy() {
258            destroy self.NFTsCollection
259        }
260    }
261
262    // This is an implementation of a custom metadata view for Leofy.
263    // This view contains the Item metadata.
264    //
265    pub struct LeofyNFTMetadataView {
266        pub let author: String
267        pub let name: String
268        pub let description: String
269        pub let thumbnail: String
270        pub let itemID: UInt64
271        pub let serialNumber: UInt32
272
273        init(
274            author: String,
275            name: String,
276            description: String,
277            thumbnail: AnyStruct{MetadataViews.File},
278            itemID: UInt64,
279            serialNumber: UInt32
280        ){
281            self.author = author
282            self.name = name
283            self.description = description
284            self.thumbnail = thumbnail.uri()
285            self.itemID = itemID
286            self.serialNumber = serialNumber
287        }
288    }
289
290    pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver  {
291        pub let id: UInt64
292        pub let itemID: UInt64
293        pub let serialNumber: UInt32
294
295        init(
296            id: UInt64,
297            itemID: UInt64,
298            serialNumber: UInt32
299        ) {
300            self.id = id
301            self.itemID = itemID
302            self.serialNumber = serialNumber
303        }
304
305        pub fun description(): String {
306            let itemCollection = LeofyNFT.getItemCollectionPublic()
307            return "NFT: '"
308                .concat(itemCollection.getItemMetaDataByField(itemID: self.itemID, field: "name") ?? "''")
309                .concat("' from Author: '")
310                .concat(itemCollection.getItemMetaDataByField(itemID: self.itemID, field: "author") ?? "''")
311                .concat("' with serial number ")
312                .concat(self.serialNumber.toString())
313        }
314
315        pub fun getViews(): [Type] {
316            return [
317                Type<MetadataViews.Display>(),
318                Type<LeofyNFTMetadataView>()
319            ]
320        }
321
322        pub fun resolveView(_ view: Type): AnyStruct? {
323            let itemCollection = LeofyNFT.getItemCollectionPublic()
324            switch view {
325                case Type<MetadataViews.Display>():
326                    return MetadataViews.Display(
327                        name: itemCollection.getItemMetaDataByField(itemID: self.itemID, field: "name") ?? "",
328                        description: self.description(),
329                        thumbnail: MetadataViews.HTTPFile(itemCollection.getItemMetaDataByField(itemID: self.itemID, field: "thumbnail") ?? "")
330                    )
331                case Type<LeofyNFTMetadataView>():
332                    return LeofyNFTMetadataView(
333                        author: itemCollection.getItemMetaDataByField(itemID: self.itemID, field: "author") ?? "", 
334                        name: itemCollection.getItemMetaDataByField(itemID: self.itemID, field: "name") ?? "",
335                        description: self.description(),
336                        thumbnail: MetadataViews.HTTPFile(itemCollection.getItemMetaDataByField(itemID: self.itemID, field: "thumbnail") ?? ""),
337                        itemID: self.itemID,
338                        serialNumber: self.serialNumber
339                    )
340            }
341
342            return nil
343        }
344    }
345
346    pub resource interface LeofyCollectionPublic {
347        pub fun deposit(token: @NonFungibleToken.NFT)
348        pub fun getIDs(): [UInt64]
349        pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
350        pub fun borrowLeofyNFT(id: UInt64): &LeofyNFT.NFT? {
351            post {
352                (result == nil) || (result?.id == id):
353                    "Cannot borrow LeofyNFT reference: the ID of the returned reference is incorrect"
354            }
355        }
356    }
357
358    pub resource Collection: LeofyCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection {
359        // dictionary of NFT conforming tokens
360        // NFT is a resource type with an `UInt64` ID field
361        pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
362
363        init () {
364            self.ownedNFTs <- {}
365        }
366
367        // withdraw removes an NFT from the collection and moves it to the caller
368        pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
369            let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT: ".concat(withdrawID.toString()))
370
371            emit Withdraw(id: token.id, from: self.owner?.address)
372
373            return <-token
374        }
375
376        // deposit takes a NFT and adds it to the collections dictionary
377        // and adds the ID to the id array
378        pub fun deposit(token: @NonFungibleToken.NFT) {
379            let token <- token as! @LeofyNFT.NFT
380
381            let id: UInt64 = token.id
382
383            // add the new token to the dictionary which removes the old one
384            let oldToken <- self.ownedNFTs[id] <- token
385
386            emit Deposit(id: id, to: self.owner?.address)
387
388            destroy oldToken
389        }
390
391        // getIDs returns an array of the IDs that are in the collection
392        pub fun getIDs(): [UInt64] {
393            return self.ownedNFTs.keys
394        }
395
396        // borrowNFT gets a reference to an NFT in the collection
397        // so that the caller can read its metadata and call its methods
398        pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
399            return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
400        }
401
402        pub fun borrowLeofyNFT(id: UInt64): &LeofyNFT.NFT? {
403            if self.ownedNFTs[id] != nil {
404                // Create an authorized reference to allow downcasting
405                let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
406                return ref as! &LeofyNFT.NFT
407            }
408
409            return nil
410        }
411
412        pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} {
413            let nft = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
414            return nft as! &LeofyNFT.NFT
415        }
416
417        destroy() {
418            destroy self.ownedNFTs
419        }
420    }
421
422    // -----------------------------------------------------------------------
423    // LeofyNFT contract-level function definitions
424    // -----------------------------------------------------------------------
425
426    // public function that anyone can call to create a new empty collection
427    pub fun createEmptyCollection(): @LeofyNFT.Collection {
428        return <- create Collection()
429    }
430
431    pub fun getItemCollectionPublic(): &AnyResource{LeofyNFT.ItemCollectionPublic} {
432        return self.account.getCapability(LeofyNFT.ItemPublicPath)
433        .borrow<&{LeofyNFT.ItemCollectionPublic}>()
434        ?? panic("Could not borrow capability from public Item Collection")
435    }
436
437    pub fun getLeofyCoinVault(): &AnyResource{FungibleToken.Receiver} {
438        return self.account.getCapability(LeofyCoin.ReceiverPublicPath)!.borrow<&{FungibleToken.Receiver}>()
439			?? panic("Could not borrow receiver reference to the recipient's Vault")
440    } 
441
442    // -----------------------------------------------------------------------
443    // LeofyNFT initialization function
444    // -----------------------------------------------------------------------
445
446    init() {
447        self.ItemStoragePath = /storage/LeofyItemCollection
448        self.ItemPublicPath = /public/LeofyItemCollection
449
450        self.CollectionStoragePath = /storage/LeofyNFTCollection
451        self.CollectionPublicPath = /public/LeofyNFTCollection
452        self.AdminStoragePath = /storage/LeofyNFTMinter
453
454        // Initialize the total supply
455        self.totalSupply = 0
456        self.totalItemSupply = 0
457
458        destroy self.account.load<@ItemCollection>(from: self.ItemStoragePath)
459        // create a public capability for the Item collection
460        self.account.save(<-create ItemCollection(), to: self.ItemStoragePath)
461        self.account.link<&LeofyNFT.ItemCollection{LeofyNFT.ItemCollectionPublic}>(
462            self.ItemPublicPath,
463            target: self.ItemStoragePath
464        )
465
466        // Create a Collection resource and save it to storage
467        destroy self.account.load<@Collection>(from: self.CollectionStoragePath)
468
469        let collection <- create Collection()
470        self.account.save(<-collection, to: self.CollectionStoragePath)
471
472        // create a public capability for the collection
473        self.account.link<&LeofyNFT.Collection{NonFungibleToken.CollectionPublic, LeofyNFT.LeofyCollectionPublic}>(
474            self.CollectionPublicPath,
475            target: self.CollectionStoragePath
476        )
477        
478        emit ContractInitialized()
479    }
480}
481