Smart Contract

Dandy

A.097bafa4e0b48eef.Dandy

Deployed

1d ago
Feb 26, 2026, 03:12:51 AM UTC

Dependents

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