Smart Contract

YahooCollectible

A.758252ab932a3416.YahooCollectible

Deployed

1w ago
Feb 20, 2026, 02:41:54 AM UTC

Dependents

25 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import ViewResolver from 0x1d7e57aa55817448
3import NonFungibleToken from 0x1d7e57aa55817448
4import MetadataViews from 0x1d7e57aa55817448
5
6access(all)
7contract YahooCollectible: NonFungibleToken {
8    access(all) entitlement AdminEntitlement
9
10    // Events
11    //
12    access(all)
13    event ContractInitialized()
14
15    access(all)
16    event Withdraw(id: UInt64, from: Address?)
17
18    access(all)
19    event Deposit(id: UInt64, to: Address?)
20
21    access(all)
22    event Minted(id: UInt64)
23
24    // Named Paths
25    //
26    access(all)
27    let CollectionStoragePath: StoragePath
28
29    access(all)
30    let CollectionPublicPath: PublicPath
31
32    access(all)
33    let AdminStoragePath: StoragePath
34    
35    // totalSupply
36    // The total number of YahooCollectible that have been minted
37    //
38    access(all)
39    var totalSupply: UInt64
40
41    // metadata for each item
42    // 
43    access(contract)
44    var itemMetadata: {UInt64: Metadata}
45
46    // Type Definitions
47    // 
48    access(all)
49    struct Metadata {
50        access(all)
51        let name: String
52
53        access(all)
54        let description: String
55
56        // mediaType: MIME type of the media
57        // - image/png
58        // - image/jpeg
59        // - video/mp4
60        // - audio/mpeg
61        access(all)
62        let mediaType: String
63
64        // mediaHash: IPFS storage hash
65        access(all)
66        let mediaHash: String
67
68        // additional metadata
69        access(self)
70        let additional: {String: String}
71
72        // number of items
73        access(all)
74        var itemCount: UInt64
75
76        init(name: String, description: String, mediaType: String, mediaHash: String, additional: {String: String}) {
77            self.name = name
78            self.description = description
79            self.mediaType = mediaType
80            self.mediaHash = mediaHash
81            self.additional = additional
82            self.itemCount = 0
83        }
84
85        access(all)
86        fun getAdditional(): {String: String} {
87            return self.additional
88        }
89
90        access(contract)
91        fun setItemCount(_ itemCount: UInt64) {
92            self.itemCount = itemCount
93        }
94    }
95
96    // NFT
97    // A Yahoo Collectible NFT
98    //
99    access(all)
100    resource NFT: NonFungibleToken.NFT {
101        // The token's ID
102        access(all)
103        let id: UInt64
104        
105        // The token's type
106        access(all)
107        let itemID: UInt64
108        
109        // The token's edition number
110        access(all)
111        let editionNumber: UInt64
112        
113        // initializer
114        //
115        init(initID: UInt64, itemID: UInt64) {
116            self.id = initID
117            self.itemID = itemID
118
119            let metadata = YahooCollectible.itemMetadata[itemID] ?? panic("itemID not valid")
120            self.editionNumber = metadata.itemCount + 1
121
122            // Increment the edition count by 1
123            metadata.setItemCount(self.editionNumber)
124
125            YahooCollectible.itemMetadata[itemID] = metadata
126        }
127
128        // Expose metadata
129        access(all)
130        fun getMetadata(): Metadata? {
131            return YahooCollectible.itemMetadata[self.itemID]
132        }
133
134        access(all)
135        fun getRoyalties(): MetadataViews.Royalties {
136            var royalties: [MetadataViews.Royalty] = []
137            let receiver = getAccount(0xfcf3a236f4cd7dbc).capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
138
139            royalties.append(
140                MetadataViews.Royalty(
141                    receiver: receiver,
142                    cut: 0.1,
143                    description: "Royalty receiver for Yahoo"
144                )
145            )
146
147            return MetadataViews.Royalties(
148                royalties
149            )
150        }
151
152        access(all)
153        fun getEditions(): MetadataViews.Editions {
154            let metadata = self.getMetadata() ?? panic("missing metadata")
155
156            let editionInfo = MetadataViews.Edition(name: metadata.name, number: self.editionNumber, max: metadata.itemCount)
157            let editionList: [MetadataViews.Edition] = [editionInfo]
158
159            return MetadataViews.Editions(
160                editionList
161            )
162        }
163
164        access(all)
165        fun getExternalURL(): MetadataViews.ExternalURL {
166            return MetadataViews.ExternalURL("https://bay.blocto.app/flow/yahoo/".concat(self.id.toString()))
167        }
168
169        access(all)
170        fun getCollectionData(): MetadataViews.NFTCollectionData {
171            return MetadataViews.NFTCollectionData(
172                storagePath: YahooCollectible.CollectionStoragePath,
173                publicPath: YahooCollectible.CollectionPublicPath,
174                publicCollection: Type<&YahooCollectible.Collection>(),
175                publicLinkedType: Type<&YahooCollectible.Collection>(),
176                createEmptyCollectionFunction: fun (): @{NonFungibleToken.Collection} {
177                    return <-YahooCollectible.createEmptyCollection(nftType: Type<@YahooCollectible.Collection>())
178                })
179        }
180
181        access(all)
182        fun getCollectionDisplay(): MetadataViews.NFTCollectionDisplay {
183            let squareImage = MetadataViews.Media(
184                file: MetadataViews.HTTPFile(
185                    url: "https://raw.githubusercontent.com/portto/assets/main/nft/flow/yahoo/logo.png"),
186                mediaType: "image/png"
187            )
188            let bannerImager = MetadataViews.Media(
189                file: MetadataViews.HTTPFile(
190                    url: "https://raw.githubusercontent.com/portto/assets/main/nft/flow/yahoo/banner.png"),
191                mediaType: "image/png"
192            )
193            return MetadataViews.NFTCollectionDisplay(
194                name: "Yahoo",
195                description: "NFT collection of Yahoo Taiwan",
196                externalURL: MetadataViews.ExternalURL("https://bay.blocto.app/market?collections=yahoo"),
197                squareImage: squareImage,
198                bannerImage: bannerImager,
199                socials: {
200                    "twitter": MetadataViews.ExternalURL("https://twitter.com/Yahoo")
201                }
202            )
203        }
204
205        access(all)
206        fun getTraits(): MetadataViews.Traits {
207            let metadata = self.getMetadata() ?? panic("missing metadata")
208
209            return MetadataViews.dictToTraits(dict: metadata.getAdditional(), excludedNames: [])
210        }
211
212        access(all)
213        fun getMedias(): MetadataViews.Medias {
214            let metadata = self.getMetadata() ?? panic("missing metadata")
215
216            let file = MetadataViews.IPFSFile(
217                cid: metadata.mediaHash,
218                path: nil
219            )
220            let mediaInfo = MetadataViews.Media(file: file, mediaType: metadata.mediaType)
221            let mediaList: [MetadataViews.Media] = [mediaInfo]
222
223            return MetadataViews.Medias(
224                mediaList
225            )
226        }
227
228        access(all)
229        view fun getViews(): [Type] {
230            return [
231                Type<MetadataViews.Display>(),
232                Type<MetadataViews.Royalties>(),
233                Type<MetadataViews.Editions>(),
234                Type<MetadataViews.ExternalURL>(),
235                Type<MetadataViews.NFTCollectionData>(),
236                Type<MetadataViews.NFTCollectionDisplay>(),
237                Type<MetadataViews.IPFSFile>(),
238                Type<MetadataViews.Traits>(),
239                Type<MetadataViews.Medias>()
240            ]
241        }
242
243        access(all)
244        fun resolveView(_ view: Type): AnyStruct? {
245            switch view {
246                case Type<MetadataViews.Display>():
247                    let metadata = self.getMetadata() ?? panic("missing metadata")
248
249                    return MetadataViews.Display(
250                        name: metadata.name,
251                        description: metadata.description,
252                        thumbnail: MetadataViews.IPFSFile(
253                            cid: metadata.mediaHash,
254                            path: nil
255                        )
256                    )
257
258                case Type<MetadataViews.Royalties>():
259                    return self.getRoyalties()
260
261                case Type<MetadataViews.Editions>():
262                    return self.getEditions()
263
264                case Type<MetadataViews.ExternalURL>():
265                    return self.getExternalURL()
266
267                case Type<MetadataViews.NFTCollectionData>():
268                    return self.getCollectionData()
269
270                case Type<MetadataViews.NFTCollectionDisplay>():
271                    return self.getCollectionDisplay()
272
273                case Type<MetadataViews.IPFSFile>():
274                    return MetadataViews.IPFSFile(
275                        cid: (self.getMetadata()!).mediaHash,
276                        path: nil
277                    )
278
279                case Type<MetadataViews.Traits>():
280                    return self.getTraits()
281
282                case Type<MetadataViews.Medias>():
283                    return self.getMedias()
284            }
285
286            return nil
287        }
288
289        access(all)
290        fun createEmptyCollection(): @{NonFungibleToken.Collection} {
291            return <-create Collection()
292        }
293    }
294
295    access(all)
296    resource interface CollectionPublic {
297        access(all)
298        fun deposit(token: @{NonFungibleToken.NFT}): Void
299
300        access(all)
301        view fun getIDs(): [UInt64]
302
303        access(all)
304        view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
305
306        access(all)
307        fun borrowYahooCollectible(id: UInt64): &YahooCollectible.NFT? {
308            // If the result isn't nil, the id of the returned reference
309            // should be the same as the argument to the function
310            post {
311                result == nil || result?.id == id:
312                    "Cannot borrow YahooCollectible reference: The ID of the returned reference is incorrect"
313            }
314        }
315    }
316
317    // Collection
318    // A collection of YahooCollectible NFTs owned by an account
319    //
320    access(all)
321    resource Collection: NonFungibleToken.Collection, CollectionPublic {
322        // dictionary of NFT conforming tokens
323        // NFT is a resource type with an `UInt64` ID field
324        //
325        access(all)
326        var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
327
328        // withdraw
329        // Removes an NFT from the collection and moves it to the caller
330        //
331        access(NonFungibleToken.Withdraw)
332        fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
333            let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
334
335            emit Withdraw(id: token.id, from: self.owner?.address)
336
337            return <-token
338        }
339
340        // deposit
341        // Takes a NFT and adds it to the collections dictionary
342        // and adds the ID to the id array
343        //
344        access(all)
345        fun deposit(token: @{NonFungibleToken.NFT}) {
346            let token <- token as! @YahooCollectible.NFT
347
348            let id: UInt64 = token.id
349
350            // add the new token to the dictionary which removes the old one
351            let oldToken <- self.ownedNFTs[id] <- token
352
353            emit Deposit(id: id, to: self.owner?.address)
354
355            destroy oldToken
356        }
357
358        // getIDs
359        // Returns an array of the IDs that are in the collection
360        //
361        access(all)
362        view fun getIDs(): [UInt64] {
363            return self.ownedNFTs.keys
364        }
365
366        // borrowNFT
367        // Gets a reference to an NFT in the collection
368        // so that the caller can read its metadata and call its methods
369        //
370        access(all)
371        view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
372            return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
373        }
374
375        // borrowYahooCollectible
376        // Gets a reference to an NFT in the collection as a YahooCollectible,
377        // exposing all of its fields.
378        // This is safe as there are no functions that can be called on the YahooCollectible.
379        //
380        access(all)
381        fun borrowYahooCollectible(id: UInt64): &YahooCollectible.NFT? {
382            if self.ownedNFTs[id] != nil {
383                let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
384                return ref as! &YahooCollectible.NFT
385            } else {
386                return nil
387            }
388        }
389
390        // borrowViewResolver
391        // Gets a reference to an MetadataView.Resolver in the collection as a YahooCollectible.
392        // This is safe as there are no functions that can be called on the YahooCollectible.
393        //
394        access(all)
395        view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
396            let nft = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
397            let exampleNFT = nft as! &YahooCollectible.NFT
398            return exampleNFT as &{ViewResolver.Resolver}
399        }
400
401        access(all)
402        view fun getSupportedNFTTypes(): {Type: Bool} {
403            let supportedTypes: {Type: Bool} = {}
404            supportedTypes[Type<@NFT>()] = true
405            return supportedTypes
406        }
407
408        access(all)
409        view fun isSupportedNFTType(type: Type): Bool {
410            return type == Type<@NFT>()
411        }
412
413        access(all)
414        fun createEmptyCollection(): @{NonFungibleToken.Collection} {
415            return <-create Collection()
416        }
417
418        // destructor
419        // initializer
420        //
421        init() {
422            self.ownedNFTs <- {} 
423        }
424    }
425
426    // createEmptyCollection
427    // public function that anyone can call to create a new empty collection
428    //
429    access(all)
430    fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
431        return <-create Collection()
432    }
433
434    // Admin
435    // Resource that an admin or something similar would own to be
436    // able to mint new NFTs
437    //
438    access(all)
439    resource Admin {
440
441        // mintNFT
442        // Mints a new NFT with a new ID
443        // and deposit it in the recipients collection using their collection reference
444        //
445        access(AdminEntitlement)
446        fun mintNFT(recipient: &{NonFungibleToken.CollectionPublic}, itemID: UInt64, codeHash: String?) {
447            pre {
448                codeHash == nil || !YahooCollectible.checkCodeHashUsed(codeHash: codeHash!):
449                    "duplicated codeHash"
450            }
451            emit Minted(id: YahooCollectible.totalSupply)
452
453            // deposit it in the recipient's account using their reference
454            recipient.deposit(token: <-create YahooCollectible.NFT(initID: YahooCollectible.totalSupply, itemID: itemID))
455            YahooCollectible.totalSupply = YahooCollectible.totalSupply + 1
456
457            // if minter passed in codeHash, register it to dictionary
458            if let checkedCodeHash = codeHash {
459                let redeemedCodes = YahooCollectible.account.storage.load<{String: Bool}>(from: /storage/redeemedCodes)!
460                redeemedCodes[checkedCodeHash] = true
461                YahooCollectible.account.storage.save<{String: Bool}>(redeemedCodes, to: /storage/redeemedCodes)
462            }
463        }
464
465        // batchMintNFT
466        // Mints a batch of new NFTs
467        // and deposit it in the recipients collection using their collection reference
468        //
469        access(AdminEntitlement)
470        fun batchMintNFT(recipient: &{NonFungibleToken.CollectionPublic}, itemID: UInt64, count: Int) {
471            var index = 0
472
473            while index < count {
474                self.mintNFT(
475                    recipient: recipient,
476                    itemID: itemID,
477                    codeHash: nil
478                )
479
480                index = index + 1
481            }
482        }
483
484        // registerMetadata
485        // Registers metadata for a itemID
486        //
487        access(AdminEntitlement)
488        fun registerMetadata(itemID: UInt64, metadata: Metadata) {
489            pre {
490                YahooCollectible.itemMetadata[itemID] == nil:
491                    "duplicated itemID"
492            }
493            YahooCollectible.itemMetadata[itemID] = metadata
494        }
495
496        // updateMetadata
497        // Registers metadata for a itemID
498        //
499        access(AdminEntitlement)
500        fun updateMetadata(itemID: UInt64, metadata: Metadata) {
501            pre {
502                YahooCollectible.itemMetadata[itemID] != nil:
503                    "itemID does not exist"
504            }
505            metadata.setItemCount((YahooCollectible.itemMetadata[itemID]!).itemCount)
506
507            // update metadata
508            YahooCollectible.itemMetadata[itemID] = metadata
509        }
510    }
511
512    // fetch
513    // Get a reference to a YahooCollectible from an account's Collection, if available.
514    // If an account does not have a YahooCollectible.Collection, panic.
515    // If it has a collection but does not contain the itemID, return nil.
516    // If it has a collection and that collection contains the itemID, return a reference to that.
517    //
518    access(all)
519    fun fetch(_ from: Address, itemID: UInt64): &YahooCollectible.NFT? {
520        let collection = getAccount(from)
521            .capabilities
522            .borrow<&YahooCollectible.Collection>(YahooCollectible.CollectionPublicPath)
523                ?? panic("Couldn't get collection")
524        // We trust YahooCollectible.Collection.borowYahooCollectible to get the correct itemID
525        // (it checks it before returning it).
526        return collection.borrowYahooCollectible(id: itemID)
527    }
528
529    // getMetadata
530    // Get the metadata for a specific type of YahooCollectible
531    //
532    access(all)
533    fun getMetadata(itemID: UInt64): Metadata? {
534        return YahooCollectible.itemMetadata[itemID]
535    }
536
537    // checkCodeHashUsed
538    // Check if a codeHash has been registered
539    //
540    access(all)
541    view fun checkCodeHashUsed(codeHash: String): Bool {
542        var redeemedCodes = YahooCollectible.account.storage.copy<{String: Bool}>(from: /storage/redeemedCodes)!
543        return redeemedCodes[codeHash] ?? false
544    }
545
546    access(all)
547    view fun getContractViews(resourceType: Type?): [Type] {
548        return [
549            Type<MetadataViews.NFTCollectionData>(),
550            Type<MetadataViews.NFTCollectionDisplay>()
551        ]
552    }
553
554    access(all)
555    view fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
556        return [
557            Type<MetadataViews.NFTCollectionData>(),
558            Type<MetadataViews.NFTCollectionDisplay>()
559        ]
560    }
561
562    // initializer
563    //
564    init() {
565        // Set our named paths
566        self.CollectionStoragePath = /storage/yahooCollectibleCollection
567        self.CollectionPublicPath = /public/yahooCollectibleCollection
568        self.AdminStoragePath = /storage/yahooCollectibleAdmin
569
570        // Initialize the total supply
571        self.totalSupply = 0
572
573        // Initialize predefined metadata
574        self.itemMetadata = {}
575
576        // Create a Admin resource and save it to storage
577        let minter <- create Admin()
578        self.account.storage.save(<-minter, to: self.AdminStoragePath)
579
580        emit ContractInitialized()
581    }
582}