Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-5185: NFT 可更新元数据扩展

用于 ERC-721/ERC-1155 控制的元数据更新的接口扩展

Authors Christophe Le Bars (@clbrge)
Created 2022-06-27
Discussion Link https://ethereum-magicians.org/t/erc-721-erc-1155-updatable-metadata-extension/9077
Requires EIP-721, EIP-1155

摘要

本规范定义了一种标准方法,允许根据预定义的公式控制 NFT 的元数据更新。原始元数据的更新受到一组配方的限制和定义,并且这些配方的顺序和结果是确定性的,并且可以通过链上元数据更新事件完全验证。该提案依赖于并扩展了 EIP-721EIP-1155

动机

在链上存储大量的 NFT 元数据通常既不实用也不 экономичный。

将 NFT 元数据离线存储在像 IPFS 这样的分布式文件系统上可以满足 NFT tokenId 和其元数据之间可验证的相关性和持久性的一些需求,但是更新的代价是全有或全无(也就是更改 tokenURI)。可以很容易地为特定的 NFT 智能合约开发定制的解决方案,但是对于 NFT 市场和第三方工具来说,理解和验证这些元数据更新需要一个通用的规范。

此 ERC 允许原始 JSON 元数据按照一组预定义的 JSON 转换公式逐步修改。根据 NFT 用例,转换公式可以或多或少地具有限制性。

例如,代表房屋的 NFT 可能只允许对连续所有者的列表进行仅追加更新,而使用 NFT 角色的游戏可以允许某些属性不时更改(例如,健康、经验、等级等),而其他一些属性则保证永远不会改变(例如,物理特征等)。

这个标准扩展与在 Ethereum 和 L2 网络之间桥接的 NFT 兼容,并且允许高效的缓存解决方案。

规范

本文档中的关键词“必须”、“不得”、“必需”、“应”、“不应”、“应该”、“不应该”、“推荐”和“可选”应按照 RFC 2119 中的描述进行解释。

元数据更新扩展 对于 EIP-721EIP-1155 合约是可选的。

/// @title ERC-721/ERC-1155 可更新元数据扩展
interface IERC5185UpdatableMetadata {
    /// @notice 一组更新的不同的统一资源标识符 (URI)
    /// @dev 此事件发出一个 URI(在 RFC 3986 中定义)的元数据更新集。
    /// URI 应该指向符合 "NFT 元数据更新 JSON 模式" 的 JSON 文件
    /// 通过按顺序将这些事件应用于每个代币,第三方平台 (例如 NFT 市场)可以确定性地计算所有代币的最新元数据。
    event MetadataUpdates(string URI);
}

原始元数据应符合 “ERC-5185 可更新元数据 JSON 模式”,它是 ERC-721 中定义的 “ERC-721 元数据 JSON 模式” 的兼容扩展。

“ERC-5185 可更新元数据 JSON 模式”:

