Streamlined Token Standards Proposal

@jacob was also asking about this (I think), my main unknown to being okay with multiple FT/NFT definitions per contract is about how dapps can interact with them in a standardized way. Is the plan that there is one Collection per NFT definition? Only ever one rolled-up Collection per contract?

1 Like

What do you mean with abused? The feature was mostly added to the language to allow modelling the current FT and NFT standards. However, it turns out that the current design of the feature does not provide the guarantees we hoped it would (see Josh’s post: ).

Can you elaborate on how interfaces are not enough? What exactly do nested type requirements provide as an additional benefit? What is the footgun? Do you maybe have an example?

Once we’ve gotten more positive feedback the community agrees with proceeding on rewriting the FT and NFT standards, we’re planning to publish FLIPs for the language changes, one of which is specifically for the removal of nested type requirements, which will go into more detail.

The short version is that we cannot statically enforce some things we hoped we could (e.g. that the parameter type of the concrete type’s function can be a subtype, due to type variance), effectively making the feature not very useful. In addition it is complicated for users to learn, and makes the implementation of Cadence quite complicated. The cost / benefit ratio is very high

1 Like

What do you mean with abused? The feature was mostly added to the language to allow modelling the current FT and NFT standards. However, it turns out that the current design of the feature does not provide the guarantees we hoped it would (see Josh’s post: ).

Ok abused maybe was a strong word for this, but my point was, if this was somehow creating some problems ( limitation ). I still think ( after your last paragraph ) there can be valid use cases ( especially standards ) that would like to ( and should be able to ) dictate a nested resource. First one comes into mind is SoulBound NFT etc, Lending NFT ( or rentable )

Can you elaborate on how interfaces are not enough? What exactly do nested type requirements provide as an additional benefit? What is the footgun? Do you maybe have an example?

Interfaces are little tricky, I am not sure every case can be covered with interfaces that can be covered with nested resources. For foot guns, don’t want to get into more detail now, I will report those in private soon. They have real world consequences, don’t want to share publicly right now.

1 Like

Sorry, I do not know anything about them, but would like to understand it better: What would benefit would nested type requirements have over just a normal interface? That there will be exactly one nested type in the contract conforming to the contract interface?

Even more curious about this, please feel free to DM / mail me! Thank you for your feedback, it’s much appreciated.

1 Like

I am on a trip right now, but I have long flight ahead, for both of them I will try to write some examples. I feel strongly though not 100% sure, nested type requirements can have benefit over interfaces in some models.

1 Like

With the upcoming release of Secure Cadence, and the changes to taking references to optionals, the collection borrow function should probably be updated to return an optional reference now, instead of aborting execution when the NFT with the given ID does not exist:

pub fun borrowNFT(id: UInt64): &NFT?

This will also allow users to quickly check if an NFT with a certain ID exists in the collection.

Likewise, maybe also the withdraw function should be changed to return an optional:

pub fun withdraw(withdrawID: UInt64): @NFT?
3 Likes

Are the Secure Cadence changes finalized? Can we confirm that the NFT standard isn’t going to dramatically change as described in this thread? Curious because the testnet spork is coming very soon…

1 Like

The Secure Cadence changes are finalized. The FT and NFT standards will not change for the time being, the changes proposed here are not part of the next spork / the Secure Cadence release and deployment.

1 Like

@jacob

If anything, I think enforcing this as a standard would only encourage developers to mess up their collections by not naming their Collection resources properly, not implementing CollectionPublic or Receiver interfaces, not implementing correct borrow functions, and just defining their collections in their own unique way which doesn’t standardize NFT discovery at all. For example one project could name their borrow function borrowNFT while another one could name it borrowToken, and then we have no way to discover the NFT more broadly.

We could define a Collection interface like we do for Vault in the fungible token standard to enforce that a resource that implements it implements all the other correct interfaces, functions, and fields. So we wouldn’t have to worry about developers making a mistake with their collection as long as they implement this Collection interface.

@austin

Is the plan that there is one Collection per NFT definition? Only ever one rolled-up Collection per contract?

There could be any number per NFT definition if we have the collection interface, but I think most people would still use only one.

It looks like there are still a lot of understandable points about the generic collection, so I am going to make an example of this that does not include the generic collection so we can discuss that without being distracted by the generic collection part. I will also include @bastian 's suggestions about optional return values for some functions and I’ll add the restricted provider types as well. I’ll make a new forum post for it

1 Like

Thanks @flowjosh! I agree splitting shared collection into another discussion is probably the right call. Happy to take up discussion on it elsewhere.

As for the multiple NFT definitions per contract, I’d like to get a better understanding on how platforms/wallets are expected to consume this. Has that been explored yet? Will there be a standard method on a contract to ask for its NFT types/collections? A metadata resolver kind of solution to resolve an NFT type to its collection?

1 Like

