Skip to content

Commit

Permalink
add support for multiple type definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuahannan committed Jan 22, 2024
1 parent b73ac84 commit ded9217
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 106 deletions.
32 changes: 16 additions & 16 deletions contracts/ExampleNFT.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
*/

import NonFungibleToken from "NonFungibleToken"
import MultipleNFT from "MultipleNFT"
import ViewResolver from "ViewResolver"
import MetadataViews from "MetadataViews"

Expand Down Expand Up @@ -102,9 +101,9 @@ access(all) contract ExampleNFT: ViewResolver {
case Type<MetadataViews.ExternalURL>():
return MetadataViews.ExternalURL("https://example-nft.onflow.org/".concat(self.id.toString()))
case Type<MetadataViews.NFTCollectionData>():
return ExampleNFT.getCollectionData(nftType: Type<@ExampleNFT.NFT>())
return ExampleNFT.resolveContractView(resourceType: Type<@ExampleNFT.NFT>(), viewType: Type<MetadataViews.NFTCollectionData>())
case Type<MetadataViews.NFTCollectionDisplay>():
return ExampleNFT.getCollectionDisplay(nftType: Type<@ExampleNFT.NFT>())
return ExampleNFT.resolveContractView(resourceType: Type<@ExampleNFT.NFT>(), viewType: Type<MetadataViews.NFTCollectionDisplay>())
case Type<MetadataViews.Traits>():
// exclude mintedTime and foo to show other uses of Traits
let excludedTraits = ["mintedTime", "foo"]
Expand All @@ -120,7 +119,6 @@ access(all) contract ExampleNFT: ViewResolver {
traitsView.addTrait(fooTrait)

return traitsView

}
return nil
}
Expand All @@ -141,10 +139,6 @@ access(all) contract ExampleNFT: ViewResolver {
self.publicPath = PublicPath(identifier: identifier)!
}

access(all) fun getNFTCollectionDataView(): AnyStruct {
return ExampleNFT.resolveContractView(Type<MetadataViews.NFTCollectionData>())
}

/// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
let supportedTypes: {Type: Bool} = {}
Expand All @@ -163,7 +157,7 @@ access(all) contract ExampleNFT: ViewResolver {
}

/// withdraw removes an NFT from the collection and moves it to the caller
access(NonFungibleToken.Withdrawable) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
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 provided ID from the collection")

Expand Down Expand Up @@ -202,12 +196,18 @@ access(all) contract ExampleNFT: ViewResolver {
}
return nil
}

/// createEmptyCollection creates an empty Collection of the same type
/// and returns it to the caller
/// @return A an empty collection of the same type
access(all) fun createEmptyCollection(): @{Collection} {
return <-ExampleNFT.createEmptyCollection(nftType: )
}
}

/// public function that anyone can call to create a new empty collection
/// Since multiple collection types can be defined in a contract,
/// The caller needs to specify which one they want to create
access(all) fun createEmptyCollection(): @ExampleNFT.Collection {
/// createEmptyCollection creates an empty Collection for the specified NFT type
/// and returns it to the caller so that they can own NFTs
access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
return <- create Collection()
}

Expand All @@ -216,7 +216,7 @@ access(all) contract ExampleNFT: ViewResolver {
/// @return An array of Types defining the implemented views. This value will be used by
/// developers to know which parameter to pass to the resolveView() method.
///
access(all) view fun getContractViews(): [Type] {
access(all) view fun getContractViews(resourceType: Type?): [Type] {
return [
Type<MetadataViews.NFTCollectionData>(),
Type<MetadataViews.NFTCollectionDisplay>()
Expand All @@ -228,7 +228,7 @@ access(all) contract ExampleNFT: ViewResolver {
/// @param view: The Type of the desired view.
/// @return A structure representing the requested view.
///
access(all) fun resolveContractView(_ view: Type): AnyStruct? {
access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
switch view {
case Type<MetadataViews.NFTCollectionData>():
let collectionRef = self.account.storage.borrow<&ExampleNFT.Collection>(
Expand All @@ -240,7 +240,7 @@ access(all) contract ExampleNFT: ViewResolver {
publicCollection: Type<&ExampleNFT.Collection>(),
publicLinkedType: Type<&ExampleNFT.Collection>(),
createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
return <-ExampleNFT.createEmptyCollection()
return <-collectionRef.createEmptyCollection()
})
)
return collectionData
Expand Down
9 changes: 3 additions & 6 deletions contracts/MetadataViews.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -605,14 +605,11 @@ access(all) contract MetadataViews {
/// including standard NFT interfaces and metadataviews interfaces
access(all) let publicPath: PublicPath

/// Public collection type that is expected to provide sufficient read-only access to standard
/// functions (deposit + getIDs + borrowNFT). For new
/// collections, this may be set to be equal to the type specified in `publicLinkedType`.
/// The concrete type of the collection that is exposed to the public
/// now that entitlements exist, it no longer needs to be restricted to a specific interface
access(all) let publicCollection: Type

/// Type that should be linked at the aforementioned public path. This is normally a
/// restricted type with many interfaces. Notably the
/// `NFT.Receiver`, and `ViewResolver.ResolverCollection` interfaces are required.
/// Type that should be linked at the aforementioned public path
access(all) let publicLinkedType: Type

/// Function that allows creation of an empty NFT collection that is intended to store
Expand Down
87 changes: 59 additions & 28 deletions contracts/NonFungibleToken.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,16 @@ import ViewResolver from "ViewResolver"
/// The main NFT contract. Other NFT contracts will
/// import and implement the interfaces defined in this contract
///
access(all) contract NonFungibleToken {
access(all) contract interface NonFungibleToken: ViewResolver {

/// An entitlement for allowing the withdrawal of tokens from a Vault
access(all) entitlement Withdrawable
access(all) entitlement Withdraw

/// An entitlement for allowing updates and update events for an NFT
access(all) entitlement Updatable
access(all) entitlement Update

/// entitlement for owner that grants Withdraw and Update
access(all) entitlement Owner

/// Event that contracts should emit when the metadata of an NFT is updated
/// It can only be emitted by calling the `emitNFTUpdated` function
Expand All @@ -60,7 +63,7 @@ access(all) contract NonFungibleToken {
/// and query the updated metadata from the owners' collections.
///
access(all) event Updated(id: UInt64, uuid: UInt64, owner: Address?, type: String)
access(all) view fun emitNFTUpdated(_ nftRef: auth(Updatable) &{NonFungibleToken.NFT})
access(all) view fun emitNFTUpdated(_ nftRef: auth(Update | Owner) &{NonFungibleToken.NFT})
{
emit Updated(id: nftRef.id, uuid: nftRef.uuid, owner: nftRef.owner?.address, type: nftRef.getType().identifier)
}
Expand All @@ -72,15 +75,18 @@ access(all) contract NonFungibleToken {
///
/// If the collection is not in an account's storage, `from` will be `nil`.
///
access(all) event Withdraw(type: String, id: UInt64, uuid: UInt64, from: Address?, providerUUID: UInt64)
access(all) event Withdrawn(type: String, id: UInt64, uuid: UInt64, from: Address?, providerUUID: UInt64)

/// Event that emitted when a token is deposited to a collection.
/// Indicates the type, id, uuid, the owner of the collection that it was deposited to,
/// and the UUID of the collection it was deposited to
///
/// If the collection is not in an account's storage, `from`, will be `nil`.
///
access(all) event Deposit(type: String, id: UInt64, uuid: UInt64, to: Address?, collectionUUID: UInt64)
access(all) event Deposited(type: String, id: UInt64, uuid: UInt64, to: Address?, collectionUUID: UInt64)

/// Included for backwards-compatibility
access(all) resource interface INFT: NFT {}

/// Interface that the NFTs must conform to
///
Expand All @@ -95,13 +101,15 @@ access(all) contract NonFungibleToken {

/// createEmptyCollection creates an empty Collection that is able to store the NFT
/// and returns it to the caller so that they can own NFTs
/// @return A an empty collection that can store this NFT
access(all) fun createEmptyCollection(): @{Collection} {
post {
result.getLength() == 0: "The created collection must be empty!"
}
}

/// Return a dictionary of all subNFTS if any
/// Gets all the NFTs that this NFT directly owns
/// @return A dictionary of all subNFTS keyed by type
access(all) view fun getAvailableSubNFTS(): {Type: UInt64} {
return {}
}
Expand Down Expand Up @@ -132,10 +140,11 @@ access(all) contract NonFungibleToken {
/// withdraw removes an NFT from the collection and moves it to the caller
/// It does not specify whether the ID is UUID or not
access(Withdrawable) fun withdraw(withdrawID: UInt64): @{NFT} {
/// @param withdrawID: The id of the NFT to withdraw from the collection
access(Withdraw | Owner) fun withdraw(withdrawID: UInt64): @{NFT} {
post {
result.id == withdrawID: "The ID of the withdrawn token must be the same as the requested ID"
emit Withdraw(type: result.getType().identifier, id: result.id, uuid: result.uuid, from: self.owner?.address, providerUUID: self.uuid)
emit Withdrawn(type: result.getType().identifier, id: result.id, uuid: result.uuid, from: self.owner?.address, providerUUID: self.uuid)
}
}
}
Expand All @@ -145,14 +154,18 @@ access(all) contract NonFungibleToken {
access(all) resource interface Receiver {

/// deposit takes an NFT as an argument and adds it to the Collection
///
/// @param token: The NFT to deposit
access(all) fun deposit(token: @{NFT})

/// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
/// @return A dictionary of types mapped to booleans indicating if this
/// reciever supports it
access(all) view fun getSupportedNFTTypes(): {Type: Bool}

/// Returns whether or not the given type is accepted by the collection
/// A collection that can accept any type should just return true by default
/// @param type: An NFT type
/// @return A boolean indicating if this receiver can recieve the desired NFT type
access(all) view fun isSupportedNFTType(type: Type): Bool
}

Expand All @@ -161,47 +174,65 @@ access(all) contract NonFungibleToken {
///
access(all) resource interface Collection: Provider, Receiver {

/// Return the NFT CollectionData View
/// has to be AnyStruct to avoid circular dependency issues
/// the return value should be cast to MetadataViews.NFTCollectionData after it is returned
///
/// Metadata Views are a critical piece that NFT projects need to implement
/// in order to function properly in the flow ecosystem
///
/// Check out https://developers.flow.com/build/advanced-concepts/metadata-views
/// for a detailed guide on how to implement metadata views properly
access(all) fun getNFTCollectionDataView(): AnyStruct

/// Normally we would require that the collection specify
/// a specific dictionary to store the NFTs, but this isn't necessary any more
/// as long as all the other functions are there
/// deposit takes a NFT as an argument and stores it in the collection
/// @param token: The NFT to deposit into the collection
access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
pre {
// We emit the deposit event in the `Collection` interface
// because the `Collection` interface is almost always the final destination
// of tokens and deposit emissions from custom receivers could be confusing
// and hard to reconcile to event listeners
emit Deposit(type: token.getType().identifier, id: token.id, uuid: token.uuid, to: self.owner?.address, collectionUUID: self.uuid)
emit Deposited(type: token.getType().identifier, id: token.id, uuid: token.uuid, to: self.owner?.address, collectionUUID: self.uuid)
}
}

/// Gets the amount of NFTs stored in the collection
/// @return An integer indicating the size of the collection
access(all) view fun getLength(): Int

/// Gets a list of all the IDs in the collection
/// @return An array of NFT IDs in the collection
access(all) view fun getIDs(): [UInt64]

/// Borrows a reference to an NFT stored in the collection
/// If the NFT with the specified ID is not in the collection,
/// the function should return `nil` and not panic
/// the function should return `nil` and not panic.
///
/// @param id: The desired nft id in the collection to return a referece for.
/// @return An optional reference to the NFT
access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
post {
(result == nil) || (result?.id == id):
"Cannot borrow NFT reference: The ID of the returned reference does not match the ID that was specified"
}
return nil
}

/// createEmptyCollection creates an empty Collection of the same type
/// and returns it to the caller
/// @return A an empty collection of the same type
access(all) fun createEmptyCollection(): @{Collection} {
post {
result.getType() == self.getType(): "The created collection does not have the same type as this collection"
result.getLength() == 0: "The created collection must be empty!"
}
}
}

/// Function to return the types that the contract implements
/// @return An array of NFT Types that the implementing contract defines.
access(all) view fun getVaultTypes(): [Type] {
post {
result.length > 0: "Must indicate what non-fungible token types this contract defines"
}
}

/// createEmptyCollection creates an empty Collection for the specified NFT type
/// and returns it to the caller so that they can own NFTs
/// @param nftType: The desired nft type to return a collection for.
/// @return An array of NFT Types that the implementing contract defines.
access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
post {
result.getIDs().length == 0: "The created collection must be empty!"
}
}
}
30 changes: 17 additions & 13 deletions contracts/ViewResolver.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,31 @@
// All you need is its address and name and you're good to go!
access(all) contract interface ViewResolver {

/// Function that returns all the Metadata Views implemented by the resolving contract
/// Function that returns all the Metadata Views implemented by the resolving contract.
/// Some contracts may have multiple resource types that support metadata views
/// so there there is an optional parameter for specify which resource type the caller
/// is looking for views for.
/// Some contract-level views may be type-agnostic. In that case, the contract
/// should return the same views regardless of what type is passed in.
///
/// @param resourceType: An optional resource type to return views for
/// @return An array of Types defining the implemented views. This value will be used by
/// developers to know which parameter to pass to the resolveView() method.
///
access(all) view fun getContractViews(): [Type] {
return []
}
access(all) view fun getContractViews(resourceType: Type?): [Type]

/// Function that resolves a metadata view for this token.
/// Some contracts may have multiple resource types that support metadata views
/// so there there is an optional parameter for specify which resource type the caller
/// is looking for views for.
/// Some contract-level views may be type-agnostic. In that case, the contract
/// should return the same views regardless of what type is passed in.
///
/// @param resourceType: An optional resource type to return views for
/// @param view: The Type of the desired view.
/// @return A structure representing the requested view.
///
access(all) fun resolveContractView(_ view: Type): AnyStruct? {
return nil
}
access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct?

/// Provides access to a set of metadata views. A struct or
/// resource (e.g. an NFT) can implement this interface to provide access to
Expand All @@ -30,14 +38,10 @@ access(all) contract interface ViewResolver {
access(all) resource interface Resolver {

/// Same as getViews above, but on a specific NFT instead of a contract
access(all) view fun getViews(): [Type] {
return []
}
access(all) view fun getViews(): [Type]

/// Same as resolveView above, but on a specific NFT instead of a contract
access(all) fun resolveView(_ view: Type): AnyStruct? {
return nil
}
access(all) fun resolveView(_ view: Type): AnyStruct?
}

/// A group of view resolvers indexed by ID.
Expand Down
Loading

0 comments on commit ded9217

Please sign in to comment.