{
    "title": "资产可更新元数据",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "标识此 NFT 代表的资产"
        },
        "description": {
            "type": "string",
            "description": "描述此 NFT 代表的资产"
        },
        "image": {
            "type": "string",
            "description": "指向 mime 类型为 image/* 的资源的 URI,代表此 NFT 代表的资产。 考虑使任何图像的宽度在 320 到 1080 像素之间,宽高比在 1.91:1 到 4:5 之间(包括 1.91:1 和 4:5)。"
        },
        "updatable": {
            "type": "object",
            "required": ["engine", "recipes"],
            "properties": {
                "engine": {
                    "type": "string",
                    "description": "非模糊的转换方法/语言(带版本)以沿着下面定义的配方处理更新"
                },
                "schema": {
                    "type": "object",
                    "description": "如果存在,则是一个 JSON 模式,所有顺序的后转换更新的元数据都需要符合。 如果转换后的 JSON 不符合,则该更新应被视为空。"
                },
                "recipes": {
                    "type": "object",
                    "description": "由它们的键标识的所有可能的配方的目录",
                    "patternProperties": {
                        ".*": {
                            "type": "object",
                            "description": "此对象的键用于选择为每个更新应用哪个配方",
                            "required": ["eval"],
                            "properties": {
                                "eval": {
                                    "type": "string",
                                    "description": "使用上面的引擎转换最后一个 JSON 元数据的评估公式(可以接受参数)"
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

“NFT 元数据更新 JSON 模式”:

{
    "title": "元数据更新 JSON 模式",
    "type": "object",
    "properties": {
        "updates": {
            "type": "array",
            "description": "按顺序应用以计算更新元数据的更新列表",
            "items": { "$ref": "#/$defs/update" },
            "$defs": {
                "update": {
                    "type": "object",
                    "required": ["tokenId", "recipeKey"],
                    "properties": {
                        "tokenId": {
                            "type": "string",
                            "description": "应该应用更新配方的 tokenId"
                         },
                        "recipeKey": {
                            "type": "string",
                            "description": "用于在当前元数据中获取 JSON 转换表达式的 recipeKey"
                        },
                        "args": {
                            "type": "string",
                            "description": "传递给 JSON 转换的参数"
                        }
                    }
                 }
            }
        }
    }
}

引擎

此扩展提案中当前仅定义了一个引擎。

如果原始元数据中的引擎是 “jsonata@1.8.*“,则更新的元数据从原始元数据开始计算,并按顺序应用每个更新(tokenId 匹配的 MetadataUpdates 事件发出的所有 URI 中存在的所有更新)。

对于每个步骤,下一个元数据通过 javascript 计算获得(或其他语言的兼容的 jsonata 实现):

const nextMetadata = jsonata(evalString).evaluate(previousMetadata, args)

其中 evalString 在原始元数据配方列表中使用 recipeKey 找到。

如果该键在原始元数据列表中不存在,则 previousMetadata 将保留为有效的更新元数据。

如果评估抛出任何错误,则 previousMetadata 将保留为有效的更新元数据。

如果已定义验证模式 JSON,并且结果 JSON nextMetadata 不符合,则该更新无效,并且 previousMetadata 将保留为有效的更新元数据。

理由

已经有许多有趣的 EIP-721EIP-1155 智能合约的用例,这些合约将每个代币的基本元数据联系起来。虽然一些项目(例如 EtherOrcs)已经成功地尝试在链上管理这些元数据,但这种实验性解决方案将始终受到在链上生成和存储 JSON 的成本和速度的限制。对称的是,虽然将 JSON 元数据存储在由传统服务器控制的 URI 端点允许对每个 NFT 的元数据进行无限更新,但在许多用例中,这在某种程度上违背了使用无需信任的区块链来管理 NFT 的全部目的:实际上,用户可能想要或需要与其 NFT 关联的元数据具有更高的持久性和不变性。

大多数用例都选择了像 IPFS 或 arweave 这样的中间解决方案,以提供一些元数据的持久性或部分/完全不变性。当 NFT 代表一种本质上是永久和不变的静态资产(就像在艺术界一样)时,这是一个很好的解决方案,但在其他用例中,如游戏或代表契约或所有权的 NFT 中,则不那么好。游戏中的可区分资产通常应该被允许以受控的方式随时间推移而进化和改变,而所有权需要记录现实生活中的变化。

这个标准的优点恰恰在于允许通过从原始元数据开始应用顺序转换,并使用原始元数据本身中定义的公式,对每个 NFT 元数据随时间进行这些类型的控制转换。

给定 NFT 的原始元数据始终被定义为 EIP-721tokenURIEIP-1155 的函数 uri 的结果指向的 JSON。

链上更新日志跟踪保证任何人都可以从原始元数据开始独立地重新计算和验证当前更新的元数据。计算是确定性的这一事实允许轻松缓存中间转换和使用这些缓存有效处理新更新。

每个事件定义的更新数量由智能合约逻辑和用例决定,但它可以很容易地扩展到每个事件数千或数百万个更新。应发出 MetadataUpdates 的函数以及这些链上更新的频率由该标准的实现自行决定。

该提案非常节省 gas,因为 gas 成本仅与提交更改的频率成正比。许多代币的许多更改可以批量处理到一个事务中,只需一个 emit 的成本。

参考实现

转换引擎

我们一直在使用 JSONata 转换语言试验这个通用的元数据更新提案。

这是一个非常简单的 NFT 元数据示例,用于一个虚构的 “小怪物” 游戏:

{
    "name": "怪物 1",
    "description": "你可以玩的小怪物。",
    "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} |"
        }
      }
    }
}

此可更新的元数据只能更新为将 trait 属性“Level”增加 1。

一个 JSON 更新元数据的示例是:

{
    "updates": [
      {"tokenId":"1","action":"levelUp"},
      {"tokenId":"2","action":"levelUp"},
      {"tokenId":"1","action":"updateDescription","args":{"newDescription":"现在我是一个大怪物"}},
      {"tokenId":"1","action":"levelUp"},
      {"tokenId":"3","action":"levelUp"}
    ]
}

安全考虑

原始元数据中的恶意配方可能会被构建为第三方市场和计算 NFT 更新的 JSON 元数据的工具的 DDoS 向量。 鼓励他们正确地封装负责这些计算的软件,并限制引擎更新处理。

智能合约应该小心并有意识地使用此扩展,并且仍然允许在某些情况下更新元数据 URI(通过不让 tokenURIuri 随着时间的推移为给定的 tokenId 返回相同的 URI)。 他们需要考虑到以前的更改是否已经被合约广播到该 NFT,如果这些更改与新的“原始元数据”兼容,以及他们决定通过组合这两种“更新”来关联什么语义。

向后兼容性

该提案与 EIP-721EIP-1155 完全兼容。 不支持此 EIP 的第三方应用程序仍然可以使用每个 NFT 的原始元数据。

版权

通过 CC0 放弃版权及相关权利。

Citation

Please cite this document as:

Christophe Le Bars (@clbrge), "ERC-5185: NFT 可更新元数据扩展 [DRAFT]," Ethereum Improvement Proposals, no. 5185, June 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5185.