Alert Source Discuss
⚠️ Review Standards Track: ERC

ERC-4973: 账户绑定 Token

一种不可转让的 NFT 接口,它绑定到以太坊账户,就像传说中的《魔兽世界》物品绑定到角色一样。

Authors Tim Daubenschütz (@TimDaub)
Created 2022-04-01
Requires EIP-165, EIP-712, EIP-721, EIP-1271

摘要

提出了一个在智能合约中使用的账户绑定 Token (ABT) 的标准 API。ABT 是一种绑定到单个账户的非同质化 Token。ABT 不实现用于转移的标准接口。此 EIP 定义了铸造、分配、撤销和跟踪 ABT 的基本功能。

动机

在流行的 MMORPG 《魔兽世界》中,其游戏设计师故意将一些物品从世界的拍卖行市场系统中移除,以防止它们具有公开发现的价格并限制其可访问性。

Vanilla WoW 的“雷霆之怒,逐风者的祝福之剑”就是这样一件传奇物品,它需要一个四十人的团队,以及其他子任务,来杀死火焰领主“拉格纳罗斯”以获得“火焰领主精华”,这是制作这把剑所需的材料。

在自愿拾取后,这把剑会永久地绑定到角色的“灵魂”,从而无法在玩家的角色之间交易、出售甚至交换它。

换句话说,“雷霆之怒”的价格是与朋友和公会成员完成困难任务线的所有社会成本的总和。其他玩家发现雷霆之怒可以肯定它的主人已经杀死了炙热的火焰领主“拉格纳罗斯”。

《魔兽世界》玩家可以丢弃传奇和灵魂绑定的物品,如雷霆之怒,以将其从他们的账户中永久删除。他们可以选择是否装备卸下物品,从而向所有人展示他们的成就。

以太坊社区已经表达了对类似于《魔兽世界》灵魂绑定物品的不可转让、非同质化和具有社会定价的 Token 的需求。流行的合约今天隐式地实现了账户绑定的交互权限。有原则的标准化有助于互操作性并改善链上数据索引。

本文档的目的是通过围绕最大程度向后兼容但其他方面最小的接口定义达成共识,使 ABT 在以太坊上成为现实。

规范

Solidity 接口

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

ABT 必须 实现以下接口:

  • ERC-165ERC165 (0x01ffc9a7)
  • ERC-721ERC721Metadata (0x5b5e139f)

ABT 不得 实现以下接口:

ABT 接收者必须始终能够调用 function unequip(address _tokenId) 以将其 ABT 移除链下。

// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.6;

/// @title 账户绑定 Token
/// @dev 请参阅 https://eips.ethereum.org/EIPS/eip-4973
/// 注意:此接口的 ERC-165 标识符为 0xeb72bb7c
interface IERC4973 {
  /// @dev 当任何 ABT 的所有权通过任何机制发生变化时,都会发出此事件。
  ///  当 ABT 被给予或装备和卸下时,会发出此事件
  ///  (`to` == 0)。
  event Transfer(
    address indexed from, address indexed to, uint256 indexed tokenId
  );

  /// @notice 计算分配给所有者的所有 ABT
  /// @dev 分配给零地址的 ABT 被认为是无效的,并且此
  ///  函数会抛出关于零地址的查询。
  /// @param owner 要查询余额的地址
  /// @return `address owner` 拥有的 ABT 数量,可能为零
  function balanceOf(address owner) external view returns (uint256);

  /// @notice 查找绑定到 ERC4973 账户绑定 Token 的地址
  /// @dev 分配给零地址的 ABT 被认为是无效的,并且关于它们的查询
  ///  会抛出异常。
  /// @param tokenId ABT 的标识符。
  /// @return 绑定到 ABT 的所有者的地址。
  function ownerOf(uint256 tokenId) external view returns (address);

