Smart Contract
Art
A.d796ff17107bbff6.Art
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