Smart Contract

Art

A.d796ff17107bbff6.Art

Deployed

4h ago
Mar 05, 2026, 08:27:48 PM UTC

Dependents

0 imports
1import NonFungibleToken from 0x1d7e57aa55817448
2import ViewResolver from 0x1d7e57aa55817448
3import FungibleToken from 0xf233dcee88fe0abe
4import MetadataViews from 0x1d7e57aa55817448
5import Content from 0xd796ff17107bbff6
6
7/// A NFT contract to store art
8access(all)
9contract Art: NonFungibleToken{ 
10
11
12    access(all)
13    let CollectionStoragePath: StoragePath
14
15    access(all)
16    let CollectionPublicPath: PublicPath
17
18    access(all)
19    var totalSupply: UInt64
20
21    access(all)
22    event ContractInitialized()
23
24    access(all)
25    event Created(id: UInt64, metadata: Metadata)
26
27    access(all)
28    event Editioned(id: UInt64, from: UInt64, edition: UInt64, maxEdition: UInt64)
29
30    //The public interface can show metadata and the content for the Art piece
31    access(all)
32    resource interface Public{ 
33        access(all)
34        let id: UInt64
35
36        access(all)
37        let metadata: Metadata
38
39        //these three are added because I think they will be in the standard. Atleast dieter thinks it will be needed
40        access(all)
41        let name: String
42
43        access(all)
44        let description: String
45
46        access(all)
47        let schema: String?
48
49        access(all)
50        fun content(): String?
51
52        access(account)
53        let royalty:{ String: Royalty}
54
55        access(all)
56        view fun cacheKey(): String
57
58        access(all)
59        view fun getMetadata(): Metadata
60
61    }
62
63
64
65    access(all)
66    struct Metadata{ 
67        access(all)
68        let name: String
69
70        access(all)
71        let artist: String
72
73        access(all)
74        let artistAddress: Address
75
76        access(all)
77        let description: String
78
79        access(all)
80        let type: String
81
82        access(all)
83        let edition: UInt64
84
85        access(all)
86        let maxEdition: UInt64
87
88        init(name: String, artist: String, artistAddress: Address, description: String, type: String, edition: UInt64, maxEdition: UInt64){ 
89            self.name = name
90            self.artist = artist
91            self.artistAddress = artistAddress
92            self.description = description
93            self.type = type
94            self.edition = edition
95            self.maxEdition = maxEdition
96        }
97    }
98
99    access(all)
100    struct Royalty{ 
101        access(all)
102        let wallet: Capability<&{FungibleToken.Receiver}>
103
104        access(all)
105        let cut: UFix64
106
107        /// @param wallet : The wallet to send royalty too
108        init(wallet: Capability<&{FungibleToken.Receiver}>, cut: UFix64){ 
109            self.wallet = wallet
110            self.cut = cut
111        }
112    }
113
114    access(all)
115    resource NFT: NonFungibleToken.NFT, Public, ViewResolver.Resolver{ 
116        access(all)
117        let id: UInt64
118
119        access(all)
120        let name: String
121
122        access(all)
123        let description: String
124
125        access(all)
126        let schema: String?
127
128        //content can either be embedded in the NFT as and URL or a pointer to a Content collection to be stored onChain
129        //a pointer will be used for all editions of the same Art when it is editioned 
130        access(all)
131        let contentCapability: Capability<&Content.Collection>?
132
133        access(all)
134        let contentId: UInt64?
135
136        access(all)
137        let url: String?
138
139        access(all)
140        let metadata: Metadata
141
142        access(account)
143        let royalty:{ String: Royalty}
144
145        init(initID: UInt64, metadata: Metadata, contentCapability: Capability<&Content.Collection>?, contentId: UInt64?, url: String?, royalty:{ String: Royalty}){ 
146            self.id = initID
147            self.metadata = metadata
148            self.contentCapability = contentCapability
149            self.contentId = contentId
150            self.url = url
151            self.royalty = royalty
152            self.schema = nil
153            self.name = metadata.name
154            self.description = metadata.description
155        }
156
157        access(all) 
158        view fun getMetadata(): Metadata{ 
159            return self.metadata
160        }
161
162        access(all) 
163        view fun getRoyalty(): {String: Royalty}{ 
164            return self.royalty
165        }
166
167        access(all) 
168        view fun cacheKey(): String{ 
169            if self.url != nil{ 
170                return self.url!
171            }
172            return (self.contentId!).toString()
173        }
174
175        //return the content for this NFT
176        access(all)
177        fun content(): String{ 
178            if self.url != nil{ 
179                return self.url!
180            }
181            let contentCollection = (self.contentCapability!).borrow()!
182            return contentCollection.content(self.contentId!)
183        }
184
185        access(all)
186        view fun getViews(): [Type]{ 
187            var views: [Type] = [
188            Type<MetadataViews.NFTCollectionData>(),
189            Type<MetadataViews.NFTCollectionDisplay>(),
190            Type<MetadataViews.Display>(),
191            Type<MetadataViews.Royalties>(),
192            Type<MetadataViews.Edition>(),
193            Type<MetadataViews.ExternalURL>()]
194            return views
195        }
196
197        access(all)
198        view fun resolveView(_ type: Type): AnyStruct?{ 
199            if type == Type<MetadataViews.ExternalURL>(){ 
200                return MetadataViews.ExternalURL("https://www.versus.auction/piece/".concat((self.owner!).address.toString()).concat("/").concat(self.id.toString()))
201            }
202            if type == Type<MetadataViews.NFTCollectionDisplay>(){ 
203                return Art.resolveContractView(resourceType: Type<@NFT>(), viewType: type)
204            }
205            if type == Type<MetadataViews.NFTCollectionData>(){ 
206                return Art.resolveContractView(resourceType: Type<@NFT>(), viewType:type)
207            }
208            if type == Type<MetadataViews.Royalties>(){ 
209                var royalties: [MetadataViews.Royalty] = []
210                for royaltyKey in self.royalty.keys{ 
211                    let value = self.royalty[royaltyKey]!
212                    royalties=royalties.concat([MetadataViews.Royalty(receiver: value.wallet, cut: value.cut, description: royaltyKey)])
213                }
214                return MetadataViews.Royalties(royalties)
215            }
216            if type == Type<MetadataViews.Display>(){ 
217                return MetadataViews.Display(name: self.name, description: self.description, thumbnail: MetadataViews.HTTPFile(url: "https://res.cloudinary.com/dxra4agvf/image/upload/c_fill,w_200/f_auto/maincache".concat(self.cacheKey())))
218            }
219            if type == Type<MetadataViews.Edition>(){ 
220                return MetadataViews.Edition(name: nil, number: self.metadata.edition, max: self.metadata.maxEdition)
221            }
222            return nil
223        }
224
225        access(all)
226        fun createEmptyCollection(): @{NonFungibleToken.Collection}{ 
227            return <-create Collection()
228        }
229    }
230
231    //Standard NFT collectionPublic interface that can also borrowArt as the correct type
232    access(all)
233    resource interface CollectionPublic{ 
234        access(all)
235        fun deposit(token: @{NonFungibleToken.NFT})
236
237        access(all)
238        fun getIDs(): [UInt64]
239
240        access(all)
241        view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
242
243        access(all)
244        fun borrowArt(id: UInt64): &{Art.Public}?
245    }
246
247    access(all)
248    resource Collection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.Collection, NonFungibleToken.CollectionPublic, ViewResolver.ResolverCollection{ 
249        // dictionary of NFT conforming tokens
250        // NFT is a resource type with an `UInt64` ID field
251        access(all)
252        var ownedNFTs: @{UInt64:{NonFungibleToken.NFT}}
253
254        init(){ 
255            self.ownedNFTs <-{} 
256        }
257
258        // used after settlement to burn remaining art that was not sold
259        access(account)
260        fun burnAll(){ 
261            for key in self.ownedNFTs.keys{ 
262                log("burning art with key=".concat(key.toString()))
263                destroy <-self.ownedNFTs.remove(key: key)
264            }
265        }
266
267        // withdraw removes an NFT from the collection and moves it to the caller
268        access(NonFungibleToken.Withdraw)
269        fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT}{ 
270            let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
271            return <-token
272        }
273
274        // deposit takes a NFT and adds it to the collections dictionary
275        // and adds the ID to the id array
276        access(all)
277        fun deposit(token: @{NonFungibleToken.NFT}){ 
278            let token <- token as! @Art.NFT
279            let id: UInt64 = token.id
280
281            // add the new token to the dictionary which removes the old one
282            let oldToken <- self.ownedNFTs[id] <- token
283            destroy oldToken
284        }
285
286        // getIDs returns an array of the IDs that are in the collection
287        access(all)
288        view fun getIDs(): [UInt64]{ 
289            return self.ownedNFTs.keys
290        }
291
292        // borrowNFT gets a reference to an NFT in the collection
293        // so that the caller can read its metadata and call its methods
294        access(all)
295        view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?{ 
296            return &self.ownedNFTs[id]
297        }
298
299        // borrowArt returns a borrowed reference to a Art 
300        // so that the caller can read data and call methods from it.
301        //
302        // Parameters: id: The ID of the NFT to get the reference for
303        //
304        // Returns: A reference to the NFT
305        access(all)
306        fun borrowArt(id: UInt64): &{Art.Public}? {  
307
308            if self.ownedNFTs[id] != nil{ 
309                let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
310                return ref as! &Art.NFT
311            } else{ 
312                return nil
313            }
314        }
315
316        access(all)
317        view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}?{ 
318            pre{ 
319                self.ownedNFTs[id] != nil:
320                "NFT does not exist"
321            }
322
323            return &self.ownedNFTs[id]
324        }
325
326        access(all)
327        fun createEmptyCollection(): @{NonFungibleToken.Collection}{ 
328            return <-create Collection()
329        }
330
331
332        access(all) view fun getIDsWithTypes(): {Type: [UInt64]} {
333            return { Type<@NFT>() : self.ownedNFTs.keys}
334        }
335
336        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
337            return { Type<@NFT>() : true}
338        }
339
340        access(all) view fun getLength() : Int {
341            return self.ownedNFTs.length
342        }
343
344        access(all) view fun isSupportedNFTType(type: Type) : Bool {
345            return type == Type<@NFT>()
346        }
347
348
349    }
350
351    // public function that anyone can call to create a new empty collection
352    access(all)
353    fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection}{ 
354        return <-create Collection()
355    }
356
357    access(all)
358    struct ArtData{ 
359        access(all)
360        let metadata: Art.Metadata
361
362        access(all)
363        let id: UInt64
364
365        access(all)
366        let cacheKey: String
367
368        init(metadata: Art.Metadata, id: UInt64, cacheKey: String){ 
369            self.metadata = metadata
370            self.id = id
371            self.cacheKey = cacheKey
372        }
373    }
374
375    access(all)
376    fun getContentForArt(address: Address, artId: UInt64): String?{ 
377        let account = getAccount(address)
378        if let artCollection = account.capabilities.borrow<&{Art.CollectionPublic}>(self.CollectionPublicPath) { 
379            return (artCollection.borrowArt(id: artId)!).content()
380        }
381        return nil
382    }
383
384    // We cannot return the content here since it will be too big to run in a script
385    access(all)
386    fun getArt(address: Address): [ArtData]{ 
387        var artData: [ArtData] = []
388        let account = getAccount(address)
389        if let artCollection = account.capabilities.borrow<&{Art.CollectionPublic}>(self.CollectionPublicPath){ 
390            for id in artCollection.getIDs(){ 
391                var art = artCollection.borrowArt(id: id)!
392                artData.append(ArtData(metadata: art.getMetadata(), id: id, cacheKey: art.cacheKey()))
393            }
394        }
395        return artData
396    }
397
398    //This method can only be called from another contract in the same account. In Versus case it is called from the VersusAdmin that is used to administer the solution
399    access(account)
400    fun createArtWithContent(name: String, artist: String, artistAddress: Address, description: String, url: String, type: String, royalty:{ String: Royalty}, edition: UInt64, maxEdition: UInt64): @Art.NFT{ 
401        var newNFT <- create NFT(initID: Art.totalSupply, metadata: Metadata(name: name, artist: artist, artistAddress: artistAddress, description: description, type: type, edition: edition, maxEdition: maxEdition), contentCapability: nil, contentId: nil, url: url, royalty: royalty)
402        emit Created(id: Art.totalSupply, metadata: newNFT.metadata)
403        Art.totalSupply = Art.totalSupply + 1
404        return <-newNFT
405    }
406
407    //This method can only be called from another contract in the same account. In Versus case it is called from the VersusAdmin that is used to administer the solution
408    access(account)
409    fun createArtWithPointer(name: String, artist: String, artistAddress: Address, description: String, type: String, contentCapability: Capability<&Content.Collection>, contentId: UInt64, royalty:{ String: Royalty}): @Art.NFT{ 
410        let metadata = Metadata(name: name, artist: artist, artistAddress: artistAddress, description: description, type: type, edition: 1, maxEdition: 1)
411        var newNFT <- create NFT(initID: Art.totalSupply, metadata: metadata, contentCapability: contentCapability, contentId: contentId, url: nil, royalty: royalty)
412        emit Created(id: Art.totalSupply, metadata: newNFT.metadata)
413        Art.totalSupply = Art.totalSupply + 1
414        return <-newNFT
415    }
416
417    //This method can only be called from another contract in the same account. In Versus case it is called from the VersusAdmin that is used to administer the solution
418    access(account)
419    fun makeEdition(original: &NFT, edition: UInt64, maxEdition: UInt64): @Art.NFT{ 
420        let metadata = Metadata(name: original.metadata.name, artist: original.metadata.artist, artistAddress: original.metadata.artistAddress, description: original.metadata.description, type: original.metadata.type, edition: edition, maxEdition: maxEdition)
421        var newNFT <- create NFT(initID: Art.totalSupply, metadata: metadata, contentCapability: original.contentCapability, contentId: original.contentId, url: original.url, royalty: original.getRoyalty())
422        emit Created(id: Art.totalSupply, metadata: newNFT.metadata)
423        emit Editioned(id: Art.totalSupply, from: original.id, edition: edition, maxEdition: maxEdition)
424        Art.totalSupply = Art.totalSupply + 1
425        return <-newNFT
426    }
427
428    access(all) view fun getContractViews(resourceType: Type?): [Type] {
429        return [
430        Type<MetadataViews.NFTCollectionData>(),
431        Type<MetadataViews.NFTCollectionDisplay>()
432        ]
433    }
434
435    access(all) view fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
436        switch viewType {
437        case Type<MetadataViews.NFTCollectionData>():
438            let collectionData= MetadataViews.NFTCollectionData(
439                storagePath: Art.CollectionStoragePath, 
440                publicPath: Art.CollectionPublicPath, 
441                publicCollection: Type<&Art.Collection>(), 
442                publicLinkedType: Type<&Art.Collection>(), 
443                createEmptyCollectionFunction: fun (): @{NonFungibleToken.Collection}{ 
444                    return <-Art.createEmptyCollection(nftType: Type<@Collection>())
445                }
446            )
447            return collectionData
448        case Type<MetadataViews.NFTCollectionDisplay>():
449            let externalURL = MetadataViews.ExternalURL("https://versus.auction")
450            let squareImage = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://pbs.twimg.com/profile_images/1295757455679528963/ibkAIRww_400x400.jpg"), mediaType: "image/jpeg")
451            let bannerImage = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://pbs.twimg.com/profile_images/1295757455679528963/ibkAIRww_400x400.jpg"), mediaType: "image/jpeg")
452            return MetadataViews.NFTCollectionDisplay(name: "Versus", description: "Curated auction house for fine art", externalURL: externalURL, squareImage: squareImage, bannerImage: bannerImage, socials:{ "twitter": MetadataViews.ExternalURL("https://twitter.com/FlowVersus")})
453
454        }
455        return nil
456    }
457
458
459    init(){ 
460        // Initialize the total supply
461        self.totalSupply = 0
462        self.CollectionPublicPath = /public/versusArtCollection
463        self.CollectionStoragePath = /storage/versusArtCollection
464        self.account.storage.save<@{NonFungibleToken.Collection}>(<-Art.createEmptyCollection(nftType: Type<@Collection>()), to: Art.CollectionStoragePath)
465        var _capForLinked1 = self.account.capabilities.storage.issue<&{Art.CollectionPublic}>(Art.CollectionStoragePath)
466        self.account.capabilities.publish(_capForLinked1, at: Art.CollectionPublicPath)
467        emit ContractInitialized()
468    }
469}
470