  /// @notice 从账户中移除 `uint256 tokenId`。在任何时候,一个
  ///  ABT 接收者必须能够通过调用此函数公开地将自己与 ABT 解除关联。
  ///  在成功执行此函数后,给定调用 `function give` 或
  ///  `function take` 的参数,Token 必须是可重新装备的。
  /// @dev 必须发出一个 `event Transfer`,其中 `address to` 字段指向
  ///  零地址。
  /// @param tokenId ABT 的标识符。
  function unequip(uint256 tokenId) external;

  /// @notice 创建 ABT 并将 ABT 的所有权从
  ///  交易的 `msg.sender` 转移到 `address to`。
  /// @dev 除非 `bytes signature` 表示
  //   EIP-712 结构化数据哈希的签名
  ///  `Agreement(address active,address passive,bytes metadata)` 表达
  ///  `address to` 明确同意公开地与 `msg.sender` 和 `bytes metadata` 相关联,否则将抛出异常。独特的 `uint256 tokenId` 必须是
  ///  通过将 `bytes32` EIP-712 结构化数据哈希类型转换为一个
  ///  `uint256` 来生成。如果 `bytes signature` 为空或 `address to` 是一个合约,
  ///  必须对 `address to` 进行与 EIP-1271 兼容的 `function isValidSignatureNow(...)` 调用。成功的执行必须导致
  ///  `event Transfer(msg.sender, to, tokenId)` 的发出。一旦 ABT 作为
  ///  合约中的 `uint256 tokenId` 存在,`function give(...)` 必须抛出异常。
  /// @param to ABT 的接收者。
  /// @param metadata 将与 ABT 关联的元数据。
  /// @param signature EIP-712 结构化数据哈希的签名
  ///  `Agreement(address active,address passive,bytes metadata)` 由
  ///  `address to` 签名。
  /// @return 通过将 `bytes32`
  ///  EIP-712 结构化数据哈希类型转换为 `uint256` 生成的唯一 `uint256 tokenId`。
  function give(address to, bytes calldata metadata, bytes calldata signature)
    external
    returns (uint256);

  /// @notice 创建 ABT 并将 ABT 的所有权从一个
  /// `address from` 转移到交易的 `msg.sender`。
  /// @dev 除非 `bytes signature` 表示
  ///  EIP-712 结构化数据哈希的签名
  ///  `Agreement(address active,address passive,bytes metadata)` 表达
  ///  `address from` 明确同意公开地与 `msg.sender` 和 `bytes metadata` 相关联,否则将抛出异常。独特的一个 `uint256 tokenId` 必须是
  ///  通过将 `bytes32` EIP-712 结构化数据哈希类型转换为一个
  ///  `uint256` 来生成。如果 `bytes signature` 为空或 `address from` 是一个合约,
  ///  必须对 `address from` 进行与 EIP-1271 兼容的 `function isValidSignatureNow(...)` 调用。成功的执行必须导致
  ///  `event Transfer(from, msg.sender, tokenId)` 的发出。一旦 ABT
  ///  作为合约中的 `uint256 tokenId` 存在,`function take(...)` 必须
  ///  抛出异常。
  /// @param from ABT 的来源。
  /// @param metadata 将与 ABT 关联的元数据。
  /// @param signature EIP-712 结构化数据哈希的签名
  ///  `Agreement(address active,address passive,bytes metadata)` 由
  ///  `address from` 签名。

  /// @return 通过将 `bytes32`
  ///  EIP-712 结构化数据哈希类型转换为 `uint256` 生成的唯一 `uint256 tokenId`。
  function take(address from, bytes calldata metadata, bytes calldata signature)
    external
    returns (uint256);

  /// @notice 将 ABT 的不透明元数据字节串解码为 Token
  ///  URI,一旦它在链上创建,它将与之关联。
  /// @param metadata 将与 ABT 关联的元数据。
  /// @return 表示元数据的 URI。
  function decodeURI(bytes calldata metadata) external returns (string memory);
}

