Smart Contract
Dandy
A.097bafa4e0b48eef.Dandy
1import NonFungibleToken from 0x1d7e57aa55817448
2import FungibleToken from 0xf233dcee88fe0abe
3import MetadataViews from 0x1d7e57aa55817448
4import FindViews from 0x097bafa4e0b48eef
5import FindForge from 0x097bafa4e0b48eef
6import ViewResolver from 0x1d7e57aa55817448
7
8access(all) contract Dandy :NonFungibleToken{
9
10 // Paths
11 access(all) let CollectionStoragePath: StoragePath
12 access(all) let CollectionPublicPath: PublicPath
13 access(all) var totalSupply: UInt64
14
15 /*store all valid type converters for Dandys
16 This is to be able to make the contract compatible with the forthcomming NFT standard.
17
18 If a Dandy supports a type with the same Identifier as a key here all the ViewConverters convertTo types are added to the list of available types
19 When resolving a type if the Dandy does not itself support this type check if any viewConverters do
20 */
21 access(account) var viewConverters: {String: [{ViewConverter}]}
22
23 // Events
24 access(all) event ContractInitialized()
25 access(all) event Minted(id:UInt64, minter:String, name:String, description:String)
26
27 access(all) struct ViewInfo {
28 access(contract) let typ: Type
29 access(contract) let result: AnyStruct
30
31 init(typ:Type, result:AnyStruct) {
32 self.typ=typ
33 self.result=result
34 }
35 }
36
37 access(all) struct DandyInfo {
38 access(all) let name: String
39 access(all) let description: String
40 access(all) let thumbnail: MetadataViews.Media
41 access(all) let schemas: [AnyStruct]
42 access(all) let externalUrlPrefix:String?
43
44 init(name: String, description: String, thumbnail: MetadataViews.Media, schemas: [AnyStruct], externalUrlPrefix:String?) {
45 self.name=name
46 self.description=description
47 self.thumbnail=thumbnail
48 self.schemas=schemas
49 self.externalUrlPrefix=externalUrlPrefix
50 }
51 }
52 access(all) resource NFT: NonFungibleToken.NFT {
53 access(all) let id: UInt64
54 access(self) var nounce: UInt64
55
56 access(self) var primaryCutPaid: Bool
57 access(contract) let schemas: {String : ViewInfo}
58 access(contract) let name: String
59 access(contract) let description: String
60 access(contract) let thumbnail: MetadataViews.Media
61 access(contract) let platform: FindForge.MinterPlatform
62
63 init(name: String, description: String, thumbnail: MetadataViews.Media, schemas: {String: ViewInfo}, platform: FindForge.MinterPlatform, externalUrlPrefix: String?) {
64 self.id = self.uuid
65 self.schemas=schemas
66 self.thumbnail=thumbnail
67 self.name=name
68 self.description=description
69 self.nounce=0
70 self.primaryCutPaid=false
71 self.platform=platform
72 if externalUrlPrefix != nil {
73 let mvt = Type<MetadataViews.ExternalURL>()
74 self.schemas[mvt.identifier] = ViewInfo(typ:mvt, result: MetadataViews.ExternalURL(externalUrlPrefix!.concat("/").concat(self.id.toString())))
75 }
76 }
77
78
79 access(all) view fun getID() : UInt64{
80 return self.id
81 }
82
83 access(contract) fun increaseNounce() {
84 self.nounce=self.nounce+1
85 }
86
87 access(all) fun getMinterPlatform() : FindForge.MinterPlatform {
88 if let fetch = FindForge.getMinterPlatform(name: self.platform.name, forgeType: Dandy.getForgeType()) {
89 let platform = &self.platform as &FindForge.MinterPlatform
90 platform.updateExternalURL(fetch.externalURL)
91 platform.updateDesription(fetch.description)
92 platform.updateSquareImagen(fetch.squareImage)
93 platform.updateBannerImage(fetch.bannerImage)
94 platform.updateSocials(fetch.socials)
95 }
96
97 return self.platform
98 }
99
100 access(all) view fun getViews() : [Type] {
101
102 let views = [
103 Type<FindViews.Nounce>(),
104 Type<MetadataViews.NFTCollectionData>(),
105 Type<MetadataViews.NFTCollectionDisplay>(),
106 Type<MetadataViews.Display>(),
107 Type<MetadataViews.Royalties>()]
108
109 for s in self.schemas.keys {
110 if !views.contains(self.schemas[s]!.typ) {
111 views.concat([self.schemas[s]!.typ])
112 }
113 }
114
115 return views
116 }
117
118 access(self) fun resolveRoyalties() : MetadataViews.Royalties {
119 let royalties : [MetadataViews.Royalty] = []
120
121 if self.schemas.containsKey(Type<MetadataViews.Royalties>().identifier) {
122 let multipleRoylaties=self.schemas[Type<MetadataViews.Royalties>().identifier]!.result as! MetadataViews.Royalties
123 royalties.appendAll(multipleRoylaties.getRoyalties())
124 }
125
126 if self.platform.minterCut != nil && self.platform.minterCut! != 0.0 {
127 let royalty = MetadataViews.Royalty(receiver: self.platform.getMinterFTReceiver(), cut: self.platform.minterCut!, description: "creator")
128 royalties.append(royalty)
129 }
130
131 if self.platform.platformPercentCut != 0.0 {
132 let royalty = MetadataViews.Royalty(receiver: self.platform.platform, cut: self.platform.platformPercentCut, description: "find forge")
133 royalties.append(royalty)
134 }
135
136 return MetadataViews.Royalties(royalties)
137 }
138
139 access(all) fun resolveDisplay() : MetadataViews.Display {
140 return MetadataViews.Display(
141 name: self.name,
142 description: self.description,
143 thumbnail: self.thumbnail.file
144 )
145 }
146
147 //Note that when resolving schemas shared data are loaded last, so use schema names that are unique. ie prefix with shared/ or something
148 //NB! This will _not_ error out if it does not return Optional!
149 access(all) fun resolveView(_ type: Type): AnyStruct? {
150
151 if type == Type<MetadataViews.NFTCollectionDisplay>() {
152 let minterPlatform = self.getMinterPlatform()
153 let externalURL = MetadataViews.ExternalURL(minterPlatform.externalURL)
154 let squareImage = MetadataViews.Media(file: MetadataViews.HTTPFile(url: minterPlatform.squareImage), mediaType: "image")
155 let bannerImage = MetadataViews.Media(file: MetadataViews.HTTPFile(url: minterPlatform.bannerImage), mediaType: "image")
156
157 let socialMap : {String : MetadataViews.ExternalURL} = {}
158 for social in minterPlatform.socials.keys {
159 socialMap[social] = MetadataViews.ExternalURL(minterPlatform.socials[social]!)
160 }
161 return MetadataViews.NFTCollectionDisplay(name: minterPlatform.name, description: minterPlatform.description, externalURL: externalURL, squareImage: squareImage, bannerImage: bannerImage, socials: socialMap)
162 }
163
164
165 if type == Type<FindViews.Nounce>() {
166 return FindViews.Nounce(self.nounce)
167 }
168
169 if type == Type<MetadataViews.Royalties>() {
170 return self.resolveRoyalties()
171 }
172
173 if type == Type<MetadataViews.Display>() {
174 return self.resolveDisplay()
175 }
176
177
178 if type == Type<MetadataViews.NFTCollectionData>() {
179 return Dandy.resolveContractView(resourceType: Type<@NFT>(), viewType: Type<MetadataViews.NFTCollectionData>())
180 }
181
182 if self.schemas.keys.contains(type.identifier) {
183 return self.schemas[type.identifier]!.result
184 }
185 return nil
186 }
187
188 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
189 return <-Dandy.createEmptyCollection(nftType: Type<@Dandy.NFT>())
190 }
191 }
192
193 access(all) view fun getContractViews(resourceType: Type?): [Type] {
194 return [
195 Type<MetadataViews.NFTCollectionData>()
196 ]
197 }
198
199 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
200 switch viewType {
201 case Type<MetadataViews.NFTCollectionData>():
202 let collectionData = MetadataViews.NFTCollectionData(
203 storagePath: Dandy.CollectionStoragePath,
204 publicPath: Dandy.CollectionPublicPath,
205 publicCollection: Type<&Dandy.Collection>(),
206 publicLinkedType: Type<&Dandy.Collection>(),
207 createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
208 return <-Dandy.createEmptyCollection(nftType: Type<@Dandy.NFT>())
209 })
210 )
211 return collectionData
212 }
213 return nil
214 }
215
216
217 access(all) resource interface CollectionPublic {
218 access(all) fun getIDsFor(minter: String): [UInt64]
219 access(all) fun getMinters(): [String]
220 }
221
222 access(all) resource Collection: NonFungibleToken.Collection, CollectionPublic, ViewResolver.ResolverCollection {
223 // dictionary of NFT conforming tokens
224 // NFT is a resource type with an `UInt64` ID field
225 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
226
227 // Mapping of {Minter Platform Name : [NFT ID]}
228 access(self) let nftIndex: {String : {UInt64 : Bool}}
229
230
231 init () {
232 self.ownedNFTs <- {}
233 self.nftIndex = {}
234 }
235
236
237 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
238 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT. withdrawID : ".concat(withdrawID.toString()))
239 let dandyToken <- token as! @NFT
240 let minterPlatform = dandyToken.getMinterPlatform()
241 let minterName = minterPlatform.name
242 if self.nftIndex.containsKey(minterName) {
243 self.nftIndex[minterName]!.remove(key: withdrawID)
244 if self.nftIndex[minterName]!.length < 1 {
245 self.nftIndex.remove(key: minterName)
246 }
247 }
248
249 return <- dandyToken
250 }
251
252 // deposit takes a NFT and adds it to the collections dictionary
253 // and adds the ID to the id array
254 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
255 let token <- token as! @Dandy.NFT
256
257 let minterPlatform = token.getMinterPlatform()
258 let minterName = minterPlatform.name
259 if self.nftIndex.containsKey(minterName) {
260 self.nftIndex[minterName]!.insert(key: token.id, false)
261 } else {
262 self.nftIndex[minterName] = {}
263 self.nftIndex[minterName]!.insert(key: token.id, false)
264 }
265
266 token.increaseNounce()
267
268 let id: UInt64 = token.id
269
270 // add the new token to the dictionary which removes the old one
271 let oldToken <- self.ownedNFTs[id] <- token
272
273 destroy oldToken
274 }
275
276 access(all) fun getMinters(): [String] {
277 return self.nftIndex.keys
278 }
279
280 access(all) fun getIDsFor(minter: String): [UInt64] {
281 return self.nftIndex[minter]?.keys ?? []
282 }
283
284 // getIDs returns an array of the IDs that are in the collection
285 access(all) view fun getIDs(): [UInt64] {
286 return self.ownedNFTs.keys
287 }
288
289 // borrowNFT gets a reference to an NFT in the collection
290 // so that the caller can read its metadata and call its methods
291 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
292 return &self.ownedNFTs[id]
293 }
294
295 /// Borrow the view resolver for the specified NFT ID
296 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
297 return &self.ownedNFTs[id]
298 }
299
300 access(all) view fun getDefaultStoragePath() : StoragePath {
301 return Dandy.CollectionStoragePath
302 }
303
304 access(all) view fun getDefaultPublicPath() : PublicPath {
305 return Dandy.CollectionPublicPath
306 }
307
308 access(all) view fun getIDsWithTypes(): {Type: [UInt64]} {
309 return { Type<@NFT>() : self.ownedNFTs.keys}
310 }
311
312 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
313 return { Type<@NFT>() : true}
314 }
315
316 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
317 return <- create Collection()
318 }
319
320 access(all) view fun getLength() : Int {
321 return self.ownedNFTs.length
322 }
323
324 access(all) view fun isSupportedNFTType(type: Type) : Bool {
325 return type == Type<@NFT>()
326 }
327
328 }
329
330 access(account) fun mintNFT(name: String, description: String, thumbnail: MetadataViews.Media, platform:FindForge.MinterPlatform, schemas: [AnyStruct], externalUrlPrefix:String?) : @NFT {
331 let views : {String: ViewInfo} = {}
332 for s in schemas {
333 //if you send in display we ignore it, this will be made for you
334 if s.getType() != Type<MetadataViews.Display>() {
335 views[s.getType().identifier]=ViewInfo(typ:s.getType(), result: s)
336 }
337 }
338
339 let nft <- create NFT(name: name, description:description,thumbnail: thumbnail, schemas:views, platform: platform, externalUrlPrefix:externalUrlPrefix)
340
341 emit Minted(id:nft.id, minter:nft.platform.name, name: name, description:description)
342 return <- nft
343 }
344
345 access(all) resource Forge: FindForge.Forge {
346 access(FindForge.ForgeOwner) fun mint(platform: FindForge.MinterPlatform, data: AnyStruct, verifier: &FindForge.Verifier) : @{NonFungibleToken.NFT} {
347 let info = data as? DandyInfo ?? panic("The data passed in is not in form of DandyInfo.")
348 return <- Dandy.mintNFT(name: info.name, description: info.description, thumbnail: info.thumbnail, platform: platform, schemas: info.schemas, externalUrlPrefix:info.externalUrlPrefix)
349 }
350
351 access(FindForge.ForgeOwner) fun addContractData(platform: FindForge.MinterPlatform, data: AnyStruct, verifier: &FindForge.Verifier) {
352 // not used here
353 panic("Not supported for Dandy Contract")
354 }
355 }
356
357 access(account) fun createForge() : @{FindForge.Forge} {
358 return <- create Forge()
359 }
360
361
362 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
363
364 return <- create Collection()
365 }
366
367 access(all) fun getForgeType() : Type {
368 return Type<@Forge>()
369 }
370
371 /// This struct interface is used on a contract level to convert from one View to another.
372 /// See Dandy nft for an example on how to convert one type to another
373 access(all) struct interface ViewConverter {
374 access(all) let to: Type
375 access(all) let from: Type
376 access(all) fun convert(_ value:AnyStruct) : AnyStruct
377 }
378
379 init() {
380 // Initialize the total supply
381 self.totalSupply=0
382 self.CollectionPublicPath = /public/findDandy
383 self.CollectionStoragePath = /storage/findDandy
384 self.viewConverters={}
385
386 FindForge.addForgeType(<- create Forge())
387
388 FindForge.addPublicForgeType(forgeType: Type<@Forge>())
389
390 emit ContractInitialized()
391 }
392}
393