Smart Contract
FINDNFTCatalog
A.097bafa4e0b48eef.FINDNFTCatalog
1import NFTCatalog from 0x49a7cda3a1eecc29
2import MetadataViews from 0x1d7e57aa55817448
3
4// NFTCatalog
5//
6// A general purpose NFT registry for Flow NonFungibleTokens.
7//
8// Each catalog entry stores data about the NFT including
9// its collection identifier, nft type, storage and public paths, etc.
10//
11// To make an addition to the catalog you can propose an NFT and provide its metadata.
12// An Admin can approve a proposal which would add the NFT to the catalog
13
14access(all) contract FINDNFTCatalog {
15 // EntryAdded
16 // An NFT collection has been added to the catalog
17 access(all) event EntryAdded(
18 collectionIdentifier : String,
19 contractName : String,
20 contractAddress : Address,
21 nftType : Type,
22 storagePath: StoragePath,
23 publicPath: PublicPath,
24 publicLinkedType : Type,
25 displayName : String,
26 description: String,
27 externalURL : String
28 )
29
30 // EntryUpdated
31 // An NFT Collection has been updated in the catalog
32 access(all) event EntryUpdated(
33 collectionIdentifier : String,
34 contractName : String,
35 contractAddress : Address,
36 nftType : Type,
37 storagePath: StoragePath,
38 publicPath: PublicPath,
39 publicLinkedType : Type,
40 displayName : String,
41 description: String,
42 externalURL : String
43 )
44
45 // EntryRemoved
46 // An NFT Collection has been removed from the catalog
47 access(all) event EntryRemoved(collectionIdentifier : String)
48
49 // ProposalEntryAdded
50 // A new proposal to make an addtion to the catalog has been made
51 access(all) event ProposalEntryAdded(proposalID : UInt64, collectionIdentifier : String, message: String, status: String, proposer : Address)
52
53 // ProposalEntryUpdated
54 // A proposal has been updated
55 access(all) event ProposalEntryUpdated(proposalID : UInt64, collectionIdentifier : String, message: String, status: String, proposer : Address)
56
57 // ProposalEntryRemoved
58 // A proposal has been removed from storage
59 access(all) event ProposalEntryRemoved(proposalID : UInt64)
60
61 access(all) let ProposalManagerStoragePath: StoragePath
62 access(all) let ProposalManagerPublicPath: PublicPath
63
64 access(self) let catalog: {String : NFTCatalog.NFTCatalogMetadata} // { collectionIdentifier -> Metadata }
65 access(self) let catalogTypeData: {String : {String : Bool}} // Additional view to go from { NFT Type Identifier -> {Collection Identifier : Bool } }
66
67 access(self) let catalogProposals : {UInt64 : NFTCatalog.NFTCatalogProposal} // { ProposalID : Metadata }
68
69 access(self) var totalProposals : UInt64
70
71 // Get FIND and Dapper NFTCatalog
72 access(all) fun getCatalog() : {String : NFTCatalog.NFTCatalogMetadata} {
73 let find = self.catalog
74 let dapper = NFTCatalog.getCatalog()
75 for item in dapper.keys {
76 find[item] = dapper[item]
77 }
78 return find
79 }
80
81 access(all) fun getCatalogEntry(collectionIdentifier : String) : NFTCatalog.NFTCatalogMetadata? {
82 if let dapper = NFTCatalog.getCatalogEntry(collectionIdentifier : collectionIdentifier) {
83 return dapper
84 }
85 return self.catalog[collectionIdentifier]
86 }
87
88 access(all) fun getCollectionsForType(nftTypeIdentifier: String) : {String : Bool}? {
89 if let dapper = NFTCatalog.getCollectionsForType(nftTypeIdentifier: nftTypeIdentifier) {
90 // If there's dappers input and Find input, dappers will overwrite what's on find
91 if let find = self.catalogTypeData[nftTypeIdentifier] {
92 for item in dapper.keys {
93 find[item] = dapper[item]
94 }
95 return find
96 }
97 // If there's only dapper input, return it
98 return dapper
99 }
100 // Else return what's on find
101 return self.catalogTypeData[nftTypeIdentifier]
102 }
103
104 access(all) fun getCatalogTypeData() : {String : {String : Bool}} {
105 let find = self.catalogTypeData
106 let dapper = NFTCatalog.getCatalogTypeData()
107 for item in dapper.keys {
108 find[item] = dapper[item]
109 }
110 return find
111 }
112
113 // A helper function to get CollectionData directly from NFTIdentifier
114 access(all) fun getCollectionDataForType(nftTypeIdentifier: String) : NFTCatalog.NFTCollectionData? {
115 if let collections = FINDNFTCatalog.getCollectionsForType(nftTypeIdentifier: nftTypeIdentifier) {
116 if collections.length < 1 {
117 return nil
118 }
119 if let collection = FINDNFTCatalog.getCatalogEntry(collectionIdentifier : collections.keys[0]) {
120 return collection.collectionData
121 }
122 }
123 return nil
124 }
125
126 // helper function to get Paths
127 access(all) fun getMetadataFromType(_ t: Type) : NFTCatalog.NFTCatalogMetadata? {
128 let collectionIdentifier = self.getCollectionsForType(nftTypeIdentifier: t.identifier)
129 if collectionIdentifier == nil || collectionIdentifier!.length < 1 {
130 return nil
131 }
132 return self.getCatalogEntry(collectionIdentifier : collectionIdentifier!.keys[0])
133 }
134
135 // Get only FIND NFTCatalog
136 access(all) fun getFINDCatalog() : {String : NFTCatalog.NFTCatalogMetadata} {
137 return self.catalog
138 }
139
140 access(all) fun getFINDCatalogEntry(collectionIdentifier : String) : NFTCatalog.NFTCatalogMetadata? {
141 return self.catalog[collectionIdentifier]
142 }
143
144 access(all) fun getFINDCollectionsForType(nftTypeIdentifier: String) : {String : Bool}? {
145 return self.catalogTypeData[nftTypeIdentifier]
146 }
147
148 access(all) fun getFINDCatalogTypeData() : {String : {String : Bool}} {
149 return self.catalogTypeData
150 }
151
152 // Propose an NFT collection to the catalog
153 // @param collectionIdentifier: The unique name assinged to this nft collection
154 // @param metadata: The Metadata for the NFT collection that will be stored in the catalog
155 // @param message: A message to the catalog owners
156 // @param proposer: Who is making the proposition(the address needs to be verified)
157 access(all) fun proposeNFTMetadata(collectionIdentifier : String, metadata : NFTCatalog.NFTCatalogMetadata, message : String, proposer : Address) : UInt64 {
158 let proposerManagerCap = getAccount(proposer).capabilities.get<&NFTCatalog.NFTCatalogProposalManager>(NFTCatalog.ProposalManagerPublicPath)!
159
160 assert(proposerManagerCap.check(), message : "Proposer needs to set up a manager")
161
162 let proposerManagerRef = proposerManagerCap.borrow()!
163
164 assert(proposerManagerRef.getCurrentProposalEntry()! == collectionIdentifier, message: "Expected proposal entry does not match entry for the proposer")
165
166 let catalogProposal = NFTCatalog.NFTCatalogProposal(collectionIdentifier : collectionIdentifier, metadata : metadata, message : message, status: "IN_REVIEW", proposer: proposer)
167 self.totalProposals = self.totalProposals + 1
168 self.catalogProposals[self.totalProposals] = catalogProposal
169
170 emit ProposalEntryAdded(proposalID : self.totalProposals, collectionIdentifier : collectionIdentifier, message: catalogProposal.message, status: catalogProposal.status, proposer: catalogProposal.proposer)
171 return self.totalProposals
172 }
173
174 // Withdraw a proposal from the catalog
175 // @param proposalID: The ID of proposal you want to withdraw
176 access(all) fun withdrawNFTProposal(proposalID : UInt64) {
177 pre {
178 self.catalogProposals[proposalID] != nil : "Invalid Proposal ID"
179 }
180 let proposal = self.catalogProposals[proposalID]!
181 let proposer = proposal.proposer
182
183 let proposerManagerCap = getAccount(proposer).capabilities.get<&NFTCatalog.NFTCatalogProposalManager>(NFTCatalog.ProposalManagerPublicPath)!
184
185 assert(proposerManagerCap.check(), message : "Proposer needs to set up a manager")
186
187 let proposerManagerRef = proposerManagerCap.borrow()!
188
189 assert(proposerManagerRef.getCurrentProposalEntry()! == proposal.collectionIdentifier, message: "Expected proposal entry does not match entry for the proposer")
190
191 self.removeCatalogProposal(proposalID : proposalID)
192 }
193
194 access(all) fun getCatalogProposals() : {UInt64 : NFTCatalog.NFTCatalogProposal} {
195 return self.catalogProposals
196 }
197
198 access(all) fun getCatalogProposalEntry(proposalID : UInt64) : NFTCatalog.NFTCatalogProposal? {
199 return self.catalogProposals[proposalID]
200 }
201
202 access(all) fun createNFTCatalogProposalManager(): @NFTCatalog.NFTCatalogProposalManager {
203 return <- NFTCatalog.createNFTCatalogProposalManager()
204 }
205
206 access(account) fun addCatalogEntry(collectionIdentifier : String, metadata: NFTCatalog.NFTCatalogMetadata) {
207 pre {
208 self.catalog[collectionIdentifier] == nil : "The nft name has already been added to the catalog"
209 }
210
211 self.addCatalogTypeEntry(collectionIdentifier : collectionIdentifier , metadata: metadata)
212
213 self.catalog[collectionIdentifier] = metadata
214
215 emit EntryAdded(
216 collectionIdentifier : collectionIdentifier,
217 contractName : metadata.contractName,
218 contractAddress : metadata.contractAddress,
219 nftType: metadata.nftType,
220 storagePath: metadata.collectionData.storagePath,
221 publicPath: metadata.collectionData.publicPath,
222 publicLinkedType : metadata.collectionData.publicLinkedType,
223 displayName : metadata.collectionDisplay.name,
224 description: metadata.collectionDisplay.description,
225 externalURL : metadata.collectionDisplay.externalURL.url
226 )
227 }
228
229 access(account) fun updateCatalogEntry(collectionIdentifier : String , metadata: NFTCatalog.NFTCatalogMetadata) {
230 pre {
231 self.catalog[collectionIdentifier] != nil : "Invalid collection identifier"
232 }
233 // remove previous nft type entry
234 self.removeCatalogTypeEntry(collectionIdentifier : collectionIdentifier , metadata: metadata)
235 // add updated nft type entry
236 self.addCatalogTypeEntry(collectionIdentifier : collectionIdentifier , metadata: metadata)
237
238 self.catalog[collectionIdentifier] = metadata
239
240 let nftType = metadata.nftType
241
242 emit EntryUpdated(
243 collectionIdentifier : collectionIdentifier,
244 contractName : metadata.contractName,
245 contractAddress : metadata.contractAddress,
246 nftType: metadata.nftType,
247 storagePath: metadata.collectionData.storagePath,
248 publicPath: metadata.collectionData.publicPath,
249 publicLinkedType : metadata.collectionData.publicLinkedType,
250 displayName : metadata.collectionDisplay.name,
251 description: metadata.collectionDisplay.description,
252 externalURL : metadata.collectionDisplay.externalURL.url
253 )
254 }
255
256 access(account) fun removeCatalogEntry(collectionIdentifier : String) {
257 pre {
258 self.catalog[collectionIdentifier] != nil : "Invalid collection identifier"
259 }
260
261 self.removeCatalogTypeEntry(collectionIdentifier : collectionIdentifier , metadata: self.catalog[collectionIdentifier]!)
262 self.catalog.remove(key: collectionIdentifier)
263
264 emit EntryRemoved(collectionIdentifier : collectionIdentifier)
265 }
266
267 access(account) fun updateCatalogProposal(proposalID: UInt64, proposalMetadata : NFTCatalog.NFTCatalogProposal) {
268 self.catalogProposals[proposalID] = proposalMetadata
269
270 emit ProposalEntryUpdated(proposalID : proposalID, collectionIdentifier : proposalMetadata.collectionIdentifier, message: proposalMetadata.message, status: proposalMetadata.status, proposer: proposalMetadata.proposer)
271 }
272
273 access(account) fun removeCatalogProposal(proposalID : UInt64) {
274 self.catalogProposals.remove(key : proposalID)
275
276 emit ProposalEntryRemoved(proposalID : proposalID)
277 }
278
279 access(contract) fun addCatalogTypeEntry(collectionIdentifier : String , metadata: NFTCatalog.NFTCatalogMetadata) {
280 if self.catalogTypeData[metadata.nftType.identifier] != nil {
281 let typeData : {String : Bool} = self.catalogTypeData[metadata.nftType.identifier]!
282 assert(self.catalogTypeData[metadata.nftType.identifier]![collectionIdentifier] == nil, message : "The nft name has already been added to the catalog")
283 typeData[collectionIdentifier] = true
284 self.catalogTypeData[metadata.nftType.identifier] = typeData
285 } else {
286 let typeData : {String : Bool} = {}
287 typeData[collectionIdentifier] = true
288 self.catalogTypeData[metadata.nftType.identifier] = typeData
289 }
290 }
291
292 access(contract) fun removeCatalogTypeEntry(collectionIdentifier : String , metadata: NFTCatalog.NFTCatalogMetadata) {
293 let prevMetadata = self.catalog[collectionIdentifier]!
294 let prevCollectionsForType = self.catalogTypeData[prevMetadata.nftType.identifier]!
295 prevCollectionsForType.remove(key : collectionIdentifier)
296 if prevCollectionsForType.length == 0 {
297 self.catalogTypeData.remove(key: prevMetadata.nftType.identifier)
298 } else {
299 self.catalogTypeData[prevMetadata.nftType.identifier] = prevCollectionsForType
300 }
301 }
302
303 init() {
304 self.ProposalManagerStoragePath = /storage/FINDnftCatalogProposalManager
305 self.ProposalManagerPublicPath = /public/FINDnftCatalogProposalManager
306
307 self.totalProposals = 0
308 self.catalog = {}
309 self.catalogTypeData = {}
310
311 self.catalogProposals = {}
312 }
313}
314