Smart Contract

Certifily

A.3c678a22cbfe5053.Certifily

Valid From

86,698,411

Deployed

2w ago
Feb 11, 2026, 06:34:46 PM UTC

Dependents

3 imports
1// Certifily NFT Smart Contract
2// NFT             : www.Certifi.ly
3// Version         : 1.0.0
4// Blockchain      : Flow www.onFlow.org
5
6import NonFungibleToken from 0x1d7e57aa55817448
7import MetadataViews from 0x1d7e57aa55817448
8import ViewResolver from 0x1d7e57aa55817448
9
10access(all) contract Certifily: NonFungibleToken {
11   
12    // Total number of token supply
13    access(all) var totalSupply: UInt64
14
15    /// Path where the `Collection` is stored
16    access(all) let certifilyStoragePath: StoragePath
17
18    /// Path where the public capability for the `Collection` is
19    access(all) let certifilyPublicPath: PublicPath
20
21    /// NFT Minter
22    access(all) let certifilyMinterPath: StoragePath
23    
24    // Contract Events
25    access(all) event ContractInitialized()
26    access(all) event Withdraw(id: UInt64, from: Address?)
27    access(all) event Deposit(id: UInt64, to: Address?)
28    access(all) event Mint(id: UInt64, content:String, owner: Address?, insitution:String)
29
30    // TOKEN RESOURCE
31    access(all) resource NFT: NonFungibleToken.NFT {
32
33        // Unique identifier for NFT Token
34        access(all) let id :UInt64
35
36        // Meta data to store token data (use dict for data)
37        access(self) let metaData: {String : String}
38
39        access(all) fun getMetadata():{String: String} {
40            return self.metaData
41        }
42
43        // NFT token insitution
44        access(all) let insitution:String
45
46        access(all) let certType:UInt8
47
48        // Certificate token holder address
49        access(all) let holder:Address?
50
51        // View permission for the NFT (an array of addresses)
52        access(all) let viewPermission: [String]
53
54
55        // In current store static dict in meta data
56        init(id : UInt64, content : String, insitution:String, description:String , holder:Address?,previewContent:String,certType:UInt8, viewPermission: [String]) {
57            self.id = id
58            self.metaData = {"content" : content, "description": description, "previewContent":previewContent }
59            self.holder = holder
60            self.insitution = insitution
61            self.certType = certType
62            self.viewPermission = viewPermission
63        }
64
65        /// createEmptyCollection creates an empty Collection
66        /// and returns it to the caller so that they can own NFTs
67        /// @{NonFungibleToken.Collection}
68        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
69            return <-Certifily.createEmptyCollection(nftType: Type<@Certifily.NFT>())
70        }
71
72
73        access(all) view fun getViews(): [Type] {
74            return [
75                Type<MetadataViews.Display>(),
76                Type<MetadataViews.ExternalURL>(),
77                Type<MetadataViews.NFTCollectionData>(),
78                Type<MetadataViews.NFTCollectionDisplay>(),
79                Type<MetadataViews.Serial>(),
80                Type<MetadataViews.Traits>()
81            ]
82        }
83        
84
85
86        // Function to add an address to viewPermission
87        access(all) fun addViewPermission(address: [String]) {
88
89          for addr in address {
90            // Check if the address is not already in the viewPermission array
91            if !self.viewPermission.contains(addr) {
92                self.viewPermission.append(addr)
93            }
94          }
95
96        }
97
98        // Function to remove an address from viewPermission
99        access(all) fun removeViewPermission(address: String) {
100        
101            // Find the index of the address in viewPermission and remove it
102            let index = self.viewPermission.firstIndex(of: address)
103            if let idx = index {
104                self.viewPermission.remove(at: idx)
105            }
106
107        }
108
109        access(all) fun resolveView(_ view: Type): AnyStruct? {
110            switch view {
111                case Type<MetadataViews.Display>():
112                    return MetadataViews.Display(
113                        name: self.insitution,
114                        description: self.metaData["description"]!,
115                        thumbnail: MetadataViews.HTTPFile(
116                            url: self.metaData["previewContent"]!
117                        )
118                    )
119                case Type<MetadataViews.Serial>():
120                    return MetadataViews.Serial(
121                        self.id
122                    )
123                case Type<MetadataViews.ExternalURL>():
124                    return MetadataViews.ExternalURL("https://certifi.ly")
125                case Type<MetadataViews.NFTCollectionData>():
126                    return MetadataViews.NFTCollectionData(
127                        storagePath: Certifily.certifilyStoragePath,
128                        publicPath: Certifily.certifilyPublicPath,
129                        publicCollection: Type<&Certifily.Collection>(),
130                        publicLinkedType: Type<&Certifily.Collection>(),
131                        createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
132                            return <-Certifily.createEmptyCollection(nftType: Type<@Certifily.NFT>())
133                        })
134                    )
135                case Type<MetadataViews.NFTCollectionDisplay>():
136                    let media = MetadataViews.Media(
137                        file: MetadataViews.HTTPFile(
138                            url: "https://alpha.certifi.ly/logo512.png"
139                        ),
140                        mediaType: "image/png"
141                    )
142                    return MetadataViews.NFTCollectionDisplay(
143                        name: "Certifily Collection",
144                        description: "Next-gen NFT documents B2B SaaS platform for all-gen",
145                        externalURL: MetadataViews.ExternalURL("https://certifi.ly"),
146                        squareImage: media,
147                        bannerImage: media,
148                        socials: {
149                            "twitter": MetadataViews.ExternalURL("https://twitter.com/certifily"),
150                            "instagram": MetadataViews.ExternalURL("https://www.instagram.com/certifi.ly/"),
151                            "discord" : MetadataViews.ExternalURL("https://discord.io/certifily")
152                        }
153                    )
154                case Type<MetadataViews.Traits>():
155                    return []
156
157            }
158            return nil
159        }
160
161    }
162
163    // Account's public collection
164    access(all) resource interface CertifilyCollectionPublic {
165
166        // Function to check if an address has view permission for an NFT
167        access(all) view fun hasViewPermission(id: UInt64, address: String): Bool?
168
169        access(all) fun borrowCertifily(id: UInt64): &{NonFungibleToken.NFT}? {
170            // If the result isn't nil, the id of the returned reference
171            // should be the same as the argument to the function
172            post {
173                (result == nil) || (result?.id == id):
174                    "Cannot borrow CaaPass reference: The ID of the returned reference is incorrect"
175            }
176        }
177
178    } 
179
180    // NFT Collection resource
181    access(all) resource Collection : CertifilyCollectionPublic, NonFungibleToken.Collection {
182
183         /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
184        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
185            let supportedTypes: {Type: Bool} = {}
186            supportedTypes[Type<@Certifily.NFT>()] = true
187            return supportedTypes
188        }
189
190        /// Returns whether or not the given type is accepted by the collection
191        /// A collection that can accept any type should just return true by default
192        access(all) view fun isSupportedNFTType(type: Type): Bool {
193            return type == Type<@Certifily.NFT>()
194        }
195
196
197         /// createEmptyCollection creates an empty Collection for the specified NFT type
198        /// and returns it to the caller so that they can own NFTs
199        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
200            return <- create Collection()
201        }
202
203        
204        // Contains caller's list of NFTs
205        access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
206
207        init() {
208            self.ownedNFTs <- {}
209        }
210
211        access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
212
213            let token <- token as! @Certifily.NFT
214
215            let id: UInt64 = token.id
216
217            // add the new token to the dictionary which removes the old one
218            let oldToken <- self.ownedNFTs[id] <- token
219
220            emit Deposit(id: id, to: self.owner?.address)
221
222            destroy oldToken
223        }
224
225        // function returns token keys of owner
226        access(all) view fun getIDs():[UInt64] {
227            return self.ownedNFTs.keys
228        }
229
230        // function returns token data of token id
231        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
232            return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
233        }
234        // Gets a reference to an NFT in the collection as a Certifily,
235        // exposing all of its fields.
236        access(all) view fun borrowCertifily(id: UInt64): &{NonFungibleToken.NFT}? {
237            if self.ownedNFTs[id] != nil {
238                // let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
239                // return ref as! &Certifily.NFT
240                return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
241            } else {
242                return nil
243            }
244        }
245
246        // function to check wether the owner have token or not
247        access(all) fun tokenExists(id:UInt64) : Bool {
248            return self.ownedNFTs[id] != nil
249        }
250
251        /// withdraw removes an NFT from the collection and moves it to the caller
252        access(NonFungibleToken.Withdraw) fun withdraw(withdrawID:UInt64) : @{NonFungibleToken.NFT} {
253
254            // let ref = (&self.ownedNFTs[withdrawID] as auth &NonFungibleToken.NFT?)!
255            let ref = (&self.ownedNFTs[withdrawID] as &{NonFungibleToken.NFT}?)
256
257            let val = ref as! &Certifily.NFT
258
259            if val.certType == 1 {
260                panic("Cert Type is not allowed to withdraw")
261            }
262                         
263            let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
264
265            emit Withdraw(id: token.id, from: self.owner?.address)
266
267            return <-token    
268
269        }
270
271        // access(all) fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} {
272        access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
273            // let nft = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
274            // let nft = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
275            // let CertifilyNFT = nft as! &Certifily.NFT
276            // return CertifilyNFT as &{ViewResolver.Resolver}
277            if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
278                return nft as &{ViewResolver.Resolver}
279            }
280            return nil
281        }
282
283        // Function to add an address to viewPermission
284        access(all) fun addViewPermission(id: UInt64, address: [String]) {
285            // let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
286            let ref =  (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
287
288            let nft = ref as! &Certifily.NFT?
289            
290            // Call the NFT's addViewPermission function
291            nft?.addViewPermission(address: address)
292            
293        }
294
295        access(all) fun removeViewPermission(id: UInt64, address: String) {
296            // let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
297            let ref =  (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
298
299            let nft = ref as! &Certifily.NFT?
300            
301            // Call the NFT's removeViewPermission function
302            nft?.removeViewPermission(address: address)
303            
304        }
305
306        access(all) view fun hasViewPermission(id: UInt64, address: String): Bool? {
307            // let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
308            let ref =  (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
309
310            let nft = ref as! &Certifily.NFT?
311
312            // Check if the address has view permission
313            return nft?.viewPermission?.contains(address)
314        }
315
316    }
317
318    /// createEmptyCollection creates an empty Collection for the specified NFT type
319    /// and returns it to the caller so that they can own NFTs
320    access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
321        return <- create Collection()
322    }
323
324    /// Function that returns all the Metadata Views implemented by a Non Fungible Token
325    ///
326    /// @return An array of Types defining the implemented views. This value will be used by
327    ///         developers to know which parameter to pass to the resolveView() method.
328    ///
329    access(all) view fun getContractViews(resourceType: Type?): [Type] {
330        return [
331            Type<MetadataViews.NFTCollectionData>(),
332            Type<MetadataViews.NFTCollectionDisplay>(),
333            Type<MetadataViews.EVMBridgedMetadata>()
334        ]
335    }
336
337    /// Function that resolves a metadata view for this contract.
338    ///
339    /// @param view: The Type of the desired view.
340    /// @return A structure representing the requested view.
341    ///
342    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
343        switch viewType {
344            case Type<MetadataViews.NFTCollectionData>():
345                let collectionData = MetadataViews.NFTCollectionData(
346                    storagePath: Certifily.certifilyStoragePath,
347                    publicPath: Certifily.certifilyPublicPath,
348                    publicCollection: Type<&Certifily.Collection>(),
349                    publicLinkedType: Type<&Certifily.Collection>(),
350                    createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
351                        return <-Certifily.createEmptyCollection(nftType: Type<@Certifily.NFT>())
352                    })
353                )
354                return collectionData
355            case Type<MetadataViews.NFTCollectionDisplay>():
356                let media = MetadataViews.Media(
357                    file: MetadataViews.HTTPFile(
358                        url: "https://certifi.ly/logo512.png"
359                    ),
360                    mediaType: "image/png+xml"
361                )
362                return MetadataViews.NFTCollectionDisplay(
363                    name: "The Example Collection",
364                    description: "This collection is used as an example to help you develop your next Flow NFT.",
365                    externalURL: MetadataViews.ExternalURL("https://certifi.ly"),
366                    squareImage: media,
367                    bannerImage: media,
368                    socials: {
369                        "twitter": MetadataViews.ExternalURL("https://twitter.com/certifily")
370                    }
371                )
372            case Type<MetadataViews.EVMBridgedMetadata>():
373                // Implementing this view gives the project control over how the bridged NFT is represented as an ERC721
374                // when bridged to EVM on Flow via the public infrastructure bridge.
375
376                // Compose the contract-level URI. In this case, the contract metadata is located on some HTTP host,
377                // but it could be IPFS, S3, a data URL containing the JSON directly, etc.
378                return MetadataViews.EVMBridgedMetadata(
379                    name: "Certifily",
380                    symbol: "XMPL",
381                    uri: MetadataViews.URI(
382                        baseURI: nil, // setting baseURI as nil sets the given value as the uri field value
383                        value: "https://certifi.ly"
384                    )
385                )
386        }
387        return nil
388    }
389
390    // NFT MINTER
391    access(all) resource NFTMinter {    
392        access(all) fun Mint(recipient: Address,content:String, insitution:String, description:String,previewContent:String,certType:UInt8,viewPermission: [String]) : @Certifily.NFT {
393            let token <- create NFT(id: Certifily.totalSupply, content:content, insitution:insitution, description:description, holder: recipient,previewContent:previewContent, certType:certType, viewPermission:viewPermission)
394            emit Mint(id:Certifily.totalSupply,content:content,owner: recipient, insitution:insitution)
395            Certifily.totalSupply = Certifily.totalSupply + 1 as UInt64
396             return <-token
397        } 
398    }
399
400    // Contract init
401    init() {
402
403        // total supply is zero at the time of contract deployment
404        self.totalSupply = 0
405
406        self.certifilyStoragePath = /storage/CertifilyNFTCollection
407
408        self.certifilyPublicPath = /public/CertifilyNFTPublicCollection
409
410        self.certifilyMinterPath = /storage/CertifilyNFTMinter
411
412
413        // Create a Collection resource and save it to storage
414        let collection <- create Collection()
415        self.account.storage.save(<-collection, to: self.certifilyStoragePath)
416
417        // create a public capability for the collection
418        let collectionCap = self.account.capabilities.storage.issue<&Certifily.Collection>(self.certifilyStoragePath)
419        self.account.capabilities.publish(collectionCap, at: self.certifilyPublicPath)
420
421        // store a minter resource in account storage
422        self.account.storage.save(<-create NFTMinter(), to: self.certifilyMinterPath)
423
424        emit ContractInitialized()
425
426    }
427
428}
429