Smart Contract
FlowverseShirt
A.9212a87501a8a6a2.FlowverseShirt
1/*
2 FlowverseShirt.cdc
3
4 Author: Brian Min brian@flowverse.co
5*/
6
7import NonFungibleToken from 0x1d7e57aa55817448
8import MetadataViews from 0x1d7e57aa55817448
9import FungibleToken from 0xf233dcee88fe0abe
10import ViewResolver from 0x1d7e57aa55817448
11import FlowversePrimarySaleV2 from 0x9212a87501a8a6a2
12
13access(all) contract FlowverseShirt: NonFungibleToken {
14
15 // Events
16 access(all) event EntityCreated(id: UInt64, metadata: {String:String})
17 access(all) event EntityUpdated(id: UInt64, metadata: {String:String})
18 access(all) event NFTMinted(nftID: UInt64, nftUUID: UInt64, entityID: UInt64, minterAddress: Address)
19
20 // Named Paths
21 access(all) let CollectionStoragePath: StoragePath
22 access(all) let CollectionPublicPath: PublicPath
23 access(all) let AdminStoragePath: StoragePath
24
25 access(self) var entityDatas: {UInt64: Entity}
26 access(self) var numMintedPerEntity: {UInt64: UInt64}
27
28 // Total number of FlowverseShirt NFTs that have been minted
29 // Incremented ID used to create nfts
30 access(all) var totalSupply: UInt64
31
32 // Incremented ID used to create entities
33 access(all) var nextEntityID: UInt64
34
35 // Entity is a blueprint that holds metadata associated with an NFT
36 access(all) struct Entity {
37 // Unique ID for the entity
38 access(all) let entityID: UInt64
39
40 // Stores all the metadata about the entity as a string mapping
41 access(contract) let metadata: {String: String}
42
43 init(metadata: {String: String}) {
44 pre {
45 metadata.length != 0: "New Entity metadata cannot be empty"
46 }
47 self.entityID = FlowverseShirt.nextEntityID
48 self.metadata = metadata
49 }
50 }
51
52 // NFT Resource that represents the Entity instances
53 access(all) resource NFT: NonFungibleToken.NFT, ViewResolver.Resolver {
54 // Global unique NFT ID
55 access(all) let id: UInt64
56
57 // The ID of the Entity that the NFT references
58 access(all) let entityID: UInt64
59
60 // The minterAddress of the NFT
61 access(all) let minterAddress: Address
62
63 init(entityID: UInt64, minterAddress: Address) {
64 self.id = FlowverseShirt.totalSupply
65 self.entityID = entityID
66 self.minterAddress = minterAddress
67
68 emit NFTMinted(nftID: self.id, nftUUID: self.uuid, entityID: entityID, minterAddress: self.minterAddress)
69 }
70
71 access(all) view fun getViews(): [Type] {
72 let supportedViews = [
73 Type<MetadataViews.Display>(),
74 Type<MetadataViews.Royalties>(),
75 Type<MetadataViews.Edition>(),
76 Type<MetadataViews.Traits>(),
77 Type<MetadataViews.ExternalURL>(),
78 Type<MetadataViews.Rarity>()
79 ]
80 return supportedViews
81 }
82
83 access(all) view fun resolveView(_ view: Type): AnyStruct? {
84 switch view {
85 case Type<MetadataViews.Display>():
86 return MetadataViews.Display(
87 name: (FlowverseShirt.getEntityMetaDataByField(entityID: self.entityID, field: "name") ?? "").concat(" #").concat(self.id.toString()),
88 description: FlowverseShirt.getEntityMetaDataByField(entityID: self.entityID, field: "description") ?? "",
89 thumbnail: MetadataViews.HTTPFile(
90 url: FlowverseShirt.getEntityMetaDataByField(entityID: self.entityID, field: "thumbnailURL") ?? ""
91 )
92 )
93 case Type<MetadataViews.Medias>():
94 let mediaURL = FlowverseShirt.getEntityMetaDataByField(entityID: self.entityID, field: "mediaURL")
95 let mediaType = FlowverseShirt.getEntityMetaDataByField(entityID: self.entityID, field: "mediaType")
96 if mediaURL != nil && mediaType != nil {
97 let media = MetadataViews.Media(
98 file: MetadataViews.HTTPFile(
99 url: mediaURL!
100 ),
101 mediaType: mediaType!
102 )
103 return MetadataViews.Medias([media])
104 }
105 return MetadataViews.Medias([])
106 case Type<MetadataViews.Royalties>():
107 let royalties : [MetadataViews.Royalty] = [
108 MetadataViews.Royalty(
109 receiver: getAccount(0x604b63bcbef5974f).capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)!,
110 cut: 0.05,
111 description: "Creator Royalty Fee")
112 ]
113 return MetadataViews.Royalties(royalties)
114 case Type<MetadataViews.Traits>():
115 let traits: [MetadataViews.Trait] = []
116 return MetadataViews.Traits(traits)
117 case Type<MetadataViews.ExternalURL>():
118 let baseURL = "https://nft.flowverse.co/collections/FlowverseShirts/"
119 return MetadataViews.ExternalURL(baseURL.concat(self.owner!.address.toString()).concat("/".concat(self.id.toString())))
120 }
121 return nil
122 }
123
124 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
125 return <- FlowverseShirt.createEmptyCollection(nftType: Type<@FlowverseShirt.NFT>())
126 }
127 }
128
129 access(self) fun mint(entityID: UInt64, minterAddress: Address): @NFT {
130 pre {
131 FlowverseShirt.entityDatas[entityID] != nil: "Cannot mint: the entity doesn't exist."
132 }
133
134 // Gets the number of NFTs that have been minted for this Entity
135 let entityMintNumber = FlowverseShirt.numMintedPerEntity[entityID]!
136
137 // Increment the global NFT ID
138 FlowverseShirt.totalSupply = FlowverseShirt.totalSupply + UInt64(1)
139
140 // Mint the new NFT
141 let newNFT: @NFT <- create NFT(entityID: entityID, minterAddress: minterAddress)
142
143 // Increment the number of copies minted for this NFT
144 FlowverseShirt.numMintedPerEntity[entityID] = entityMintNumber + UInt64(1)
145 return <-newNFT
146 }
147
148 access(all) resource NFTMinter: FlowversePrimarySaleV2.IMinter {
149 init() {}
150 access(all) fun mint(entityID: UInt64, minterAddress: Address): @NFT {
151 return <-FlowverseShirt.mint(entityID: entityID, minterAddress: minterAddress)
152 }
153 }
154
155 // Admin is a special authorization resource that
156 // allows the owner to perform important functions to modify the
157 // various aspects of the Entities, Sets, and NFTs
158 //
159 access(all) resource Admin {
160
161 // createEntity creates a new Entity struct
162 // and stores it in the Entities dictionary in the FlowverseShirt smart contract
163 access(all) fun createEntity(metadata: {String: String}): UInt64 {
164 // Create the new Entity
165 var newEntity = Entity(metadata: metadata)
166 let newID = newEntity.entityID
167
168 // Increment the ID so that it isn't used again
169 FlowverseShirt.nextEntityID = FlowverseShirt.nextEntityID + UInt64(1)
170
171 // Store it in the contract storage
172 FlowverseShirt.entityDatas[newID] = newEntity
173
174 // Initialise numMintedPerEntity
175 FlowverseShirt.numMintedPerEntity[newID] = UInt64(0)
176
177 emit EntityCreated(id: newID, metadata: metadata)
178
179 return newID
180 }
181
182 access(all) fun mint(entityID: UInt64, minterAddress: Address): @NFT {
183 return <-FlowverseShirt.mint(entityID: entityID, minterAddress: minterAddress)
184 }
185
186 // createNFTMinter creates a new NFTMinter resource
187 access(all) fun createNFTMinter(): @NFTMinter {
188 return <-create NFTMinter()
189 }
190
191 // createNewAdmin creates a new Admin resource
192 access(all) fun createNewAdmin(): @Admin {
193 return <-create Admin()
194 }
195 }
196
197 // Public interface for the FlowverseShirt Collection that allows users access to certain functionalities
198 access(all) resource interface CollectionPublic {
199 access(all) fun deposit(token: @{NonFungibleToken.NFT})
200 access(all) view fun getIDs(): [UInt64]
201 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
202 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}?
203 }
204
205 // Collection of FlowverseShirt NFTs owned by an account
206 access(all) resource Collection: CollectionPublic, NonFungibleToken.Collection {
207 // Dictionary of entity instances conforming tokens
208 // NFT is a resource type with a UInt64 ID field
209 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
210
211 init () {
212 self.ownedNFTs <- {}
213 }
214
215 /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
216 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
217 let supportedTypes: {Type: Bool} = {}
218 supportedTypes[Type<@FlowverseShirt.NFT>()] = true
219 return supportedTypes
220 }
221
222 /// Returns whether or not the given type is accepted by the collection
223 /// A collection that can accept any type should just return true by default
224 access(all) view fun isSupportedNFTType(type: Type): Bool {
225 if type == Type<@FlowverseShirt.NFT>() {
226 return true
227 } else {
228 return false
229 }
230 }
231
232 /// Gets the amount of NFTs stored in the collection
233 access(all) view fun getLength(): Int {
234 return self.ownedNFTs.keys.length
235 }
236
237 /// withdraw removes an NFT from the collection and moves it to the caller
238 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
239 let token <- self.ownedNFTs.remove(key: withdrawID)
240 ?? panic("Could not withdraw an NFT with the provided ID from the collection")
241
242 return <-token
243 }
244
245 /// deposit takes a NFT and adds it to the collections dictionary
246 /// and adds the ID to the id array
247 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
248 let token <- token as! @FlowverseShirt.NFT
249 let id = token.id
250
251 // add the new token to the dictionary which removes the old one
252 let oldToken <- self.ownedNFTs[token.id] <- token
253
254 destroy oldToken
255 }
256
257 access(all) view fun getIDs(): [UInt64] {
258 return self.ownedNFTs.keys
259 }
260
261 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
262 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
263 }
264
265 /// Borrow the view resolver for the specified NFT ID
266 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
267 if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
268 return nft as &{ViewResolver.Resolver}
269 }
270 return nil
271 }
272
273 /// createEmptyCollection creates an empty Collection of the same type
274 /// and returns it to the caller
275 /// @return A an empty collection of the same type
276 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
277 return <-FlowverseShirt.createEmptyCollection(nftType: Type<@FlowverseShirt.NFT>())
278 }
279 }
280
281 // -----------------------------------------------------------------------
282 // FlowverseShirt contract-level function definitions
283 // -----------------------------------------------------------------------
284
285 /// createEmptyCollection creates an empty Collection for the specified NFT type
286 /// and returns it to the caller so that they can own NFTs
287 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
288 return <- create Collection()
289 }
290
291 /// Function that returns all the Metadata Views implemented by a Non Fungible Token
292 ///
293 /// @return An array of Types defining the implemented views. This value will be used by
294 /// developers to know which parameter to pass to the resolveView() method.
295 ///
296 access(all) view fun getContractViews(resourceType: Type?): [Type] {
297 return [
298 Type<MetadataViews.NFTCollectionData>(),
299 Type<MetadataViews.NFTCollectionDisplay>()
300 ]
301 }
302
303 /// Function that resolves a metadata view for this contract.
304 ///
305 /// @param view: The Type of the desired view.
306 /// @return A structure representing the requested view.
307 ///
308 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
309 switch viewType {
310 case Type<MetadataViews.NFTCollectionData>():
311 return MetadataViews.NFTCollectionData(
312 storagePath: self.CollectionStoragePath,
313 publicPath: self.CollectionPublicPath,
314 publicCollection: Type<&FlowverseShirt.Collection>(),
315 publicLinkedType: Type<&FlowverseShirt.Collection>(),
316 createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {return <- FlowverseShirt.createEmptyCollection(nftType: Type<@FlowverseShirt.NFT>())}),
317 )
318 case Type<MetadataViews.NFTCollectionDisplay>():
319 return MetadataViews.NFTCollectionDisplay(
320 name: "Flowverse Shirt",
321 description: "The Flowverse Shirt is the official shirt collection for the Flowverse community. Join a group of die-hard Flow enthusiasts and rep the Flowverse Shirt on the Flow Blockchain",
322 externalURL: MetadataViews.ExternalURL("https://twitter.com/flowverse_"),
323 squareImage: MetadataViews.Media(
324 file: MetadataViews.HTTPFile(
325 url: "https://flowverse.myfilebase.com/ipfs/QmeFH4AXFLkCzKJ64nRmtRyqxXsVH8N98QDZcUNwJphXBz"
326 ),
327 mediaType: "image/png"
328 ),
329 bannerImage: MetadataViews.Media(
330 file: MetadataViews.HTTPFile(
331 url: "https://flowverse.myfilebase.com/ipfs/QmSevyXCcgmHse2TTc6mtK2sdAGDSWncERUXrFYP2hxMgu"
332 ),
333 mediaType: "image/png"
334 ),
335 socials: {
336 "twitter": MetadataViews.ExternalURL("https://twitter.com/flowverse_")
337 }
338 )
339 }
340 return nil
341 }
342
343 // getAllEntities returns all the entities available
344 access(all) view fun getAllEntities(): [FlowverseShirt.Entity] {
345 return FlowverseShirt.entityDatas.values
346 }
347
348 // getEntity returns an entity by ID
349 access(all) view fun getEntity(entityID: UInt64): FlowverseShirt.Entity? {
350 return self.entityDatas[entityID]
351 }
352
353 // getEntityMetaData returns all the metadata associated with a specific Entity
354 access(all) view fun getEntityMetaData(entityID: UInt64): {String: String}? {
355 return self.entityDatas[entityID]?.metadata
356 }
357
358 access(all) view fun getEntityMetaDataByField(entityID: UInt64, field: String): String? {
359 if let entity = FlowverseShirt.entityDatas[entityID] {
360 return entity.metadata[field]
361 } else {
362 return nil
363 }
364 }
365
366 access(all) view fun getNumMintedPerEntity(): {UInt64: UInt64} {
367 return self.numMintedPerEntity
368 }
369
370 // -----------------------------------------------------------------------
371 // FlowverseShirt initialization function
372 // -----------------------------------------------------------------------
373 //
374 init() {
375 self.AdminStoragePath = /storage/FlowverseShirtAdmin
376 self.CollectionStoragePath = /storage/FlowverseShirtCollection
377 self.CollectionPublicPath = /public/FlowverseShirtCollection
378
379 // Initialize contract fields
380 self.entityDatas = {}
381 self.numMintedPerEntity = {}
382 self.nextEntityID = 1
383 self.totalSupply = 0
384
385 // Create and save a new Collection in storage
386 let collection <- create Collection()
387 self.account.storage.save(<-collection, to: self.CollectionStoragePath)
388
389 // Issue a public capability for the Collection
390 let collectionCap = self.account.capabilities.storage.issue<&FlowverseShirt.Collection>(self.CollectionStoragePath)
391 self.account.capabilities.publish(collectionCap, at: self.CollectionPublicPath)
392
393 // Create and save Admin resource in storage
394 self.account.storage.save<@Admin>(<- create Admin(), to: self.AdminStoragePath)
395 }
396}
397