Extending NEP-171 Events Standard to include NFT `update` events

Summary:

To extend the NEP-171 events standard to include nft_update events.

Motivation:

The existing events standard for NFTs as described in NEP-171 does not include any events to be logged when the metadata for an individual NFT is updated.

An increasing number of NFT projects (e.g. NEAR Future) are creating upgradable/evolving NFTs, where the individual NFT will change over time. A nft_update event would notify listeners of these updates, ensuring consistent rendering of the latest NFT data.

Some projects and marketplaces (e.g. TENK, Paras, NEAR Future) have implemented a solution for this and the purpose here is to discuss this implementation, iterate as necessary and move to standardize it.

Specification

nft_update

Emitted by NFT contract when any field of TokenMetadata or its associated off-chain data (e.g. reference JSON) is updated.

Not sure if this should include the updated data in question - this could get large for multiple token_ids. Suggest instead that the receiver can then query nft_token() for the provided token_ids to get the updated data.

EVENT_JSON:{
  "standard": "nep171",
  "version": "1.1.0",
  "event": "nft_update",
  "data": [{"token_ids": ["1", "2"]}]
}
5 Likes
  • Outside of listening for updates, I think there should be a way to check whether a NFT updates (is dynamic) vs static, so indexers + marketplaces can optimize which NFTs they are listening for. One of the things that I’m most skeptical about is NFT projects rugging on the art and updating it later when the buyer did not intend for this. Would appreciate it if nonupdatable static NFTs had a lock next to the URI and attributes on marketplaces to indicate this. Would also appreciate if this was indicated by a number_of_updates where
  • — 0 = static, 1-many (phased), and -1 meant this NFT can be updated forever (dynamic NFT)
    • With this, would appreciate And there was also a way to check the times a NFT has been updated
    • A use case for phased NFTs is like Pokemon, where you know a NFT has a certain number of levels, until it reaches its final form, where it becomes static again.
  • Would also appreciate it if this was combined with a rich media standard in development as we could tell if the update also changed with the file type, but an aggregation of all these features may result in greater chances of having standard denied
2 Likes

Great points @minorityprogrammers, thanks for the comment!

Re. the first point, this sounds like it could benefit from more robust conversation, but I would argue that the specific implementation details of these kinds of “dynamic” NFTs are beyond the scope of this specific standard addition. Ultimately, developers and project creators will make NFTs that have the functionality specific to their use case, and I imagine this could be a controversial topic. However, I believe a less controversial point is that if/when an update is made, NFT UI’s should reflect this update (at the very least so that the owner is aware that their NFT has updated/evolved), so I would propose that this basic event is implemented first; otherwise IMO we risk putting the cart before the horse and agreeing on nothing rather than making incremental progress. The reality to me is that NFTs are already evolving/updating (see NEAR Future), and as a marketplace we need to know about these updates.

However, if you and others feel that this standard shouldn’t be pushed through before these larger questions are answered, I should probably split these two events into separate proposals, as I think the contract_metadata_update event is less controversial and frankly that’s the one that we really need right now, as a marketplace.

Re. rich media metadata - perhaps this proposal would encompass what you are describing? E.g. according to this proposal, if the media changed (even a minimal change such as filetype), an nft_update event would be logged. Or perhaps I’m not quite understanding, in which case feel free to correct me!

2 Likes

For rich media however standard would still be needed to interpret file type even before a change or update occurred. I believe the rich media standard would optimize display different media types for static NFTs as well.

Good stuff, love to see the work TENK + Paras put in for NEAR Future Primal getting baked into standards everyone can benefit from!

The benefit of events is being able to efficiently listen to them from all assets.

Permanently locking a collection (so that the ART URI or other metadata can not change) can be done by removing all keys from the contract. Whether or not there are keys on a contract is publicly visible.

It would be beneficial for the community to agree on some definitions.

When I think of a dynamic NFT I think of something which doesn’t require events to update itself. Something that’s dynamically rendering itself with code on the client side which allows for data to be pulled from oracles or HCI inputs like webcams/kinect/etc.

There are a different class of dynamic NFTs that render themselves with code on a server and update the image sitting at the NFT art URI. This kind of NFT do benefit from events in case a marketplace or wallet has cached a previous render of the NFT image. Maybe the update standard can have a way for the NFT to indicate NOT to cache the image and always go to the URI (something like the suggested -1 forever updating proposed by @minorityprogrammers ). Otherwise the events channel will become full of spam from NFTs that are constantly re-rendering themselves.

1 Like

I have been under the impression the dynamic NFTs or dNFTs meant that NFTs were not static and could be changed. This may be from a data stream, on chain interactions, or predetermined phases or preuploaded URI links like in pokemon as Starpause is mentioning.

To the point of removing all keys, this seems not directly the same thing as the inability to change metadata. Would be interested to brainstorm on possibilities for creating a locked contract with the ability to still change NFT metadata. Also would be interested in brainstorming scenarios where a contract isn’t locked but NFTs can only remain static.

UPDATE: @lachlanglen will be separating this into general contract events as NFTs are adding complexity that may get the standard rejected

