-
Notifications
You must be signed in to change notification settings - Fork 169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
IMPROVEMENT: NFT Metadata #9
Comments
I can solve this problem if you have the patience to walk me through some basic knowledge necessary to understand what all the moving parts are.
|
@MiyaSteven Sorry for the late response. I can answer a few of your questions, but I'll need to find some more help answering some other ones, as I am not super familiar with the state of metadata storage in other blockchains
Thanks for reaching out and feel free to ask more questions if you still need help! |
I'm of the opinion that metadata should be flexible and scalable. You don't get any of those things with current on-chain ethereum solutions. Metadata should be flexible Metadata should be scalable My platforms solution for this is to use a traditional storage space and have a server that dynamically retrieves metadata based on the contract address and token ID. This allows for metadata to be updated at scale to provide flexibility for enterprise projects. If this storage space was decentralized and there could be a history of metadata updates I think that would suffice. Updating metadata needs to be cheap, fast and easy. |
Could a start to this be to support labels that are pure {String,String} Dictionary and expose that in the public interface and collection interface. Just doing that would make it a lot easier to experiment with. Metadata should have a schema that could be migratedAdding support for migrating the data in the schema to a new format would be a very nice feature here. A binary format like avro supports this. There are lots of examples on how people use it in kafka to allow sending messages that are backwards compatible with old formats. What is the size of a flow block gonna be? Will it be feasible to store a pretty large SVG on-chain. |
We originally had a Yes, I totally agree that the schema should be able to be migrated. We are discussing a process for how contracts are stored and upgraded in accounts in onflow/cadence#221 (comment), which includes a part about how data from contracts is migrated to the new version. We'd love some feedback or ideas if you have any. We aren't totally clear on what the size of a flow block will be but I feel fairly confident that you'll be able to store larger files like that as long as you have the money to pay for storage. |
Another thing me and @psiemens talked about yesterday was implicit support for something like IPVS. When sending a transaction with an specific type that field is not sent on chain but sent into IPVS, and if the field is accessed it is pullef from there. Or something like that. |
Cost per GB of storage on Ethereum is measured in the millions of dollars. For comparison, price per GB of storage on cloud services like AWS is $0.05/month. Storage architecture must be radically different on Flow to make this idea even remotely cost-effective. Currently, it's not so hard to fire up an IPFS pinning service, and store only the hash on-chain (which is a URL in IPFS-land).
I don't know the Flow architecture well enough to answer this question.
Different NFTs can represent radically different information, needing radically different data structures. There is no one-size-fits-all solution for NFT data representation.
That would effectively shard Flow storage. How would you achieve consensus on data representation?
Store real data on IPFS. Encode only the hash on the blockchain. The primary blocker for this on Ethereum is the cost of storing data on Ethereum ($millions/GB). |
An idea would be to use Edit:
Reference for querying bson data in Postgres Example:
Query examples Filter by presence of a key.
Filer by checking inclusion of one JSON into another one.
Field names
0x01.NBATopShot.json could look like this:
|
Not sure if there should be a metadata standard. Perhaps just a metadata field that is a custom Metadata resource per NFT. Then any client can traverse this resource to get relavent metadata. The contract can also define a metadata template field for recommendations on displaying the data using handlebars or similar; although, these metadata display templates are probably better suited as scripts rather than contract functions. Tldr; seems metadata can be stored however the dev wants, but have a standard for the metadata script used to read and format the data. |
And building standards for all complex data types will be beneficial. Although, you can jam almost anything into a string ;) |
Many schemas that may be suitable for NFTs have already been defined and are available at schema.org including images, video, audio, and so on. ERC-721 specifies a simple metadata schema that looks like this: {
"title": "Asset Metadata",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Identifies the asset to which this NFT represents",
},
"description": {
"type": "string",
"description": "Describes the asset to which this NFT represents",
},
"image": {
"type": "string",
"description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive.",
}
}
} ERC-721 tokens get associated with their metadata on-chain using a tokenURI field in the token contract. Further information such as description and image are parsed from the corresponding JSON record referenced by URI. (See above). That URI generally references a JSON document and assets on IPFS. Open questions:
|
Using standard schemas for different types of assets from schema and specifying these from the start is a great idea. Case in point - the ERC-721 spec on Ethereum has a limited metadata schema definition and now different platforms have different metadata shapes for different media types which ruins the off-the-shelf interoperability between these platforms. |
@pizzarob Yep. I just added the ERC-721 metadata schema to my comment, above. I agree that mappability to ERC-721 is an important feature to strive for. |
Just catching up on this issue. RE: Joshua's June 16th's comment Questions:
|
I make a little something to see if Mixins could be used to solve this. https://github.com/bjartek/flow-nft-mixin |
@bjartek I think that's an interesting idea to add additional features to the NFT, such as artist royalties. I think smart contract composability is a great feature. Are smart contract mixins idiomatic on Flow? I'd love to read more about them with more descriptions of how they work and more examples of using them. |
Mixin might not be the correct term to use here. But interface, contract, capability are already used so it just used mixin. My refence for it is https://docs.scala-lang.org/tour/mixin-class-composition.html Would Trait be better? |
It might be better for Trait just to be an interface that requires some methods. Like
Aso. Then you do not need for it to own a resouce since it can be the resource itself. |
Here are a series of observations, opinions, and facts that will hopefully help this discussion:
|
To use your example for royalties, you could define an extension of the NFT interface that includes royalty tracking, and (so long as it doesn't break the requirements of the base NFT interface), would be seen as a generic NFT to use cases that didn't have to worry about royalties, while adding in the royalty functionality where appropriate. |
Are interfaces on Flow first-class and composable? For example, can we say a contract implements the NFT, Royalties, and timelock interface? |
You bet! Provided those interfaces don't have any conflicts. |
struct and resource interfaces are composable, but contract interfaces aren't, correct? @turbolent ? |
If you mean "composable" as in, a concrete struct/resource/contract can implement multiple interfaces, then yes, all of them can do that (if it's not working right now it is a bug), e.g.
|
But it is not possible to import the code for fulfulling that interface contract from another account right? You will have to duplicate that yourself? |
That's right, @bjartek. We have some thoughts on how to implement code-reuse, but they are still only thoughts. We definitely didn't want to use standard object inheritance, because it already has lots of weird edge cases in off-chain code, and we were pretty sure that it would be an absolute disaster in the context of smart contracts. Imagine if I could define a sub-class of CryptoKitty that overrode the breeding method! Or a subclass of Vault that "extended" the interface to include a "makeMeRich" method! Doesn't exactly fit the use case... 😀 |
@dete exactly. That was part of the reasoning behind my mixin experiment. Each trait would store all its method and state in a seperate «namespace» inside the NFT. Will this feature be done before main net launches? Or if not how should we structure our NFT to be compatible. In my case I want to store Art that could be unique or editioned. Either as a single «trait» or ad two. |
I discussed this with @briandilley in dm a bit and with the ability to resolve a list of a type then my pervious need are covered. So using types and not strings will work |
Types cannot currently be used as dictionary keys (that would be a good feature to add), but we can use Type().identifier as the key in a dictionary of {String: AnyStruct} and hide this with the API. |
Is it easy/possible to create a Type from fcl/go-sdk and send it as argument to a script/transacrion? Or to create a type from a string in cadence? |
It's tricky because you need the content of the type, I think. @turbolent I think I asked about this before and it wasn't practical? |
I updated my implementation of the above in the following gist. You can find it in the playground here too |
@briandilley @bjartek Can one of you write up a summary of the current state of the metadata proposal? I think it is probably a little hard for any newcomers to understand what the state of the proposal is, and we'd probably like to start sharing it with some other teams soon, like OpenSea and such. It would be good to have a proposal with design rationale, trade-offs, examples, and blockers. |
FWIW- I agree that "View" is a reasonable name and will use it here. I humble suggest we consider "Atom", since that has an antecedent with metadata chunks in the MP4 file format. One last attempt to make the case that name->Type is potentially problematic: In practice, the code is going to be much easier to write if a particular name always maps to a particular type. It doesn't do anyone any good if there is a well defined tag called "Artist", but then there are 5 different structs that represent the artist data in different ways. Additionally, name collisions and/or squatting is bound to happen in practice (two llama projects launch at about the same time, both have metadata for "Llama Genes", but the two data types they map to are wildly different). So, what's likely to happen in the real world is that you'll actually be looking for name/Type pairs. So, if you have to check the type anyway, why bother with the name part? On the other hand, Types in Cadence are guaranteed to be distinct (no possibility for squatting or collisions), and code that examines the metadata views will always know the right type it can expect to be returned, because it's literally the type that it asked for. The interface can even include a postcondition that says that the returned type needs to match the type argument. It's worth noting here that I'm optimizing for on-chain computation. On-chain code (or even Cadence scripts running on an Access Node) is always going to be more "expensive" to run (and update) than purely client-side code, so if there's ever a trade-off between making things easier on-chain or easier for client code, I'll always lean towards making things easier for the on-chain case. In particular, it seems like some of motivation for tagging with names is imagining that, as time goes on, new View structs will replace old ones for the same tag. This sounds like a nightmare for on-chain code to me! 😁 I have a smart contract that requires that you supply a Giannis Moment in order to be granted access. If the struct that a Moment returns when I ask for the Player View changes, you've broken my whole thing. On the other hand, if a Moment starts to offer a new, "better" version of the Player type, newer contracts can use the new version, and I can keep getting the "backwards compatible" version without any code changes. |
I think there's a useful pattern that would be helpful when adding "newer and better" Atom/View structs. I think @briandilley was implying this pattern in a comment he made above, but I think it's useful to have an explicit (albeit contrived) example here:
(Note, I'm using the names ArtistA and ArtistB here, but in practice this is more likely to be something like "anAddress.MusicMetadata.Artist" and "anotherAddress.MusicMetadataEnhanced.Artist".) |
Very good points in your above post @dete. I have not found time to update my reference impl to support the Type only stype but I really want to see if it works in practise because I can see that the string style can cause problems. I also really like you optimization choice. We had a discussion in discord the other day and I am more convinced then ever that the NFTv2 standard needs to support both immutable data and view on top of that data. One could even argue that you should not be allowed to change a view, but you should be allowed to add or even in some scenarios remove a view. I also like your concrete example with the "newer and better" approach. I will see if I find time to write the proposal that @joshuahannan suggests above in the next week. Down with the flu at the moment and have some other things that take up some time. |
I tried to hack together a small poc for the 'type-only' approach. https://github.com/bjartek/flow-nfmt/tree/types It works, but I have to hard code the Type in the script to resolve the View since I do not know how to create that type from a string in the go-sdk. Anybody have any hints on that? So create a type from its Identifier is what I am looking for. |
I think one thing we are missing something important. I want to separate for the sake of discussion, 2 parts of information, I will define them as:
So technically if I have a
From the discussion we had with @briandilley and @bjartek, I believe the main need from the developer side is some kind of upgrading of views. (which is pretty similar to providing
So as we don't want to lose on-chain integration as @dete suggested. We should have both of them at the same time. So when I update my NFT to provide more view/atom. It should return both ArtistA and ArtistB. But I think there is something important to show that In my opinion |
@dete about as we will have an interface like AtomProvider as follows:
If the presentation layer (let's say wallet) doesn't understand ArtistB it can ask for alternate representations and can go till it can recognize something. |
If we wish to fetch the instance of that type to work with it, we probably know its structure and can import its definition to access it better. If we don't, maybe we should access it via an interface. In either case, if we need to get a I'm worried that type construction from strings promises a dynamicness that Cadence doesn't have. But I've been wrong about this kind of thing before. 😺 |
@bjartek Also we already have to get supported |
@bluesign are you suggesting using the index of the getViews array as the parameter to resolve things? I do not see that as an improvement at all. What if we expose the views as Type but resolve them using their type identifier? My example implementation from above already supports this. |
I meant a bit like this, if someone will use this, they will not just blindly call Also as @rheaplex suggested, in order to use the struct probably, they will reference the contract containing that struct anyway. I would love to be able to create the type from identifier, but In general, it is big work for a little gain. Requires importing another contract on runtime, parser, and checker running again, also seems like can be a potential security hole (at least for DoS). |
How are you supposed to do this in any other way then using the index right now? There is AFAIK no way of creating a Type in any of the SDKs or clients.
Many people will want to do this yes. But imagine you have a block explorer. It an see your NFTs, it can see your views, it can resolve the views and peek as the data behind them as data that is exporeted as json. Personally I would love to see that, and it can be done I think.
Then I suggest we do not use Types to resolve the views, using index in views array is too brittle, atleast if you want to be able to store the "url" to a view somewhere as a stable identifier. What if somebody adds or removes some view? What if a contract want to store the view it used in a NFT as a field and fetch that view later. The 'thing' used to resolve a view must be able to support this IMHO. The way I see it using the identifiser of the type will solve this. |
Having to import views from another contract is not a problem IMO.
Especially since we'll likely have a set of views hosted by a known
trustworthy source (ie: dapper or some standards group).
That said, there is definitely something to be said for something a bit
less statically typed - like JSON where you never have to import anything
from anywhere.
|
Presented for feedback and comment based on Dete's comments above, a suggestion for supporting forward compatibility in NFT metadata without limiting experimentation and innovation: Let us assume a metadata standard defining a resource interface that supports at least the following member functions:
Let us assume that we do not wish this standard to specify the To enable forward compatibility, we create type converter contracts that allow instances of types not known or supported by a given implementation of the Here is an example converter contract:
Note that we fetch the metadata directly from the NFT. This is intended to ensure that the data cannot be manipulated in between being fetched from the NFT and passed to the converter. New types can be added until the contract is locked. This makes the code reliable without leaving an open attack vector. |
First i must mention that this discussion has progressed in the relevant flip fest repositories and we also have had a set of meetings with Glen about this.
How are you going to enforce that? If those methods take a Type as a parameter? I do not see the value at this at all, just limitations. |
Hey everybody! @bjartek @bluesign @briandilley and @figs999 formalized the ideas from this thread into a Flow Improvement Proposal (FLIP) here: onflow/flow#636 It's on track to be the first community-written FLIP that is written, reviewed and implemented on Flow. To prevent communication fragmentation, we're encouraging everybody to move this discussion to that PR and any future extensions or amendments that result from it. |
I'm closing this issue because the core ideas in this thread were proposed and accepted in this FLIP: onflow/flow#636. The new standard is flexible to support a wide variety of data formats. We'll be giving it a permanent home in this repository. We can have focused discussions about specific data formats (e.g. royalties) in dedicated issues or PRs on this repository. Thanks again everybody! |
Issue To Be Solved
NFTs always have some sort of metadata associated with them. Historically, most of that metadata has been stored off-chain, but we would like to create a standard for metadata that allows all metadata to be stored on-chain so everything about the NFTs is truly decentralized.
This issue is meant for discussion about the possibilities of the solution. More documentation and examples will be added as we research and discuss more.
I am currently leading the charge on this, but I have a lot on my plate and don't know if I can give this issue the love it deserves, so if someone from the community wants to lead, I would love to speak with you!
Suggest A Solution
Context
The Avastars project is an interesting project on Ethereum that that we could potentially take inspiration from for our metadata.
The text was updated successfully, but these errors were encountered: