Smart Contract

FlowverseShirt

A.9212a87501a8a6a2.FlowverseShirt

Deployed

1d ago
Feb 27, 2026, 05:49:21 AM UTC

Dependents

0 imports
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