Updated to focus on individual NFT updates, moved contract metadata updates proposal here: Extending NEP-171 Events Standard to include contract metadata `update` events

As much as I would be in favor of merging a token/contract metadata update standard, how about discussing these separately but tying their interface. First of all I’d use NEP177 as base for the events, as they clearly have more to do with metadata than with NFT core functionality. I’d also include updates of on-chain properties:

EVENT_JSON:{
  "standard": "nep177", // use corresponding metadata NEP as event basis
  "version":" 1.0.0",
  "event": "nft_update_token", // name consistently with other events and contract update event
  "data": [
    { "token_id": "1", "metadata": { "refererence": "..." } }, // add info on what exactly is updated
    { "token_id": "2", "metadata": { "media": "..." } }
  ]
}

This way, if I was to update whatever is stored in my reference, I could emit the event, leaving the URI unchanged, but notifying indexers that changes took place. I could also store everything in an IPFS/arweave/whatever repo, and change the reference to point to the current state of a dNFT, while leaving all possible states inspectable. If future states should not be inspectable, that’s certainly possible with this structure as well. I also propose to change the event name to stay consistent with other events/NFT standards and to relate it to my proposal on the NFT contract update.

The other part of this discussion is the data guarantee for immutable NFTs. Right now we have a base URI/reference/media combo with no mandate whatsoever to use permanent data storage. I have seen endpoints going down, so we could argue that we already have unintentional dNFTs. The most secure way to ensure that NFTs never change is including all the material on-chain or exclusively using decentralized permanent storage and then burning keys. However I haven’t tested yet if near_sdk::Promise::add_full_access_key (sry, can’t link for some reason) used in a contract method might be able to add a key to an account with no key, voiding those guarantees as well. So even by marking NFTs or NFT contract as mutable/limitedly mutable/immutable, we never know what happens. There are more edge cases to this as well. E.g. a token where media might change has vastly different implications to users as a token where copies may change. So ideally all those fields are marked with their mutability, and for now we would have to trust NFT contract implementors to behave as they advertised.

Update: Need to wait until tomorrow until I can post my thoughts on the contract metadata update

1 Like

Thanks for the comment @till_mb! Really happy that conversation is starting up on this again.

First of all I’d use NEP177 as base for the events, as they clearly have more to do with metadata than with NFT core functionality

I don’t have a super strong opinion here but can see both POVs. There is more to an NFT than its metadata, so this event could indicate a higher-level update at the Token’s base level. But I also see what you’re saying. Honestly, I think most indexers will be looking for the event name rather than the standard anyway, so I don’t think it really matters that much either way. I’m open to changing to nep177 if others share your POV.

I also propose to change the event name to stay consistent with other events/NFT standards

I don’t understand - other NFT events are nft_mint, nft_burn and nft_transfer. None of them contain “token”. I think the naming proposed here (nft_update) is consistent with existing events.

I also don’t think that the updated data should be required in the log. There is a 16kb log limit and certain items (e.g. a base64 media string - not that anyone I know actually stores media data on-chain, but technically it’s allowed) may surpass this limit. I am in favor of just specifying the token_id and allowing indexers to call nft_token() to get the updated Token. Perhaps updated fields could be specified, but that adds complexity once you get into nested structures and it’s not like knowing the fields that were updated makes it any less work to get the updated data - you still have to call nft_token().

The other thing to bear in mind is that many projects (Few and Far, Paras, TENK, probably others) already have these events running in production in the format described in my original post, so any changes should be essential (IMO) as these would require a lot of contract upgrades and indexer updates.

1 Like

Regarding the NEP177/171, I see your point that this update could be more than just metadata. Our indexer uses the full triplet, and that saved us from some spam engines that would just mint garbage with wrong triplets. All in all I think this is the least point of debate.

Regarding event names, nft_update might be more consistent, but it’s better to have clear distinction between contract-level update and token-level update. Given that the nft_metadata method returns contract metadata, I interpret the nft_ prefix to tell me that this method/event is from an NFT contract, and not necessarily related to a NF Token. So an nft_update event for me still has the question attached as to what is being updated, as it is way less obvious than with nft_{mint,transfer,burn}. The consistency comment was more update the update contract event. I find the contract_update_metadata quite confusing, especially if you’re telling me that you’re not really paying attention to the standard part of the event triplet. How do you know that the event was emitted from an NFT contract and not some FT contract or DAO or anything else? So IMO the contract update should have a nft_ prefix. And from there on having a clear line between update token and update contract is logical to me. Also updating the indexer with a new event name if the data might be incompatible with the existing, pre-used format is actually a plus, since that way you can update backwards-compatible and go back in time more easily than by coding special rules for block heights when events were updated in a per-contract-level.

The 16 kB log limit is kind of a point, since I already saw people using the media field to store base64. I do not like the current pattern of the indexer having to call RPC methods, as it breaks unidirectional data flow and creates HTTP requests that increase cloud costs and load on all systems (NEAR, yours, ours). So replacing nft_token calls with event logs would be a big improvement, but that might better be paired as an NEP that allows for increasing the log limit and then the mint event should emit metadata as well. Making metadata optional in the update event could spare us some of that, but create one path for unidirectional and another path for the current data flow. I think that’s more an introduction of complexity than is worth for our road to have sane data flow.