有关其元数据 JSON 模式的定义,请参见 ERC-721

EIP-712 类型化结构化数据哈希和字节数组签名创建

要调用 function give(...)function take(...),必须使用 EIP-712 创建一个字节数组签名。在 Node.js 中经过测试的参考实现附加在 index.mjs, index_test.mjspackage.json。在 Solidity 中,可以如下创建此字节数组签名:

bytes32 r = 0x68a020a209d3d56c46f38cc50a33f704f4a9a10a59377f8dd762ac66910e9b90;
bytes32 s = 0x7e865ad05c4035ab5792787d4a0297a43617ae897930a6fe4d822b8faea52064;
uint8 v   = 27;
bytes memory signature = abi.encodePacked(r, s, v);

原理

接口

ABT 应最大程度地向后兼容,但仍然只公开一个最小且易于实现的接口定义。

由于 ERC-721 Token 已被钱包提供商和市场广泛采用,因此使用其具有 ERC-165ERC721Metadata 接口进行功能检测可能允许实现者开箱即用地支持 ABT。

如果 ERC-721 的实现者正确构建了 ERC-165function supportsInterface(bytes4 interfaceID) 函数,通过识别出 ERC-721 的具有标识符 0x80ac58cd 的跟踪和转移接口组件未被实现,Token 的转移不应被建议作为用户界面选项。

不过,由于 ABT 支持 ERC-721ERC721Metadata 扩展,钱包和市场应显示账户绑定 Token,而无需进行任何更改。

尽管其他账户绑定 Token 的实现是可能的,例如,通过使所有转移函数还原,但 ABT 更优越,因为它支持通过 ERC-165 进行的功能检测。

我们公开 function unequip(address _tokenId) 并要求 ABT 的所有者随时调用它,因为它确保了所有者公开地将自己与已向其账户发布的内容解除关联的权利。

异常处理

鉴于 ABT 的账户之间不可转移的特性,如果用户账户或合约的密钥泄露或轮换,用户可能会失去将其自身与 Token 相关联的能力。在某些情况下,这可能是期望的效果。因此,ABT 实现者应构建重新发布和撤销流程以启用追索权。我们建议实施严格去中心化、无需许可和抗审查的重新发布流程。

但是,当用户密钥泄露或轮换时,本文档有意避免提供标准化形式的异常处理。

如果实现者希望使账户绑定 Token 在不同的账户之间共享,例如,为了避免在密钥被盗时失去访问权限,我们建议将账户绑定 Token 发布到实现多重签名功能的合约账户。

出处索引

可以通过跟踪 event Transfer(address indexed from, address indexed to, uint256 indexed tokenId) 的发出来索引 ABT。与 ERC-721 一样,两个账户之间的转移由 address fromaddress to 为非零地址表示。卸载 Token 通过发出一个 address to 设置为零地址的转移来表示。address from 设置为零的 Mint 操作不存在。为了避免被恶意实现的 event Transfer 公布合约欺骗,索引器应确保交易的发送者等于 event Transferfrom 值。

向后兼容性

我们有意采用 ERC-165ERC721Metadata 函数来创建与 ERC-721 的高度向后兼容性。我们特意使用了 ERC-721 术语,如 function ownerOf(...)function balanceOf(...),以最大程度地减少已经熟悉(例如)ERC-20ERC-721 的 ABT 实现者的熟悉工作。对于索引器,我们重新使用了广泛实现的 event Transfer 事件签名。

参考实现

您可以在 ERC-4973-flat.sol 中找到此标准的实现。

安全考虑

没有与此标准的实现直接相关的安全考虑。

版权

CC0 下放弃版权及相关权利。

Citation

Please cite this document as:

Tim Daubenschütz (@TimDaub), "ERC-4973: 账户绑定 Token [DRAFT]," Ethereum Improvement Proposals, no. 4973, April 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4973.