Smart Contract
LeofyNFT
A.14af75b8c487333c.LeofyNFT
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