diff --git a/contracts/BasicNFT.cdc b/contracts/BasicNFT.cdc new file mode 100644 index 0000000..5b8a0a9 --- /dev/null +++ b/contracts/BasicNFT.cdc @@ -0,0 +1,137 @@ +/* +* +* This is an basic implementation of a Flow Non-Fungible Token using the V2 standard. +* It shows that a basic NFT can be defined in very few lines of code (less than 100 here) +* +* Unlike the `ExampleNFT-v2` contract, this NFT illustrates a minimal implementation +* of an NFT that is now possible with the NFT standard since Events, collections, +* and other old requirements are not required any more. +* +* It also includes minimal metadata to showcase the simplicity +* +*/ + +import "NonFungibleToken" +import "MetadataViews" +import "ViewResolver" +import "UniversalCollection" + +access(all) contract BasicNFT: NonFungibleToken { + + /// The only thing that an NFT really needs to have is this resource definition + access(all) resource NFT: NonFungibleToken.NFT { + /// Arbitrary trait mapping metadata + access(self) let metadata: {String: AnyStruct} + + access(all) let id: UInt64 + + init( + metadata: {String: AnyStruct}, + ) { + self.id = self.uuid + self.metadata = metadata + } + + access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} { + return <- BasicNFT.createEmptyCollection(nftType: self.getType()) + } + + /// Uses the basic NFT views + access(all) view fun getViews(): [Type] { + return [ + Type(), + Type(), + Type(), + Type(), + Type() + ] + } + + access(all) fun resolveView(_ view: Type): AnyStruct? { + switch view { + case Type(): + return MetadataViews.Display( + name: self.metadata["name"] as! String, + description: self.metadata["description"] as! String, + thumbnail: MetadataViews.HTTPFile( + url: self.metadata["thumbnail"] as! String + ) + ) + case Type(): + return MetadataViews.Serial( + self.id + ) + case Type(): + return MetadataViews.dictToTraits(dict: self.metadata, excludedNames: nil) + case Type(): + return BasicNFT.resolveContractView(resourceType: nil, viewType: Type()) + case Type(): + return BasicNFT.resolveContractView(resourceType: nil, viewType: Type()) + } + return nil + } + } + + access(all) view fun getContractViews(resourceType: Type?): [Type] { + return [ + Type(), + Type() + ] + } + + access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? { + switch viewType { + case Type(): + let collectionRef = self.account.storage.borrow<&UniversalCollection.Collection>( + from: /storage/flowBasicNFTCollection + ) ?? panic("Could not borrow a reference to the stored collection") + let collectionData = MetadataViews.NFTCollectionData( + storagePath: collectionRef.storagePath, + publicPath: collectionRef.publicPath, + publicCollection: Type<&UniversalCollection.Collection>(), + publicLinkedType: Type<&UniversalCollection.Collection>(), + createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} { + return <-BasicNFT.createEmptyCollection(nftType: Type<@BasicNFT.NFT>()) + }) + ) + return collectionData + case Type(): + let media = MetadataViews.Media( + file: MetadataViews.HTTPFile( + url: "https://assets.website-files.com/5f6294c0c7a8cdd643b1c820/5f6294c0c7a8cda55cb1c936_Flow_Wordmark.svg" + ), + mediaType: "image/svg+xml" + ) + return MetadataViews.NFTCollectionDisplay( + name: "The Example Collection", + description: "This collection is used as an example to help you develop your next Flow NFT.", + externalURL: MetadataViews.ExternalURL("https://example-nft.onflow.org"), + squareImage: media, + bannerImage: media, + socials: { + "twitter": MetadataViews.ExternalURL("https://twitter.com/flow_blockchain") + } + ) + } + return nil + } + + access(all) resource NFTMinter { + access(all) fun mintNFT(metadata: {String: AnyStruct}): @BasicNFT.NFT { + return <- create NFT(metadata: metadata) + } + } + + access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} { + return <- UniversalCollection.createEmptyCollection(identifier: "flowBasicNFTCollection", type: Type<@BasicNFT.NFT>()) + } + + init() { + let minter <- create NFTMinter() + self.account.storage.save(<-minter, to: /storage/flowBasicNFTMinterPath) + + let collection <- self.createEmptyCollection(nftType: Type<@BasicNFT.NFT>()) + self.account.storage.save(<-collection, to: /storage/flowBasicNFTCollection) + } +} + \ No newline at end of file diff --git a/contracts/UniversalCollection.cdc b/contracts/UniversalCollection.cdc new file mode 100644 index 0000000..a7b3428 --- /dev/null +++ b/contracts/UniversalCollection.cdc @@ -0,0 +1,111 @@ +/* +* +* This is an example collection that can store any one type of NFT +* The Collection is restricted to one NFT type. +* This allows developers to write NFT contracts without having +* to also write all of the Collection boilerplate code, +* saving many lines of code. +* +*/ + +import "NonFungibleToken" +import "MetadataViews" +import "ViewResolver" + +access(all) contract UniversalCollection { + + /// The typical Collection resource, but one that anyone can use + /// + access(all) resource Collection: NonFungibleToken.Collection { + + /// every Universal collection supports a single type + /// All deposits and withdrawals must be of this type + access(all) let supportedType : Type + + /// The path identifier + access(all) let identifier: String + + /// Dictionary mapping NFT IDs to the stored NFTs + access(contract) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}} + + access(all) var storagePath: StoragePath + access(all) var publicPath: PublicPath + + access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} { + return <- create Collection(identifier: self.identifier, type: self.supportedType) + } + + init (identifier: String, type:Type) { + self.ownedNFTs <- {} + self.identifier = identifier + self.supportedType = type + self.storagePath = StoragePath(identifier: identifier)! + self.publicPath = PublicPath(identifier: identifier)! + } + + /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts + access(all) view fun getSupportedNFTTypes(): {Type: Bool} { + let supportedTypes: {Type: Bool} = {} + supportedTypes[self.supportedType] = true + return supportedTypes + } + + /// Returns whether or not the given type is accepted by the collection + access(all) view fun isSupportedNFTType(type: Type): Bool { + if type == self.supportedType { + return true + } else { + return false + } + } + + /// withdraw removes an NFT from the collection and moves it to the caller + access(NonFungibleToken.Withdraw | NonFungibleToken.Owner) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} { + let token <- self.ownedNFTs.remove(key: withdrawID) + ?? panic("Could not withdraw an NFT with the ID: ".concat(withdrawID.toString()).concat(" from the collection")) + + return <-token + } + + /// deposit takes a NFT and adds it to the collections dictionary + /// and adds the ID to the id array + access(all) fun deposit(token: @{NonFungibleToken.NFT}) { + if self.supportedType != token.getType() { + panic("Cannot deposit an NFT of the given type") + } + + // add the new token to the dictionary which removes the old one + let oldToken <- self.ownedNFTs[token.id] <- token + destroy oldToken + } + + /// getIDs returns an array of the IDs that are in the collection + access(all) view fun getIDs(): [UInt64] { + return self.ownedNFTs.keys + } + + /// getLength retusnt the number of items in the collection + access(all) view fun getLength(): Int { + return self.ownedNFTs.length + } + + /// Borrows a reference to an NFT in the collection if it is there + /// otherwise, returns `nil` + access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? { + return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?) + } + + /// Borrow the view resolver for the specified NFT ID + access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? { + return (&self.ownedNFTs[id] as &{ViewResolver.Resolver}?)! + } + } + + /// Public function that anyone can call to create + /// a new empty collection with the specified type restriction + /// NFT contracts can include a call to this method in + /// their own createEmptyCollection method + access(all) fun createEmptyCollection(identifier: String, type: Type): @{NonFungibleToken.Collection} { + return <- create Collection(identifier: identifier, type:type) + } +} \ No newline at end of file