forked from DecentralizedClimateFoundation/DCIPs
233 lines
12 KiB
Markdown
233 lines
12 KiB
Markdown
|
---
|
||
|
eip: 5185
|
||
|
title: NFT Updatable Metadata Extension
|
||
|
description: An interface extension for ERC-721/ERC-1155 controlled metadata updates
|
||
|
author: Christophe Le Bars (@clbrge)
|
||
|
discussions-to: https://ethereum-magicians.org/t/erc-721-erc-1155-updatable-metadata-extension/9077
|
||
|
status: Stagnant
|
||
|
type: Standards Track
|
||
|
category: ERC
|
||
|
requires: 721, 1155
|
||
|
created: 2022-06-27
|
||
|
---
|
||
|
|
||
|
## Abstract
|
||
|
|
||
|
This specification defines a standard way to allow controlled NFTs' metadata updates along predefined formulas. Updates of the original metadata are restricted and defined by a set of recipes and the sequence and results of these recipes are deterministic and fully verifiable with on-chain metadata updates event. The proposal depends on and extends the [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md).
|
||
|
|
||
|
## Motivation
|
||
|
|
||
|
Storing voluminous NFT metadata on-chain is often neither practical nor cost-efficient.
|
||
|
|
||
|
Storing NFT metadata off-chain on distributed file systems like IPFS can answer some needs of verifiable correlation and permanence between an NFT tokenId and its metadata but updates come at the cost of being all or nothing (aka changing the `tokenURI`). Bespoke solutions can be easily developed for a specific NFT smart contract but a common specification is necessary for NFT marketplaces and third parties tools to understand and verify these metadata updates.
|
||
|
|
||
|
This ERC allows the original JSON metadata to be modified step by step along a set of predefined JSON transformation formulas. Depending on NFT use-cases, the transformation formulas can be more or less restrictive.
|
||
|
|
||
|
As examples, an NFT representing a house could only allow append-only updates to the list of successive owners, and a game using NFT characters could let some attributes change from time to time (e.g. health, experience, level, etc) while some other would be guaranteed to never change (e.g. physicals traits etc).
|
||
|
|
||
|
This standard extension is compatible with NFTs bridged between Ethereum and L2 networks and allows efficient caching solutions.
|
||
|
|
||
|
## Specification
|
||
|
|
||
|
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
|
||
|
|
||
|
The **metadata updates extension** is OPTIONAL for [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md) contracts.
|
||
|
|
||
|
```solidity
|
||
|
/// @title ERC-721/ERC-1155 Updatable Metadata Extension
|
||
|
interface IERC5185UpdatableMetadata {
|
||
|
/// @notice A distinct Uniform Resource Identifier (URI) for a set of updates
|
||
|
/// @dev This event emits an URI (defined in RFC 3986) of a set of metadata updates.
|
||
|
/// The URI should point to a JSON file that conforms to the "NFT Metadata Updates JSON Schema"
|
||
|
/// Third-party platforms such as NFT marketplace can deterministically calculate the latest
|
||
|
/// metadata for all tokens using these events by applying them in sequence for each token.
|
||
|
event MetadataUpdates(string URI);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The original metadata SHOULD conform to the "ERC-5185 Updatable Metadata JSON Schema" which is a compatible extension of the "ERC-721 Metadata JSON Schema" defined in ERC-721.
|
||
|
|
||
|
"ERC-5185 Updatable Metadata JSON Schema" :
|
||
|
|
||
|
```json
|
||
|
{
|
||
|
"title": "Asset Updatable 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."
|
||
|
},
|
||
|
"updatable": {
|
||
|
"type": "object",
|
||
|
"required": ["engine", "recipes"],
|
||
|
"properties": {
|
||
|
"engine": {
|
||
|
"type": "string",
|
||
|
"description": "Non ambiguous transformation method/language (with version) to process updates along recipes defined below"
|
||
|
},
|
||
|
"schema": {
|
||
|
"type": "object",
|
||
|
"description": "if present, a JSON Schema that all sequential post transformation updated metadata need to conform. If a transformed JSON does not conform, the update should be considered voided."
|
||
|
},
|
||
|
"recipes": {
|
||
|
"type": "object",
|
||
|
"description": "A catalog of all possibles recipes identified by their keys",
|
||
|
"patternProperties": {
|
||
|
".*": {
|
||
|
"type": "object",
|
||
|
"description": "The key of this object is used to select which recipe to apply for each update",
|
||
|
"required": ["eval"],
|
||
|
"properties": {
|
||
|
"eval": {
|
||
|
"type": "string",
|
||
|
"description": "The evaluation formula to transform the last JSON metadata using the engine above (can take arguments)"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
"NFT Metadata Updates JSON Schema" :
|
||
|
|
||
|
```json
|
||
|
{
|
||
|
"title": "Metadata Updates JSON Schema",
|
||
|
"type": "object",
|
||
|
"properties": {
|
||
|
"updates": {
|
||
|
"type": "array",
|
||
|
"description": "A list of updates to apply sequentially to calculate updated metadata",
|
||
|
"items": { "$ref": "#/$defs/update" },
|
||
|
"$defs": {
|
||
|
"update": {
|
||
|
"type": "object",
|
||
|
"required": ["tokenId", "recipeKey"],
|
||
|
"properties": {
|
||
|
"tokenId": {
|
||
|
"type": "string",
|
||
|
"description": "The tokenId for which the update recipe should apply"
|
||
|
},
|
||
|
"recipeKey": {
|
||
|
"type": "string",
|
||
|
"description": "recipeKey to use to get the JSON transformation expression in current metadata"
|
||
|
},
|
||
|
"args": {
|
||
|
"type": "string",
|
||
|
"description": "arguments to pass to the JSON transformation"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Engines
|
||
|
|
||
|
Only one engine is currently defined in this extension proposal.
|
||
|
|
||
|
If the engine in the original metadata is "jsonata@1.8.*", updated metadata is calculated starting from original metadata and applying each update sequentially (all updates which are present in all the URIs emitted by the event `MetadataUpdates` for which tokenId matches).
|
||
|
|
||
|
For each step, the next metadata is obtained by the javascript calculation (or compatible jsonata implementation in other language) :
|
||
|
|
||
|
```js
|
||
|
const nextMetadata = jsonata(evalString).evaluate(previousMetadata, args)
|
||
|
```
|
||
|
|
||
|
With `evalString` is found with `recipeKey` in the original metadata recipes list.
|
||
|
|
||
|
If the key is not present in the original metadata list, `previousMetadata` is kept as the valid updated metadata.
|
||
|
|
||
|
If the evaluation throws any errors, `previousMetadata` is kept as the valid updated metadata.
|
||
|
|
||
|
If a validation Schema JSON has been defined and the result JSON `nextMetadata` does not conform, that update is not valid and `previousMetadata` is kept as the valid updated metadata.
|
||
|
|
||
|
## Rationale
|
||
|
|
||
|
There have been numerous interesting uses of [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md) smart contracts that associate for each token essential and significant metadata. While some projects (e.g. EtherOrcs) have experimented successfully to manage these metadata on-chain, that experimental solution will always be limited by the cost and speed of generating and storing JSON on-chain. Symmetrically, while storing the JSON metadata at URI endpoint controlled by traditional servers permit limitless updates the the metadata for each NFT, it is somehow defeating in many uses cases, the whole purpose of using a trustless blockchain to manage NFT: indeed users may want or demand more permanence and immutability from the metadata associated with their NFT.
|
||
|
|
||
|
Most use cases have chosen intermediate solutions like IPFS or arweave to provide some permanence or partial/full immutability of metadata. This is a good solution when an NFT represents a static asset whose characteristics are by nature permanent and immutable (like in the art world) but less so with other use cases like gaming or NFT representing a deed or title. Distinguishable assets in a game often should be allowed to evolve and change over time in a controlled way and titles need to record real life changes.
|
||
|
|
||
|
The advantages of this standard is precisely to allow these types of controlled transformations over time of each NFT metadata by applying sequential transformations starting with the original metadata and using formulas themselves defined in the original metadata.
|
||
|
|
||
|
The original metadata for a given NFT is always defined as the JSON pointed by the result of `tokenURI` for [EIP-721](./eip-721.md) and function `uri` for [EIP-1155](./eip-1155.md).
|
||
|
|
||
|
The on-chain log trace of updates guarantee that anyone can recalculate and verify independently the current updated metadata starting from the original metadata. The fact that the calculation is deterministic allows easy caching of intermediate transformations and the efficient processing of new updates using these caches.
|
||
|
|
||
|
The number of updates defined by each event is to be determined by the smart contract logic and use case, but it can easily scale to thousands or millions of updates per event. The function(s) that should emit `MetadataUpdates` and the frequency of these on-chain updates is left at the discretion of this standard implementation.
|
||
|
|
||
|
The proposal is extremely gas efficient, since gas costs are only proportional to the frequency of committing changes. Many changes for many tokens can be batched in one transaction for the cost of only one `emit`.
|
||
|
|
||
|
## Reference Implementation
|
||
|
|
||
|
### Transformation engines
|
||
|
|
||
|
We have been experimenting with this generic Metadata update proposal using the JSONata transformation language.
|
||
|
|
||
|
Here is a very simple example of a NFT metadata for an imaginary 'little monster' game :
|
||
|
|
||
|
```json
|
||
|
{
|
||
|
"name": "Monster 1",
|
||
|
"description": "Little monsters you can play with.",
|
||
|
"attributes": [
|
||
|
{ "trait_type": "Level", "value": 0 },
|
||
|
{ "trait_type": "Stamina", "value": 100 }
|
||
|
],
|
||
|
"updatable": {
|
||
|
"engine": "jsonata@1.8.*",
|
||
|
"recipes": {
|
||
|
"levelUp": {
|
||
|
"eval": "$ ~> | attributes[trait_type='Level'] | {'value': value + 1} |"
|
||
|
},
|
||
|
"updateDescription": {
|
||
|
"eval": "$ ~> | $ | {'description': $newDescription} |"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
This updatable metadata can only be updated to increment by one the trait attribute "Level".
|
||
|
|
||
|
An example JSON updates metadata would be :
|
||
|
```json
|
||
|
{
|
||
|
"updates": [
|
||
|
{"tokenId":"1","action":"levelUp"},
|
||
|
{"tokenId":"2","action":"levelUp"},
|
||
|
{"tokenId":"1","action":"updateDescription","args":{"newDescription":"Now I'm a big monster"}},
|
||
|
{"tokenId":"1","action":"levelUp"},
|
||
|
{"tokenId":"3","action":"levelUp"}
|
||
|
]
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Security Considerations
|
||
|
|
||
|
A malicious recipe in the original metadata might be constructed as a DDoS vector for third parties marketplaces and tools that calculate NFT updated JSON metadata. They are encouraged to properly encapsulate software in charge of these calculations and put limits for the engine updates processing.
|
||
|
|
||
|
Smart contracts should be careful and conscious of using this extension and still allow the metadata URI to be updated in some contexts (by not having the same URI returned by `tokenURI` or `uri` for a given tokenId over time). They need to take into account if previous changes could have been already broadcasted for that NFT by the contract, if these changes are compatible with the new "original metadata" and what semantic they decide to associate by combining these two kinds of "updates".
|
||
|
|
||
|
## Backwards Compatibility
|
||
|
|
||
|
The proposal is fully compatible with both [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md). Third-party applications that don't support this EIP will still be able to use the original metadata for each NFT.
|
||
|
|
||
|
## Copyright
|
||
|
Copyright and related rights waived via [CC0](../LICENSE.md).
|