All in all, no problem with NEP171/177, I think we can skip emitting metadata, even though current data flow is messy. On the event name I am still convinced that nft_update is not really clear. I could also make my comment on the proposal for updating contract metadata. If everything else stays the same, all you need to do on your indexers is:

match (standard, version, event) {
  ...
  (_, _, "nft_update") => handle_update(data),
  (_, _, "nft_update_token") => handle_update(data),
  ...
}

Ofc this assumes you are ignoring standard as you said, not sure what you’re doing with version. But it’s literally one line of code. Let’s just make the standards as clear and self-communicating as possible.

@till_mb I agree that the event naming hasn’t been clearly implemented from the start. nft_metadata is the worst-named method IMO. My approach working with (and extending) NEP-171 has been to treat that particular event as an anomaly, and use contract_ prefix for contract-level events, and nft_ prefix for NFT-level events. So while I completely agree with your point, my proposal would be to make the NFT update event consistent with existing individual NFT events (nft_) and just remember that nft_metadata is actually a contract metadata method. In our spec we have an additional method contract_metadata which returns the same result as nft_metadata just to avoid this confusion. I think this is an easier solution for now, but honestly I think we need a new standard which addresses this and many other issues with NEP-171. But that’s a topic for a different conversation…

I do agree that it would be helpful to have the contract “type” available somewhere, but maybe that belongs as a separate field on the Event object rather than a prefix on the event name?

Re. logs - another serious consideration is that logs are stored on-chain, so I think it makes sense to minimize size and allowing interested clients to query.

Finally, to clarify, the contract metadata update NEP has passed and is being implemented, so that is no longer a proposal. You can see it here. The event name is contract_metadata_update (which further strengthens the position of treating nft_metadata as an annoying anomaly).

@frol where do we go from here?

@frol, @starpause, @mattlockyer any thoughts on this?

Good stuff!

from Paras here, we are struggling with NFT projects that needed to update metadata, since they must contact us manually. We already provided an update API to NFT projects, but the proposal here will be necessary for the whole NFT space to know if an update is happening. Is there any way to speed up this proposal?

1 Like

@irfianto Any thoughts on the discussion points raised here?

I want to define the motivation more precisely. One alternative to nft_update is to emit two events: nft_burn + nft_mint, right? That would be a bit cumbersome, so I agree that nft_update might be a better option.

Here are my concerns:

  1. What is the definition of NFT change? Is it expected that nft_update will only be triggered if the metadata is changed or might it also mean that the underlying media or reference has changed (while the URI in the metadata has not)?
    • I treat NFT to be the metadata that is stored on the chain and the set of common operations (function interfaces). There are no guarantees of transferability (transferable or non-transferable) or immutability - those cannot be enforced on the API level. (Just to throw some ideas here: we may create an accounting contract and force NFT/FT contracts to send all the operations through it, and that contract will track all the tokens to ensure consistency, but that feels like it will be a mega-multi-token contract after all).
  2. Making a view call for each nft_update’ed token might be overwhelming (slowing down the indexers due to the extra network requests that introduce unpredictable latency, making indexers dependent on RPCs).
    • I don’t think it is a blocker, and I don’t see a better way, so indexers will probably need to be implemented in an asynchronous way (backfilling the data from RPC in the background)
  3. View-calls are granular to the block, so if the token was updated and burned or updated several times in the same receipt or in parallel receipts in the same block, there won’t be an easy way to get the content of the token.
    • I don’t think it is a blocker, as the same problem is applicable to nft_mintnft_transfernft_burn in the same block/receipt.
  4. Some of the fields in NFT metadata might be irrelevant to the indexers (e.g. extra and copies), but at the same time, I believe, the nft_update event should be emitted if any of the fields are changed. This means that this will dump more work on the indexers that only need to index the title changes.
    • I don’t think it is a huge blocker, but we might at least consider allowing developers to specify which fields were updated (if smart contract developers are lazy and don’t provide them, indexers will need to consider that any of the fields could have changed and thus would need to make a view-call to check; yet, if the fields are provided and indexer does not care about those fields, then no view-call is needed)

Exactly, the contract still can add full access keys to the locked account, so there are no immutability guarantees (see my (1) point above).

Overall, the proposal looks reasonable to me, so I would suggest creating a NEP extension. Yet, I got stuck with the previous PR you submitted as it was not a standalone NEP document, instead, it was an edit of the nomicon, so I will need to refactor that into the NEP before you can submit a NEP extension to it.

Sorry @frol, that’s how I was directed to do it. Please let me know how best to create this extension proposal when it is ready.

No problem. We were still in transition mode back then. I will refactor it by Thursday.

It took me less than I expected and NEP-0423 is now merged! Please, feel free to submit a new PR with the NEP extension to NEP-0171. There is no rocket science there, see how NEP-0423 became a NEP extension to NEP-0171; basically, submit changes to the NEP instead of the nomicon page (one day we will integrate NEPs directly into the nomicon :crossed_fingers: )

2 Likes