@austin Can you explain more what you mean in your question? I don’t think that it would change much. Some token objects might have different names than Vault or NFT, but you can still just borrow them as their interface types

1 Like

Some token objects might have different names than Vault or NFT , but you can still just borrow them as their interface types

Maybe the big thing here is I feel like we’re missing helper functions to answer questions like:

  • “What collection does this nft belong to?”

  • “What NFT types are in this contract?”

  • “What collection types are in this contract?”

When I think about how a watcher that ingests events from flow might work, I don’t see how it would be able to understand where it would borrow from to get an NFT’s data. For instance, if I deposit an NFT and see the event

pub event Transfer(type: Type, id: UFix64, from: Address?, to: Address)

Thankfully, I know the type that this transfer was associated with, but how do I know what collection it is supposed to belong to? This gets especially complicated if the community opts to move forward with a universal receiver, or if a contract has multiple types of collections. Somehow, I need a way to map this event to where it should be. As of now that collection would just be &Contract.Collection{NonFungibleToken.CollectionPublic} but with the proposed universal receiver and with multiple nft collections opening up in one contract, I think we there are some discoverability factors that aren’t clear just yet

1 Like

Those are really good points. Would it satisfy your concerns if there were methods included in the standard that returned the types that the contract supports? For example:

pub contract interface NonFungibleToken {

    // get a list of all the NFT types that the contract defines
    // could include a post-condition that verifies that each Type is an NFT type
    getNftTypes(): [Type]

    // get a list of all the NFT collection that the contract defines
    // could include a post-condition that verifies that each Type is an NFT collection type
    getCollectionTypes(): [Type]

    // tells what collection type should be used for the specified NFT type
    getCollectionTypeForNftType(nftType: Type): Type?

Something like that? we could potentially add more that return paths for collections, etc.
And we can always add more parameters to the Transfer event to include the collection that it belongs to. We’re putting the universal collection off to the side for now, so we don’t need to worry about that for now.

1 Like

I have to say I am strongly against to ‘transfer’ method. (If there will not be unified collection)

  • it can be some helper function somewhere (which using withdraw and deposit)

  • if we allow transfer method to standard, there is no motivation for people to implement withdraw / deposit ( they can simply panic on them and say use transfer )

1 Like

I don’t know if I agree with you there. So many apps and transactions will still need to use withdraw/deposit, so it would be really dumb for any developer to not implement them in their contract.
I am not super attached to having a transfer method though, so if enough people agree with you, I am fine removing it, but I’m on the side of keeping it for now.

1 Like

To be honest, it is good utility function but even from signature:

pub fun transfer(type: Type, withdrawID: UInt64, recipient: &AnyResource{Receiver})

we can see, there is only one implementation makes sense:

recipient.deposit(token:<-self.withdraw(type: type, withdrawID: withdrawID)

Btw maybe on another interface it can work ( not Provider but Transferable etc can be ), this way we can extend on that later if necessary.

1 Like

I am actually now leaning towards making the parameter of transfer an address instead of a capability. If it is gonna be a utility function, it should actually remove some of the steps from regular transfer transactions. I think I like your idea of putting it in a different interface though. I’ll add that to my proposal.

1 Like

I put up a PR in the fungible token repo with my proposal for the standard: https://github.com/onflow/flow-ft/pull/77

Feedback would be much appreciated. I am still working on the NFT one, but that should be coming soon.

1 Like

Helper methods are probably the way to go, yes. I think the best way to enumerate what’s needed is just to explore what questions we should be able to ask the contract. The ones that come to mind for me are:

  • “What collections do you have?”
  • “What nft types do you support?”
  • “Where should I put this NFT?”
  • “Where should I store this collection?”
  • “What collections do you have?”

Your suggestion satisfies some of this, but what about using the MetadataViews.CollectionData struct to help do the heavy lifting on this? Maybe that can be leveraged to answer some of the other unanswered questions:

pub contract interface NonFungibleToken {

    // get a list of all the NFT types that the contract defines
    // could include a post-condition that verifies that each Type is an NFT type
    getNftTypes(): [Type]

    // get a list of all the NFT collection that the contract defines
    // could include a post-condition that verifies that each Type is an NFT collection type
    getCollectionTypes(): [Type]

    // tells what collection type should be used for the specified NFT type
    getCollectionTypeForNftType(nftType: Type): Type?

    // resolve a type to its CollectionData so you know where to store it
    getCollectionData(type: Type): MetadataViews.CollectionData
    // for dapps that want to show information about a collection, we also need CollectionDisplay. 
    getCollectionDisplay(type: Type): MetadataViews.CollectionDisplay?
}
1 Like

We can also make use of the LostAndFound contract we’ve been working on at Flowty to help with this kind of transfer:

It would also take care of making sure the capability is actually valid and other edge cases in the future like whether there are enough flow tokens available to pay for storage

1 Like