Smart Contract
YahooCollectible
A.758252ab932a3416.YahooCollectible
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}